@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/src/graphql.ts
ADDED
|
@@ -0,0 +1,1321 @@
|
|
|
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 {
|
|
9
|
+
Kind,
|
|
10
|
+
parseType,
|
|
11
|
+
print,
|
|
12
|
+
type FieldNode,
|
|
13
|
+
type SelectionNode,
|
|
14
|
+
type VariableDefinitionNode,
|
|
15
|
+
} from "graphql";
|
|
16
|
+
import type { GraphQLModel, GraphQLField } from "./codegen/graphql-client.ts";
|
|
17
|
+
|
|
18
|
+
export type { GraphQLModel } from "./codegen/graphql-client.ts";
|
|
19
|
+
|
|
20
|
+
/** A generated field's arguments, GraphQL output reference, and error tags. */
|
|
21
|
+
export interface Field<Args, Ref extends string, E extends string = never> {
|
|
22
|
+
readonly args: Args;
|
|
23
|
+
readonly type: Ref;
|
|
24
|
+
readonly errors: E;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Schema {
|
|
28
|
+
types: Record<string, Record<string, Field<any, string, string>>>;
|
|
29
|
+
scalars: Record<string, unknown>;
|
|
30
|
+
errors: Record<string, GraphQLIssue>;
|
|
31
|
+
possibleTypes: Record<string, string>;
|
|
32
|
+
query: string;
|
|
33
|
+
mutation: string;
|
|
34
|
+
subscription: string;
|
|
35
|
+
globalErrors: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type Named<T extends string> = T extends `${infer R}!`
|
|
39
|
+
? Named<R>
|
|
40
|
+
: T extends `[${infer R}]`
|
|
41
|
+
? Named<R>
|
|
42
|
+
: T;
|
|
43
|
+
type Fields<C extends Schema, N extends string> = N extends keyof C["types"]
|
|
44
|
+
? C["types"][N]
|
|
45
|
+
: {};
|
|
46
|
+
type TypeOf<F> = F extends Field<any, infer T, any> ? T : never;
|
|
47
|
+
type ArgsOf<F> = F extends Field<infer A, any, any> ? A : never;
|
|
48
|
+
type TagsOf<F> = F extends Field<any, any, infer E> ? E : never;
|
|
49
|
+
type Where<A> = {} extends A ? { readonly where?: A } : { readonly where: A };
|
|
50
|
+
type Directives = { readonly $include?: boolean; readonly $skip?: boolean };
|
|
51
|
+
type FieldSelection<C extends Schema, F> =
|
|
52
|
+
Named<TypeOf<F>> extends keyof C["scalars"]
|
|
53
|
+
?
|
|
54
|
+
| ({} extends ArgsOf<F> ? boolean : never)
|
|
55
|
+
| (Where<ArgsOf<F>> & Directives & { readonly select?: true })
|
|
56
|
+
:
|
|
57
|
+
| (keyof ArgsOf<F> extends never
|
|
58
|
+
? Selection<C, Named<TypeOf<F>>>
|
|
59
|
+
: never)
|
|
60
|
+
| (Where<ArgsOf<F>> &
|
|
61
|
+
Directives & { readonly select: Selection<C, Named<TypeOf<F>>> });
|
|
62
|
+
type Alias<C extends Schema, N extends string> = {
|
|
63
|
+
[K in keyof Fields<C, N>]: {
|
|
64
|
+
readonly [P in K]: FieldSelection<C, Fields<C, N>[K]>;
|
|
65
|
+
};
|
|
66
|
+
}[keyof Fields<C, N>];
|
|
67
|
+
|
|
68
|
+
/** Mobius-style field selection. Arguments are passed in `where`. */
|
|
69
|
+
export type Selection<C extends Schema, N extends string> = {
|
|
70
|
+
readonly [K in keyof Fields<C, N>]?: FieldSelection<C, Fields<C, N>[K]>;
|
|
71
|
+
} & {
|
|
72
|
+
readonly __typename?: boolean;
|
|
73
|
+
readonly __alias?: Readonly<Record<string, Alias<C, N>>>;
|
|
74
|
+
readonly __on?: N extends keyof C["possibleTypes"]
|
|
75
|
+
? { readonly [K in C["possibleTypes"][N]]?: Selection<C, K> }
|
|
76
|
+
: never;
|
|
77
|
+
};
|
|
78
|
+
type Selected<Q> = Q extends { readonly select: infer P } ? P : Q;
|
|
79
|
+
type Keys<Q> = {
|
|
80
|
+
[K in keyof Q]: Q[K] extends false | undefined ? never : K;
|
|
81
|
+
}[keyof Q];
|
|
82
|
+
type Simplify<A> = { [K in keyof A]: A[K] };
|
|
83
|
+
type AliasValue<C extends Schema, N extends string, Q> = {
|
|
84
|
+
[K in Extract<Keys<Q>, keyof Fields<C, N>>]: Result<
|
|
85
|
+
C,
|
|
86
|
+
TypeOf<Fields<C, N>[K]>,
|
|
87
|
+
Selected<Q[K]>
|
|
88
|
+
>;
|
|
89
|
+
}[Extract<Keys<Q>, keyof Fields<C, N>>];
|
|
90
|
+
type AliasResult<C extends Schema, N extends string, Q> = Q extends {
|
|
91
|
+
readonly __alias: infer A;
|
|
92
|
+
}
|
|
93
|
+
? Simplify<
|
|
94
|
+
{
|
|
95
|
+
[
|
|
96
|
+
K in keyof A as Conditional<A[K][keyof A[K]]> extends true ? never : K
|
|
97
|
+
]: AliasValue<C, N, A[K]>;
|
|
98
|
+
} & {
|
|
99
|
+
[
|
|
100
|
+
K in keyof A as Conditional<A[K][keyof A[K]]> extends true ? K : never
|
|
101
|
+
]?: AliasValue<C, N, A[K]>;
|
|
102
|
+
}
|
|
103
|
+
>
|
|
104
|
+
: {};
|
|
105
|
+
type Conditional<Q> = boolean extends Q
|
|
106
|
+
? true
|
|
107
|
+
: Q extends { readonly $include: infer I }
|
|
108
|
+
? [I] extends [true]
|
|
109
|
+
? Q extends { readonly $skip: infer S }
|
|
110
|
+
? [S] extends [false]
|
|
111
|
+
? false
|
|
112
|
+
: true
|
|
113
|
+
: false
|
|
114
|
+
: true
|
|
115
|
+
: Q extends { readonly $skip: infer S }
|
|
116
|
+
? [S] extends [false]
|
|
117
|
+
? false
|
|
118
|
+
: true
|
|
119
|
+
: false;
|
|
120
|
+
type ObjectFields<C extends Schema, N extends string, Q> = {
|
|
121
|
+
[
|
|
122
|
+
K in Extract<Keys<Q>, keyof Fields<C, N>> as Conditional<Q[K]> extends true
|
|
123
|
+
? never
|
|
124
|
+
: K
|
|
125
|
+
]: Result<C, TypeOf<Fields<C, N>[K]>, Selected<Q[K]>>;
|
|
126
|
+
} & {
|
|
127
|
+
[
|
|
128
|
+
K in Extract<Keys<Q>, keyof Fields<C, N>> as Conditional<Q[K]> extends true
|
|
129
|
+
? K
|
|
130
|
+
: never
|
|
131
|
+
]?: Result<C, TypeOf<Fields<C, N>[K]>, Selected<Q[K]>>;
|
|
132
|
+
};
|
|
133
|
+
type ObjectResult<C extends Schema, N extends string, Q> = Simplify<
|
|
134
|
+
ObjectFields<C, N, Q> &
|
|
135
|
+
(Q extends { readonly __typename: true } ? { __typename: N } : {}) &
|
|
136
|
+
AliasResult<C, N, Q>
|
|
137
|
+
>;
|
|
138
|
+
type Branch<
|
|
139
|
+
C extends Schema,
|
|
140
|
+
N extends string,
|
|
141
|
+
Q,
|
|
142
|
+
T extends string,
|
|
143
|
+
> = T extends unknown
|
|
144
|
+
? Simplify<
|
|
145
|
+
Omit<ObjectResult<C, N, Q>, "__typename"> &
|
|
146
|
+
(Q extends { readonly __on: infer O }
|
|
147
|
+
? T extends keyof O
|
|
148
|
+
? ObjectResult<C, T, O[T]>
|
|
149
|
+
: {}
|
|
150
|
+
: {}) & { __typename: T }
|
|
151
|
+
>
|
|
152
|
+
: never;
|
|
153
|
+
type Value<C extends Schema, T extends string, Q> = T extends `[${infer I}]`
|
|
154
|
+
? Array<Result<C, I, Q>>
|
|
155
|
+
: T extends keyof C["scalars"]
|
|
156
|
+
? C["scalars"][T]
|
|
157
|
+
: T extends keyof C["possibleTypes"]
|
|
158
|
+
? Branch<C, T, Q, C["possibleTypes"][T]>
|
|
159
|
+
: ObjectResult<C, T, Q>;
|
|
160
|
+
/** Selected output, preserving list and nullable wrappers. */
|
|
161
|
+
export type Result<
|
|
162
|
+
C extends Schema,
|
|
163
|
+
Ref extends string,
|
|
164
|
+
Q,
|
|
165
|
+
> = Ref extends `${infer T}!` ? Value<C, T, Q> : Value<C, Ref, Q> | null;
|
|
166
|
+
type SelectionTags<C extends Schema, N extends string, Q> = string extends N
|
|
167
|
+
? string
|
|
168
|
+
: Q extends object
|
|
169
|
+
?
|
|
170
|
+
| {
|
|
171
|
+
[K in Extract<Keys<Q>, keyof Fields<C, N>>]:
|
|
172
|
+
| TagsOf<Fields<C, N>[K]>
|
|
173
|
+
| SelectionTags<
|
|
174
|
+
C,
|
|
175
|
+
Named<TypeOf<Fields<C, N>[K]>>,
|
|
176
|
+
Selected<Q[K]>
|
|
177
|
+
>;
|
|
178
|
+
}[Extract<Keys<Q>, keyof Fields<C, N>>]
|
|
179
|
+
| (Q extends { readonly __on: infer O }
|
|
180
|
+
? { [K in keyof O & string]: SelectionTags<C, K, O[K]> }[keyof O &
|
|
181
|
+
string]
|
|
182
|
+
: never)
|
|
183
|
+
| (Q extends { readonly __alias: infer A }
|
|
184
|
+
? { [K in keyof A]: SelectionTags<C, N, A[K]> }[keyof A]
|
|
185
|
+
: never)
|
|
186
|
+
: never;
|
|
187
|
+
/** Known selected errors plus provider-wide errors and an honest fallback. */
|
|
188
|
+
export type Errors<C extends Schema, N extends string, Q> =
|
|
189
|
+
| (SelectionTags<C, N, Q> extends infer Tags
|
|
190
|
+
? C["errors"][Extract<C["globalErrors"] | Tags, keyof C["errors"]>]
|
|
191
|
+
: never)
|
|
192
|
+
| UnknownGraphQLError;
|
|
193
|
+
type Root<C extends Schema, K extends OperationKind> = K extends "query"
|
|
194
|
+
? C["query"]
|
|
195
|
+
: C["mutation"];
|
|
196
|
+
type OperationSelection<
|
|
197
|
+
C extends Schema,
|
|
198
|
+
N extends string,
|
|
199
|
+
K extends keyof Fields<C, N>,
|
|
200
|
+
Q,
|
|
201
|
+
> = { [P in K]: { select: Q } };
|
|
202
|
+
|
|
203
|
+
export const errorFields = {
|
|
204
|
+
message: S.String,
|
|
205
|
+
code: S.optional(S.String),
|
|
206
|
+
path: S.optional(S.Array(S.Union([S.String, S.Number]))),
|
|
207
|
+
locations: S.optional(
|
|
208
|
+
S.Array(S.Struct({ line: S.Number, column: S.Number })),
|
|
209
|
+
),
|
|
210
|
+
extensions: S.optional(S.Unknown),
|
|
211
|
+
traceId: S.optional(S.String),
|
|
212
|
+
status: S.optional(S.Number),
|
|
213
|
+
retryAfter: S.optional(S.Number),
|
|
214
|
+
};
|
|
215
|
+
export interface GraphQLIssue {
|
|
216
|
+
readonly _tag: string;
|
|
217
|
+
readonly message: string;
|
|
218
|
+
readonly code?: string;
|
|
219
|
+
readonly path?: ReadonlyArray<string | number>;
|
|
220
|
+
readonly locations?: ReadonlyArray<{
|
|
221
|
+
readonly line: number;
|
|
222
|
+
readonly column: number;
|
|
223
|
+
}>;
|
|
224
|
+
readonly extensions?: unknown;
|
|
225
|
+
readonly traceId?: string;
|
|
226
|
+
readonly status?: number;
|
|
227
|
+
/** Server retry hint in seconds. */
|
|
228
|
+
readonly retryAfter?: number;
|
|
229
|
+
}
|
|
230
|
+
export class UnknownGraphQLError extends S.TaggedError<UnknownGraphQLError>()(
|
|
231
|
+
"UnknownGraphQLError",
|
|
232
|
+
{
|
|
233
|
+
...errorFields,
|
|
234
|
+
coordinate: S.optional(S.String),
|
|
235
|
+
},
|
|
236
|
+
) {}
|
|
237
|
+
/** All errors from one GraphQL execution, with the original partial response. */
|
|
238
|
+
export class GraphQLFailure<
|
|
239
|
+
E extends GraphQLIssue = GraphQLIssue,
|
|
240
|
+
> extends Data.TaggedError("GraphQLFailure")<{
|
|
241
|
+
readonly errors: readonly [E, ...E[]];
|
|
242
|
+
readonly data: unknown;
|
|
243
|
+
readonly status: number;
|
|
244
|
+
}> {
|
|
245
|
+
get message(): string {
|
|
246
|
+
return this.errors
|
|
247
|
+
.map(
|
|
248
|
+
(e) =>
|
|
249
|
+
`${e._tag}${e.path?.length ? ` at ${e.path.join(".")}` : ""}: ${e.message}`,
|
|
250
|
+
)
|
|
251
|
+
.join("; ");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
export class GraphQLTransportError extends Data.TaggedError(
|
|
255
|
+
"GraphQLTransportError",
|
|
256
|
+
)<{
|
|
257
|
+
readonly message: string;
|
|
258
|
+
readonly cause?: unknown;
|
|
259
|
+
readonly status?: number;
|
|
260
|
+
readonly retryAfter?: number;
|
|
261
|
+
}> {}
|
|
262
|
+
export class GraphQLDecodeError extends Data.TaggedError("GraphQLDecodeError")<{
|
|
263
|
+
readonly message: string;
|
|
264
|
+
readonly path?: ReadonlyArray<string | number>;
|
|
265
|
+
readonly cause?: unknown;
|
|
266
|
+
}> {}
|
|
267
|
+
export class GraphQLRequestError extends Data.TaggedError(
|
|
268
|
+
"GraphQLRequestError",
|
|
269
|
+
)<{ readonly message: string }> {}
|
|
270
|
+
export type ClientError =
|
|
271
|
+
| GraphQLTransportError
|
|
272
|
+
| GraphQLDecodeError
|
|
273
|
+
| GraphQLRequestError;
|
|
274
|
+
type IssueOf<E> = E extends GraphQLFailure<infer I> ? I : never;
|
|
275
|
+
/** True only when every issue has an allowed tag; mixed failures are never hidden. */
|
|
276
|
+
export const isErrorTag = (
|
|
277
|
+
error: unknown,
|
|
278
|
+
tags: string | readonly string[],
|
|
279
|
+
): boolean => {
|
|
280
|
+
const allowed = typeof tags === "string" ? [tags] : tags;
|
|
281
|
+
return (
|
|
282
|
+
error instanceof GraphQLFailure &&
|
|
283
|
+
error.errors.length > 0 &&
|
|
284
|
+
error.errors.every((issue) => allowed.includes(issue._tag))
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
/** Recover only an aggregate whose every issue matches. The original aggregate is also supplied. */
|
|
288
|
+
export const catchTags =
|
|
289
|
+
<const T extends readonly string[] | string, B, E2, R2>(
|
|
290
|
+
tags: T,
|
|
291
|
+
handler: (
|
|
292
|
+
error: GraphQLIssue & { readonly _tag: T extends string ? T : T[number] },
|
|
293
|
+
failure: GraphQLFailure,
|
|
294
|
+
) => Effect.Effect<B, E2, R2>,
|
|
295
|
+
) =>
|
|
296
|
+
<A, E, R>(
|
|
297
|
+
self: Effect.Effect<A, E, R>,
|
|
298
|
+
): Effect.Effect<A | B, E | E2, R | R2> =>
|
|
299
|
+
self.pipe(
|
|
300
|
+
Effect.catch((error): Effect.Effect<B, E | E2, R2> =>
|
|
301
|
+
isErrorTag(error, tags)
|
|
302
|
+
? handler(
|
|
303
|
+
(error as GraphQLFailure).errors[0] as any,
|
|
304
|
+
error as GraphQLFailure,
|
|
305
|
+
)
|
|
306
|
+
: Effect.fail(error),
|
|
307
|
+
),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
export type OperationKind = "query" | "mutation";
|
|
311
|
+
export interface GraphQLRequest {
|
|
312
|
+
readonly query: string;
|
|
313
|
+
readonly variables: Record<string, unknown>;
|
|
314
|
+
readonly operationName: string;
|
|
315
|
+
readonly kind: OperationKind;
|
|
316
|
+
}
|
|
317
|
+
export interface GraphQLResponse {
|
|
318
|
+
readonly body: unknown;
|
|
319
|
+
readonly status: number;
|
|
320
|
+
readonly headers: Readonly<Record<string, string | undefined>>;
|
|
321
|
+
}
|
|
322
|
+
export type Transport<R> = (
|
|
323
|
+
request: GraphQLRequest,
|
|
324
|
+
) => Effect.Effect<
|
|
325
|
+
GraphQLResponse,
|
|
326
|
+
GraphQLTransportError | GraphQLDecodeError,
|
|
327
|
+
R
|
|
328
|
+
>;
|
|
329
|
+
interface Position {
|
|
330
|
+
readonly field: string;
|
|
331
|
+
readonly key: string;
|
|
332
|
+
readonly coordinate: string;
|
|
333
|
+
readonly type: string;
|
|
334
|
+
readonly errors: readonly string[];
|
|
335
|
+
readonly children: readonly Position[];
|
|
336
|
+
readonly branches: Readonly<Record<string, readonly Position[]>>;
|
|
337
|
+
readonly typename?: boolean;
|
|
338
|
+
readonly conditional: boolean;
|
|
339
|
+
}
|
|
340
|
+
export interface CompiledQuery extends GraphQLRequest {
|
|
341
|
+
readonly rootType: string;
|
|
342
|
+
readonly positions: readonly Position[];
|
|
343
|
+
}
|
|
344
|
+
const object = (value: unknown): value is Record<string, any> =>
|
|
345
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
346
|
+
const named = (ref: string): string => ref.replace(/[\[\]!]/g, "");
|
|
347
|
+
const nameNode = (value: string) => ({ kind: Kind.NAME, value }) as const;
|
|
348
|
+
const requestError = (message: string): never => {
|
|
349
|
+
throw new GraphQLRequestError({ message });
|
|
350
|
+
};
|
|
351
|
+
const assertName = (name: string) => {
|
|
352
|
+
if (!/^[_A-Za-z][_0-9A-Za-z]*$/.test(name))
|
|
353
|
+
requestError(`Invalid GraphQL name ${name}`);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
/** Validate and encode inputs recursively. Undefined omits; null stays null. */
|
|
357
|
+
const encodeInput = (
|
|
358
|
+
model: GraphQLModel,
|
|
359
|
+
ref: string,
|
|
360
|
+
value: unknown,
|
|
361
|
+
at: string,
|
|
362
|
+
): unknown => {
|
|
363
|
+
if (value === undefined) return undefined;
|
|
364
|
+
if (value === null) {
|
|
365
|
+
if (ref.endsWith("!")) requestError(`${at} must not be null`);
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const base = ref.endsWith("!") ? ref.slice(0, -1) : ref;
|
|
369
|
+
if (base.startsWith("[")) {
|
|
370
|
+
if (!Array.isArray(value)) requestError(`${at} must be an array`);
|
|
371
|
+
return (value as unknown[]).map((v, i) => {
|
|
372
|
+
if (v === undefined) requestError(`${at}[${i}] must not be undefined`);
|
|
373
|
+
return encodeInput(model, base.slice(1, -1), v, `${at}[${i}]`);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
const type = model.types[base];
|
|
377
|
+
if (!type) requestError(`${at}: unknown input type ${base}`);
|
|
378
|
+
if (type.kind === "INPUT_OBJECT") {
|
|
379
|
+
if (!object(value)) requestError(`${at} must be an input object`);
|
|
380
|
+
const input = value as Record<string, unknown>;
|
|
381
|
+
const result: Record<string, unknown> = {};
|
|
382
|
+
for (const key of Object.keys(input))
|
|
383
|
+
if (!type.inputFields?.[key])
|
|
384
|
+
requestError(`${at}: unknown input field ${key}`);
|
|
385
|
+
for (const [key, field] of Object.entries(type.inputFields ?? {})) {
|
|
386
|
+
if (input[key] === undefined) {
|
|
387
|
+
if (field.type.endsWith("!") && field.defaultValue === undefined)
|
|
388
|
+
requestError(`${at}.${key} is required`);
|
|
389
|
+
} else
|
|
390
|
+
result[key] = encodeInput(
|
|
391
|
+
model,
|
|
392
|
+
field.type,
|
|
393
|
+
input[key],
|
|
394
|
+
`${at}.${key}`,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
if (type.kind === "ENUM") {
|
|
400
|
+
if (typeof value !== "string" || !type.enumValues?.includes(value))
|
|
401
|
+
requestError(`${at}: invalid ${base}`);
|
|
402
|
+
} else if (type.scalar === "string" || base === "String" || base === "ID") {
|
|
403
|
+
if (typeof value !== "string") requestError(`${at} must be a string`);
|
|
404
|
+
} else if (type.scalar === "number" || base === "Int" || base === "Float") {
|
|
405
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
406
|
+
requestError(`${at} must be a finite number`);
|
|
407
|
+
if (
|
|
408
|
+
base === "Int" &&
|
|
409
|
+
(!Number.isInteger(value) ||
|
|
410
|
+
(value as number) < -2147483648 ||
|
|
411
|
+
(value as number) > 2147483647)
|
|
412
|
+
)
|
|
413
|
+
requestError(`${at} must be a GraphQL Int`);
|
|
414
|
+
} else if (type.scalar === "boolean" || base === "Boolean") {
|
|
415
|
+
if (typeof value !== "boolean") requestError(`${at} must be a boolean`);
|
|
416
|
+
}
|
|
417
|
+
if (
|
|
418
|
+
type.scalar === "string | number" &&
|
|
419
|
+
typeof value !== "string" &&
|
|
420
|
+
typeof value !== "number"
|
|
421
|
+
)
|
|
422
|
+
requestError(`${at} must be a string or number`);
|
|
423
|
+
if (
|
|
424
|
+
base === "BigInt" &&
|
|
425
|
+
((typeof value === "number" && !Number.isSafeInteger(value)) ||
|
|
426
|
+
(typeof value === "string" && !/^-?\d+$/.test(value)))
|
|
427
|
+
)
|
|
428
|
+
requestError(`${at} must be a safe integer or an integer string`);
|
|
429
|
+
// JSON custom scalars must still be transportable JSON; preserve scalar contents.
|
|
430
|
+
try {
|
|
431
|
+
return JSON.parse(JSON.stringify(value));
|
|
432
|
+
} catch {
|
|
433
|
+
return requestError(`${at} is not a JSON-encodable scalar`);
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
/** Compile one selection to a GraphQL AST, variables, and an error/decoder path map. */
|
|
438
|
+
export const compile = (
|
|
439
|
+
model: GraphQLModel,
|
|
440
|
+
kind: OperationKind,
|
|
441
|
+
selection: unknown,
|
|
442
|
+
operationName = "Distilled",
|
|
443
|
+
): CompiledQuery => {
|
|
444
|
+
const rootType = kind === "query" ? model.queryType : model.mutationType;
|
|
445
|
+
if (!rootType) return requestError(`Schema has no ${kind} root`);
|
|
446
|
+
assertName(operationName);
|
|
447
|
+
const variables: Record<string, unknown> = {};
|
|
448
|
+
const definitions: VariableDefinitionNode[] = [];
|
|
449
|
+
const variable = (ref: string, value: unknown) => {
|
|
450
|
+
const id = `v${definitions.length}`;
|
|
451
|
+
variables[id] = value;
|
|
452
|
+
definitions.push({
|
|
453
|
+
kind: Kind.VARIABLE_DEFINITION,
|
|
454
|
+
variable: { kind: Kind.VARIABLE, name: nameNode(id) },
|
|
455
|
+
type: parseType(ref),
|
|
456
|
+
});
|
|
457
|
+
return { kind: Kind.VARIABLE, name: nameNode(id) } as const;
|
|
458
|
+
};
|
|
459
|
+
const walk = (
|
|
460
|
+
parent: string,
|
|
461
|
+
input: unknown,
|
|
462
|
+
): { nodes: SelectionNode[]; positions: Position[] } => {
|
|
463
|
+
if (!object(input))
|
|
464
|
+
return requestError(`${parent} requires an object selection`);
|
|
465
|
+
const type = model.types[parent];
|
|
466
|
+
if (!type) return requestError(`Unknown GraphQL type ${parent}`);
|
|
467
|
+
const nodes: SelectionNode[] = [];
|
|
468
|
+
const positions: Position[] = [];
|
|
469
|
+
const addField = (fieldName: string, selected: unknown, alias?: string) => {
|
|
470
|
+
if (selected === false || selected === undefined) return;
|
|
471
|
+
assertName(fieldName);
|
|
472
|
+
if (alias) assertName(alias);
|
|
473
|
+
const key = alias ?? fieldName;
|
|
474
|
+
if (positions.some((p) => p.key === key))
|
|
475
|
+
return requestError(
|
|
476
|
+
`Duplicate response key ${parent}.${key}; use distinct aliases`,
|
|
477
|
+
);
|
|
478
|
+
const field: GraphQLField | undefined =
|
|
479
|
+
fieldName === "__typename"
|
|
480
|
+
? { type: "String!", args: {}, errors: [] }
|
|
481
|
+
: type.fields?.[fieldName];
|
|
482
|
+
if (!field) return requestError(`Unknown field ${parent}.${fieldName}`);
|
|
483
|
+
const wrapper =
|
|
484
|
+
object(selected) &&
|
|
485
|
+
("where" in selected ||
|
|
486
|
+
"select" in selected ||
|
|
487
|
+
"$include" in selected ||
|
|
488
|
+
"$skip" in selected)
|
|
489
|
+
? selected
|
|
490
|
+
: undefined;
|
|
491
|
+
const args = wrapper?.where ?? {};
|
|
492
|
+
if (!object(args))
|
|
493
|
+
return requestError(`${parent}.${fieldName}.where must be an object`);
|
|
494
|
+
for (const arg of Object.keys(args))
|
|
495
|
+
if (!field.args[arg])
|
|
496
|
+
return requestError(
|
|
497
|
+
`Unknown argument ${parent}.${fieldName}(${arg})`,
|
|
498
|
+
);
|
|
499
|
+
const arguments_: NonNullable<FieldNode["arguments"]>[number][] = [];
|
|
500
|
+
for (const [arg, definition] of Object.entries(field.args)) {
|
|
501
|
+
if (args[arg] === undefined) {
|
|
502
|
+
if (
|
|
503
|
+
definition.type.endsWith("!") &&
|
|
504
|
+
definition.defaultValue === undefined
|
|
505
|
+
)
|
|
506
|
+
return requestError(`${parent}.${fieldName}(${arg}) is required`);
|
|
507
|
+
} else
|
|
508
|
+
arguments_.push({
|
|
509
|
+
kind: Kind.ARGUMENT,
|
|
510
|
+
name: nameNode(arg),
|
|
511
|
+
value: variable(
|
|
512
|
+
definition.type,
|
|
513
|
+
encodeInput(
|
|
514
|
+
model,
|
|
515
|
+
definition.type,
|
|
516
|
+
args[arg],
|
|
517
|
+
`${parent}.${fieldName}(${arg})`,
|
|
518
|
+
),
|
|
519
|
+
),
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
const directives: NonNullable<FieldNode["directives"]>[number][] = [];
|
|
523
|
+
for (const directive of ["include", "skip"] as const) {
|
|
524
|
+
const value = wrapper?.[`$${directive}`];
|
|
525
|
+
if (value !== undefined) {
|
|
526
|
+
if (typeof value !== "boolean")
|
|
527
|
+
return requestError(`$${directive} must be a boolean`);
|
|
528
|
+
directives.push({
|
|
529
|
+
kind: Kind.DIRECTIVE,
|
|
530
|
+
name: nameNode(directive),
|
|
531
|
+
arguments: [
|
|
532
|
+
{
|
|
533
|
+
kind: Kind.ARGUMENT,
|
|
534
|
+
name: nameNode("if"),
|
|
535
|
+
value: variable("Boolean!", value),
|
|
536
|
+
},
|
|
537
|
+
],
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const outputType = model.types[named(field.type)];
|
|
542
|
+
const leaf =
|
|
543
|
+
fieldName === "__typename" ||
|
|
544
|
+
outputType?.kind === "SCALAR" ||
|
|
545
|
+
outputType?.kind === "ENUM";
|
|
546
|
+
const sub = wrapper ? wrapper.select : selected;
|
|
547
|
+
let child: ReturnType<typeof walk> = { nodes: [], positions: [] };
|
|
548
|
+
if (leaf) {
|
|
549
|
+
if (sub !== true && sub !== undefined)
|
|
550
|
+
return requestError(
|
|
551
|
+
`${parent}.${fieldName} is a leaf and takes no selection`,
|
|
552
|
+
);
|
|
553
|
+
} else child = walk(named(field.type), sub);
|
|
554
|
+
nodes.push({
|
|
555
|
+
kind: Kind.FIELD,
|
|
556
|
+
name: nameNode(fieldName),
|
|
557
|
+
...(alias ? { alias: nameNode(alias) } : {}),
|
|
558
|
+
arguments: arguments_,
|
|
559
|
+
directives,
|
|
560
|
+
...(!leaf
|
|
561
|
+
? {
|
|
562
|
+
selectionSet: {
|
|
563
|
+
kind: Kind.SELECTION_SET,
|
|
564
|
+
selections: child.nodes,
|
|
565
|
+
},
|
|
566
|
+
}
|
|
567
|
+
: {}),
|
|
568
|
+
});
|
|
569
|
+
const branches: Record<string, readonly Position[]> = {};
|
|
570
|
+
// Branch metadata is attached to sentinel positions by walk and removed from regular children.
|
|
571
|
+
for (const p of child.positions)
|
|
572
|
+
if (p.field === "__fragment") branches[p.type] = p.children;
|
|
573
|
+
positions.push({
|
|
574
|
+
field: fieldName,
|
|
575
|
+
key,
|
|
576
|
+
coordinate: `${parent}.${fieldName}`,
|
|
577
|
+
type: field.type,
|
|
578
|
+
errors: field.errors,
|
|
579
|
+
children: child.positions.filter((p) => p.field !== "__fragment"),
|
|
580
|
+
branches,
|
|
581
|
+
typename: fieldName === "__typename",
|
|
582
|
+
conditional: wrapper?.$include === false || wrapper?.$skip === true,
|
|
583
|
+
});
|
|
584
|
+
};
|
|
585
|
+
for (const [key, value] of Object.entries(input)) {
|
|
586
|
+
if (key === "__alias") {
|
|
587
|
+
if (!object(value))
|
|
588
|
+
return requestError(`${parent}.__alias must be an object`);
|
|
589
|
+
for (const [alias, fields] of Object.entries(value)) {
|
|
590
|
+
if (!object(fields) || Object.keys(fields).length !== 1)
|
|
591
|
+
return requestError(`Alias ${alias} must select exactly one field`);
|
|
592
|
+
const [field, selected] = Object.entries(fields)[0]!;
|
|
593
|
+
addField(field, selected, alias);
|
|
594
|
+
}
|
|
595
|
+
} else if (key === "__on") {
|
|
596
|
+
if (!object(value))
|
|
597
|
+
return requestError(`${parent}.__on must be an object`);
|
|
598
|
+
for (const [branch, fields] of Object.entries(value)) {
|
|
599
|
+
if (!type.possibleTypes?.includes(branch))
|
|
600
|
+
return requestError(
|
|
601
|
+
`${branch} is not a possible type of ${parent}`,
|
|
602
|
+
);
|
|
603
|
+
const children = walk(branch, fields);
|
|
604
|
+
nodes.push({
|
|
605
|
+
kind: Kind.INLINE_FRAGMENT,
|
|
606
|
+
typeCondition: { kind: Kind.NAMED_TYPE, name: nameNode(branch) },
|
|
607
|
+
selectionSet: {
|
|
608
|
+
kind: Kind.SELECTION_SET,
|
|
609
|
+
selections: children.nodes,
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
positions.push({
|
|
613
|
+
field: "__fragment",
|
|
614
|
+
key: "",
|
|
615
|
+
coordinate: parent,
|
|
616
|
+
type: branch,
|
|
617
|
+
errors: [],
|
|
618
|
+
children: children.positions,
|
|
619
|
+
branches: {},
|
|
620
|
+
conditional: false,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
} else addField(key, value);
|
|
624
|
+
}
|
|
625
|
+
if (type.possibleTypes?.length && !positions.some((p) => p.typename))
|
|
626
|
+
addField("__typename", true);
|
|
627
|
+
if (!nodes.length) return requestError(`${parent} has an empty selection`);
|
|
628
|
+
return { nodes, positions };
|
|
629
|
+
};
|
|
630
|
+
const selected = walk(rootType, selection);
|
|
631
|
+
const query = print({
|
|
632
|
+
kind: Kind.DOCUMENT,
|
|
633
|
+
definitions: [
|
|
634
|
+
{
|
|
635
|
+
kind: Kind.OPERATION_DEFINITION,
|
|
636
|
+
operation: kind as any,
|
|
637
|
+
name: nameNode(operationName),
|
|
638
|
+
variableDefinitions: definitions,
|
|
639
|
+
selectionSet: { kind: Kind.SELECTION_SET, selections: selected.nodes },
|
|
640
|
+
},
|
|
641
|
+
],
|
|
642
|
+
});
|
|
643
|
+
return {
|
|
644
|
+
query,
|
|
645
|
+
variables,
|
|
646
|
+
operationName,
|
|
647
|
+
kind,
|
|
648
|
+
rootType,
|
|
649
|
+
positions: selected.positions,
|
|
650
|
+
};
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
const decodeFailure = (
|
|
654
|
+
message: string,
|
|
655
|
+
path: ReadonlyArray<string | number>,
|
|
656
|
+
): never => {
|
|
657
|
+
throw new GraphQLDecodeError({ message, path });
|
|
658
|
+
};
|
|
659
|
+
/** Validate only selected fields. Preserve the schema's nullability exactly. */
|
|
660
|
+
const decodeData = (
|
|
661
|
+
model: GraphQLModel,
|
|
662
|
+
ref: string,
|
|
663
|
+
data: unknown,
|
|
664
|
+
positions: readonly Position[],
|
|
665
|
+
branches: Readonly<Record<string, readonly Position[]>>,
|
|
666
|
+
path: ReadonlyArray<string | number>,
|
|
667
|
+
): unknown => {
|
|
668
|
+
if (data === null) {
|
|
669
|
+
if (ref.endsWith("!"))
|
|
670
|
+
return decodeFailure(`Non-null ${ref} was null`, path);
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
if (data === undefined) return decodeFailure(`Missing selected ${ref}`, path);
|
|
674
|
+
const base = ref.endsWith("!") ? ref.slice(0, -1) : ref;
|
|
675
|
+
if (base.startsWith("[")) {
|
|
676
|
+
if (!Array.isArray(data))
|
|
677
|
+
return decodeFailure(`Expected ${ref} array`, path);
|
|
678
|
+
return data.map((v, i) =>
|
|
679
|
+
decodeData(model, base.slice(1, -1), v, positions, branches, [
|
|
680
|
+
...path,
|
|
681
|
+
i,
|
|
682
|
+
]),
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
const type = model.types[base];
|
|
686
|
+
if (!type) return decodeFailure(`Unknown response type ${base}`, path);
|
|
687
|
+
if (type.kind === "SCALAR") {
|
|
688
|
+
const primitive =
|
|
689
|
+
type.scalar ??
|
|
690
|
+
(
|
|
691
|
+
{
|
|
692
|
+
String: "string",
|
|
693
|
+
ID: "string",
|
|
694
|
+
Boolean: "boolean",
|
|
695
|
+
Int: "number",
|
|
696
|
+
Float: "number",
|
|
697
|
+
} as Record<string, string>
|
|
698
|
+
)[base];
|
|
699
|
+
if (
|
|
700
|
+
primitive === "string | number" &&
|
|
701
|
+
typeof data !== "string" &&
|
|
702
|
+
typeof data !== "number"
|
|
703
|
+
)
|
|
704
|
+
return decodeFailure(`Expected ${base}`, path);
|
|
705
|
+
if (
|
|
706
|
+
base === "BigInt" &&
|
|
707
|
+
((typeof data === "number" && !Number.isSafeInteger(data)) ||
|
|
708
|
+
(typeof data === "string" && !/^-?\d+$/.test(data)))
|
|
709
|
+
)
|
|
710
|
+
return decodeFailure(`Invalid ${base}`, path);
|
|
711
|
+
if (
|
|
712
|
+
["string", "number", "boolean"].includes(primitive ?? "") &&
|
|
713
|
+
typeof data !== primitive
|
|
714
|
+
)
|
|
715
|
+
return decodeFailure(`Expected ${base}`, path);
|
|
716
|
+
if (
|
|
717
|
+
typeof data === "number" &&
|
|
718
|
+
(!Number.isFinite(data) ||
|
|
719
|
+
(base === "Int" &&
|
|
720
|
+
(!Number.isInteger(data) || data < -2147483648 || data > 2147483647)))
|
|
721
|
+
)
|
|
722
|
+
return decodeFailure(`Invalid ${base}`, path);
|
|
723
|
+
return data;
|
|
724
|
+
}
|
|
725
|
+
if (type.kind === "ENUM") {
|
|
726
|
+
if (typeof data !== "string" || !type.enumValues?.includes(data))
|
|
727
|
+
return decodeFailure(`Invalid ${base} enum`, path);
|
|
728
|
+
return data;
|
|
729
|
+
}
|
|
730
|
+
if (!object(data)) return decodeFailure(`Expected ${base} object`, path);
|
|
731
|
+
if (
|
|
732
|
+
type.possibleTypes?.length &&
|
|
733
|
+
(typeof data.__typename !== "string" ||
|
|
734
|
+
!type.possibleTypes.includes(data.__typename))
|
|
735
|
+
)
|
|
736
|
+
return decodeFailure(`Missing or invalid ${base} __typename`, path);
|
|
737
|
+
const active = [
|
|
738
|
+
...positions,
|
|
739
|
+
...(typeof data.__typename === "string"
|
|
740
|
+
? (branches[data.__typename] ?? [])
|
|
741
|
+
: []),
|
|
742
|
+
];
|
|
743
|
+
const result: Record<string, unknown> = {};
|
|
744
|
+
const merged = new Map<string, Position>();
|
|
745
|
+
for (const position of active) {
|
|
746
|
+
if (position.conditional) continue;
|
|
747
|
+
const previous = merged.get(position.key);
|
|
748
|
+
merged.set(
|
|
749
|
+
position.key,
|
|
750
|
+
previous
|
|
751
|
+
? {
|
|
752
|
+
...position,
|
|
753
|
+
children: [...previous.children, ...position.children],
|
|
754
|
+
branches: Object.fromEntries(
|
|
755
|
+
[
|
|
756
|
+
...new Set([
|
|
757
|
+
...Object.keys(previous.branches),
|
|
758
|
+
...Object.keys(position.branches),
|
|
759
|
+
]),
|
|
760
|
+
].map((key) => [
|
|
761
|
+
key,
|
|
762
|
+
[
|
|
763
|
+
...(previous.branches[key] ?? []),
|
|
764
|
+
...(position.branches[key] ?? []),
|
|
765
|
+
],
|
|
766
|
+
]),
|
|
767
|
+
),
|
|
768
|
+
}
|
|
769
|
+
: position,
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
for (const position of merged.values()) {
|
|
773
|
+
if (position.conditional) continue;
|
|
774
|
+
if (position.typename) {
|
|
775
|
+
if (
|
|
776
|
+
typeof data[position.key] !== "string" ||
|
|
777
|
+
(type.kind === "OBJECT" && data[position.key] !== base)
|
|
778
|
+
)
|
|
779
|
+
return decodeFailure(`Invalid __typename for ${base}`, [
|
|
780
|
+
...path,
|
|
781
|
+
position.key,
|
|
782
|
+
]);
|
|
783
|
+
Object.defineProperty(result, position.key, {
|
|
784
|
+
value: data[position.key],
|
|
785
|
+
enumerable: true,
|
|
786
|
+
configurable: true,
|
|
787
|
+
writable: true,
|
|
788
|
+
});
|
|
789
|
+
} else
|
|
790
|
+
Object.defineProperty(result, position.key, {
|
|
791
|
+
value: decodeData(
|
|
792
|
+
model,
|
|
793
|
+
position.type,
|
|
794
|
+
data[position.key],
|
|
795
|
+
position.children,
|
|
796
|
+
position.branches,
|
|
797
|
+
[...path, position.key],
|
|
798
|
+
),
|
|
799
|
+
enumerable: true,
|
|
800
|
+
configurable: true,
|
|
801
|
+
writable: true,
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
return result;
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
type ErrorConstructor = new (args: any) => GraphQLIssue;
|
|
808
|
+
const classify = (
|
|
809
|
+
model: GraphQLModel,
|
|
810
|
+
compiled: CompiledQuery,
|
|
811
|
+
raw: Record<string, any>,
|
|
812
|
+
response: GraphQLResponse,
|
|
813
|
+
classes: Readonly<Record<string, ErrorConstructor>>,
|
|
814
|
+
): GraphQLIssue => {
|
|
815
|
+
const path = Array.isArray(raw.path)
|
|
816
|
+
? (raw.path as (string | number)[])
|
|
817
|
+
: undefined;
|
|
818
|
+
let candidates = compiled.positions;
|
|
819
|
+
let position: Position | undefined;
|
|
820
|
+
let currentData: unknown = object(response.body)
|
|
821
|
+
? response.body.data
|
|
822
|
+
: undefined;
|
|
823
|
+
let parents: readonly Position[] = [];
|
|
824
|
+
const descendants = (
|
|
825
|
+
positions: readonly Position[],
|
|
826
|
+
value: unknown,
|
|
827
|
+
): readonly Position[] =>
|
|
828
|
+
positions.flatMap((p) => [
|
|
829
|
+
...p.children,
|
|
830
|
+
...(object(value) && typeof value.__typename === "string"
|
|
831
|
+
? (p.branches[value.__typename] ?? [])
|
|
832
|
+
: Object.values(p.branches).flat()),
|
|
833
|
+
]);
|
|
834
|
+
let valid = !!path?.length;
|
|
835
|
+
for (const segment of path ?? []) {
|
|
836
|
+
if (typeof segment === "number") {
|
|
837
|
+
currentData = Array.isArray(currentData)
|
|
838
|
+
? currentData[segment]
|
|
839
|
+
: undefined;
|
|
840
|
+
candidates = descendants(parents, currentData);
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
const matches = candidates.filter((p) => p.key === segment);
|
|
844
|
+
if (!matches.length) {
|
|
845
|
+
valid = false;
|
|
846
|
+
break;
|
|
847
|
+
}
|
|
848
|
+
position = {
|
|
849
|
+
...matches[0]!,
|
|
850
|
+
errors: [...new Set(matches.flatMap((p) => p.errors))],
|
|
851
|
+
};
|
|
852
|
+
currentData = object(currentData) ? currentData[segment] : undefined;
|
|
853
|
+
parents = matches;
|
|
854
|
+
candidates = descendants(matches, currentData);
|
|
855
|
+
}
|
|
856
|
+
const extensions = object(raw.extensions) ? raw.extensions : {};
|
|
857
|
+
const code =
|
|
858
|
+
typeof extensions.code === "string"
|
|
859
|
+
? extensions.code
|
|
860
|
+
: typeof extensions.errorCode === "string"
|
|
861
|
+
? extensions.errorCode
|
|
862
|
+
: undefined;
|
|
863
|
+
const retryHeader = response.headers["retry-after"];
|
|
864
|
+
const retryAfter =
|
|
865
|
+
retryHeader && /^\d+(\.\d+)?$/.test(retryHeader)
|
|
866
|
+
? Number(retryHeader)
|
|
867
|
+
: undefined;
|
|
868
|
+
const props = {
|
|
869
|
+
message: raw.message,
|
|
870
|
+
code,
|
|
871
|
+
path,
|
|
872
|
+
locations: raw.locations,
|
|
873
|
+
extensions: raw.extensions,
|
|
874
|
+
traceId:
|
|
875
|
+
typeof extensions.traceId === "string"
|
|
876
|
+
? extensions.traceId
|
|
877
|
+
: typeof raw.traceId === "string"
|
|
878
|
+
? raw.traceId
|
|
879
|
+
: undefined,
|
|
880
|
+
status: response.status,
|
|
881
|
+
retryAfter,
|
|
882
|
+
};
|
|
883
|
+
const tags = [
|
|
884
|
+
...model.globalErrors,
|
|
885
|
+
...(valid ? (position?.errors ?? []) : []),
|
|
886
|
+
];
|
|
887
|
+
const matches = tags.flatMap((tag) =>
|
|
888
|
+
(model.errors[tag]?.matchers ?? [])
|
|
889
|
+
.filter(
|
|
890
|
+
(matcher) =>
|
|
891
|
+
(matcher.code === undefined || matcher.code === code) &&
|
|
892
|
+
(matcher.message === undefined || matcher.message === raw.message) &&
|
|
893
|
+
(matcher.messageIncludes === undefined ||
|
|
894
|
+
raw.message.includes(matcher.messageIncludes)),
|
|
895
|
+
)
|
|
896
|
+
.map((matcher) => ({
|
|
897
|
+
tag,
|
|
898
|
+
score:
|
|
899
|
+
(matcher.code ? 1 : 0) +
|
|
900
|
+
(matcher.message ? 4 : 0) +
|
|
901
|
+
(matcher.messageIncludes ? 2 : 0),
|
|
902
|
+
})),
|
|
903
|
+
);
|
|
904
|
+
matches.sort((a, b) => b.score - a.score);
|
|
905
|
+
const best = matches[0];
|
|
906
|
+
const tag =
|
|
907
|
+
best && !matches.some((m) => m.score === best.score && m.tag !== best.tag)
|
|
908
|
+
? best.tag
|
|
909
|
+
: undefined;
|
|
910
|
+
if (tag && classes[tag]) return new classes[tag](props);
|
|
911
|
+
return new UnknownGraphQLError({
|
|
912
|
+
...props,
|
|
913
|
+
coordinate: valid ? position?.coordinate : undefined,
|
|
914
|
+
});
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
export interface Report<A, E extends GraphQLIssue> {
|
|
918
|
+
readonly data: A | null | undefined;
|
|
919
|
+
readonly errors: ReadonlyArray<E>;
|
|
920
|
+
readonly status: number;
|
|
921
|
+
readonly extensions?: unknown;
|
|
922
|
+
}
|
|
923
|
+
type Failure<C extends Schema, N extends string, Q> =
|
|
924
|
+
| ClientError
|
|
925
|
+
| GraphQLFailure<Errors<C, N, Q>>;
|
|
926
|
+
type FieldAt<
|
|
927
|
+
C extends Schema,
|
|
928
|
+
N extends string,
|
|
929
|
+
K extends string,
|
|
930
|
+
> = K extends keyof Fields<C, N> ? Fields<C, N>[K] : never;
|
|
931
|
+
type ConnectionEdge<C extends Schema, Ref extends string> = Named<
|
|
932
|
+
TypeOf<FieldAt<C, Named<Ref>, "edges">>
|
|
933
|
+
>;
|
|
934
|
+
type ConnectionNode<C extends Schema, Ref extends string> = TypeOf<
|
|
935
|
+
FieldAt<C, ConnectionEdge<C, Ref>, "node">
|
|
936
|
+
>;
|
|
937
|
+
|
|
938
|
+
export interface ObjectOperation<
|
|
939
|
+
C extends Schema,
|
|
940
|
+
K extends OperationKind,
|
|
941
|
+
N extends keyof Fields<C, Root<C, K>> & string,
|
|
942
|
+
R,
|
|
943
|
+
> {
|
|
944
|
+
<const Q extends Selection<C, Named<TypeOf<Fields<C, Root<C, K>>[N]>>>>(
|
|
945
|
+
args: ArgsOf<Fields<C, Root<C, K>>[N]>,
|
|
946
|
+
select: Q,
|
|
947
|
+
): Effect.Effect<
|
|
948
|
+
Result<C, TypeOf<Fields<C, Root<C, K>>[N]>, Q>,
|
|
949
|
+
Failure<C, Root<C, K>, OperationSelection<C, Root<C, K>, N, Q>>,
|
|
950
|
+
R
|
|
951
|
+
>;
|
|
952
|
+
}
|
|
953
|
+
export interface ScalarOperation<
|
|
954
|
+
C extends Schema,
|
|
955
|
+
K extends OperationKind,
|
|
956
|
+
N extends keyof Fields<C, Root<C, K>> & string,
|
|
957
|
+
R,
|
|
958
|
+
> {
|
|
959
|
+
(
|
|
960
|
+
args: ArgsOf<Fields<C, Root<C, K>>[N]>,
|
|
961
|
+
): Effect.Effect<
|
|
962
|
+
Result<C, TypeOf<Fields<C, Root<C, K>>[N]>, true>,
|
|
963
|
+
Failure<C, Root<C, K>, OperationSelection<C, Root<C, K>, N, true>>,
|
|
964
|
+
R
|
|
965
|
+
>;
|
|
966
|
+
}
|
|
967
|
+
export interface Pagination<
|
|
968
|
+
C extends Schema,
|
|
969
|
+
K extends OperationKind,
|
|
970
|
+
N extends keyof Fields<C, Root<C, K>> & string,
|
|
971
|
+
R,
|
|
972
|
+
> {
|
|
973
|
+
pages<const Q extends Selection<C, Named<TypeOf<Fields<C, Root<C, K>>[N]>>>>(
|
|
974
|
+
args: ArgsOf<Fields<C, Root<C, K>>[N]>,
|
|
975
|
+
select: Q,
|
|
976
|
+
): Stream.Stream<
|
|
977
|
+
Result<C, TypeOf<Fields<C, Root<C, K>>[N]>, Q>,
|
|
978
|
+
| ClientError
|
|
979
|
+
| GraphQLFailure<
|
|
980
|
+
| Errors<C, Root<C, K>, OperationSelection<C, Root<C, K>, N, Q>>
|
|
981
|
+
| Errors<
|
|
982
|
+
C,
|
|
983
|
+
Named<TypeOf<Fields<C, Root<C, K>>[N]>>,
|
|
984
|
+
{ pageInfo: { endCursor: true; hasNextPage: true } }
|
|
985
|
+
>
|
|
986
|
+
>,
|
|
987
|
+
R
|
|
988
|
+
>;
|
|
989
|
+
items<
|
|
990
|
+
const Q extends Selection<
|
|
991
|
+
C,
|
|
992
|
+
Named<ConnectionNode<C, TypeOf<Fields<C, Root<C, K>>[N]>>>
|
|
993
|
+
>,
|
|
994
|
+
>(
|
|
995
|
+
args: ArgsOf<Fields<C, Root<C, K>>[N]>,
|
|
996
|
+
select: Q,
|
|
997
|
+
): Stream.Stream<
|
|
998
|
+
Result<C, ConnectionNode<C, TypeOf<Fields<C, Root<C, K>>[N]>>, Q>,
|
|
999
|
+
Failure<
|
|
1000
|
+
C,
|
|
1001
|
+
Root<C, K>,
|
|
1002
|
+
OperationSelection<
|
|
1003
|
+
C,
|
|
1004
|
+
Root<C, K>,
|
|
1005
|
+
N,
|
|
1006
|
+
{ edges: { node: Q }; pageInfo: { endCursor: true; hasNextPage: true } }
|
|
1007
|
+
>
|
|
1008
|
+
>,
|
|
1009
|
+
R
|
|
1010
|
+
>;
|
|
1011
|
+
}
|
|
1012
|
+
export type Operation<
|
|
1013
|
+
C extends Schema,
|
|
1014
|
+
K extends OperationKind,
|
|
1015
|
+
N extends keyof Fields<C, Root<C, K>> & string,
|
|
1016
|
+
R,
|
|
1017
|
+
> =
|
|
1018
|
+
Named<TypeOf<Fields<C, Root<C, K>>[N]>> extends keyof C["scalars"]
|
|
1019
|
+
? ScalarOperation<C, K, N, R>
|
|
1020
|
+
: ObjectOperation<C, K, N, R> &
|
|
1021
|
+
(K extends "query"
|
|
1022
|
+
? "after" extends keyof ArgsOf<Fields<C, Root<C, K>>[N]>
|
|
1023
|
+
? [ConnectionNode<C, TypeOf<Fields<C, Root<C, K>>[N]>>] extends [
|
|
1024
|
+
never,
|
|
1025
|
+
]
|
|
1026
|
+
? {}
|
|
1027
|
+
: Pagination<C, K, N, R>
|
|
1028
|
+
: {}
|
|
1029
|
+
: {});
|
|
1030
|
+
|
|
1031
|
+
/** Construct an Effect client; credentials and HTTP are resolved by the transport per call. */
|
|
1032
|
+
export const makeClient = <C extends Schema, R>(
|
|
1033
|
+
model: GraphQLModel,
|
|
1034
|
+
transport: Transport<R>,
|
|
1035
|
+
classes: Readonly<Record<string, ErrorConstructor>>,
|
|
1036
|
+
) => {
|
|
1037
|
+
const executeReport = (
|
|
1038
|
+
kind: OperationKind,
|
|
1039
|
+
selection: unknown,
|
|
1040
|
+
): Effect.Effect<Report<any, GraphQLIssue>, ClientError, R> =>
|
|
1041
|
+
Effect.gen(function* () {
|
|
1042
|
+
const compiled = yield* Effect.try({
|
|
1043
|
+
try: () => compile(model, kind, selection),
|
|
1044
|
+
catch: (cause) =>
|
|
1045
|
+
cause instanceof GraphQLRequestError
|
|
1046
|
+
? cause
|
|
1047
|
+
: new GraphQLRequestError({ message: String(cause) }),
|
|
1048
|
+
});
|
|
1049
|
+
const response = yield* transport(compiled);
|
|
1050
|
+
return yield* Effect.try({
|
|
1051
|
+
try: () => {
|
|
1052
|
+
const body = response.body;
|
|
1053
|
+
if (!object(body))
|
|
1054
|
+
return decodeFailure("GraphQL response must be an object", []);
|
|
1055
|
+
if (
|
|
1056
|
+
body.errors !== undefined &&
|
|
1057
|
+
(!Array.isArray(body.errors) || !body.errors.length)
|
|
1058
|
+
)
|
|
1059
|
+
return decodeFailure("GraphQL errors must be a nonempty array", []);
|
|
1060
|
+
const rawErrors = body.errors ?? [];
|
|
1061
|
+
for (const error of rawErrors) {
|
|
1062
|
+
if (!object(error) || typeof error.message !== "string")
|
|
1063
|
+
return decodeFailure("Invalid GraphQL error", []);
|
|
1064
|
+
if (
|
|
1065
|
+
error.path !== undefined &&
|
|
1066
|
+
(!Array.isArray(error.path) ||
|
|
1067
|
+
error.path.some(
|
|
1068
|
+
(p: unknown) =>
|
|
1069
|
+
typeof p !== "string" &&
|
|
1070
|
+
(typeof p !== "number" || !Number.isInteger(p) || p < 0),
|
|
1071
|
+
))
|
|
1072
|
+
)
|
|
1073
|
+
return decodeFailure("Invalid GraphQL error path", []);
|
|
1074
|
+
}
|
|
1075
|
+
if (!("data" in body) && !rawErrors.length) {
|
|
1076
|
+
if (response.status >= 400)
|
|
1077
|
+
throw new GraphQLTransportError({
|
|
1078
|
+
message: `HTTP ${response.status} without a GraphQL response`,
|
|
1079
|
+
status: response.status,
|
|
1080
|
+
});
|
|
1081
|
+
return decodeFailure(
|
|
1082
|
+
"GraphQL response has neither data nor errors",
|
|
1083
|
+
[],
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
if (response.status >= 400 && !rawErrors.length)
|
|
1087
|
+
throw new GraphQLTransportError({
|
|
1088
|
+
message: `HTTP ${response.status} without GraphQL errors`,
|
|
1089
|
+
status: response.status,
|
|
1090
|
+
});
|
|
1091
|
+
const errors = rawErrors.map((raw: Record<string, any>) =>
|
|
1092
|
+
classify(model, compiled, raw, response, classes),
|
|
1093
|
+
);
|
|
1094
|
+
let data = body.data;
|
|
1095
|
+
if (data !== undefined && data !== null)
|
|
1096
|
+
data = decodeData(
|
|
1097
|
+
model,
|
|
1098
|
+
compiled.rootType + "!",
|
|
1099
|
+
data,
|
|
1100
|
+
compiled.positions,
|
|
1101
|
+
{},
|
|
1102
|
+
[],
|
|
1103
|
+
);
|
|
1104
|
+
if ((data === undefined || data === null) && !errors.length)
|
|
1105
|
+
return decodeFailure("GraphQL success has no data", []);
|
|
1106
|
+
return {
|
|
1107
|
+
data,
|
|
1108
|
+
errors,
|
|
1109
|
+
status: response.status,
|
|
1110
|
+
extensions: body.extensions,
|
|
1111
|
+
};
|
|
1112
|
+
},
|
|
1113
|
+
catch: (cause) =>
|
|
1114
|
+
cause instanceof GraphQLDecodeError ||
|
|
1115
|
+
cause instanceof GraphQLTransportError
|
|
1116
|
+
? cause
|
|
1117
|
+
: new GraphQLDecodeError({
|
|
1118
|
+
message: "Invalid GraphQL response",
|
|
1119
|
+
cause,
|
|
1120
|
+
}),
|
|
1121
|
+
});
|
|
1122
|
+
});
|
|
1123
|
+
const retryable = (error: ClientError | GraphQLFailure): boolean =>
|
|
1124
|
+
error instanceof GraphQLFailure
|
|
1125
|
+
? error.errors.every(
|
|
1126
|
+
(issue) => model.errors[issue._tag]?.retryable === true,
|
|
1127
|
+
)
|
|
1128
|
+
: error instanceof GraphQLTransportError &&
|
|
1129
|
+
(error.status === undefined ||
|
|
1130
|
+
error.status === 429 ||
|
|
1131
|
+
error.status >= 500);
|
|
1132
|
+
const execute = (
|
|
1133
|
+
kind: OperationKind,
|
|
1134
|
+
selection: unknown,
|
|
1135
|
+
): Effect.Effect<any, ClientError | GraphQLFailure, R> => {
|
|
1136
|
+
const effect = executeReport(kind, selection).pipe(
|
|
1137
|
+
Effect.flatMap((report) =>
|
|
1138
|
+
report.errors.length
|
|
1139
|
+
? Effect.fail(
|
|
1140
|
+
new GraphQLFailure({
|
|
1141
|
+
errors: report.errors as [GraphQLIssue, ...GraphQLIssue[]],
|
|
1142
|
+
data: report.data,
|
|
1143
|
+
status: report.status,
|
|
1144
|
+
}),
|
|
1145
|
+
)
|
|
1146
|
+
: Effect.succeed(report.data),
|
|
1147
|
+
),
|
|
1148
|
+
);
|
|
1149
|
+
return kind === "query"
|
|
1150
|
+
? effect.pipe(
|
|
1151
|
+
Effect.retry({
|
|
1152
|
+
while: retryable,
|
|
1153
|
+
times: 5,
|
|
1154
|
+
schedule: Schedule.exponential("200 millis"),
|
|
1155
|
+
}),
|
|
1156
|
+
)
|
|
1157
|
+
: effect;
|
|
1158
|
+
};
|
|
1159
|
+
const operation = <
|
|
1160
|
+
K extends OperationKind,
|
|
1161
|
+
N extends keyof Fields<C, Root<C, K>> & string,
|
|
1162
|
+
>(
|
|
1163
|
+
kind: K,
|
|
1164
|
+
name: N,
|
|
1165
|
+
): Operation<C, K, N, R> => {
|
|
1166
|
+
const field =
|
|
1167
|
+
model.types[kind === "query" ? model.queryType : model.mutationType!]
|
|
1168
|
+
?.fields?.[name];
|
|
1169
|
+
const call = (args: unknown, selection?: unknown) =>
|
|
1170
|
+
execute(kind, {
|
|
1171
|
+
[name]: { where: args, select: selection ?? true },
|
|
1172
|
+
}).pipe(Effect.map((data) => data[name]));
|
|
1173
|
+
const pages = (args: any, selection: any) =>
|
|
1174
|
+
Stream.unwrap(
|
|
1175
|
+
Effect.try({
|
|
1176
|
+
try: () => {
|
|
1177
|
+
const connection = field && model.types[named(field.type)];
|
|
1178
|
+
if (
|
|
1179
|
+
kind !== "query" ||
|
|
1180
|
+
!field?.args.after ||
|
|
1181
|
+
!connection?.fields?.edges ||
|
|
1182
|
+
!connection.fields.pageInfo
|
|
1183
|
+
)
|
|
1184
|
+
return Stream.fromEffect(
|
|
1185
|
+
Effect.fail(
|
|
1186
|
+
new GraphQLRequestError({
|
|
1187
|
+
message: `${name} is not a paginated query connection`,
|
|
1188
|
+
}),
|
|
1189
|
+
),
|
|
1190
|
+
);
|
|
1191
|
+
// Cursor fields are internal additions; the caller's projection remains exact.
|
|
1192
|
+
const projection = compile(model, kind, {
|
|
1193
|
+
[name]: { where: args, select: selection },
|
|
1194
|
+
}).positions[0]!;
|
|
1195
|
+
let cursorKey = "pageInfo";
|
|
1196
|
+
let complete: Record<string, unknown>;
|
|
1197
|
+
if (
|
|
1198
|
+
selection.__alias &&
|
|
1199
|
+
Object.hasOwn(selection.__alias, "pageInfo")
|
|
1200
|
+
) {
|
|
1201
|
+
cursorKey = "_distilledPageInfo";
|
|
1202
|
+
while (
|
|
1203
|
+
Object.hasOwn(selection, cursorKey) ||
|
|
1204
|
+
Object.hasOwn(selection.__alias, cursorKey)
|
|
1205
|
+
)
|
|
1206
|
+
cursorKey += "_";
|
|
1207
|
+
complete = {
|
|
1208
|
+
...selection,
|
|
1209
|
+
__alias: {
|
|
1210
|
+
...selection.__alias,
|
|
1211
|
+
[cursorKey]: {
|
|
1212
|
+
pageInfo: { endCursor: true, hasNextPage: true },
|
|
1213
|
+
},
|
|
1214
|
+
},
|
|
1215
|
+
};
|
|
1216
|
+
} else {
|
|
1217
|
+
const {
|
|
1218
|
+
$include: _include,
|
|
1219
|
+
$skip: _skip,
|
|
1220
|
+
...pageFields
|
|
1221
|
+
} = selection.pageInfo?.select ?? selection.pageInfo ?? {};
|
|
1222
|
+
complete = {
|
|
1223
|
+
...selection,
|
|
1224
|
+
pageInfo: { ...pageFields, endCursor: true, hasNextPage: true },
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
return Stream.paginate(
|
|
1228
|
+
{
|
|
1229
|
+
after: args.after as string | undefined,
|
|
1230
|
+
seen: new Set<string>(),
|
|
1231
|
+
},
|
|
1232
|
+
(state) =>
|
|
1233
|
+
call({ ...args, after: state.after }, complete).pipe(
|
|
1234
|
+
Effect.flatMap((page) => {
|
|
1235
|
+
const cursor = page?.[cursorKey]?.endCursor;
|
|
1236
|
+
const more = page?.[cursorKey]?.hasNextPage;
|
|
1237
|
+
if (
|
|
1238
|
+
more &&
|
|
1239
|
+
(typeof cursor !== "string" ||
|
|
1240
|
+
cursor === state.after ||
|
|
1241
|
+
state.seen.has(cursor))
|
|
1242
|
+
)
|
|
1243
|
+
return Effect.fail(
|
|
1244
|
+
new GraphQLDecodeError({
|
|
1245
|
+
message: `${name} returned a non-advancing pagination cursor`,
|
|
1246
|
+
}),
|
|
1247
|
+
);
|
|
1248
|
+
const seen = new Set(state.seen);
|
|
1249
|
+
if (typeof cursor === "string") seen.add(cursor);
|
|
1250
|
+
const projected: any = decodeData(
|
|
1251
|
+
model,
|
|
1252
|
+
field.type,
|
|
1253
|
+
page,
|
|
1254
|
+
projection.children,
|
|
1255
|
+
projection.branches,
|
|
1256
|
+
[],
|
|
1257
|
+
);
|
|
1258
|
+
return Effect.succeed([
|
|
1259
|
+
[projected],
|
|
1260
|
+
more
|
|
1261
|
+
? Option.some({ after: cursor, seen })
|
|
1262
|
+
: Option.none(),
|
|
1263
|
+
] as const);
|
|
1264
|
+
}),
|
|
1265
|
+
),
|
|
1266
|
+
);
|
|
1267
|
+
},
|
|
1268
|
+
catch: (cause) =>
|
|
1269
|
+
cause instanceof GraphQLRequestError
|
|
1270
|
+
? cause
|
|
1271
|
+
: new GraphQLRequestError({ message: String(cause) }),
|
|
1272
|
+
}),
|
|
1273
|
+
);
|
|
1274
|
+
return Object.assign(call, {
|
|
1275
|
+
pages,
|
|
1276
|
+
items: (args: unknown, select: unknown) =>
|
|
1277
|
+
pages(args, { edges: { node: select } }).pipe(
|
|
1278
|
+
Stream.flatMap((page) =>
|
|
1279
|
+
Stream.fromIterable(
|
|
1280
|
+
(page?.edges ?? []).flatMap((edge: any) =>
|
|
1281
|
+
edge === null ? [] : [edge.node],
|
|
1282
|
+
),
|
|
1283
|
+
),
|
|
1284
|
+
),
|
|
1285
|
+
),
|
|
1286
|
+
}) as unknown as Operation<C, K, N, R>;
|
|
1287
|
+
};
|
|
1288
|
+
return {
|
|
1289
|
+
query: <const Q extends Selection<C, C["query"]>>(
|
|
1290
|
+
selection: Q,
|
|
1291
|
+
): Effect.Effect<
|
|
1292
|
+
Result<C, `${C["query"]}!`, Q>,
|
|
1293
|
+
Failure<C, C["query"], Q>,
|
|
1294
|
+
R
|
|
1295
|
+
> => execute("query", selection) as any,
|
|
1296
|
+
mutation: <const Q extends Selection<C, C["mutation"]>>(
|
|
1297
|
+
selection: Q,
|
|
1298
|
+
): Effect.Effect<
|
|
1299
|
+
Result<C, `${C["mutation"]}!`, Q>,
|
|
1300
|
+
Failure<C, C["mutation"], Q>,
|
|
1301
|
+
R
|
|
1302
|
+
> => execute("mutation", selection) as any,
|
|
1303
|
+
report: {
|
|
1304
|
+
query: <const Q extends Selection<C, C["query"]>>(
|
|
1305
|
+
selection: Q,
|
|
1306
|
+
): Effect.Effect<
|
|
1307
|
+
Report<Result<C, `${C["query"]}!`, Q>, Errors<C, C["query"], Q>>,
|
|
1308
|
+
ClientError,
|
|
1309
|
+
R
|
|
1310
|
+
> => executeReport("query", selection) as any,
|
|
1311
|
+
mutation: <const Q extends Selection<C, C["mutation"]>>(
|
|
1312
|
+
selection: Q,
|
|
1313
|
+
): Effect.Effect<
|
|
1314
|
+
Report<Result<C, `${C["mutation"]}!`, Q>, Errors<C, C["mutation"], Q>>,
|
|
1315
|
+
ClientError,
|
|
1316
|
+
R
|
|
1317
|
+
> => executeReport("mutation", selection) as any,
|
|
1318
|
+
},
|
|
1319
|
+
operation,
|
|
1320
|
+
};
|
|
1321
|
+
};
|