@distilled.cloud/core 1.0.0-rc.1 → 1.0.0-rc.11

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 (148) hide show
  1. package/LICENSE +201 -0
  2. package/lib/api.d.ts.map +1 -1
  3. package/lib/api.js +16 -4
  4. package/lib/api.js.map +1 -1
  5. package/lib/category.d.ts +4 -4
  6. package/lib/category.js +4 -4
  7. package/lib/codegen/boolean-string-enums.d.ts +36 -0
  8. package/lib/codegen/boolean-string-enums.d.ts.map +1 -0
  9. package/lib/codegen/boolean-string-enums.js +94 -0
  10. package/lib/codegen/boolean-string-enums.js.map +1 -0
  11. package/lib/codegen/boolean-string-enums.test.d.ts +2 -0
  12. package/lib/codegen/boolean-string-enums.test.d.ts.map +1 -0
  13. package/lib/codegen/boolean-string-enums.test.js +147 -0
  14. package/lib/codegen/boolean-string-enums.test.js.map +1 -0
  15. package/lib/codegen/cli.d.ts +56 -6
  16. package/lib/codegen/cli.d.ts.map +1 -1
  17. package/lib/codegen/cli.js +59 -75
  18. package/lib/codegen/cli.js.map +1 -1
  19. package/lib/codegen/emit.d.ts +3 -7
  20. package/lib/codegen/emit.d.ts.map +1 -1
  21. package/lib/codegen/emit.js +6 -10
  22. package/lib/codegen/emit.js.map +1 -1
  23. package/lib/codegen/generator.d.ts.map +1 -1
  24. package/lib/codegen/generator.js +142 -20
  25. package/lib/codegen/generator.js.map +1 -1
  26. package/lib/codegen/graphql-client.d.ts +62 -0
  27. package/lib/codegen/graphql-client.d.ts.map +1 -0
  28. package/lib/codegen/graphql-client.js +294 -0
  29. package/lib/codegen/graphql-client.js.map +1 -0
  30. package/lib/codegen/graphql-client.test.d.ts +2 -0
  31. package/lib/codegen/graphql-client.test.d.ts.map +1 -0
  32. package/lib/codegen/graphql-client.test.js +311 -0
  33. package/lib/codegen/graphql-client.test.js.map +1 -0
  34. package/lib/codegen/graphql.d.ts +207 -0
  35. package/lib/codegen/graphql.d.ts.map +1 -0
  36. package/lib/codegen/graphql.js +799 -0
  37. package/lib/codegen/graphql.js.map +1 -0
  38. package/lib/codegen/openapi-cli.d.ts +17 -3
  39. package/lib/codegen/openapi-cli.d.ts.map +1 -1
  40. package/lib/codegen/openapi-cli.js +50 -48
  41. package/lib/codegen/openapi-cli.js.map +1 -1
  42. package/lib/codegen/openapi.d.ts +69 -6
  43. package/lib/codegen/openapi.d.ts.map +1 -1
  44. package/lib/codegen/openapi.js +173 -16
  45. package/lib/codegen/openapi.js.map +1 -1
  46. package/lib/codegen/patches.d.ts +65 -0
  47. package/lib/codegen/patches.d.ts.map +1 -0
  48. package/lib/codegen/patches.js +236 -0
  49. package/lib/codegen/patches.js.map +1 -0
  50. package/lib/codegen/patches.test.d.ts +2 -0
  51. package/lib/codegen/patches.test.d.ts.map +1 -0
  52. package/lib/codegen/patches.test.js +105 -0
  53. package/lib/codegen/patches.test.js.map +1 -0
  54. package/lib/codegen/proto.d.ts +121 -0
  55. package/lib/codegen/proto.d.ts.map +1 -0
  56. package/lib/codegen/proto.js +962 -0
  57. package/lib/codegen/proto.js.map +1 -0
  58. package/lib/codegen/rewrite-operation-ids.d.ts +131 -0
  59. package/lib/codegen/rewrite-operation-ids.d.ts.map +1 -0
  60. package/lib/codegen/rewrite-operation-ids.js +1079 -0
  61. package/lib/codegen/rewrite-operation-ids.js.map +1 -0
  62. package/lib/codegen/rewrite-operation-ids.test.d.ts +2 -0
  63. package/lib/codegen/rewrite-operation-ids.test.d.ts.map +1 -0
  64. package/lib/codegen/rewrite-operation-ids.test.js +533 -0
  65. package/lib/codegen/rewrite-operation-ids.test.js.map +1 -0
  66. package/lib/codegen/spec-path.d.ts +16 -0
  67. package/lib/codegen/spec-path.d.ts.map +1 -0
  68. package/lib/codegen/spec-path.js +101 -0
  69. package/lib/codegen/spec-path.js.map +1 -0
  70. package/lib/errors.d.ts.map +1 -1
  71. package/lib/errors.js +17 -13
  72. package/lib/errors.js.map +1 -1
  73. package/lib/graphql.d.ts +284 -0
  74. package/lib/graphql.d.ts.map +1 -0
  75. package/lib/graphql.fixture.d.ts +249 -0
  76. package/lib/graphql.fixture.d.ts.map +1 -0
  77. package/lib/graphql.fixture.js +240 -0
  78. package/lib/graphql.fixture.js.map +1 -0
  79. package/lib/graphql.js +718 -0
  80. package/lib/graphql.js.map +1 -0
  81. package/lib/graphql.test.d.ts +2 -0
  82. package/lib/graphql.test.d.ts.map +1 -0
  83. package/lib/graphql.test.js +780 -0
  84. package/lib/graphql.test.js.map +1 -0
  85. package/lib/graphql.types.d.ts +2 -0
  86. package/lib/graphql.types.d.ts.map +1 -0
  87. package/lib/graphql.types.js +45 -0
  88. package/lib/graphql.types.js.map +1 -0
  89. package/lib/json-patch.d.ts +18 -11
  90. package/lib/json-patch.d.ts.map +1 -1
  91. package/lib/json-patch.js +63 -25
  92. package/lib/json-patch.js.map +1 -1
  93. package/lib/pagination.d.ts +43 -3
  94. package/lib/pagination.d.ts.map +1 -1
  95. package/lib/pagination.js +91 -5
  96. package/lib/pagination.js.map +1 -1
  97. package/lib/protocol-http.d.ts.map +1 -1
  98. package/lib/protocol-http.js +62 -26
  99. package/lib/protocol-http.js.map +1 -1
  100. package/lib/protocol-http.test.d.ts +2 -0
  101. package/lib/protocol-http.test.d.ts.map +1 -0
  102. package/lib/protocol-http.test.js +88 -0
  103. package/lib/protocol-http.test.js.map +1 -0
  104. package/lib/protocol-rest.d.ts +12 -2
  105. package/lib/protocol-rest.d.ts.map +1 -1
  106. package/lib/protocol-rest.js +17 -3
  107. package/lib/protocol-rest.js.map +1 -1
  108. package/lib/retry.js +1 -1
  109. package/lib/schema.d.ts +1 -1
  110. package/lib/schema.d.ts.map +1 -1
  111. package/lib/schema.js +1 -1
  112. package/lib/schema.js.map +1 -1
  113. package/lib/trait.d.ts +27 -3
  114. package/lib/trait.d.ts.map +1 -1
  115. package/lib/trait.js +17 -1
  116. package/lib/trait.js.map +1 -1
  117. package/package.json +13 -10
  118. package/src/api.ts +18 -4
  119. package/src/category.ts +4 -4
  120. package/src/codegen/boolean-string-enums.test.ts +168 -0
  121. package/src/codegen/boolean-string-enums.ts +106 -0
  122. package/src/codegen/cli.ts +127 -110
  123. package/src/codegen/emit.ts +6 -10
  124. package/src/codegen/generator.ts +151 -21
  125. package/src/codegen/graphql-client.test.ts +386 -0
  126. package/src/codegen/graphql-client.ts +419 -0
  127. package/src/codegen/graphql.ts +1217 -0
  128. package/src/codegen/openapi-cli.ts +75 -59
  129. package/src/codegen/openapi.ts +255 -16
  130. package/src/codegen/patches.test.ts +130 -0
  131. package/src/codegen/patches.ts +291 -0
  132. package/src/codegen/proto.ts +1128 -0
  133. package/src/codegen/rewrite-operation-ids.test.ts +563 -0
  134. package/src/codegen/rewrite-operation-ids.ts +1206 -0
  135. package/src/codegen/spec-path.ts +115 -0
  136. package/src/errors.ts +20 -25
  137. package/src/graphql.fixture.ts +371 -0
  138. package/src/graphql.test.ts +974 -0
  139. package/src/graphql.ts +1321 -0
  140. package/src/graphql.types.ts +185 -0
  141. package/src/json-patch.ts +82 -25
  142. package/src/pagination.ts +134 -7
  143. package/src/protocol-http.test.ts +107 -0
  144. package/src/protocol-http.ts +67 -31
  145. package/src/protocol-rest.ts +28 -4
  146. package/src/retry.ts +1 -1
  147. package/src/schema.ts +2 -2
  148. package/src/trait.ts +39 -3
package/lib/graphql.js ADDED
@@ -0,0 +1,718 @@
1
+ /** GraphQL selections, typed failures and Effect execution for generated SDKs. */
2
+ import * as Data from "effect/Data";
3
+ import * as Effect from "effect/Effect";
4
+ import * as Option from "effect/Option";
5
+ import * as S from "effect/Schema";
6
+ import * as Schedule from "effect/Schedule";
7
+ import * as Stream from "effect/Stream";
8
+ import { Kind, parseType, print, } from "graphql";
9
+ export const errorFields = {
10
+ message: S.String,
11
+ code: S.optional(S.String),
12
+ path: S.optional(S.Array(S.Union([S.String, S.Number]))),
13
+ locations: S.optional(S.Array(S.Struct({ line: S.Number, column: S.Number }))),
14
+ extensions: S.optional(S.Unknown),
15
+ traceId: S.optional(S.String),
16
+ status: S.optional(S.Number),
17
+ retryAfter: S.optional(S.Number),
18
+ };
19
+ export class UnknownGraphQLError extends S.TaggedError()("UnknownGraphQLError", {
20
+ ...errorFields,
21
+ coordinate: S.optional(S.String),
22
+ }) {
23
+ }
24
+ /** All errors from one GraphQL execution, with the original partial response. */
25
+ export class GraphQLFailure extends Data.TaggedError("GraphQLFailure") {
26
+ get message() {
27
+ return this.errors
28
+ .map((e) => `${e._tag}${e.path?.length ? ` at ${e.path.join(".")}` : ""}: ${e.message}`)
29
+ .join("; ");
30
+ }
31
+ }
32
+ export class GraphQLTransportError extends Data.TaggedError("GraphQLTransportError") {
33
+ }
34
+ export class GraphQLDecodeError extends Data.TaggedError("GraphQLDecodeError") {
35
+ }
36
+ export class GraphQLRequestError extends Data.TaggedError("GraphQLRequestError") {
37
+ }
38
+ /** True only when every issue has an allowed tag; mixed failures are never hidden. */
39
+ export const isErrorTag = (error, tags) => {
40
+ const allowed = typeof tags === "string" ? [tags] : tags;
41
+ return (error instanceof GraphQLFailure &&
42
+ error.errors.length > 0 &&
43
+ error.errors.every((issue) => allowed.includes(issue._tag)));
44
+ };
45
+ /** Recover only an aggregate whose every issue matches. The original aggregate is also supplied. */
46
+ export const catchTags = (tags, handler) => (self) => self.pipe(Effect.catch((error) => isErrorTag(error, tags)
47
+ ? handler(error.errors[0], error)
48
+ : Effect.fail(error)));
49
+ const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
50
+ const named = (ref) => ref.replace(/[\[\]!]/g, "");
51
+ const nameNode = (value) => ({ kind: Kind.NAME, value });
52
+ const requestError = (message) => {
53
+ throw new GraphQLRequestError({ message });
54
+ };
55
+ const assertName = (name) => {
56
+ if (!/^[_A-Za-z][_0-9A-Za-z]*$/.test(name))
57
+ requestError(`Invalid GraphQL name ${name}`);
58
+ };
59
+ /** Validate and encode inputs recursively. Undefined omits; null stays null. */
60
+ const encodeInput = (model, ref, value, at) => {
61
+ if (value === undefined)
62
+ return undefined;
63
+ if (value === null) {
64
+ if (ref.endsWith("!"))
65
+ requestError(`${at} must not be null`);
66
+ return null;
67
+ }
68
+ const base = ref.endsWith("!") ? ref.slice(0, -1) : ref;
69
+ if (base.startsWith("[")) {
70
+ if (!Array.isArray(value))
71
+ requestError(`${at} must be an array`);
72
+ return value.map((v, i) => {
73
+ if (v === undefined)
74
+ requestError(`${at}[${i}] must not be undefined`);
75
+ return encodeInput(model, base.slice(1, -1), v, `${at}[${i}]`);
76
+ });
77
+ }
78
+ const type = model.types[base];
79
+ if (!type)
80
+ requestError(`${at}: unknown input type ${base}`);
81
+ if (type.kind === "INPUT_OBJECT") {
82
+ if (!object(value))
83
+ requestError(`${at} must be an input object`);
84
+ const input = value;
85
+ const result = {};
86
+ for (const key of Object.keys(input))
87
+ if (!type.inputFields?.[key])
88
+ requestError(`${at}: unknown input field ${key}`);
89
+ for (const [key, field] of Object.entries(type.inputFields ?? {})) {
90
+ if (input[key] === undefined) {
91
+ if (field.type.endsWith("!") && field.defaultValue === undefined)
92
+ requestError(`${at}.${key} is required`);
93
+ }
94
+ else
95
+ result[key] = encodeInput(model, field.type, input[key], `${at}.${key}`);
96
+ }
97
+ return result;
98
+ }
99
+ if (type.kind === "ENUM") {
100
+ if (typeof value !== "string" || !type.enumValues?.includes(value))
101
+ requestError(`${at}: invalid ${base}`);
102
+ }
103
+ else if (type.scalar === "string" || base === "String" || base === "ID") {
104
+ if (typeof value !== "string")
105
+ requestError(`${at} must be a string`);
106
+ }
107
+ else if (type.scalar === "number" || base === "Int" || base === "Float") {
108
+ if (typeof value !== "number" || !Number.isFinite(value))
109
+ requestError(`${at} must be a finite number`);
110
+ if (base === "Int" &&
111
+ (!Number.isInteger(value) ||
112
+ value < -2147483648 ||
113
+ value > 2147483647))
114
+ requestError(`${at} must be a GraphQL Int`);
115
+ }
116
+ else if (type.scalar === "boolean" || base === "Boolean") {
117
+ if (typeof value !== "boolean")
118
+ requestError(`${at} must be a boolean`);
119
+ }
120
+ if (type.scalar === "string | number" &&
121
+ typeof value !== "string" &&
122
+ typeof value !== "number")
123
+ requestError(`${at} must be a string or number`);
124
+ if (base === "BigInt" &&
125
+ ((typeof value === "number" && !Number.isSafeInteger(value)) ||
126
+ (typeof value === "string" && !/^-?\d+$/.test(value))))
127
+ requestError(`${at} must be a safe integer or an integer string`);
128
+ // JSON custom scalars must still be transportable JSON; preserve scalar contents.
129
+ try {
130
+ return JSON.parse(JSON.stringify(value));
131
+ }
132
+ catch {
133
+ return requestError(`${at} is not a JSON-encodable scalar`);
134
+ }
135
+ };
136
+ /** Compile one selection to a GraphQL AST, variables, and an error/decoder path map. */
137
+ export const compile = (model, kind, selection, operationName = "Distilled") => {
138
+ const rootType = kind === "query" ? model.queryType : model.mutationType;
139
+ if (!rootType)
140
+ return requestError(`Schema has no ${kind} root`);
141
+ assertName(operationName);
142
+ const variables = {};
143
+ const definitions = [];
144
+ const variable = (ref, value) => {
145
+ const id = `v${definitions.length}`;
146
+ variables[id] = value;
147
+ definitions.push({
148
+ kind: Kind.VARIABLE_DEFINITION,
149
+ variable: { kind: Kind.VARIABLE, name: nameNode(id) },
150
+ type: parseType(ref),
151
+ });
152
+ return { kind: Kind.VARIABLE, name: nameNode(id) };
153
+ };
154
+ const walk = (parent, input) => {
155
+ if (!object(input))
156
+ return requestError(`${parent} requires an object selection`);
157
+ const type = model.types[parent];
158
+ if (!type)
159
+ return requestError(`Unknown GraphQL type ${parent}`);
160
+ const nodes = [];
161
+ const positions = [];
162
+ const addField = (fieldName, selected, alias) => {
163
+ if (selected === false || selected === undefined)
164
+ return;
165
+ assertName(fieldName);
166
+ if (alias)
167
+ assertName(alias);
168
+ const key = alias ?? fieldName;
169
+ if (positions.some((p) => p.key === key))
170
+ return requestError(`Duplicate response key ${parent}.${key}; use distinct aliases`);
171
+ const field = fieldName === "__typename"
172
+ ? { type: "String!", args: {}, errors: [] }
173
+ : type.fields?.[fieldName];
174
+ if (!field)
175
+ return requestError(`Unknown field ${parent}.${fieldName}`);
176
+ const wrapper = object(selected) &&
177
+ ("where" in selected ||
178
+ "select" in selected ||
179
+ "$include" in selected ||
180
+ "$skip" in selected)
181
+ ? selected
182
+ : undefined;
183
+ const args = wrapper?.where ?? {};
184
+ if (!object(args))
185
+ return requestError(`${parent}.${fieldName}.where must be an object`);
186
+ for (const arg of Object.keys(args))
187
+ if (!field.args[arg])
188
+ return requestError(`Unknown argument ${parent}.${fieldName}(${arg})`);
189
+ const arguments_ = [];
190
+ for (const [arg, definition] of Object.entries(field.args)) {
191
+ if (args[arg] === undefined) {
192
+ if (definition.type.endsWith("!") &&
193
+ definition.defaultValue === undefined)
194
+ return requestError(`${parent}.${fieldName}(${arg}) is required`);
195
+ }
196
+ else
197
+ arguments_.push({
198
+ kind: Kind.ARGUMENT,
199
+ name: nameNode(arg),
200
+ value: variable(definition.type, encodeInput(model, definition.type, args[arg], `${parent}.${fieldName}(${arg})`)),
201
+ });
202
+ }
203
+ const directives = [];
204
+ for (const directive of ["include", "skip"]) {
205
+ const value = wrapper?.[`$${directive}`];
206
+ if (value !== undefined) {
207
+ if (typeof value !== "boolean")
208
+ return requestError(`$${directive} must be a boolean`);
209
+ directives.push({
210
+ kind: Kind.DIRECTIVE,
211
+ name: nameNode(directive),
212
+ arguments: [
213
+ {
214
+ kind: Kind.ARGUMENT,
215
+ name: nameNode("if"),
216
+ value: variable("Boolean!", value),
217
+ },
218
+ ],
219
+ });
220
+ }
221
+ }
222
+ const outputType = model.types[named(field.type)];
223
+ const leaf = fieldName === "__typename" ||
224
+ outputType?.kind === "SCALAR" ||
225
+ outputType?.kind === "ENUM";
226
+ const sub = wrapper ? wrapper.select : selected;
227
+ let child = { nodes: [], positions: [] };
228
+ if (leaf) {
229
+ if (sub !== true && sub !== undefined)
230
+ return requestError(`${parent}.${fieldName} is a leaf and takes no selection`);
231
+ }
232
+ else
233
+ child = walk(named(field.type), sub);
234
+ nodes.push({
235
+ kind: Kind.FIELD,
236
+ name: nameNode(fieldName),
237
+ ...(alias ? { alias: nameNode(alias) } : {}),
238
+ arguments: arguments_,
239
+ directives,
240
+ ...(!leaf
241
+ ? {
242
+ selectionSet: {
243
+ kind: Kind.SELECTION_SET,
244
+ selections: child.nodes,
245
+ },
246
+ }
247
+ : {}),
248
+ });
249
+ const branches = {};
250
+ // Branch metadata is attached to sentinel positions by walk and removed from regular children.
251
+ for (const p of child.positions)
252
+ if (p.field === "__fragment")
253
+ branches[p.type] = p.children;
254
+ positions.push({
255
+ field: fieldName,
256
+ key,
257
+ coordinate: `${parent}.${fieldName}`,
258
+ type: field.type,
259
+ errors: field.errors,
260
+ children: child.positions.filter((p) => p.field !== "__fragment"),
261
+ branches,
262
+ typename: fieldName === "__typename",
263
+ conditional: wrapper?.$include === false || wrapper?.$skip === true,
264
+ });
265
+ };
266
+ for (const [key, value] of Object.entries(input)) {
267
+ if (key === "__alias") {
268
+ if (!object(value))
269
+ return requestError(`${parent}.__alias must be an object`);
270
+ for (const [alias, fields] of Object.entries(value)) {
271
+ if (!object(fields) || Object.keys(fields).length !== 1)
272
+ return requestError(`Alias ${alias} must select exactly one field`);
273
+ const [field, selected] = Object.entries(fields)[0];
274
+ addField(field, selected, alias);
275
+ }
276
+ }
277
+ else if (key === "__on") {
278
+ if (!object(value))
279
+ return requestError(`${parent}.__on must be an object`);
280
+ for (const [branch, fields] of Object.entries(value)) {
281
+ if (!type.possibleTypes?.includes(branch))
282
+ return requestError(`${branch} is not a possible type of ${parent}`);
283
+ const children = walk(branch, fields);
284
+ nodes.push({
285
+ kind: Kind.INLINE_FRAGMENT,
286
+ typeCondition: { kind: Kind.NAMED_TYPE, name: nameNode(branch) },
287
+ selectionSet: {
288
+ kind: Kind.SELECTION_SET,
289
+ selections: children.nodes,
290
+ },
291
+ });
292
+ positions.push({
293
+ field: "__fragment",
294
+ key: "",
295
+ coordinate: parent,
296
+ type: branch,
297
+ errors: [],
298
+ children: children.positions,
299
+ branches: {},
300
+ conditional: false,
301
+ });
302
+ }
303
+ }
304
+ else
305
+ addField(key, value);
306
+ }
307
+ if (type.possibleTypes?.length && !positions.some((p) => p.typename))
308
+ addField("__typename", true);
309
+ if (!nodes.length)
310
+ return requestError(`${parent} has an empty selection`);
311
+ return { nodes, positions };
312
+ };
313
+ const selected = walk(rootType, selection);
314
+ const query = print({
315
+ kind: Kind.DOCUMENT,
316
+ definitions: [
317
+ {
318
+ kind: Kind.OPERATION_DEFINITION,
319
+ operation: kind,
320
+ name: nameNode(operationName),
321
+ variableDefinitions: definitions,
322
+ selectionSet: { kind: Kind.SELECTION_SET, selections: selected.nodes },
323
+ },
324
+ ],
325
+ });
326
+ return {
327
+ query,
328
+ variables,
329
+ operationName,
330
+ kind,
331
+ rootType,
332
+ positions: selected.positions,
333
+ };
334
+ };
335
+ const decodeFailure = (message, path) => {
336
+ throw new GraphQLDecodeError({ message, path });
337
+ };
338
+ /** Validate only selected fields. Preserve the schema's nullability exactly. */
339
+ const decodeData = (model, ref, data, positions, branches, path) => {
340
+ if (data === null) {
341
+ if (ref.endsWith("!"))
342
+ return decodeFailure(`Non-null ${ref} was null`, path);
343
+ return null;
344
+ }
345
+ if (data === undefined)
346
+ return decodeFailure(`Missing selected ${ref}`, path);
347
+ const base = ref.endsWith("!") ? ref.slice(0, -1) : ref;
348
+ if (base.startsWith("[")) {
349
+ if (!Array.isArray(data))
350
+ return decodeFailure(`Expected ${ref} array`, path);
351
+ return data.map((v, i) => decodeData(model, base.slice(1, -1), v, positions, branches, [
352
+ ...path,
353
+ i,
354
+ ]));
355
+ }
356
+ const type = model.types[base];
357
+ if (!type)
358
+ return decodeFailure(`Unknown response type ${base}`, path);
359
+ if (type.kind === "SCALAR") {
360
+ const primitive = type.scalar ??
361
+ {
362
+ String: "string",
363
+ ID: "string",
364
+ Boolean: "boolean",
365
+ Int: "number",
366
+ Float: "number",
367
+ }[base];
368
+ if (primitive === "string | number" &&
369
+ typeof data !== "string" &&
370
+ typeof data !== "number")
371
+ return decodeFailure(`Expected ${base}`, path);
372
+ if (base === "BigInt" &&
373
+ ((typeof data === "number" && !Number.isSafeInteger(data)) ||
374
+ (typeof data === "string" && !/^-?\d+$/.test(data))))
375
+ return decodeFailure(`Invalid ${base}`, path);
376
+ if (["string", "number", "boolean"].includes(primitive ?? "") &&
377
+ typeof data !== primitive)
378
+ return decodeFailure(`Expected ${base}`, path);
379
+ if (typeof data === "number" &&
380
+ (!Number.isFinite(data) ||
381
+ (base === "Int" &&
382
+ (!Number.isInteger(data) || data < -2147483648 || data > 2147483647))))
383
+ return decodeFailure(`Invalid ${base}`, path);
384
+ return data;
385
+ }
386
+ if (type.kind === "ENUM") {
387
+ if (typeof data !== "string" || !type.enumValues?.includes(data))
388
+ return decodeFailure(`Invalid ${base} enum`, path);
389
+ return data;
390
+ }
391
+ if (!object(data))
392
+ return decodeFailure(`Expected ${base} object`, path);
393
+ if (type.possibleTypes?.length &&
394
+ (typeof data.__typename !== "string" ||
395
+ !type.possibleTypes.includes(data.__typename)))
396
+ return decodeFailure(`Missing or invalid ${base} __typename`, path);
397
+ const active = [
398
+ ...positions,
399
+ ...(typeof data.__typename === "string"
400
+ ? (branches[data.__typename] ?? [])
401
+ : []),
402
+ ];
403
+ const result = {};
404
+ const merged = new Map();
405
+ for (const position of active) {
406
+ if (position.conditional)
407
+ continue;
408
+ const previous = merged.get(position.key);
409
+ merged.set(position.key, previous
410
+ ? {
411
+ ...position,
412
+ children: [...previous.children, ...position.children],
413
+ branches: Object.fromEntries([
414
+ ...new Set([
415
+ ...Object.keys(previous.branches),
416
+ ...Object.keys(position.branches),
417
+ ]),
418
+ ].map((key) => [
419
+ key,
420
+ [
421
+ ...(previous.branches[key] ?? []),
422
+ ...(position.branches[key] ?? []),
423
+ ],
424
+ ])),
425
+ }
426
+ : position);
427
+ }
428
+ for (const position of merged.values()) {
429
+ if (position.conditional)
430
+ continue;
431
+ if (position.typename) {
432
+ if (typeof data[position.key] !== "string" ||
433
+ (type.kind === "OBJECT" && data[position.key] !== base))
434
+ return decodeFailure(`Invalid __typename for ${base}`, [
435
+ ...path,
436
+ position.key,
437
+ ]);
438
+ Object.defineProperty(result, position.key, {
439
+ value: data[position.key],
440
+ enumerable: true,
441
+ configurable: true,
442
+ writable: true,
443
+ });
444
+ }
445
+ else
446
+ Object.defineProperty(result, position.key, {
447
+ value: decodeData(model, position.type, data[position.key], position.children, position.branches, [...path, position.key]),
448
+ enumerable: true,
449
+ configurable: true,
450
+ writable: true,
451
+ });
452
+ }
453
+ return result;
454
+ };
455
+ const classify = (model, compiled, raw, response, classes) => {
456
+ const path = Array.isArray(raw.path)
457
+ ? raw.path
458
+ : undefined;
459
+ let candidates = compiled.positions;
460
+ let position;
461
+ let currentData = object(response.body)
462
+ ? response.body.data
463
+ : undefined;
464
+ let parents = [];
465
+ const descendants = (positions, value) => positions.flatMap((p) => [
466
+ ...p.children,
467
+ ...(object(value) && typeof value.__typename === "string"
468
+ ? (p.branches[value.__typename] ?? [])
469
+ : Object.values(p.branches).flat()),
470
+ ]);
471
+ let valid = !!path?.length;
472
+ for (const segment of path ?? []) {
473
+ if (typeof segment === "number") {
474
+ currentData = Array.isArray(currentData)
475
+ ? currentData[segment]
476
+ : undefined;
477
+ candidates = descendants(parents, currentData);
478
+ continue;
479
+ }
480
+ const matches = candidates.filter((p) => p.key === segment);
481
+ if (!matches.length) {
482
+ valid = false;
483
+ break;
484
+ }
485
+ position = {
486
+ ...matches[0],
487
+ errors: [...new Set(matches.flatMap((p) => p.errors))],
488
+ };
489
+ currentData = object(currentData) ? currentData[segment] : undefined;
490
+ parents = matches;
491
+ candidates = descendants(matches, currentData);
492
+ }
493
+ const extensions = object(raw.extensions) ? raw.extensions : {};
494
+ const code = typeof extensions.code === "string"
495
+ ? extensions.code
496
+ : typeof extensions.errorCode === "string"
497
+ ? extensions.errorCode
498
+ : undefined;
499
+ const retryHeader = response.headers["retry-after"];
500
+ const retryAfter = retryHeader && /^\d+(\.\d+)?$/.test(retryHeader)
501
+ ? Number(retryHeader)
502
+ : undefined;
503
+ const props = {
504
+ message: raw.message,
505
+ code,
506
+ path,
507
+ locations: raw.locations,
508
+ extensions: raw.extensions,
509
+ traceId: typeof extensions.traceId === "string"
510
+ ? extensions.traceId
511
+ : typeof raw.traceId === "string"
512
+ ? raw.traceId
513
+ : undefined,
514
+ status: response.status,
515
+ retryAfter,
516
+ };
517
+ const tags = [
518
+ ...model.globalErrors,
519
+ ...(valid ? (position?.errors ?? []) : []),
520
+ ];
521
+ const matches = tags.flatMap((tag) => (model.errors[tag]?.matchers ?? [])
522
+ .filter((matcher) => (matcher.code === undefined || matcher.code === code) &&
523
+ (matcher.message === undefined || matcher.message === raw.message) &&
524
+ (matcher.messageIncludes === undefined ||
525
+ raw.message.includes(matcher.messageIncludes)))
526
+ .map((matcher) => ({
527
+ tag,
528
+ score: (matcher.code ? 1 : 0) +
529
+ (matcher.message ? 4 : 0) +
530
+ (matcher.messageIncludes ? 2 : 0),
531
+ })));
532
+ matches.sort((a, b) => b.score - a.score);
533
+ const best = matches[0];
534
+ const tag = best && !matches.some((m) => m.score === best.score && m.tag !== best.tag)
535
+ ? best.tag
536
+ : undefined;
537
+ if (tag && classes[tag])
538
+ return new classes[tag](props);
539
+ return new UnknownGraphQLError({
540
+ ...props,
541
+ coordinate: valid ? position?.coordinate : undefined,
542
+ });
543
+ };
544
+ /** Construct an Effect client; credentials and HTTP are resolved by the transport per call. */
545
+ export const makeClient = (model, transport, classes) => {
546
+ const executeReport = (kind, selection) => Effect.gen(function* () {
547
+ const compiled = yield* Effect.try({
548
+ try: () => compile(model, kind, selection),
549
+ catch: (cause) => cause instanceof GraphQLRequestError
550
+ ? cause
551
+ : new GraphQLRequestError({ message: String(cause) }),
552
+ });
553
+ const response = yield* transport(compiled);
554
+ return yield* Effect.try({
555
+ try: () => {
556
+ const body = response.body;
557
+ if (!object(body))
558
+ return decodeFailure("GraphQL response must be an object", []);
559
+ if (body.errors !== undefined &&
560
+ (!Array.isArray(body.errors) || !body.errors.length))
561
+ return decodeFailure("GraphQL errors must be a nonempty array", []);
562
+ const rawErrors = body.errors ?? [];
563
+ for (const error of rawErrors) {
564
+ if (!object(error) || typeof error.message !== "string")
565
+ return decodeFailure("Invalid GraphQL error", []);
566
+ if (error.path !== undefined &&
567
+ (!Array.isArray(error.path) ||
568
+ error.path.some((p) => typeof p !== "string" &&
569
+ (typeof p !== "number" || !Number.isInteger(p) || p < 0))))
570
+ return decodeFailure("Invalid GraphQL error path", []);
571
+ }
572
+ if (!("data" in body) && !rawErrors.length) {
573
+ if (response.status >= 400)
574
+ throw new GraphQLTransportError({
575
+ message: `HTTP ${response.status} without a GraphQL response`,
576
+ status: response.status,
577
+ });
578
+ return decodeFailure("GraphQL response has neither data nor errors", []);
579
+ }
580
+ if (response.status >= 400 && !rawErrors.length)
581
+ throw new GraphQLTransportError({
582
+ message: `HTTP ${response.status} without GraphQL errors`,
583
+ status: response.status,
584
+ });
585
+ const errors = rawErrors.map((raw) => classify(model, compiled, raw, response, classes));
586
+ let data = body.data;
587
+ if (data !== undefined && data !== null)
588
+ data = decodeData(model, compiled.rootType + "!", data, compiled.positions, {}, []);
589
+ if ((data === undefined || data === null) && !errors.length)
590
+ return decodeFailure("GraphQL success has no data", []);
591
+ return {
592
+ data,
593
+ errors,
594
+ status: response.status,
595
+ extensions: body.extensions,
596
+ };
597
+ },
598
+ catch: (cause) => cause instanceof GraphQLDecodeError ||
599
+ cause instanceof GraphQLTransportError
600
+ ? cause
601
+ : new GraphQLDecodeError({
602
+ message: "Invalid GraphQL response",
603
+ cause,
604
+ }),
605
+ });
606
+ });
607
+ const retryable = (error) => error instanceof GraphQLFailure
608
+ ? error.errors.every((issue) => model.errors[issue._tag]?.retryable === true)
609
+ : error instanceof GraphQLTransportError &&
610
+ (error.status === undefined ||
611
+ error.status === 429 ||
612
+ error.status >= 500);
613
+ const execute = (kind, selection) => {
614
+ const effect = executeReport(kind, selection).pipe(Effect.flatMap((report) => report.errors.length
615
+ ? Effect.fail(new GraphQLFailure({
616
+ errors: report.errors,
617
+ data: report.data,
618
+ status: report.status,
619
+ }))
620
+ : Effect.succeed(report.data)));
621
+ return kind === "query"
622
+ ? effect.pipe(Effect.retry({
623
+ while: retryable,
624
+ times: 5,
625
+ schedule: Schedule.exponential("200 millis"),
626
+ }))
627
+ : effect;
628
+ };
629
+ const operation = (kind, name) => {
630
+ const field = model.types[kind === "query" ? model.queryType : model.mutationType]
631
+ ?.fields?.[name];
632
+ const call = (args, selection) => execute(kind, {
633
+ [name]: { where: args, select: selection ?? true },
634
+ }).pipe(Effect.map((data) => data[name]));
635
+ const pages = (args, selection) => Stream.unwrap(Effect.try({
636
+ try: () => {
637
+ const connection = field && model.types[named(field.type)];
638
+ if (kind !== "query" ||
639
+ !field?.args.after ||
640
+ !connection?.fields?.edges ||
641
+ !connection.fields.pageInfo)
642
+ return Stream.fromEffect(Effect.fail(new GraphQLRequestError({
643
+ message: `${name} is not a paginated query connection`,
644
+ })));
645
+ // Cursor fields are internal additions; the caller's projection remains exact.
646
+ const projection = compile(model, kind, {
647
+ [name]: { where: args, select: selection },
648
+ }).positions[0];
649
+ let cursorKey = "pageInfo";
650
+ let complete;
651
+ if (selection.__alias &&
652
+ Object.hasOwn(selection.__alias, "pageInfo")) {
653
+ cursorKey = "_distilledPageInfo";
654
+ while (Object.hasOwn(selection, cursorKey) ||
655
+ Object.hasOwn(selection.__alias, cursorKey))
656
+ cursorKey += "_";
657
+ complete = {
658
+ ...selection,
659
+ __alias: {
660
+ ...selection.__alias,
661
+ [cursorKey]: {
662
+ pageInfo: { endCursor: true, hasNextPage: true },
663
+ },
664
+ },
665
+ };
666
+ }
667
+ else {
668
+ const { $include: _include, $skip: _skip, ...pageFields } = selection.pageInfo?.select ?? selection.pageInfo ?? {};
669
+ complete = {
670
+ ...selection,
671
+ pageInfo: { ...pageFields, endCursor: true, hasNextPage: true },
672
+ };
673
+ }
674
+ return Stream.paginate({
675
+ after: args.after,
676
+ seen: new Set(),
677
+ }, (state) => call({ ...args, after: state.after }, complete).pipe(Effect.flatMap((page) => {
678
+ const cursor = page?.[cursorKey]?.endCursor;
679
+ const more = page?.[cursorKey]?.hasNextPage;
680
+ if (more &&
681
+ (typeof cursor !== "string" ||
682
+ cursor === state.after ||
683
+ state.seen.has(cursor)))
684
+ return Effect.fail(new GraphQLDecodeError({
685
+ message: `${name} returned a non-advancing pagination cursor`,
686
+ }));
687
+ const seen = new Set(state.seen);
688
+ if (typeof cursor === "string")
689
+ seen.add(cursor);
690
+ const projected = decodeData(model, field.type, page, projection.children, projection.branches, []);
691
+ return Effect.succeed([
692
+ [projected],
693
+ more
694
+ ? Option.some({ after: cursor, seen })
695
+ : Option.none(),
696
+ ]);
697
+ })));
698
+ },
699
+ catch: (cause) => cause instanceof GraphQLRequestError
700
+ ? cause
701
+ : new GraphQLRequestError({ message: String(cause) }),
702
+ }));
703
+ return Object.assign(call, {
704
+ pages,
705
+ items: (args, select) => pages(args, { edges: { node: select } }).pipe(Stream.flatMap((page) => Stream.fromIterable((page?.edges ?? []).flatMap((edge) => edge === null ? [] : [edge.node])))),
706
+ });
707
+ };
708
+ return {
709
+ query: (selection) => execute("query", selection),
710
+ mutation: (selection) => execute("mutation", selection),
711
+ report: {
712
+ query: (selection) => executeReport("query", selection),
713
+ mutation: (selection) => executeReport("mutation", selection),
714
+ },
715
+ operation,
716
+ };
717
+ };
718
+ //# sourceMappingURL=graphql.js.map