@distilled.cloud/core 1.0.0-rc.7 → 1.0.0-rc.9

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 (57) hide show
  1. package/LICENSE +201 -0
  2. package/lib/codegen/cli.d.ts +8 -6
  3. package/lib/codegen/cli.d.ts.map +1 -1
  4. package/lib/codegen/cli.js +6 -62
  5. package/lib/codegen/cli.js.map +1 -1
  6. package/lib/codegen/graphql.d.ts +6 -0
  7. package/lib/codegen/graphql.d.ts.map +1 -1
  8. package/lib/codegen/graphql.js +14 -4
  9. package/lib/codegen/graphql.js.map +1 -1
  10. package/lib/codegen/openapi-cli.d.ts +17 -3
  11. package/lib/codegen/openapi-cli.d.ts.map +1 -1
  12. package/lib/codegen/openapi-cli.js +50 -48
  13. package/lib/codegen/openapi-cli.js.map +1 -1
  14. package/lib/codegen/openapi.d.ts +27 -1
  15. package/lib/codegen/openapi.d.ts.map +1 -1
  16. package/lib/codegen/openapi.js +81 -3
  17. package/lib/codegen/openapi.js.map +1 -1
  18. package/lib/codegen/patches.d.ts +65 -0
  19. package/lib/codegen/patches.d.ts.map +1 -0
  20. package/lib/codegen/patches.js +236 -0
  21. package/lib/codegen/patches.js.map +1 -0
  22. package/lib/codegen/patches.test.d.ts +2 -0
  23. package/lib/codegen/patches.test.d.ts.map +1 -0
  24. package/lib/codegen/patches.test.js +105 -0
  25. package/lib/codegen/patches.test.js.map +1 -0
  26. package/lib/codegen/proto.d.ts +121 -0
  27. package/lib/codegen/proto.d.ts.map +1 -0
  28. package/lib/codegen/proto.js +962 -0
  29. package/lib/codegen/proto.js.map +1 -0
  30. package/lib/codegen/rewrite-operation-ids.d.ts +131 -0
  31. package/lib/codegen/rewrite-operation-ids.d.ts.map +1 -0
  32. package/lib/codegen/rewrite-operation-ids.js +1079 -0
  33. package/lib/codegen/rewrite-operation-ids.js.map +1 -0
  34. package/lib/codegen/rewrite-operation-ids.test.d.ts +2 -0
  35. package/lib/codegen/rewrite-operation-ids.test.d.ts.map +1 -0
  36. package/lib/codegen/rewrite-operation-ids.test.js +533 -0
  37. package/lib/codegen/rewrite-operation-ids.test.js.map +1 -0
  38. package/lib/codegen/spec-path.d.ts +16 -0
  39. package/lib/codegen/spec-path.d.ts.map +1 -0
  40. package/lib/codegen/spec-path.js +101 -0
  41. package/lib/codegen/spec-path.js.map +1 -0
  42. package/lib/json-patch.d.ts +18 -11
  43. package/lib/json-patch.d.ts.map +1 -1
  44. package/lib/json-patch.js +63 -25
  45. package/lib/json-patch.js.map +1 -1
  46. package/package.json +4 -4
  47. package/src/codegen/cli.ts +14 -91
  48. package/src/codegen/graphql.ts +25 -3
  49. package/src/codegen/openapi-cli.ts +75 -59
  50. package/src/codegen/openapi.ts +118 -4
  51. package/src/codegen/patches.test.ts +130 -0
  52. package/src/codegen/patches.ts +291 -0
  53. package/src/codegen/proto.ts +1128 -0
  54. package/src/codegen/rewrite-operation-ids.test.ts +563 -0
  55. package/src/codegen/rewrite-operation-ids.ts +1206 -0
  56. package/src/codegen/spec-path.ts +115 -0
  57. package/src/json-patch.ts +82 -25
@@ -0,0 +1,16 @@
1
+ /** Set to a non-empty value to read specs from `specs/.local` instead. */
2
+ export declare const LOCAL_ENV = "DISTILLED_SPECS_LOCAL";
3
+ /** The local working copy of a mirror, relative to a package root. */
4
+ export declare const LOCAL_DIR: string;
5
+ /** Repository name of the mirror feeding `packages/<pkg>`. */
6
+ export declare const mirrorRepositoryName: (pkg: string) => string;
7
+ /** Whether this process reads specs from `specs/.local`. */
8
+ export declare const isLocalSpecs: () => boolean;
9
+ /**
10
+ * Resolve a package-relative spec path to an absolute one.
11
+ *
12
+ * @param root absolute package root (`path.resolve(import.meta.dir, "..")`)
13
+ * @param specPath the production path, relative to `root` (or absolute)
14
+ */
15
+ export declare const resolveSpecPath: (root: string, specPath: string) => string;
16
+ //# sourceMappingURL=spec-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-path.d.ts","sourceRoot":"","sources":["../../src/codegen/spec-path.ts"],"names":[],"mappings":"AAoCA,0EAA0E;AAC1E,eAAO,MAAM,SAAS,0BAA0B,CAAC;AAEjD,sEAAsE;AACtE,eAAO,MAAM,SAAS,QAA+B,CAAC;AAEtD,8DAA8D;AAC9D,eAAO,MAAM,oBAAoB,QAAS,MAAM,WAAyB,CAAC;AAE1E,4DAA4D;AAC5D,eAAO,MAAM,YAAY,QAAO,OACc,CAAC;AAyB/C;;;;;GAKG;AACH,eAAO,MAAM,eAAe,SAAU,MAAM,YAAY,MAAM,KAAG,MAoChE,CAAC"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Spec path resolution — the seam between a production spec mirror and a
3
+ * local working copy of one (dev-time only).
4
+ *
5
+ * Every SDK package reads its specs from `packages/<pkg>/specs/`. In
6
+ * production that directory holds a submodule of the package's mirror
7
+ * repository, `distilled-mirror/spec-mirror-<pkg>`, whose layout is fixed:
8
+ *
9
+ * packages/<pkg>/specs/spec-mirror-<pkg>/
10
+ * ├── .meta/ fetch-specs.ts + its package.json
11
+ * └── specs/ what the generator reads
12
+ *
13
+ * A contributor adding a provider cannot create that repository — it lives in
14
+ * an org they have no write access to, and it is only created once the
15
+ * `distilled-submodules` stack deploys on merge to main. So `pnpm specs:local
16
+ * <pkg>` runs the same `fetch-specs.ts` into a gitignored directory shaped
17
+ * exactly like the mirror:
18
+ *
19
+ * packages/<pkg>/specs/.local/
20
+ * ├── .meta/
21
+ * └── specs/
22
+ *
23
+ * Setting {@link LOCAL_ENV} then re-roots every spec read into it. Nothing in
24
+ * the package's source changes — the `specPath` a `convert.ts` declares stays
25
+ * the production one — so there is no `.local` reference that could be
26
+ * committed by accident, and the switch is impossible to leave on: it lives
27
+ * in the environment of one command, never in a file.
28
+ *
29
+ * The opt-in is deliberate rather than a fallback-when-missing. A silent
30
+ * fallback would regenerate an SDK from a stale `.local` copy the moment a
31
+ * submodule failed to check out, and the result — committed, formatted,
32
+ * plausible — would be indistinguishable from a real regeneration.
33
+ */
34
+ import * as fs from "node:fs";
35
+ import * as path from "node:path";
36
+ /** Set to a non-empty value to read specs from `specs/.local` instead. */
37
+ export const LOCAL_ENV = "DISTILLED_SPECS_LOCAL";
38
+ /** The local working copy of a mirror, relative to a package root. */
39
+ export const LOCAL_DIR = path.join("specs", ".local");
40
+ /** Repository name of the mirror feeding `packages/<pkg>`. */
41
+ export const mirrorRepositoryName = (pkg) => `spec-mirror-${pkg}`;
42
+ /** Whether this process reads specs from `specs/.local`. */
43
+ export const isLocalSpecs = () => (process.env[LOCAL_ENV] ?? "").trim() !== "";
44
+ let announced = false;
45
+ /**
46
+ * The path within a mirror's `specs/` directory that `specPath` names.
47
+ *
48
+ * Spec paths take one of two shapes — `specs/<mirror>/specs/<tail>` for a
49
+ * submodule and `specs/<tail>` for a committed file — and in both the tail is
50
+ * everything after the LAST `specs` segment. That one rule covers the
51
+ * submodule form, the committed form, the nested forms
52
+ * (`specs/<mirror>/specs/models/<service>/...`) and the directory form
53
+ * (`specs/<mirror>/specs`, whose tail is empty — the mirror's `specs/`
54
+ * directory itself) without needing to know which it was handed.
55
+ *
56
+ * `undefined` means there is no `specs` segment at all, which local mode
57
+ * cannot interpret.
58
+ */
59
+ const tailWithinSpecs = (specPath) => {
60
+ const segments = specPath.split(/[/\\]/).filter((s) => s !== "" && s !== ".");
61
+ const last = segments.lastIndexOf("specs");
62
+ if (last === -1)
63
+ return undefined;
64
+ return segments.slice(last + 1).join(path.sep);
65
+ };
66
+ /**
67
+ * Resolve a package-relative spec path to an absolute one.
68
+ *
69
+ * @param root absolute package root (`path.resolve(import.meta.dir, "..")`)
70
+ * @param specPath the production path, relative to `root` (or absolute)
71
+ */
72
+ export const resolveSpecPath = (root, specPath) => {
73
+ const production = path.resolve(root, specPath);
74
+ if (!isLocalSpecs() || path.isAbsolute(specPath))
75
+ return production;
76
+ const tail = tailWithinSpecs(specPath);
77
+ if (tail === undefined) {
78
+ throw new Error(`${LOCAL_ENV} is set, but "${specPath}" has no \`specs/\` segment to ` +
79
+ `re-root. Local mode only understands paths under the package's ` +
80
+ `specs/ directory.`);
81
+ }
82
+ if (!announced) {
83
+ announced = true;
84
+ // stderr, not stdout: generate-all captures stdout per package and only
85
+ // prints it on failure, and this is exactly the line you need to see on
86
+ // a run that succeeded against the wrong specs.
87
+ console.error(`⚠ ${LOCAL_ENV} — reading specs from ${LOCAL_DIR}, not the mirror submodule.`);
88
+ }
89
+ const local = path.join(root, LOCAL_DIR, "specs", tail);
90
+ if (!fs.existsSync(local)) {
91
+ // A missing local spec is a setup mistake, not a generator bug, and the
92
+ // ENOENT the caller would otherwise raise names a path nobody recognises.
93
+ throw new Error(`${local} does not exist. Materialise this package's mirror first:\n` +
94
+ ` pnpm specs:local ${path.basename(root)}\n` +
95
+ `If that succeeds and this path is still missing, the package's ` +
96
+ `declared spec path (${specPath}) is not mirror-shaped yet — it must ` +
97
+ `read \`specs/<mirror>/specs/<file>\` for local mode to find it.`);
98
+ }
99
+ return local;
100
+ };
101
+ //# sourceMappingURL=spec-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-path.js","sourceRoot":"","sources":["../../src/codegen/spec-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,0EAA0E;AAC1E,MAAM,CAAC,MAAM,SAAS,GAAG,uBAAuB,CAAC;AAEjD,sEAAsE;AACtE,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAEtD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,eAAe,GAAG,EAAE,CAAC;AAE1E,4DAA4D;AAC5D,MAAM,CAAC,MAAM,YAAY,GAAG,GAAY,EAAE,CACxC,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;AAE/C,IAAI,SAAS,GAAG,KAAK,CAAC;AAEtB;;;;;;;;;;;;;GAaG;AACH,MAAM,eAAe,GAAG,CAAC,QAAgB,EAAsB,EAAE;IAC/D,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAClC,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjD,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,IAAY,EAAE,QAAgB,EAAU,EAAE;IACxE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAChD,IAAI,CAAC,YAAY,EAAE,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,UAAU,CAAC;IAEpE,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,GAAG,SAAS,iBAAiB,QAAQ,iCAAiC;YACpE,iEAAiE;YACjE,mBAAmB,CACtB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,SAAS,GAAG,IAAI,CAAC;QACjB,wEAAwE;QACxE,wEAAwE;QACxE,gDAAgD;QAChD,OAAO,CAAC,KAAK,CACX,MAAM,SAAS,yBAAyB,SAAS,6BAA6B,CAC/E,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACxD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,wEAAwE;QACxE,0EAA0E;QAC1E,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,6DAA6D;YACnE,sBAAsB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI;YAC7C,iEAAiE;YACjE,uBAAuB,QAAQ,uCAAuC;YACtE,iEAAiE,CACpE,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * JSON Patch (RFC 6902) Implementation
3
3
  *
4
- * Provides a unified spec patching system for all SDKs. Patches are applied to
5
- * generated intermediary specs (Smithy models, OpenAPI docs, …) before code
6
- * generation to add typed error shapes, rename operations, mark nullable
7
- * fields, etc. Ported from the distilled repo's `core/json-patch`.
4
+ * Provides a unified spec patching system for all SDKs. Patches apply in
5
+ * `convert` so `.generated-specs` is the patched Smithy model. generate does
6
+ * not patch. Prefer `operationNaming: "verbNoun"` for operationId renames —
7
+ * JSON pointers at `/paths/~1foo/operationId` go stale when upstream prefixes
8
+ * paths.
8
9
  *
9
10
  * Pure functions only — callers load patch files themselves (the generators
10
11
  * use Effect's FileSystem) and hand the parsed operations to `applyPatch`.
@@ -23,6 +24,13 @@ export interface PatchFile {
23
24
  /** Parse a JSON Pointer (RFC 6901) path into segments. */
24
25
  export declare function parseJsonPointer(pointer: string): string[];
25
26
  /** Get a value at a JSON Pointer path. */
27
+ /**
28
+ * The target location of a patch operation is absent from the document —
29
+ * spec drift (renamed/removed upstream), not a malformed patch.
30
+ */
31
+ export declare class StaleTargetError extends Error {
32
+ readonly name = "StaleTargetError";
33
+ }
26
34
  export declare function getValueAtPath(obj: unknown, pointer: string): unknown;
27
35
  /** Set a value at a JSON Pointer path. */
28
36
  export declare function setValueAtPath(obj: unknown, pointer: string, value: unknown): void;
@@ -33,12 +41,11 @@ export declare function applyOperation(obj: unknown, operation: JsonPatchOperati
33
41
  /** Apply a JSON Patch to an object (mutates in place). */
34
42
  export declare function applyPatch(obj: unknown, patch: JsonPatch): void;
35
43
  /**
36
- * Whether a per-operation failure is caused by the target location being
37
- * absent from the spec (i.e. spec drift — the operation/shape the patch
38
- * targets was renamed or removed upstream) as opposed to a malformed patch.
39
- * Stale targets should be skipped with a warning rather than aborting the
40
- * whole generate; a patch that only adds an error to an operation that no
41
- * longer exists is harmless to drop.
44
+ * Whether a per-operation failure is a {@link StaleTargetError} (the
45
+ * operation/shape the patch targets was renamed or removed upstream) as
46
+ * opposed to a malformed patch. Accepts the thrown value or its message.
47
+ * Convert fails the run on these by default (`onStalePatch: "fail"`);
48
+ * `"warn"` restores skip-and-continue.
42
49
  */
43
- export declare function isStaleTargetError(message: string): boolean;
50
+ export declare function isStaleTargetError(error: unknown): boolean;
44
51
  //# sourceMappingURL=json-patch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"json-patch.d.ts","sourceRoot":"","sources":["../src/json-patch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,SAAS,GAAG,kBAAkB,EAAE,CAAC;AAE7C,MAAM,WAAW,SAAS;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,SAAS,CAAC;CACpB;AAMD,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAS1D;AAED,0CAA0C;AAC1C,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAiBrE;AAED,0CAA0C;AAC1C,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,GACb,IAAI,CAoCN;AAED,6CAA6C;AAC7C,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAgCrE;AAMD,oEAAoE;AACpE,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,kBAAkB,GAC5B,IAAI,CA+CN;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAI/D;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK3D"}
1
+ {"version":3,"file":"json-patch.d.ts","sourceRoot":"","sources":["../src/json-patch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,SAAS,GAAG,kBAAkB,EAAE,CAAC;AAE7C,MAAM,WAAW,SAAS;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,SAAS,CAAC;CACpB;AAMD,0DAA0D;AAC1D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAS1D;AAED,0CAA0C;AAC1C;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAkB,IAAI,sBAAsB;CAC7C;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAkCrE;AAED,0CAA0C;AAC1C,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,OAAO,GACb,IAAI,CAsCN;AAED,6CAA6C;AAC7C,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CA+CrE;AAMD,oEAAoE;AACpE,wBAAgB,cAAc,CAC5B,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,kBAAkB,GAC5B,IAAI,CAoDN;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAI/D;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAe1D"}
package/lib/json-patch.js CHANGED
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * JSON Patch (RFC 6902) Implementation
3
3
  *
4
- * Provides a unified spec patching system for all SDKs. Patches are applied to
5
- * generated intermediary specs (Smithy models, OpenAPI docs, …) before code
6
- * generation to add typed error shapes, rename operations, mark nullable
7
- * fields, etc. Ported from the distilled repo's `core/json-patch`.
4
+ * Provides a unified spec patching system for all SDKs. Patches apply in
5
+ * `convert` so `.generated-specs` is the patched Smithy model. generate does
6
+ * not patch. Prefer `operationNaming: "verbNoun"` for operationId renames —
7
+ * JSON pointers at `/paths/~1foo/operationId` go stale when upstream prefixes
8
+ * paths.
8
9
  *
9
10
  * Pure functions only — callers load patch files themselves (the generators
10
11
  * use Effect's FileSystem) and hand the parsed operations to `applyPatch`.
@@ -25,19 +26,35 @@ export function parseJsonPointer(pointer) {
25
26
  .map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
26
27
  }
27
28
  /** Get a value at a JSON Pointer path. */
29
+ /**
30
+ * The target location of a patch operation is absent from the document —
31
+ * spec drift (renamed/removed upstream), not a malformed patch.
32
+ */
33
+ export class StaleTargetError extends Error {
34
+ name = "StaleTargetError";
35
+ }
28
36
  export function getValueAtPath(obj, pointer) {
29
37
  const segments = parseJsonPointer(pointer);
30
38
  let current = obj;
31
39
  for (const segment of segments) {
32
- if (current === null || typeof current !== "object") {
33
- throw new Error(`Cannot traverse path ${pointer}: not an object`);
40
+ if (current === null ||
41
+ current === undefined ||
42
+ typeof current !== "object") {
43
+ throw new StaleTargetError(`JSON pointer ${pointer} missing (at '${segment}'): not an object`);
34
44
  }
35
45
  if (Array.isArray(current)) {
36
46
  const index = segment === "-" ? current.length : parseInt(segment, 10);
47
+ if (index < 0 || index >= current.length) {
48
+ throw new StaleTargetError(`JSON pointer ${pointer} missing index '${segment}'`);
49
+ }
37
50
  current = current[index];
38
51
  }
39
52
  else {
40
- current = current[segment];
53
+ const record = current;
54
+ if (!Object.prototype.hasOwnProperty.call(record, segment)) {
55
+ throw new StaleTargetError(`JSON pointer ${pointer} missing key '${segment}'`);
56
+ }
57
+ current = record[segment];
41
58
  }
42
59
  }
43
60
  return current;
@@ -52,7 +69,7 @@ export function setValueAtPath(obj, pointer, value) {
52
69
  for (let i = 0; i < segments.length - 1; i++) {
53
70
  const segment = segments[i];
54
71
  if (current === null || typeof current !== "object") {
55
- throw new Error(`Cannot traverse path ${pointer}: not an object`);
72
+ throw new StaleTargetError(`Cannot traverse path ${pointer}: not an object`);
56
73
  }
57
74
  if (Array.isArray(current)) {
58
75
  current = current[parseInt(segment, 10)];
@@ -63,7 +80,7 @@ export function setValueAtPath(obj, pointer, value) {
63
80
  }
64
81
  const lastSegment = segments[segments.length - 1];
65
82
  if (current === null || typeof current !== "object") {
66
- throw new Error(`Cannot set value at path ${pointer}: parent is not an object`);
83
+ throw new StaleTargetError(`Cannot set value at path ${pointer}: parent is not an object`);
67
84
  }
68
85
  if (Array.isArray(current)) {
69
86
  if (lastSegment === "-") {
@@ -87,7 +104,7 @@ export function removeValueAtPath(obj, pointer) {
87
104
  for (let i = 0; i < segments.length - 1; i++) {
88
105
  const segment = segments[i];
89
106
  if (current === null || typeof current !== "object") {
90
- throw new Error(`Cannot traverse path ${pointer}: not an object`);
107
+ throw new StaleTargetError(`Cannot traverse path ${pointer}: not an object`);
91
108
  }
92
109
  if (Array.isArray(current)) {
93
110
  current = current[parseInt(segment, 10)];
@@ -98,13 +115,22 @@ export function removeValueAtPath(obj, pointer) {
98
115
  }
99
116
  const lastSegment = segments[segments.length - 1];
100
117
  if (current === null || typeof current !== "object") {
101
- throw new Error(`Cannot remove at path ${pointer}: parent is not an object`);
118
+ throw new StaleTargetError(`Cannot remove at path ${pointer}: parent is not an object`);
102
119
  }
120
+ // RFC 6902 §4.2: the target location MUST exist.
103
121
  if (Array.isArray(current)) {
104
- current.splice(parseInt(lastSegment, 10), 1);
122
+ const index = parseInt(lastSegment, 10);
123
+ if (Number.isNaN(index) || index < 0 || index >= current.length) {
124
+ throw new StaleTargetError(`JSON pointer ${pointer} missing index '${lastSegment}'`);
125
+ }
126
+ current.splice(index, 1);
105
127
  }
106
128
  else {
107
- delete current[lastSegment];
129
+ const record = current;
130
+ if (!Object.prototype.hasOwnProperty.call(record, lastSegment)) {
131
+ throw new StaleTargetError(`JSON pointer ${pointer} missing key '${lastSegment}'`);
132
+ }
133
+ delete record[lastSegment];
108
134
  }
109
135
  }
110
136
  // ============================================================================
@@ -119,17 +145,20 @@ export function applyOperation(obj, operation) {
119
145
  case "remove":
120
146
  removeValueAtPath(obj, operation.path);
121
147
  break;
122
- case "replace":
123
- // For replace, the path must exist
124
- getValueAtPath(obj, operation.path); // throws if doesn't exist
148
+ case "replace": {
149
+ const existing = getValueAtPath(obj, operation.path);
150
+ if (existing === undefined) {
151
+ throw new StaleTargetError(`JSON pointer ${operation.path} does not exist`);
152
+ }
125
153
  setValueAtPath(obj, operation.path, operation.value);
126
154
  break;
155
+ }
127
156
  case "move": {
128
157
  if (!operation.from)
129
158
  throw new Error("move operation requires 'from'");
130
159
  const moveValue = getValueAtPath(obj, operation.from);
131
160
  if (moveValue === undefined) {
132
- throw new Error(`Cannot move from path ${operation.from}: not an object`);
161
+ throw new StaleTargetError(`Cannot move from path ${operation.from}: not an object`);
133
162
  }
134
163
  removeValueAtPath(obj, operation.from);
135
164
  setValueAtPath(obj, operation.path, moveValue);
@@ -160,15 +189,24 @@ export function applyPatch(obj, patch) {
160
189
  }
161
190
  }
162
191
  /**
163
- * Whether a per-operation failure is caused by the target location being
164
- * absent from the spec (i.e. spec drift — the operation/shape the patch
165
- * targets was renamed or removed upstream) as opposed to a malformed patch.
166
- * Stale targets should be skipped with a warning rather than aborting the
167
- * whole generate; a patch that only adds an error to an operation that no
168
- * longer exists is harmless to drop.
192
+ * Whether a per-operation failure is a {@link StaleTargetError} (the
193
+ * operation/shape the patch targets was renamed or removed upstream) as
194
+ * opposed to a malformed patch. Accepts the thrown value or its message.
195
+ * Convert fails the run on these by default (`onStalePatch: "fail"`);
196
+ * `"warn"` restores skip-and-continue.
169
197
  */
170
- export function isStaleTargetError(message) {
198
+ export function isStaleTargetError(error) {
199
+ if (error instanceof StaleTargetError)
200
+ return true;
201
+ const message = typeof error === "string"
202
+ ? error
203
+ : error instanceof Error
204
+ ? error.message
205
+ : "";
171
206
  return (message.includes("not an object") ||
172
- message.includes("parent is not an object"));
207
+ message.includes("parent is not an object") ||
208
+ message.includes("missing key") ||
209
+ message.includes("missing index") ||
210
+ message.includes("does not exist"));
173
211
  }
174
212
  //# sourceMappingURL=json-patch.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"json-patch.js","sourceRoot":"","sources":["../src/json-patch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"json-patch.js","sourceRoot":"","sources":["../src/json-patch.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distilled.cloud/core",
3
- "version": "1.0.0-rc.7",
3
+ "version": "1.0.0-rc.9",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/alchemy-run/distilled",
@@ -28,13 +28,13 @@
28
28
  }
29
29
  },
30
30
  "devDependencies": {
31
- "@effect/platform-bun": ">=4.0.0-rc.111 || >=4.0.0",
31
+ "@effect/platform-bun": ">=4.0.0-rc.112 || >=4.0.0",
32
32
  "@types/bun": "latest",
33
33
  "@types/node": "latest",
34
- "effect": ">=4.0.0-rc.111 || >=4.0.0"
34
+ "effect": ">=4.0.0-rc.112 || >=4.0.0"
35
35
  },
36
36
  "peerDependencies": {
37
- "effect": ">=4.0.0-rc.111 || >=4.0.0"
37
+ "effect": ">=4.0.0-rc.112 || >=4.0.0"
38
38
  },
39
39
  "scripts": {
40
40
  "build": "tsc -b"
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * The generic generator CLI harness (dev-time only).
3
3
  *
4
- * Owns the smithy-model pipeline every SDK package shares: scan the model
5
- * directory (plus optional hand-authored manual specs), apply the RFC-6902
6
- * patch chain (`patches/<resource>/*.json`, `*.manual.json` last; stale
7
- * targets warn, malformed patches fail the run), compile each model through
8
- * {@link generateService} with the provider's {@link SdkSpec}, write the
9
- * service modules and the namespaced barrel.
4
+ * Owns the smithy→SDK pipeline every SDK package shares: scan the model
5
+ * directory (plus optional hand-authored manual specs), compile each model
6
+ * through {@link generateService} with the provider's {@link SdkSpec}, write
7
+ * the service modules and the namespaced barrel. RFC-6902 patches apply in
8
+ * convert so `.generated-specs` is already the patched model.
10
9
  *
11
10
  * A provider's `scripts/generate.ts` is: trait consts + an SdkSpec + a
12
11
  * `runGeneratorCli` call.
@@ -17,11 +16,6 @@ import * as FileSystem from "effect/FileSystem";
17
16
  import * as Path from "effect/Path";
18
17
  import { Flag } from "effect/unstable/cli";
19
18
  import { Command } from "effect/unstable/cli";
20
- import {
21
- applyOperation,
22
- isStaleTargetError,
23
- type PatchFile,
24
- } from "../json-patch.ts";
25
19
  import { barrel } from "./emit.ts";
26
20
  import { formatGenerated } from "./format.ts";
27
21
  import { generateService, type SdkSpec } from "./generator.ts";
@@ -85,13 +79,15 @@ export interface GeneratorCliOptions {
85
79
  readonly continueOnModelError?: boolean;
86
80
  /** Directory of hand-authored models merged after the generated ones. */
87
81
  readonly manualSpecsDir?: string;
88
- /** RFC-6902 patch chain root. Default `patches`; `false` disables. */
89
- readonly patchesDir?: string | false;
90
82
  /**
91
- * Model transform applied AFTER the patch chain, before generation —
92
- * for whole-model rewrites that must see post-patch shape names (e.g.
93
- * cloudflare's scope-twin structural dedup). May mutate the model;
94
- * a returned log line is printed.
83
+ * RFC-6902 patches apply in convert, never here. Only `false` is accepted
84
+ * so a string is a type error rather than a silently ignored setting.
85
+ */
86
+ readonly patchesDir?: false;
87
+ /**
88
+ * Model transform applied before generation (e.g. AWS dropping
89
+ * unreachable foreign-namespace shapes from a vendored model). Does not
90
+ * write back to `.generated-specs` — convert owns the committed model.
95
91
  */
96
92
  readonly transformModel?: (model: any, resource: string) => string | void;
97
93
  /** The provider spec — built per model (metadata may vary per model). */
@@ -168,28 +164,6 @@ export const runGeneratorCli = (options: GeneratorCliOptions): void => {
168
164
  const written: string[] = [];
169
165
  const failedModels: string[] = [];
170
166
  let totalOps = 0;
171
- let totalPatches = 0;
172
- let staleOps = 0;
173
- const badPatches: string[] = [];
174
-
175
- // Orphan check: a patch directory matching no smithy model would be
176
- // silently dropped — flag it instead.
177
- const patchRoot =
178
- options.patchesDir === false
179
- ? undefined
180
- : path.join(root, options.patchesDir ?? "patches");
181
- if (patchRoot && (yield* fs.exists(patchRoot))) {
182
- const resources = new Set(
183
- entries.map((e) => e.file.replace(/\.json$/, "")),
184
- );
185
- for (const dir of yield* fs.readDirectory(patchRoot)) {
186
- if (!resources.has(dir)) {
187
- yield* Console.warn(
188
- `⚠️ patches/${dir}/ matches no smithy model — orphaned?`,
189
- );
190
- }
191
- }
192
- }
193
167
 
194
168
  for (const { file, dir } of entries) {
195
169
  const model = JSON.parse(
@@ -202,43 +176,6 @@ export const runGeneratorCli = (options: GeneratorCliOptions): void => {
202
176
  file.replace(/\.json$/, "");
203
177
  if (config.resource && resource !== config.resource) continue;
204
178
 
205
- // Apply the RFC-6902 patch chain before generating. Hand-written
206
- // *.manual.json patches apply after the generated ones — they
207
- // usually target post-rename shape names.
208
- const patchDir = patchRoot && path.join(patchRoot, resource);
209
- if (patchDir && (yield* fs.exists(patchDir))) {
210
- const patchFiles = (yield* fs.readDirectory(patchDir))
211
- .filter((f) => f.endsWith(".json"))
212
- .sort(
213
- (a, b) =>
214
- Number(a.endsWith(".manual.json")) -
215
- Number(b.endsWith(".manual.json")) || a.localeCompare(b),
216
- );
217
- for (const pf of patchFiles) {
218
- const parsed = JSON.parse(
219
- yield* fs.readFileString(path.join(patchDir, pf)),
220
- ) as PatchFile;
221
- for (const patchOp of parsed.patches ?? []) {
222
- try {
223
- applyOperation(model, patchOp);
224
- } catch (e) {
225
- const msg = e instanceof Error ? e.message : String(e);
226
- if (isStaleTargetError(msg)) {
227
- staleOps++;
228
- yield* Console.warn(
229
- ` ⚠️ stale: ${resource}/${pf} [${patchOp.op} ${patchOp.path}]`,
230
- );
231
- } else {
232
- badPatches.push(
233
- `${resource}/${pf} [${patchOp.op} ${patchOp.path}]: ${msg}`,
234
- );
235
- }
236
- }
237
- }
238
- totalPatches++;
239
- }
240
- }
241
-
242
179
  if (options.transformModel) {
243
180
  const note = options.transformModel(model, resource);
244
181
  if (note) yield* Console.log(` ${note}`);
@@ -267,17 +204,6 @@ export const runGeneratorCli = (options: GeneratorCliOptions): void => {
267
204
  totalOps += operations;
268
205
  }
269
206
 
270
- if (badPatches.length) {
271
- for (const b of badPatches) {
272
- yield* Console.error(`❌ bad patch: ${b}`);
273
- }
274
- return yield* Effect.die(
275
- new Error(
276
- `${badPatches.length} malformed patch operation(s) — fix or remove them`,
277
- ),
278
- );
279
- }
280
-
281
207
  // Barrel — namespace per resource to avoid op-name collisions.
282
208
  //
283
209
  // A FULL run is authoritative: the barrel is exactly what was
@@ -331,10 +257,7 @@ export const runGeneratorCli = (options: GeneratorCliOptions): void => {
331
257
  yield* (options.finalize ?? formatGenerated)(outDir);
332
258
 
333
259
  yield* Console.log(
334
- `\n✅ Generated ${totalOps} operations across ${written.length} resource modules` +
335
- (totalPatches
336
- ? ` (${totalPatches} patch files applied${staleOps ? `, ${staleOps} stale op(s) skipped` : ""}).`
337
- : "."),
260
+ `\n✅ Generated ${totalOps} operations across ${written.length} resource modules.`,
338
261
  );
339
262
  yield* Console.log(` ${path.join(outDir, "index.ts")}`);
340
263
 
@@ -39,6 +39,8 @@
39
39
  * out of packages/expo-eas.
40
40
  */
41
41
 
42
+ import { toVerbNoun } from "./rewrite-operation-ids.ts";
43
+
42
44
  // ============================================================================
43
45
  // GraphQL introspection types (subset of the canonical spec)
44
46
  // ============================================================================
@@ -222,6 +224,12 @@ export interface GraphQLConvertOptions {
222
224
  readonly customScalars?: Record<string, string>;
223
225
  /** Relay connection pagination. Omit (or `false`) to emit no `paginated`. */
224
226
  readonly relay?: RelayOptions | false;
227
+ /**
228
+ * SDK operation name style. Default `"verbNoun"` (`projectCreate` →
229
+ * `createProject`). The GraphQL document's `operationName` stays the
230
+ * schema field name.
231
+ */
232
+ readonly operationNaming?: "as-is" | "verbNoun";
225
233
  }
226
234
 
227
235
  export interface GraphQLConvertResult {
@@ -917,6 +925,7 @@ export const convertGraphQLToSmithy = (
917
925
  skipRootField = () => false,
918
926
  customScalars = {},
919
927
  relay = false,
928
+ operationNaming = "verbNoun",
920
929
  } = options;
921
930
 
922
931
  const typeMap = new Map<string, IntrospectionType>();
@@ -950,11 +959,24 @@ export const convertGraphQLToSmithy = (
950
959
  );
951
960
  for (const opPath of paths) {
952
961
  const functionName = pathToFunctionName(opPath);
953
- if (seenNames.has(functionName)) continue;
954
- seenNames.add(functionName);
962
+ const sdkName =
963
+ operationNaming === "verbNoun"
964
+ ? toVerbNoun(functionName)
965
+ : functionName;
966
+ if (seenNames.has(sdkName)) {
967
+ // Two schema fields (or the same field under two paths) map to
968
+ // one SDK name; the first wins. Silent drops hide real ops.
969
+ if (sdkName !== functionName) {
970
+ console.warn(
971
+ ` ⚠️ verbNoun collision: ${functionName} → ${sdkName} already taken (dropped)`,
972
+ );
973
+ }
974
+ continue;
975
+ }
976
+ seenNames.add(sdkName);
955
977
  pending.push({
956
978
  functionName,
957
- opName: toPascalCase(functionName),
979
+ opName: toPascalCase(sdkName),
958
980
  type,
959
981
  path: opPath,
960
982
  description: