@distilled.cloud/core 0.30.3 → 1.0.0-rc.10

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 (216) hide show
  1. package/LICENSE +201 -0
  2. package/lib/api.d.ts +165 -0
  3. package/lib/api.d.ts.map +1 -0
  4. package/lib/api.js +190 -0
  5. package/lib/api.js.map +1 -0
  6. package/lib/category.d.ts +4 -4
  7. package/lib/category.js +4 -4
  8. package/lib/codegen/boolean-string-enums.d.ts +36 -0
  9. package/lib/codegen/boolean-string-enums.d.ts.map +1 -0
  10. package/lib/codegen/boolean-string-enums.js +94 -0
  11. package/lib/codegen/boolean-string-enums.js.map +1 -0
  12. package/lib/codegen/boolean-string-enums.test.d.ts +2 -0
  13. package/lib/codegen/boolean-string-enums.test.d.ts.map +1 -0
  14. package/lib/codegen/boolean-string-enums.test.js +147 -0
  15. package/lib/codegen/boolean-string-enums.test.js.map +1 -0
  16. package/lib/codegen/cli.d.ts +79 -0
  17. package/lib/codegen/cli.d.ts.map +1 -0
  18. package/lib/codegen/cli.js +149 -0
  19. package/lib/codegen/cli.js.map +1 -0
  20. package/lib/codegen/emit.d.ts +125 -0
  21. package/lib/codegen/emit.d.ts.map +1 -0
  22. package/lib/codegen/emit.js +101 -0
  23. package/lib/codegen/emit.js.map +1 -0
  24. package/lib/codegen/format.d.ts +23 -0
  25. package/lib/codegen/format.d.ts.map +1 -0
  26. package/lib/codegen/format.js +28 -0
  27. package/lib/codegen/format.js.map +1 -0
  28. package/lib/codegen/generator.d.ts +334 -0
  29. package/lib/codegen/generator.d.ts.map +1 -0
  30. package/lib/codegen/generator.js +813 -0
  31. package/lib/codegen/generator.js.map +1 -0
  32. package/lib/codegen/graph.d.ts +36 -0
  33. package/lib/codegen/graph.d.ts.map +1 -0
  34. package/lib/codegen/graph.js +136 -0
  35. package/lib/codegen/graph.js.map +1 -0
  36. package/lib/codegen/graphql-client.d.ts +62 -0
  37. package/lib/codegen/graphql-client.d.ts.map +1 -0
  38. package/lib/codegen/graphql-client.js +294 -0
  39. package/lib/codegen/graphql-client.js.map +1 -0
  40. package/lib/codegen/graphql-client.test.d.ts +2 -0
  41. package/lib/codegen/graphql-client.test.d.ts.map +1 -0
  42. package/lib/codegen/graphql-client.test.js +311 -0
  43. package/lib/codegen/graphql-client.test.js.map +1 -0
  44. package/lib/codegen/graphql.d.ts +207 -0
  45. package/lib/codegen/graphql.d.ts.map +1 -0
  46. package/lib/codegen/graphql.js +799 -0
  47. package/lib/codegen/graphql.js.map +1 -0
  48. package/lib/codegen/members.d.ts +25 -0
  49. package/lib/codegen/members.d.ts.map +1 -0
  50. package/lib/codegen/members.js +55 -0
  51. package/lib/codegen/members.js.map +1 -0
  52. package/lib/codegen/naming.d.ts +29 -0
  53. package/lib/codegen/naming.d.ts.map +1 -0
  54. package/lib/codegen/naming.js +74 -0
  55. package/lib/codegen/naming.js.map +1 -0
  56. package/lib/codegen/openapi-cli.d.ts +52 -0
  57. package/lib/codegen/openapi-cli.d.ts.map +1 -0
  58. package/lib/codegen/openapi-cli.js +109 -0
  59. package/lib/codegen/openapi-cli.js.map +1 -0
  60. package/lib/codegen/openapi.d.ts +178 -0
  61. package/lib/codegen/openapi.d.ts.map +1 -0
  62. package/lib/codegen/openapi.js +1377 -0
  63. package/lib/codegen/openapi.js.map +1 -0
  64. package/lib/codegen/operations.d.ts +24 -0
  65. package/lib/codegen/operations.d.ts.map +1 -0
  66. package/lib/codegen/operations.js +56 -0
  67. package/lib/codegen/operations.js.map +1 -0
  68. package/lib/codegen/pagination.d.ts +39 -0
  69. package/lib/codegen/pagination.d.ts.map +1 -0
  70. package/lib/codegen/pagination.js +33 -0
  71. package/lib/codegen/pagination.js.map +1 -0
  72. package/lib/codegen/patches.d.ts +65 -0
  73. package/lib/codegen/patches.d.ts.map +1 -0
  74. package/lib/codegen/patches.js +236 -0
  75. package/lib/codegen/patches.js.map +1 -0
  76. package/lib/codegen/patches.test.d.ts +2 -0
  77. package/lib/codegen/patches.test.d.ts.map +1 -0
  78. package/lib/codegen/patches.test.js +105 -0
  79. package/lib/codegen/patches.test.js.map +1 -0
  80. package/lib/codegen/prelude.d.ts +15 -0
  81. package/lib/codegen/prelude.d.ts.map +1 -0
  82. package/lib/codegen/prelude.js +60 -0
  83. package/lib/codegen/prelude.js.map +1 -0
  84. package/lib/codegen/proto.d.ts +121 -0
  85. package/lib/codegen/proto.d.ts.map +1 -0
  86. package/lib/codegen/proto.js +962 -0
  87. package/lib/codegen/proto.js.map +1 -0
  88. package/lib/codegen/rewrite-operation-ids.d.ts +131 -0
  89. package/lib/codegen/rewrite-operation-ids.d.ts.map +1 -0
  90. package/lib/codegen/rewrite-operation-ids.js +1079 -0
  91. package/lib/codegen/rewrite-operation-ids.js.map +1 -0
  92. package/lib/codegen/rewrite-operation-ids.test.d.ts +2 -0
  93. package/lib/codegen/rewrite-operation-ids.test.d.ts.map +1 -0
  94. package/lib/codegen/rewrite-operation-ids.test.js +533 -0
  95. package/lib/codegen/rewrite-operation-ids.test.js.map +1 -0
  96. package/lib/codegen/spec-path.d.ts +16 -0
  97. package/lib/codegen/spec-path.d.ts.map +1 -0
  98. package/lib/codegen/spec-path.js +101 -0
  99. package/lib/codegen/spec-path.js.map +1 -0
  100. package/lib/error-category.d.ts +28 -0
  101. package/lib/error-category.d.ts.map +1 -0
  102. package/lib/error-category.js +46 -0
  103. package/lib/error-category.js.map +1 -0
  104. package/lib/errors.d.ts +1 -0
  105. package/lib/errors.d.ts.map +1 -1
  106. package/lib/errors.js +18 -13
  107. package/lib/errors.js.map +1 -1
  108. package/lib/graphql.d.ts +284 -0
  109. package/lib/graphql.d.ts.map +1 -0
  110. package/lib/graphql.fixture.d.ts +249 -0
  111. package/lib/graphql.fixture.d.ts.map +1 -0
  112. package/lib/graphql.fixture.js +240 -0
  113. package/lib/graphql.fixture.js.map +1 -0
  114. package/lib/graphql.js +718 -0
  115. package/lib/graphql.js.map +1 -0
  116. package/lib/graphql.test.d.ts +2 -0
  117. package/lib/graphql.test.d.ts.map +1 -0
  118. package/lib/graphql.test.js +780 -0
  119. package/lib/graphql.test.js.map +1 -0
  120. package/lib/graphql.types.d.ts +2 -0
  121. package/lib/graphql.types.d.ts.map +1 -0
  122. package/lib/graphql.types.js +45 -0
  123. package/lib/graphql.types.js.map +1 -0
  124. package/lib/json-patch.d.ts +30 -30
  125. package/lib/json-patch.d.ts.map +1 -1
  126. package/lib/json-patch.js +73 -107
  127. package/lib/json-patch.js.map +1 -1
  128. package/lib/pagination.d.ts +77 -51
  129. package/lib/pagination.d.ts.map +1 -1
  130. package/lib/pagination.js +162 -94
  131. package/lib/pagination.js.map +1 -1
  132. package/lib/protocol-http.d.ts +74 -0
  133. package/lib/protocol-http.d.ts.map +1 -0
  134. package/lib/protocol-http.js +590 -0
  135. package/lib/protocol-http.js.map +1 -0
  136. package/lib/protocol-http.test.d.ts +2 -0
  137. package/lib/protocol-http.test.d.ts.map +1 -0
  138. package/lib/protocol-http.test.js +88 -0
  139. package/lib/protocol-http.test.js.map +1 -0
  140. package/lib/protocol-rest.d.ts +134 -0
  141. package/lib/protocol-rest.d.ts.map +1 -0
  142. package/lib/protocol-rest.js +256 -0
  143. package/lib/protocol-rest.js.map +1 -0
  144. package/lib/retry.d.ts +8 -2
  145. package/lib/retry.d.ts.map +1 -1
  146. package/lib/retry.js +22 -16
  147. package/lib/retry.js.map +1 -1
  148. package/lib/schema.d.ts +8 -9
  149. package/lib/schema.d.ts.map +1 -1
  150. package/lib/schema.js +8 -9
  151. package/lib/schema.js.map +1 -1
  152. package/lib/trait.d.ts +174 -0
  153. package/lib/trait.d.ts.map +1 -0
  154. package/lib/trait.js +123 -0
  155. package/lib/trait.js.map +1 -0
  156. package/package.json +24 -78
  157. package/src/api.ts +460 -0
  158. package/src/category.ts +4 -4
  159. package/src/codegen/boolean-string-enums.test.ts +168 -0
  160. package/src/codegen/boolean-string-enums.ts +106 -0
  161. package/src/codegen/cli.ts +285 -0
  162. package/src/codegen/emit.ts +203 -0
  163. package/src/codegen/format.ts +47 -0
  164. package/src/codegen/generator.ts +1283 -0
  165. package/src/codegen/graph.ts +151 -0
  166. package/src/codegen/graphql-client.test.ts +386 -0
  167. package/src/codegen/graphql-client.ts +419 -0
  168. package/src/codegen/graphql.ts +1217 -0
  169. package/src/codegen/members.ts +71 -0
  170. package/src/codegen/naming.ts +86 -0
  171. package/src/codegen/openapi-cli.ts +182 -0
  172. package/src/codegen/openapi.ts +1689 -0
  173. package/src/codegen/operations.ts +76 -0
  174. package/src/codegen/pagination.ts +71 -0
  175. package/src/codegen/patches.test.ts +130 -0
  176. package/src/codegen/patches.ts +291 -0
  177. package/src/codegen/prelude.ts +70 -0
  178. package/src/codegen/proto.ts +1128 -0
  179. package/src/codegen/rewrite-operation-ids.test.ts +563 -0
  180. package/src/codegen/rewrite-operation-ids.ts +1206 -0
  181. package/src/codegen/spec-path.ts +115 -0
  182. package/src/error-category.ts +84 -0
  183. package/src/errors.ts +22 -25
  184. package/src/graphql.fixture.ts +371 -0
  185. package/src/graphql.test.ts +974 -0
  186. package/src/graphql.ts +1321 -0
  187. package/src/graphql.types.ts +185 -0
  188. package/src/json-patch.ts +95 -122
  189. package/src/pagination.ts +217 -146
  190. package/src/protocol-http.test.ts +107 -0
  191. package/src/protocol-http.ts +735 -0
  192. package/src/protocol-rest.ts +391 -0
  193. package/src/retry.ts +21 -22
  194. package/src/schema.ts +9 -10
  195. package/src/trait.ts +274 -0
  196. package/README.md +0 -30
  197. package/lib/client.d.ts +0 -167
  198. package/lib/client.d.ts.map +0 -1
  199. package/lib/client.js +0 -659
  200. package/lib/client.js.map +0 -1
  201. package/lib/schemas.d.ts +0 -60
  202. package/lib/schemas.d.ts.map +0 -1
  203. package/lib/schemas.js +0 -79
  204. package/lib/schemas.js.map +0 -1
  205. package/lib/sensitive.d.ts +0 -71
  206. package/lib/sensitive.d.ts.map +0 -1
  207. package/lib/sensitive.js +0 -96
  208. package/lib/sensitive.js.map +0 -1
  209. package/lib/traits.d.ts +0 -421
  210. package/lib/traits.d.ts.map +0 -1
  211. package/lib/traits.js +0 -737
  212. package/lib/traits.js.map +0 -1
  213. package/src/client.ts +0 -1177
  214. package/src/schemas.ts +0 -128
  215. package/src/sensitive.ts +0 -119
  216. package/src/traits.ts +0 -996
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Structure-member analysis for the SDK generators (dev-time only).
3
+ *
4
+ * The TS-facing name policy is the provider's (cloudflare camelCases wire
5
+ * names, AWS keeps them) — supplied as `nameFn`. Wire-name resolution from
6
+ * the smithy HTTP binding traits is generic smithy semantics.
7
+ */
8
+ import { oneLine } from "./naming.ts";
9
+
10
+ export interface MemberBase {
11
+ /** Original smithy member name. */
12
+ readonly name: string;
13
+ /** TS-facing name (per `nameFn`, deduplicated within the structure). */
14
+ readonly tsName: string;
15
+ readonly target: string;
16
+ readonly required: boolean;
17
+ readonly doc: string | undefined;
18
+ readonly traits: Record<string, any>;
19
+ }
20
+
21
+ /**
22
+ * Base analysis of a structure's members: TS naming (with collision
23
+ * dedup — a clashing name falls back to the original, then a numeric
24
+ * suffix), required-ness, and one-line docs. Binding classification and
25
+ * anything provider-specific layer on top via `traits`.
26
+ */
27
+ export const memberBases = (
28
+ def: any,
29
+ nameFn: (memberName: string) => string = (n) => n,
30
+ ): MemberBase[] => {
31
+ const used = new Set<string>();
32
+ return Object.entries(def.members ?? {}).map(([mn, m]: [string, any]) => {
33
+ const traits = m.traits ?? {};
34
+ let tsName = nameFn(mn);
35
+ if (used.has(tsName)) tsName = mn;
36
+ let k = 2;
37
+ while (used.has(tsName)) tsName = `${nameFn(mn)}${k++}`;
38
+ used.add(tsName);
39
+ return {
40
+ name: mn,
41
+ tsName,
42
+ target: m.target,
43
+ required: "smithy.api#required" in traits,
44
+ doc: oneLine(traits["smithy.api#documentation"]),
45
+ traits,
46
+ };
47
+ });
48
+ };
49
+
50
+ /**
51
+ * The wire name a bound member serializes under, per the smithy HTTP
52
+ * binding traits: labels use the member name (URI placeholders), query and
53
+ * header use the trait value when it's a non-empty string, everything else
54
+ * uses `jsonName` when present.
55
+ */
56
+ export const smithyWireName = (
57
+ traits: Record<string, any>,
58
+ memberName: string,
59
+ binding: "label" | "query" | "header" | "other",
60
+ ): string => {
61
+ if (binding === "label") return memberName;
62
+ if (binding === "query") {
63
+ const v = traits["smithy.api#httpQuery"];
64
+ return typeof v === "string" && v ? v : memberName;
65
+ }
66
+ if (binding === "header") {
67
+ const v = traits["smithy.api#httpHeader"];
68
+ return typeof v === "string" && v ? v : memberName;
69
+ }
70
+ return traits["smithy.api#jsonName"] ?? memberName;
71
+ };
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Shared naming utilities for the SDK generators (dev-time only — imported by
3
+ * `packages/<sdk>/scripts/generate.ts`, never by runtime code).
4
+ */
5
+
6
+ /** JSON-string-quote a value for emission into generated source. */
7
+ export const q = (s: string): string => JSON.stringify(s);
8
+
9
+ /** Smithy shape id → local name (`ns#Shape` → `Shape`). */
10
+ export const local = (id: string): string => id.split("#")[1] ?? id;
11
+
12
+ /** Whether a shape id is a smithy prelude shape (`smithy.api#…`). */
13
+ export const isPrelude = (id: string): boolean => id.startsWith("smithy.api#");
14
+
15
+ const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
16
+
17
+ /** Emit a TS object key: bare when a valid identifier, quoted otherwise. */
18
+ export const tsKey = (s: string): string => (IDENT.test(s) ? s : q(s));
19
+
20
+ /** snake_case / kebab-case wire name → camelCase TS-facing name. */
21
+ export const camel = (s: string): string =>
22
+ s.replace(/[_-]+([A-Za-z0-9])/g, (_, c: string) => c.toUpperCase());
23
+
24
+ /** Reserved words that can't be `const` names. */
25
+ export const RESERVED = new Set([
26
+ "break",
27
+ "case",
28
+ "catch",
29
+ "class",
30
+ "const",
31
+ "continue",
32
+ "debugger",
33
+ "default",
34
+ "delete",
35
+ "do",
36
+ "else",
37
+ "enum",
38
+ "export",
39
+ "extends",
40
+ "false",
41
+ "finally",
42
+ "for",
43
+ "function",
44
+ "if",
45
+ "import",
46
+ "in",
47
+ "instanceof",
48
+ "let",
49
+ "new",
50
+ "null",
51
+ "package",
52
+ "return",
53
+ "static",
54
+ "super",
55
+ "switch",
56
+ "this",
57
+ "throw",
58
+ "true",
59
+ "try",
60
+ "typeof",
61
+ "var",
62
+ "void",
63
+ "while",
64
+ "with",
65
+ "yield",
66
+ ]);
67
+
68
+ /**
69
+ * Lower-case the first character for an exported operation name — unless the
70
+ * result is a reserved word, in which case the original casing is kept.
71
+ */
72
+ export const lowerFirst = (s: string): string => {
73
+ const lowered = s.charAt(0).toLowerCase() + s.slice(1);
74
+ return RESERVED.has(lowered) ? s : lowered;
75
+ };
76
+
77
+ /** Upper-case the first character. */
78
+ export const upperFirst = (s: string): string =>
79
+ s.charAt(0).toUpperCase() + s.slice(1);
80
+
81
+ /**
82
+ * Collapse a smithy documentation string to a single safe line for a
83
+ * `/** … *​/` comment.
84
+ */
85
+ export const oneLine = (s: string | undefined): string | undefined =>
86
+ s ? s.replace(/\s+/g, " ").replace(/\*\//g, "*\\/").trim() : undefined;
@@ -0,0 +1,182 @@
1
+ /**
2
+ * OpenAPI → Smithy pipeline helper (dev-time only, provider-agnostic).
3
+ *
4
+ * Owns the spec-side pipeline every OpenAPI-sourced provider shares: read the
5
+ * spec file, apply OpenAPI RFC-6902 ops to the document, convert with
6
+ * {@link convertOpenApiToSmithy} (which owns verbNoun naming), apply Smithy
7
+ * RFC-6902 ops (`/shapes`, `/metadata`) to the model, and write
8
+ * `.generated-specs/<name>.json`. Stale targets fail unless
9
+ * `onStalePatch: "warn"`.
10
+ *
11
+ * A provider's `scripts/convert.ts` is: a `runOpenApiConvert` call.
12
+ * `scripts/generate.ts` compiles the already-patched models and does not
13
+ * apply patches.
14
+ */
15
+ import * as fs from "node:fs/promises";
16
+ import * as path from "node:path";
17
+ import {
18
+ convertOpenApiToSmithy,
19
+ type OpenApiConvertOptions,
20
+ } from "./openapi.ts";
21
+ import {
22
+ applyRfc6902Files,
23
+ finalizeConvert,
24
+ isSmithyPatchPath,
25
+ listRfc6902PatchFiles,
26
+ } from "./patches.ts";
27
+ import { resolveSpecPath } from "./spec-path.ts";
28
+
29
+ export interface OpenApiSpecEntry {
30
+ /** Output model name — written to `<outDir>/<name>.json`. */
31
+ readonly name: string;
32
+ /** Spec file path, relative to `root` (or absolute). */
33
+ readonly specPath: string;
34
+ /**
35
+ * Hook between patching and conversion (e.g. path prefixing, server
36
+ * rewrites). May mutate the spec in place or return a replacement.
37
+ */
38
+ readonly preprocess?: (spec: any) => unknown | void | Promise<unknown | void>;
39
+ /** Per-spec converter option overrides (merged over the shared options). */
40
+ readonly options?: Partial<OpenApiConvertOptions>;
41
+ }
42
+
43
+ export interface RunOpenApiConvertOptions {
44
+ /** Absolute package root (usually `path.resolve(import.meta.dir, "..")`). */
45
+ readonly root: string;
46
+ readonly specs: readonly OpenApiSpecEntry[];
47
+ /**
48
+ * RFC-6902 patch chain root, relative to `root`. Default `"patches"`;
49
+ * `false` disables. Layout: `<patchesDir>/<name>/*.json` when the
50
+ * per-spec directory exists; for single-spec providers a flat
51
+ * `<patchesDir>/*.json` also works. Ops under `/shapes` or `/metadata`
52
+ * apply to the Smithy model after conversion; the rest apply to OpenAPI.
53
+ */
54
+ readonly patchesDir?: string | false;
55
+ /** Output directory, relative to `root`. Default `".generated-specs"`. */
56
+ readonly outDir?: string;
57
+ /**
58
+ * Spec text parser. Default `JSON.parse` — the seam for YAML specs
59
+ * (`parse: (text) => YAML.parse(text)`).
60
+ */
61
+ readonly parse?: (text: string, specPath: string) => unknown;
62
+ /** Shared converter options (per-spec `options` merge over these). */
63
+ readonly options: OpenApiConvertOptions;
64
+ /**
65
+ * What to do when a patch JSON pointer does not resolve. Default `"fail"`:
66
+ * silent skip is how a whole patch chain can vanish after an upstream path
67
+ * prefix change. `"warn"` restores the old skip-and-continue behaviour.
68
+ */
69
+ readonly onStalePatch?: "fail" | "warn";
70
+ /**
71
+ * Run {@link finalizeConvert} on the written models (reference check +
72
+ * finalized marker; naming already happened in the converter). Default
73
+ * true. Pass false when the caller finalizes once after several convert
74
+ * steps (Fly machines + sprites + addons).
75
+ */
76
+ readonly finalize?: boolean;
77
+ }
78
+
79
+ const exists = async (p: string): Promise<boolean> => {
80
+ try {
81
+ await fs.access(p);
82
+ return true;
83
+ } catch {
84
+ return false;
85
+ }
86
+ };
87
+
88
+ /** Run the OpenAPI→Smithy conversion pipeline (plain async — call from a script). */
89
+ export const runOpenApiConvert = async (
90
+ o: RunOpenApiConvertOptions,
91
+ ): Promise<void> => {
92
+ const outDir = path.resolve(o.root, o.outDir ?? ".generated-specs");
93
+ const patchRoot =
94
+ o.patchesDir === false
95
+ ? undefined
96
+ : path.resolve(o.root, o.patchesDir ?? "patches");
97
+ const parse = o.parse ?? ((text: string) => JSON.parse(text));
98
+ const onStalePatch = o.onStalePatch ?? "fail";
99
+
100
+ await fs.mkdir(outDir, { recursive: true });
101
+
102
+ console.log("🛠️ openapi → smithy");
103
+ console.log(` Output: ${outDir}`);
104
+
105
+ for (const entry of o.specs) {
106
+ // Production path by default; `specs/.local` under DISTILLED_SPECS_LOCAL.
107
+ const specPath = resolveSpecPath(o.root, entry.specPath);
108
+ let spec: any = parse(await fs.readFile(specPath, "utf8"), specPath);
109
+
110
+ // ---- RFC-6902: OpenAPI ops before convert, Smithy ops after ----
111
+ let patchDir: string | undefined;
112
+ if (patchRoot && (await exists(patchRoot))) {
113
+ const perSpec = path.join(patchRoot, entry.name);
114
+ if (await exists(perSpec)) {
115
+ patchDir = perSpec;
116
+ } else if (o.specs.length === 1) {
117
+ patchDir = patchRoot;
118
+ }
119
+ }
120
+ const patchFiles = patchDir ? await listRfc6902PatchFiles(patchDir) : [];
121
+ const patchLabel = (file: string) => `${entry.name}/${path.basename(file)}`;
122
+ const openapiPatches = await applyRfc6902Files(spec, patchFiles, {
123
+ onStalePatch,
124
+ include: (op) => !isSmithyPatchPath(op.path),
125
+ label: patchLabel,
126
+ });
127
+ if (openapiPatches.errors.length) {
128
+ for (const b of openapiPatches.errors)
129
+ console.error(`❌ bad patch: ${b}`);
130
+ throw new Error(
131
+ `${openapiPatches.errors.length} patch operation(s) failed — fix the pointers or delete the patch`,
132
+ );
133
+ }
134
+
135
+ // ---- Preprocess hook ----
136
+ if (entry.preprocess) {
137
+ const replaced = await entry.preprocess(spec);
138
+ if (replaced !== undefined) spec = replaced;
139
+ }
140
+
141
+ // ---- Convert, Smithy patches, write ----
142
+ const model = convertOpenApiToSmithy(spec, {
143
+ ...o.options,
144
+ ...entry.options,
145
+ });
146
+ const smithyPatches = await applyRfc6902Files(model, patchFiles, {
147
+ onStalePatch,
148
+ include: (op) => isSmithyPatchPath(op.path),
149
+ label: patchLabel,
150
+ });
151
+ if (smithyPatches.errors.length) {
152
+ for (const b of smithyPatches.errors) console.error(`❌ bad patch: ${b}`);
153
+ throw new Error(
154
+ `${smithyPatches.errors.length} Smithy patch operation(s) failed — fix the pointers or delete the patch`,
155
+ );
156
+ }
157
+ const opCount = Object.values(model.shapes).filter(
158
+ (s: any) => s.type === "operation",
159
+ ).length;
160
+ const outPath = path.join(outDir, `${entry.name}.json`);
161
+ await fs.writeFile(outPath, JSON.stringify(model, null, 2) + "\n");
162
+ const staleOps = openapiPatches.stale + smithyPatches.stale;
163
+ console.log(
164
+ ` ✅ ${entry.name}: ${opCount} operations, ${Object.keys(model.shapes).length} shapes` +
165
+ (patchFiles.length
166
+ ? ` (${patchFiles.length} patch file(s) applied${staleOps ? `, ${staleOps} stale op(s) skipped` : ""})`
167
+ : ""),
168
+ );
169
+ }
170
+
171
+ if (o.finalize !== false) {
172
+ const written = new Set(o.specs.map((s) => s.name));
173
+ await finalizeConvert({
174
+ root: o.root,
175
+ outDir,
176
+ // Patches and naming already ran above.
177
+ patchesDir: false,
178
+ operationNaming: "as-is",
179
+ include: (resource) => written.has(resource),
180
+ });
181
+ }
182
+ };