@haddowg/json-api-codegen 1.13.0 → 1.15.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/README.md CHANGED
@@ -30,19 +30,23 @@ pnpm add -D @haddowg/json-api-codegen
30
30
 
31
31
  Two bin names are installed: `json-api-codegen` and the short alias `japi`.
32
32
 
33
+ The codegen reads documents up to generator contract 18 (`info.x-generator.contract`). A newer
34
+ document still generates, with a warning that the client may miss what the server added.
35
+
33
36
  ## CLI
34
37
 
35
38
  ```bash
36
- json-api-codegen --input <url|file> --output <file> [--server <name>] [--schemas <url|file>] [--check]
39
+ json-api-codegen --input <url|file> --output <file> [--server <name>] [--schemas <url|file>] [--examples] [--check]
37
40
  ```
38
41
 
39
- | Flag | Required | Description |
40
- | ----------- | -------- | ------------------------------------------------------------------------------------- |
41
- | `--input` | yes | OpenAPI document source — an http(s) URL or a local file path (JSON or YAML). |
42
- | `--output` | yes | Output file for the generated client (e.g. `src/api/music.gen.ts`). |
43
- | `--server` | no | Server name to target. Metadata-only — the served document is already per-server. |
44
- | `--schemas` | no | JSON Schema source (URL or path) for the validation seam. Omit to skip schema output. |
45
- | `--check` | no | Drift gate — regenerate in memory and exit non-zero if the committed output differs. |
42
+ | Flag | Required | Description |
43
+ | ------------ | -------- | ------------------------------------------------------------------------------------- |
44
+ | `--input` | yes | OpenAPI document source — an http(s) URL or a local file path (JSON or YAML). |
45
+ | `--output` | yes | Output file for the generated client (e.g. `src/api/music.gen.ts`). |
46
+ | `--server` | no | Server name to target. Metadata-only — the served document is already per-server. |
47
+ | `--schemas` | no | JSON Schema source (URL or path) for the validation seam. Omit to skip schema output. |
48
+ | `--examples` | no | Also write the operations' worked examples to a sibling `.examples.gen.ts` module. |
49
+ | `--check` | no | Drift gate — regenerate in memory and exit non-zero if the committed output differs. |
46
50
 
47
51
  Example — generate against a served API, with the validation schemas:
48
52
 
@@ -57,6 +61,8 @@ This writes:
57
61
 
58
62
  - `src/api/music.gen.ts` — the typed client (descriptor + types + bound `createClient`);
59
63
  - `src/api/music.schemas.gen.ts` — the per-type JSON Schema map (only with `--schemas`).
64
+ - `src/api/music.examples.gen.ts` — each operation's worked examples (only with `--examples`, and
65
+ only when the document declares some).
60
66
 
61
67
  The schema artifact path is derived from `--output`: a `.gen.ts` suffix becomes `.schemas.gen.ts`,
62
68
  a plain `.ts` becomes `.schemas.ts`.
@@ -118,6 +124,13 @@ The output module exports:
118
124
  - `Attributes` / `WriteAttributes` — the per-type attribute maps (read vs create/update).
119
125
  - Per-type `…Attributes` / `…CreateAttributes` / `…UpdateAttributes` interfaces, enum types
120
126
  (e.g. `AlbumStatus`), and action input/output types.
127
+ - `Links` — the typed declared links of each type that declares any.
128
+ - `ErrorCodes` / `ErrorCode` / `ErrorCodeByStatus` / `hasErrorCode` — the server's documented
129
+ error codes and a type guard bound to them.
130
+ - `pageLimits` / `relatedPageLimits` — the page defaults and page-size caps of each collection and
131
+ each related collection.
132
+ - `pageKinds` / `relatedPageKinds` — the pagination strategies each collection and related
133
+ collection offers, the server's default first.
121
134
 
122
135
  With `--schemas`, the sibling module exports `schemas` — the per-type JSON Schema 2020-12 map.
123
136
 
@@ -162,8 +175,10 @@ if (!result.ok) process.exit(1)
162
175
  ```
163
176
 
164
177
  Lower-level building blocks are also exported for advanced use: `readDocument` / `readSchemas`,
165
- `buildDescriptor`, `emit`, `emitSchemas`, `detectVerbCollisions`, `schemasOutputPath`, and the
166
- provenance helpers `deriveProvenance` / `provenanceLines` / `hashJson`.
178
+ `buildDescriptor`, `buildExamples`, `emit`, `emitSchemas`, `emitExamples`, `readErrorCatalogue`,
179
+ `emitErrorCodes`, `checkContract`, `detectVerbCollisions`, `schemasOutputPath`,
180
+ `examplesOutputPath`, and the provenance helpers `deriveProvenance` / `provenanceLines` /
181
+ `hashJson`. `generateFiles(config)` generates like `generate` and returns the paths it wrote.
167
182
 
168
183
  ## License
169
184
 
package/dist/cli.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as CodegenConfig } from "./index-C0T-tofN.mjs";
1
+ import { r as CodegenConfig } from "./index-FaJ8Xx6v.mjs";
2
2
 
3
3
  //#region src/cli.d.ts
4
4
  declare const USAGE = "Usage: json-api-codegen --input <url|file> --output <file> [--server <name>] [--schemas <url|file>] [--examples] [--check]";
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as generateFiles, t as check } from "./src-Qe0YLO-w.mjs";
2
+ import { i as generateFiles, t as check } from "./src-i5j2I9fk.mjs";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/cli.ts
@@ -169,9 +169,9 @@ declare class DescriptorBuilder {
169
169
  */
170
170
  private relationMutations;
171
171
  /**
172
- * Attach the per-relation endpoint-exposure signal to a relation descriptor (D24). The bundle
172
+ * Attach the per-relation endpoint-exposure signal to a relation descriptor. The bundle
173
173
  * can suppress a relation's related or relationship endpoint (`withoutRelatedEndpoint()` /
174
- * `withoutRelationshipEndpoint()`, ADR 0027), which the OpenAPI reflects by simply omitting that
174
+ * `withoutRelationshipEndpoint()`), which the OpenAPI reflects by simply omitting that
175
175
  * relation's path — but codegen synthesizes generic `{rel}` templates, erasing the per-relation
176
176
  * distinction. So mark `related: false` / `relationship: false` when this relation's specific
177
177
  * path is absent, and ALWAYS emit an explicit `mutations` object (the verb flags when the
@@ -213,6 +213,12 @@ declare class DescriptorBuilder {
213
213
  private documentPrimary;
214
214
  /** The wire type of a single `data` schema — a `$ref` to a resource, or an inline `type.const`. */
215
215
  private dataConst;
216
+ /**
217
+ * The wire types a read's compound document can carry in `included`: the members of its
218
+ * document's `included.items` union. `undefined` when the document has no `included`, or types
219
+ * its items only as a generic `{type, id}` object.
220
+ */
221
+ private includedTypes;
216
222
  /** The `$ref` of the first 2xx JSON:API response body on an operation (the output document). */
217
223
  private firstOkJsonApiRef;
218
224
  /** Resolve a relationship component's `data` into a {@link RelationDescriptor}. */
@@ -233,6 +239,12 @@ declare class DescriptorBuilder {
233
239
  private pivotDescriptor;
234
240
  /** Pull the related type from the `$ref` entry of an `allOf` (the pivot shape). */
235
241
  private constFromAllOf;
242
+ /**
243
+ * The wire types of a union of `$ref`s: a `oneOf` discriminated by `type`, or the `anyOf` an
244
+ * older document writes. A member whose component carries no `type` const falls back to the
245
+ * discriminator mapping's key for its `$ref`. `undefined` when the schema is no union.
246
+ */
247
+ private unionTypes;
236
248
  /** Resolve every `$ref` entry in a list to its wire type. */
237
249
  private constsFromRefs;
238
250
  private clientId;
@@ -330,7 +342,7 @@ declare function buildExamples(doc: OpenApiDocument, descriptor: ApiDescriptor):
330
342
  /** Below this the document predates the structures this codegen reads. */
331
343
  declare const MINIMUM_CONTRACT = 1;
332
344
  /** Above this the document carries structures this codegen does not read. */
333
- declare const MAXIMUM_CONTRACT = 18;
345
+ declare const MAXIMUM_CONTRACT = 19;
334
346
  /**
335
347
  * Check the document's `info.x-generator.contract` against the range this codegen reads. A
336
348
  * document older than the range throws; a newer one returns a warning, because its new
@@ -397,6 +409,11 @@ declare class Emitter {
397
409
  emit(): string;
398
410
  private header;
399
411
  private imports;
412
+ /**
413
+ * The typed `included` of each read: a resource union discriminated by `type` over the types
414
+ * the document's typed `included` lists, or over every type when the document doesn't type it.
415
+ */
416
+ private includedAliases;
400
417
  /**
401
418
  * The links interface of a type that declares links, e.g. `ImagesLinks`: each declared link an
402
419
  * optional member, since the server leaves a link out when its href resolves to `null`. It
@@ -475,11 +492,39 @@ declare class Emitter {
475
492
  private atomicConst;
476
493
  /**
477
494
  * The `pageLimits` const: per collection, the `default`, `minimum` and `maximum` each `page`
478
- * member declares (generator contract 5 adds the paginator's default and page-size cap). A
479
- * menu nests them under each arm's `kind`. Members declaring none, and collections with no
480
- * such member, are left out. The client never clamps a `page` argument to these.
495
+ * member declares. A menu nests them under each arm's `kind`. Members declaring none, and
496
+ * collections with no such member, are left out. The client never clamps a `page` argument
497
+ * to these.
481
498
  */
482
499
  private pageLimitsConst;
500
+ /**
501
+ * The `relatedPageLimits` const: the `pageLimits` shape for each related collection
502
+ * (`GET /{type}/{id}/{rel}`), keyed by parent type, then by relation name. Relations with no
503
+ * bounded member, and parents left with none, are left out.
504
+ */
505
+ private relatedPageLimitsConst;
506
+ /**
507
+ * The `pageKinds` const: per paginated collection, the strategies its `page` parameter offers,
508
+ * the server's default first.
509
+ */
510
+ private pageKindsConst;
511
+ /**
512
+ * The `relatedPageKinds` const: `pageKinds` for each related collection, keyed by parent type,
513
+ * then by relation name.
514
+ */
515
+ private relatedPageKindsConst;
516
+ /**
517
+ * One line per parent type holding `render`'s value for each relation whose related endpoint
518
+ * it answers for. Relations it skips, and parents left with none, are left out.
519
+ */
520
+ private byRelation;
521
+ /** The `page` parameter schema of the GET at `path`, if it has one. */
522
+ private pageSchema;
523
+ /**
524
+ * A page schema's bounded members as literal lines at `depth` (two spaces per level): flat for
525
+ * one strategy, nested under each arm's `kind` for a menu.
526
+ */
527
+ private pageLimitLines;
483
528
  /** One page schema's members that declare a `default`, `minimum` or `maximum`, as literal lines. */
484
529
  private pageMemberLimits;
485
530
  /**
@@ -612,7 +657,7 @@ declare function readSchemas(input: string): Promise<SchemaBundle>;
612
657
  *
613
658
  * The values are the raw, self-contained resource-object JSON Schema documents the bundle
614
659
  * serves at `GET /schemas.json` — suitable to hand straight to a validation engine via the
615
- * opt-in `validate?` seam (ADR 0004). This is a separate file from the client so the main
660
+ * opt-in `validate?` seam. This is a separate file from the client so the main
616
661
  * descriptor/client output is unaffected when `--schemas` is absent.
617
662
  *
618
663
  * The schemas are emitted as JSON (deterministic, key order preserved) so the artifact is
@@ -697,8 +742,8 @@ declare function examplesOutputPath(output: string): string;
697
742
  *
698
743
  * When `config.schemas` is set, also read the bundle's JSON Schema bundle and write a
699
744
  * separate per-type schema artifact (`<output>.schemas.gen.ts`) beside the client, wiring
700
- * the opt-in validation seam (ADR 0004). The main client output is unchanged either way.
701
- * `config.examples` writes the operation examples the same way (ADR 0011).
745
+ * the opt-in validation seam. The main client output is unchanged either way.
746
+ * `config.examples` writes the operation examples the same way.
702
747
  *
703
748
  * `config.server` is metadata-only for now: the served document is already per-server,
704
749
  * so the caller selects the server by pointing `input` at the right document.
@@ -718,4 +763,4 @@ declare function generateFiles(config: CodegenConfig): Promise<string[]>;
718
763
  declare function check(config: CodegenConfig): Promise<CheckResult>;
719
764
  //#endregion
720
765
  export { operationExamples as A, PathItemObject as B, VerbCollision as C, MINIMUM_CONTRACT as D, MAXIMUM_CONTRACT as E, InfoObject as F, SecurityRequirement as G, ResponseObject as H, MediaTypeObject as I, SecuritySchemeObject as K, OpenApiDocument as L, buildAtomic as M, buildDescriptor as N, checkContract as O, HttpMethod as P, OperationObject as R, Emitter as S, emit as T, SchemaObject as U, RequestBodyObject as V, SchemaOrBool as W, emitExamples as _, examplesOutputPath as a, emitErrorCodes as b, schemasOutputPath as c, hashJson as d, provenanceLines as f, readSchemas as g, readDocument as h, check as i, DescriptorBuilder as j, buildExamples as k, Provenance as l, SchemaBundle as m, CheckResult as n, generate as o, emitSchemas as p, CodegenConfig as r, generateFiles as s, ArtifactDrift as t, deriveProvenance as u, DocumentedErrorCode as v, detectVerbCollisions as w, readErrorCatalogue as x, ErrorCatalogue as y, ParameterObject as z };
721
- //# sourceMappingURL=index-C0T-tofN.d.mts.map
766
+ //# sourceMappingURL=index-FaJ8Xx6v.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-FaJ8Xx6v.d.mts","names":[],"sources":["../src/openapi.ts","../src/build-descriptor.ts","../src/build-examples.ts","../src/contract.ts","../src/emit.ts","../src/error-codes.ts","../src/emit-examples.ts","../src/reader.ts","../src/emit-schemas.ts","../src/provenance.ts","../src/index.ts"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as operationExamples, B as PathItemObject, C as VerbCollision, D as MINIMUM_CONTRACT, E as MAXIMUM_CONTRACT, F as InfoObject, G as SecurityRequirement, H as ResponseObject, I as MediaTypeObject, K as SecuritySchemeObject, L as OpenApiDocument, M as buildAtomic, N as buildDescriptor, O as checkContract, P as HttpMethod, R as OperationObject, S as Emitter, T as emit, U as SchemaObject, V as RequestBodyObject, W as SchemaOrBool, _ as emitExamples, a as examplesOutputPath, b as emitErrorCodes, c as schemasOutputPath, d as hashJson, f as provenanceLines, g as readSchemas, h as readDocument, i as check, j as DescriptorBuilder, k as buildExamples, l as Provenance, m as SchemaBundle, n as CheckResult, o as generate, p as emitSchemas, r as CodegenConfig, s as generateFiles, t as ArtifactDrift, u as deriveProvenance, v as DocumentedErrorCode, w as detectVerbCollisions, x as readErrorCatalogue, y as ErrorCatalogue, z as ParameterObject } from "./index-C0T-tofN.mjs";
1
+ import { A as operationExamples, B as PathItemObject, C as VerbCollision, D as MINIMUM_CONTRACT, E as MAXIMUM_CONTRACT, F as InfoObject, G as SecurityRequirement, H as ResponseObject, I as MediaTypeObject, K as SecuritySchemeObject, L as OpenApiDocument, M as buildAtomic, N as buildDescriptor, O as checkContract, P as HttpMethod, R as OperationObject, S as Emitter, T as emit, U as SchemaObject, V as RequestBodyObject, W as SchemaOrBool, _ as emitExamples, a as examplesOutputPath, b as emitErrorCodes, c as schemasOutputPath, d as hashJson, f as provenanceLines, g as readSchemas, h as readDocument, i as check, j as DescriptorBuilder, k as buildExamples, l as Provenance, m as SchemaBundle, n as CheckResult, o as generate, p as emitSchemas, r as CodegenConfig, s as generateFiles, t as ArtifactDrift, u as deriveProvenance, v as DocumentedErrorCode, w as detectVerbCollisions, x as readErrorCatalogue, y as ErrorCatalogue, z as ParameterObject } from "./index-FaJ8Xx6v.mjs";
2
2
  export { ArtifactDrift, CheckResult, CodegenConfig, DescriptorBuilder, type DocumentedErrorCode, Emitter, type ErrorCatalogue, type HttpMethod, type InfoObject, MAXIMUM_CONTRACT, MINIMUM_CONTRACT, type MediaTypeObject, type OpenApiDocument, type OperationObject, type ParameterObject, type PathItemObject, type Provenance, type RequestBodyObject, type ResponseObject, type SchemaBundle, type SchemaObject, type SchemaOrBool, type SecurityRequirement, type SecuritySchemeObject, type VerbCollision, buildAtomic, buildDescriptor, buildExamples, check, checkContract, deriveProvenance, detectVerbCollisions, emit, emitErrorCodes, emitExamples, emitSchemas, examplesOutputPath, generate, generateFiles, hashJson, operationExamples, provenanceLines, readDocument, readErrorCatalogue, readSchemas, schemasOutputPath };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { C as DescriptorBuilder, S as operationExamples, T as buildDescriptor, _ as readErrorCatalogue, a as schemasOutputPath, b as checkContract, c as emitSchemas, d as detectVerbCollisions, f as emit, g as emitErrorCodes, h as provenanceLines, i as generateFiles, l as emitExamples, m as hashJson, n as examplesOutputPath, o as readDocument, p as deriveProvenance, r as generate, s as readSchemas, t as check, u as Emitter, v as MAXIMUM_CONTRACT, w as buildAtomic, x as buildExamples, y as MINIMUM_CONTRACT } from "./src-Qe0YLO-w.mjs";
1
+ import { C as DescriptorBuilder, S as operationExamples, T as buildDescriptor, _ as readErrorCatalogue, a as schemasOutputPath, b as checkContract, c as emitSchemas, d as detectVerbCollisions, f as emit, g as emitErrorCodes, h as provenanceLines, i as generateFiles, l as emitExamples, m as hashJson, n as examplesOutputPath, o as readDocument, p as deriveProvenance, r as generate, s as readSchemas, t as check, u as Emitter, v as MAXIMUM_CONTRACT, w as buildAtomic, x as buildExamples, y as MINIMUM_CONTRACT } from "./src-i5j2I9fk.mjs";
2
2
  export { DescriptorBuilder, Emitter, MAXIMUM_CONTRACT, MINIMUM_CONTRACT, buildAtomic, buildDescriptor, buildExamples, check, checkContract, deriveProvenance, detectVerbCollisions, emit, emitErrorCodes, emitExamples, emitSchemas, examplesOutputPath, generate, generateFiles, hashJson, operationExamples, provenanceLines, readDocument, readErrorCatalogue, readSchemas, schemasOutputPath };
@@ -173,6 +173,8 @@ var DescriptorBuilder = class {
173
173
  if (countable !== void 0) descriptor.countable = countable;
174
174
  const includable = this.includable(collection);
175
175
  if (includable !== void 0) descriptor.includable = includable;
176
+ const included = this.includedTypes(collection === void 0 ? void 0 : this.operation(collection, "get"));
177
+ if (included !== void 0) descriptor.included = included;
176
178
  const sortable = this.sortable(collection);
177
179
  if (sortable !== void 0) descriptor.sortable = sortable;
178
180
  const filterable = this.filterable(collection);
@@ -337,9 +339,9 @@ var DescriptorBuilder = class {
337
339
  return mutations;
338
340
  }
339
341
  /**
340
- * Attach the per-relation endpoint-exposure signal to a relation descriptor (D24). The bundle
342
+ * Attach the per-relation endpoint-exposure signal to a relation descriptor. The bundle
341
343
  * can suppress a relation's related or relationship endpoint (`withoutRelatedEndpoint()` /
342
- * `withoutRelationshipEndpoint()`, ADR 0027), which the OpenAPI reflects by simply omitting that
344
+ * `withoutRelationshipEndpoint()`), which the OpenAPI reflects by simply omitting that
343
345
  * relation's path — but codegen synthesizes generic `{rel}` templates, erasing the per-relation
344
346
  * distinction. So mark `related: false` / `relationship: false` when this relation's specific
345
347
  * path is absent, and ALWAYS emit an explicit `mutations` object (the verb flags when the
@@ -359,6 +361,8 @@ var DescriptorBuilder = class {
359
361
  out.relationship = false;
360
362
  out.mutations = {};
361
363
  } else out.mutations = mutations;
364
+ const included = this.includedTypes(relatedGet);
365
+ if (included !== void 0) out.included = included;
362
366
  const countable = withCountParam(relatedGet) ?? withCountParam(this.operation(relationshipPath, "get"));
363
367
  if (countable !== void 0) out.countable = countable;
364
368
  if (descriptor.cardinality === "many") {
@@ -532,6 +536,18 @@ var DescriptorBuilder = class {
532
536
  const typeProp = data.properties?.["type"];
533
537
  return isSchema$3(typeProp) && typeof typeProp.const === "string" ? typeProp.const : void 0;
534
538
  }
539
+ /**
540
+ * The wire types a read's compound document can carry in `included`: the members of its
541
+ * document's `included.items` union. `undefined` when the document has no `included`, or types
542
+ * its items only as a generic `{type, id}` object.
543
+ */
544
+ includedTypes(op) {
545
+ const ref = op === void 0 ? void 0 : this.firstOkJsonApiRef(op);
546
+ const included = (ref === void 0 ? void 0 : this.schemas[refName$1(ref) ?? ""])?.properties?.["included"];
547
+ const items = isSchema$3(included) ? included.items : void 0;
548
+ const types = items === void 0 || !Array.isArray(items.oneOf) ? void 0 : this.unionTypes(items);
549
+ return types !== void 0 && types.length > 0 ? types : void 0;
550
+ }
535
551
  /** The `$ref` of the first 2xx JSON:API response body on an operation (the output document). */
536
552
  firstOkJsonApiRef(op) {
537
553
  for (const [code, response] of Object.entries(op.responses ?? {})) {
@@ -568,15 +584,13 @@ var DescriptorBuilder = class {
568
584
  if (readPivot !== void 0) relation.readPivot = readPivot;
569
585
  return relation;
570
586
  }
571
- if (Array.isArray(items.anyOf)) {
572
- const types = this.constsFromRefs(items.anyOf);
573
- return types.length === 0 ? void 0 : {
574
- cardinality: "many",
575
- types,
576
- polymorphic: true,
577
- pivot: false
578
- };
579
- }
587
+ const types = this.unionTypes(items);
588
+ if (types !== void 0) return types.length === 0 ? void 0 : {
589
+ cardinality: "many",
590
+ types,
591
+ polymorphic: true,
592
+ pivot: false
593
+ };
580
594
  return;
581
595
  }
582
596
  if (Array.isArray(data.anyOf)) {
@@ -594,9 +608,12 @@ var DescriptorBuilder = class {
594
608
  pivot = true;
595
609
  readPivot = this.pivotDescriptor(pivotNodeFromAllOf(entry.allOf), "read");
596
610
  }
597
- } else if (Array.isArray(entry.anyOf)) {
598
- types.push(...this.constsFromRefs(entry.anyOf));
599
- polymorphic = true;
611
+ } else {
612
+ const union = this.unionTypes(entry);
613
+ if (union !== void 0) {
614
+ types.push(...union);
615
+ polymorphic = true;
616
+ }
600
617
  }
601
618
  if (types.length === 0) return;
602
619
  const relation = polymorphic ? {
@@ -655,6 +672,24 @@ var DescriptorBuilder = class {
655
672
  if (type !== void 0) return type;
656
673
  }
657
674
  }
675
+ /**
676
+ * The wire types of a union of `$ref`s: a `oneOf` discriminated by `type`, or the `anyOf` an
677
+ * older document writes. A member whose component carries no `type` const falls back to the
678
+ * discriminator mapping's key for its `$ref`. `undefined` when the schema is no union.
679
+ */
680
+ unionTypes(schema) {
681
+ if (Array.isArray(schema.oneOf)) {
682
+ const byRef = discriminatorTypesByRef(schema);
683
+ const types = [];
684
+ for (const entry of schema.oneOf) {
685
+ if (typeof entry.$ref !== "string") continue;
686
+ const type = this.resolveConst(entry.$ref) ?? byRef.get(entry.$ref);
687
+ if (type !== void 0) types.push(type);
688
+ }
689
+ return types;
690
+ }
691
+ return Array.isArray(schema.anyOf) ? this.constsFromRefs(schema.anyOf) : void 0;
692
+ }
658
693
  /** Resolve every `$ref` entry in a list to its wire type. */
659
694
  constsFromRefs(entries) {
660
695
  const types = [];
@@ -846,6 +881,16 @@ function pivotNodeOf(component) {
846
881
  if (Array.isArray(data.items?.allOf)) return pivotNodeFromAllOf(data.items.allOf);
847
882
  for (const entry of data.anyOf ?? []) if (Array.isArray(entry.allOf)) return pivotNodeFromAllOf(entry.allOf);
848
883
  }
884
+ /** A `type` discriminator's mapping, inverted to the wire type each `$ref` stands for. */
885
+ function discriminatorTypesByRef(schema) {
886
+ const byRef = /* @__PURE__ */ new Map();
887
+ const discriminator = schema["discriminator"];
888
+ if (typeof discriminator !== "object" || discriminator === null) return byRef;
889
+ const { propertyName, mapping } = discriminator;
890
+ if (propertyName !== "type" || typeof mapping !== "object" || mapping === null) return byRef;
891
+ for (const [type, ref] of Object.entries(mapping)) if (typeof ref === "string") byRef.set(ref, type);
892
+ return byRef;
893
+ }
849
894
  /** Whether a schema admits `null`: a `null` type, or a `null` branch in `anyOf`/`oneOf`. */
850
895
  function isNullable(schema) {
851
896
  const type = schema.type;
@@ -1032,7 +1077,7 @@ function buildAtomic(doc) {
1032
1077
  }
1033
1078
  /**
1034
1079
  * Classify a page member set: `number`+`size` = `page`; `offset`+`limit` = `offset`; either
1035
- * cursor bound (`after`/`before`) = `cursor` (link-driven, D44); `number` alone = `fixed` (the
1080
+ * cursor bound (`after`/`before`) = `cursor` (link-driven); `number` alone = `fixed` (the
1036
1081
  * server fixes the size); no members = `none`; anything else = `undefined` (unrecognised).
1037
1082
  */
1038
1083
  function kindOfMembers(members) {
@@ -1185,7 +1230,7 @@ function buildExamples(doc, descriptor) {
1185
1230
  /** Below this the document predates the structures this codegen reads. */
1186
1231
  const MINIMUM_CONTRACT = 1;
1187
1232
  /** Above this the document carries structures this codegen does not read. */
1188
- const MAXIMUM_CONTRACT = 18;
1233
+ const MAXIMUM_CONTRACT = 19;
1189
1234
  /**
1190
1235
  * Check the document's `info.x-generator.contract` against the range this codegen reads. A
1191
1236
  * document older than the range throws; a newer one returns a warning, because its new
@@ -1195,8 +1240,8 @@ const MAXIMUM_CONTRACT = 18;
1195
1240
  function checkContract(doc) {
1196
1241
  const contract = doc.info?.["x-generator"]?.contract;
1197
1242
  if (typeof contract !== "number") return;
1198
- if (contract < 1) throw new Error(`the document declares contract ${contract}, which predates what this codegen reads (it supports 1 to 18). Use an older codegen release, or regenerate the document from a newer server.`);
1199
- if (contract > 18) return `the document declares contract ${contract} and this codegen reads up to 18. It describes capabilities that were not generated; upgrade the codegen to pick them up.`;
1243
+ if (contract < 1) throw new Error(`the document declares contract ${contract}, which predates what this codegen reads (it supports 1 to 19). Use an older codegen release, or regenerate the document from a newer server.`);
1244
+ if (contract > 19) return `the document declares contract ${contract} and this codegen reads up to 19. It describes capabilities that were not generated; upgrade the codegen to pick them up.`;
1200
1245
  }
1201
1246
  //#endregion
1202
1247
  //#region src/declared-links.ts
@@ -1559,6 +1604,33 @@ function refName(ref) {
1559
1604
  function isSchema(value) {
1560
1605
  return typeof value === "object" && value !== null;
1561
1606
  }
1607
+ /** A `{ … }` object literal of pre-rendered entry lines, or `{}` when there are none. */
1608
+ function objectBody(lines) {
1609
+ return lines.length === 0 ? "{}" : `{\n${lines.join("\n")}\n}`;
1610
+ }
1611
+ /** The `kind` const a page strategy menu arm is selected by. */
1612
+ function armKind(arm) {
1613
+ const kind = arm.properties?.["kind"];
1614
+ return isSchema(kind) && typeof kind.const === "string" ? kind.const : void 0;
1615
+ }
1616
+ /**
1617
+ * The strategies a page schema offers, as a tuple literal, read from the `x-paginator` stamp: a
1618
+ * single strategy's name, or a menu's arm kinds with its stamped default first. `undefined` when
1619
+ * the schema is absent, unstamped, or a menu whose default is not one of its arms.
1620
+ */
1621
+ function pageKinds(schema) {
1622
+ const stamp = schema?.["x-paginator"];
1623
+ if (typeof stamp !== "object" || stamp === null) return;
1624
+ const { strategy, default: fallback } = stamp;
1625
+ let kinds;
1626
+ if (strategy === "multi") {
1627
+ const arms = (schema?.oneOf ?? []).flatMap((arm) => armKind(arm) ?? []);
1628
+ if (typeof fallback !== "string" || !arms.includes(fallback)) return;
1629
+ kinds = [fallback, ...arms.filter((kind) => kind !== fallback)];
1630
+ } else if (typeof strategy === "string") kinds = [strategy];
1631
+ else return;
1632
+ return `[${kinds.map((kind) => stringLiteral(kind)).join(", ")}]`;
1633
+ }
1562
1634
  /** PascalCase a JSON:API wire type for use in a TS identifier (e.g. `public-profiles` -> `PublicProfiles`). */
1563
1635
  function pascalCase(value) {
1564
1636
  return value.split(/[^a-zA-Z0-9]+/).filter((part) => part.length > 0).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
@@ -1912,8 +1984,12 @@ var Emitter = class {
1912
1984
  this.actionTypesMap(),
1913
1985
  this.resourceMap(),
1914
1986
  "export type ResourceMap = typeof resourceMap",
1987
+ this.includedAliases(),
1915
1988
  this.atomicConst(),
1916
1989
  this.pageLimitsConst(),
1990
+ this.relatedPageLimitsConst(),
1991
+ this.pageKindsConst(),
1992
+ this.relatedPageKindsConst(),
1917
1993
  emitErrorCodes(readErrorCatalogue(this.doc)),
1918
1994
  this.boundFactory()
1919
1995
  ].join("\n\n")}\n`;
@@ -1936,11 +2012,42 @@ var Emitter = class {
1936
2012
  " createClient as createClientRuntime,",
1937
2013
  " type ErrorCodeGuard,",
1938
2014
  " hasErrorCode as hasErrorCodeRuntime,",
2015
+ " type IncludedResource,",
2016
+ " type RelatedIncludedResource,",
2017
+ " type RelationName,",
1939
2018
  ...this.declaredLinks.size > 0 ? [" type ResourceLinks,"] : [],
2019
+ " type TypeName,",
2020
+ " type WithLinks,",
1940
2021
  "} from '@haddowg/json-api-client'"
1941
2022
  ].join("\n");
1942
2023
  }
1943
2024
  /**
2025
+ * The typed `included` of each read: a resource union discriminated by `type` over the types
2026
+ * the document's typed `included` lists, or over every type when the document doesn't type it.
2027
+ */
2028
+ includedAliases() {
2029
+ return [
2030
+ "/**",
2031
+ " * One resource of a compound document's `included` for a read of type `T`, discriminated by",
2032
+ " * `type` over the types its `include` paths reach.",
2033
+ " */",
2034
+ "export type Included<T extends TypeName<ResourceMap>> = IncludedResource<",
2035
+ " ResourceMap,",
2036
+ " WithLinks<Attributes, Links>,",
2037
+ " T",
2038
+ ">",
2039
+ "",
2040
+ "/**",
2041
+ " * One resource of the `included` of relation `R`'s related read on type `T`, discriminated by",
2042
+ " * `type` over the types its `include` paths reach.",
2043
+ " */",
2044
+ "export type RelatedIncluded<",
2045
+ " T extends TypeName<ResourceMap>,",
2046
+ " R extends RelationName<ResourceMap, T>,",
2047
+ "> = RelatedIncludedResource<ResourceMap, WithLinks<Attributes, Links>, T, R>"
2048
+ ].join("\n");
2049
+ }
2050
+ /**
1944
2051
  * The links interface of a type that declares links, e.g. `ImagesLinks`: each declared link an
1945
2052
  * optional member, since the server leaves a link out when its href resolves to `null`. It
1946
2053
  * extends the loose `ResourceLinks`, so `self` and any link the type doesn't declare stay
@@ -2214,23 +2321,14 @@ var Emitter = class {
2214
2321
  }
2215
2322
  /**
2216
2323
  * The `pageLimits` const: per collection, the `default`, `minimum` and `maximum` each `page`
2217
- * member declares (generator contract 5 adds the paginator's default and page-size cap). A
2218
- * menu nests them under each arm's `kind`. Members declaring none, and collections with no
2219
- * such member, are left out. The client never clamps a `page` argument to these.
2324
+ * member declares. A menu nests them under each arm's `kind`. Members declaring none, and
2325
+ * collections with no such member, are left out. The client never clamps a `page` argument
2326
+ * to these.
2220
2327
  */
2221
2328
  pageLimitsConst() {
2222
2329
  const typeLines = [];
2223
2330
  for (const type of Object.keys(this.descriptor)) {
2224
- const path = this.descriptor[type].paths["fetchMany"];
2225
- const schema = (path === void 0 ? void 0 : this.doc.paths?.[path]?.get?.parameters)?.find((param) => param.name === "page")?.schema;
2226
- if (schema === void 0) continue;
2227
- let body = [];
2228
- if (schema.oneOf !== void 0) for (const arm of schema.oneOf) {
2229
- const kind = isSchema(arm.properties?.["kind"]) ? arm.properties["kind"].const : void 0;
2230
- const members = this.pageMemberLimits(arm, " ");
2231
- if (typeof kind === "string" && members.length > 0) body.push(` ${propertyKey(kind)}: {\n${members.join("\n")}\n },`);
2232
- }
2233
- else body = this.pageMemberLimits(schema, " ");
2331
+ const body = this.pageLimitLines(this.pageSchema(this.descriptor[type].paths["fetchMany"]), 2);
2234
2332
  if (body.length > 0) typeLines.push(` ${propertyKey(type)}: {\n${body.join("\n")}\n },`);
2235
2333
  }
2236
2334
  return `${[
@@ -2240,7 +2338,96 @@ var Emitter = class {
2240
2338
  " * is absent and `maximum` is the largest value it serves. The client sends the `page` you pass",
2241
2339
  " * unchanged, so read these to bound a page-size picker.",
2242
2340
  " */"
2243
- ].join("\n")}\nexport const pageLimits = ${typeLines.length === 0 ? "{}" : `{\n${typeLines.join("\n")}\n}`} as const`;
2341
+ ].join("\n")}\nexport const pageLimits = ${objectBody(typeLines)} as const`;
2342
+ }
2343
+ /**
2344
+ * The `relatedPageLimits` const: the `pageLimits` shape for each related collection
2345
+ * (`GET /{type}/{id}/{rel}`), keyed by parent type, then by relation name. Relations with no
2346
+ * bounded member, and parents left with none, are left out.
2347
+ */
2348
+ relatedPageLimitsConst() {
2349
+ const lines = this.byRelation((path) => {
2350
+ const body = this.pageLimitLines(this.pageSchema(path), 3);
2351
+ return body.length === 0 ? void 0 : `{\n${body.join("\n")}\n }`;
2352
+ });
2353
+ return `${[
2354
+ "/**",
2355
+ " * The bounds each related collection's `page` parameter declares, keyed by parent type, then by",
2356
+ " * relation name, then as in `pageLimits`: by strategy for a related collection offering several,",
2357
+ " * then by wire key. Relations whose related endpoint declares no bounds are left out.",
2358
+ " */"
2359
+ ].join("\n")}\nexport const relatedPageLimits = ${objectBody(lines)} as const`;
2360
+ }
2361
+ /**
2362
+ * The `pageKinds` const: per paginated collection, the strategies its `page` parameter offers,
2363
+ * the server's default first.
2364
+ */
2365
+ pageKindsConst() {
2366
+ const lines = [];
2367
+ for (const type of Object.keys(this.descriptor)) {
2368
+ const kinds = pageKinds(this.pageSchema(this.descriptor[type].paths["fetchMany"]));
2369
+ if (kinds !== void 0) lines.push(` ${propertyKey(type)}: ${kinds},`);
2370
+ }
2371
+ return `${[
2372
+ "/**",
2373
+ " * The pagination strategies each collection offers, keyed by type. A single strategy is a",
2374
+ " * one-entry tuple; a menu lists its arms' `kind` values with the one the server uses when the",
2375
+ " * request names none first. Unpaginated collections are left out.",
2376
+ " */"
2377
+ ].join("\n")}\nexport const pageKinds = ${objectBody(lines)} as const`;
2378
+ }
2379
+ /**
2380
+ * The `relatedPageKinds` const: `pageKinds` for each related collection, keyed by parent type,
2381
+ * then by relation name.
2382
+ */
2383
+ relatedPageKindsConst() {
2384
+ const lines = this.byRelation((path) => pageKinds(this.pageSchema(path)));
2385
+ return `${[
2386
+ "/**",
2387
+ " * The pagination strategies each related collection offers, keyed by parent type, then by",
2388
+ " * relation name, in the same form as `pageKinds`. Unpaginated relations are left out.",
2389
+ " */"
2390
+ ].join("\n")}\nexport const relatedPageKinds = ${objectBody(lines)} as const`;
2391
+ }
2392
+ /**
2393
+ * One line per parent type holding `render`'s value for each relation whose related endpoint
2394
+ * it answers for. Relations it skips, and parents left with none, are left out.
2395
+ */
2396
+ byRelation(render) {
2397
+ const lines = [];
2398
+ for (const type of Object.keys(this.descriptor)) {
2399
+ const resource = this.descriptor[type];
2400
+ const template = resource.paths["fetchRelated"];
2401
+ if (template === void 0) continue;
2402
+ const relLines = [];
2403
+ for (const [name, relation] of Object.entries(resource.relations)) {
2404
+ if (relation.related === false) continue;
2405
+ const value = render(template.replace("{rel}", relation.uriField ?? name));
2406
+ if (value !== void 0) relLines.push(` ${propertyKey(name)}: ${value},`);
2407
+ }
2408
+ if (relLines.length > 0) lines.push(` ${propertyKey(type)}: {\n${relLines.join("\n")}\n },`);
2409
+ }
2410
+ return lines;
2411
+ }
2412
+ /** The `page` parameter schema of the GET at `path`, if it has one. */
2413
+ pageSchema(path) {
2414
+ return (path === void 0 ? void 0 : this.doc.paths?.[path]?.get?.parameters)?.find((param) => param.name === "page")?.schema;
2415
+ }
2416
+ /**
2417
+ * A page schema's bounded members as literal lines at `depth` (two spaces per level): flat for
2418
+ * one strategy, nested under each arm's `kind` for a menu.
2419
+ */
2420
+ pageLimitLines(schema, depth) {
2421
+ if (schema === void 0) return [];
2422
+ const indent = " ".repeat(depth);
2423
+ if (schema.oneOf === void 0) return this.pageMemberLimits(schema, indent);
2424
+ const lines = [];
2425
+ for (const arm of schema.oneOf) {
2426
+ const kind = armKind(arm);
2427
+ const members = this.pageMemberLimits(arm, `${indent} `);
2428
+ if (kind !== void 0 && members.length > 0) lines.push(`${indent}${propertyKey(kind)}: {\n${members.join("\n")}\n${indent}},`);
2429
+ }
2430
+ return lines;
2244
2431
  }
2245
2432
  /** One page schema's members that declare a `default`, `minimum` or `maximum`, as literal lines. */
2246
2433
  pageMemberLimits(schema, indent) {
@@ -2501,7 +2688,7 @@ function emitExamples(doc, examples) {
2501
2688
  *
2502
2689
  * The values are the raw, self-contained resource-object JSON Schema documents the bundle
2503
2690
  * serves at `GET /schemas.json` — suitable to hand straight to a validation engine via the
2504
- * opt-in `validate?` seam (ADR 0004). This is a separate file from the client so the main
2691
+ * opt-in `validate?` seam. This is a separate file from the client so the main
2505
2692
  * descriptor/client output is unaffected when `--schemas` is absent.
2506
2693
  *
2507
2694
  * The schemas are emitted as JSON (deterministic, key order preserved) so the artifact is
@@ -2572,10 +2759,10 @@ async function readSchemas(input) {
2572
2759
  /**
2573
2760
  * @haddowg/json-api-codegen — reads the bundle's OpenAPI 3.1 document (from a URL or
2574
2761
  * a local file) and emits the runtime descriptor + derived types + a bound
2575
- * `createClient` (ADR 0001). Optionally reads the bundle's JSON Schemas to wire the
2762
+ * `createClient`. Optionally reads the bundle's JSON Schemas to wire the
2576
2763
  * opt-in validation seam.
2577
2764
  *
2578
- * The spec is fully self-describing for our needs (see CONTEXT.md): type identity is
2765
+ * The spec is fully self-describing for our needs (see docs/concepts.md): type identity is
2579
2766
  * `properties.type.const`; relationships, cardinality and related types come from the
2580
2767
  * relationship components' `data`; allowed `?include` paths are a bounded, pre-expanded
2581
2768
  * enum per endpoint; the create client-id policy is encoded in the create request
@@ -2637,8 +2824,8 @@ async function render(config) {
2637
2824
  *
2638
2825
  * When `config.schemas` is set, also read the bundle's JSON Schema bundle and write a
2639
2826
  * separate per-type schema artifact (`<output>.schemas.gen.ts`) beside the client, wiring
2640
- * the opt-in validation seam (ADR 0004). The main client output is unchanged either way.
2641
- * `config.examples` writes the operation examples the same way (ADR 0011).
2827
+ * the opt-in validation seam. The main client output is unchanged either way.
2828
+ * `config.examples` writes the operation examples the same way.
2642
2829
  *
2643
2830
  * `config.server` is metadata-only for now: the served document is already per-server,
2644
2831
  * so the caller selects the server by pointing `input` at the right document.
@@ -2688,4 +2875,4 @@ async function check(config) {
2688
2875
  //#endregion
2689
2876
  export { DescriptorBuilder as C, operationExamples as S, buildDescriptor as T, readErrorCatalogue as _, schemasOutputPath as a, checkContract as b, emitSchemas as c, detectVerbCollisions as d, emit as f, emitErrorCodes as g, provenanceLines as h, generateFiles as i, emitExamples as l, hashJson as m, examplesOutputPath as n, readDocument as o, deriveProvenance as p, generate as r, readSchemas as s, check as t, Emitter as u, MAXIMUM_CONTRACT as v, buildAtomic as w, buildExamples as x, MINIMUM_CONTRACT as y };
2690
2877
 
2691
- //# sourceMappingURL=src-Qe0YLO-w.mjs.map
2878
+ //# sourceMappingURL=src-i5j2I9fk.mjs.map