@kurotako/gen-openapi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,388 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ OpenApiGenError: () => OpenApiGenError,
34
+ OpenApiGenInvalidSchemaNameError: () => OpenApiGenInvalidSchemaNameError,
35
+ OpenApiGeneratorOptions: () => OpenApiGeneratorOptions,
36
+ openapiGenerator: () => openapiGenerator
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/errors.ts
41
+ var OpenApiGenError = class extends Error {
42
+ code;
43
+ constructor(code, message, options) {
44
+ super(message, options);
45
+ this.name = new.target.name;
46
+ this.code = code;
47
+ }
48
+ };
49
+ var OpenApiGenInvalidSchemaNameError = class extends OpenApiGenError {
50
+ schemaName;
51
+ constructor(schemaName) {
52
+ super(
53
+ "openapi_gen_invalid_schema_name",
54
+ `'${schemaName}' is not a valid OpenAPI components/schemas key (must match ^[a-zA-Z0-9._-]+$)`
55
+ );
56
+ this.schemaName = schemaName;
57
+ }
58
+ };
59
+
60
+ // src/generator.ts
61
+ var import_config = require("@kurotako/config");
62
+
63
+ // src/artifact.ts
64
+ var import_ir = require("@kurotako/ir");
65
+
66
+ // src/render/schema.ts
67
+ function schemaRef(name) {
68
+ return `#/components/schemas/${name}`;
69
+ }
70
+ function scalarSchema(scalar) {
71
+ switch (scalar) {
72
+ case "string":
73
+ return { type: "string" };
74
+ case "boolean":
75
+ return { type: "boolean" };
76
+ case "int":
77
+ return { type: "integer" };
78
+ case "bigint":
79
+ return { type: "integer", format: "int64" };
80
+ case "float":
81
+ return { type: "number" };
82
+ case "decimal":
83
+ return { type: "number" };
84
+ case "date":
85
+ return { type: "string", format: "date" };
86
+ case "datetime":
87
+ return { type: "string", format: "date-time" };
88
+ case "uuid":
89
+ return { type: "string", format: "uuid" };
90
+ case "bytes":
91
+ return { type: "string", format: "byte" };
92
+ case "json":
93
+ return {};
94
+ }
95
+ }
96
+ function renderFieldType(type) {
97
+ switch (type.kind) {
98
+ case "scalar":
99
+ return scalarSchema(type.scalar);
100
+ case "enum":
101
+ return { $ref: schemaRef(type.ref) };
102
+ case "unknown":
103
+ return {};
104
+ case "ref":
105
+ return { $ref: schemaRef(type.ref) };
106
+ case "map":
107
+ return {
108
+ type: "object",
109
+ additionalProperties: type.value.kind === "unknown" ? true : renderFieldType(type.value)
110
+ };
111
+ case "array":
112
+ return { type: "array", items: renderFieldType(type.element) };
113
+ case "union": {
114
+ const schema = {
115
+ oneOf: type.variants.map((variant) => renderFieldType(variant))
116
+ };
117
+ if (type.discriminator !== void 0) {
118
+ const discriminator = {
119
+ propertyName: type.discriminator.propertyName
120
+ };
121
+ if (type.discriminator.mapping !== void 0) {
122
+ discriminator.mapping = Object.fromEntries(
123
+ Object.entries(type.discriminator.mapping).map(([key, target]) => [
124
+ key,
125
+ schemaRef(target)
126
+ ])
127
+ );
128
+ }
129
+ schema.discriminator = discriminator;
130
+ }
131
+ return schema;
132
+ }
133
+ }
134
+ }
135
+ function wrapListed(schema, type) {
136
+ if (type.kind === "array") {
137
+ return schema;
138
+ }
139
+ return { type: "array", items: schema };
140
+ }
141
+ function hasRef(schema) {
142
+ return typeof schema.$ref === "string";
143
+ }
144
+ function wrapNullable(schema, openapiVersion) {
145
+ if (openapiVersion === "3.0") {
146
+ if (hasRef(schema)) {
147
+ return { allOf: [schema], nullable: true };
148
+ }
149
+ return { ...schema, nullable: true };
150
+ }
151
+ if (hasRef(schema) || schema.oneOf !== void 0) {
152
+ return { oneOf: [schema, { type: "null" }] };
153
+ }
154
+ if (typeof schema.type === "string") {
155
+ return { ...schema, type: [schema.type, "null"] };
156
+ }
157
+ return { oneOf: [schema, { type: "null" }] };
158
+ }
159
+
160
+ // src/artifact.ts
161
+ function buildArtifact(ir, options) {
162
+ const ext = options.format === "yaml" ? "yaml" : "json";
163
+ const entities = {};
164
+ for (const { namespace, entity } of (0, import_ir.iterEntities)(ir)) {
165
+ entities[`${namespace}.${entity.name}`] = {
166
+ module: `${namespace}/openapi/openapi.${ext}`,
167
+ symbols: { schema: schemaRef(entity.name) }
168
+ };
169
+ }
170
+ return { entities };
171
+ }
172
+
173
+ // src/render/entity.ts
174
+ var import_ir2 = require("@kurotako/ir");
175
+
176
+ // src/render/constraints.ts
177
+ var FORMAT_KEYWORDS = {
178
+ email: "email",
179
+ url: "uri",
180
+ ipv4: "ipv4",
181
+ ipv6: "ipv6",
182
+ time: "time",
183
+ duration: "duration"
184
+ };
185
+ function applyConstraints(schema, type, constraints) {
186
+ const out = { ...schema };
187
+ if (constraints.min !== void 0) {
188
+ out.minimum = constraints.min;
189
+ }
190
+ if (constraints.max !== void 0) {
191
+ out.maximum = constraints.max;
192
+ }
193
+ if (constraints.minLength !== void 0) {
194
+ out.minLength = constraints.minLength;
195
+ }
196
+ if (constraints.maxLength !== void 0) {
197
+ out.maxLength = constraints.maxLength;
198
+ }
199
+ if (constraints.regex !== void 0) {
200
+ out.pattern = constraints.regex;
201
+ }
202
+ if (constraints.format !== void 0 && type.kind === "scalar" && type.scalar === "string") {
203
+ const keyword = FORMAT_KEYWORDS[constraints.format];
204
+ if (keyword !== void 0) {
205
+ out.format = keyword;
206
+ }
207
+ }
208
+ return out;
209
+ }
210
+
211
+ // src/render/entity.ts
212
+ function renderRelation(rel, opts) {
213
+ if ((0, import_ir2.isCrossSource)(opts.namespace, rel)) {
214
+ opts.logger?.debug(
215
+ `gen-openapi: relation '${rel.name}' targets another source ('${rel.target.namespace}.${rel.target.entity}'); omitting it from the emitted schema`
216
+ );
217
+ return null;
218
+ }
219
+ const target = schemaRef(rel.target.entity);
220
+ let schema = rel.cardinality === "many" ? { type: "array", items: { $ref: target } } : { $ref: target };
221
+ if (rel.cardinality === "one" && rel.optional) {
222
+ schema = wrapNullable(schema, opts.openapiVersion);
223
+ }
224
+ return schema;
225
+ }
226
+ function renderEntity(entity, opts) {
227
+ const properties = {};
228
+ const required = [];
229
+ for (const field of entity.fields) {
230
+ let schema2 = renderFieldType(field.type);
231
+ schema2 = applyConstraints(schema2, field.type, field.constraints);
232
+ if (field.list) {
233
+ schema2 = wrapListed(schema2, field.type);
234
+ }
235
+ if (field.nullable) {
236
+ schema2 = wrapNullable(schema2, opts.openapiVersion);
237
+ }
238
+ properties[field.name] = schema2;
239
+ if (!field.optional) {
240
+ required.push(field.name);
241
+ }
242
+ }
243
+ for (const relation of entity.relations) {
244
+ const schema2 = renderRelation(relation, opts);
245
+ if (schema2 !== null) {
246
+ properties[relation.name] = schema2;
247
+ }
248
+ }
249
+ const schema = {
250
+ type: "object",
251
+ properties
252
+ };
253
+ if (required.length > 0) {
254
+ schema.required = required;
255
+ }
256
+ if (entity.additionalProperties !== void 0) {
257
+ schema.additionalProperties = entity.additionalProperties.kind === "unknown" ? true : renderFieldType(entity.additionalProperties);
258
+ }
259
+ if (entity.doc !== void 0) {
260
+ schema.description = entity.doc;
261
+ }
262
+ return schema;
263
+ }
264
+ function renderTypeAlias(alias) {
265
+ const schema = renderFieldType(alias.type);
266
+ if (alias.doc !== void 0) {
267
+ return { ...schema, description: alias.doc };
268
+ }
269
+ return schema;
270
+ }
271
+
272
+ // src/document.ts
273
+ function collectEnums(source) {
274
+ const byName = /* @__PURE__ */ new Map();
275
+ for (const def of Object.values(source.enums)) {
276
+ byName.set(def.name, def);
277
+ }
278
+ for (const entity of Object.values(source.entities)) {
279
+ for (const def of Object.values(entity.enums ?? {})) {
280
+ byName.set(def.name, def);
281
+ }
282
+ }
283
+ return [...byName.values()];
284
+ }
285
+ function renderEnum(def) {
286
+ const schema = {
287
+ type: "string",
288
+ enum: def.values.map((value) => value.name)
289
+ };
290
+ if (def.doc !== void 0) {
291
+ schema.description = def.doc;
292
+ }
293
+ return schema;
294
+ }
295
+ var SCHEMA_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
296
+ function pinnedOpenapiVersion(version) {
297
+ return version === "3.0" ? "3.0.3" : "3.1.0";
298
+ }
299
+ function buildDocument(source, options, namespace, logger) {
300
+ const schemas = {};
301
+ const enumsByName = new Map(
302
+ collectEnums(source).map((def) => [def.name, def])
303
+ );
304
+ const names = [
305
+ ...Object.keys(source.entities),
306
+ ...enumsByName.keys(),
307
+ ...Object.keys(source.typeAliases ?? {}).filter(
308
+ (name) => !enumsByName.has(name) && source.entities[name] === void 0
309
+ )
310
+ ].sort((a, b) => a.localeCompare(b));
311
+ for (const name of names) {
312
+ if (!SCHEMA_NAME_PATTERN.test(name)) {
313
+ throw new OpenApiGenInvalidSchemaNameError(name);
314
+ }
315
+ const entity = source.entities[name];
316
+ if (entity !== void 0) {
317
+ schemas[name] = renderEntity(entity, {
318
+ openapiVersion: options.openapiVersion,
319
+ namespace,
320
+ logger
321
+ });
322
+ continue;
323
+ }
324
+ const enumDef = enumsByName.get(name);
325
+ if (enumDef !== void 0) {
326
+ schemas[name] = renderEnum(enumDef);
327
+ continue;
328
+ }
329
+ const alias = source.typeAliases?.[name];
330
+ if (alias !== void 0) {
331
+ schemas[name] = renderTypeAlias(alias);
332
+ }
333
+ }
334
+ return {
335
+ openapi: pinnedOpenapiVersion(options.openapiVersion),
336
+ info: {
337
+ title: options.title ?? namespace,
338
+ version: options.version
339
+ },
340
+ paths: {},
341
+ components: { schemas }
342
+ };
343
+ }
344
+
345
+ // src/options.ts
346
+ var v = __toESM(require("valibot"), 1);
347
+ var OpenApiGeneratorOptions = v.object({
348
+ openapiVersion: v.optional(v.picklist(["3.0", "3.1"]), "3.1"),
349
+ format: v.optional(v.picklist(["json", "yaml"]), "json"),
350
+ title: v.optional(v.string()),
351
+ version: v.optional(v.string(), "0.0.0")
352
+ });
353
+
354
+ // src/serialize.ts
355
+ var YAML = __toESM(require("yaml"), 1);
356
+ function serialize(document, format) {
357
+ if (format === "yaml") {
358
+ return YAML.stringify(document);
359
+ }
360
+ return `${JSON.stringify(document, null, 2)}
361
+ `;
362
+ }
363
+
364
+ // src/generator.ts
365
+ var openapiGenerator = (0, import_config.defineGenerator)({
366
+ name: "openapi",
367
+ optionsSchema: OpenApiGeneratorOptions,
368
+ generate(ctx, options) {
369
+ const files = [];
370
+ const ext = options.format === "yaml" ? "yaml" : "json";
371
+ for (const [namespace, source] of Object.entries(ctx.ir.sources)) {
372
+ const document = buildDocument(source, options, namespace, ctx.logger);
373
+ files.push({
374
+ path: `${namespace}/openapi/openapi.${ext}`,
375
+ content: serialize(document, options.format)
376
+ });
377
+ }
378
+ return { files, artifact: buildArtifact(ctx.ir, options) };
379
+ }
380
+ });
381
+ // Annotate the CommonJS export names for ESM import in node:
382
+ 0 && (module.exports = {
383
+ OpenApiGenError,
384
+ OpenApiGenInvalidSchemaNameError,
385
+ OpenApiGeneratorOptions,
386
+ openapiGenerator
387
+ });
388
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/generator.ts","../src/artifact.ts","../src/render/schema.ts","../src/render/entity.ts","../src/render/constraints.ts","../src/document.ts","../src/options.ts","../src/serialize.ts"],"sourcesContent":["/**\n * `@kurotako/gen-openapi` — the OpenAPI generator driver.\n *\n * Maps the IR to one OpenAPI document (`components/schemas` only, no\n * `paths`/operations) per namespace. Single entry point: the driver object,\n * its options schema/type and the error classes.\n */\nexport {\n OpenApiGenError,\n OpenApiGenInvalidSchemaNameError,\n} from './errors.js';\nexport { openapiGenerator } from './generator.js';\nexport { OpenApiGeneratorOptions } from './options.js';\n","/**\n * `@kurotako/gen-openapi` error classes.\n *\n * `OpenApiGenError` is a plain `Error` subclass carrying a stable `code`; the\n * OpenAPI generator has no dependency on `@kurotako/core` at runtime, and\n * `@kurotako/core` wraps any throw from `generate()` as a `DriverError` for the\n * CLI's single `instanceof TakoError` catch.\n *\n * Codes: `openapi_gen_invalid_schema_name`.\n */\n\nexport class OpenApiGenError extends Error {\n readonly code: string;\n\n constructor(code: string, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = new.target.name;\n this.code = code;\n }\n}\n\n/**\n * An entity or type-alias name is not a valid OpenAPI `components/schemas` key\n * (the spec restricts it to `^[a-zA-Z0-9._-]+$`). `parser-openapi` never\n * produces such a name, but a name coming from `parser-prisma` (arbitrary model\n * name) is unconstrained.\n */\nexport class OpenApiGenInvalidSchemaNameError extends OpenApiGenError {\n readonly schemaName: string;\n\n constructor(schemaName: string) {\n super(\n 'openapi_gen_invalid_schema_name',\n `'${schemaName}' is not a valid OpenAPI components/schemas key (must match ^[a-zA-Z0-9._-]+$)`,\n );\n this.schemaName = schemaName;\n }\n}\n","/**\n * `openapiGenerator` — the `@kurotako/gen-openapi` driver.\n *\n * `@kurotako/config` validates `options` against `optionsSchema` and curries it\n * away; `@kurotako/core` then calls `generate(ctx)` with a namespace-filtered IR.\n * One `VirtualFile` per namespace: a single `openapi.<ext>` document, not one\n * file per entity — a spec is normally not split, and external tooling\n * consuming it expects one document.\n */\nimport { defineGenerator } from '@kurotako/config';\nimport type { GenerateContext, GenOutput, VirtualFile } from '@kurotako/core';\nimport { buildArtifact } from './artifact.js';\nimport { buildDocument } from './document.js';\nimport { OpenApiGeneratorOptions } from './options.js';\nimport { serialize } from './serialize.js';\n\nexport const openapiGenerator = defineGenerator({\n name: 'openapi',\n optionsSchema: OpenApiGeneratorOptions,\n\n generate(ctx: GenerateContext, options): GenOutput {\n const files: VirtualFile[] = [];\n const ext = options.format === 'yaml' ? 'yaml' : 'json';\n\n for (const [namespace, source] of Object.entries(ctx.ir.sources)) {\n const document = buildDocument(source, options, namespace, ctx.logger);\n files.push({\n path: `${namespace}/openapi/openapi.${ext}`,\n content: serialize(document, options.format),\n });\n }\n\n return { files, artifact: buildArtifact(ctx.ir, options) };\n },\n});\n","/**\n * Assemble the `GeneratorArtifact` — one `EntitySymbols` per `${namespace}.${entity}`,\n * pointing at the emitted document and the entity's `#/components/schemas/<name>`\n * pointer. No `dependsOn` (this generator reads only the IR), no\n * `peerDependencies` (the emitted document has no runtime import surface), no\n * `extra` (no consumer needs OpenAPI-specific artifact data yet).\n */\nimport type { EntitySymbols, GeneratorArtifact } from '@kurotako/core';\nimport type { IR } from '@kurotako/ir';\nimport { iterEntities } from '@kurotako/ir';\nimport type { OpenApiGeneratorOptions } from './options.js';\nimport { schemaRef } from './render/schema.js';\n\nexport function buildArtifact(\n ir: IR,\n options: OpenApiGeneratorOptions,\n): GeneratorArtifact {\n const ext = options.format === 'yaml' ? 'yaml' : 'json';\n const entities: Record<string, EntitySymbols> = {};\n\n for (const { namespace, entity } of iterEntities(ir)) {\n entities[`${namespace}.${entity.name}`] = {\n module: `${namespace}/openapi/openapi.${ext}`,\n symbols: { schema: schemaRef(entity.name) },\n };\n }\n\n return { entities };\n}\n","/**\n * `FieldType` -> JSON Schema fragment, symmetric to `parser-openapi`'s\n * `mapSchema` (inverted). Also carries the `Field.list` and nullability\n * wrapping, which are properties of a `Field`/`Relation`, not of a\n * `FieldType` — applied by the caller once the base fragment is built.\n */\nimport type { FieldType, ScalarType } from '@kurotako/ir';\n\nexport type JsonSchemaFragment = Record<string, unknown>;\n\nexport function schemaRef(name: string): string {\n return `#/components/schemas/${name}`;\n}\n\nfunction scalarSchema(scalar: ScalarType): JsonSchemaFragment {\n switch (scalar) {\n case 'string':\n return { type: 'string' };\n case 'boolean':\n return { type: 'boolean' };\n case 'int':\n return { type: 'integer' };\n case 'bigint':\n return { type: 'integer', format: 'int64' };\n case 'float':\n return { type: 'number' };\n case 'decimal':\n return { type: 'number' };\n case 'date':\n return { type: 'string', format: 'date' };\n case 'datetime':\n return { type: 'string', format: 'date-time' };\n case 'uuid':\n return { type: 'string', format: 'uuid' };\n case 'bytes':\n return { type: 'string', format: 'byte' };\n case 'json':\n return {};\n }\n}\n\n/** Pure recursive mapping of a `FieldType` to a JSON Schema fragment. */\nexport function renderFieldType(type: FieldType): JsonSchemaFragment {\n switch (type.kind) {\n case 'scalar':\n return scalarSchema(type.scalar);\n case 'enum':\n return { $ref: schemaRef(type.ref) };\n case 'unknown':\n return {};\n case 'ref':\n return { $ref: schemaRef(type.ref) };\n case 'map':\n return {\n type: 'object',\n additionalProperties:\n type.value.kind === 'unknown' ? true : renderFieldType(type.value),\n };\n case 'array':\n return { type: 'array', items: renderFieldType(type.element) };\n case 'union': {\n const schema: JsonSchemaFragment = {\n oneOf: type.variants.map((variant) => renderFieldType(variant)),\n };\n if (type.discriminator !== undefined) {\n const discriminator: JsonSchemaFragment = {\n propertyName: type.discriminator.propertyName,\n };\n if (type.discriminator.mapping !== undefined) {\n discriminator.mapping = Object.fromEntries(\n Object.entries(type.discriminator.mapping).map(([key, target]) => [\n key,\n schemaRef(target),\n ]),\n );\n }\n schema.discriminator = discriminator;\n }\n return schema;\n }\n }\n}\n\n/**\n * `Field.list` (legacy boolean, still present alongside `FieldType.kind ===\n * 'array'`) wraps the mapped schema one more time in an array — skipped when\n * `type.kind` is already `'array'`, to avoid a double array-of-array.\n */\nexport function wrapListed(\n schema: JsonSchemaFragment,\n type: FieldType,\n): JsonSchemaFragment {\n if (type.kind === 'array') {\n return schema;\n }\n return { type: 'array', items: schema };\n}\n\nfunction hasRef(schema: JsonSchemaFragment): boolean {\n return typeof schema.$ref === 'string';\n}\n\n/**\n * Nullability wrapping for a mapped schema, per OpenAPI version.\n *\n * - 3.1: widen `type` to include `'null'`; a `$ref` or `oneOf` (ref / union /\n * enum, and anything with no bare `type` keyword to widen) wraps instead in\n * `{ oneOf: [<schema>, { type: 'null' }] }`.\n * - 3.0: add `nullable: true` as a sibling keyword; a `$ref` cannot carry a\n * sibling per the 3.0 spec, so it wraps in `{ allOf: [<schema>], nullable:\n * true }` instead.\n */\nexport function wrapNullable(\n schema: JsonSchemaFragment,\n openapiVersion: '3.0' | '3.1',\n): JsonSchemaFragment {\n if (openapiVersion === '3.0') {\n if (hasRef(schema)) {\n return { allOf: [schema], nullable: true };\n }\n return { ...schema, nullable: true };\n }\n\n if (hasRef(schema) || schema.oneOf !== undefined) {\n return { oneOf: [schema, { type: 'null' }] };\n }\n if (typeof schema.type === 'string') {\n return { ...schema, type: [schema.type, 'null'] };\n }\n return { oneOf: [schema, { type: 'null' }] };\n}\n","/**\n * `Entity` -> a `components/schemas/<Entity>` object: own fields (via\n * `render/schema.ts` + `render/constraints.ts`), `required`, `additionalProperties`\n * and relations rendered nested via `$ref` (the \"deep\" family in `gen-zod`'s\n * vocabulary — see `packages/gen-zod/src/render/relations.ts`).\n *\n * Cross-source relations degrade to omitting the relation property entirely\n * (logged at `debug`), same choice `gen-zod`'s deep family makes: v1 cannot\n * reference a schema defined in another namespace's document. Circular\n * relations need no special handling — `$ref` in JSON Schema is natively\n * self- and mutually-recursive.\n */\nimport type { Logger } from '@kurotako/core';\nimport type { Entity, Relation, TypeAlias } from '@kurotako/ir';\nimport { isCrossSource } from '@kurotako/ir';\nimport { applyConstraints } from './constraints.js';\nimport {\n type JsonSchemaFragment,\n renderFieldType,\n schemaRef,\n wrapListed,\n wrapNullable,\n} from './schema.js';\n\nexport interface RenderEntityOptions {\n openapiVersion: '3.0' | '3.1';\n /** The namespace the entity belongs to, for cross-source relation detection. */\n namespace: string;\n logger?: Logger;\n}\n\nfunction renderRelation(\n rel: Relation,\n opts: RenderEntityOptions,\n): JsonSchemaFragment | null {\n if (isCrossSource(opts.namespace, rel)) {\n opts.logger?.debug(\n `gen-openapi: relation '${rel.name}' targets another source ('${rel.target.namespace}.${rel.target.entity}'); omitting it from the emitted schema`,\n );\n return null;\n }\n\n const target = schemaRef(rel.target.entity);\n let schema: JsonSchemaFragment =\n rel.cardinality === 'many'\n ? { type: 'array', items: { $ref: target } }\n : { $ref: target };\n\n if (rel.cardinality === 'one' && rel.optional) {\n schema = wrapNullable(schema, opts.openapiVersion);\n }\n\n return schema;\n}\n\nexport function renderEntity(\n entity: Entity,\n opts: RenderEntityOptions,\n): JsonSchemaFragment {\n const properties: Record<string, JsonSchemaFragment> = {};\n const required: string[] = [];\n\n for (const field of entity.fields) {\n let schema = renderFieldType(field.type);\n schema = applyConstraints(schema, field.type, field.constraints);\n if (field.list) {\n schema = wrapListed(schema, field.type);\n }\n if (field.nullable) {\n schema = wrapNullable(schema, opts.openapiVersion);\n }\n properties[field.name] = schema;\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n for (const relation of entity.relations) {\n const schema = renderRelation(relation, opts);\n if (schema !== null) {\n properties[relation.name] = schema;\n }\n }\n\n const schema: JsonSchemaFragment = {\n type: 'object',\n properties,\n };\n if (required.length > 0) {\n schema.required = required;\n }\n if (entity.additionalProperties !== undefined) {\n schema.additionalProperties =\n entity.additionalProperties.kind === 'unknown'\n ? true\n : renderFieldType(entity.additionalProperties);\n }\n if (entity.doc !== undefined) {\n schema.description = entity.doc;\n }\n\n return schema;\n}\n\nexport function renderTypeAlias(alias: TypeAlias): JsonSchemaFragment {\n const schema = renderFieldType(alias.type);\n if (alias.doc !== undefined) {\n return { ...schema, description: alias.doc };\n }\n return schema;\n}\n","/**\n * `Constraints` -> JSON Schema keywords, symmetric to `parser-openapi`'s\n * `field()`. `unique` has no JSON Schema keyword for object-property\n * uniqueness (`uniqueItems` only applies to arrays) and is dropped silently.\n */\nimport type { Constraints, FieldType, StringFormat } from '@kurotako/ir';\nimport type { JsonSchemaFragment } from './schema.js';\n\n/**\n * Inverse of `parser-openapi`'s `STRING_FORMATS`. `datetime`/`date`/`uuid`/\n * `cuid`/`cuid2`/`ulid` never reach here as a `Constraints.format` value —\n * those scalars carry their own fixed format via `renderFieldType` instead;\n * an unrecognised format (`cuid`/`cuid2`/`ulid`) emits no keyword, same\n * escape `parser-openapi` takes in the reverse direction.\n */\nconst FORMAT_KEYWORDS: Partial<Record<StringFormat, string>> = {\n email: 'email',\n url: 'uri',\n ipv4: 'ipv4',\n ipv6: 'ipv6',\n time: 'time',\n duration: 'duration',\n};\n\nexport function applyConstraints(\n schema: JsonSchemaFragment,\n type: FieldType,\n constraints: Constraints,\n): JsonSchemaFragment {\n const out: JsonSchemaFragment = { ...schema };\n\n if (constraints.min !== undefined) {\n out.minimum = constraints.min;\n }\n if (constraints.max !== undefined) {\n out.maximum = constraints.max;\n }\n if (constraints.minLength !== undefined) {\n out.minLength = constraints.minLength;\n }\n if (constraints.maxLength !== undefined) {\n out.maxLength = constraints.maxLength;\n }\n if (constraints.regex !== undefined) {\n out.pattern = constraints.regex;\n }\n if (\n constraints.format !== undefined &&\n type.kind === 'scalar' &&\n type.scalar === 'string'\n ) {\n const keyword = FORMAT_KEYWORDS[constraints.format];\n if (keyword !== undefined) {\n out.format = keyword;\n }\n }\n\n return out;\n}\n","/**\n * Assemble the full `OpenApiDocument` object for one namespace's `SourceIR`.\n *\n * `components.schemas` covers both `source.entities` and `source.typeAliases`\n * (unions, refs-only aliases, enums) — the IR's cross-reference pass already\n * guarantees the two never collide within one source (`docs/ir.md` \"Closed\n * points\" §3), so both can be merged into the same flat namespace safely.\n */\nimport type { Logger } from '@kurotako/core';\nimport type { EnumDef, SourceIR } from '@kurotako/ir';\nimport { OpenApiGenInvalidSchemaNameError } from './errors.js';\nimport type { OpenApiGeneratorOptions } from './options.js';\nimport { renderEntity, renderTypeAlias } from './render/entity.js';\nimport type { JsonSchemaFragment } from './render/schema.js';\n\n/**\n * Every reachable `EnumDef` of a source (source-level + entity-local, entity\n * shadowing on a name collision), each rendered as its own\n * `components/schemas` entry (`{ type: 'string', enum: [...] }`).\n *\n * Not called out as its own bullet in the technical design's \"Document\n * assembly\" section, but required for `$ref` resolvability: `render/schema.ts`\n * maps `FieldType.kind === 'enum'` to a bare `$ref` (mirroring\n * `parser-openapi`'s self-referencing `typeAliases[name] = { kind: 'enum', ref:\n * name }` entry), so every enum name needs a real schema behind it — the\n * synthetic self-referencing alias is skipped in favour of this one.\n */\nfunction collectEnums(source: SourceIR): EnumDef[] {\n const byName = new Map<string, EnumDef>();\n for (const def of Object.values(source.enums)) {\n byName.set(def.name, def);\n }\n for (const entity of Object.values(source.entities)) {\n for (const def of Object.values(entity.enums ?? {})) {\n byName.set(def.name, def);\n }\n }\n return [...byName.values()];\n}\n\nfunction renderEnum(def: EnumDef): JsonSchemaFragment {\n const schema: JsonSchemaFragment = {\n type: 'string',\n enum: def.values.map((value) => value.name),\n };\n if (def.doc !== undefined) {\n schema.description = def.doc;\n }\n return schema;\n}\n\nexport interface OpenApiDocument {\n openapi: string;\n info: { title: string; version: string };\n paths: Record<string, never>;\n components: { schemas: Record<string, JsonSchemaFragment> };\n}\n\n/** OpenAPI restricts a `components/schemas` key to this pattern. */\nconst SCHEMA_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;\n\nfunction pinnedOpenapiVersion(version: '3.0' | '3.1'): string {\n return version === '3.0' ? '3.0.3' : '3.1.0';\n}\n\nexport function buildDocument(\n source: SourceIR,\n options: OpenApiGeneratorOptions,\n namespace: string,\n logger?: Logger,\n): OpenApiDocument {\n const schemas: Record<string, JsonSchemaFragment> = {};\n const enumsByName = new Map(\n collectEnums(source).map((def) => [def.name, def]),\n );\n const names = [\n ...Object.keys(source.entities),\n ...enumsByName.keys(),\n ...Object.keys(source.typeAliases ?? {}).filter(\n (name) => !enumsByName.has(name) && source.entities[name] === undefined,\n ),\n ].sort((a, b) => a.localeCompare(b));\n\n for (const name of names) {\n if (!SCHEMA_NAME_PATTERN.test(name)) {\n throw new OpenApiGenInvalidSchemaNameError(name);\n }\n const entity = source.entities[name];\n if (entity !== undefined) {\n schemas[name] = renderEntity(entity, {\n openapiVersion: options.openapiVersion,\n namespace,\n logger,\n });\n continue;\n }\n const enumDef = enumsByName.get(name);\n if (enumDef !== undefined) {\n schemas[name] = renderEnum(enumDef);\n continue;\n }\n const alias = source.typeAliases?.[name];\n if (alias !== undefined) {\n schemas[name] = renderTypeAlias(alias);\n }\n }\n\n return {\n openapi: pinnedOpenapiVersion(options.openapiVersion),\n info: {\n title: options.title ?? namespace,\n version: options.version,\n },\n paths: {},\n components: { schemas },\n };\n}\n","/**\n * Valibot schema for `@kurotako/gen-openapi`'s `options`, plus the inferred type.\n * `@kurotako/config` validates a config entry's `options` against this schema and\n * curries it away before `@kurotako/core` sees the generator.\n *\n * `title` has no static default here: it depends on the namespace, which this\n * schema cannot see — `generator.ts` applies `options.title ?? namespace`.\n */\nimport * as v from 'valibot';\n\nexport const OpenApiGeneratorOptions = v.object({\n openapiVersion: v.optional(v.picklist(['3.0', '3.1']), '3.1'),\n format: v.optional(v.picklist(['json', 'yaml']), 'json'),\n title: v.optional(v.string()),\n version: v.optional(v.string(), '0.0.0'),\n});\n\nexport type OpenApiGeneratorOptions = v.InferOutput<\n typeof OpenApiGeneratorOptions\n>;\n","/**\n * `OpenApiDocument` -> JSON or YAML string.\n */\nimport * as YAML from 'yaml';\nimport type { OpenApiDocument } from './document.js';\nimport type { OpenApiGeneratorOptions } from './options.js';\n\nexport function serialize(\n document: OpenApiDocument,\n format: OpenApiGeneratorOptions['format'],\n): string {\n if (format === 'yaml') {\n return YAML.stringify(document);\n }\n return `${JSON.stringify(document, null, 2)}\\n`;\n}\n"],"mappings":"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","names":["import_ir","schema"]}
@@ -0,0 +1,66 @@
1
+ import * as v from 'valibot';
2
+ import { GenerateContext, GenOutput } from '@kurotako/core';
3
+
4
+ /**
5
+ * `@kurotako/gen-openapi` error classes.
6
+ *
7
+ * `OpenApiGenError` is a plain `Error` subclass carrying a stable `code`; the
8
+ * OpenAPI generator has no dependency on `@kurotako/core` at runtime, and
9
+ * `@kurotako/core` wraps any throw from `generate()` as a `DriverError` for the
10
+ * CLI's single `instanceof TakoError` catch.
11
+ *
12
+ * Codes: `openapi_gen_invalid_schema_name`.
13
+ */
14
+ declare class OpenApiGenError extends Error {
15
+ readonly code: string;
16
+ constructor(code: string, message: string, options?: {
17
+ cause?: unknown;
18
+ });
19
+ }
20
+ /**
21
+ * An entity or type-alias name is not a valid OpenAPI `components/schemas` key
22
+ * (the spec restricts it to `^[a-zA-Z0-9._-]+$`). `parser-openapi` never
23
+ * produces such a name, but a name coming from `parser-prisma` (arbitrary model
24
+ * name) is unconstrained.
25
+ */
26
+ declare class OpenApiGenInvalidSchemaNameError extends OpenApiGenError {
27
+ readonly schemaName: string;
28
+ constructor(schemaName: string);
29
+ }
30
+
31
+ declare const openapiGenerator: {
32
+ name: string;
33
+ dependsOn?: string[];
34
+ optionalDependsOn?: string[];
35
+ optionsSchema?: v.ObjectSchema<{
36
+ readonly openapiVersion: v.OptionalSchema<v.PicklistSchema<["3.0", "3.1"], undefined>, "3.1">;
37
+ readonly format: v.OptionalSchema<v.PicklistSchema<["json", "yaml"], undefined>, "json">;
38
+ readonly title: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
39
+ readonly version: v.OptionalSchema<v.StringSchema<undefined>, "0.0.0">;
40
+ }, undefined> | undefined;
41
+ generate(ctx: GenerateContext, options: {
42
+ openapiVersion: "3.0" | "3.1";
43
+ format: "json" | "yaml";
44
+ title?: string | undefined;
45
+ version: string;
46
+ }): GenOutput | Promise<GenOutput>;
47
+ };
48
+
49
+ /**
50
+ * Valibot schema for `@kurotako/gen-openapi`'s `options`, plus the inferred type.
51
+ * `@kurotako/config` validates a config entry's `options` against this schema and
52
+ * curries it away before `@kurotako/core` sees the generator.
53
+ *
54
+ * `title` has no static default here: it depends on the namespace, which this
55
+ * schema cannot see — `generator.ts` applies `options.title ?? namespace`.
56
+ */
57
+
58
+ declare const OpenApiGeneratorOptions: v.ObjectSchema<{
59
+ readonly openapiVersion: v.OptionalSchema<v.PicklistSchema<["3.0", "3.1"], undefined>, "3.1">;
60
+ readonly format: v.OptionalSchema<v.PicklistSchema<["json", "yaml"], undefined>, "json">;
61
+ readonly title: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
62
+ readonly version: v.OptionalSchema<v.StringSchema<undefined>, "0.0.0">;
63
+ }, undefined>;
64
+ type OpenApiGeneratorOptions = v.InferOutput<typeof OpenApiGeneratorOptions>;
65
+
66
+ export { OpenApiGenError, OpenApiGenInvalidSchemaNameError, OpenApiGeneratorOptions, openapiGenerator };
@@ -0,0 +1,66 @@
1
+ import * as v from 'valibot';
2
+ import { GenerateContext, GenOutput } from '@kurotako/core';
3
+
4
+ /**
5
+ * `@kurotako/gen-openapi` error classes.
6
+ *
7
+ * `OpenApiGenError` is a plain `Error` subclass carrying a stable `code`; the
8
+ * OpenAPI generator has no dependency on `@kurotako/core` at runtime, and
9
+ * `@kurotako/core` wraps any throw from `generate()` as a `DriverError` for the
10
+ * CLI's single `instanceof TakoError` catch.
11
+ *
12
+ * Codes: `openapi_gen_invalid_schema_name`.
13
+ */
14
+ declare class OpenApiGenError extends Error {
15
+ readonly code: string;
16
+ constructor(code: string, message: string, options?: {
17
+ cause?: unknown;
18
+ });
19
+ }
20
+ /**
21
+ * An entity or type-alias name is not a valid OpenAPI `components/schemas` key
22
+ * (the spec restricts it to `^[a-zA-Z0-9._-]+$`). `parser-openapi` never
23
+ * produces such a name, but a name coming from `parser-prisma` (arbitrary model
24
+ * name) is unconstrained.
25
+ */
26
+ declare class OpenApiGenInvalidSchemaNameError extends OpenApiGenError {
27
+ readonly schemaName: string;
28
+ constructor(schemaName: string);
29
+ }
30
+
31
+ declare const openapiGenerator: {
32
+ name: string;
33
+ dependsOn?: string[];
34
+ optionalDependsOn?: string[];
35
+ optionsSchema?: v.ObjectSchema<{
36
+ readonly openapiVersion: v.OptionalSchema<v.PicklistSchema<["3.0", "3.1"], undefined>, "3.1">;
37
+ readonly format: v.OptionalSchema<v.PicklistSchema<["json", "yaml"], undefined>, "json">;
38
+ readonly title: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
39
+ readonly version: v.OptionalSchema<v.StringSchema<undefined>, "0.0.0">;
40
+ }, undefined> | undefined;
41
+ generate(ctx: GenerateContext, options: {
42
+ openapiVersion: "3.0" | "3.1";
43
+ format: "json" | "yaml";
44
+ title?: string | undefined;
45
+ version: string;
46
+ }): GenOutput | Promise<GenOutput>;
47
+ };
48
+
49
+ /**
50
+ * Valibot schema for `@kurotako/gen-openapi`'s `options`, plus the inferred type.
51
+ * `@kurotako/config` validates a config entry's `options` against this schema and
52
+ * curries it away before `@kurotako/core` sees the generator.
53
+ *
54
+ * `title` has no static default here: it depends on the namespace, which this
55
+ * schema cannot see — `generator.ts` applies `options.title ?? namespace`.
56
+ */
57
+
58
+ declare const OpenApiGeneratorOptions: v.ObjectSchema<{
59
+ readonly openapiVersion: v.OptionalSchema<v.PicklistSchema<["3.0", "3.1"], undefined>, "3.1">;
60
+ readonly format: v.OptionalSchema<v.PicklistSchema<["json", "yaml"], undefined>, "json">;
61
+ readonly title: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
62
+ readonly version: v.OptionalSchema<v.StringSchema<undefined>, "0.0.0">;
63
+ }, undefined>;
64
+ type OpenApiGeneratorOptions = v.InferOutput<typeof OpenApiGeneratorOptions>;
65
+
66
+ export { OpenApiGenError, OpenApiGenInvalidSchemaNameError, OpenApiGeneratorOptions, openapiGenerator };
package/dist/index.js ADDED
@@ -0,0 +1,348 @@
1
+ // src/errors.ts
2
+ var OpenApiGenError = class extends Error {
3
+ code;
4
+ constructor(code, message, options) {
5
+ super(message, options);
6
+ this.name = new.target.name;
7
+ this.code = code;
8
+ }
9
+ };
10
+ var OpenApiGenInvalidSchemaNameError = class extends OpenApiGenError {
11
+ schemaName;
12
+ constructor(schemaName) {
13
+ super(
14
+ "openapi_gen_invalid_schema_name",
15
+ `'${schemaName}' is not a valid OpenAPI components/schemas key (must match ^[a-zA-Z0-9._-]+$)`
16
+ );
17
+ this.schemaName = schemaName;
18
+ }
19
+ };
20
+
21
+ // src/generator.ts
22
+ import { defineGenerator } from "@kurotako/config";
23
+
24
+ // src/artifact.ts
25
+ import { iterEntities } from "@kurotako/ir";
26
+
27
+ // src/render/schema.ts
28
+ function schemaRef(name) {
29
+ return `#/components/schemas/${name}`;
30
+ }
31
+ function scalarSchema(scalar) {
32
+ switch (scalar) {
33
+ case "string":
34
+ return { type: "string" };
35
+ case "boolean":
36
+ return { type: "boolean" };
37
+ case "int":
38
+ return { type: "integer" };
39
+ case "bigint":
40
+ return { type: "integer", format: "int64" };
41
+ case "float":
42
+ return { type: "number" };
43
+ case "decimal":
44
+ return { type: "number" };
45
+ case "date":
46
+ return { type: "string", format: "date" };
47
+ case "datetime":
48
+ return { type: "string", format: "date-time" };
49
+ case "uuid":
50
+ return { type: "string", format: "uuid" };
51
+ case "bytes":
52
+ return { type: "string", format: "byte" };
53
+ case "json":
54
+ return {};
55
+ }
56
+ }
57
+ function renderFieldType(type) {
58
+ switch (type.kind) {
59
+ case "scalar":
60
+ return scalarSchema(type.scalar);
61
+ case "enum":
62
+ return { $ref: schemaRef(type.ref) };
63
+ case "unknown":
64
+ return {};
65
+ case "ref":
66
+ return { $ref: schemaRef(type.ref) };
67
+ case "map":
68
+ return {
69
+ type: "object",
70
+ additionalProperties: type.value.kind === "unknown" ? true : renderFieldType(type.value)
71
+ };
72
+ case "array":
73
+ return { type: "array", items: renderFieldType(type.element) };
74
+ case "union": {
75
+ const schema = {
76
+ oneOf: type.variants.map((variant) => renderFieldType(variant))
77
+ };
78
+ if (type.discriminator !== void 0) {
79
+ const discriminator = {
80
+ propertyName: type.discriminator.propertyName
81
+ };
82
+ if (type.discriminator.mapping !== void 0) {
83
+ discriminator.mapping = Object.fromEntries(
84
+ Object.entries(type.discriminator.mapping).map(([key, target]) => [
85
+ key,
86
+ schemaRef(target)
87
+ ])
88
+ );
89
+ }
90
+ schema.discriminator = discriminator;
91
+ }
92
+ return schema;
93
+ }
94
+ }
95
+ }
96
+ function wrapListed(schema, type) {
97
+ if (type.kind === "array") {
98
+ return schema;
99
+ }
100
+ return { type: "array", items: schema };
101
+ }
102
+ function hasRef(schema) {
103
+ return typeof schema.$ref === "string";
104
+ }
105
+ function wrapNullable(schema, openapiVersion) {
106
+ if (openapiVersion === "3.0") {
107
+ if (hasRef(schema)) {
108
+ return { allOf: [schema], nullable: true };
109
+ }
110
+ return { ...schema, nullable: true };
111
+ }
112
+ if (hasRef(schema) || schema.oneOf !== void 0) {
113
+ return { oneOf: [schema, { type: "null" }] };
114
+ }
115
+ if (typeof schema.type === "string") {
116
+ return { ...schema, type: [schema.type, "null"] };
117
+ }
118
+ return { oneOf: [schema, { type: "null" }] };
119
+ }
120
+
121
+ // src/artifact.ts
122
+ function buildArtifact(ir, options) {
123
+ const ext = options.format === "yaml" ? "yaml" : "json";
124
+ const entities = {};
125
+ for (const { namespace, entity } of iterEntities(ir)) {
126
+ entities[`${namespace}.${entity.name}`] = {
127
+ module: `${namespace}/openapi/openapi.${ext}`,
128
+ symbols: { schema: schemaRef(entity.name) }
129
+ };
130
+ }
131
+ return { entities };
132
+ }
133
+
134
+ // src/render/entity.ts
135
+ import { isCrossSource } from "@kurotako/ir";
136
+
137
+ // src/render/constraints.ts
138
+ var FORMAT_KEYWORDS = {
139
+ email: "email",
140
+ url: "uri",
141
+ ipv4: "ipv4",
142
+ ipv6: "ipv6",
143
+ time: "time",
144
+ duration: "duration"
145
+ };
146
+ function applyConstraints(schema, type, constraints) {
147
+ const out = { ...schema };
148
+ if (constraints.min !== void 0) {
149
+ out.minimum = constraints.min;
150
+ }
151
+ if (constraints.max !== void 0) {
152
+ out.maximum = constraints.max;
153
+ }
154
+ if (constraints.minLength !== void 0) {
155
+ out.minLength = constraints.minLength;
156
+ }
157
+ if (constraints.maxLength !== void 0) {
158
+ out.maxLength = constraints.maxLength;
159
+ }
160
+ if (constraints.regex !== void 0) {
161
+ out.pattern = constraints.regex;
162
+ }
163
+ if (constraints.format !== void 0 && type.kind === "scalar" && type.scalar === "string") {
164
+ const keyword = FORMAT_KEYWORDS[constraints.format];
165
+ if (keyword !== void 0) {
166
+ out.format = keyword;
167
+ }
168
+ }
169
+ return out;
170
+ }
171
+
172
+ // src/render/entity.ts
173
+ function renderRelation(rel, opts) {
174
+ if (isCrossSource(opts.namespace, rel)) {
175
+ opts.logger?.debug(
176
+ `gen-openapi: relation '${rel.name}' targets another source ('${rel.target.namespace}.${rel.target.entity}'); omitting it from the emitted schema`
177
+ );
178
+ return null;
179
+ }
180
+ const target = schemaRef(rel.target.entity);
181
+ let schema = rel.cardinality === "many" ? { type: "array", items: { $ref: target } } : { $ref: target };
182
+ if (rel.cardinality === "one" && rel.optional) {
183
+ schema = wrapNullable(schema, opts.openapiVersion);
184
+ }
185
+ return schema;
186
+ }
187
+ function renderEntity(entity, opts) {
188
+ const properties = {};
189
+ const required = [];
190
+ for (const field of entity.fields) {
191
+ let schema2 = renderFieldType(field.type);
192
+ schema2 = applyConstraints(schema2, field.type, field.constraints);
193
+ if (field.list) {
194
+ schema2 = wrapListed(schema2, field.type);
195
+ }
196
+ if (field.nullable) {
197
+ schema2 = wrapNullable(schema2, opts.openapiVersion);
198
+ }
199
+ properties[field.name] = schema2;
200
+ if (!field.optional) {
201
+ required.push(field.name);
202
+ }
203
+ }
204
+ for (const relation of entity.relations) {
205
+ const schema2 = renderRelation(relation, opts);
206
+ if (schema2 !== null) {
207
+ properties[relation.name] = schema2;
208
+ }
209
+ }
210
+ const schema = {
211
+ type: "object",
212
+ properties
213
+ };
214
+ if (required.length > 0) {
215
+ schema.required = required;
216
+ }
217
+ if (entity.additionalProperties !== void 0) {
218
+ schema.additionalProperties = entity.additionalProperties.kind === "unknown" ? true : renderFieldType(entity.additionalProperties);
219
+ }
220
+ if (entity.doc !== void 0) {
221
+ schema.description = entity.doc;
222
+ }
223
+ return schema;
224
+ }
225
+ function renderTypeAlias(alias) {
226
+ const schema = renderFieldType(alias.type);
227
+ if (alias.doc !== void 0) {
228
+ return { ...schema, description: alias.doc };
229
+ }
230
+ return schema;
231
+ }
232
+
233
+ // src/document.ts
234
+ function collectEnums(source) {
235
+ const byName = /* @__PURE__ */ new Map();
236
+ for (const def of Object.values(source.enums)) {
237
+ byName.set(def.name, def);
238
+ }
239
+ for (const entity of Object.values(source.entities)) {
240
+ for (const def of Object.values(entity.enums ?? {})) {
241
+ byName.set(def.name, def);
242
+ }
243
+ }
244
+ return [...byName.values()];
245
+ }
246
+ function renderEnum(def) {
247
+ const schema = {
248
+ type: "string",
249
+ enum: def.values.map((value) => value.name)
250
+ };
251
+ if (def.doc !== void 0) {
252
+ schema.description = def.doc;
253
+ }
254
+ return schema;
255
+ }
256
+ var SCHEMA_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
257
+ function pinnedOpenapiVersion(version) {
258
+ return version === "3.0" ? "3.0.3" : "3.1.0";
259
+ }
260
+ function buildDocument(source, options, namespace, logger) {
261
+ const schemas = {};
262
+ const enumsByName = new Map(
263
+ collectEnums(source).map((def) => [def.name, def])
264
+ );
265
+ const names = [
266
+ ...Object.keys(source.entities),
267
+ ...enumsByName.keys(),
268
+ ...Object.keys(source.typeAliases ?? {}).filter(
269
+ (name) => !enumsByName.has(name) && source.entities[name] === void 0
270
+ )
271
+ ].sort((a, b) => a.localeCompare(b));
272
+ for (const name of names) {
273
+ if (!SCHEMA_NAME_PATTERN.test(name)) {
274
+ throw new OpenApiGenInvalidSchemaNameError(name);
275
+ }
276
+ const entity = source.entities[name];
277
+ if (entity !== void 0) {
278
+ schemas[name] = renderEntity(entity, {
279
+ openapiVersion: options.openapiVersion,
280
+ namespace,
281
+ logger
282
+ });
283
+ continue;
284
+ }
285
+ const enumDef = enumsByName.get(name);
286
+ if (enumDef !== void 0) {
287
+ schemas[name] = renderEnum(enumDef);
288
+ continue;
289
+ }
290
+ const alias = source.typeAliases?.[name];
291
+ if (alias !== void 0) {
292
+ schemas[name] = renderTypeAlias(alias);
293
+ }
294
+ }
295
+ return {
296
+ openapi: pinnedOpenapiVersion(options.openapiVersion),
297
+ info: {
298
+ title: options.title ?? namespace,
299
+ version: options.version
300
+ },
301
+ paths: {},
302
+ components: { schemas }
303
+ };
304
+ }
305
+
306
+ // src/options.ts
307
+ import * as v from "valibot";
308
+ var OpenApiGeneratorOptions = v.object({
309
+ openapiVersion: v.optional(v.picklist(["3.0", "3.1"]), "3.1"),
310
+ format: v.optional(v.picklist(["json", "yaml"]), "json"),
311
+ title: v.optional(v.string()),
312
+ version: v.optional(v.string(), "0.0.0")
313
+ });
314
+
315
+ // src/serialize.ts
316
+ import * as YAML from "yaml";
317
+ function serialize(document, format) {
318
+ if (format === "yaml") {
319
+ return YAML.stringify(document);
320
+ }
321
+ return `${JSON.stringify(document, null, 2)}
322
+ `;
323
+ }
324
+
325
+ // src/generator.ts
326
+ var openapiGenerator = defineGenerator({
327
+ name: "openapi",
328
+ optionsSchema: OpenApiGeneratorOptions,
329
+ generate(ctx, options) {
330
+ const files = [];
331
+ const ext = options.format === "yaml" ? "yaml" : "json";
332
+ for (const [namespace, source] of Object.entries(ctx.ir.sources)) {
333
+ const document = buildDocument(source, options, namespace, ctx.logger);
334
+ files.push({
335
+ path: `${namespace}/openapi/openapi.${ext}`,
336
+ content: serialize(document, options.format)
337
+ });
338
+ }
339
+ return { files, artifact: buildArtifact(ctx.ir, options) };
340
+ }
341
+ });
342
+ export {
343
+ OpenApiGenError,
344
+ OpenApiGenInvalidSchemaNameError,
345
+ OpenApiGeneratorOptions,
346
+ openapiGenerator
347
+ };
348
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/generator.ts","../src/artifact.ts","../src/render/schema.ts","../src/render/entity.ts","../src/render/constraints.ts","../src/document.ts","../src/options.ts","../src/serialize.ts"],"sourcesContent":["/**\n * `@kurotako/gen-openapi` error classes.\n *\n * `OpenApiGenError` is a plain `Error` subclass carrying a stable `code`; the\n * OpenAPI generator has no dependency on `@kurotako/core` at runtime, and\n * `@kurotako/core` wraps any throw from `generate()` as a `DriverError` for the\n * CLI's single `instanceof TakoError` catch.\n *\n * Codes: `openapi_gen_invalid_schema_name`.\n */\n\nexport class OpenApiGenError extends Error {\n readonly code: string;\n\n constructor(code: string, message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = new.target.name;\n this.code = code;\n }\n}\n\n/**\n * An entity or type-alias name is not a valid OpenAPI `components/schemas` key\n * (the spec restricts it to `^[a-zA-Z0-9._-]+$`). `parser-openapi` never\n * produces such a name, but a name coming from `parser-prisma` (arbitrary model\n * name) is unconstrained.\n */\nexport class OpenApiGenInvalidSchemaNameError extends OpenApiGenError {\n readonly schemaName: string;\n\n constructor(schemaName: string) {\n super(\n 'openapi_gen_invalid_schema_name',\n `'${schemaName}' is not a valid OpenAPI components/schemas key (must match ^[a-zA-Z0-9._-]+$)`,\n );\n this.schemaName = schemaName;\n }\n}\n","/**\n * `openapiGenerator` — the `@kurotako/gen-openapi` driver.\n *\n * `@kurotako/config` validates `options` against `optionsSchema` and curries it\n * away; `@kurotako/core` then calls `generate(ctx)` with a namespace-filtered IR.\n * One `VirtualFile` per namespace: a single `openapi.<ext>` document, not one\n * file per entity — a spec is normally not split, and external tooling\n * consuming it expects one document.\n */\nimport { defineGenerator } from '@kurotako/config';\nimport type { GenerateContext, GenOutput, VirtualFile } from '@kurotako/core';\nimport { buildArtifact } from './artifact.js';\nimport { buildDocument } from './document.js';\nimport { OpenApiGeneratorOptions } from './options.js';\nimport { serialize } from './serialize.js';\n\nexport const openapiGenerator = defineGenerator({\n name: 'openapi',\n optionsSchema: OpenApiGeneratorOptions,\n\n generate(ctx: GenerateContext, options): GenOutput {\n const files: VirtualFile[] = [];\n const ext = options.format === 'yaml' ? 'yaml' : 'json';\n\n for (const [namespace, source] of Object.entries(ctx.ir.sources)) {\n const document = buildDocument(source, options, namespace, ctx.logger);\n files.push({\n path: `${namespace}/openapi/openapi.${ext}`,\n content: serialize(document, options.format),\n });\n }\n\n return { files, artifact: buildArtifact(ctx.ir, options) };\n },\n});\n","/**\n * Assemble the `GeneratorArtifact` — one `EntitySymbols` per `${namespace}.${entity}`,\n * pointing at the emitted document and the entity's `#/components/schemas/<name>`\n * pointer. No `dependsOn` (this generator reads only the IR), no\n * `peerDependencies` (the emitted document has no runtime import surface), no\n * `extra` (no consumer needs OpenAPI-specific artifact data yet).\n */\nimport type { EntitySymbols, GeneratorArtifact } from '@kurotako/core';\nimport type { IR } from '@kurotako/ir';\nimport { iterEntities } from '@kurotako/ir';\nimport type { OpenApiGeneratorOptions } from './options.js';\nimport { schemaRef } from './render/schema.js';\n\nexport function buildArtifact(\n ir: IR,\n options: OpenApiGeneratorOptions,\n): GeneratorArtifact {\n const ext = options.format === 'yaml' ? 'yaml' : 'json';\n const entities: Record<string, EntitySymbols> = {};\n\n for (const { namespace, entity } of iterEntities(ir)) {\n entities[`${namespace}.${entity.name}`] = {\n module: `${namespace}/openapi/openapi.${ext}`,\n symbols: { schema: schemaRef(entity.name) },\n };\n }\n\n return { entities };\n}\n","/**\n * `FieldType` -> JSON Schema fragment, symmetric to `parser-openapi`'s\n * `mapSchema` (inverted). Also carries the `Field.list` and nullability\n * wrapping, which are properties of a `Field`/`Relation`, not of a\n * `FieldType` — applied by the caller once the base fragment is built.\n */\nimport type { FieldType, ScalarType } from '@kurotako/ir';\n\nexport type JsonSchemaFragment = Record<string, unknown>;\n\nexport function schemaRef(name: string): string {\n return `#/components/schemas/${name}`;\n}\n\nfunction scalarSchema(scalar: ScalarType): JsonSchemaFragment {\n switch (scalar) {\n case 'string':\n return { type: 'string' };\n case 'boolean':\n return { type: 'boolean' };\n case 'int':\n return { type: 'integer' };\n case 'bigint':\n return { type: 'integer', format: 'int64' };\n case 'float':\n return { type: 'number' };\n case 'decimal':\n return { type: 'number' };\n case 'date':\n return { type: 'string', format: 'date' };\n case 'datetime':\n return { type: 'string', format: 'date-time' };\n case 'uuid':\n return { type: 'string', format: 'uuid' };\n case 'bytes':\n return { type: 'string', format: 'byte' };\n case 'json':\n return {};\n }\n}\n\n/** Pure recursive mapping of a `FieldType` to a JSON Schema fragment. */\nexport function renderFieldType(type: FieldType): JsonSchemaFragment {\n switch (type.kind) {\n case 'scalar':\n return scalarSchema(type.scalar);\n case 'enum':\n return { $ref: schemaRef(type.ref) };\n case 'unknown':\n return {};\n case 'ref':\n return { $ref: schemaRef(type.ref) };\n case 'map':\n return {\n type: 'object',\n additionalProperties:\n type.value.kind === 'unknown' ? true : renderFieldType(type.value),\n };\n case 'array':\n return { type: 'array', items: renderFieldType(type.element) };\n case 'union': {\n const schema: JsonSchemaFragment = {\n oneOf: type.variants.map((variant) => renderFieldType(variant)),\n };\n if (type.discriminator !== undefined) {\n const discriminator: JsonSchemaFragment = {\n propertyName: type.discriminator.propertyName,\n };\n if (type.discriminator.mapping !== undefined) {\n discriminator.mapping = Object.fromEntries(\n Object.entries(type.discriminator.mapping).map(([key, target]) => [\n key,\n schemaRef(target),\n ]),\n );\n }\n schema.discriminator = discriminator;\n }\n return schema;\n }\n }\n}\n\n/**\n * `Field.list` (legacy boolean, still present alongside `FieldType.kind ===\n * 'array'`) wraps the mapped schema one more time in an array — skipped when\n * `type.kind` is already `'array'`, to avoid a double array-of-array.\n */\nexport function wrapListed(\n schema: JsonSchemaFragment,\n type: FieldType,\n): JsonSchemaFragment {\n if (type.kind === 'array') {\n return schema;\n }\n return { type: 'array', items: schema };\n}\n\nfunction hasRef(schema: JsonSchemaFragment): boolean {\n return typeof schema.$ref === 'string';\n}\n\n/**\n * Nullability wrapping for a mapped schema, per OpenAPI version.\n *\n * - 3.1: widen `type` to include `'null'`; a `$ref` or `oneOf` (ref / union /\n * enum, and anything with no bare `type` keyword to widen) wraps instead in\n * `{ oneOf: [<schema>, { type: 'null' }] }`.\n * - 3.0: add `nullable: true` as a sibling keyword; a `$ref` cannot carry a\n * sibling per the 3.0 spec, so it wraps in `{ allOf: [<schema>], nullable:\n * true }` instead.\n */\nexport function wrapNullable(\n schema: JsonSchemaFragment,\n openapiVersion: '3.0' | '3.1',\n): JsonSchemaFragment {\n if (openapiVersion === '3.0') {\n if (hasRef(schema)) {\n return { allOf: [schema], nullable: true };\n }\n return { ...schema, nullable: true };\n }\n\n if (hasRef(schema) || schema.oneOf !== undefined) {\n return { oneOf: [schema, { type: 'null' }] };\n }\n if (typeof schema.type === 'string') {\n return { ...schema, type: [schema.type, 'null'] };\n }\n return { oneOf: [schema, { type: 'null' }] };\n}\n","/**\n * `Entity` -> a `components/schemas/<Entity>` object: own fields (via\n * `render/schema.ts` + `render/constraints.ts`), `required`, `additionalProperties`\n * and relations rendered nested via `$ref` (the \"deep\" family in `gen-zod`'s\n * vocabulary — see `packages/gen-zod/src/render/relations.ts`).\n *\n * Cross-source relations degrade to omitting the relation property entirely\n * (logged at `debug`), same choice `gen-zod`'s deep family makes: v1 cannot\n * reference a schema defined in another namespace's document. Circular\n * relations need no special handling — `$ref` in JSON Schema is natively\n * self- and mutually-recursive.\n */\nimport type { Logger } from '@kurotako/core';\nimport type { Entity, Relation, TypeAlias } from '@kurotako/ir';\nimport { isCrossSource } from '@kurotako/ir';\nimport { applyConstraints } from './constraints.js';\nimport {\n type JsonSchemaFragment,\n renderFieldType,\n schemaRef,\n wrapListed,\n wrapNullable,\n} from './schema.js';\n\nexport interface RenderEntityOptions {\n openapiVersion: '3.0' | '3.1';\n /** The namespace the entity belongs to, for cross-source relation detection. */\n namespace: string;\n logger?: Logger;\n}\n\nfunction renderRelation(\n rel: Relation,\n opts: RenderEntityOptions,\n): JsonSchemaFragment | null {\n if (isCrossSource(opts.namespace, rel)) {\n opts.logger?.debug(\n `gen-openapi: relation '${rel.name}' targets another source ('${rel.target.namespace}.${rel.target.entity}'); omitting it from the emitted schema`,\n );\n return null;\n }\n\n const target = schemaRef(rel.target.entity);\n let schema: JsonSchemaFragment =\n rel.cardinality === 'many'\n ? { type: 'array', items: { $ref: target } }\n : { $ref: target };\n\n if (rel.cardinality === 'one' && rel.optional) {\n schema = wrapNullable(schema, opts.openapiVersion);\n }\n\n return schema;\n}\n\nexport function renderEntity(\n entity: Entity,\n opts: RenderEntityOptions,\n): JsonSchemaFragment {\n const properties: Record<string, JsonSchemaFragment> = {};\n const required: string[] = [];\n\n for (const field of entity.fields) {\n let schema = renderFieldType(field.type);\n schema = applyConstraints(schema, field.type, field.constraints);\n if (field.list) {\n schema = wrapListed(schema, field.type);\n }\n if (field.nullable) {\n schema = wrapNullable(schema, opts.openapiVersion);\n }\n properties[field.name] = schema;\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n for (const relation of entity.relations) {\n const schema = renderRelation(relation, opts);\n if (schema !== null) {\n properties[relation.name] = schema;\n }\n }\n\n const schema: JsonSchemaFragment = {\n type: 'object',\n properties,\n };\n if (required.length > 0) {\n schema.required = required;\n }\n if (entity.additionalProperties !== undefined) {\n schema.additionalProperties =\n entity.additionalProperties.kind === 'unknown'\n ? true\n : renderFieldType(entity.additionalProperties);\n }\n if (entity.doc !== undefined) {\n schema.description = entity.doc;\n }\n\n return schema;\n}\n\nexport function renderTypeAlias(alias: TypeAlias): JsonSchemaFragment {\n const schema = renderFieldType(alias.type);\n if (alias.doc !== undefined) {\n return { ...schema, description: alias.doc };\n }\n return schema;\n}\n","/**\n * `Constraints` -> JSON Schema keywords, symmetric to `parser-openapi`'s\n * `field()`. `unique` has no JSON Schema keyword for object-property\n * uniqueness (`uniqueItems` only applies to arrays) and is dropped silently.\n */\nimport type { Constraints, FieldType, StringFormat } from '@kurotako/ir';\nimport type { JsonSchemaFragment } from './schema.js';\n\n/**\n * Inverse of `parser-openapi`'s `STRING_FORMATS`. `datetime`/`date`/`uuid`/\n * `cuid`/`cuid2`/`ulid` never reach here as a `Constraints.format` value —\n * those scalars carry their own fixed format via `renderFieldType` instead;\n * an unrecognised format (`cuid`/`cuid2`/`ulid`) emits no keyword, same\n * escape `parser-openapi` takes in the reverse direction.\n */\nconst FORMAT_KEYWORDS: Partial<Record<StringFormat, string>> = {\n email: 'email',\n url: 'uri',\n ipv4: 'ipv4',\n ipv6: 'ipv6',\n time: 'time',\n duration: 'duration',\n};\n\nexport function applyConstraints(\n schema: JsonSchemaFragment,\n type: FieldType,\n constraints: Constraints,\n): JsonSchemaFragment {\n const out: JsonSchemaFragment = { ...schema };\n\n if (constraints.min !== undefined) {\n out.minimum = constraints.min;\n }\n if (constraints.max !== undefined) {\n out.maximum = constraints.max;\n }\n if (constraints.minLength !== undefined) {\n out.minLength = constraints.minLength;\n }\n if (constraints.maxLength !== undefined) {\n out.maxLength = constraints.maxLength;\n }\n if (constraints.regex !== undefined) {\n out.pattern = constraints.regex;\n }\n if (\n constraints.format !== undefined &&\n type.kind === 'scalar' &&\n type.scalar === 'string'\n ) {\n const keyword = FORMAT_KEYWORDS[constraints.format];\n if (keyword !== undefined) {\n out.format = keyword;\n }\n }\n\n return out;\n}\n","/**\n * Assemble the full `OpenApiDocument` object for one namespace's `SourceIR`.\n *\n * `components.schemas` covers both `source.entities` and `source.typeAliases`\n * (unions, refs-only aliases, enums) — the IR's cross-reference pass already\n * guarantees the two never collide within one source (`docs/ir.md` \"Closed\n * points\" §3), so both can be merged into the same flat namespace safely.\n */\nimport type { Logger } from '@kurotako/core';\nimport type { EnumDef, SourceIR } from '@kurotako/ir';\nimport { OpenApiGenInvalidSchemaNameError } from './errors.js';\nimport type { OpenApiGeneratorOptions } from './options.js';\nimport { renderEntity, renderTypeAlias } from './render/entity.js';\nimport type { JsonSchemaFragment } from './render/schema.js';\n\n/**\n * Every reachable `EnumDef` of a source (source-level + entity-local, entity\n * shadowing on a name collision), each rendered as its own\n * `components/schemas` entry (`{ type: 'string', enum: [...] }`).\n *\n * Not called out as its own bullet in the technical design's \"Document\n * assembly\" section, but required for `$ref` resolvability: `render/schema.ts`\n * maps `FieldType.kind === 'enum'` to a bare `$ref` (mirroring\n * `parser-openapi`'s self-referencing `typeAliases[name] = { kind: 'enum', ref:\n * name }` entry), so every enum name needs a real schema behind it — the\n * synthetic self-referencing alias is skipped in favour of this one.\n */\nfunction collectEnums(source: SourceIR): EnumDef[] {\n const byName = new Map<string, EnumDef>();\n for (const def of Object.values(source.enums)) {\n byName.set(def.name, def);\n }\n for (const entity of Object.values(source.entities)) {\n for (const def of Object.values(entity.enums ?? {})) {\n byName.set(def.name, def);\n }\n }\n return [...byName.values()];\n}\n\nfunction renderEnum(def: EnumDef): JsonSchemaFragment {\n const schema: JsonSchemaFragment = {\n type: 'string',\n enum: def.values.map((value) => value.name),\n };\n if (def.doc !== undefined) {\n schema.description = def.doc;\n }\n return schema;\n}\n\nexport interface OpenApiDocument {\n openapi: string;\n info: { title: string; version: string };\n paths: Record<string, never>;\n components: { schemas: Record<string, JsonSchemaFragment> };\n}\n\n/** OpenAPI restricts a `components/schemas` key to this pattern. */\nconst SCHEMA_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;\n\nfunction pinnedOpenapiVersion(version: '3.0' | '3.1'): string {\n return version === '3.0' ? '3.0.3' : '3.1.0';\n}\n\nexport function buildDocument(\n source: SourceIR,\n options: OpenApiGeneratorOptions,\n namespace: string,\n logger?: Logger,\n): OpenApiDocument {\n const schemas: Record<string, JsonSchemaFragment> = {};\n const enumsByName = new Map(\n collectEnums(source).map((def) => [def.name, def]),\n );\n const names = [\n ...Object.keys(source.entities),\n ...enumsByName.keys(),\n ...Object.keys(source.typeAliases ?? {}).filter(\n (name) => !enumsByName.has(name) && source.entities[name] === undefined,\n ),\n ].sort((a, b) => a.localeCompare(b));\n\n for (const name of names) {\n if (!SCHEMA_NAME_PATTERN.test(name)) {\n throw new OpenApiGenInvalidSchemaNameError(name);\n }\n const entity = source.entities[name];\n if (entity !== undefined) {\n schemas[name] = renderEntity(entity, {\n openapiVersion: options.openapiVersion,\n namespace,\n logger,\n });\n continue;\n }\n const enumDef = enumsByName.get(name);\n if (enumDef !== undefined) {\n schemas[name] = renderEnum(enumDef);\n continue;\n }\n const alias = source.typeAliases?.[name];\n if (alias !== undefined) {\n schemas[name] = renderTypeAlias(alias);\n }\n }\n\n return {\n openapi: pinnedOpenapiVersion(options.openapiVersion),\n info: {\n title: options.title ?? namespace,\n version: options.version,\n },\n paths: {},\n components: { schemas },\n };\n}\n","/**\n * Valibot schema for `@kurotako/gen-openapi`'s `options`, plus the inferred type.\n * `@kurotako/config` validates a config entry's `options` against this schema and\n * curries it away before `@kurotako/core` sees the generator.\n *\n * `title` has no static default here: it depends on the namespace, which this\n * schema cannot see — `generator.ts` applies `options.title ?? namespace`.\n */\nimport * as v from 'valibot';\n\nexport const OpenApiGeneratorOptions = v.object({\n openapiVersion: v.optional(v.picklist(['3.0', '3.1']), '3.1'),\n format: v.optional(v.picklist(['json', 'yaml']), 'json'),\n title: v.optional(v.string()),\n version: v.optional(v.string(), '0.0.0'),\n});\n\nexport type OpenApiGeneratorOptions = v.InferOutput<\n typeof OpenApiGeneratorOptions\n>;\n","/**\n * `OpenApiDocument` -> JSON or YAML string.\n */\nimport * as YAML from 'yaml';\nimport type { OpenApiDocument } from './document.js';\nimport type { OpenApiGeneratorOptions } from './options.js';\n\nexport function serialize(\n document: OpenApiDocument,\n format: OpenApiGeneratorOptions['format'],\n): string {\n if (format === 'yaml') {\n return YAML.stringify(document);\n }\n return `${JSON.stringify(document, null, 2)}\\n`;\n}\n"],"mappings":"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","names":["schema"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@kurotako/gen-openapi",
3
+ "version": "0.1.0",
4
+ "description": "kurotako generator that emits an OpenAPI document (components/schemas) from the intermediate representation.",
5
+ "keywords": [
6
+ "kurotako",
7
+ "codegen",
8
+ "typescript",
9
+ "openapi",
10
+ "generator"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "Marmotz",
14
+ "homepage": "https://kurotako.marmotz.dev/",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/marmotz/kurotako.git",
18
+ "directory": "packages/gen-openapi"
19
+ },
20
+ "bugs": "https://github.com/marmotz/kurotako/issues",
21
+ "type": "module",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js",
26
+ "require": "./dist/index.cjs"
27
+ }
28
+ },
29
+ "main": "./dist/index.cjs",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "sideEffects": false,
33
+ "files": [
34
+ "dist",
35
+ "CHANGELOG.md",
36
+ "LICENSE"
37
+ ],
38
+ "engines": {
39
+ "node": ">=24"
40
+ },
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "typecheck": "tsc -b",
44
+ "test": "vitest run"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "dependencies": {
50
+ "@kurotako/ir": "^0.2.0",
51
+ "yaml": "^2.9.0",
52
+ "valibot": "1.4.2"
53
+ },
54
+ "peerDependencies": {
55
+ "@kurotako/config": "^0.1.1",
56
+ "@kurotako/core": "^0.1.1"
57
+ },
58
+ "devDependencies": {
59
+ "@apidevtools/json-schema-ref-parser": "^15.1.3",
60
+ "@kurotako/config": "^0.1.1",
61
+ "@kurotako/core": "^0.1.1",
62
+ "typescript": "5.9.3"
63
+ }
64
+ }