Skip to content
Open
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
18 changes: 18 additions & 0 deletions .changeset/jsonld-schema-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"swagger-typescript-api": minor
---

Add opt-in JSON-LD schema support.

With `jsonLdOptions.enabled` (CLI: `--jsonld`), schemas that declare the
`x-jsonld` extension — or one of `x-jsonld-context`, `x-jsonld-type`,
`x-jsonld-id` — are parsed as JSON-LD entities. Entities gain typed
`@context`, `@type` and `@id` members and extend a shared `JsonLdEntity`
interface; a property-less `x-jsonld-type` schema becomes a string-literal
type alias. In modular output the entities are emitted as `jsonld-entity`
and the shared interfaces as `jsonld-utils`, both re-exported from
`data-contracts`. Set `jsonLdOptions.generateUtils` to `false` to emit
standalone entity interfaces without the shared module.

The feature is fully opt-in — schemas without the extension and runs
without `--jsonld` produce byte-identical output to previous versions.
7 changes: 7 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,12 @@ const generateCommand = defineCommand({
description: "generate js api module with declaration file",
default: codeGenBaseConfig.toJS,
},
jsonld: {
type: "boolean",
description:
"enable JSON-LD support; schemas declaring the `x-jsonld` extension produce additional context/entity/utility types",
default: codeGenBaseConfig.jsonLdOptions.enabled,
},
modular: {
type: "boolean",
description:
Expand Down Expand Up @@ -347,6 +353,7 @@ const generateCommand = defineCommand({
? HTTP_CLIENT.AXIOS
: HTTP_CLIENT.FETCH,
input: path.resolve(process.cwd(), args.path as string),
jsonLdOptions: { enabled: args.jsonld },
modular: args.modular,
moduleNameFirstTag: args["module-name-first-tag"],
moduleNameIndex: +args["module-name-index"] || 0,
Expand Down
66 changes: 65 additions & 1 deletion src/code-gen-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
} from "../types/index.js";
import { CodeFormatter } from "./code-formatter.js";
import { CodeGenConfig } from "./configuration.js";
import { SCHEMA_TYPES } from "./constants.js";
import { SchemaComponentsMap } from "./schema-components-map.js";
import { SchemaParserFabric } from "./schema-parser/schema-parser-fabric.js";
import { SchemaRoutes } from "./schema-routes/schema-routes.js";
Expand Down Expand Up @@ -477,6 +478,39 @@ export class CodeGenProcess {
}
}

const jsonldOutputFiles: TranslatorIO[] = [];

const { jsonLdOptions } = configuration.config;
const hasJsonLdSchemas = this.hasJsonLdEntities(configuration);

if (hasJsonLdSchemas) {
if (templatesToRender.jsonldEntityDataContract) {
jsonldOutputFiles.push(
...(await this.createOutputFileInfo(
configuration,
fileNames.jsonldEntity,
this.templatesWorker.renderTemplate(
templatesToRender.jsonldEntityDataContract,
configuration,
),
)),
);
}

if (jsonLdOptions.generateUtils && templatesToRender.jsonldUtils) {
jsonldOutputFiles.push(
...(await this.createOutputFileInfo(
configuration,
fileNames.jsonldUtils,
this.templatesWorker.renderTemplate(
templatesToRender.jsonldUtils,
configuration,
),
)),
);
}
}

return [
...(await this.createOutputFileInfo(
configuration,
Expand All @@ -496,6 +530,7 @@ export class CodeGenProcess {
),
)
: []),
...jsonldOutputFiles,
...modularApiFileInfos,
];
};
Expand All @@ -504,7 +539,10 @@ export class CodeGenProcess {
templatesToRender,
configuration,
): Promise<TranslatorIO[]> => {
const { generateRouteTypes, generateClient } = configuration.config;
const { generateRouteTypes, generateClient, jsonLdOptions } =
configuration.config;

const hasJsonLdSchemas = this.hasJsonLdEntities(configuration);

return await this.createOutputFileInfo(
configuration,
Expand All @@ -514,6 +552,19 @@ export class CodeGenProcess {
templatesToRender.dataContracts,
configuration,
),
hasJsonLdSchemas &&
templatesToRender.jsonldEntityDataContract &&
this.templatesWorker.renderTemplate(
templatesToRender.jsonldEntityDataContract,
configuration,
),
hasJsonLdSchemas &&
jsonLdOptions?.generateUtils &&
templatesToRender.jsonldUtils &&
this.templatesWorker.renderTemplate(
templatesToRender.jsonldUtils,
configuration,
),
generateRouteTypes &&
this.templatesWorker.renderTemplate(
templatesToRender.routeTypes,
Expand All @@ -533,6 +584,19 @@ export class CodeGenProcess {
);
};

/**
* `jsonld-type` schemas stay in `data-contracts`; only entities are moved
* into their own module, and the utility types exist to support them.
*/
hasJsonLdEntities = (configuration): boolean =>
Boolean(
configuration.config.jsonLdOptions?.enabled &&
configuration.modelTypes?.some?.(
(modelType) =>
modelType.typeData?.schemaType === SCHEMA_TYPES.JSONLD_ENTITY,
),
);

createOutputFileInfo = async (
configuration,
fileNameFull,
Expand Down
17 changes: 17 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export class CodeGenConfig {
routeTypes: "route-types",
httpClient: "http-client",
outOfModuleApi: "Common",
jsonldEntity: "jsonld-entity",
jsonldUtils: "jsonld-utils",
};
routeNameDuplicatesMap = new Map();
hooks: Hooks = {
Expand Down Expand Up @@ -153,6 +155,8 @@ export class CodeGenConfig {
httpClient: "",
routeTypes: "",
routeName: "",
jsonldEntityDataContract: "",
jsonldUtils: "",
};
schemaParsers: Record<string, (...args: unknown[]) => MonoSchemaParser> = {};
toJS = false;
Expand Down Expand Up @@ -194,6 +198,14 @@ export class CodeGenConfig {

successResponseStatusRange = [200, 299];

/** JSON-LD specific configuration options */
jsonLdOptions = {
/** Enable JSON-LD support. Schemas are detected via the `x-jsonld` extension. */
enabled: false,
/** Generate the shared `jsonld-utils` file with base interfaces (JsonLdEntity, JsonLdGraph, ...) */
generateUtils: true,
};

extractingOptions: Partial<ExtractingOptions> = {
requestBodySuffix: ["Payload", "Body", "Input"],
requestParamsSuffix: ["Params"],
Expand Down Expand Up @@ -421,6 +433,11 @@ export class CodeGenConfig {
{ name: "httpClient", fileName: "http-client" },
{ name: "routeTypes", fileName: "route-types" },
{ name: "routeName", fileName: "route-name" },
{
name: "jsonldEntityDataContract",
fileName: "jsonld-entity-data-contract",
},
{ name: "jsonldUtils", fileName: "jsonld-utils" },
];

templateExtensions = [".eta", ".ejs"];
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,6 @@ export const SCHEMA_TYPES = {
COMPLEX_ALL_OF: "allOf",
COMPLEX_NOT: "not",
COMPLEX_UNKNOWN: "__unknown",
JSONLD_ENTITY: "jsonld-entity",
JSONLD_TYPE: "jsonld-type",
} as const;
Loading