openapi-typesafe 1.0.0-rc.1 → 1.0.0-rc.2

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.
Files changed (203) hide show
  1. package/README.md +12 -5
  2. package/dist/adapters/redocly-loader/configured-headers.cjs +32 -0
  3. package/dist/adapters/redocly-loader/configured-headers.cjs.map +1 -0
  4. package/dist/adapters/redocly-loader/configured-headers.mjs +30 -0
  5. package/dist/adapters/redocly-loader/configured-headers.mjs.map +1 -0
  6. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs +19 -6
  7. package/dist/adapters/redocly-loader/redocly-schema-loader.cjs.map +1 -1
  8. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs +18 -5
  9. package/dist/adapters/redocly-loader/redocly-schema-loader.mjs.map +1 -1
  10. package/dist/adapters/redocly-loader/ref-access.cjs +37 -4
  11. package/dist/adapters/redocly-loader/ref-access.cjs.map +1 -1
  12. package/dist/adapters/redocly-loader/ref-access.mjs +37 -4
  13. package/dist/adapters/redocly-loader/ref-access.mjs.map +1 -1
  14. package/dist/adapters/redocly-loader/ref-resolution-check.cjs +191 -0
  15. package/dist/adapters/redocly-loader/ref-resolution-check.cjs.map +1 -0
  16. package/dist/adapters/redocly-loader/ref-resolution-check.mjs +189 -0
  17. package/dist/adapters/redocly-loader/ref-resolution-check.mjs.map +1 -0
  18. package/dist/adapters/redocly-loader/ref-resolution-steps.cjs +73 -0
  19. package/dist/adapters/redocly-loader/ref-resolution-steps.cjs.map +1 -0
  20. package/dist/adapters/redocly-loader/ref-resolution-steps.mjs +60 -0
  21. package/dist/adapters/redocly-loader/ref-resolution-steps.mjs.map +1 -0
  22. package/dist/adapters/redocly-loader/remote-fetch.cjs +41 -14
  23. package/dist/adapters/redocly-loader/remote-fetch.cjs.map +1 -1
  24. package/dist/adapters/redocly-loader/remote-fetch.mjs +40 -14
  25. package/dist/adapters/redocly-loader/remote-fetch.mjs.map +1 -1
  26. package/dist/adapters/redocly-loader/schema-load-errors.cjs +23 -5
  27. package/dist/adapters/redocly-loader/schema-load-errors.cjs.map +1 -1
  28. package/dist/adapters/redocly-loader/schema-load-errors.mjs +23 -5
  29. package/dist/adapters/redocly-loader/schema-load-errors.mjs.map +1 -1
  30. package/dist/adapters/redocly-loader/unsupported-keys.cjs +9 -1
  31. package/dist/adapters/redocly-loader/unsupported-keys.cjs.map +1 -1
  32. package/dist/adapters/redocly-loader/unsupported-keys.mjs +9 -1
  33. package/dist/adapters/redocly-loader/unsupported-keys.mjs.map +1 -1
  34. package/dist/adapters/terminal-logger/terminal-logger.cjs +6 -4
  35. package/dist/adapters/terminal-logger/terminal-logger.cjs.map +1 -1
  36. package/dist/adapters/terminal-logger/terminal-logger.mjs +6 -4
  37. package/dist/adapters/terminal-logger/terminal-logger.mjs.map +1 -1
  38. package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs +2 -1
  39. package/dist/adapters/type-ast-bridge/ir-to-typescript.cjs.map +1 -1
  40. package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs +2 -1
  41. package/dist/adapters/type-ast-bridge/ir-to-typescript.mjs.map +1 -1
  42. package/dist/adapters/type-ast-bridge/print-typescript.cjs.map +1 -1
  43. package/dist/adapters/type-ast-bridge/print-typescript.mjs.map +1 -1
  44. package/dist/api/default-ports.cjs +9 -0
  45. package/dist/api/default-ports.cjs.map +1 -1
  46. package/dist/api/default-ports.mjs +8 -1
  47. package/dist/api/default-ports.mjs.map +1 -1
  48. package/dist/api/discriminator-patch.cjs +5 -1
  49. package/dist/api/discriminator-patch.cjs.map +1 -1
  50. package/dist/api/discriminator-patch.mjs +5 -1
  51. package/dist/api/discriminator-patch.mjs.map +1 -1
  52. package/dist/api/document-refs.cjs +15 -12
  53. package/dist/api/document-refs.cjs.map +1 -1
  54. package/dist/api/document-refs.mjs +15 -12
  55. package/dist/api/document-refs.mjs.map +1 -1
  56. package/dist/api/document-run.cjs +4 -2
  57. package/dist/api/document-run.cjs.map +1 -1
  58. package/dist/api/document-run.mjs +4 -2
  59. package/dist/api/document-run.mjs.map +1 -1
  60. package/dist/api/footer-sink.cjs +25 -2
  61. package/dist/api/footer-sink.cjs.map +1 -1
  62. package/dist/api/footer-sink.mjs +25 -2
  63. package/dist/api/footer-sink.mjs.map +1 -1
  64. package/dist/api/legacy-callback-warning.cjs +4 -1
  65. package/dist/api/legacy-callback-warning.cjs.map +1 -1
  66. package/dist/api/legacy-callback-warning.mjs +4 -1
  67. package/dist/api/legacy-callback-warning.mjs.map +1 -1
  68. package/dist/api/legacy-ts-helpers.cjs.map +1 -1
  69. package/dist/api/legacy-ts-helpers.mjs.map +1 -1
  70. package/dist/api/legacy-utils.cjs.map +1 -1
  71. package/dist/api/legacy-utils.mjs.map +1 -1
  72. package/dist/api/openapi-ts.cjs +2 -1
  73. package/dist/api/openapi-ts.cjs.map +1 -1
  74. package/dist/api/openapi-ts.mjs +3 -2
  75. package/dist/api/openapi-ts.mjs.map +1 -1
  76. package/dist/api/transform-context.cjs +1 -1
  77. package/dist/api/transform-context.cjs.map +1 -1
  78. package/dist/api/transform-context.mjs +1 -1
  79. package/dist/api/transform-context.mjs.map +1 -1
  80. package/dist/api/transform-schema.cjs.map +1 -1
  81. package/dist/api/transform-schema.mjs.map +1 -1
  82. package/dist/application/print/type-printer.cjs +2 -1
  83. package/dist/application/print/type-printer.cjs.map +1 -1
  84. package/dist/application/print/type-printer.mjs +2 -1
  85. package/dist/application/print/type-printer.mjs.map +1 -1
  86. package/dist/application/transform/document/parameters.cjs +1 -1
  87. package/dist/application/transform/document/parameters.cjs.map +1 -1
  88. package/dist/application/transform/document/parameters.mjs +1 -1
  89. package/dist/application/transform/document/parameters.mjs.map +1 -1
  90. package/dist/application/transform/document/paths.cjs +1 -1
  91. package/dist/application/transform/document/paths.cjs.map +1 -1
  92. package/dist/application/transform/document/paths.mjs +1 -1
  93. package/dist/application/transform/document/paths.mjs.map +1 -1
  94. package/dist/application/transform/footer/evaluation.cjs +9 -0
  95. package/dist/application/transform/footer/evaluation.cjs.map +1 -1
  96. package/dist/application/transform/footer/evaluation.mjs +8 -1
  97. package/dist/application/transform/footer/evaluation.mjs.map +1 -1
  98. package/dist/application/transform/footer/type-evaluation.cjs +4 -112
  99. package/dist/application/transform/footer/type-evaluation.cjs.map +1 -1
  100. package/dist/application/transform/footer/type-evaluation.mjs +4 -112
  101. package/dist/application/transform/footer/type-evaluation.mjs.map +1 -1
  102. package/dist/application/transform/footer/type-indexing.cjs +107 -0
  103. package/dist/application/transform/footer/type-indexing.cjs.map +1 -0
  104. package/dist/application/transform/footer/type-indexing.mjs +105 -0
  105. package/dist/application/transform/footer/type-indexing.mjs.map +1 -0
  106. package/dist/cli/api-settings.cjs +78 -0
  107. package/dist/cli/api-settings.cjs.map +1 -0
  108. package/dist/cli/api-settings.mjs +73 -0
  109. package/dist/cli/api-settings.mjs.map +1 -0
  110. package/dist/cli/cli-arguments.cjs +1 -0
  111. package/dist/cli/cli-arguments.cjs.map +1 -1
  112. package/dist/cli/cli-arguments.mjs +1 -1
  113. package/dist/cli/cli-arguments.mjs.map +1 -1
  114. package/dist/cli/help-text.cjs +3 -3
  115. package/dist/cli/help-text.cjs.map +1 -1
  116. package/dist/cli/help-text.mjs +3 -3
  117. package/dist/cli/help-text.mjs.map +1 -1
  118. package/dist/cli/redocly-apis.cjs +8 -4
  119. package/dist/cli/redocly-apis.cjs.map +1 -1
  120. package/dist/cli/redocly-apis.mjs +7 -3
  121. package/dist/cli/redocly-apis.mjs.map +1 -1
  122. package/dist/cli/run-cli.cjs +5 -1
  123. package/dist/cli/run-cli.cjs.map +1 -1
  124. package/dist/cli/run-cli.mjs +5 -1
  125. package/dist/cli/run-cli.mjs.map +1 -1
  126. package/dist/domain/errors.cjs +4 -1
  127. package/dist/domain/errors.cjs.map +1 -1
  128. package/dist/domain/errors.mjs +4 -1
  129. package/dist/domain/errors.mjs.map +1 -1
  130. package/dist/domain/ir/constructors.cjs +2 -0
  131. package/dist/domain/ir/constructors.cjs.map +1 -1
  132. package/dist/domain/ir/constructors.mjs +2 -1
  133. package/dist/domain/ir/constructors.mjs.map +1 -1
  134. package/dist/domain/openapi/path-templates.cjs +1 -1
  135. package/dist/domain/openapi/path-templates.cjs.map +1 -1
  136. package/dist/domain/openapi/path-templates.mjs +1 -1
  137. package/dist/domain/openapi/path-templates.mjs.map +1 -1
  138. package/dist/domain/url-credentials.cjs +10 -0
  139. package/dist/domain/url-credentials.cjs.map +1 -0
  140. package/dist/domain/url-credentials.mjs +8 -0
  141. package/dist/domain/url-credentials.mjs.map +1 -0
  142. package/dist/domain/warnings.cjs +9 -1
  143. package/dist/domain/warnings.cjs.map +1 -1
  144. package/dist/domain/warnings.mjs +9 -1
  145. package/dist/domain/warnings.mjs.map +1 -1
  146. package/dist/index.cjs +2 -0
  147. package/dist/index.cjs.map +1 -1
  148. package/dist/index.d.cts +1160 -53
  149. package/dist/index.d.mts +1160 -53
  150. package/dist/index.d.ts +1160 -53
  151. package/dist/index.mjs +1 -0
  152. package/dist/index.mjs.map +1 -1
  153. package/dist/transform/path-item-object.cjs.map +1 -1
  154. package/dist/transform/path-item-object.mjs.map +1 -1
  155. package/package.json +6 -5
  156. package/src/adapters/redocly-loader/configured-headers.ts +65 -0
  157. package/src/adapters/redocly-loader/redocly-schema-loader.ts +22 -7
  158. package/src/adapters/redocly-loader/ref-access.ts +60 -5
  159. package/src/adapters/redocly-loader/ref-resolution-check.ts +257 -0
  160. package/src/adapters/redocly-loader/ref-resolution-steps.ts +113 -0
  161. package/src/adapters/redocly-loader/remote-fetch.ts +77 -21
  162. package/src/adapters/redocly-loader/schema-load-errors.ts +33 -11
  163. package/src/adapters/redocly-loader/unsupported-keys.ts +17 -2
  164. package/src/adapters/terminal-logger/terminal-logger.ts +9 -5
  165. package/src/adapters/type-ast-bridge/ir-to-typescript.ts +2 -1
  166. package/src/adapters/type-ast-bridge/print-typescript.ts +3 -1
  167. package/src/adapters/type-ast-bridge/typescript-types.ts +57 -5
  168. package/src/api/default-ports.ts +16 -0
  169. package/src/api/discriminator-patch.ts +7 -2
  170. package/src/api/document-refs.ts +19 -15
  171. package/src/api/document-run.ts +9 -6
  172. package/src/api/footer-sink.ts +34 -2
  173. package/src/api/legacy-callback-warning.ts +7 -3
  174. package/src/api/legacy-ts-helpers.ts +7 -5
  175. package/src/api/legacy-utils.ts +1 -1
  176. package/src/api/openapi-ts.ts +2 -0
  177. package/src/api/run-context.ts +34 -0
  178. package/src/api/transform-context.ts +10 -8
  179. package/src/api/transform-schema.ts +6 -5
  180. package/src/application/print/type-printer.ts +2 -1
  181. package/src/application/transform/document/parameters.ts +2 -1
  182. package/src/application/transform/document/paths.ts +2 -1
  183. package/src/application/transform/footer/evaluation.ts +10 -0
  184. package/src/application/transform/footer/type-evaluation.ts +6 -133
  185. package/src/application/transform/footer/type-indexing.ts +152 -0
  186. package/src/cli/api-settings.ts +109 -0
  187. package/src/cli/cli-arguments.ts +5 -2
  188. package/src/cli/help-text.ts +4 -4
  189. package/src/cli/redocly-apis.ts +13 -7
  190. package/src/cli/run-cli.ts +5 -1
  191. package/src/domain/errors.ts +8 -2
  192. package/src/domain/ir/constructors.ts +3 -0
  193. package/src/domain/ir/nodes.ts +9 -6
  194. package/src/domain/openapi/path-templates.ts +2 -1
  195. package/src/domain/url-credentials.ts +18 -0
  196. package/src/domain/warnings.ts +8 -0
  197. package/src/index.ts +1 -0
  198. package/src/transform/path-item-object.ts +6 -2
  199. package/src/types/generator-options.ts +12 -32
  200. package/src/types/openapi-common.ts +1 -0
  201. package/src/types/openapi-request.ts +3 -0
  202. package/src/types/openapi-schema.ts +2 -0
  203. package/src/types/openapi-security.ts +1 -0
@@ -11,11 +11,14 @@ import {
11
11
  outputFixNamesOf,
12
12
  resolveOutputFixes,
13
13
  } from "../domain/output-fixes.js";
14
- import type { GeneratorOptions, GlobalContext, OpenAPI3, SchemaSource } from "../types.js";
14
+ import { WARNING_CODES, warningText } from "../domain/warnings.js";
15
+ import type { GeneratorOptions, OpenAPI3, SchemaSource } from "../types.js";
16
+ import { processCwd } from "./default-ports.js";
15
17
  import { warnDiscriminatorValueConflicts } from "./discriminator-conflict-warnings.js";
16
18
  import { findDiscriminators } from "./discriminator-scan.js";
17
19
  import { resolveDocumentRef } from "./document-refs.js";
18
20
  import { warnIdenticalPathTemplates } from "./path-template-warnings.js";
21
+ import type { RunContext } from "./run-context.js";
19
22
  import { warnUnsupportedFeatures } from "./unsupported-feature-warnings.js";
20
23
 
21
24
  /** The ports of one generator run, wired by the Node API or the CLI (ENGINEERING_PLAN 4.5). */
@@ -31,12 +34,12 @@ export interface GeneratorPorts {
31
34
  /** A validated, bundled document and the context of one generator run. */
32
35
  export interface DocumentRun {
33
36
  readonly schema: OpenAPI3;
34
- readonly ctx: GlobalContext;
37
+ readonly ctx: RunContext;
35
38
  }
36
39
 
37
40
  /** The context fields that are user flags, with the original defaults. */
38
41
  type ContextFlags = Omit<
39
- GlobalContext,
42
+ RunContext,
40
43
  | "discriminators"
41
44
  | "injectFooter"
42
45
  | "redoc"
@@ -56,7 +59,7 @@ function sourceCwd(options: GeneratorOptions): URL {
56
59
  if (cwd instanceof URL) {
57
60
  return cwd.pathname.endsWith("/") ? cwd : new URL(`${cwd.pathname}/`, cwd);
58
61
  }
59
- const directory = path.resolve(cwd === undefined ? process.cwd() : cwd.toString());
62
+ const directory = path.resolve(cwd === undefined ? processCwd() : cwd.toString());
60
63
  return pathToFileURL(path.join(directory, path.sep));
61
64
  }
62
65
 
@@ -119,7 +122,7 @@ function contextFlags(options: GeneratorOptions): ContextFlags {
119
122
  function outputFixesOf(options: GeneratorOptions, logger: Logger): readonly OutputFix[] {
120
123
  const names = outputFixNamesOf(options.fix ?? []);
121
124
  for (const notice of ineffectiveFixNotices(names, (option) => options[option] === true)) {
122
- logger.warn(notice);
125
+ logger.warn(warningText(notice, WARNING_CODES.FIX_NEEDS_OPTION));
123
126
  }
124
127
  return [...resolveOutputFixes(names)];
125
128
  }
@@ -156,7 +159,7 @@ export async function createDocumentRun(
156
159
  refAccess: refAccessOf(options, cwd),
157
160
  });
158
161
  warnAboutDocument(schema, fix, logger);
159
- const ctx: GlobalContext = {
162
+ const ctx: RunContext = {
160
163
  ...contextFlags(options),
161
164
  fix,
162
165
  // The scan walks every schema before the transform does, so on a larger stack, where loading
@@ -4,6 +4,7 @@ import type { FooterSink } from "../application/transform/footer/document-footer
4
4
  import type {
5
5
  IrDeclaration,
6
6
  IrInterfaceDeclaration,
7
+ IrMember,
7
8
  IrPropertySignature,
8
9
  } from "../domain/ir/nodes.js";
9
10
  import { declaresTypeAlias, type FooterEntry, isIrDeclaration } from "./footer-entry.js";
@@ -16,6 +17,12 @@ const OPERATIONS_NAME = "operations";
16
17
  */
17
18
  export class ContextFooterSink implements FooterSink {
18
19
  private readonly footer: FooterEntry[];
20
+ /**
21
+ * The IR `operations` entry this sink wrote last, with its members array that only this sink
22
+ * holds. Adding to it in place keeps a run linear: copying the members on every operation was
23
+ * quadratic (about 0.4 s on Microsoft Graph beta).
24
+ */
25
+ private owned: OwnedOperations | null = null;
19
26
 
20
27
  constructor(footer: FooterEntry[]) {
21
28
  this.footer = footer;
@@ -35,6 +42,11 @@ export class ContextFooterSink implements FooterSink {
35
42
  * the member in place, as in the original.
36
43
  */
37
44
  addOperation(build: () => IrPropertySignature): void {
45
+ const owned = this.currentOwnedOperations();
46
+ if (owned !== null) {
47
+ owned.members.push(build());
48
+ return;
49
+ }
38
50
  const existing = this.findOperations();
39
51
  if (existing !== null && !isIrDeclaration(existing.entry)) {
40
52
  const ts = loadTypeScript();
@@ -45,9 +57,23 @@ export class ContextFooterSink implements FooterSink {
45
57
  return;
46
58
  }
47
59
  const index = existing?.index ?? this.footer.push(emptyOperations()) - 1;
48
- const member = build();
60
+ this.takeOwnership(index).members.push(build());
61
+ }
62
+
63
+ /** The owned entry, unless user code replaced or moved it in the footer since. */
64
+ private currentOwnedOperations(): OwnedOperations | null {
65
+ const owned = this.owned;
66
+ return owned !== null && this.footer[owned.index] === owned.entry ? owned : null;
67
+ }
68
+
69
+ /** Replace the IR entry at `index` with a copy whose members array only this sink holds. */
70
+ private takeOwnership(index: number): OwnedOperations {
49
71
  const operations = this.footer[index] as IrInterfaceDeclaration;
50
- this.footer[index] = { ...operations, members: [...operations.members, member] };
72
+ const members: IrMember[] = [...operations.members];
73
+ const entry: IrInterfaceDeclaration = { ...operations, members };
74
+ this.footer[index] = entry;
75
+ this.owned = { entry, members, index };
76
+ return this.owned;
51
77
  }
52
78
 
53
79
  /** The first `operations` interface: the generator's (IR) or a user's (TypeScript). */
@@ -62,6 +88,12 @@ export class ContextFooterSink implements FooterSink {
62
88
  }
63
89
  }
64
90
 
91
+ interface OwnedOperations {
92
+ readonly entry: IrInterfaceDeclaration;
93
+ readonly members: IrMember[];
94
+ readonly index: number;
95
+ }
96
+
65
97
  function emptyOperations(): IrInterfaceDeclaration {
66
98
  return { kind: "interface", name: OPERATIONS_NAME, isExported: true, members: [], comments: [] };
67
99
  }
@@ -1,5 +1,7 @@
1
- import type { GlobalContext, LegacyCallbackName } from "../types.js";
1
+ import { WARNING_CODES, warningText } from "../domain/warnings.js";
2
+ import type { LegacyCallbackName } from "../types.js";
2
3
  import { defaultLogger } from "./default-ports.js";
4
+ import type { RunContext } from "./run-context.js";
3
5
 
4
6
  /** Where the IR callbacks are documented; the warning points users there. */
5
7
  export const IR_CALLBACKS_GUIDE_URL =
@@ -19,11 +21,13 @@ function message(name: LegacyCallbackName): string {
19
21
  }
20
22
 
21
23
  /** Warn once per run and callback; `silent` runs record the use but print nothing. */
22
- export function warnLegacyCallback(ctx: GlobalContext, name: LegacyCallbackName): void {
24
+ export function warnLegacyCallback(ctx: RunContext, name: LegacyCallbackName): void {
23
25
  ctx.legacyCallbackWarnings ??= new Set();
24
26
  if (ctx.legacyCallbackWarnings.has(name)) {
25
27
  return;
26
28
  }
27
29
  ctx.legacyCallbackWarnings.add(name);
28
- (ctx.logger ?? defaultLogger(ctx.silent)).warn(message(name));
30
+ (ctx.logger ?? defaultLogger(ctx.silent)).warn(
31
+ warningText(message(name), WARNING_CODES.LEGACY_CALLBACK_DEPRECATED),
32
+ );
29
33
  }
@@ -103,7 +103,7 @@ export function tsDedupe(types: TypeInput[]): ts.TypeNode[] {
103
103
  }
104
104
 
105
105
  /** @deprecated The generator keeps its own enum registry per run; kept for 1.x (ADR-004). */
106
- export const enumCache = new Map<string, ts.EnumDeclaration>();
106
+ export const enumCache: Map<string, ts.EnumDeclaration> = new Map();
107
107
 
108
108
  /**
109
109
  * Create a TS enum (with sanitized name and members)
@@ -114,7 +114,7 @@ export function tsEnum(
114
114
  members: (string | number)[],
115
115
  metadata?: { name?: string; description?: string | null }[],
116
116
  options?: { export?: boolean; shouldCache?: boolean },
117
- ) {
117
+ ): ts.EnumDeclaration {
118
118
  const ts = loadTypeScript();
119
119
  let enumName = sanitizeMemberName(name);
120
120
  enumName = `${enumName.charAt(0).toUpperCase()}${enumName.substring(1)}`;
@@ -149,7 +149,7 @@ export function tsArrayLiteralExpression(
149
149
  elementType: TypeInput,
150
150
  values: (string | number)[],
151
151
  options?: { export?: boolean; readonly?: boolean; injectFooter?: FooterEntry[] },
152
- ) {
152
+ ): ts.VariableStatement {
153
153
  const ts = loadTypeScript();
154
154
  let variableName = sanitizeMemberName(name);
155
155
  variableName = `${variableName.charAt(0).toLowerCase()}${variableName.substring(1)}`;
@@ -223,7 +223,7 @@ function enumMemberName(value: string | number, explicitName: string | undefined
223
223
  export function tsEnumMember(
224
224
  value: string | number,
225
225
  metadata: { name?: string; description?: string | null } = {},
226
- ) {
226
+ ): ts.EnumMember {
227
227
  const ts = loadTypeScript();
228
228
  const name = enumMemberName(value, metadata.name);
229
229
  const member = ts.factory.createEnumMember(name, toValueExpression(ts, value));
@@ -281,7 +281,9 @@ export function tsModifiers(modifiers: { readonly?: boolean; export?: boolean })
281
281
  * Create a valid property index
282
282
  * @deprecated Kept for 1.x; removed in 2.0 (ADR-004, decision 9).
283
283
  */
284
- export function tsPropertyIndex(index: string | number) {
284
+ export function tsPropertyIndex(
285
+ index: string | number,
286
+ ): ts.Identifier | ts.NumericLiteral | ts.StringLiteral {
285
287
  const ts = loadTypeScript();
286
288
  if (
287
289
  (typeof index === "number" && !(index < 0)) ||
@@ -90,7 +90,7 @@ export type { ResolveRefOptions } from "./document-refs.js";
90
90
  * @deprecated Kept for 1.x; removed in 2.0 (ADR-004, decision 9).
91
91
  */
92
92
  export function resolveRef<T>(
93
- schema: any,
93
+ schema: unknown,
94
94
  $ref: string,
95
95
  options: ResolveRefOptions,
96
96
  ): T | undefined {
@@ -3,6 +3,7 @@ import { withStackOverflowReported } from "../application/stack-overflow.js";
3
3
  import type { IrDeclaration } from "../domain/ir/nodes.js";
4
4
  import type { GeneratorOptions, SchemaSource } from "../types.js";
5
5
  import {
6
+ DEFAULT_REMOTE_DOCUMENT_LIMITS,
6
7
  defaultLogger,
7
8
  defaultRedoclyConfig,
8
9
  monotonicNow,
@@ -42,6 +43,7 @@ export async function defaultGeneratorPorts(options: GeneratorOptions): Promise<
42
43
  logger,
43
44
  now: monotonicNow,
44
45
  remoteRefTimeoutMs: REMOTE_REF_TIMEOUT_MS,
46
+ remoteDocumentLimits: DEFAULT_REMOTE_DOCUMENT_LIMITS,
45
47
  });
46
48
  return { loader, logger, redoc, now: monotonicNow };
47
49
  }
@@ -0,0 +1,34 @@
1
+ import type { Logger } from "../application/ports/logger.js";
2
+ import type { DocumentFooter } from "../application/transform/transform-context.js";
3
+ import type { GlobalContext, LegacyCallbackName, SchemaObject } from "../types.js";
4
+
5
+ /**
6
+ * The context the generator builds for a run: the public `GlobalContext` plus the run's own
7
+ * state. The state is not part of the public type, so it can change in a minor release; every
8
+ * field is optional, so a context a user builds by hand is a valid `RunContext` without it.
9
+ * Callbacks receive this object as `options.ctx` and may spread it; the state travels with it.
10
+ */
11
+ export interface RunContext extends GlobalContext {
12
+ readonly discriminators: GlobalContext["discriminators"] & {
13
+ /**
14
+ * With the `discriminator-values` fix: for each enum the scan added to a member and each
15
+ * inherited discriminator object, the enum of the owner's property and the location it is
16
+ * typed at.
17
+ */
18
+ readonly enumParents?: ReadonlyMap<
19
+ object,
20
+ { readonly schema: SchemaObject; readonly path: string }
21
+ >;
22
+ /**
23
+ * With the `discriminator-values` fix: for each schema, the properties of the discriminators
24
+ * it inherits besides `objects[ref]`, which it redefines too.
25
+ */
26
+ readonly otherProperties?: Readonly<Record<string, readonly string[]>>;
27
+ };
28
+ /** Footer state of this run (enum registry and enum naming), created on first use. */
29
+ runFooter?: DocumentFooter;
30
+ /** The deprecated callback forms this run has warned about (once each). */
31
+ legacyCallbackWarnings?: Set<LegacyCallbackName>;
32
+ /** Where this run reports warnings and debug output; without it, the terminal. */
33
+ logger?: Logger;
34
+ }
@@ -11,22 +11,24 @@ import type {
11
11
  TransformDiagnostics,
12
12
  TransformOptions,
13
13
  } from "../application/transform/transform-context.js";
14
- import type { GlobalContext, TransformNodeOptions } from "../types.js";
14
+ import type { TransformNodeOptions } from "../types.js";
15
15
  import { defaultLogger, monotonicNow } from "./default-ports.js";
16
16
  import { ContextFooterSink } from "./footer-sink.js";
17
+ import type { RunContext } from "./run-context.js";
17
18
  import { schemaCallbacks } from "./schema-callbacks.js";
18
19
 
19
20
  /**
20
- * Builds the ported transformers' context from the original `GlobalContext`: options, refs,
21
- * discriminators, the run footer and the user callbacks (see `schema-callbacks.ts`).
21
+ * Builds the ported transformers' context from the original `GlobalContext` (in a run, a
22
+ * `RunContext`): options, refs, discriminators, the run footer and the user callbacks (see
23
+ * `schema-callbacks.ts`).
22
24
  */
23
25
 
24
- function transformOptions(ctx: GlobalContext): TransformOptions {
26
+ function transformOptions(ctx: RunContext): TransformOptions {
25
27
  return {
26
28
  additionalProperties: ctx.additionalProperties,
27
29
  alphabetize: ctx.alphabetize,
28
30
  arrayLength: ctx.arrayLength,
29
- binaryAsBlob: ctx.binaryAsBlob,
31
+ binaryAsBlob: ctx.binaryAsBlob ?? false,
30
32
  conditionalEnums: ctx.conditionalEnums,
31
33
  dedupeEnums: ctx.dedupeEnums,
32
34
  defaultNonNullable: ctx.defaultNonNullable,
@@ -50,7 +52,7 @@ function transformOptions(ctx: GlobalContext): TransformOptions {
50
52
  }
51
53
 
52
54
  /** One footer per run: it holds the enum registry, so it lives on the run's context. */
53
- function runFooter(ctx: GlobalContext): DocumentFooter {
55
+ function runFooter(ctx: RunContext): DocumentFooter {
54
56
  ctx.runFooter ??= new RunFooter(new ContextFooterSink(ctx.injectFooter), ctx.dedupeEnums);
55
57
  return ctx.runFooter;
56
58
  }
@@ -60,7 +62,7 @@ const CASING: NameCasing = { pascalCase: (text) => changeCase.pascalCase(text) }
60
62
  /**
61
63
  * A monotonic clock and the run's logger (the default one for a user's own context), "ts" group.
62
64
  */
63
- function diagnostics(ctx: GlobalContext): TransformDiagnostics {
65
+ function diagnostics(ctx: RunContext): TransformDiagnostics {
64
66
  const logger = ctx.logger ?? defaultLogger(ctx.silent);
65
67
  return {
66
68
  now: monotonicNow,
@@ -69,7 +71,7 @@ function diagnostics(ctx: GlobalContext): TransformDiagnostics {
69
71
  }
70
72
 
71
73
  /** The ported transformers' context for one call from a legacy transformer. */
72
- export function createTransformContext(ctx: GlobalContext): TransformContext {
74
+ export function createTransformContext(ctx: RunContext): TransformContext {
73
75
  return {
74
76
  options: transformOptions(ctx),
75
77
  refs: { resolve: (ref) => ctx.resolve(ref) },
@@ -16,6 +16,7 @@ import { documentDeclarations, type TransformedDocument } from "./document-decla
16
16
  import { excludedValuesWarning } from "./enum-values-warnings.js";
17
17
  import { type FooterEntry, isIrDeclaration } from "./footer-entry.js";
18
18
  import { ContextFooterSink } from "./footer-sink.js";
19
+ import type { RunContext } from "./run-context.js";
19
20
  import { createTransformContext } from "./transform-context.js";
20
21
 
21
22
  const OPERATIONS_NAME = "operations";
@@ -46,11 +47,11 @@ interface EnumNameRecorder extends SharedEnumNames {
46
47
  */
47
48
  function sharedEnumNamesOf(
48
49
  document: DocumentObject,
49
- ctx: GlobalContext,
50
+ ctx: RunContext,
50
51
  recorder: EnumNameRecorder,
51
52
  ): SharedEnumNames {
52
53
  const injectFooter: FooterEntry[] = [];
53
- const scratch: GlobalContext = {
54
+ const scratch: RunContext = {
54
55
  ...ctx,
55
56
  injectFooter,
56
57
  runFooter: new RunFooter(new ContextFooterSink(injectFooter), true, recorder),
@@ -71,7 +72,7 @@ function enumNameRecorderOf(ctx: GlobalContext): EnumNameRecorder | null {
71
72
  }
72
73
 
73
74
  /** With a naming fix, `--enum` and `--dedupe-enums`, the run's footer names shared enums. */
74
- function withSharedEnumNames(document: DocumentObject, ctx: GlobalContext): void {
75
+ function withSharedEnumNames(document: DocumentObject, ctx: RunContext): void {
75
76
  const recorder = ctx.runFooter === undefined ? enumNameRecorderOf(ctx) : null;
76
77
  if (recorder === null || !ctx.enum || !ctx.dedupeEnums) {
77
78
  return;
@@ -87,7 +88,7 @@ function withSharedEnumNames(document: DocumentObject, ctx: GlobalContext): void
87
88
  function enumValuesChecked(
88
89
  body: readonly IrDeclaration[],
89
90
  footer: FooterEntry[],
90
- ctx: GlobalContext,
91
+ ctx: RunContext,
91
92
  ): FooterEntry[] {
92
93
  if (!ctx.enumValues) {
93
94
  return footer;
@@ -102,7 +103,7 @@ function enumValuesChecked(
102
103
  }
103
104
 
104
105
  /** @internal Root types, the footer, then the closing declarations (Phase 5, step 6). */
105
- export function transformDocument(schema: OpenAPI3, ctx: GlobalContext): TransformedDocument {
106
+ export function transformDocument(schema: OpenAPI3, ctx: RunContext): TransformedDocument {
106
107
  const document = schema as DocumentObject;
107
108
  withSharedEnumNames(document, ctx);
108
109
  const context = createTransformContext(ctx);
@@ -1,4 +1,5 @@
1
1
  import { assertNever } from "../../domain/assert-never.js";
2
+ import { DEFAULT_STRING_ENCODING } from "../../domain/ir/constructors.js";
2
3
  import type {
3
4
  IrIndexedAccessType,
4
5
  IrMember,
@@ -196,7 +197,7 @@ function printLeafType(type: IrLeafType): string {
196
197
  case "keyword":
197
198
  return type.keyword;
198
199
  case "stringLiteral":
199
- return printStringLiteral(type.value, type.encoding);
200
+ return printStringLiteral(type.value, type.encoding ?? DEFAULT_STRING_ENCODING);
200
201
  case "numberLiteral":
201
202
  return printNumberLiteral(type.value);
202
203
  case "booleanLiteral":
@@ -24,7 +24,8 @@ import type { TransformContext } from "../transform-context.js";
24
24
  import { isExplodedQueryObject } from "./exploded-query.js";
25
25
  import { parameterType } from "./parameter.js";
26
26
 
27
- const PATH_TEMPLATE_RE = /\{([^}]+)\}/g;
27
+ /** `[^{}]` keeps the scan linear: `[^}]` retried from every `{` of a long run of them. */
28
+ const PATH_TEMPLATE_RE = /\{([^{}]+)\}/g;
28
29
 
29
30
  /** A parameter as written in the document, and the object its `$ref` points to. */
30
31
  interface ParameterEntry {
@@ -35,7 +35,8 @@ import {
35
35
  } from "./path-template-replace.js";
36
36
  import { assignabilityProof } from "./type-assignability.js";
37
37
 
38
- const PATH_PARAMETER_RE = /\{[^}]+\}/g;
38
+ /** `[^{}]` keeps the scan linear: `[^}]` retried from every `{` of a long run of them. */
39
+ const PATH_PARAMETER_RE = /\{[^{}]+\}/g;
39
40
 
40
41
  /** Placeholders by parameter schema type; anything else is a string placeholder. */
41
42
  const PLACEHOLDERS: ReadonlyMap<unknown, PathPlaceholder> = new Map([
@@ -23,6 +23,16 @@ export function unionType(types: readonly IrType[]): IrType {
23
23
  return types.length === 0 ? NEVER : { kind: "union", types };
24
24
  }
25
25
 
26
+ export function intersectionType(types: readonly IrType[]): IrType {
27
+ return types.length === 1 ? (types[0] as IrType) : { kind: "intersection", types };
28
+ }
29
+
30
+ const NULLISH: ReadonlySet<string> = new Set(["null", "undefined"]);
31
+
32
+ export function isNullish(type: IrType): boolean {
33
+ return type.kind === "keyword" && NULLISH.has(type.keyword);
34
+ }
35
+
26
36
  export function propertyKey(name: IrPropertyName): string {
27
37
  return name.kind === "string" ? name.value : name.text;
28
38
  }
@@ -1,21 +1,16 @@
1
- import type { IrDeclaration, IrMember, IrType } from "../../../domain/ir/nodes.js";
1
+ import type { IrDeclaration, IrType } from "../../../domain/ir/nodes.js";
2
2
  import {
3
3
  absorbed,
4
4
  type Evaluated,
5
5
  type Evaluation,
6
- MISSING,
7
- NEVER,
6
+ intersectionType,
7
+ isNullish,
8
8
  OPAQUE,
9
9
  propertyKey,
10
10
  unionType,
11
11
  } from "./evaluation.js";
12
- import {
13
- declaredFirst,
14
- extracted,
15
- isNeverIntersection,
16
- markerObject,
17
- omitted,
18
- } from "./type-rules.js";
12
+ import { indexType } from "./type-indexing.js";
13
+ import { extracted, markerObject, omitted } from "./type-rules.js";
19
14
 
20
15
  /**
21
16
  * A small evaluator of the generated types, as TypeScript reads them, for the `--enum-values`
@@ -30,7 +25,6 @@ const MAX_EXPANSIONS = 200;
30
25
  const DEEP_PREFIX = "FlattenedDeepRequired<";
31
26
  /** How many members distributing an intersection may give; more is opaque. */
32
27
  const MAX_DISTRIBUTED = 64;
33
- const NULLISH: ReadonlySet<string> = new Set(["null", "undefined"]);
34
28
 
35
29
  export class TypeEvaluator {
36
30
  private readonly declared = new Map<string, IrType>();
@@ -168,127 +162,6 @@ export class TypeEvaluator {
168
162
 
169
163
  /** `value[index]` for a string literal, `string` or `number` index. */
170
164
  index(value: Evaluated, index: IrType): Evaluation {
171
- const owner = this.expand(value);
172
- if (owner.kind !== "type") {
173
- return owner;
174
- }
175
- if (this.membersOf(owner).length > 1) {
176
- return this.unionIndex(owner, index);
177
- }
178
- const { type } = owner;
179
- switch (type.kind) {
180
- case "object":
181
- return this.memberType(owner, type.members, index);
182
- case "intersection":
183
- return this.partsIndex(owner, type.types, index);
184
- case "union":
185
- return this.unionIndex(owner, index);
186
- case "array":
187
- return isNumberIndex(index) ? this.flattened(owner, type.element) : builtInIndex(index);
188
- case "keyword":
189
- return type.keyword === "never" ? owner : builtInIndex(index);
190
- default:
191
- return builtInIndex(index);
192
- }
193
- }
194
-
195
- private memberType(owner: Evaluated, members: readonly IrMember[], index: IrType): Evaluation {
196
- const key = indexKey(index);
197
- const property = members.find(
198
- (member) => member.kind === "property" && key !== null && propertyKey(member.name) === key,
199
- );
200
- if (property !== undefined) {
201
- return this.flattened(owner, property.type);
202
- }
203
- const signature = members.find((member) => member.kind === "indexSignature");
204
- if (signature !== undefined) {
205
- return this.flattened(owner, signature.type);
206
- }
207
- return key === null || isObjectBuiltIn(key) ? OPAQUE : MISSING;
208
- }
209
-
210
- private partsIndex(owner: Evaluated, parts: readonly IrType[], index: IrType): Evaluation {
211
- if (isNeverIntersection(this, owner, parts)) {
212
- return { ...owner, type: NEVER };
213
- }
214
- const found = parts.map((type) => this.index({ ...owner, type }, index));
215
- if (found.some((part) => part.kind === "opaque")) {
216
- return OPAQUE;
217
- }
218
- const read = parts.map((type, at) => ({ type, found: found[at] as Evaluation }));
219
- const types = declaredFirst(this, owner, indexKey(index), read).flatMap((part) =>
220
- part.kind === "type" ? [part.type] : [],
221
- );
222
- return types.length === 0 ? MISSING : { ...owner, type: intersectionType(types) };
165
+ return indexType(this, value, index);
223
166
  }
224
-
225
- private unionIndex(owner: Evaluated, index: IrType): Evaluation {
226
- const found = this.membersOf(owner).map((member) =>
227
- member.kind === "type" ? this.index(member, index) : member,
228
- );
229
- if (found.some((member) => member.kind === "missing")) {
230
- return MISSING;
231
- }
232
- if (found.some((member) => member.kind === "opaque")) {
233
- return OPAQUE;
234
- }
235
- return { ...owner, type: unionType((found as Evaluated[]).map((member) => member.type)) };
236
- }
237
-
238
- /**
239
- * A member read through `FlattenedDeepRequired`: a value that is only arrays (and `undefined` or
240
- * `null`) is replaced by its elements, anything else loses `undefined` and `null`.
241
- */
242
- private flattened(owner: Evaluated, type: IrType): Evaluation {
243
- const member: Evaluated = { ...owner, type };
244
- if (!owner.deep) {
245
- return this.expand(member);
246
- }
247
- const members = this.membersOf(member);
248
- if (members.some((candidate) => candidate.kind !== "type")) {
249
- return OPAQUE;
250
- }
251
- const present = (members as Evaluated[]).filter((candidate) => !isNullish(candidate.type));
252
- const elements = present.map((candidate) => arrayElement(candidate.type));
253
- if (present.length > 0 && elements.every((element) => element !== null)) {
254
- return this.expand({ ...owner, type: unionType(elements as IrType[]) });
255
- }
256
- return { ...owner, type: unionType(present.map((candidate) => candidate.type)) };
257
- }
258
- }
259
-
260
- function isNullish(type: IrType): boolean {
261
- return type.kind === "keyword" && NULLISH.has(type.keyword);
262
- }
263
-
264
- /** The element of an expanded member; expanding already dropped `readonly`. */
265
- function arrayElement(type: IrType): IrType | null {
266
- return type.kind === "array" ? type.element : null;
267
- }
268
-
269
- function intersectionType(types: readonly IrType[]): IrType {
270
- return types.length === 1 ? (types[0] as IrType) : { kind: "intersection", types };
271
- }
272
-
273
- function indexKey(index: IrType): string | null {
274
- if (index.kind === "stringLiteral") {
275
- return index.value;
276
- }
277
- return index.kind === "numberLiteral" ? String(index.value) : null;
278
- }
279
-
280
- function isNumberIndex(index: IrType): boolean {
281
- const key = indexKey(index);
282
- return (index.kind === "keyword" && index.keyword === "number") || /^\d+$/.test(key ?? "");
283
- }
284
-
285
- function isObjectBuiltIn(key: string): boolean {
286
- return key in {};
287
- }
288
-
289
- /** A key of a primitive or an array: a built-in member is opaque, any other key is missing. */
290
- function builtInIndex(index: IrType): Evaluation {
291
- const key = indexKey(index);
292
- const isBuiltIn = key === null || key in String.prototype || key in Array.prototype;
293
- return isBuiltIn || key in Number.prototype ? OPAQUE : MISSING;
294
167
  }