@contractkit/plugin-openapi 0.9.3 → 0.10.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.
@@ -5,9 +5,9 @@ $ tsup src/index.ts --format esm --sourcemap --dts && tsc --emitDeclarationOnly
5
5
  CLI tsup v8.5.1
6
6
  CLI Target: esnext
7
7
  ESM Build start
8
- ESM dist/index.js 20.43 KB
9
- ESM dist/index.js.map 47.89 KB
10
- ESM ⚡️ Build success in 76ms
8
+ ESM dist/index.js 20.83 KB
9
+ ESM dist/index.js.map 49.13 KB
10
+ ESM ⚡️ Build success in 74ms
11
11
  DTS Build start
12
- DTS ⚡️ Build success in 980ms
12
+ DTS ⚡️ Build success in 977ms
13
13
  DTS dist/index.d.ts 1.60 KB
@@ -3,21 +3,21 @@ $ vitest run --coverage
3
3
   RUN  v4.1.5 /home/runner/work/ContractKit/ContractKit/packages/plugin-openapi
4
4
  Coverage enabled with v8
5
5
 
6
- ✓ tests/codegen-openapi.test.ts (57 tests) 84ms
7
- ✓ tests/round-trip-ck.test.ts (2 tests) 153ms
6
+ ✓ tests/codegen-openapi.test.ts (60 tests) 156ms
7
+ ✓ tests/round-trip-ck.test.ts (2 tests) 137ms
8
8
 
9
9
   Test Files  2 passed (2)
10
-  Tests  59 passed (59)
11
-  Start at  16:35:40
12
-  Duration  2.30s (transform 983ms, setup 0ms, import 3.32s, tests 237ms, environment 0ms)
10
+  Tests  62 passed (62)
11
+  Start at  17:30:12
12
+  Duration  2.30s (transform 994ms, setup 0ms, import 2.83s, tests 293ms, environment 0ms)
13
13
 
14
14
   % Coverage report from v8
15
15
  -------------------|---------|----------|---------|---------|-------------------
16
16
  File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
17
17
  -------------------|---------|----------|---------|---------|-------------------
18
- All files | 80.51 | 76.14 | 82.25 | 82.58 |
19
- src | 81.23 | 76.55 | 86.11 | 83.14 |
20
- ...en-openapi.ts | 81.23 | 76.55 | 86.11 | 83.14 | ...17-720,744,754
18
+ All files | 80.92 | 76.78 | 82.25 | 82.88 |
19
+ src | 81.67 | 77.25 | 86.11 | 83.46 |
20
+ ...en-openapi.ts | 81.67 | 77.25 | 86.11 | 83.46 | ...30-733,757,767
21
21
  tests | 73.91 | 72.97 | 76.92 | 77.5 |
22
22
  helpers.ts | 73.91 | 72.97 | 76.92 | 77.5 | ...18,141,152-153
23
23
  -------------------|---------|----------|---------|---------|-------------------
package/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # @contractkit/contractkit-plugin-openapi
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e102a2c: Add a `decimal` scalar, for money and anything else that has to be exact
8
+
9
+ `number` compiles to `z.coerce.number()` — an IEEE-754 double. Anything monetary has to be exact,
10
+ so contracts either lied about their types or routed the value through `string` by hand. `decimal`
11
+ gives the language a type for it:
12
+
13
+ ```
14
+ contract Payslip: {
15
+ gross: decimal(min=0, scale=2)
16
+ rate: decimal
17
+ }
18
+ ```
19
+
20
+ A decimal travels as a **quoted JSON string** (`{"gross": "1250.00"}`) and becomes a decimal.js
21
+ `Decimal` — the same class Prisma hands you for a `Decimal` column, so a value moves between the
22
+ two with no conversion. Python gets `decimal.Decimal`; OpenAPI gets `type: string, format: decimal`.
23
+
24
+ A raw JSON number is **rejected**, not coerced. By the time one reaches the schema it has already
25
+ been through a double, which is precisely the loss the scalar exists to prevent, so accepting it
26
+ would defeat the point silently.
27
+
28
+ **`scale=` is a validation constraint — at most N decimal places — not a formatting directive.**
29
+ It cannot be one: the router assigns `ctx.body` and Koa serializes it with a `JSON.stringify` we
30
+ have no replacer for, so the wire form is whatever decimal.js normalizes to and `"1250.00"` reads
31
+ back as `"1250"`. The two are the same number; format at the display edge if you need the trailing
32
+ zeros. This is also what `scale` means in OpenAPI `pattern`, pydantic `condecimal(decimal_places=)`
33
+ and Prisma `@db.Decimal(_, n)`, so every downstream mapping stays honest.
34
+
35
+ Generated code sets `Decimal.set({ toExpNeg: -9e15, toExpPos: 9e15 })` so values never serialize in
36
+ exponential notation — without it `0.00000001` ships as `"1e-8"` and any peer validating
37
+ `^-?\d+(\.\d+)?$` rejects it. Note this is global decimal.js configuration and affects every
38
+ `Decimal` in the consuming process.
39
+
40
+ SDK clients rehydrate decimals through generated `reviveX` functions. The `bigint` approach does
41
+ not transfer — it works only because bigint invented a tagged `"123n"` wire encoding, and tagging a
42
+ decimal would corrupt the format for every non-ContractKit consumer. Re-parsing responses through
43
+ the Zod schema is not available either: `XOutput` is a type alias with no runtime value, and models
44
+ default to `z.strictObject`, so any field the server added would throw in every deployed client.
45
+ The revivers mutate in place, which preserves unknown server-added keys.
46
+
47
+ Two placements are rejected at parse time, both errors rather than warnings since no existing
48
+ contract can be relying on them. A decimal inside an **undiscriminated union** cannot be rehydrated
49
+ — the SDK has no way to tell which arm arrived, and a convert-if-string fallback would silently
50
+ rewrite a genuine `string` field in a sibling arm. A decimal in a **response header** has no
51
+ parsing step at all, so the annotation would simply be false at runtime.
52
+
53
+ `min`/`max` are kept as exact decimal strings rather than coerced through `Number()`, and OpenAPI
54
+ carries them in `x-contractkit-min`/`-max` extensions, since JSON Schema's numeric `minimum` and
55
+ `maximum` are ignored on a string type. A contract round-trips through OpenAPI and back with its
56
+ bounds intact.
57
+
58
+ SDKs that scaffold a `package.json` gain `decimal.js` as a dependency when a covered model uses the
59
+ scalar. Existing scaffolds are write-once and are not updated, so add it by hand there.
60
+
61
+ ### Patch Changes
62
+
63
+ - Updated dependencies [e102a2c]
64
+ - @contractkit/core@0.28.0
65
+
3
66
  ## 0.9.3
4
67
 
5
68
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"codegen-openapi.d.ts","sourceRoot":"","sources":["../src/codegen-openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,UAAU,EAOb,MAAM,mBAAmB,CAAC;AAG3B,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,8FAA8F;AAC9F,MAAM,WAAW,qBAAqB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAED,+HAA+H;AAC/H,MAAM,WAAW,aAAa;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACtC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AA8FD,8HAA8H;AAC9H,MAAM,WAAW,qBAAqB;IAClC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,yFAAyF;IACzF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CAC3D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CAoFlE;AA0ID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,mBAAmB,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsFxG;AA6MD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,SAAI,GAAG,MAAM,CA+DzD"}
1
+ {"version":3,"file":"codegen-openapi.d.ts","sourceRoot":"","sources":["../src/codegen-openapi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,UAAU,EAOb,MAAM,mBAAmB,CAAC;AAG3B,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,8FAA8F;AAC9F,MAAM,WAAW,qBAAqB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAED,+HAA+H;AAC/H,MAAM,WAAW,aAAa;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACH,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACtC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AA8FD,8HAA8H;AAC9H,MAAM,WAAW,qBAAqB;IAClC,aAAa,EAAE,gBAAgB,EAAE,CAAC;IAClC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,yFAAyF;IACzF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CAC3D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,qBAAqB,GAAG,MAAM,CAoFlE;AA0ID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,mBAAmB,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmGxG;AA6MD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,SAAI,GAAG,MAAM,CA+DzD"}
package/dist/index.js CHANGED
@@ -334,6 +334,14 @@ function scalarToSchema(type) {
334
334
  s.type = "integer";
335
335
  s.format = "int64";
336
336
  break;
337
+ case "decimal":
338
+ s.type = "string";
339
+ s.format = "decimal";
340
+ s.pattern = type.scale !== void 0 ? `^-?\\d+(\\.\\d{1,${type.scale}})?$` : "^-?\\d+(\\.\\d+)?$";
341
+ if (type.scale !== void 0) s["x-contractkit-scale"] = type.scale;
342
+ if (type.min !== void 0) s["x-contractkit-min"] = String(type.min);
343
+ if (type.max !== void 0) s["x-contractkit-max"] = String(type.max);
344
+ break;
337
345
  case "boolean":
338
346
  s.type = "boolean";
339
347
  break;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/codegen-openapi.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { generateOpenApi } from './codegen-openapi.js';\nimport type { ContractKitPlugin } from '@contractkit/core';\nimport type { OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\nexport type { OpenApiServerEntry, OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\n\nexport interface OpenApiPluginOptions extends OpenApiConfig {\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n// ─── Default export: loaded via plugins array, reads config from ctx.options ─\n\nconst plugin: ContractKitPlugin = {\n name: 'openapi',\n cacheKey: 'openapi',\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const { securitySchemes, ...openapiConfig } = ctx.options as OpenApiPluginOptions;\n const base = openapiConfig.baseDir ? resolve(ctx.rootDir, openapiConfig.baseDir) : ctx.rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n ctx.emitFile(outPath, generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes }));\n },\n};\n\nexport default plugin;\n\n// ─── Factory: for programmatic use with explicit config ────────────────────\n\nexport function createOpenApiPlugin(\n openapiConfig: OpenApiConfig,\n rootDir: string,\n securitySchemes?: Record<string, OpenApiSecurityScheme>,\n): ContractKitPlugin {\n return {\n name: 'openapi',\n cacheKey: `openapi:${JSON.stringify(openapiConfig)}`,\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const base = openapiConfig.baseDir ? resolve(rootDir, openapiConfig.baseDir) : rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n const content = generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes });\n ctx.emitFile(outPath, content);\n },\n };\n}\n","import type {\n ContractRootNode,\n OpRootNode,\n ContractTypeNode,\n FieldNode,\n ModelNode,\n OpRouteNode,\n OpOperationNode,\n ParamSource,\n} from '@contractkit/core';\nimport { resolveModifiers, resolveSecurity, SECURITY_NONE } from '@contractkit/core';\n\n/** A single entry for the OpenAPI `servers` array. */\nexport interface OpenApiServerEntry {\n url: string;\n description?: string;\n}\n\n/** A named entry for `components.securitySchemes` (e.g. an HTTP bearer or API-key scheme). */\nexport interface OpenApiSecurityScheme {\n type: string;\n scheme?: string;\n bearerFormat?: string;\n name?: string;\n in?: string;\n}\n\n/** Plugin configuration controlling the generated OpenAPI document (info block, servers, security, internal-op visibility). */\nexport interface OpenApiConfig {\n baseDir?: string;\n output?: string;\n info?: {\n title?: string;\n version?: string;\n description?: string;\n };\n servers?: OpenApiServerEntry[];\n /** Global OpenAPI security requirements (e.g. [{ bearerAuth: [] }]). Distinct from scheme definitions. */\n security?: Record<string, string[]>[];\n /**\n * Whether to document operations marked `internal`. Defaults to `false` — internal ops\n * are omitted from the spec so external consumers don't see them. Set to `true` for an\n * internal-use spec.\n */\n includeInternal?: boolean;\n}\n\n// ─── Type reachability ────────────────────────────────────────────────────\n\nfunction collectRefsFromType(type: ContractTypeNode, out: Set<string>): void {\n switch (type.kind) {\n case 'ref':\n out.add(type.name);\n break;\n case 'array':\n collectRefsFromType(type.item, out);\n break;\n case 'tuple':\n for (const item of type.items) collectRefsFromType(item, out);\n break;\n case 'record':\n collectRefsFromType(type.value, out);\n break;\n case 'union':\n case 'discriminatedUnion':\n case 'intersection':\n for (const member of type.members) collectRefsFromType(member, out);\n break;\n case 'lazy':\n collectRefsFromType(type.inner, out);\n break;\n case 'inlineObject':\n for (const field of type.fields) collectRefsFromType(field.type, out);\n break;\n }\n}\n\nfunction collectParamSourceRefs(source: ParamSource | undefined, out: Set<string>): void {\n if (!source) return;\n if (source.kind === 'ref') {\n out.add(source.name);\n return;\n }\n if (source.kind === 'params') {\n for (const p of source.nodes) collectRefsFromType(p.type, out);\n return;\n }\n collectRefsFromType(source.node, out);\n}\n\n/** Collect all type names directly referenced by public operations (seed set). */\nfunction collectPublicTypeRefs(opRoots: OpRootNode[], includeInternal = false): Set<string> {\n const refs = new Set<string>();\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n for (const op of route.operations) {\n if (!includeInternal && resolveModifiers(route, op).includes('internal')) continue;\n if (op.request) {\n for (const body of op.request.bodies) collectRefsFromType(body.bodyType, refs);\n }\n for (const resp of op.responses) {\n for (const body of resp.bodies) collectRefsFromType(body.bodyType, refs);\n if (resp.headers) {\n for (const h of resp.headers) collectRefsFromType(h.type, refs);\n }\n }\n collectParamSourceRefs(route.params, refs);\n collectParamSourceRefs(op.query, refs);\n collectParamSourceRefs(op.headers, refs);\n }\n }\n }\n return refs;\n}\n\n/** BFS-expand seed type names through the contract model graph. */\nfunction computeReachableSchemas(seeds: Set<string>, modelMap: Map<string, ModelNode>): Set<string> {\n const reachable = new Set<string>(seeds);\n const frontier = [...seeds];\n while (frontier.length > 0) {\n const name = frontier.pop()!;\n const model = modelMap.get(name);\n if (!model) continue;\n const refs = new Set<string>();\n if (model.type) collectRefsFromType(model.type, refs);\n for (const field of model.fields) collectRefsFromType(field.type, refs);\n if (model.bases) for (const b of model.bases) refs.add(b);\n for (const ref of refs) {\n if (!reachable.has(ref)) {\n reachable.add(ref);\n frontier.push(ref);\n }\n }\n }\n return reachable;\n}\n\n// ─── Public entry point ────────────────────────────────────────────────────\n\n/** Inputs to {@link generateOpenApi}: the parsed contracts/operations plus the OpenAPI config and shared security schemes. */\nexport interface OpenApiCodegenContext {\n contractRoots: ContractRootNode[];\n opRoots: OpRootNode[];\n config: OpenApiConfig;\n /** Named OpenAPI security scheme definitions to include in components.securitySchemes */\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n/**\n * Generate an OpenAPI 3.0 document (YAML) from the parsed contracts and operations.\n *\n * Emits `paths` from the operations and `components.schemas` from the contracts, honoring\n * `config.includeInternal` for `operation(internal)` visibility.\n *\n * @returns The OpenAPI document serialized as a YAML string.\n */\nexport function generateOpenApi(ctx: OpenApiCodegenContext): string {\n const { contractRoots, opRoots, config, securitySchemes } = ctx;\n const includeInternal = config.includeInternal ?? false;\n\n const doc: Record<string, unknown> = {\n openapi: '3.1.0',\n info: {\n title: config.info?.title ?? 'API',\n version: config.info?.version ?? '0.0.1',\n ...(config.info?.description ? { description: config.info.description } : {}),\n },\n };\n\n if (config.servers && config.servers.length > 0) {\n doc.servers = config.servers;\n }\n\n if (config.security && config.security.length > 0) {\n doc.security = config.security;\n }\n\n // Build component schemas from all contract models\n const allSchemas: Record<string, unknown> = {};\n const modelMap = new Map<string, ModelNode>();\n\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n modelMap.set(model.name, model);\n }\n }\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n allSchemas[model.name] = modelToSchema(model, modelMap);\n }\n }\n\n // Build paths from all operation files\n const paths: Record<string, Record<string, unknown>> = {};\n\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n const oaPath = convertPath(route.path);\n\n for (const op of route.operations) {\n const mods = resolveModifiers(route, op);\n if (!includeInternal && mods.includes('internal')) continue;\n // Lazily initialize the path object so all-internal routes\n // leave no empty entry in the output\n if (!paths[oaPath]) paths[oaPath] = {};\n const operation = buildOperation(route, op);\n if (mods.includes('deprecated')) (operation as Record<string, unknown>).deprecated = true;\n paths[oaPath][op.method] = operation;\n }\n }\n }\n\n doc.paths = paths;\n\n // Filter schemas to only include types reachable from public operations.\n // When there are no op files, all schemas are included (no filtering).\n const schemas: Record<string, unknown> =\n opRoots.length > 0\n ? (() => {\n const reachable = computeReachableSchemas(collectPublicTypeRefs(opRoots, includeInternal), modelMap);\n const filtered: Record<string, unknown> = {};\n for (const [name, schema] of Object.entries(allSchemas)) {\n if (reachable.has(name)) filtered[name] = schema;\n }\n return filtered;\n })()\n : allSchemas;\n\n const components: Record<string, unknown> = {};\n if (Object.keys(schemas).length > 0) {\n components.schemas = schemas;\n }\n if (securitySchemes && Object.keys(securitySchemes).length > 0) {\n components.securitySchemes = securitySchemes;\n }\n if (Object.keys(components).length > 0) {\n doc.components = components;\n }\n\n return toYaml(doc);\n}\n\n// ─── Path conversion ──────────────────────────────────────────────────────\n\n/** Path is already in OpenAPI `{param}` style — return as-is. */\nfunction convertPath(path: string): string {\n return path;\n}\n\n// ─── Schema conversion ───────────────────────────────────────────────────\n\nfunction modelToSchema(model: ModelNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n // Type alias (no fields)\n if (model.type) {\n const schema = typeToSchema(model.type, modelMap);\n if (model.description) schema.description = model.description;\n return schema;\n }\n\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of model.fields) {\n const prop = fieldToSchema(field, modelMap);\n properties[field.name] = prop;\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = {\n type: 'object',\n properties,\n };\n\n if (required.length > 0) {\n schema.required = required;\n }\n\n if (model.bases && model.bases.length > 0) {\n return {\n allOf: [...model.bases.map(b => ({ $ref: `#/components/schemas/${b}` })), schema],\n };\n }\n\n if (model.description) {\n schema.description = model.description;\n }\n if (model.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction fieldToSchema(field: FieldNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n let schema = typeToSchema(field.type, modelMap);\n\n if (field.nullable) {\n schema = wrapNullable(schema);\n }\n if (field.visibility === 'readonly') {\n schema.readOnly = true;\n } else if (field.visibility === 'writeonly') {\n schema.writeOnly = true;\n }\n if (field.default !== undefined) {\n schema.default = field.default;\n }\n if (field.description) {\n schema.description = field.description;\n }\n if (field.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction typeToSchema(type: ContractTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n switch (type.kind) {\n case 'scalar':\n return scalarToSchema(type);\n case 'array':\n return arrayToSchema(type, modelMap);\n case 'tuple':\n return { type: 'array', prefixItems: type.items.map(i => typeToSchema(i, modelMap)) };\n case 'record':\n return { type: 'object', additionalProperties: typeToSchema(type.value, modelMap) };\n case 'enum':\n return { type: 'string', enum: type.values };\n case 'literal':\n return { const: type.value };\n case 'union':\n return { oneOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'discriminatedUnion': {\n const oneOf = type.members.map(m => typeToSchema(m, modelMap));\n const mapping: Record<string, string> = {};\n for (const member of type.members) {\n if (member.kind !== 'ref') continue;\n const literalValues = resolveDiscriminatorLiterals(member.name, type.discriminator, modelMap);\n if (literalValues.length === 0) continue;\n for (const v of literalValues) {\n mapping[v] = `#/components/schemas/${member.name}`;\n }\n }\n const result: Record<string, unknown> = {\n oneOf,\n discriminator: { propertyName: type.discriminator },\n };\n if (Object.keys(mapping).length > 0) {\n (result.discriminator as Record<string, unknown>).mapping = mapping;\n }\n return result;\n }\n case 'intersection':\n return { allOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'ref':\n return { $ref: `#/components/schemas/${type.name}` };\n case 'inlineObject':\n return inlineObjectToSchema(type.fields, modelMap);\n case 'lazy':\n return typeToSchema(type.inner, modelMap);\n }\n}\n\n/** Resolve literal values of a model's discriminator field. Returns [] if not resolvable. */\nfunction resolveDiscriminatorLiterals(modelName: string, discriminator: string, modelMap?: Map<string, ModelNode>): string[] {\n if (!modelMap) return [];\n const model = modelMap.get(modelName);\n if (!model) return [];\n const field = model.fields.find(f => f.name === discriminator);\n if (!field) return [];\n if (field.type.kind === 'literal') return [String(field.type.value)];\n if (field.type.kind === 'enum') return field.type.values;\n return [];\n}\n\n/**\n * Map a ContractKit scalar type node to its OpenAPI schema object (`{ type, format, ... }`),\n * carrying across constraints like min/max/length.\n *\n * @throws {Error} If the scalar name is not mapped — every member of the closed scalar set must\n * have an explicit case, so an unmapped name signals a scalar was added to core without updating\n * this plugin (rather than silently emitting a type-less, permissive schema).\n */\nexport function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Record<string, unknown> {\n const s: Record<string, unknown> = {};\n\n switch (type.name) {\n case 'string':\n s.type = 'string';\n if (type.min !== undefined) s.minLength = Number(type.min);\n if (type.max !== undefined) s.maxLength = Number(type.max);\n if (type.len !== undefined) {\n s.minLength = type.len;\n s.maxLength = type.len;\n }\n if (type.regex) s.pattern = type.regex;\n break;\n case 'number':\n s.type = 'number';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'int':\n s.type = 'integer';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'bigint':\n s.type = 'integer';\n s.format = 'int64';\n break;\n case 'boolean':\n s.type = 'boolean';\n break;\n case 'date':\n s.type = 'string';\n s.format = 'date';\n break;\n case 'time':\n s.type = 'string';\n s.format = 'time';\n break;\n case 'datetime':\n s.type = 'string';\n s.format = 'date-time';\n break;\n case 'duration':\n s.type = 'string';\n s.format = 'duration';\n break;\n case 'interval':\n s.type = 'string';\n s.format = 'interval';\n break;\n case 'email':\n s.type = 'string';\n s.format = 'email';\n break;\n case 'url':\n s.type = 'string';\n s.format = 'uri';\n break;\n case 'uuid':\n s.type = 'string';\n s.format = 'uuid';\n break;\n case 'unknown':\n // No type constraint\n break;\n case 'null':\n s.type = 'null';\n break;\n case 'object':\n s.type = 'object';\n break;\n case 'binary':\n s.type = 'string';\n s.format = 'binary';\n break;\n case 'json':\n // Any JSON value — no type constraint\n break;\n default: {\n const _exhaustive: never = type.name;\n throw new Error(`plugin-openapi: unmapped scalar '${String(_exhaustive)}' — add a case`);\n }\n }\n\n return s;\n}\n\nfunction arrayToSchema(type: import('@contractkit/core').ArrayTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const s: Record<string, unknown> = { type: 'array', items: typeToSchema(type.item, modelMap) };\n if (type.min !== undefined) s.minItems = type.min;\n if (type.max !== undefined) s.maxItems = type.max;\n return s;\n}\n\nfunction inlineObjectToSchema(fields: FieldNode[], modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of fields) {\n properties[field.name] = fieldToSchema(field, modelMap);\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = { type: 'object', properties };\n if (required.length > 0) schema.required = required;\n return schema;\n}\n\nfunction wrapNullable(schema: Record<string, unknown>): Record<string, unknown> {\n // OpenAPI 3.1 uses JSON Schema nullable via oneOf or type array\n if (schema.$ref) {\n return { oneOf: [schema, { type: 'null' }] };\n }\n if (typeof schema.type === 'string') {\n schema.type = [schema.type, 'null'];\n }\n return schema;\n}\n\n// ─── Operation building ─────────────────────────────────────────────────\n\nfunction buildOperation(route: OpRouteNode, op: OpOperationNode): Record<string, unknown> {\n const operation: Record<string, unknown> = {};\n\n // operationId from service binding or SDK name\n if (op.sdk) {\n operation.operationId = op.sdk;\n } else if (op.service) {\n const methodPart = op.service.split('.').pop();\n if (methodPart) operation.operationId = methodPart;\n }\n\n // `name:` is the operation's human-readable label, which is what `summary` is for. Without\n // it the name is lost on the way out, and lost again on the way back through `openapi-to-ck`.\n if (op.name) {\n operation.summary = op.name;\n }\n\n if (op.description) {\n operation.description = op.description;\n }\n\n // Parameters: path params + query + headers\n const parameters: Record<string, unknown>[] = [];\n\n if (route.params) {\n parameters.push(...paramSourceToParams(route.params, 'path'));\n }\n if (op.query) {\n parameters.push(...paramSourceToParams(op.query, 'query'));\n }\n if (op.headers) {\n parameters.push(...paramSourceToParams(op.headers, 'header'));\n }\n\n if (parameters.length > 0) {\n operation.parameters = parameters;\n }\n\n // Request body\n if (op.request && op.request.bodies.length > 0) {\n const content: Record<string, { schema: ReturnType<typeof typeToSchema> }> = {};\n for (const body of op.request.bodies) {\n content[body.contentType] = { schema: typeToSchema(body.bodyType) };\n }\n operation.requestBody = { required: true, content };\n }\n\n // Effective security (operation-level wins; falls back to route-level)\n // security: none → empty array (explicit public endpoint, overrides global default)\n // security: { fields } → omit operation-level entry (rely on global security from config)\n const effectiveSecurity = resolveSecurity(route, op);\n if (effectiveSecurity === SECURITY_NONE) {\n operation.security = [];\n }\n\n // Responses\n const responses: Record<string, unknown> = {};\n for (const resp of op.responses) {\n const statusKey = String(resp.statusCode);\n const responseObject: Record<string, unknown> = {\n description: statusDescription(resp.statusCode),\n };\n const bodies = resp.bodies;\n if (bodies.length > 0) {\n // One `content` entry per declared mime — this is where a status serving several\n // formats becomes visible to every consumer of the spec.\n const content: Record<string, unknown> = {};\n for (const body of bodies) {\n content[body.contentType] = { schema: typeToSchema(body.bodyType) };\n }\n responseObject.content = content;\n }\n if (resp.headers && resp.headers.length > 0) {\n const headers: Record<string, unknown> = {};\n for (const h of resp.headers) {\n const headerObject: Record<string, unknown> = {\n schema: typeToSchema(h.type),\n };\n if (!h.optional) headerObject.required = true;\n if (h.description) headerObject.description = h.description;\n headers[h.name] = headerObject;\n }\n responseObject.headers = headers;\n }\n // OpenAPI cannot say whether the service produces a status or merely documents it, so\n // the distinction `.ck` draws would be lost on every round trip — every `(documented)`\n // error would come back as service-produced. A vendor extension is spec-legal and\n // ignored by other tooling, so carry it rather than drop it.\n if (resp.emit === 'documented') {\n responseObject['x-contractkit-emit'] = 'documented';\n }\n responses[statusKey] = responseObject;\n }\n\n if (Object.keys(responses).length > 0) {\n operation.responses = responses;\n }\n\n return operation;\n}\n\nfunction paramSourceToParams(source: ParamSource, location: 'path' | 'query' | 'header'): Record<string, unknown>[] {\n if (source.kind === 'ref') {\n // Type reference name used as param source — emit a single $ref\n return [\n {\n name: source.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.name}` },\n },\n ];\n }\n\n if (source.kind === 'params') {\n // Inline param declarations\n return source.nodes.map(p => ({\n name: p.name,\n in: location,\n required: location === 'path',\n schema: typeToSchema(p.type),\n }));\n }\n\n // ContractTypeNode (inline object or other type) — if it's an inlineObject, expand fields\n if (source.node.kind === 'inlineObject') {\n return source.node.fields.map(f => ({\n name: f.name,\n in: location,\n required: location === 'path' ? true : !f.optional,\n schema: typeToSchema(f.type),\n }));\n }\n\n // For a ref type used as param source\n if (source.node.kind === 'ref') {\n return [\n {\n name: source.node.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.node.name}` },\n },\n ];\n }\n\n return [];\n}\n\nfunction statusDescription(code: number): string {\n const descriptions: Record<number, string> = {\n 200: 'Successful response',\n 201: 'Created',\n 204: 'No content',\n 400: 'Bad request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not found',\n 409: 'Conflict',\n 422: 'Unprocessable entity',\n 500: 'Internal server error',\n };\n return descriptions[code] ?? `Response ${code}`;\n}\n\n// ─── YAML serializer ──────────────────────────────────────────────────────\n\n/**\n * Minimal YAML serializer sufficient for OpenAPI documents.\n * Avoids external dependency while producing clean, readable output.\n */\nexport function toYaml(value: unknown, indent = 0): string {\n if (value === null || value === undefined) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (typeof value === 'number') return String(value);\n if (typeof value === 'bigint') return String(value);\n\n if (typeof value === 'string') {\n return yamlString(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]';\n\n // Check if all items are simple scalars (for inline arrays like enum values, required lists)\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n const inline = `[${items.join(', ')}]`;\n if (inline.length < 80) return inline;\n }\n\n const lines: string[] = [];\n const pad = ' '.repeat(indent);\n for (const item of value) {\n if (isPlainObject(item)) {\n const entries = Object.entries(item as Record<string, unknown>);\n if (entries.length > 0) {\n const [firstKey, firstVal] = entries[0]!;\n const firstValStr = isComplex(firstVal) ? `\\n${toYamlValue(firstVal, indent + 2)}` : ` ${toYaml(firstVal, indent + 2)}`;\n lines.push(`${pad}- ${yamlKey(firstKey)}:${firstValStr}`);\n for (let i = 1; i < entries.length; i++) {\n const [k, v] = entries[i]!;\n const valStr = isComplex(v) ? `\\n${toYamlValue(v, indent + 2)}` : ` ${toYaml(v, indent + 2)}`;\n lines.push(`${pad} ${yamlKey(k)}:${valStr}`);\n }\n } else {\n lines.push(`${pad}- {}`);\n }\n } else {\n lines.push(`${pad}- ${toYaml(item, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n if (isPlainObject(value)) {\n const obj = value as Record<string, unknown>;\n const entries = Object.entries(obj);\n if (entries.length === 0) return '{}';\n\n const pad = ' '.repeat(indent);\n const lines: string[] = [];\n for (const [key, val] of entries) {\n if (isComplex(val)) {\n lines.push(`${pad}${yamlKey(key)}:`);\n lines.push(toYamlValue(val, indent + 1));\n } else {\n lines.push(`${pad}${yamlKey(key)}: ${toYaml(val, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n return String(value);\n}\n\nfunction toYamlValue(value: unknown, indent: number): string {\n if (Array.isArray(value)) {\n return toYaml(value, indent);\n }\n if (isPlainObject(value)) {\n return toYaml(value, indent);\n }\n return ' '.repeat(indent) + toYaml(value, indent);\n}\n\nfunction isComplex(value: unknown): boolean {\n if (Array.isArray(value)) {\n // Simple scalar arrays can be inlined\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n return `[${items.join(', ')}]`.length >= 80;\n }\n return true;\n }\n return isPlainObject(value);\n}\n\nfunction isPlainObject(value: unknown): boolean {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction yamlString(s: string): string {\n // Use plain style if safe, otherwise single-quoted\n if (s === '') return \"''\";\n if (/^[\\w./-]+$/.test(s) && !/^(true|false|null|yes|no|on|off)$/i.test(s) && !/^\\d/.test(s)) {\n return s;\n }\n // Single-quote, escaping internal single quotes by doubling\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nfunction yamlKey(key: string): string {\n // Keys with special chars need quoting\n if (/^[\\w-]+$/.test(key) && !/^(true|false|null|yes|no|on|off)$/i.test(key)) {\n return key;\n }\n return `'${key.replace(/'/g, \"''\")}'`;\n}\n"],"mappings":"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","names":["resolve","resolveModifiers","resolveSecurity","SECURITY_NONE","collectRefsFromType","type","out","kind","add","name","item","items","value","member","members","inner","field","fields","collectParamSourceRefs","source","p","nodes","node","collectPublicTypeRefs","opRoots","includeInternal","refs","Set","opRoot","route","routes","op","operations","resolveModifiers","includes","request","body","bodies","bodyType","resp","responses","headers","h","params","query","computeReachableSchemas","seeds","modelMap","reachable","frontier","length","pop","model","get","bases","b","ref","has","push","generateOpenApi","ctx","contractRoots","config","securitySchemes","doc","openapi","info","title","version","description","servers","security","allSchemas","Map","contractRoot","models","set","modelToSchema","paths","oaPath","convertPath","path","mods","operation","buildOperation","deprecated","method","schemas","filtered","schema","Object","entries","components","keys","toYaml","typeToSchema","properties","required","prop","fieldToSchema","optional","allOf","map","$ref","nullable","wrapNullable","visibility","readOnly","writeOnly","default","undefined","scalarToSchema","arrayToSchema","prefixItems","i","additionalProperties","enum","values","const","oneOf","m","mapping","literalValues","resolveDiscriminatorLiterals","discriminator","v","result","propertyName","inlineObjectToSchema","modelName","find","f","String","s","min","minLength","Number","max","maxLength","len","regex","pattern","minimum","maximum","format","_exhaustive","Error","minItems","maxItems","sdk","operationId","service","methodPart","split","summary","parameters","paramSourceToParams","content","contentType","requestBody","effectiveSecurity","resolveSecurity","SECURITY_NONE","statusKey","statusCode","responseObject","statusDescription","headerObject","emit","location","in","code","descriptions","indent","yamlString","Array","isArray","every","inline","join","lines","pad","repeat","isPlainObject","firstKey","firstVal","firstValStr","isComplex","toYamlValue","yamlKey","k","valStr","obj","key","val","test","replace","plugin","name","cacheKey","generateTargets","contractRoots","opRoots","ctx","securitySchemes","openapiConfig","options","base","baseDir","resolve","rootDir","outPath","output","emitFile","generateOpenApi","config","createOpenApiPlugin","JSON","stringify","content"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/codegen-openapi.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { generateOpenApi } from './codegen-openapi.js';\nimport type { ContractKitPlugin } from '@contractkit/core';\nimport type { OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\nexport type { OpenApiServerEntry, OpenApiConfig, OpenApiSecurityScheme } from './codegen-openapi.js';\n\nexport interface OpenApiPluginOptions extends OpenApiConfig {\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n// ─── Default export: loaded via plugins array, reads config from ctx.options ─\n\nconst plugin: ContractKitPlugin = {\n name: 'openapi',\n cacheKey: 'openapi',\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const { securitySchemes, ...openapiConfig } = ctx.options as OpenApiPluginOptions;\n const base = openapiConfig.baseDir ? resolve(ctx.rootDir, openapiConfig.baseDir) : ctx.rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n ctx.emitFile(outPath, generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes }));\n },\n};\n\nexport default plugin;\n\n// ─── Factory: for programmatic use with explicit config ────────────────────\n\nexport function createOpenApiPlugin(\n openapiConfig: OpenApiConfig,\n rootDir: string,\n securitySchemes?: Record<string, OpenApiSecurityScheme>,\n): ContractKitPlugin {\n return {\n name: 'openapi',\n cacheKey: `openapi:${JSON.stringify(openapiConfig)}`,\n async generateTargets({ contractRoots, opRoots }, ctx) {\n const base = openapiConfig.baseDir ? resolve(rootDir, openapiConfig.baseDir) : rootDir;\n const outPath = resolve(base, openapiConfig.output ?? 'openapi.yaml');\n const content = generateOpenApi({ contractRoots, opRoots, config: openapiConfig, securitySchemes });\n ctx.emitFile(outPath, content);\n },\n };\n}\n","import type {\n ContractRootNode,\n OpRootNode,\n ContractTypeNode,\n FieldNode,\n ModelNode,\n OpRouteNode,\n OpOperationNode,\n ParamSource,\n} from '@contractkit/core';\nimport { resolveModifiers, resolveSecurity, SECURITY_NONE } from '@contractkit/core';\n\n/** A single entry for the OpenAPI `servers` array. */\nexport interface OpenApiServerEntry {\n url: string;\n description?: string;\n}\n\n/** A named entry for `components.securitySchemes` (e.g. an HTTP bearer or API-key scheme). */\nexport interface OpenApiSecurityScheme {\n type: string;\n scheme?: string;\n bearerFormat?: string;\n name?: string;\n in?: string;\n}\n\n/** Plugin configuration controlling the generated OpenAPI document (info block, servers, security, internal-op visibility). */\nexport interface OpenApiConfig {\n baseDir?: string;\n output?: string;\n info?: {\n title?: string;\n version?: string;\n description?: string;\n };\n servers?: OpenApiServerEntry[];\n /** Global OpenAPI security requirements (e.g. [{ bearerAuth: [] }]). Distinct from scheme definitions. */\n security?: Record<string, string[]>[];\n /**\n * Whether to document operations marked `internal`. Defaults to `false` — internal ops\n * are omitted from the spec so external consumers don't see them. Set to `true` for an\n * internal-use spec.\n */\n includeInternal?: boolean;\n}\n\n// ─── Type reachability ────────────────────────────────────────────────────\n\nfunction collectRefsFromType(type: ContractTypeNode, out: Set<string>): void {\n switch (type.kind) {\n case 'ref':\n out.add(type.name);\n break;\n case 'array':\n collectRefsFromType(type.item, out);\n break;\n case 'tuple':\n for (const item of type.items) collectRefsFromType(item, out);\n break;\n case 'record':\n collectRefsFromType(type.value, out);\n break;\n case 'union':\n case 'discriminatedUnion':\n case 'intersection':\n for (const member of type.members) collectRefsFromType(member, out);\n break;\n case 'lazy':\n collectRefsFromType(type.inner, out);\n break;\n case 'inlineObject':\n for (const field of type.fields) collectRefsFromType(field.type, out);\n break;\n }\n}\n\nfunction collectParamSourceRefs(source: ParamSource | undefined, out: Set<string>): void {\n if (!source) return;\n if (source.kind === 'ref') {\n out.add(source.name);\n return;\n }\n if (source.kind === 'params') {\n for (const p of source.nodes) collectRefsFromType(p.type, out);\n return;\n }\n collectRefsFromType(source.node, out);\n}\n\n/** Collect all type names directly referenced by public operations (seed set). */\nfunction collectPublicTypeRefs(opRoots: OpRootNode[], includeInternal = false): Set<string> {\n const refs = new Set<string>();\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n for (const op of route.operations) {\n if (!includeInternal && resolveModifiers(route, op).includes('internal')) continue;\n if (op.request) {\n for (const body of op.request.bodies) collectRefsFromType(body.bodyType, refs);\n }\n for (const resp of op.responses) {\n for (const body of resp.bodies) collectRefsFromType(body.bodyType, refs);\n if (resp.headers) {\n for (const h of resp.headers) collectRefsFromType(h.type, refs);\n }\n }\n collectParamSourceRefs(route.params, refs);\n collectParamSourceRefs(op.query, refs);\n collectParamSourceRefs(op.headers, refs);\n }\n }\n }\n return refs;\n}\n\n/** BFS-expand seed type names through the contract model graph. */\nfunction computeReachableSchemas(seeds: Set<string>, modelMap: Map<string, ModelNode>): Set<string> {\n const reachable = new Set<string>(seeds);\n const frontier = [...seeds];\n while (frontier.length > 0) {\n const name = frontier.pop()!;\n const model = modelMap.get(name);\n if (!model) continue;\n const refs = new Set<string>();\n if (model.type) collectRefsFromType(model.type, refs);\n for (const field of model.fields) collectRefsFromType(field.type, refs);\n if (model.bases) for (const b of model.bases) refs.add(b);\n for (const ref of refs) {\n if (!reachable.has(ref)) {\n reachable.add(ref);\n frontier.push(ref);\n }\n }\n }\n return reachable;\n}\n\n// ─── Public entry point ────────────────────────────────────────────────────\n\n/** Inputs to {@link generateOpenApi}: the parsed contracts/operations plus the OpenAPI config and shared security schemes. */\nexport interface OpenApiCodegenContext {\n contractRoots: ContractRootNode[];\n opRoots: OpRootNode[];\n config: OpenApiConfig;\n /** Named OpenAPI security scheme definitions to include in components.securitySchemes */\n securitySchemes?: Record<string, OpenApiSecurityScheme>;\n}\n\n/**\n * Generate an OpenAPI 3.0 document (YAML) from the parsed contracts and operations.\n *\n * Emits `paths` from the operations and `components.schemas` from the contracts, honoring\n * `config.includeInternal` for `operation(internal)` visibility.\n *\n * @returns The OpenAPI document serialized as a YAML string.\n */\nexport function generateOpenApi(ctx: OpenApiCodegenContext): string {\n const { contractRoots, opRoots, config, securitySchemes } = ctx;\n const includeInternal = config.includeInternal ?? false;\n\n const doc: Record<string, unknown> = {\n openapi: '3.1.0',\n info: {\n title: config.info?.title ?? 'API',\n version: config.info?.version ?? '0.0.1',\n ...(config.info?.description ? { description: config.info.description } : {}),\n },\n };\n\n if (config.servers && config.servers.length > 0) {\n doc.servers = config.servers;\n }\n\n if (config.security && config.security.length > 0) {\n doc.security = config.security;\n }\n\n // Build component schemas from all contract models\n const allSchemas: Record<string, unknown> = {};\n const modelMap = new Map<string, ModelNode>();\n\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n modelMap.set(model.name, model);\n }\n }\n for (const contractRoot of contractRoots) {\n for (const model of contractRoot.models) {\n allSchemas[model.name] = modelToSchema(model, modelMap);\n }\n }\n\n // Build paths from all operation files\n const paths: Record<string, Record<string, unknown>> = {};\n\n for (const opRoot of opRoots) {\n for (const route of opRoot.routes) {\n const oaPath = convertPath(route.path);\n\n for (const op of route.operations) {\n const mods = resolveModifiers(route, op);\n if (!includeInternal && mods.includes('internal')) continue;\n // Lazily initialize the path object so all-internal routes\n // leave no empty entry in the output\n if (!paths[oaPath]) paths[oaPath] = {};\n const operation = buildOperation(route, op);\n if (mods.includes('deprecated')) (operation as Record<string, unknown>).deprecated = true;\n paths[oaPath][op.method] = operation;\n }\n }\n }\n\n doc.paths = paths;\n\n // Filter schemas to only include types reachable from public operations.\n // When there are no op files, all schemas are included (no filtering).\n const schemas: Record<string, unknown> =\n opRoots.length > 0\n ? (() => {\n const reachable = computeReachableSchemas(collectPublicTypeRefs(opRoots, includeInternal), modelMap);\n const filtered: Record<string, unknown> = {};\n for (const [name, schema] of Object.entries(allSchemas)) {\n if (reachable.has(name)) filtered[name] = schema;\n }\n return filtered;\n })()\n : allSchemas;\n\n const components: Record<string, unknown> = {};\n if (Object.keys(schemas).length > 0) {\n components.schemas = schemas;\n }\n if (securitySchemes && Object.keys(securitySchemes).length > 0) {\n components.securitySchemes = securitySchemes;\n }\n if (Object.keys(components).length > 0) {\n doc.components = components;\n }\n\n return toYaml(doc);\n}\n\n// ─── Path conversion ──────────────────────────────────────────────────────\n\n/** Path is already in OpenAPI `{param}` style — return as-is. */\nfunction convertPath(path: string): string {\n return path;\n}\n\n// ─── Schema conversion ───────────────────────────────────────────────────\n\nfunction modelToSchema(model: ModelNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n // Type alias (no fields)\n if (model.type) {\n const schema = typeToSchema(model.type, modelMap);\n if (model.description) schema.description = model.description;\n return schema;\n }\n\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of model.fields) {\n const prop = fieldToSchema(field, modelMap);\n properties[field.name] = prop;\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = {\n type: 'object',\n properties,\n };\n\n if (required.length > 0) {\n schema.required = required;\n }\n\n if (model.bases && model.bases.length > 0) {\n return {\n allOf: [...model.bases.map(b => ({ $ref: `#/components/schemas/${b}` })), schema],\n };\n }\n\n if (model.description) {\n schema.description = model.description;\n }\n if (model.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction fieldToSchema(field: FieldNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n let schema = typeToSchema(field.type, modelMap);\n\n if (field.nullable) {\n schema = wrapNullable(schema);\n }\n if (field.visibility === 'readonly') {\n schema.readOnly = true;\n } else if (field.visibility === 'writeonly') {\n schema.writeOnly = true;\n }\n if (field.default !== undefined) {\n schema.default = field.default;\n }\n if (field.description) {\n schema.description = field.description;\n }\n if (field.deprecated) {\n schema.deprecated = true;\n }\n\n return schema;\n}\n\nfunction typeToSchema(type: ContractTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n switch (type.kind) {\n case 'scalar':\n return scalarToSchema(type);\n case 'array':\n return arrayToSchema(type, modelMap);\n case 'tuple':\n return { type: 'array', prefixItems: type.items.map(i => typeToSchema(i, modelMap)) };\n case 'record':\n return { type: 'object', additionalProperties: typeToSchema(type.value, modelMap) };\n case 'enum':\n return { type: 'string', enum: type.values };\n case 'literal':\n return { const: type.value };\n case 'union':\n return { oneOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'discriminatedUnion': {\n const oneOf = type.members.map(m => typeToSchema(m, modelMap));\n const mapping: Record<string, string> = {};\n for (const member of type.members) {\n if (member.kind !== 'ref') continue;\n const literalValues = resolveDiscriminatorLiterals(member.name, type.discriminator, modelMap);\n if (literalValues.length === 0) continue;\n for (const v of literalValues) {\n mapping[v] = `#/components/schemas/${member.name}`;\n }\n }\n const result: Record<string, unknown> = {\n oneOf,\n discriminator: { propertyName: type.discriminator },\n };\n if (Object.keys(mapping).length > 0) {\n (result.discriminator as Record<string, unknown>).mapping = mapping;\n }\n return result;\n }\n case 'intersection':\n return { allOf: type.members.map(m => typeToSchema(m, modelMap)) };\n case 'ref':\n return { $ref: `#/components/schemas/${type.name}` };\n case 'inlineObject':\n return inlineObjectToSchema(type.fields, modelMap);\n case 'lazy':\n return typeToSchema(type.inner, modelMap);\n }\n}\n\n/** Resolve literal values of a model's discriminator field. Returns [] if not resolvable. */\nfunction resolveDiscriminatorLiterals(modelName: string, discriminator: string, modelMap?: Map<string, ModelNode>): string[] {\n if (!modelMap) return [];\n const model = modelMap.get(modelName);\n if (!model) return [];\n const field = model.fields.find(f => f.name === discriminator);\n if (!field) return [];\n if (field.type.kind === 'literal') return [String(field.type.value)];\n if (field.type.kind === 'enum') return field.type.values;\n return [];\n}\n\n/**\n * Map a ContractKit scalar type node to its OpenAPI schema object (`{ type, format, ... }`),\n * carrying across constraints like min/max/length.\n *\n * @throws {Error} If the scalar name is not mapped — every member of the closed scalar set must\n * have an explicit case, so an unmapped name signals a scalar was added to core without updating\n * this plugin (rather than silently emitting a type-less, permissive schema).\n */\nexport function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode): Record<string, unknown> {\n const s: Record<string, unknown> = {};\n\n switch (type.name) {\n case 'string':\n s.type = 'string';\n if (type.min !== undefined) s.minLength = Number(type.min);\n if (type.max !== undefined) s.maxLength = Number(type.max);\n if (type.len !== undefined) {\n s.minLength = type.len;\n s.maxLength = type.len;\n }\n if (type.regex) s.pattern = type.regex;\n break;\n case 'number':\n s.type = 'number';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'int':\n s.type = 'integer';\n if (type.min !== undefined) s.minimum = Number(type.min);\n if (type.max !== undefined) s.maximum = Number(type.max);\n break;\n case 'bigint':\n s.type = 'integer';\n s.format = 'int64';\n break;\n case 'decimal':\n // A quoted string, not `type: number` — the whole point is to keep the value away from\n // the IEEE-754 double a JSON number becomes in most generators.\n s.type = 'string';\n s.format = 'decimal';\n s.pattern = type.scale !== undefined ? `^-?\\\\d+(\\\\.\\\\d{1,${type.scale}})?$` : '^-?\\\\d+(\\\\.\\\\d+)?$';\n // `minimum`/`maximum` are numeric in JSON Schema and ignored on a string type, so the\n // exact bounds ride in vendor extensions instead — which also lets the importer recover\n // them verbatim rather than reverse-engineering the pattern.\n if (type.scale !== undefined) s['x-contractkit-scale'] = type.scale;\n if (type.min !== undefined) s['x-contractkit-min'] = String(type.min);\n if (type.max !== undefined) s['x-contractkit-max'] = String(type.max);\n break;\n case 'boolean':\n s.type = 'boolean';\n break;\n case 'date':\n s.type = 'string';\n s.format = 'date';\n break;\n case 'time':\n s.type = 'string';\n s.format = 'time';\n break;\n case 'datetime':\n s.type = 'string';\n s.format = 'date-time';\n break;\n case 'duration':\n s.type = 'string';\n s.format = 'duration';\n break;\n case 'interval':\n s.type = 'string';\n s.format = 'interval';\n break;\n case 'email':\n s.type = 'string';\n s.format = 'email';\n break;\n case 'url':\n s.type = 'string';\n s.format = 'uri';\n break;\n case 'uuid':\n s.type = 'string';\n s.format = 'uuid';\n break;\n case 'unknown':\n // No type constraint\n break;\n case 'null':\n s.type = 'null';\n break;\n case 'object':\n s.type = 'object';\n break;\n case 'binary':\n s.type = 'string';\n s.format = 'binary';\n break;\n case 'json':\n // Any JSON value — no type constraint\n break;\n default: {\n const _exhaustive: never = type.name;\n throw new Error(`plugin-openapi: unmapped scalar '${String(_exhaustive)}' — add a case`);\n }\n }\n\n return s;\n}\n\nfunction arrayToSchema(type: import('@contractkit/core').ArrayTypeNode, modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const s: Record<string, unknown> = { type: 'array', items: typeToSchema(type.item, modelMap) };\n if (type.min !== undefined) s.minItems = type.min;\n if (type.max !== undefined) s.maxItems = type.max;\n return s;\n}\n\nfunction inlineObjectToSchema(fields: FieldNode[], modelMap?: Map<string, ModelNode>): Record<string, unknown> {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const field of fields) {\n properties[field.name] = fieldToSchema(field, modelMap);\n if (!field.optional) {\n required.push(field.name);\n }\n }\n\n const schema: Record<string, unknown> = { type: 'object', properties };\n if (required.length > 0) schema.required = required;\n return schema;\n}\n\nfunction wrapNullable(schema: Record<string, unknown>): Record<string, unknown> {\n // OpenAPI 3.1 uses JSON Schema nullable via oneOf or type array\n if (schema.$ref) {\n return { oneOf: [schema, { type: 'null' }] };\n }\n if (typeof schema.type === 'string') {\n schema.type = [schema.type, 'null'];\n }\n return schema;\n}\n\n// ─── Operation building ─────────────────────────────────────────────────\n\nfunction buildOperation(route: OpRouteNode, op: OpOperationNode): Record<string, unknown> {\n const operation: Record<string, unknown> = {};\n\n // operationId from service binding or SDK name\n if (op.sdk) {\n operation.operationId = op.sdk;\n } else if (op.service) {\n const methodPart = op.service.split('.').pop();\n if (methodPart) operation.operationId = methodPart;\n }\n\n // `name:` is the operation's human-readable label, which is what `summary` is for. Without\n // it the name is lost on the way out, and lost again on the way back through `openapi-to-ck`.\n if (op.name) {\n operation.summary = op.name;\n }\n\n if (op.description) {\n operation.description = op.description;\n }\n\n // Parameters: path params + query + headers\n const parameters: Record<string, unknown>[] = [];\n\n if (route.params) {\n parameters.push(...paramSourceToParams(route.params, 'path'));\n }\n if (op.query) {\n parameters.push(...paramSourceToParams(op.query, 'query'));\n }\n if (op.headers) {\n parameters.push(...paramSourceToParams(op.headers, 'header'));\n }\n\n if (parameters.length > 0) {\n operation.parameters = parameters;\n }\n\n // Request body\n if (op.request && op.request.bodies.length > 0) {\n const content: Record<string, { schema: ReturnType<typeof typeToSchema> }> = {};\n for (const body of op.request.bodies) {\n content[body.contentType] = { schema: typeToSchema(body.bodyType) };\n }\n operation.requestBody = { required: true, content };\n }\n\n // Effective security (operation-level wins; falls back to route-level)\n // security: none → empty array (explicit public endpoint, overrides global default)\n // security: { fields } → omit operation-level entry (rely on global security from config)\n const effectiveSecurity = resolveSecurity(route, op);\n if (effectiveSecurity === SECURITY_NONE) {\n operation.security = [];\n }\n\n // Responses\n const responses: Record<string, unknown> = {};\n for (const resp of op.responses) {\n const statusKey = String(resp.statusCode);\n const responseObject: Record<string, unknown> = {\n description: statusDescription(resp.statusCode),\n };\n const bodies = resp.bodies;\n if (bodies.length > 0) {\n // One `content` entry per declared mime — this is where a status serving several\n // formats becomes visible to every consumer of the spec.\n const content: Record<string, unknown> = {};\n for (const body of bodies) {\n content[body.contentType] = { schema: typeToSchema(body.bodyType) };\n }\n responseObject.content = content;\n }\n if (resp.headers && resp.headers.length > 0) {\n const headers: Record<string, unknown> = {};\n for (const h of resp.headers) {\n const headerObject: Record<string, unknown> = {\n schema: typeToSchema(h.type),\n };\n if (!h.optional) headerObject.required = true;\n if (h.description) headerObject.description = h.description;\n headers[h.name] = headerObject;\n }\n responseObject.headers = headers;\n }\n // OpenAPI cannot say whether the service produces a status or merely documents it, so\n // the distinction `.ck` draws would be lost on every round trip — every `(documented)`\n // error would come back as service-produced. A vendor extension is spec-legal and\n // ignored by other tooling, so carry it rather than drop it.\n if (resp.emit === 'documented') {\n responseObject['x-contractkit-emit'] = 'documented';\n }\n responses[statusKey] = responseObject;\n }\n\n if (Object.keys(responses).length > 0) {\n operation.responses = responses;\n }\n\n return operation;\n}\n\nfunction paramSourceToParams(source: ParamSource, location: 'path' | 'query' | 'header'): Record<string, unknown>[] {\n if (source.kind === 'ref') {\n // Type reference name used as param source — emit a single $ref\n return [\n {\n name: source.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.name}` },\n },\n ];\n }\n\n if (source.kind === 'params') {\n // Inline param declarations\n return source.nodes.map(p => ({\n name: p.name,\n in: location,\n required: location === 'path',\n schema: typeToSchema(p.type),\n }));\n }\n\n // ContractTypeNode (inline object or other type) — if it's an inlineObject, expand fields\n if (source.node.kind === 'inlineObject') {\n return source.node.fields.map(f => ({\n name: f.name,\n in: location,\n required: location === 'path' ? true : !f.optional,\n schema: typeToSchema(f.type),\n }));\n }\n\n // For a ref type used as param source\n if (source.node.kind === 'ref') {\n return [\n {\n name: source.node.name,\n in: location,\n required: location === 'path',\n schema: { $ref: `#/components/schemas/${source.node.name}` },\n },\n ];\n }\n\n return [];\n}\n\nfunction statusDescription(code: number): string {\n const descriptions: Record<number, string> = {\n 200: 'Successful response',\n 201: 'Created',\n 204: 'No content',\n 400: 'Bad request',\n 401: 'Unauthorized',\n 403: 'Forbidden',\n 404: 'Not found',\n 409: 'Conflict',\n 422: 'Unprocessable entity',\n 500: 'Internal server error',\n };\n return descriptions[code] ?? `Response ${code}`;\n}\n\n// ─── YAML serializer ──────────────────────────────────────────────────────\n\n/**\n * Minimal YAML serializer sufficient for OpenAPI documents.\n * Avoids external dependency while producing clean, readable output.\n */\nexport function toYaml(value: unknown, indent = 0): string {\n if (value === null || value === undefined) return 'null';\n if (typeof value === 'boolean') return value ? 'true' : 'false';\n if (typeof value === 'number') return String(value);\n if (typeof value === 'bigint') return String(value);\n\n if (typeof value === 'string') {\n return yamlString(value);\n }\n\n if (Array.isArray(value)) {\n if (value.length === 0) return '[]';\n\n // Check if all items are simple scalars (for inline arrays like enum values, required lists)\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n const inline = `[${items.join(', ')}]`;\n if (inline.length < 80) return inline;\n }\n\n const lines: string[] = [];\n const pad = ' '.repeat(indent);\n for (const item of value) {\n if (isPlainObject(item)) {\n const entries = Object.entries(item as Record<string, unknown>);\n if (entries.length > 0) {\n const [firstKey, firstVal] = entries[0]!;\n const firstValStr = isComplex(firstVal) ? `\\n${toYamlValue(firstVal, indent + 2)}` : ` ${toYaml(firstVal, indent + 2)}`;\n lines.push(`${pad}- ${yamlKey(firstKey)}:${firstValStr}`);\n for (let i = 1; i < entries.length; i++) {\n const [k, v] = entries[i]!;\n const valStr = isComplex(v) ? `\\n${toYamlValue(v, indent + 2)}` : ` ${toYaml(v, indent + 2)}`;\n lines.push(`${pad} ${yamlKey(k)}:${valStr}`);\n }\n } else {\n lines.push(`${pad}- {}`);\n }\n } else {\n lines.push(`${pad}- ${toYaml(item, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n if (isPlainObject(value)) {\n const obj = value as Record<string, unknown>;\n const entries = Object.entries(obj);\n if (entries.length === 0) return '{}';\n\n const pad = ' '.repeat(indent);\n const lines: string[] = [];\n for (const [key, val] of entries) {\n if (isComplex(val)) {\n lines.push(`${pad}${yamlKey(key)}:`);\n lines.push(toYamlValue(val, indent + 1));\n } else {\n lines.push(`${pad}${yamlKey(key)}: ${toYaml(val, indent + 1)}`);\n }\n }\n return lines.join('\\n');\n }\n\n return String(value);\n}\n\nfunction toYamlValue(value: unknown, indent: number): string {\n if (Array.isArray(value)) {\n return toYaml(value, indent);\n }\n if (isPlainObject(value)) {\n return toYaml(value, indent);\n }\n return ' '.repeat(indent) + toYaml(value, indent);\n}\n\nfunction isComplex(value: unknown): boolean {\n if (Array.isArray(value)) {\n // Simple scalar arrays can be inlined\n if (value.every(v => typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {\n const items = value.map(v => (typeof v === 'string' ? yamlString(v) : String(v)));\n return `[${items.join(', ')}]`.length >= 80;\n }\n return true;\n }\n return isPlainObject(value);\n}\n\nfunction isPlainObject(value: unknown): boolean {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction yamlString(s: string): string {\n // Use plain style if safe, otherwise single-quoted\n if (s === '') return \"''\";\n if (/^[\\w./-]+$/.test(s) && !/^(true|false|null|yes|no|on|off)$/i.test(s) && !/^\\d/.test(s)) {\n return s;\n }\n // Single-quote, escaping internal single quotes by doubling\n return `'${s.replace(/'/g, \"''\")}'`;\n}\n\nfunction yamlKey(key: string): string {\n // Keys with special chars need quoting\n if (/^[\\w-]+$/.test(key) && !/^(true|false|null|yes|no|on|off)$/i.test(key)) {\n return key;\n }\n return `'${key.replace(/'/g, \"''\")}'`;\n}\n"],"mappings":"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","names":["resolve","resolveModifiers","resolveSecurity","SECURITY_NONE","collectRefsFromType","type","out","kind","add","name","item","items","value","member","members","inner","field","fields","collectParamSourceRefs","source","p","nodes","node","collectPublicTypeRefs","opRoots","includeInternal","refs","Set","opRoot","route","routes","op","operations","resolveModifiers","includes","request","body","bodies","bodyType","resp","responses","headers","h","params","query","computeReachableSchemas","seeds","modelMap","reachable","frontier","length","pop","model","get","bases","b","ref","has","push","generateOpenApi","ctx","contractRoots","config","securitySchemes","doc","openapi","info","title","version","description","servers","security","allSchemas","Map","contractRoot","models","set","modelToSchema","paths","oaPath","convertPath","path","mods","operation","buildOperation","deprecated","method","schemas","filtered","schema","Object","entries","components","keys","toYaml","typeToSchema","properties","required","prop","fieldToSchema","optional","allOf","map","$ref","nullable","wrapNullable","visibility","readOnly","writeOnly","default","undefined","scalarToSchema","arrayToSchema","prefixItems","i","additionalProperties","enum","values","const","oneOf","m","mapping","literalValues","resolveDiscriminatorLiterals","discriminator","v","result","propertyName","inlineObjectToSchema","modelName","find","f","String","s","min","minLength","Number","max","maxLength","len","regex","pattern","minimum","maximum","format","scale","_exhaustive","Error","minItems","maxItems","sdk","operationId","service","methodPart","split","summary","parameters","paramSourceToParams","content","contentType","requestBody","effectiveSecurity","resolveSecurity","SECURITY_NONE","statusKey","statusCode","responseObject","statusDescription","headerObject","emit","location","in","code","descriptions","indent","yamlString","Array","isArray","every","inline","join","lines","pad","repeat","isPlainObject","firstKey","firstVal","firstValStr","isComplex","toYamlValue","yamlKey","k","valStr","obj","key","val","test","replace","plugin","name","cacheKey","generateTargets","contractRoots","opRoots","ctx","securitySchemes","openapiConfig","options","base","baseDir","resolve","rootDir","outPath","output","emitFile","generateOpenApi","config","createOpenApiPlugin","JSON","stringify","content"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractkit/plugin-openapi",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "description": "ContractKit built-in plugin: OpenAPI 3.0 YAML generation",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -27,11 +27,11 @@
27
27
  ".": "./dist/index.js"
28
28
  },
29
29
  "dependencies": {
30
- "@contractkit/core": "0.27.0"
30
+ "@contractkit/core": "0.28.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "yaml": "^2.8.3",
34
- "@contractkit/openapi-to-ck": "0.11.0",
34
+ "@contractkit/openapi-to-ck": "0.12.0",
35
35
  "@repo/config-eslint": "0.3.1",
36
36
  "@repo/config-typescript": "0.1.0"
37
37
  },
@@ -412,6 +412,19 @@ export function scalarToSchema(type: import('@contractkit/core').ScalarTypeNode)
412
412
  s.type = 'integer';
413
413
  s.format = 'int64';
414
414
  break;
415
+ case 'decimal':
416
+ // A quoted string, not `type: number` — the whole point is to keep the value away from
417
+ // the IEEE-754 double a JSON number becomes in most generators.
418
+ s.type = 'string';
419
+ s.format = 'decimal';
420
+ s.pattern = type.scale !== undefined ? `^-?\\d+(\\.\\d{1,${type.scale}})?$` : '^-?\\d+(\\.\\d+)?$';
421
+ // `minimum`/`maximum` are numeric in JSON Schema and ignored on a string type, so the
422
+ // exact bounds ride in vendor extensions instead — which also lets the importer recover
423
+ // them verbatim rather than reverse-engineering the pattern.
424
+ if (type.scale !== undefined) s['x-contractkit-scale'] = type.scale;
425
+ if (type.min !== undefined) s['x-contractkit-min'] = String(type.min);
426
+ if (type.max !== undefined) s['x-contractkit-max'] = String(type.max);
427
+ break;
415
428
  case 'boolean':
416
429
  s.type = 'boolean';
417
430
  break;
@@ -34,8 +34,34 @@ describe('scalarToSchema', () => {
34
34
  expect(scalarToSchema({ kind: 'scalar', name: 'interval' })).toEqual({ type: 'string', format: 'interval' });
35
35
  });
36
36
 
37
+ it('maps decimal to a string, never a number', () => {
38
+ // `type: number` would let a generator route the value through an IEEE-754 double, which
39
+ // is the loss the scalar exists to prevent.
40
+ expect(scalarToSchema({ kind: 'scalar', name: 'decimal' })).toEqual({
41
+ type: 'string',
42
+ format: 'decimal',
43
+ pattern: '^-?\\d+(\\.\\d+)?$',
44
+ });
45
+ });
46
+
47
+ it('narrows the decimal pattern by scale', () => {
48
+ expect(scalarToSchema({ kind: 'scalar', name: 'decimal', scale: 2 })).toMatchObject({
49
+ pattern: '^-?\\d+(\\.\\d{1,2})?$',
50
+ 'x-contractkit-scale': 2,
51
+ });
52
+ });
53
+
54
+ it('carries exact decimal bounds in extensions, not in numeric minimum/maximum', () => {
55
+ // JSON Schema's `minimum`/`maximum` are numeric and ignored on a string type, so the exact
56
+ // values ride in extensions the importer can read back verbatim.
57
+ const schema = scalarToSchema({ kind: 'scalar', name: 'decimal', min: '0.01', max: '999999.99' });
58
+ expect(schema).toMatchObject({ 'x-contractkit-min': '0.01', 'x-contractkit-max': '999999.99' });
59
+ expect(schema).not.toHaveProperty('minimum');
60
+ expect(schema).not.toHaveProperty('maximum');
61
+ });
62
+
37
63
  it('throws on an unmapped scalar name', () => {
38
- expect(() => scalarToSchema({ kind: 'scalar', name: 'decimal' } as any)).toThrow(/unmapped scalar 'decimal'/);
64
+ expect(() => scalarToSchema({ kind: 'scalar', name: 'quaternion' } as any)).toThrow(/unmapped scalar 'quaternion'/);
39
65
  });
40
66
  });
41
67
 
@@ -23,6 +23,7 @@ contract Pet: {
23
23
  name: string(min=1, max=64)
24
24
  code?: string(regex=/^[a-z]+$/)
25
25
  status: enum(available, "on hold")
26
+ price: decimal(min=0.01, max=999999.99, scale=2)
26
27
  }
27
28
 
28
29
  contract ApiError: {
@@ -98,10 +99,20 @@ describe('ck → openapi → ck', () => {
98
99
 
99
100
  // Models, including a regex and a quoted enum value.
100
101
  const pet = after.models.find(m => m.name === 'Pet')!;
101
- expect(pet.fields.map(f => f.name)).toEqual(['id', 'name', 'code', 'status']);
102
+ expect(pet.fields.map(f => f.name)).toEqual(['id', 'name', 'code', 'status', 'price']);
102
103
  expect(pet.fields.find(f => f.name === 'code')!.type).toEqual({ kind: 'scalar', name: 'string', regex: '^[a-z]+$' });
103
104
  expect(pet.fields.find(f => f.name === 'status')!.type).toEqual({ kind: 'enum', values: ['available', 'on hold'] });
104
105
  expect(pet.fields.find(f => f.name === 'name')!.type).toEqual({ kind: 'scalar', name: 'string', min: 1, max: 64 });
106
+
107
+ // A decimal survives as a decimal — bounds recovered from the vendor extensions as exact
108
+ // strings, and the scale-derived `pattern` NOT reimported as a redundant `regex=`.
109
+ expect(pet.fields.find(f => f.name === 'price')!.type).toEqual({
110
+ kind: 'scalar',
111
+ name: 'decimal',
112
+ min: '0.01',
113
+ max: '999999.99',
114
+ scale: 2,
115
+ });
105
116
  });
106
117
 
107
118
  it('carries the documented marker through the vendor extension, not the status heuristic', async () => {