Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 81 additions & 28 deletions src/lib/blueprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,6 @@
| BooleanProperty
| DatetimeProperty
| IdProperty
| BatchResourceProperty

interface StringProperty extends BaseProperty {
format: 'string'
Expand Down Expand Up @@ -366,12 +365,6 @@
itemFormat: 'id'
}

interface BatchResourceProperty extends BaseProperty {
format: 'record'
jsonType: 'object'
resourceType: string
}

interface EnumListProperty extends BaseListProperty {
itemFormat: 'enum'
itemEnumValues: EnumValue[]
Expand Down Expand Up @@ -458,6 +451,75 @@
formatCode?: (content: string, syntax: SyntaxName) => Promise<string>
}

/**
* Injects resource sample data into code sample definitions.
* Replaces string values that match resource types with actual resource sample properties.
*/
const injectResourceSamplesIntoCodeSamples = (
codeSampleDefinitions: CodeSampleDefinition[],
resourceSampleDefinitions: ResourceSampleDefinition[],
): CodeSampleDefinition[] => {
// Create a map of resource type to sample properties
const resourceSampleMap = new Map<string, unknown>()
for (const resourceSample of resourceSampleDefinitions) {
resourceSampleMap.set(
resourceSample.resource_type,
resourceSample.properties,
)
}

return codeSampleDefinitions.map((codeSample) => {

Check failure on line 471 in src/lib/blueprint.ts

View workflow job for this annotation

GitHub Actions / Typecheck (Node.js v20)

Type '({ description: string; title: string; request: { path: string; parameters: Record<string, Json>; }; response: { body: Record<string, Json> | null; }; } | { response: { ...; }; description: string; title: string; request: { ...; }; })[]' is not assignable to type '{ description: string; title: string; request: { path: string; parameters: Record<string, Json>; }; response: { body: Record<string, Json> | null; }; }[]'.

Check failure on line 471 in src/lib/blueprint.ts

View workflow job for this annotation

GitHub Actions / Typecheck (Node.js v22)

Type '({ description: string; title: string; request: { path: string; parameters: Record<string, Json>; }; response: { body: Record<string, Json> | null; }; } | { response: { ...; }; description: string; title: string; request: { ...; }; })[]' is not assignable to type '{ description: string; title: string; request: { path: string; parameters: Record<string, Json>; }; response: { body: Record<string, Json> | null; }; }[]'.

Check failure on line 471 in src/lib/blueprint.ts

View workflow job for this annotation

GitHub Actions / Build / Package

Type '({ description: string; title: string; request: { path: string; parameters: Record<string, Json>; }; response: { body: Record<string, Json> | null; }; } | { response: { ...; }; description: string; title: string; request: { ...; }; })[]' is not assignable to type '{ description: string; title: string; request: { path: string; parameters: Record<string, Json>; }; response: { body: Record<string, Json> | null; }; }[]'.
if (codeSample.response.body == null) {
return codeSample
}

const processedBody = processResponseBody(
codeSample.response.body,
resourceSampleMap,
)

return {
...codeSample,
response: {
...codeSample.response,
body: processedBody,
},
}
})
}

/**
* Recursively processes response body to replace resource type strings with sample data.
*/
const processResponseBody = (
body: Record<string, unknown>,
resourceSampleMap: Map<string, unknown>,
): Record<string, unknown> => {
const processed: Record<string, unknown> = {}

for (const [key, value] of Object.entries(body)) {
if (typeof value === 'string' && resourceSampleMap.has(value)) {
// Replace resource type string with array containing the sample
processed[key] = [resourceSampleMap.get(value)]
} else if (
typeof value === 'object' &&
value !== null &&
!Array.isArray(value)
) {
// Recursively process nested objects
processed[key] = processResponseBody(
value as Record<string, unknown>,
resourceSampleMap,
)
} else {
// Keep other values as-is
processed[key] = value
}
}

return processed
}

export const createBlueprint = async (
typesModule: TypesModuleInput,
{ formatCode = async (content) => content }: BlueprintOptions = {},
Expand All @@ -475,9 +537,15 @@
openapi.components.schemas,
)

const context: Context = {
// Inject resource samples into code sample definitions before creating blueprint
const processedCodeSampleDefinitions = injectResourceSamplesIntoCodeSamples(
codeSampleDefinitions,
resourceSampleDefinitions,
)

const context: Context = {
codeSampleDefinitions: processedCodeSampleDefinitions,
resourceSampleDefinitions,
formatCode,
schemas,
validActionAttemptTypes,
Expand Down Expand Up @@ -1132,6 +1200,11 @@
): Promise<Resource[]> => {
const resources: Resource[] = []
for (const [schemaName, schema] of Object.entries(schemas)) {
// Skip batch resource - it's handled via code samples
if (schemaName === 'batch') {
continue
}

const { success: isValidEventSchema, data: parsedEvent } =
EventResourceSchema.safeParse(schema)

Expand Down Expand Up @@ -1519,26 +1592,6 @@
propertyGroupKey: propertyGroupKey === '' ? null : propertyGroupKey,
}

if (
parentPaths.includes('batch') &&
prop.type === 'array' &&
prop.items?.$ref
) {
const refPath = prop.items.$ref
const resourceType = refPath.split('/').pop()

if (resourceType) {
const batchResourceProperty: BatchResourceProperty = {
...baseProperty,
format: 'record',
jsonType: 'object',
resourceType,
}

return batchResourceProperty
}
}

switch (parsedProp.type) {
case 'string':
if (parsedProp.enum !== undefined) {
Expand Down
15 changes: 15 additions & 0 deletions test/fixtures/types/code-sample-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ export default [
},
},
},
{
title: 'Get batch with resource injection',
description: 'Test batch resource injection',
request: {
path: '/foos/get_batch',
parameters: {},
},
response: {
body: {
batch: {
foos: 'foo',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@razor-x, this is first-pass by AI, but do you mean something like this, where we reference the code sample name here?

Copy link
Member

@razor-x razor-x Dec 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikewuu The logic should be in the docs repo, it doesn't make sense to put this in the blueprint. The blueprint should only ever be passed complete and correct samples. Any "cleverness" of how those samples are defined is out of scope of this repo.

My thought was to add a hook here:

  .use(
    metadata({
      codeSampleDefinitions: './data/code-sample-definitions',
      resourceSampleDefinitions: './data/resource-sample-definitions',
      pathMetadata: './data/paths.yaml',
    }),
  )
  .use(preprocessSamples) // update codeSampleDefinitions and resourceSampleDefinitions in place!
  .use(
    blueprint({
      types,
      formatCode,
      skipCodeFormat: env['SKIP_CODE_FORMAT'] != null,
    }),
  )

},
},
},
},
{
title: 'List planes',
description: 'This is the wya to get all plans',
Expand Down
42 changes: 42 additions & 0 deletions test/fixtures/types/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,48 @@ export default {
'x-title': 'List foos',
},
},
'/foos/get_batch': {
post: {
operationId: 'foosGetBatchPost',
responses: {
200: {
content: {
'application/json': {
schema: {
properties: {
ok: { type: 'boolean' },
batch: {
type: 'object',
properties: {
foos: {
items: { $ref: '#/components/schemas/foo' },
type: 'array',
},
},
},
},
required: ['batch', 'ok'],
type: 'object',
},
},
},
description: 'Get batch of foos.',
},
400: { description: 'Bad Request' },
401: { description: 'Unauthorized' },
},
security: [
{
api_key: [],
},
],
summary: '/foos/get_batch',
tags: ['/foos'],
'x-response-key': 'batch',
'x-batch-keys': ['foos'],
'x-title': 'Get batch of foos',
},
},
'/foos/advanced/get': {
get: {
operationId: 'foosAdvancedGetGet',
Expand Down
Loading
Loading