Skip to content

Commit 4ed01cb

Browse files
razor-xseambot
andauthored
feat: Add resource samples (#183)
* Rename code-sample module to samples * Move some types to syntax.ts * Rename syntax types * Add createResourceSample * Add resourceSamples and resourceSampleDefinitions to Blueprint * Generate resource samples * ci: Format code * ci: Generate code * Add test for resource samples * Update resource-sample-definitions.ts * ci: Generate code * Add missing continue * Fix continue * ci: Generate code --------- Co-authored-by: Seam Bot <seambot@getseam.com>
1 parent 9d08ecb commit 4ed01cb

24 files changed

+613
-219
lines changed

src/lib/blueprint.ts

Lines changed: 146 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
import { z } from 'zod'
22

3-
import {
4-
type CodeSample,
5-
CodeSampleDefinitionSchema,
6-
createCodeSample,
7-
} from './code-sample/index.js'
8-
import type {
9-
CodeSampleDefinition,
10-
CodeSampleSyntax,
11-
} from './code-sample/schema.js'
123
import { findCommonOpenapiSchemaProperties } from './openapi/find-common-openapi-schema-properties.js'
134
import { flattenOpenapiSchema } from './openapi/flatten-openapi-schema.js'
145
import {
@@ -26,6 +17,17 @@ import type {
2617
OpenapiPaths,
2718
OpenapiSchema,
2819
} from './openapi/types.js'
20+
import {
21+
type CodeSample,
22+
type CodeSampleDefinition,
23+
CodeSampleDefinitionSchema,
24+
createCodeSample,
25+
createResourceSample,
26+
type ResourceSample,
27+
type ResourceSampleDefinition,
28+
ResourceSampleDefinitionSchema,
29+
type SyntaxName,
30+
} from './samples/index.js'
2931
import {
3032
mapOpenapiToSeamAuthMethod,
3133
type SeamAuthMethod,
@@ -66,6 +68,7 @@ export interface Resource {
6668
isDraft: boolean
6769
draftMessage: string
6870
propertyGroups: Record<string, PropertyGroup>
71+
resourceSamples: ResourceSample[]
6972
}
7073

7174
interface PropertyGroup {
@@ -413,11 +416,15 @@ export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
413416

414417
interface Context extends Required<BlueprintOptions> {
415418
codeSampleDefinitions: CodeSampleDefinition[]
419+
resourceSampleDefinitions: ResourceSampleDefinition[]
416420
validActionAttemptTypes: string[]
417421
}
418422

419423
export const TypesModuleSchema = z.object({
420424
codeSampleDefinitions: z.array(CodeSampleDefinitionSchema).default([]),
425+
resourceSampleDefinitions: z
426+
.array(ResourceSampleDefinitionSchema)
427+
.default([]),
421428
// TODO: Import and use openapi zod schema here
422429
openapi: z.any(),
423430
})
@@ -427,14 +434,15 @@ export type TypesModuleInput = z.input<typeof TypesModuleSchema>
427434
export type TypesModule = z.output<typeof TypesModuleSchema>
428435

429436
export interface BlueprintOptions {
430-
formatCode?: (content: string, syntax: CodeSampleSyntax) => Promise<string>
437+
formatCode?: (content: string, syntax: SyntaxName) => Promise<string>
431438
}
432439

433440
export const createBlueprint = async (
434441
typesModule: TypesModuleInput,
435442
{ formatCode = async (content) => content }: BlueprintOptions = {},
436443
): Promise<Blueprint> => {
437-
const { codeSampleDefinitions } = TypesModuleSchema.parse(typesModule)
444+
const { codeSampleDefinitions, resourceSampleDefinitions } =
445+
TypesModuleSchema.parse(typesModule)
438446

439447
// TODO: Move openapi to TypesModuleSchema
440448
const openapi = typesModule.openapi as Openapi
@@ -445,6 +453,7 @@ export const createBlueprint = async (
445453

446454
const context: Context = {
447455
codeSampleDefinitions,
456+
resourceSampleDefinitions,
448457
formatCode,
449458
validActionAttemptTypes,
450459
}
@@ -458,18 +467,19 @@ export const createBlueprint = async (
458467
),
459468
)
460469

461-
const resources = createResources(openapiSchemas, routes)
462-
const actionAttempts = createActionAttempts(
470+
const resources = await createResources(openapiSchemas, routes, context)
471+
const actionAttempts = await createActionAttempts(
463472
openapi.components.schemas,
464473
routes,
474+
context,
465475
)
466476

467477
return {
468478
title: openapi.info.title,
469479
routes,
470480
resources,
471481
pagination: createPagination(pagination),
472-
events: createEvents(openapiSchemas, resources, routes),
482+
events: await createEvents(openapiSchemas, resources, routes, context),
473483
actionAttempts,
474484
}
475485
}
@@ -1074,59 +1084,66 @@ const createPagination = (
10741084
}
10751085
}
10761086

1077-
export const createResources = (
1087+
export const createResources = async (
10781088
schemas: Openapi['components']['schemas'],
10791089
routes: Route[],
1080-
): Record<string, Resource> => {
1081-
return Object.entries(schemas).reduce<Record<string, Resource>>(
1082-
(resources, [schemaName, schema]) => {
1083-
const { success: isValidEventSchema, data: parsedEvent } =
1084-
EventResourceSchema.safeParse(schema)
1085-
if (isValidEventSchema) {
1086-
const commonProperties = findCommonOpenapiSchemaProperties(
1087-
parsedEvent.oneOf,
1088-
)
1089-
const eventSchema: OpenapiSchema = {
1090-
'x-route-path': parsedEvent['x-route-path'],
1091-
properties: commonProperties,
1092-
type: 'object',
1093-
}
1094-
return {
1095-
...resources,
1096-
[schemaName]: createResource(schemaName, eventSchema, routes),
1097-
}
1090+
context: Context,
1091+
): Promise<Record<string, Resource>> => {
1092+
const resources: Record<string, Resource> = {}
1093+
for (const [schemaName, schema] of Object.entries(schemas)) {
1094+
const { success: isValidEventSchema, data: parsedEvent } =
1095+
EventResourceSchema.safeParse(schema)
1096+
1097+
if (isValidEventSchema) {
1098+
const commonProperties = findCommonOpenapiSchemaProperties(
1099+
parsedEvent.oneOf,
1100+
)
1101+
const eventSchema: OpenapiSchema = {
1102+
'x-route-path': parsedEvent['x-route-path'],
1103+
properties: commonProperties,
1104+
type: 'object',
10981105
}
1106+
resources[schemaName] = await createResource(
1107+
schemaName,
1108+
eventSchema,
1109+
routes,
1110+
context,
1111+
)
1112+
continue
1113+
}
10991114

1100-
const { success: isValidResourceSchema } =
1101-
ResourceSchema.safeParse(schema)
1102-
if (isValidResourceSchema) {
1103-
return {
1104-
...resources,
1105-
[schemaName]: createResource(schemaName, schema, routes),
1106-
}
1107-
}
1115+
const { success: isValidResourceSchema } = ResourceSchema.safeParse(schema)
1116+
if (isValidResourceSchema) {
1117+
resources[schemaName] = await createResource(
1118+
schemaName,
1119+
schema,
1120+
routes,
1121+
context,
1122+
)
1123+
continue
1124+
}
1125+
}
11081126

1109-
return resources
1110-
},
1111-
{},
1112-
)
1127+
return resources
11131128
}
11141129

1115-
const createResource = (
1130+
const createResource = async (
11161131
schemaName: string,
11171132
schema: OpenapiSchema,
11181133
routes: Route[],
1119-
): Resource => {
1134+
context: Context,
1135+
): Promise<Resource> => {
11201136
const routePath = validateRoutePath(
11211137
schemaName,
11221138
schema['x-route-path'],
11231139
routes,
11241140
)
11251141

11261142
const propertyGroups = getPropertyGroupsForResource(schema)
1143+
const resourceType = schemaName
11271144

1128-
return {
1129-
resourceType: schemaName,
1145+
const resource: Omit<Resource, 'resourceSamples'> = {
1146+
resourceType,
11301147
properties: createProperties(
11311148
schema.properties ?? {},
11321149
[schemaName],
@@ -1142,6 +1159,23 @@ const createResource = (
11421159
draftMessage: schema['x-draft'] ?? '',
11431160
propertyGroups,
11441161
}
1162+
1163+
return {
1164+
...resource,
1165+
resourceSamples: await Promise.all(
1166+
context.resourceSampleDefinitions
1167+
.filter(
1168+
(resourceSample) => resourceSample.resource_type === resourceType,
1169+
)
1170+
.map(
1171+
async (resourceSampleDefinition) =>
1172+
await createResourceSample(resourceSampleDefinition, {
1173+
resource,
1174+
formatCode: context.formatCode,
1175+
}),
1176+
),
1177+
),
1178+
}
11451179
}
11461180

11471181
const validateRoutePath = (
@@ -1620,11 +1654,12 @@ export const getPreferredMethod = (
16201654
return semanticMethod
16211655
}
16221656

1623-
const createEvents = (
1657+
const createEvents = async (
16241658
schemas: Openapi['components']['schemas'],
16251659
resources: Record<string, Resource>,
16261660
routes: Route[],
1627-
): EventResource[] => {
1661+
context: Context,
1662+
): Promise<EventResource[]> => {
16281663
const eventSchema = schemas['event']
16291664
if (
16301665
eventSchema == null ||
@@ -1635,8 +1670,8 @@ const createEvents = (
16351670
return []
16361671
}
16371672

1638-
return eventSchema.oneOf
1639-
.map((schema) => {
1673+
const events = await Promise.all(
1674+
eventSchema.oneOf.map(async (schema) => {
16401675
if (
16411676
typeof schema !== 'object' ||
16421677
schema.properties?.['event_type']?.enum?.[0] == null
@@ -1650,18 +1685,21 @@ const createEvents = (
16501685
)
16511686

16521687
return {
1653-
...createResource('event', schema, routes),
1688+
...(await createResource('event', schema, routes, context)),
16541689
eventType,
16551690
targetResourceType: targetResourceType ?? null,
16561691
}
1657-
})
1658-
.filter((event): event is EventResource => event !== null)
1692+
}),
1693+
)
1694+
1695+
return events.filter((event): event is EventResource => event !== null)
16591696
}
16601697

1661-
const createActionAttempts = (
1698+
const createActionAttempts = async (
16621699
schemas: Openapi['components']['schemas'],
16631700
routes: Route[],
1664-
): ActionAttempt[] => {
1701+
context: Context,
1702+
): Promise<ActionAttempt[]> => {
16651703
const actionAttemptSchema = schemas['action_attempt']
16661704
if (
16671705
actionAttemptSchema == null ||
@@ -1692,62 +1730,65 @@ const createActionAttempts = (
16921730
}
16931731
}
16941732

1695-
return Array.from(schemasByActionType.entries()).map(
1696-
([actionType, schemas]) => {
1697-
const mergedProperties: Record<string, OpenapiSchema> = {}
1698-
1699-
const allPropertyKeys = new Set<string>()
1700-
for (const schema of schemas) {
1701-
if (schema.properties != null) {
1702-
Object.keys(schema.properties).forEach((key) =>
1703-
allPropertyKeys.add(key),
1704-
)
1733+
return await Promise.all(
1734+
Array.from(schemasByActionType.entries()).map(
1735+
async ([actionType, schemas]) => {
1736+
const mergedProperties: Record<string, OpenapiSchema> = {}
1737+
1738+
const allPropertyKeys = new Set<string>()
1739+
for (const schema of schemas) {
1740+
if (schema.properties != null) {
1741+
Object.keys(schema.properties).forEach((key) =>
1742+
allPropertyKeys.add(key),
1743+
)
1744+
}
17051745
}
1706-
}
17071746

1708-
for (const propKey of allPropertyKeys) {
1709-
const propDefinitions = schemas
1710-
.filter((schema) => schema.properties?.[propKey] != null)
1711-
.map((schema) => schema.properties?.[propKey])
1747+
for (const propKey of allPropertyKeys) {
1748+
const propDefinitions = schemas
1749+
.filter((schema) => schema.properties?.[propKey] != null)
1750+
.map((schema) => schema.properties?.[propKey])
17121751

1713-
if (propDefinitions.length === 0) continue
1752+
if (propDefinitions.length === 0) continue
17141753

1715-
const nonNullableDefinition = propDefinitions.find((prop) => {
1716-
if (prop == null) return false
1754+
const nonNullableDefinition = propDefinitions.find((prop) => {
1755+
if (prop == null) return false
17171756

1718-
return !('nullable' in prop && prop.nullable === true)
1719-
})
1757+
return !('nullable' in prop && prop.nullable === true)
1758+
})
17201759

1721-
mergedProperties[propKey] =
1722-
nonNullableDefinition ?? propDefinitions[0] ?? {}
1723-
}
1760+
mergedProperties[propKey] =
1761+
nonNullableDefinition ?? propDefinitions[0] ?? {}
1762+
}
17241763

1725-
// Ensure standard status field
1726-
mergedProperties['status'] = {
1727-
...mergedProperties['status'],
1728-
type: 'string',
1729-
enum: ['success', 'pending', 'error'],
1730-
}
1764+
// Ensure standard status field
1765+
mergedProperties['status'] = {
1766+
...mergedProperties['status'],
1767+
type: 'string',
1768+
enum: ['success', 'pending', 'error'],
1769+
}
17311770

1732-
const schemaWithMergedProperties: OpenapiSchema = {
1733-
...(actionAttemptSchema['x-route-path'] != null && {
1734-
'x-route-path': actionAttemptSchema['x-route-path'],
1735-
}),
1736-
...schemas[0],
1737-
properties: mergedProperties,
1738-
}
1771+
const schemaWithMergedProperties: OpenapiSchema = {
1772+
...(actionAttemptSchema['x-route-path'] != null && {
1773+
'x-route-path': actionAttemptSchema['x-route-path'],
1774+
}),
1775+
...schemas[0],
1776+
properties: mergedProperties,
1777+
}
17391778

1740-
const resource = createResource(
1741-
'action_attempt',
1742-
schemaWithMergedProperties,
1743-
routes,
1744-
)
1779+
const resource = await createResource(
1780+
'action_attempt',
1781+
schemaWithMergedProperties,
1782+
routes,
1783+
context,
1784+
)
17451785

1746-
return {
1747-
...resource,
1748-
resourceType: 'action_attempt',
1749-
actionAttemptType: actionType,
1750-
}
1751-
},
1786+
return {
1787+
...resource,
1788+
resourceType: 'action_attempt',
1789+
actionAttemptType: actionType,
1790+
}
1791+
},
1792+
),
17521793
)
17531794
}

0 commit comments

Comments
 (0)