Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ dist
tmp
jest.config.js
website
playground/__gen__
playground
test/codegen/generators/*/output
mcp-server
37 changes: 5 additions & 32 deletions src/browser/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
* Uses the shared rendering pipeline for in-memory code generation.
* No filesystem I/O - returns generated files as data.
*/
import {parse as parseYaml} from 'yaml';
import {parse, dereference} from '@readme/openapi-parser';
import {OpenAPIV2, OpenAPIV3, OpenAPIV3_1} from 'openapi-types';
// Use the shim's interface for browser compatibility, cast to any when passing to generators
// that expect @asyncapi/parser.AsyncAPIDocumentInterface
import {AsyncAPIDocumentInterface} from './shims/asyncapi-parser';
import type {AsyncAPIDocumentInterface as NodeAsyncAPIDocumentInterface} from '@asyncapi/parser';
import {CodegenError} from '../codegen/errors';
import {loadAsyncapiFromMemoryBrowser} from './parser';
import {loadOpenapiFromMemory} from '../codegen/inputs/openapi';
import {
loadJsonSchemaFromMemory,
JsonSchemaDocument
Expand Down Expand Up @@ -99,7 +98,9 @@ export async function generate(

case 'openapi':
try {
openapiDocument = await parseOpenAPIFromMemory(input.spec);
// Shared with the Node in-memory loader so the browser playground
// gets the same normalization (incl. reflectComponentSchemaNames).
openapiDocument = await loadOpenapiFromMemory(input.spec);
} catch (error) {
errors.push(
`Failed to parse OpenAPI spec: ${error instanceof Error ? error.message : String(error)}`
Expand All @@ -110,10 +111,7 @@ export async function generate(

case 'jsonschema':
try {
const parsed = parseSpecString(input.spec);
jsonSchemaDocument = loadJsonSchemaFromMemory(
parsed as JsonSchemaDocument
);
jsonSchemaDocument = loadJsonSchemaFromMemory(input.spec);
} catch (error) {
errors.push(
`Failed to parse JSON Schema: ${error instanceof Error ? error.message : String(error)}`
Expand Down Expand Up @@ -160,28 +158,3 @@ export async function generate(

return {files, errors};
}

/**
* Parse an OpenAPI spec from a string.
*/
async function parseOpenAPIFromMemory(
specString: string
): Promise<OpenAPIV3.Document | OpenAPIV2.Document | OpenAPIV3_1.Document> {
const document = parseSpecString(specString);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const parsedDocument = await parse(document as any);
return await dereference(parsedDocument);
}

/**
* Parse a spec string (YAML or JSON) into an object.
*/
function parseSpecString(specString: string): unknown {
// Try JSON first
try {
return JSON.parse(specString);
} catch {
// Fall back to YAML
return parseYaml(specString);
}
}
43 changes: 40 additions & 3 deletions src/codegen/configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ import {
includeTypeScriptClientDependencies
} from './generators/typescript/client';
import path from 'path';
import {loadAsyncapi} from './inputs/asyncapi';
import {loadOpenapi} from './inputs/openapi';
import {loadJsonSchema} from './inputs/jsonschema';
import {loadAsyncapi, loadAsyncapiFromMemory} from './inputs/asyncapi';
import {loadOpenapi, loadOpenapiFromMemory} from './inputs/openapi';
import {loadJsonSchema, loadJsonSchemaFromMemory} from './inputs/jsonschema';
import {
defaultTypeScriptHeadersOptions,
defaultTypeScriptTypesOptions
Expand Down Expand Up @@ -315,3 +315,40 @@ export async function realizeGeneratorContext(

return context;
}

/**
* In-memory counterpart of {@link realizeGeneratorContext}. Given a config
* object and the raw specification string, it produces a fully-loaded
* `RunGeneratorContext` using the same per-input loaders as the file flow —
* so callers that hold a spec in memory (the platform's `generateInMemory`,
* preview, the playground) generate identical output to `codegen generate`.
* This is the single seam for in-memory generation: parsing/normalization
* lives here, never in downstream consumers.
*/
export async function realizeInMemoryGeneratorContext({
configuration,
specificationDocument
}: {
configuration: TheCodegenConfiguration;
specificationDocument: string;
}): Promise<RunGeneratorContext> {
const config = realizeConfiguration(configuration);
const context: RunGeneratorContext = {
configuration: config,
// Root-level virtual paths (matching the browser in-memory path) so
// generated output paths resolve identically regardless of caller cwd.
documentPath: '/virtual-spec',
configFilePath: '/virtual-config.mjs'
};
if (config.inputType === 'asyncapi') {
context.asyncapiDocument =
await loadAsyncapiFromMemory(specificationDocument);
} else if (config.inputType === 'openapi') {
context.openapiDocument =
await loadOpenapiFromMemory(specificationDocument);
} else if (config.inputType === 'jsonschema') {
context.jsonSchemaDocument =
loadJsonSchemaFromMemory(specificationDocument);
}
return context;
}
9 changes: 5 additions & 4 deletions src/codegen/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export {loadAsyncapi} from './inputs/asyncapi';
export {loadOpenapi} from './inputs/openapi';
export {loadJsonSchema} from './inputs/jsonschema';
export {loadAsyncapi, loadAsyncapiFromMemory} from './inputs/asyncapi';
export {loadOpenapi, loadOpenapiFromMemory} from './inputs/openapi';
export {loadJsonSchema, loadJsonSchemaFromMemory} from './inputs/jsonschema';

export {
defaultTypeScriptChannelsGenerator,
Expand Down Expand Up @@ -52,7 +52,8 @@ export {
realizeConfiguration,
loadAndRealizeConfigFile,
loadConfigFile,
realizeGeneratorContext
realizeGeneratorContext,
realizeInMemoryGeneratorContext
} from './configurations';

export {renderGenerator, determineRenderGraph, renderGraph} from './renderer';
10 changes: 7 additions & 3 deletions src/codegen/inputs/jsonschema/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,18 @@ function parseDocument(
* Load JSON Schema document from memory.
*/
export function loadJsonSchemaFromMemory(
document: JsonSchemaDocument,
document: string | JsonSchemaDocument,
documentPath?: string
): JsonSchemaDocument {
const path = documentPath || 'memory';
Logger.verbose(`Loading JSON Schema document from ${path}`);

validateJsonSchemaDocument(document, path);
return document;
const parsed =
typeof document === 'string'
? (parseDocument(document, path, null) as JsonSchemaDocument)
: document;
validateJsonSchemaDocument(parsed, path);
return parsed;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/codegen/inputs/openapi/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export {loadOpenapi, loadOpenapiDocument} from './parser';
export {loadOpenapi, loadOpenapiDocument, loadOpenapiFromMemory} from './parser';
export {processOpenAPIPayloads} from './generators/payloads';
20 changes: 20 additions & 0 deletions src/codegen/inputs/openapi/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,26 @@ export async function loadOpenapiDocument(
}
}

/**
* Load and normalize an OpenAPI document already held in memory (as a raw
* JSON/YAML string). Runs the exact same normalization as the local-file path
* of {@link loadOpenapiDocument} — dereference followed by
* {@link reflectComponentSchemaNames} — so in-memory callers (the platform's
* `generateInMemory`, preview, and the playground) produce byte-identical
* output to `codegen generate` on a file.
*/
export async function loadOpenapiFromMemory(
specString: string
): Promise<OpenAPIV3.Document | OpenAPIV2.Document | OpenAPIV3_1.Document> {
const document = parseDocumentContent(specString, 'memory', null);
const parsedDocument = await parse(document);
const {dereference} = await import('@readme/openapi-parser');
const dereferenced = await dereference(parsedDocument);
reflectComponentSchemaNames(dereferenced);
Logger.debug(`OpenAPI document loaded and dereferenced from memory`);
return dereferenced;
}

function parseDocumentContent(
content: string,
documentPath: string,
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ export {
loadAndRealizeConfigFile,
loadConfigFile,
realizeGeneratorContext,
realizeInMemoryGeneratorContext,
loadOpenapiFromMemory,
loadAsyncapiFromMemory,
loadJsonSchemaFromMemory,
ChannelFunctionTypes
} from './codegen';

Expand Down
Loading