@haddowg/json-api-codegen 1.6.1 → 1.7.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-CjNGhJJH.mjs";
1
+ import { r as CodegenConfig } from "./index-CeSU_OuC.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>] [--check]";
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as generate, r as schemasOutputPath, t as check } from "./src-CKsM7dXw.mjs";
2
+ import { n as generate, r as schemasOutputPath, t as check } from "./src-DhZyiWge.mjs";
3
3
  import { realpathSync } from "node:fs";
4
4
  import { fileURLToPath } from "node:url";
5
5
  //#region src/cli.ts
@@ -49,6 +49,8 @@ interface ParameterObject {
49
49
  }
50
50
  interface OperationObject {
51
51
  operationId?: string;
52
+ summary?: string;
53
+ description?: string;
52
54
  parameters?: readonly ParameterObject[];
53
55
  requestBody?: RequestBodyObject;
54
56
  responses?: Record<string, ResponseObject>;
@@ -151,6 +153,21 @@ declare class DescriptorBuilder {
151
153
  * `output` is `document` (a 2xx returns a JSON:API document) or `none` (only `204`).
152
154
  */
153
155
  private actions;
156
+ /**
157
+ * The non-JSON:API media types an action answers with under its 2xx responses, each once, in
158
+ * the document's order, with how the client reads its body.
159
+ */
160
+ private rawOutput;
161
+ /**
162
+ * How the client reads or sends a non-JSON:API body: `binary` for `format: binary` (or no schema
163
+ * on a type that isn't text or JSON), `text` for any other string schema (or no schema on a
164
+ * `text/*` type), `json` for any other schema (or no schema on a JSON type).
165
+ */
166
+ private rawMediaKind;
167
+ /** How the client sends one of an action's several request media types. */
168
+ private inputMediaKind;
169
+ /** A schema with a component `$ref` followed, else the schema as is. */
170
+ private resolveSchema;
154
171
  /**
155
172
  * The wire type (and cardinality) of a document component's primary `data` — a `$ref` to a
156
173
  * resource, an inline object carrying `properties.type.const` (a create-request body), or an
@@ -260,7 +277,7 @@ declare function buildAtomic(doc: OpenApiDocument): AtomicDescriptor | null;
260
277
  /** Below this the document predates the structures this codegen reads. */
261
278
  declare const MINIMUM_CONTRACT = 1;
262
279
  /** Above this the document carries structures this codegen does not read. */
263
- declare const MAXIMUM_CONTRACT = 9;
280
+ declare const MAXIMUM_CONTRACT = 11;
264
281
  /**
265
282
  * Check the document's `info.x-generator.contract` against the range this codegen reads. A
266
283
  * document older than the range throws; a newer one returns a warning, because its new
@@ -307,10 +324,16 @@ declare class Emitter {
307
324
  * type-map only references aliases that exist (a `none` input/output contributes neither).
308
325
  */
309
326
  private readonly actionAliasNames;
327
+ /** Raw body components already emitted as a named alias, by component name. */
328
+ private readonly rawBodyAliases;
329
+ /** JSDoc for entries of the `resourceMap` literal, keyed by their path joined with `/`. */
330
+ private readonly literalDocs;
310
331
  /** Provenance of the source document, stamped into the generated header for drift tracking. */
311
332
  private readonly provenance;
312
333
  private readonly doc;
313
334
  constructor(source: OpenApiDocument, descriptor: ApiDescriptor, atomic: AtomicDescriptor | null);
335
+ /** The operation an action is invoked over: its declared method, else `POST`. */
336
+ private actionOperation;
314
337
  /**
315
338
  * The alias an enum component is emitted under: its own name when that's a usable type name,
316
339
  * else one derived from it (with a warning), kept clear of every other name in the module.
@@ -339,6 +362,26 @@ declare class Emitter {
339
362
  * no alias. Deterministic order: types, then action names (the descriptor is sorted).
340
363
  */
341
364
  private actionTypeAliases;
365
+ /**
366
+ * The alias a JSON-like raw body is typed by. A `$ref` to a component is emitted once under the
367
+ * component's own name when that name is free; anything else gets `fallback`. The alias source
368
+ * is pushed onto `out` the first time.
369
+ */
370
+ private rawBodyAlias;
371
+ /**
372
+ * The typed fields of a multipart or URL-encoded request body whose object schema lists
373
+ * `properties`, which the client encodes into a `FormData` or `URLSearchParams`. `undefined`
374
+ * when the schema lists none, so the body stays a raw `FormData` or `URLSearchParams`.
375
+ */
376
+ private formFieldsType;
377
+ /**
378
+ * One form field's type: `Blob | File` for a binary part of a multipart body, an array of
379
+ * its item type for an array, else the scalar's own type. A field with no usable type takes
380
+ * any scalar a form can carry.
381
+ */
382
+ private formFieldType;
383
+ /** `export type Name = …` for a raw body schema, with its description as JSDoc. */
384
+ private rawBodyDeclaration;
342
385
  /**
343
386
  * Emit the `ActionTypes` interface wiring the emitted per-action aliases onto the typed
344
387
  * action surface — `type -> actionName -> { input?; output? }`, the client's fourth type
@@ -574,4 +617,4 @@ declare function generate(config: CodegenConfig): Promise<string>;
574
617
  declare function check(config: CodegenConfig): Promise<CheckResult>;
575
618
  //#endregion
576
619
  export { InfoObject as A, MAXIMUM_CONTRACT as C, buildAtomic as D, DescriptorBuilder as E, PathItemObject as F, RequestBodyObject as I, ResponseObject as L, OpenApiDocument as M, OperationObject as N, buildDescriptor as O, ParameterObject as P, SchemaObject as R, emit as S, checkContract as T, emitErrorCodes as _, generate as a, VerbCollision as b, deriveProvenance as c, emitSchemas as d, SchemaBundle as f, ErrorCatalogue as g, DocumentedErrorCode as h, check as i, MediaTypeObject as j, HttpMethod as k, hashJson as l, readSchemas as m, CheckResult as n, schemasOutputPath as o, readDocument as p, CodegenConfig as r, Provenance as s, ArtifactDrift as t, provenanceLines as u, readErrorCatalogue as v, MINIMUM_CONTRACT as w, detectVerbCollisions as x, Emitter as y, SchemaOrBool as z };
577
- //# sourceMappingURL=index-CjNGhJJH.d.mts.map
620
+ //# sourceMappingURL=index-CeSU_OuC.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-CeSU_OuC.d.mts","names":[],"sources":["../src/openapi.ts","../src/build-descriptor.ts","../src/contract.ts","../src/emit.ts","../src/error-codes.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 InfoObject, C as MAXIMUM_CONTRACT, D as buildAtomic, E as DescriptorBuilder, F as PathItemObject, I as RequestBodyObject, L as ResponseObject, M as OpenApiDocument, N as OperationObject, O as buildDescriptor, P as ParameterObject, R as SchemaObject, S as emit, T as checkContract, _ as emitErrorCodes, a as generate, b as VerbCollision, c as deriveProvenance, d as emitSchemas, f as SchemaBundle, g as ErrorCatalogue, h as DocumentedErrorCode, i as check, j as MediaTypeObject, k as HttpMethod, l as hashJson, m as readSchemas, n as CheckResult, o as schemasOutputPath, p as readDocument, r as CodegenConfig, s as Provenance, t as ArtifactDrift, u as provenanceLines, v as readErrorCatalogue, w as MINIMUM_CONTRACT, x as detectVerbCollisions, y as Emitter, z as SchemaOrBool } from "./index-CjNGhJJH.mjs";
1
+ import { A as InfoObject, C as MAXIMUM_CONTRACT, D as buildAtomic, E as DescriptorBuilder, F as PathItemObject, I as RequestBodyObject, L as ResponseObject, M as OpenApiDocument, N as OperationObject, O as buildDescriptor, P as ParameterObject, R as SchemaObject, S as emit, T as checkContract, _ as emitErrorCodes, a as generate, b as VerbCollision, c as deriveProvenance, d as emitSchemas, f as SchemaBundle, g as ErrorCatalogue, h as DocumentedErrorCode, i as check, j as MediaTypeObject, k as HttpMethod, l as hashJson, m as readSchemas, n as CheckResult, o as schemasOutputPath, p as readDocument, r as CodegenConfig, s as Provenance, t as ArtifactDrift, u as provenanceLines, v as readErrorCatalogue, w as MINIMUM_CONTRACT, x as detectVerbCollisions, y as Emitter, z as SchemaOrBool } from "./index-CeSU_OuC.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 VerbCollision, buildAtomic, buildDescriptor, check, checkContract, deriveProvenance, detectVerbCollisions, emit, emitErrorCodes, emitSchemas, generate, hashJson, provenanceLines, readDocument, readErrorCatalogue, readSchemas, schemasOutputPath };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as checkContract, a as readSchemas, b as buildDescriptor, c as detectVerbCollisions, d as hashJson, f as provenanceLines, g as MINIMUM_CONTRACT, h as MAXIMUM_CONTRACT, i as readDocument, l as emit, m as readErrorCatalogue, n as generate, o as emitSchemas, p as emitErrorCodes, r as schemasOutputPath, s as Emitter, t as check, u as deriveProvenance, v as DescriptorBuilder, y as buildAtomic } from "./src-CKsM7dXw.mjs";
1
+ import { _ as checkContract, a as readSchemas, b as buildDescriptor, c as detectVerbCollisions, d as hashJson, f as provenanceLines, g as MINIMUM_CONTRACT, h as MAXIMUM_CONTRACT, i as readDocument, l as emit, m as readErrorCatalogue, n as generate, o as emitSchemas, p as emitErrorCodes, r as schemasOutputPath, s as Emitter, t as check, u as deriveProvenance, v as DescriptorBuilder, y as buildAtomic } from "./src-DhZyiWge.mjs";
2
2
  export { DescriptorBuilder, Emitter, MAXIMUM_CONTRACT, MINIMUM_CONTRACT, buildAtomic, buildDescriptor, check, checkContract, deriveProvenance, detectVerbCollisions, emit, emitErrorCodes, emitSchemas, generate, hashJson, provenanceLines, readDocument, readErrorCatalogue, readSchemas, schemasOutputPath };
@@ -374,11 +374,63 @@ var DescriptorBuilder = class {
374
374
  const contentType = rawContentType(op);
375
375
  if (contentType !== void 0) action.contentType = contentType;
376
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
+ }));
377
384
  out[name] = action;
378
385
  }
379
386
  return sortRecord(out);
380
387
  }
381
388
  /**
389
+ * The non-JSON:API media types an action answers with under its 2xx responses, each once, in
390
+ * the document's order, with how the client reads its body.
391
+ */
392
+ rawOutput(op) {
393
+ const out = [];
394
+ for (const [code, response] of Object.entries(op.responses ?? {})) {
395
+ if (!code.startsWith("2")) continue;
396
+ for (const [mediaType, media] of Object.entries(response.content ?? {})) {
397
+ if (isJsonApiMediaType(mediaType) || out.some((entry) => entry.mediaType === mediaType)) continue;
398
+ out.push({
399
+ mediaType,
400
+ kind: this.rawMediaKind(mediaType, media.schema)
401
+ });
402
+ }
403
+ }
404
+ return out;
405
+ }
406
+ /**
407
+ * How the client reads or sends a non-JSON:API body: `binary` for `format: binary` (or no schema
408
+ * on a type that isn't text or JSON), `text` for any other string schema (or no schema on a
409
+ * `text/*` type), `json` for any other schema (or no schema on a JSON type).
410
+ */
411
+ rawMediaKind(mediaType, schema) {
412
+ const resolved = this.resolveSchema(schema);
413
+ if (resolved === void 0) {
414
+ if (mediaType.startsWith("text/")) return "text";
415
+ return isJsonMediaType(mediaType) ? "json" : "binary";
416
+ }
417
+ if (resolved.type === "string") return resolved.format === "binary" ? "binary" : "text";
418
+ return "json";
419
+ }
420
+ /** How the client sends one of an action's several request media types. */
421
+ inputMediaKind(mediaType, schema) {
422
+ if (isJsonApiMediaType(mediaType)) return "document";
423
+ if (mediaType.startsWith("multipart/")) return "multipart";
424
+ if (mediaType === "application/x-www-form-urlencoded") return "form";
425
+ return this.rawMediaKind(mediaType, schema);
426
+ }
427
+ /** A schema with a component `$ref` followed, else the schema as is. */
428
+ resolveSchema(schema) {
429
+ if (schema === void 0 || typeof schema.$ref !== "string") return schema;
430
+ const name = refName$1(schema.$ref);
431
+ return name === void 0 ? schema : this.schemas[name] ?? schema;
432
+ }
433
+ /**
382
434
  * The wire type (and cardinality) of a document component's primary `data` — a `$ref` to a
383
435
  * resource, an inline object carrying `properties.type.const` (a create-request body), or an
384
436
  * array of resource/identifier refs (a collection). `undefined` when the type can't be resolved.
@@ -767,6 +819,15 @@ function paramItemsEnum(op, paramName) {
767
819
  function isAtomicMediaType(mediaType) {
768
820
  return mediaType.startsWith(JSON_API_MEDIA_TYPE$1) && mediaType.includes(ATOMIC_EXT);
769
821
  }
822
+ /** Whether a media type is JSON:API, with or without `ext` / `profile` parameters. */
823
+ function isJsonApiMediaType(mediaType) {
824
+ return mediaType.split(";")[0].trim().toLowerCase() === JSON_API_MEDIA_TYPE$1;
825
+ }
826
+ /** Whether a non-JSON:API media type carries JSON: `application/json` or a `+json` suffix. */
827
+ function isJsonMediaType(mediaType) {
828
+ const essence = mediaType.split(";")[0].trim().toLowerCase();
829
+ return essence === "application/json" || essence.endsWith("+json");
830
+ }
770
831
  /** The HTTP methods an action operation may be advertised under, in resolution preference order. */
771
832
  const ACTION_METHODS = [
772
833
  "post",
@@ -905,7 +966,7 @@ function stampedMenu(schema, stamp, warn) {
905
966
  /** Below this the document predates the structures this codegen reads. */
906
967
  const MINIMUM_CONTRACT = 1;
907
968
  /** Above this the document carries structures this codegen does not read. */
908
- const MAXIMUM_CONTRACT = 9;
969
+ const MAXIMUM_CONTRACT = 11;
909
970
  /**
910
971
  * Check the document's `info.x-generator.contract` against the range this codegen reads. A
911
972
  * document older than the range throws; a newer one returns a warning, because its new
@@ -915,8 +976,8 @@ const MAXIMUM_CONTRACT = 9;
915
976
  function checkContract(doc) {
916
977
  const contract = doc.info?.["x-generator"]?.contract;
917
978
  if (typeof contract !== "number") return;
918
- if (contract < 1) throw new Error(`the document declares contract ${contract}, which predates what this codegen reads (it supports 1 to 9). Use an older codegen release, or regenerate the document from a newer server.`);
919
- if (contract > 9) return `the document declares contract ${contract} and this codegen reads up to 9. It describes capabilities that were not generated; upgrade the codegen to pick them up.`;
979
+ if (contract < 1) throw new Error(`the document declares contract ${contract}, which predates what this codegen reads (it supports 1 to 11). Use an older codegen release, or regenerate the document from a newer server.`);
980
+ if (contract > 11) return `the document declares contract ${contract} and this codegen reads up to 11. It describes capabilities that were not generated; upgrade the codegen to pick them up.`;
920
981
  }
921
982
  //#endregion
922
983
  //#region src/source.ts
@@ -1318,6 +1379,34 @@ function memberDoc(schema) {
1318
1379
  if (tags.length === 0) return description;
1319
1380
  return description ? `${description}\n\n${tags.join("\n")}` : tags.join("\n");
1320
1381
  }
1382
+ /** An `ActionTypes` media map: `{ 'text/csv': Alias; … }`. */
1383
+ function mediaMap(aliases) {
1384
+ return `{ ${[...aliases].map(([mediaType, alias]) => `${stringLiteral(mediaType)}: ${alias}`).join("; ")} }`;
1385
+ }
1386
+ /** The schema of `mediaType` under an operation's first 2xx response that lists it. */
1387
+ function firstSuccessSchema(op, mediaType) {
1388
+ for (const [code, response] of Object.entries(op?.responses ?? {})) {
1389
+ const media = code.startsWith("2") ? response.content?.[mediaType] : void 0;
1390
+ if (media !== void 0) return media.schema;
1391
+ }
1392
+ }
1393
+ /**
1394
+ * An action's summary and description as JSDoc, leaving out the prose the projector writes for an
1395
+ * action whose author gave none ("Invoke the `name` action", "Invokes the `name` custom action on
1396
+ * …"), which says nothing the name doesn't.
1397
+ */
1398
+ function actionDoc(name, op) {
1399
+ const summary = op?.summary?.trim();
1400
+ const description = op?.description?.trim();
1401
+ const parts = [];
1402
+ if (summary && summary !== `Invoke the \`${name}\` action`) parts.push(summary);
1403
+ const generated = new RegExp(`^Invokes the \`${escapeRegExp(name)}\` custom action on [^.]*\\.$`);
1404
+ if (description && !generated.test(description)) parts.push(description);
1405
+ return parts.length === 0 ? void 0 : parts.join("\n\n");
1406
+ }
1407
+ function escapeRegExp(value) {
1408
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1409
+ }
1321
1410
  var Emitter = class {
1322
1411
  descriptor;
1323
1412
  atomic;
@@ -1345,6 +1434,10 @@ var Emitter = class {
1345
1434
  * type-map only references aliases that exist (a `none` input/output contributes neither).
1346
1435
  */
1347
1436
  actionAliasNames = /* @__PURE__ */ new Map();
1437
+ /** Raw body components already emitted as a named alias, by component name. */
1438
+ rawBodyAliases = /* @__PURE__ */ new Map();
1439
+ /** JSDoc for entries of the `resourceMap` literal, keyed by their path joined with `/`. */
1440
+ literalDocs = /* @__PURE__ */ new Map();
1348
1441
  /** Provenance of the source document, stamped into the generated header for drift tracking. */
1349
1442
  provenance;
1350
1443
  doc;
@@ -1363,12 +1456,23 @@ var Emitter = class {
1363
1456
  "CreateAttributes",
1364
1457
  "UpdateAttributes"
1365
1458
  ]) this.takenNames.add(`${stem}${suffix}`);
1366
- for (const action of Object.keys(descriptor[type]?.actions ?? {})) {
1367
- this.takenNames.add(`${stem}${pascalCase(action)}Input`);
1368
- this.takenNames.add(`${stem}${pascalCase(action)}Output`);
1459
+ for (const [name, action] of Object.entries(descriptor[type]?.actions ?? {})) {
1460
+ this.takenNames.add(`${stem}${pascalCase(name)}Input`);
1461
+ this.takenNames.add(`${stem}${pascalCase(name)}Output`);
1462
+ for (const { mediaType } of action.rawOutput ?? []) this.takenNames.add(`${stem}${pascalCase(name)}${pascalCase(mediaType)}Output`);
1463
+ for (const { mediaType } of action.inputMedia ?? []) this.takenNames.add(`${stem}${pascalCase(name)}${pascalCase(mediaType)}Input`);
1464
+ const single = action.contentType;
1465
+ if (single !== void 0 && (single.startsWith("multipart/") || single === "application/x-www-form-urlencoded")) this.takenNames.add(`${stem}${pascalCase(name)}${pascalCase(single)}Input`);
1466
+ const doc = actionDoc(name, this.actionOperation(action));
1467
+ if (doc !== void 0) this.literalDocs.set(`${type}/actions/${name}`, doc);
1369
1468
  }
1370
1469
  }
1371
1470
  }
1471
+ /** The operation an action is invoked over: its declared method, else `POST`. */
1472
+ actionOperation(action) {
1473
+ const method = (action.method ?? "POST").toLowerCase();
1474
+ return this.doc.paths?.[action.path]?.[method];
1475
+ }
1372
1476
  /**
1373
1477
  * The alias an enum component is emitted under: its own name when that's a usable type name,
1374
1478
  * else one derived from it (with a warning), kept clear of every other name in the module.
@@ -1501,7 +1605,35 @@ var Emitter = class {
1501
1605
  out.push(`export type ${member}Output = ${outputType}`);
1502
1606
  names.output = `${member}Output`;
1503
1607
  }
1504
- if (names.input !== void 0 || names.output !== void 0) {
1608
+ const op = this.actionOperation(action);
1609
+ for (const { mediaType, kind } of action.rawOutput ?? []) if (kind === "json") {
1610
+ const schema = firstSuccessSchema(op, mediaType);
1611
+ names.responses ??= /* @__PURE__ */ new Map();
1612
+ names.responses.set(mediaType, this.rawBodyAlias(schema, `${member}${pascalCase(mediaType)}Output`, out));
1613
+ }
1614
+ const singleKind = !(action.input === "raw" && action.inputMedia === void 0) ? void 0 : action.contentType?.startsWith("multipart/") === true ? "multipart" : action.contentType === "application/x-www-form-urlencoded" ? "form" : void 0;
1615
+ const listed = action.inputMedia ?? (singleKind === void 0 || action.contentType === void 0 ? [] : [{
1616
+ mediaType: action.contentType,
1617
+ kind: singleKind
1618
+ }]);
1619
+ for (const { mediaType, kind } of listed) {
1620
+ const schema = op?.requestBody?.content?.[mediaType]?.schema;
1621
+ if (kind === "json") {
1622
+ names.requests ??= /* @__PURE__ */ new Map();
1623
+ names.requests.set(mediaType, this.rawBodyAlias(schema, `${member}${pascalCase(mediaType)}Input`, out));
1624
+ } else if (kind === "multipart" || kind === "form") {
1625
+ const fields = this.formFieldsType(schema, kind);
1626
+ if (fields !== void 0) {
1627
+ const alias = `${member}${pascalCase(mediaType)}Input`;
1628
+ const doc = schema === void 0 ? void 0 : jsDoc(schema.description, "");
1629
+ const declaration = `export type ${alias} = ${fields}`;
1630
+ out.push(doc === void 0 ? declaration : `${doc}\n${declaration}`);
1631
+ names.requests ??= /* @__PURE__ */ new Map();
1632
+ names.requests.set(mediaType, alias);
1633
+ }
1634
+ }
1635
+ }
1636
+ if (names.input !== void 0 || names.output !== void 0 || names.responses !== void 0 || names.requests !== void 0) {
1505
1637
  let byAction = this.actionAliasNames.get(type);
1506
1638
  if (byAction === void 0) {
1507
1639
  byAction = /* @__PURE__ */ new Map();
@@ -1514,6 +1646,83 @@ var Emitter = class {
1514
1646
  return out;
1515
1647
  }
1516
1648
  /**
1649
+ * The alias a JSON-like raw body is typed by. A `$ref` to a component is emitted once under the
1650
+ * component's own name when that name is free; anything else gets `fallback`. The alias source
1651
+ * is pushed onto `out` the first time.
1652
+ */
1653
+ rawBodyAlias(schema, fallback, out) {
1654
+ const target = typeof schema?.$ref === "string" ? refName(schema.$ref) : void 0;
1655
+ const resolved = target === void 0 ? void 0 : this.schemas[target];
1656
+ if (target !== void 0 && resolved !== void 0) {
1657
+ const known = this.rawBodyAliases.get(target);
1658
+ if (known !== void 0) return known;
1659
+ if (isTypeName(target) && !this.takenNames.has(target) && !Array.isArray(resolved.enum)) {
1660
+ this.takenNames.add(target);
1661
+ this.rawBodyAliases.set(target, target);
1662
+ out.push(this.rawBodyDeclaration(target, resolved));
1663
+ this.emittedInterfaces.add(target);
1664
+ return target;
1665
+ }
1666
+ }
1667
+ out.push(this.rawBodyDeclaration(fallback, schema));
1668
+ return fallback;
1669
+ }
1670
+ /**
1671
+ * The typed fields of a multipart or URL-encoded request body whose object schema lists
1672
+ * `properties`, which the client encodes into a `FormData` or `URLSearchParams`. `undefined`
1673
+ * when the schema lists none, so the body stays a raw `FormData` or `URLSearchParams`.
1674
+ */
1675
+ formFieldsType(schema, kind) {
1676
+ let target = schema;
1677
+ if (typeof target?.$ref === "string") target = this.schemas[refName(target.$ref) ?? ""];
1678
+ const props = target?.properties;
1679
+ if (target === void 0 || props === void 0) return;
1680
+ const required = new Set(target.required ?? []);
1681
+ const members = [];
1682
+ const valueTypes = [];
1683
+ for (const [name, prop] of Object.entries(props)) {
1684
+ if (!isSchema(prop)) continue;
1685
+ const type = this.formFieldType(prop, kind);
1686
+ valueTypes.push(type);
1687
+ members.push(`${propertyKey(name)}${required.has(name) ? "" : "?"}: ${type}`);
1688
+ }
1689
+ if (members.length === 0) return;
1690
+ const extra = target["additionalProperties"];
1691
+ if (extra === true || isSchema(extra)) {
1692
+ const additional = isSchema(extra) ? this.formFieldType(extra, kind) : this.formFieldType({}, kind);
1693
+ const all = [...new Set([...valueTypes, additional].flatMap((type) => type.split(" | ")))];
1694
+ members.push(`[field: string]: ${all.join(" | ")} | undefined`);
1695
+ }
1696
+ return `{ ${members.join("; ")} }`;
1697
+ }
1698
+ /**
1699
+ * One form field's type: `Blob | File` for a binary part of a multipart body, an array of
1700
+ * its item type for an array, else the scalar's own type. A field with no usable type takes
1701
+ * any scalar a form can carry.
1702
+ */
1703
+ formFieldType(schema, kind) {
1704
+ const any = kind === "multipart" ? "string | number | boolean | Blob" : "string | number | boolean";
1705
+ let target = schema;
1706
+ if (typeof target.$ref === "string") {
1707
+ const resolved = this.schemas[refName(target.$ref) ?? ""];
1708
+ if (resolved !== void 0 && !Array.isArray(resolved.enum)) target = resolved;
1709
+ }
1710
+ if (kind === "multipart" && target.format === "binary") return "Blob | File";
1711
+ if (target.type === "array") {
1712
+ const item = target.items === void 0 ? any : this.formFieldType(target.items, kind);
1713
+ return item.includes(" ") ? `(${item})[]` : `${item}[]`;
1714
+ }
1715
+ const type = this.tsType(target);
1716
+ return type === "unknown" || type.includes("{") || type.startsWith("Record<") ? any : type;
1717
+ }
1718
+ /** `export type Name = …` for a raw body schema, with its description as JSDoc. */
1719
+ rawBodyDeclaration(name, schema) {
1720
+ const type = schema === void 0 ? "unknown" : this.tsType(schema);
1721
+ const doc = schema === void 0 ? void 0 : jsDoc(schema.description, "");
1722
+ const declaration = `export type ${name} = ${type}`;
1723
+ return doc === void 0 ? declaration : `${doc}\n${declaration}`;
1724
+ }
1725
+ /**
1517
1726
  * Emit the `ActionTypes` interface wiring the emitted per-action aliases onto the typed
1518
1727
  * action surface — `type -> actionName -> { input?; output? }`, the client's fourth type
1519
1728
  * argument. So `client.albums.id(id).actions.reissue(body)` takes `AlbumsReissueInput` and
@@ -1531,6 +1740,8 @@ var Emitter = class {
1531
1740
  const members = [];
1532
1741
  if (names.input !== void 0) members.push(`input: ${names.input}`);
1533
1742
  if (names.output !== void 0) members.push(`output: ${names.output}`);
1743
+ if (names.responses !== void 0) members.push(`responses: ${mediaMap(names.responses)}`);
1744
+ if (names.requests !== void 0) members.push(`requests: ${mediaMap(names.requests)}`);
1534
1745
  actionLines.push(` ${propertyKey(actionName)}: { ${members.join("; ")} }`);
1535
1746
  }
1536
1747
  typeLines.push(` ${propertyKey(type)}: {\n${actionLines.join("\n")}\n }`);
@@ -1756,10 +1967,10 @@ var Emitter = class {
1756
1967
  }
1757
1968
  /** Emit the descriptor as a literal `as const satisfies ApiDescriptor`. */
1758
1969
  resourceMap() {
1759
- return `export const resourceMap = ${this.literal(this.descriptor, 0)} as const satisfies ApiDescriptor`;
1970
+ return `export const resourceMap = ${this.literal(this.descriptor, 0, "")} as const satisfies ApiDescriptor`;
1760
1971
  }
1761
1972
  /** Render a JSON-serialisable descriptor value as deterministic TS object-literal source. */
1762
- literal(value, depth) {
1973
+ literal(value, depth, path) {
1763
1974
  const indent = " ".repeat(depth);
1764
1975
  const inner = " ".repeat(depth + 1);
1765
1976
  if (Array.isArray(value)) {
@@ -1769,7 +1980,12 @@ var Emitter = class {
1769
1980
  if (value !== null && typeof value === "object") {
1770
1981
  const entries = Object.entries(value);
1771
1982
  if (entries.length === 0) return "{}";
1772
- return `{\n${entries.map(([key, v]) => `${inner}${propertyKey(key)}: ${this.literal(v, depth + 1)}`).join(",\n")}\n${indent}}`;
1983
+ return `{\n${entries.map(([key, v]) => {
1984
+ const childPath = path === void 0 ? void 0 : path === "" ? key : `${path}/${key}`;
1985
+ const line = `${inner}${propertyKey(key)}: ${this.literal(v, depth + 1, childPath)}`;
1986
+ const doc = childPath === void 0 ? void 0 : this.literalDocs.get(childPath);
1987
+ return doc === void 0 ? line : `${jsDoc(doc, inner)}\n${line}`;
1988
+ }).join(",\n")}\n${indent}}`;
1773
1989
  }
1774
1990
  return typeof value === "string" ? stringLiteral(value) : JSON.stringify(value);
1775
1991
  }
@@ -1974,4 +2190,4 @@ async function check(config) {
1974
2190
  //#endregion
1975
2191
  export { checkContract as _, readSchemas as a, buildDescriptor as b, detectVerbCollisions as c, hashJson as d, provenanceLines as f, MINIMUM_CONTRACT as g, MAXIMUM_CONTRACT as h, readDocument as i, emit as l, readErrorCatalogue as m, generate as n, emitSchemas as o, emitErrorCodes as p, schemasOutputPath as r, Emitter as s, check as t, deriveProvenance as u, DescriptorBuilder as v, buildAtomic as y };
1976
2192
 
1977
- //# sourceMappingURL=src-CKsM7dXw.mjs.map
2193
+ //# sourceMappingURL=src-DhZyiWge.mjs.map