@haddowg/json-api-codegen 1.12.0 → 1.14.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,10 @@ 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` — each collection's page defaults and page-size caps.
121
131
 
122
132
  With `--schemas`, the sibling module exports `schemas` — the per-type JSON Schema 2020-12 map.
123
133
 
@@ -162,8 +172,10 @@ if (!result.ok) process.exit(1)
162
172
  ```
163
173
 
164
174
  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`.
175
+ `buildDescriptor`, `buildExamples`, `emit`, `emitSchemas`, `emitExamples`, `readErrorCatalogue`,
176
+ `emitErrorCodes`, `checkContract`, `detectVerbCollisions`, `schemasOutputPath`,
177
+ `examplesOutputPath`, and the provenance helpers `deriveProvenance` / `provenanceLines` /
178
+ `hashJson`. `generateFiles(config)` generates like `generate` and returns the paths it wrote.
167
179
 
168
180
  ## License
169
181
 
package/dist/cli.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as CodegenConfig } from "./index-Ch2PJH9Y.mjs";
1
+ import { r as CodegenConfig } from "./index-DQ_twN9s.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-D4apiVHe.mjs";
2
+ import { i as generateFiles, t as check } from "./src-7yVCQTGQ.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 = 17;
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
@@ -612,7 +629,7 @@ declare function readSchemas(input: string): Promise<SchemaBundle>;
612
629
  *
613
630
  * The values are the raw, self-contained resource-object JSON Schema documents the bundle
614
631
  * 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
632
+ * opt-in `validate?` seam. This is a separate file from the client so the main
616
633
  * descriptor/client output is unaffected when `--schemas` is absent.
617
634
  *
618
635
  * The schemas are emitted as JSON (deterministic, key order preserved) so the artifact is
@@ -697,8 +714,8 @@ declare function examplesOutputPath(output: string): string;
697
714
  *
698
715
  * When `config.schemas` is set, also read the bundle's JSON Schema bundle and write a
699
716
  * 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).
717
+ * the opt-in validation seam. The main client output is unchanged either way.
718
+ * `config.examples` writes the operation examples the same way.
702
719
  *
703
720
  * `config.server` is metadata-only for now: the served document is already per-server,
704
721
  * so the caller selects the server by pointing `input` at the right document.
@@ -718,4 +735,4 @@ declare function generateFiles(config: CodegenConfig): Promise<string[]>;
718
735
  declare function check(config: CodegenConfig): Promise<CheckResult>;
719
736
  //#endregion
720
737
  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-Ch2PJH9Y.d.mts.map
738
+ //# sourceMappingURL=index-DQ_twN9s.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-DQ_twN9s.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-Ch2PJH9Y.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-DQ_twN9s.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-D4apiVHe.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-7yVCQTGQ.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 = 17;
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 = 17;
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 17). Use an older codegen release, or regenerate the document from a newer server.`);
1199
- if (contract > 17) return `the document declares contract ${contract} and this codegen reads up to 17. 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
@@ -1205,6 +1250,16 @@ function isSchema$2(value) {
1205
1250
  return typeof value === "object" && value !== null;
1206
1251
  }
1207
1252
  /**
1253
+ * The named links of a `<Type>ResourceLinks` component. From generator contract 18 the component
1254
+ * is an `allOf` of the shared `ResourceLinks` and an object listing them; before, it listed them
1255
+ * beside `self`.
1256
+ */
1257
+ function namedLinks(component) {
1258
+ const own = Object.entries(component?.properties ?? {});
1259
+ const extended = (component?.allOf ?? []).filter((member) => member.$ref === void 0).flatMap((member) => Object.entries(member.properties ?? {}));
1260
+ return [...own, ...extended];
1261
+ }
1262
+ /**
1208
1263
  * The declared links on a `<Type>ResourceLinks` component, in document order. `self` is never
1209
1264
  * declared, and a link the server builds itself, such as an action's link, points at
1210
1265
  * `RenderedLink`, so neither is read. A server that renders its links as strings points those at
@@ -1213,7 +1268,7 @@ function isSchema$2(value) {
1213
1268
  */
1214
1269
  function readDeclaredLinks(component) {
1215
1270
  const out = [];
1216
- for (const [name, schema] of Object.entries(component?.properties ?? {})) {
1271
+ for (const [name, schema] of namedLinks(component)) {
1217
1272
  if (name === "self" || !isSchema$2(schema)) continue;
1218
1273
  if (schema.$ref === LINK_OBJECT_REF) out.push({
1219
1274
  name,
@@ -1902,6 +1957,7 @@ var Emitter = class {
1902
1957
  this.actionTypesMap(),
1903
1958
  this.resourceMap(),
1904
1959
  "export type ResourceMap = typeof resourceMap",
1960
+ this.includedAliases(),
1905
1961
  this.atomicConst(),
1906
1962
  this.pageLimitsConst(),
1907
1963
  emitErrorCodes(readErrorCatalogue(this.doc)),
@@ -1926,11 +1982,42 @@ var Emitter = class {
1926
1982
  " createClient as createClientRuntime,",
1927
1983
  " type ErrorCodeGuard,",
1928
1984
  " hasErrorCode as hasErrorCodeRuntime,",
1985
+ " type IncludedResource,",
1986
+ " type RelatedIncludedResource,",
1987
+ " type RelationName,",
1929
1988
  ...this.declaredLinks.size > 0 ? [" type ResourceLinks,"] : [],
1989
+ " type TypeName,",
1990
+ " type WithLinks,",
1930
1991
  "} from '@haddowg/json-api-client'"
1931
1992
  ].join("\n");
1932
1993
  }
1933
1994
  /**
1995
+ * The typed `included` of each read: a resource union discriminated by `type` over the types
1996
+ * the document's typed `included` lists, or over every type when the document doesn't type it.
1997
+ */
1998
+ includedAliases() {
1999
+ return [
2000
+ "/**",
2001
+ " * One resource of a compound document's `included` for a read of type `T`, discriminated by",
2002
+ " * `type` over the types its `include` paths reach.",
2003
+ " */",
2004
+ "export type Included<T extends TypeName<ResourceMap>> = IncludedResource<",
2005
+ " ResourceMap,",
2006
+ " WithLinks<Attributes, Links>,",
2007
+ " T",
2008
+ ">",
2009
+ "",
2010
+ "/**",
2011
+ " * One resource of the `included` of relation `R`'s related read on type `T`, discriminated by",
2012
+ " * `type` over the types its `include` paths reach.",
2013
+ " */",
2014
+ "export type RelatedIncluded<",
2015
+ " T extends TypeName<ResourceMap>,",
2016
+ " R extends RelationName<ResourceMap, T>,",
2017
+ "> = RelatedIncludedResource<ResourceMap, WithLinks<Attributes, Links>, T, R>"
2018
+ ].join("\n");
2019
+ }
2020
+ /**
1934
2021
  * The links interface of a type that declares links, e.g. `ImagesLinks`: each declared link an
1935
2022
  * optional member, since the server leaves a link out when its href resolves to `null`. It
1936
2023
  * extends the loose `ResourceLinks`, so `self` and any link the type doesn't declare stay
@@ -2491,7 +2578,7 @@ function emitExamples(doc, examples) {
2491
2578
  *
2492
2579
  * The values are the raw, self-contained resource-object JSON Schema documents the bundle
2493
2580
  * serves at `GET /schemas.json` — suitable to hand straight to a validation engine via the
2494
- * opt-in `validate?` seam (ADR 0004). This is a separate file from the client so the main
2581
+ * opt-in `validate?` seam. This is a separate file from the client so the main
2495
2582
  * descriptor/client output is unaffected when `--schemas` is absent.
2496
2583
  *
2497
2584
  * The schemas are emitted as JSON (deterministic, key order preserved) so the artifact is
@@ -2562,10 +2649,10 @@ async function readSchemas(input) {
2562
2649
  /**
2563
2650
  * @haddowg/json-api-codegen — reads the bundle's OpenAPI 3.1 document (from a URL or
2564
2651
  * a local file) and emits the runtime descriptor + derived types + a bound
2565
- * `createClient` (ADR 0001). Optionally reads the bundle's JSON Schemas to wire the
2652
+ * `createClient`. Optionally reads the bundle's JSON Schemas to wire the
2566
2653
  * opt-in validation seam.
2567
2654
  *
2568
- * The spec is fully self-describing for our needs (see CONTEXT.md): type identity is
2655
+ * The spec is fully self-describing for our needs (see docs/concepts.md): type identity is
2569
2656
  * `properties.type.const`; relationships, cardinality and related types come from the
2570
2657
  * relationship components' `data`; allowed `?include` paths are a bounded, pre-expanded
2571
2658
  * enum per endpoint; the create client-id policy is encoded in the create request
@@ -2627,8 +2714,8 @@ async function render(config) {
2627
2714
  *
2628
2715
  * When `config.schemas` is set, also read the bundle's JSON Schema bundle and write a
2629
2716
  * separate per-type schema artifact (`<output>.schemas.gen.ts`) beside the client, wiring
2630
- * the opt-in validation seam (ADR 0004). The main client output is unchanged either way.
2631
- * `config.examples` writes the operation examples the same way (ADR 0011).
2717
+ * the opt-in validation seam. The main client output is unchanged either way.
2718
+ * `config.examples` writes the operation examples the same way.
2632
2719
  *
2633
2720
  * `config.server` is metadata-only for now: the served document is already per-server,
2634
2721
  * so the caller selects the server by pointing `input` at the right document.
@@ -2678,4 +2765,4 @@ async function check(config) {
2678
2765
  //#endregion
2679
2766
  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 };
2680
2767
 
2681
- //# sourceMappingURL=src-D4apiVHe.mjs.map
2768
+ //# sourceMappingURL=src-7yVCQTGQ.mjs.map