@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.
- package/LICENSE +201 -0
- package/lib/api.d.ts +165 -0
- package/lib/api.d.ts.map +1 -0
- package/lib/api.js +190 -0
- package/lib/api.js.map +1 -0
- package/lib/category.d.ts +4 -4
- package/lib/category.js +4 -4
- package/lib/codegen/boolean-string-enums.d.ts +36 -0
- package/lib/codegen/boolean-string-enums.d.ts.map +1 -0
- package/lib/codegen/boolean-string-enums.js +94 -0
- package/lib/codegen/boolean-string-enums.js.map +1 -0
- package/lib/codegen/boolean-string-enums.test.d.ts +2 -0
- package/lib/codegen/boolean-string-enums.test.d.ts.map +1 -0
- package/lib/codegen/boolean-string-enums.test.js +147 -0
- package/lib/codegen/boolean-string-enums.test.js.map +1 -0
- package/lib/codegen/cli.d.ts +79 -0
- package/lib/codegen/cli.d.ts.map +1 -0
- package/lib/codegen/cli.js +149 -0
- package/lib/codegen/cli.js.map +1 -0
- package/lib/codegen/emit.d.ts +125 -0
- package/lib/codegen/emit.d.ts.map +1 -0
- package/lib/codegen/emit.js +101 -0
- package/lib/codegen/emit.js.map +1 -0
- package/lib/codegen/format.d.ts +23 -0
- package/lib/codegen/format.d.ts.map +1 -0
- package/lib/codegen/format.js +28 -0
- package/lib/codegen/format.js.map +1 -0
- package/lib/codegen/generator.d.ts +334 -0
- package/lib/codegen/generator.d.ts.map +1 -0
- package/lib/codegen/generator.js +813 -0
- package/lib/codegen/generator.js.map +1 -0
- package/lib/codegen/graph.d.ts +36 -0
- package/lib/codegen/graph.d.ts.map +1 -0
- package/lib/codegen/graph.js +136 -0
- package/lib/codegen/graph.js.map +1 -0
- package/lib/codegen/graphql-client.d.ts +62 -0
- package/lib/codegen/graphql-client.d.ts.map +1 -0
- package/lib/codegen/graphql-client.js +294 -0
- package/lib/codegen/graphql-client.js.map +1 -0
- package/lib/codegen/graphql-client.test.d.ts +2 -0
- package/lib/codegen/graphql-client.test.d.ts.map +1 -0
- package/lib/codegen/graphql-client.test.js +311 -0
- package/lib/codegen/graphql-client.test.js.map +1 -0
- package/lib/codegen/graphql.d.ts +207 -0
- package/lib/codegen/graphql.d.ts.map +1 -0
- package/lib/codegen/graphql.js +799 -0
- package/lib/codegen/graphql.js.map +1 -0
- package/lib/codegen/members.d.ts +25 -0
- package/lib/codegen/members.d.ts.map +1 -0
- package/lib/codegen/members.js +55 -0
- package/lib/codegen/members.js.map +1 -0
- package/lib/codegen/naming.d.ts +29 -0
- package/lib/codegen/naming.d.ts.map +1 -0
- package/lib/codegen/naming.js +74 -0
- package/lib/codegen/naming.js.map +1 -0
- package/lib/codegen/openapi-cli.d.ts +52 -0
- package/lib/codegen/openapi-cli.d.ts.map +1 -0
- package/lib/codegen/openapi-cli.js +109 -0
- package/lib/codegen/openapi-cli.js.map +1 -0
- package/lib/codegen/openapi.d.ts +178 -0
- package/lib/codegen/openapi.d.ts.map +1 -0
- package/lib/codegen/openapi.js +1377 -0
- package/lib/codegen/openapi.js.map +1 -0
- package/lib/codegen/operations.d.ts +24 -0
- package/lib/codegen/operations.d.ts.map +1 -0
- package/lib/codegen/operations.js +56 -0
- package/lib/codegen/operations.js.map +1 -0
- package/lib/codegen/pagination.d.ts +39 -0
- package/lib/codegen/pagination.d.ts.map +1 -0
- package/lib/codegen/pagination.js +33 -0
- package/lib/codegen/pagination.js.map +1 -0
- package/lib/codegen/patches.d.ts +65 -0
- package/lib/codegen/patches.d.ts.map +1 -0
- package/lib/codegen/patches.js +236 -0
- package/lib/codegen/patches.js.map +1 -0
- package/lib/codegen/patches.test.d.ts +2 -0
- package/lib/codegen/patches.test.d.ts.map +1 -0
- package/lib/codegen/patches.test.js +105 -0
- package/lib/codegen/patches.test.js.map +1 -0
- package/lib/codegen/prelude.d.ts +15 -0
- package/lib/codegen/prelude.d.ts.map +1 -0
- package/lib/codegen/prelude.js +60 -0
- package/lib/codegen/prelude.js.map +1 -0
- package/lib/codegen/proto.d.ts +121 -0
- package/lib/codegen/proto.d.ts.map +1 -0
- package/lib/codegen/proto.js +962 -0
- package/lib/codegen/proto.js.map +1 -0
- package/lib/codegen/rewrite-operation-ids.d.ts +131 -0
- package/lib/codegen/rewrite-operation-ids.d.ts.map +1 -0
- package/lib/codegen/rewrite-operation-ids.js +1079 -0
- package/lib/codegen/rewrite-operation-ids.js.map +1 -0
- package/lib/codegen/rewrite-operation-ids.test.d.ts +2 -0
- package/lib/codegen/rewrite-operation-ids.test.d.ts.map +1 -0
- package/lib/codegen/rewrite-operation-ids.test.js +533 -0
- package/lib/codegen/rewrite-operation-ids.test.js.map +1 -0
- package/lib/codegen/spec-path.d.ts +16 -0
- package/lib/codegen/spec-path.d.ts.map +1 -0
- package/lib/codegen/spec-path.js +101 -0
- package/lib/codegen/spec-path.js.map +1 -0
- package/lib/error-category.d.ts +28 -0
- package/lib/error-category.d.ts.map +1 -0
- package/lib/error-category.js +46 -0
- package/lib/error-category.js.map +1 -0
- package/lib/errors.d.ts +1 -0
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +18 -13
- package/lib/errors.js.map +1 -1
- package/lib/graphql.d.ts +284 -0
- package/lib/graphql.d.ts.map +1 -0
- package/lib/graphql.fixture.d.ts +249 -0
- package/lib/graphql.fixture.d.ts.map +1 -0
- package/lib/graphql.fixture.js +240 -0
- package/lib/graphql.fixture.js.map +1 -0
- package/lib/graphql.js +718 -0
- package/lib/graphql.js.map +1 -0
- package/lib/graphql.test.d.ts +2 -0
- package/lib/graphql.test.d.ts.map +1 -0
- package/lib/graphql.test.js +780 -0
- package/lib/graphql.test.js.map +1 -0
- package/lib/graphql.types.d.ts +2 -0
- package/lib/graphql.types.d.ts.map +1 -0
- package/lib/graphql.types.js +45 -0
- package/lib/graphql.types.js.map +1 -0
- package/lib/json-patch.d.ts +30 -30
- package/lib/json-patch.d.ts.map +1 -1
- package/lib/json-patch.js +73 -107
- package/lib/json-patch.js.map +1 -1
- package/lib/pagination.d.ts +77 -51
- package/lib/pagination.d.ts.map +1 -1
- package/lib/pagination.js +162 -94
- package/lib/pagination.js.map +1 -1
- package/lib/protocol-http.d.ts +74 -0
- package/lib/protocol-http.d.ts.map +1 -0
- package/lib/protocol-http.js +590 -0
- package/lib/protocol-http.js.map +1 -0
- package/lib/protocol-http.test.d.ts +2 -0
- package/lib/protocol-http.test.d.ts.map +1 -0
- package/lib/protocol-http.test.js +88 -0
- package/lib/protocol-http.test.js.map +1 -0
- package/lib/protocol-rest.d.ts +134 -0
- package/lib/protocol-rest.d.ts.map +1 -0
- package/lib/protocol-rest.js +256 -0
- package/lib/protocol-rest.js.map +1 -0
- package/lib/retry.d.ts +8 -2
- package/lib/retry.d.ts.map +1 -1
- package/lib/retry.js +22 -16
- package/lib/retry.js.map +1 -1
- package/lib/schema.d.ts +8 -9
- package/lib/schema.d.ts.map +1 -1
- package/lib/schema.js +8 -9
- package/lib/schema.js.map +1 -1
- package/lib/trait.d.ts +174 -0
- package/lib/trait.d.ts.map +1 -0
- package/lib/trait.js +123 -0
- package/lib/trait.js.map +1 -0
- package/package.json +24 -78
- package/src/api.ts +460 -0
- package/src/category.ts +4 -4
- package/src/codegen/boolean-string-enums.test.ts +168 -0
- package/src/codegen/boolean-string-enums.ts +106 -0
- package/src/codegen/cli.ts +285 -0
- package/src/codegen/emit.ts +203 -0
- package/src/codegen/format.ts +47 -0
- package/src/codegen/generator.ts +1283 -0
- package/src/codegen/graph.ts +151 -0
- package/src/codegen/graphql-client.test.ts +386 -0
- package/src/codegen/graphql-client.ts +419 -0
- package/src/codegen/graphql.ts +1217 -0
- package/src/codegen/members.ts +71 -0
- package/src/codegen/naming.ts +86 -0
- package/src/codegen/openapi-cli.ts +182 -0
- package/src/codegen/openapi.ts +1689 -0
- package/src/codegen/operations.ts +76 -0
- package/src/codegen/pagination.ts +71 -0
- package/src/codegen/patches.test.ts +130 -0
- package/src/codegen/patches.ts +291 -0
- package/src/codegen/prelude.ts +70 -0
- package/src/codegen/proto.ts +1128 -0
- package/src/codegen/rewrite-operation-ids.test.ts +563 -0
- package/src/codegen/rewrite-operation-ids.ts +1206 -0
- package/src/codegen/spec-path.ts +115 -0
- package/src/error-category.ts +84 -0
- package/src/errors.ts +22 -25
- package/src/graphql.fixture.ts +371 -0
- package/src/graphql.test.ts +974 -0
- package/src/graphql.ts +1321 -0
- package/src/graphql.types.ts +185 -0
- package/src/json-patch.ts +95 -122
- package/src/pagination.ts +217 -146
- package/src/protocol-http.test.ts +107 -0
- package/src/protocol-http.ts +735 -0
- package/src/protocol-rest.ts +391 -0
- package/src/retry.ts +21 -22
- package/src/schema.ts +9 -10
- package/src/trait.ts +274 -0
- package/README.md +0 -30
- package/lib/client.d.ts +0 -167
- package/lib/client.d.ts.map +0 -1
- package/lib/client.js +0 -659
- package/lib/client.js.map +0 -1
- package/lib/schemas.d.ts +0 -60
- package/lib/schemas.d.ts.map +0 -1
- package/lib/schemas.js +0 -79
- package/lib/schemas.js.map +0 -1
- package/lib/sensitive.d.ts +0 -71
- package/lib/sensitive.d.ts.map +0 -1
- package/lib/sensitive.js +0 -96
- package/lib/sensitive.js.map +0 -1
- package/lib/traits.d.ts +0 -421
- package/lib/traits.d.ts.map +0 -1
- package/lib/traits.js +0 -737
- package/lib/traits.js.map +0 -1
- package/src/client.ts +0 -1177
- package/src/schemas.ts +0 -128
- package/src/sensitive.ts +0 -119
- package/src/traits.ts +0 -996
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
|