@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,151 @@
1
+ /**
2
+ * Shared shape-graph utilities for the SDK generators (dev-time only).
3
+ *
4
+ * Works over plain Smithy-JSON shape maps (`{ "ns#Name": { type, members,
5
+ * … } }`). Providers supply the shape map and (optionally) a custom
6
+ * dependency extractor; reachability, topological ordering, and cycle
7
+ * analysis are generic.
8
+ */
9
+ import { isPrelude } from "./naming.ts";
10
+
11
+ export type ShapeMap = Record<string, any>;
12
+
13
+ /**
14
+ * Direct dependencies of a shape: structure/union member targets, list
15
+ * member, map key/value. Operations/services are not traversed here —
16
+ * providers seed reachability from operation I/O explicitly.
17
+ */
18
+ export const shapeDeps = (def: any): string[] => {
19
+ if (!def) return [];
20
+ if (def.type === "structure" || def.type === "union") {
21
+ return Object.values(def.members ?? {}).map((m: any) => m.target);
22
+ }
23
+ if (def.type === "list") return [def.member.target];
24
+ if (def.type === "map") {
25
+ return def.key ? [def.key.target, def.value.target] : [def.value.target];
26
+ }
27
+ return [];
28
+ };
29
+
30
+ /**
31
+ * Every shape reachable from `roots` through `deps`, excluding prelude
32
+ * shapes and ids missing from the map.
33
+ */
34
+ export const reachableFrom = (
35
+ shapes: ShapeMap,
36
+ roots: Iterable<string>,
37
+ deps: (def: any) => string[] = shapeDeps,
38
+ ): Set<string> => {
39
+ const reachable = new Set<string>();
40
+ const visit = (id: string) => {
41
+ if (isPrelude(id) || reachable.has(id) || !shapes[id]) return;
42
+ reachable.add(id);
43
+ for (const dep of deps(shapes[id])) visit(dep);
44
+ };
45
+ for (const r of roots) visit(r);
46
+ return reachable;
47
+ };
48
+
49
+ /**
50
+ * Dependencies-first topological order over `ids`. Cycles are tolerated:
51
+ * a back-edge is simply skipped, so mutually-recursive shapes come out in
52
+ * discovery order and emitters break the cycle with `S.suspend` at
53
+ * forward references (see {@link forwardRef}).
54
+ */
55
+ export const topoOrder = (
56
+ shapes: ShapeMap,
57
+ ids: Iterable<string>,
58
+ deps: (def: any) => string[] = shapeDeps,
59
+ ): string[] => {
60
+ const order: string[] = [];
61
+ const done = new Set<string>();
62
+ const stack = new Set<string>();
63
+ const walk = (id: string) => {
64
+ if (done.has(id) || stack.has(id) || isPrelude(id) || !shapes[id]) return;
65
+ stack.add(id);
66
+ for (const dep of deps(shapes[id])) walk(dep);
67
+ stack.delete(id);
68
+ done.add(id);
69
+ order.push(id);
70
+ };
71
+ for (const id of ids) walk(id);
72
+ return order;
73
+ };
74
+
75
+ /** Position index for {@link forwardRef} decisions. */
76
+ export const orderIndex = (order: readonly string[]): Map<string, number> => {
77
+ const indexOf = new Map<string, number>();
78
+ order.forEach((id, i) => indexOf.set(id, i));
79
+ return indexOf;
80
+ };
81
+
82
+ /**
83
+ * Whether a reference from the shape at `selfIdx` to `target` points
84
+ * forward in emission order (including into a cycle) and therefore needs
85
+ * `S.suspend(() => …)` to defer evaluation.
86
+ */
87
+ export const isForwardRef = (
88
+ indexOf: ReadonlyMap<string, number>,
89
+ target: string,
90
+ selfIdx: number,
91
+ ): boolean => {
92
+ const ti = indexOf.get(target);
93
+ return ti !== undefined && ti > selfIdx;
94
+ };
95
+
96
+ /**
97
+ * All shape ids that participate in a dependency cycle (Tarjan SCC —
98
+ * components of size > 1, plus direct self-loops). Used by generators
99
+ * that must know cyclicity independent of emission position (e.g. to add
100
+ * an explicit `S.Schema<T>` return type on suspended thunks and break
101
+ * circular type inference).
102
+ */
103
+ export const cyclicShapeIds = (
104
+ shapes: ShapeMap,
105
+ ids: Iterable<string>,
106
+ deps: (def: any) => string[] = shapeDeps,
107
+ ): Set<string> => {
108
+ const idSet = new Set(ids);
109
+ const index = new Map<string, number>();
110
+ const low = new Map<string, number>();
111
+ const onStack = new Set<string>();
112
+ const stack: string[] = [];
113
+ const cyclic = new Set<string>();
114
+ let counter = 0;
115
+
116
+ const strongConnect = (v: string) => {
117
+ index.set(v, counter);
118
+ low.set(v, counter);
119
+ counter++;
120
+ stack.push(v);
121
+ onStack.add(v);
122
+
123
+ for (const w of deps(shapes[v])) {
124
+ if (!idSet.has(w) || !shapes[w]) continue;
125
+ if (!index.has(w)) {
126
+ strongConnect(w);
127
+ low.set(v, Math.min(low.get(v)!, low.get(w)!));
128
+ } else if (onStack.has(w)) {
129
+ low.set(v, Math.min(low.get(v)!, index.get(w)!));
130
+ }
131
+ }
132
+
133
+ if (low.get(v) === index.get(v)) {
134
+ const component: string[] = [];
135
+ let w: string;
136
+ do {
137
+ w = stack.pop()!;
138
+ onStack.delete(w);
139
+ component.push(w);
140
+ } while (w !== v);
141
+ if (component.length > 1 || deps(shapes[v]).some((d) => d === v)) {
142
+ for (const c of component) cyclic.add(c);
143
+ }
144
+ }
145
+ };
146
+
147
+ for (const id of idSet) {
148
+ if (!index.has(id) && shapes[id]) strongConnect(id);
149
+ }
150
+ return cyclic;
151
+ };
@@ -0,0 +1,386 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { buildSchema, introspectionFromSchema, parse, validate } from "graphql";
3
+ import { compile } from "../graphql.ts";
4
+ import { fixtureSDL } from "../graphql.fixture.ts";
5
+ import { applyOperation } from "../json-patch.ts";
6
+ import {
7
+ convertGraphQLClient,
8
+ generateGraphQLClient,
9
+ graphqlTypeString,
10
+ validateGraphQLModel,
11
+ } from "./graphql-client.ts";
12
+
13
+ const named = (name: string, kind = "OBJECT") => ({ kind, name });
14
+ const required = (ofType: object) => ({ kind: "NON_NULL", ofType });
15
+ const list = (ofType: object) => ({ kind: "LIST", ofType });
16
+ const field = (name: string, type: object, args: object[] = []) => ({
17
+ name,
18
+ type,
19
+ args,
20
+ });
21
+ const fixture = {
22
+ queryType: { name: "Query" },
23
+ mutationType: { name: "Mutation" },
24
+ types: [
25
+ { kind: "SCALAR", name: "String" },
26
+ { kind: "SCALAR", name: "Int" },
27
+ { kind: "SCALAR", name: "JSON" },
28
+ {
29
+ kind: "ENUM",
30
+ name: "Status",
31
+ enumValues: [{ name: "ACTIVE" }, { name: "OLD", isDeprecated: true }],
32
+ },
33
+ {
34
+ kind: "INPUT_OBJECT",
35
+ name: "Filter",
36
+ inputFields: [
37
+ {
38
+ name: "status",
39
+ type: named("Status", "ENUM"),
40
+ defaultValue: "ACTIVE",
41
+ },
42
+ ],
43
+ },
44
+ {
45
+ kind: "OBJECT",
46
+ name: "Query",
47
+ fields: [
48
+ field("project", required(named("Project")), [
49
+ { name: "id", type: required(named("String", "SCALAR")) },
50
+ ]),
51
+ field("search", list(named("Search", "UNION"))),
52
+ ],
53
+ },
54
+ {
55
+ kind: "OBJECT",
56
+ name: "Mutation",
57
+ fields: [
58
+ field("projectCreate", named("Project"), [
59
+ { name: "filter", type: named("Filter", "INPUT_OBJECT") },
60
+ ]),
61
+ ],
62
+ },
63
+ {
64
+ kind: "OBJECT",
65
+ name: "Project",
66
+ interfaces: [named("Node", "INTERFACE")],
67
+ fields: [
68
+ field("id", required(named("String", "SCALAR"))),
69
+ field("children", required(list(required(named("Project")))), [
70
+ {
71
+ name: "first",
72
+ type: required(named("Int", "SCALAR")),
73
+ defaultValue: "20",
74
+ },
75
+ { name: "filter", type: named("Filter", "INPUT_OBJECT") },
76
+ ]),
77
+ {
78
+ ...field("legacy", named("String", "SCALAR")),
79
+ isDeprecated: true,
80
+ deprecationReason: "Use id",
81
+ },
82
+ ],
83
+ },
84
+ {
85
+ kind: "OBJECT",
86
+ name: "Service",
87
+ fields: [field("id", required(named("String", "SCALAR")))],
88
+ },
89
+ {
90
+ kind: "INTERFACE",
91
+ name: "Node",
92
+ fields: [field("id", required(named("String", "SCALAR")))],
93
+ possibleTypes: [named("Project")],
94
+ },
95
+ {
96
+ kind: "UNION",
97
+ name: "Search",
98
+ possibleTypes: [named("Project"), named("Service")],
99
+ },
100
+ ],
101
+ };
102
+
103
+ const options = {
104
+ transportImport: "./transport.ts",
105
+ requirementsImport: "./transport.ts",
106
+ requirementsType: "Requirements",
107
+ operationAliases: { createProject: "projectCreate" },
108
+ };
109
+
110
+ describe("GraphQL-native compiler", () => {
111
+ test("root helpers allocate safe unique names without hiding schema operations", () => {
112
+ const source = buildSchema(`
113
+ type Query {
114
+ query: String report: String schema: String transport: String G: String
115
+ catchTags: String delete: String Scalars: String Selection: String
116
+ same: String mutationSame: String
117
+ }
118
+ type Mutation { same: String }
119
+ `);
120
+ const model = convertGraphQLClient(introspectionFromSchema(source));
121
+ const output = generateGraphQLClient(model, {
122
+ ...options,
123
+ operationAliases: {
124
+ default: "delete",
125
+ deleteField: "delete",
126
+ transport: "report",
127
+ readReport: "report",
128
+ },
129
+ });
130
+ expect(output).toContain(
131
+ 'export const queryQuery = client.operation("query", "query");',
132
+ );
133
+ expect(output).toContain(
134
+ 'export const queryReport = client.operation("query", "report");',
135
+ );
136
+ expect(output).toContain(
137
+ 'export const queryTransport = client.operation("query", "transport");',
138
+ );
139
+ expect(output).toContain(
140
+ 'export const queryDelete = client.operation("query", "delete");',
141
+ );
142
+ expect(output).toContain(
143
+ 'export const queryScalars = client.operation("query", "Scalars");',
144
+ );
145
+ expect(output).toContain(
146
+ 'export const mutationSame2 = client.operation("mutation", "same");',
147
+ );
148
+ expect(output).toContain("export const deleteField = queryDelete;");
149
+ expect(output).toContain("export const readReport = queryReport;");
150
+ expect(output).not.toContain("export const default =");
151
+ expect(
152
+ [...output.matchAll(/export const ([A-Za-z_$][\w$]*) =/g)].map(
153
+ (match) => match[1],
154
+ ),
155
+ ).toEqual(expect.arrayContaining(["query", "report", "queryCatchTags"]));
156
+ const declarations = [
157
+ ...output.matchAll(/export (?:const|class|type) ([A-Za-z_$][\w$]*)/g),
158
+ ].map((match) => match[1]);
159
+ expect(new Set(declarations).size).toBe(declarations.length);
160
+ expect(output.match(/client\.operation\(/g)).toHaveLength(12);
161
+ expect(() =>
162
+ new Bun.Transpiler({ loader: "ts" }).transformSync(output),
163
+ ).not.toThrow();
164
+ expect(
165
+ generateGraphQLClient(model, {
166
+ ...options,
167
+ operationAliases: {
168
+ default: "delete",
169
+ deleteField: "delete",
170
+ transport: "report",
171
+ readReport: "report",
172
+ },
173
+ }),
174
+ ).toBe(output);
175
+ });
176
+
177
+ test("schema type names and patched error tags cannot shadow generated support symbols", () => {
178
+ const model = convertGraphQLClient(
179
+ introspectionFromSchema(
180
+ buildSchema(`
181
+ type Query { data: Schema }
182
+ type Schema { selection: Selection }
183
+ type Selection { id: ID! }
184
+ `),
185
+ ),
186
+ );
187
+ for (const tag of ["Schema", "catchTags", "class"]) {
188
+ applyOperation(model, {
189
+ op: "add",
190
+ path: `/errors/${tag}`,
191
+ value: { matchers: [{ code: tag }] },
192
+ });
193
+ applyOperation(model, {
194
+ op: "add",
195
+ path: "/types/Query/fields/data/errors/-",
196
+ value: tag,
197
+ });
198
+ }
199
+ const output = generateGraphQLClient(model, options);
200
+ expect(output).toContain(
201
+ 'export class SchemaError extends S.TaggedError<SchemaError>()("Schema", G.errorFields)',
202
+ );
203
+ expect(output).toContain(
204
+ 'export class catchTagsError extends S.TaggedError<catchTagsError>()("catchTags", G.errorFields)',
205
+ );
206
+ expect(output).toContain(
207
+ 'export class classError extends S.TaggedError<classError>()("class", G.errorFields)',
208
+ );
209
+ expect(output).toContain('"Schema": SchemaError;');
210
+ expect(output).toContain('"Schema": {');
211
+ expect(output).toContain('"Selection": {');
212
+ expect(() =>
213
+ new Bun.Transpiler({ loader: "ts" }).transformSync(output),
214
+ ).not.toThrow();
215
+ });
216
+
217
+ test("real introspection round-trips abstract fragments, recursive selections and defaulted required arguments", () => {
218
+ const schema = buildSchema(
219
+ fixtureSDL.replaceAll("first: Int = 20", "first: Int! = 20"),
220
+ );
221
+ const model = convertGraphQLClient(introspectionFromSchema(schema));
222
+ const compiled = compile(model, "query", {
223
+ node: {
224
+ where: { id: "node-id" },
225
+ select: {
226
+ id: true,
227
+ __typename: true,
228
+ __on: {
229
+ Project: {
230
+ services: { select: { edges: { node: { id: true } } } },
231
+ },
232
+ Service: { status: true, project: { parent: { name: true } } },
233
+ },
234
+ },
235
+ },
236
+ search: {
237
+ where: { text: "query" },
238
+ select: {
239
+ __typename: true,
240
+ __on: { Project: { name: true }, Service: { status: true } },
241
+ },
242
+ },
243
+ });
244
+ expect(validate(schema, parse(compiled.query))).toEqual([]);
245
+ expect(compiled.query).not.toContain("first:");
246
+ expect(model.types.Project!.fields!.services!.args.first).toEqual({
247
+ type: "Int!",
248
+ defaultValue: "20",
249
+ });
250
+ expect(model.types.ProjectFilter!.inputFields!.statuses).toEqual({
251
+ type: "[Status!]",
252
+ defaultValue: "[ACTIVE]",
253
+ });
254
+ const output = generateGraphQLClient(model, options);
255
+ expect(output).toContain('"first"?: Scalars["Int"]');
256
+ expect(output).toContain('"Node": "Project" | "Service"');
257
+ expect(output).toContain('"SearchResult": "Project" | "Service"');
258
+ });
259
+
260
+ test("required input fields with defaults are omittable but other required fields stay required", () => {
261
+ const schema = buildSchema(`
262
+ input Paging { limit: Int! = 20, owner: ID!, statuses: [String!]! = [] }
263
+ type Query { names(paging: Paging!): [String!]! }
264
+ `);
265
+ const model = convertGraphQLClient(introspectionFromSchema(schema));
266
+ const output = generateGraphQLClient(model, options);
267
+ expect(output).toContain('"limit"?: Scalars["Int"]');
268
+ expect(output).toContain('"owner": Scalars["ID"]');
269
+ expect(output).toContain('"statuses"?: ReadonlyArray<Scalars["String"]>');
270
+ const compiled = compile(model, "query", {
271
+ names: { where: { paging: { owner: "account" } } },
272
+ });
273
+ expect(Object.values(compiled.variables)).toEqual([{ owner: "account" }]);
274
+ expect(validate(schema, parse(compiled.query))).toEqual([]);
275
+ expect(() =>
276
+ compile(model, "query", { names: { where: { paging: {} } } }),
277
+ ).toThrow("owner is required");
278
+ });
279
+
280
+ test("provider scalar overrides stay aligned in runtime metadata and generated scalar types", () => {
281
+ const schema = buildSchema(
282
+ `scalar DateTime scalar JSON type Query { date: DateTime! blob: JSON! }`,
283
+ );
284
+ const model = convertGraphQLClient(introspectionFromSchema(schema), {
285
+ scalars: { DateTime: "string" },
286
+ });
287
+ const output = generateGraphQLClient(model, options);
288
+ expect(model.types.DateTime!.scalar).toBe("string");
289
+ expect(model.types.JSON!.scalar).toBe("unknown");
290
+ expect(output).toContain('"DateTime": string;');
291
+ expect(output).toContain('"JSON": unknown;');
292
+ expect(
293
+ validate(
294
+ schema,
295
+ parse(compile(model, "query", { date: true, blob: true }).query),
296
+ ),
297
+ ).toEqual([]);
298
+ });
299
+
300
+ test("preserves nested required arguments, defaults, recursion, union/interface members and deprecated fields", () => {
301
+ const model = convertGraphQLClient({ data: { __schema: fixture } });
302
+ expect(model.types.Project!.fields!.children).toEqual({
303
+ type: "[Project!]!",
304
+ args: {
305
+ first: { type: "Int!", defaultValue: "20" },
306
+ filter: { type: "Filter" },
307
+ },
308
+ errors: [],
309
+ });
310
+ expect(model.types.Project!.interfaces).toEqual(["Node"]);
311
+ expect(model.types.Node!.possibleTypes).toEqual(["Project"]);
312
+ expect(model.types.Search!.possibleTypes).toEqual(["Project", "Service"]);
313
+ expect(model.types.Project!.fields!.legacy!.deprecated).toBe("Use id");
314
+ expect(model.types.Status!.enumValues).toEqual(["ACTIVE", "OLD"]);
315
+ expect(model.types.Filter!.inputFields!.status!.defaultValue).toBe(
316
+ "ACTIVE",
317
+ );
318
+ expect(model.types.JSON!.scalar).toBe("unknown");
319
+ validateGraphQLModel(model);
320
+ });
321
+
322
+ test("field errors patch the one model used by emitted type unions and runtime classification", () => {
323
+ const model = convertGraphQLClient(fixture);
324
+ applyOperation(model, {
325
+ op: "add",
326
+ path: "/errors/ChildrenUnavailable",
327
+ value: {
328
+ category: "server",
329
+ matchers: [{ code: "CHILDREN_UNAVAILABLE" }],
330
+ },
331
+ });
332
+ applyOperation(model, {
333
+ op: "add",
334
+ path: "/types/Project/fields/children/errors/-",
335
+ value: "ChildrenUnavailable",
336
+ });
337
+ const output = generateGraphQLClient(model, options);
338
+ expect(output).toContain(
339
+ '"children": G.Field<{ "first"?: Scalars["Int"]; "filter"?: Inputs["Filter"] | null }, "[Project!]!", "ChildrenUnavailable">;',
340
+ );
341
+ expect(output).toContain(
342
+ 'export class ChildrenUnavailable extends S.TaggedError<ChildrenUnavailable>()("ChildrenUnavailable", G.errorFields).pipe(Category.withServerError)',
343
+ );
344
+ expect(output).toContain('"errors":["ChildrenUnavailable"]');
345
+ expect(output).toContain('"matchers":[{"code":"CHILDREN_UNAVAILABLE"}]');
346
+ expect(output).toContain(
347
+ 'export const projectCreate = client.operation("mutation", "projectCreate");',
348
+ );
349
+ expect(output).toContain("export const createProject = projectCreate;");
350
+ });
351
+
352
+ test("generation has no fixed depth projection or artificial output optionality", () => {
353
+ const output = generateGraphQLClient(
354
+ convertGraphQLClient(fixture),
355
+ options,
356
+ );
357
+ expect(output).toContain('"Project": {');
358
+ expect(output).toContain('"Search": "Project" | "Service";');
359
+ expect(output).toContain('"Node": "Project";');
360
+ expect(output).toContain('"id": G.Field<{ }, "String!", never>;');
361
+ expect(output).not.toContain('"id"?: G.Field');
362
+ expect(output).not.toContain("maxDepth");
363
+ });
364
+
365
+ test("unknown coordinates, incomplete references and stale patches fail immediately", () => {
366
+ const model = convertGraphQLClient(fixture);
367
+ applyOperation(model, {
368
+ op: "add",
369
+ path: "/types/Project/fields/children/errors/-",
370
+ value: "MissingError",
371
+ });
372
+ expect(() => validateGraphQLModel(model)).toThrow(
373
+ "Project.children: unknown GraphQL error MissingError",
374
+ );
375
+ expect(() => graphqlTypeString({ kind: "NON_NULL" })).toThrow(
376
+ "Incomplete GraphQL NON_NULL",
377
+ );
378
+ expect(() =>
379
+ applyOperation(model, {
380
+ op: "replace",
381
+ path: "/types/Project/fields/removed/errors",
382
+ value: [],
383
+ }),
384
+ ).toThrow();
385
+ });
386
+ });