@haddowg/json-api-codegen 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { r as CodegenConfig } from "./index-k_oATv7Q.mjs";
1
+ import { r as CodegenConfig } from "./index-BLBSnK8y.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-py1RBWZr.mjs";
2
+ import { i as generateFiles, t as check } from "./src-D29xU79S.mjs";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/cli.ts
@@ -56,6 +56,20 @@ interface OperationObject {
56
56
  parameters?: readonly ParameterObject[];
57
57
  requestBody?: RequestBodyObject;
58
58
  responses?: Record<string, ResponseObject>;
59
+ /** What a custom action declares about its effects (generator contract 15). */
60
+ 'x-safety'?: {
61
+ readOnly?: unknown;
62
+ destructive?: unknown;
63
+ idempotent?: unknown;
64
+ };
65
+ /** The credentials the operation takes: scheme name to the scopes it needs, per alternative. */
66
+ security?: readonly SecurityRequirement[];
67
+ }
68
+ /** One security alternative: each scheme it names, with the scopes it needs under that scheme. */
69
+ type SecurityRequirement = Readonly<Record<string, readonly string[]>>;
70
+ /** A security scheme; the codegen reads only its `type`, to find the `oauth2` ones. */
71
+ interface SecuritySchemeObject {
72
+ type?: string;
59
73
  }
60
74
  type HttpMethod = 'get' | 'post' | 'patch' | 'put' | 'delete';
61
75
  type PathItemObject = Partial<Record<HttpMethod, OperationObject>>;
@@ -74,8 +88,11 @@ interface OpenApiDocument {
74
88
  url?: string;
75
89
  }[];
76
90
  paths?: Record<string, PathItemObject>;
91
+ /** The default security of every operation that declares none of its own. */
92
+ security?: readonly SecurityRequirement[];
77
93
  components?: {
78
94
  schemas?: Record<string, SchemaObject>;
95
+ securitySchemes?: Record<string, SecuritySchemeObject>;
79
96
  };
80
97
  }
81
98
  //#endregion
@@ -85,7 +102,22 @@ declare class DescriptorBuilder {
85
102
  private readonly paths;
86
103
  /** Lazily-built wire-type -> collection-level paginator kind (for per-relation divergence, D6a). */
87
104
  private typePaginators?;
105
+ /** The names of the document's `oauth2` security schemes, under which operations list scopes. */
106
+ private readonly scopeSchemes;
107
+ /** The document's default security, which an operation without its own inherits. */
108
+ private readonly defaultSecurity;
88
109
  constructor(source: OpenApiDocument);
110
+ /**
111
+ * The OAuth scopes an operation needs (generator contract 16): those its `security` lists under
112
+ * an `oauth2` scheme, in order and without repeats. `undefined` when it lists none.
113
+ */
114
+ private scopes;
115
+ /**
116
+ * The scopes each of a type's CRUD, related and relationship operations needs, keyed by the
117
+ * operationId without the type (`fetchOne`, `fetchRelated.author`). Operations needing none are
118
+ * left out; actions carry their own.
119
+ */
120
+ private operationScopes;
89
121
  /**
90
122
  * The collection-level paginator kind for a WIRE type (`tracks`), memoised. Resolves each
91
123
  * resource schema's wire type (its `type.const`) to the paginator of its collection GET — the
@@ -152,9 +184,12 @@ declare class DescriptorBuilder {
152
184
  * `{collection}/{id}/-actions/{name}` paths. The action name is the last segment; scope
153
185
  * is `resource` when the path carries `/{id}/`, else `collection`. `input` is `document`
154
186
  * (a JSON:API requestBody), `none` (no requestBody) or `raw` (a non-JSON:API body);
155
- * `output` is `document` (a 2xx returns a JSON:API document) or `none` (only `204`).
187
+ * `output` is `document` (a 2xx returns a JSON:API document) or `none` (only `204`). An action
188
+ * answering to several methods gives one entry per method (see {@link pickOperations}).
156
189
  */
157
190
  private actions;
191
+ /** The descriptor for one operation of a custom action. */
192
+ private action;
158
193
  /**
159
194
  * The non-JSON:API media types an action answers with under its 2xx responses, each once, in
160
195
  * the document's order, with how the client reads its body.
@@ -295,7 +330,7 @@ declare function buildExamples(doc: OpenApiDocument, descriptor: ApiDescriptor):
295
330
  /** Below this the document predates the structures this codegen reads. */
296
331
  declare const MINIMUM_CONTRACT = 1;
297
332
  /** Above this the document carries structures this codegen does not read. */
298
- declare const MAXIMUM_CONTRACT = 14;
333
+ declare const MAXIMUM_CONTRACT = 16;
299
334
  /**
300
335
  * Check the document's `info.x-generator.contract` against the range this codegen reads. A
301
336
  * document older than the range throws; a newer one returns a warning, because its new
@@ -682,5 +717,5 @@ declare function generateFiles(config: CodegenConfig): Promise<string[]>;
682
717
  */
683
718
  declare function check(config: CodegenConfig): Promise<CheckResult>;
684
719
  //#endregion
685
- export { operationExamples as A, PathItemObject as B, VerbCollision as C, MINIMUM_CONTRACT as D, MAXIMUM_CONTRACT as E, InfoObject as F, ResponseObject as H, MediaTypeObject as I, 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 };
686
- //# sourceMappingURL=index-k_oATv7Q.d.mts.map
720
+ 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-BLBSnK8y.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BLBSnK8y.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, H as ResponseObject, I as MediaTypeObject, 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-k_oATv7Q.mjs";
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 VerbCollision, buildAtomic, buildDescriptor, buildExamples, check, checkContract, deriveProvenance, detectVerbCollisions, emit, emitErrorCodes, emitExamples, emitSchemas, examplesOutputPath, generate, generateFiles, hashJson, operationExamples, provenanceLines, readDocument, readErrorCatalogue, readSchemas, schemasOutputPath };
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-BLBSnK8y.mjs";
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-py1RBWZr.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-D29xU79S.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 };
@@ -95,10 +95,49 @@ var DescriptorBuilder = class {
95
95
  paths;
96
96
  /** Lazily-built wire-type -> collection-level paginator kind (for per-relation divergence, D6a). */
97
97
  typePaginators;
98
+ /** The names of the document's `oauth2` security schemes, under which operations list scopes. */
99
+ scopeSchemes;
100
+ /** The document's default security, which an operation without its own inherits. */
101
+ defaultSecurity;
98
102
  constructor(source) {
99
103
  const doc = unwrapRefs(source);
100
104
  this.schemas = doc.components?.schemas ?? {};
101
105
  this.paths = doc.paths ?? {};
106
+ this.scopeSchemes = new Set(Object.entries(doc.components?.securitySchemes ?? {}).filter(([, scheme]) => scheme?.type === "oauth2").map(([name]) => name));
107
+ this.defaultSecurity = doc.security ?? [];
108
+ }
109
+ /**
110
+ * The OAuth scopes an operation needs (generator contract 16): those its `security` lists under
111
+ * an `oauth2` scheme, in order and without repeats. `undefined` when it lists none.
112
+ */
113
+ scopes(op) {
114
+ if (op === void 0 || this.scopeSchemes.size === 0) return;
115
+ const out = /* @__PURE__ */ new Set();
116
+ for (const requirement of op.security ?? this.defaultSecurity) for (const [scheme, scopes] of Object.entries(requirement)) if (this.scopeSchemes.has(scheme) && Array.isArray(scopes)) for (const scope of scopes) out.add(scope);
117
+ return out.size === 0 ? void 0 : [...out];
118
+ }
119
+ /**
120
+ * The scopes each of a type's CRUD, related and relationship operations needs, keyed by the
121
+ * operationId without the type (`fetchOne`, `fetchRelated.author`). Operations needing none are
122
+ * left out; actions carry their own.
123
+ */
124
+ operationScopes(wireType) {
125
+ const out = {};
126
+ for (const item of Object.values(this.paths)) for (const op of Object.values(item)) {
127
+ const id = op?.operationId;
128
+ if (typeof id !== "string") continue;
129
+ const dot = id.indexOf(".");
130
+ const prefix = id.slice(0, dot);
131
+ const rest = id.slice(dot + 1);
132
+ if (dot < 0 || prefix === "action" || prefix === "atomic") continue;
133
+ let key;
134
+ if (rest === wireType) key = prefix;
135
+ else if (rest.startsWith(`${wireType}.`)) key = `${prefix}.${rest.slice(wireType.length + 1)}`;
136
+ else continue;
137
+ const scopes = this.scopes(op);
138
+ if (scopes !== void 0) out[key] = scopes;
139
+ }
140
+ return sortRecord(out);
102
141
  }
103
142
  /**
104
143
  * The collection-level paginator kind for a WIRE type (`tracks`), memoised. Resolves each
@@ -139,6 +178,8 @@ var DescriptorBuilder = class {
139
178
  const filterable = this.filterable(collection);
140
179
  if (filterable !== void 0) descriptor.filterable = filterable;
141
180
  if (Object.keys(actions).length > 0) descriptor.actions = actions;
181
+ const scopes = this.operationScopes(wireType);
182
+ if (Object.keys(scopes).length > 0) descriptor.scopes = scopes;
142
183
  if (Object.keys(schema.properties ?? {}).every((prop) => prop === "type") && Object.keys(descriptor.paths).length === 0) continue;
143
184
  out[wireType] = descriptor;
144
185
  }
@@ -152,7 +193,13 @@ var DescriptorBuilder = class {
152
193
  buildAtomic() {
153
194
  for (const [path, item] of Object.entries(this.paths)) {
154
195
  const content = item.post?.requestBody?.content ?? {};
155
- if (Object.keys(content).some(isAtomicMediaType)) return { path };
196
+ if (Object.keys(content).some(isAtomicMediaType)) {
197
+ const scopes = this.scopes(item.post);
198
+ return scopes === void 0 ? { path } : {
199
+ path,
200
+ scopes
201
+ };
202
+ }
156
203
  }
157
204
  return null;
158
205
  }
@@ -327,7 +374,8 @@ var DescriptorBuilder = class {
327
374
  * `{collection}/{id}/-actions/{name}` paths. The action name is the last segment; scope
328
375
  * is `resource` when the path carries `/{id}/`, else `collection`. `input` is `document`
329
376
  * (a JSON:API requestBody), `none` (no requestBody) or `raw` (a non-JSON:API body);
330
- * `output` is `document` (a 2xx returns a JSON:API document) or `none` (only `204`).
377
+ * `output` is `document` (a 2xx returns a JSON:API document) or `none` (only `204`). An action
378
+ * answering to several methods gives one entry per method (see {@link pickOperations}).
331
379
  */
332
380
  actions(collection) {
333
381
  const out = {};
@@ -344,47 +392,68 @@ var DescriptorBuilder = class {
344
392
  name = path.slice(collectionPrefix.length);
345
393
  } else continue;
346
394
  if (name.length === 0 || name.includes("/")) continue;
347
- const picked = pickOperation(item);
348
- if (picked === void 0) {
395
+ const picked = pickOperations(item);
396
+ if (picked.length === 0) {
349
397
  console.warn(`json-api codegen: custom action path "${path}" advertises no usable operation; skipping.`);
350
398
  continue;
351
399
  }
352
- const { method, op } = picked;
353
- const input = actionInput(op);
354
- const output = actionOutput(op);
355
- const action = {
356
- scope,
357
- path,
358
- input,
359
- output
360
- };
361
- if (method !== "post") action.method = method.toUpperCase();
362
- if (input === "document") {
363
- const inputType = this.documentPrimary(actionRequestRef(op))?.type;
364
- if (inputType !== void 0) action.inputType = inputType;
365
- }
366
- if (output === "document") {
367
- const primary = this.documentPrimary(this.firstOkJsonApiRef(op));
368
- if (primary !== void 0) {
369
- action.outputType = primary.type;
370
- action.outputCardinality = primary.cardinality;
400
+ for (const [index, { method, op }] of picked.entries()) {
401
+ const key = index === 0 ? name : methodActionName(name, method);
402
+ if (key in out) {
403
+ console.warn(`json-api codegen: the ${method.toUpperCase()} operation of custom action path "${path}" would be named "${key}", which another action already uses; skipping.`);
404
+ continue;
371
405
  }
406
+ out[key] = this.action(scope, path, method, op);
372
407
  }
373
- if (input === "raw") {
374
- const contentType = rawContentType(op);
375
- if (contentType !== void 0) action.contentType = contentType;
376
- }
377
- const rawOutput = this.rawOutput(op);
378
- if (rawOutput.length > 0) action.rawOutput = rawOutput;
379
- const requestContent = Object.entries(op.requestBody?.content ?? {});
380
- if (requestContent.length > 1) action.inputMedia = requestContent.map(([mediaType, media]) => ({
381
- mediaType,
382
- kind: this.inputMediaKind(mediaType, media.schema)
383
- }));
384
- out[name] = action;
385
408
  }
386
409
  return sortRecord(out);
387
410
  }
411
+ /** The descriptor for one operation of a custom action. */
412
+ action(scope, path, method, op) {
413
+ const input = actionInput(op);
414
+ const output = actionOutput(op);
415
+ const action = {
416
+ scope,
417
+ path,
418
+ input,
419
+ output
420
+ };
421
+ if (method !== "post") action.method = method.toUpperCase();
422
+ if (input === "document") {
423
+ const inputType = this.documentPrimary(actionRequestRef(op))?.type;
424
+ if (inputType !== void 0) action.inputType = inputType;
425
+ }
426
+ if (output === "document") {
427
+ const primary = this.documentPrimary(this.firstOkJsonApiRef(op));
428
+ if (primary !== void 0) {
429
+ action.outputType = primary.type;
430
+ action.outputCardinality = primary.cardinality;
431
+ }
432
+ }
433
+ if (input === "raw") {
434
+ const contentType = rawContentType(op);
435
+ if (contentType !== void 0) action.contentType = contentType;
436
+ }
437
+ const rawOutput = this.rawOutput(op);
438
+ if (rawOutput.length > 0) action.rawOutput = rawOutput;
439
+ const requestContent = Object.entries(op.requestBody?.content ?? {});
440
+ if (requestContent.length > 1) action.inputMedia = requestContent.map(([mediaType, media]) => ({
441
+ mediaType,
442
+ kind: this.inputMediaKind(mediaType, media.schema)
443
+ }));
444
+ const safety = op["x-safety"];
445
+ for (const flag of [
446
+ "readOnly",
447
+ "destructive",
448
+ "idempotent"
449
+ ]) {
450
+ const value = safety?.[flag];
451
+ if (typeof value === "boolean") action[flag] = value;
452
+ }
453
+ const scopes = this.scopes(op);
454
+ if (scopes !== void 0) action.scopes = scopes;
455
+ return action;
456
+ }
388
457
  /**
389
458
  * The non-JSON:API media types an action answers with under its 2xx responses, each once, in
390
459
  * the document's order, with how the client reads its body.
@@ -495,16 +564,21 @@ var DescriptorBuilder = class {
495
564
  if (readPivot !== void 0) relation.readPivot = readPivot;
496
565
  return relation;
497
566
  }
498
- if (Array.isArray(items.anyOf)) return {
499
- cardinality: "many",
500
- types: this.constsFromRefs(items.anyOf),
501
- pivot: false
502
- };
567
+ if (Array.isArray(items.anyOf)) {
568
+ const types = this.constsFromRefs(items.anyOf);
569
+ return types.length === 0 ? void 0 : {
570
+ cardinality: "many",
571
+ types,
572
+ polymorphic: true,
573
+ pivot: false
574
+ };
575
+ }
503
576
  return;
504
577
  }
505
578
  if (Array.isArray(data.anyOf)) {
506
579
  const types = [];
507
580
  let pivot = false;
581
+ let polymorphic = false;
508
582
  let readPivot;
509
583
  for (const entry of data.anyOf) if (typeof entry.$ref === "string") {
510
584
  const type = this.resolveConst(entry.$ref);
@@ -516,9 +590,17 @@ var DescriptorBuilder = class {
516
590
  pivot = true;
517
591
  readPivot = this.pivotDescriptor(pivotNodeFromAllOf(entry.allOf), "read");
518
592
  }
519
- } else if (Array.isArray(entry.anyOf)) types.push(...this.constsFromRefs(entry.anyOf));
593
+ } else if (Array.isArray(entry.anyOf)) {
594
+ types.push(...this.constsFromRefs(entry.anyOf));
595
+ polymorphic = true;
596
+ }
520
597
  if (types.length === 0) return;
521
- const relation = {
598
+ const relation = polymorphic ? {
599
+ cardinality: "one",
600
+ types,
601
+ polymorphic: true,
602
+ pivot
603
+ } : {
522
604
  cardinality: "one",
523
605
  types,
524
606
  pivot
@@ -837,18 +919,39 @@ const ACTION_METHODS = [
837
919
  "delete"
838
920
  ];
839
921
  /**
840
- * The single operation an action is invoked over: POST when advertised (the common default), else
841
- * the first advertised method. `undefined` when the path item carries no operation at all.
922
+ * The operations an action is invoked over, the preferred one first: POST when advertised (the
923
+ * common default), else the first advertised method. The other methods follow only when their
924
+ * `operationId` differs from the preferred one's, as contract 15 makes it for an action answering
925
+ * to several methods. An older document gave every method one id, and its client exposed only the
926
+ * preferred one. Empty when the path item carries no operation at all.
842
927
  */
843
- function pickOperation(item) {
928
+ function pickOperations(item) {
844
929
  const available = ACTION_METHODS.filter((method) => item[method] !== void 0);
845
- const method = available.includes("post") ? "post" : available[0];
846
- if (method === void 0) return;
847
- const op = item[method];
848
- return op === void 0 ? void 0 : {
849
- method,
850
- op
851
- };
930
+ const preferred = available.includes("post") ? "post" : available[0];
931
+ if (preferred === void 0) return [];
932
+ const first = item[preferred];
933
+ const out = [{
934
+ method: preferred,
935
+ op: first
936
+ }];
937
+ for (const method of available) {
938
+ const op = item[method];
939
+ if (method !== preferred && op.operationId !== first.operationId) out.push({
940
+ method,
941
+ op
942
+ });
943
+ }
944
+ return out;
945
+ }
946
+ /**
947
+ * The name a multi-method action's other methods go under: the action's name and the method,
948
+ * joined the way the name joins its words. So `export` gives `exportGet` and `bulk-import` gives
949
+ * `bulk-import-get`. Core refuses action ids that differ only in case or punctuation, so the
950
+ * name can't clash with another action's.
951
+ */
952
+ function methodActionName(name, method) {
953
+ const separator = /[-_.]/.exec(name)?.[0];
954
+ return separator === void 0 ? `${name}${method[0].toUpperCase()}${method.slice(1)}` : `${name}${separator}${method}`;
852
955
  }
853
956
  /** The body shape a custom action accepts: a JSON:API document, none, or a raw payload. */
854
957
  function actionInput(op) {
@@ -1044,7 +1147,7 @@ function buildExamples(doc, descriptor) {
1044
1147
  /** Below this the document predates the structures this codegen reads. */
1045
1148
  const MINIMUM_CONTRACT = 1;
1046
1149
  /** Above this the document carries structures this codegen does not read. */
1047
- const MAXIMUM_CONTRACT = 14;
1150
+ const MAXIMUM_CONTRACT = 16;
1048
1151
  /**
1049
1152
  * Check the document's `info.x-generator.contract` against the range this codegen reads. A
1050
1153
  * document older than the range throws; a newer one returns a warning, because its new
@@ -1054,8 +1157,8 @@ const MAXIMUM_CONTRACT = 14;
1054
1157
  function checkContract(doc) {
1055
1158
  const contract = doc.info?.["x-generator"]?.contract;
1056
1159
  if (typeof contract !== "number") return;
1057
- if (contract < 1) throw new Error(`the document declares contract ${contract}, which predates what this codegen reads (it supports 1 to 14). Use an older codegen release, or regenerate the document from a newer server.`);
1058
- if (contract > 14) return `the document declares contract ${contract} and this codegen reads up to 14. It describes capabilities that were not generated; upgrade the codegen to pick them up.`;
1160
+ if (contract < 1) throw new Error(`the document declares contract ${contract}, which predates what this codegen reads (it supports 1 to 16). Use an older codegen release, or regenerate the document from a newer server.`);
1161
+ if (contract > 16) return `the document declares contract ${contract} and this codegen reads up to 16. It describes capabilities that were not generated; upgrade the codegen to pick them up.`;
1059
1162
  }
1060
1163
  //#endregion
1061
1164
  //#region src/declared-links.ts
@@ -1521,6 +1624,57 @@ function actionDoc(name, op) {
1521
1624
  if (description && !generated.test(description)) parts.push(description);
1522
1625
  return parts.length === 0 ? void 0 : parts.join("\n\n");
1523
1626
  }
1627
+ /**
1628
+ * What an action declares about its effects (generator contract 15), as one sentence, or
1629
+ * `undefined` when it declares nothing.
1630
+ */
1631
+ function safetyDoc(action) {
1632
+ const words = [
1633
+ [action.readOnly, "read-only"],
1634
+ [action.destructive, "destructive"],
1635
+ [action.idempotent, "idempotent"]
1636
+ ].filter(([value]) => value !== void 0).map(([value, word]) => value === true ? word : `not ${word}`);
1637
+ if (words.length === 0) return;
1638
+ return `The server declares this action ${words.length === 1 ? words[0] : `${words.slice(0, -1).join(", ")} and ${words.at(-1)}`}.`;
1639
+ }
1640
+ /** A list of scopes as inline code joined into prose: `a`, `b` and `c`. */
1641
+ function scopeList(scopes) {
1642
+ const quoted = scopes.map((scope) => `\`${scope}\``);
1643
+ return quoted.length === 1 ? quoted[0] : `${quoted.slice(0, -1).join(", ")} and ${quoted.at(-1)}`;
1644
+ }
1645
+ /** The sentence naming the OAuth scopes an operation needs (generator contract 16). */
1646
+ function scopeDoc(scopes) {
1647
+ if (scopes === void 0 || scopes.length === 0) return;
1648
+ return `Needs the OAuth ${scopes.length === 1 ? "scope" : "scopes"} ${scopeList(scopes)}.`;
1649
+ }
1650
+ /** The descriptor's `paths` keys, by the operation whose scopes they carry. */
1651
+ const PATH_OPERATIONS = {
1652
+ fetchMany: "fetchCollection",
1653
+ create: "create",
1654
+ fetchOne: "fetchOne",
1655
+ update: "update",
1656
+ delete: "delete"
1657
+ };
1658
+ /** A relation's operations, in the order its JSDoc lists their scopes. */
1659
+ const RELATION_OPERATIONS = [
1660
+ "fetchRelated",
1661
+ "fetchRelationship",
1662
+ "updateRelationship",
1663
+ "addRelationship",
1664
+ "removeRelationship"
1665
+ ];
1666
+ /** The OAuth scopes each of a relation's endpoints needs, one line per operation, or `undefined`. */
1667
+ function relationScopeDoc(scopes, relation) {
1668
+ const lines = RELATION_OPERATIONS.flatMap((operation) => {
1669
+ const needed = scopes?.[`${operation}.${relation}`];
1670
+ return needed === void 0 || needed.length === 0 ? [] : [`- \`${operation}\` needs ${scopeList(needed)}`];
1671
+ });
1672
+ return lines.length === 0 ? void 0 : [
1673
+ "OAuth scopes:",
1674
+ "",
1675
+ ...lines
1676
+ ].join("\n");
1677
+ }
1524
1678
  /** Whether a media type carries JSON: JSON:API, `application/json` or a `+json` suffix. */
1525
1679
  function isJsonMedia(mediaType) {
1526
1680
  const essence = mediaType.split(";")[0].trim().toLowerCase();
@@ -1630,10 +1784,24 @@ var Emitter = class {
1630
1784
  const single = action.contentType;
1631
1785
  if (single !== void 0 && (single.startsWith("multipart/") || single === "application/x-www-form-urlencoded")) this.takenNames.add(`${stem}${pascalCase(name)}${pascalCase(single)}Input`);
1632
1786
  const op = actionOperation(this.doc, action);
1633
- const parts = [actionDoc(name, op), exampleDoc(op)].filter((part) => part !== void 0);
1787
+ const parts = [
1788
+ actionDoc(action.path.slice(action.path.lastIndexOf("/") + 1), op),
1789
+ safetyDoc(action),
1790
+ scopeDoc(action.scopes),
1791
+ exampleDoc(op)
1792
+ ].filter((part) => part !== void 0);
1634
1793
  const doc = parts.length === 0 ? void 0 : parts.join("\n\n");
1635
1794
  if (doc !== void 0) this.literalDocs.set(`${type}/actions/${name}`, doc);
1636
1795
  }
1796
+ const scopes = descriptor[type]?.scopes;
1797
+ for (const [key, operation] of Object.entries(PATH_OPERATIONS)) {
1798
+ const doc = scopeDoc(scopes?.[operation]);
1799
+ if (doc !== void 0 && descriptor[type]?.paths[key] !== void 0) this.literalDocs.set(`${type}/paths/${key}`, doc);
1800
+ }
1801
+ for (const relation of Object.keys(descriptor[type]?.relations ?? {})) {
1802
+ const doc = relationScopeDoc(scopes, relation);
1803
+ if (doc !== void 0) this.literalDocs.set(`${type}/relations/${relation}`, doc);
1804
+ }
1637
1805
  }
1638
1806
  }
1639
1807
  /**
@@ -1977,10 +2145,12 @@ var Emitter = class {
1977
2145
  }
1978
2146
  /** Emit the server-level atomic capability constant (`{ path }` or `null`). */
1979
2147
  atomicConst() {
2148
+ const atomicScopes = scopeDoc(this.atomic?.scopes);
1980
2149
  return `${[
1981
2150
  "/**",
1982
2151
  " * The server-level Atomic Operations endpoint (the atomic ext media type), or `null`",
1983
2152
  " * when this server exposes none. The runtime `client.atomic` builder posts the batch here.",
2153
+ ...atomicScopes === void 0 ? [] : [" *", ` * ${atomicScopes}`],
1984
2154
  " */"
1985
2155
  ].join("\n")}\nexport const atomic = ${this.atomic === null ? "null" : `${this.literal(this.atomic, 0)} as const`}`;
1986
2156
  }
@@ -2460,4 +2630,4 @@ async function check(config) {
2460
2630
  //#endregion
2461
2631
  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 };
2462
2632
 
2463
- //# sourceMappingURL=src-py1RBWZr.mjs.map
2633
+ //# sourceMappingURL=src-D29xU79S.mjs.map