@mearie/core 0.0.0-next-20260228035926
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 +20 -0
- package/README.md +10 -0
- package/dist/index.cjs +1410 -0
- package/dist/index.d.cts +348 -0
- package/dist/index.d.mts +348 -0
- package/dist/index.mjs +1393 -0
- package/dist/make-BhxZYW3l.d.cts +405 -0
- package/dist/make-C7I1YIXm.mjs +712 -0
- package/dist/make-DxW2Pxe-.cjs +856 -0
- package/dist/make-Ve8TkMHJ.d.mts +405 -0
- package/dist/stream/index.cjs +27 -0
- package/dist/stream/index.d.cts +2 -0
- package/dist/stream/index.d.mts +2 -0
- package/dist/stream/index.mjs +3 -0
- package/package.json +79 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1410 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
const require_make = require('./make-DxW2Pxe-.cjs');
|
|
3
|
+
|
|
4
|
+
//#region src/errors.ts
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
*/
|
|
8
|
+
var GraphQLError = class GraphQLError extends Error {
|
|
9
|
+
path;
|
|
10
|
+
locations;
|
|
11
|
+
extensions;
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message, { cause: options?.cause });
|
|
14
|
+
this.name = "GraphQLError";
|
|
15
|
+
this.path = options?.path;
|
|
16
|
+
this.locations = options?.locations;
|
|
17
|
+
this.extensions = options?.extensions;
|
|
18
|
+
Object.setPrototypeOf(this, GraphQLError.prototype);
|
|
19
|
+
}
|
|
20
|
+
toJSON() {
|
|
21
|
+
const json = { message: this.message };
|
|
22
|
+
if (this.path) json.path = this.path;
|
|
23
|
+
if (this.locations) json.locations = this.locations;
|
|
24
|
+
if (this.extensions) json.extensions = this.extensions;
|
|
25
|
+
return json;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
*
|
|
30
|
+
*/
|
|
31
|
+
var ExchangeError = class ExchangeError extends Error {
|
|
32
|
+
exchangeName;
|
|
33
|
+
extensions;
|
|
34
|
+
constructor(message, options) {
|
|
35
|
+
super(message, { cause: options.cause });
|
|
36
|
+
this.name = "ExchangeError";
|
|
37
|
+
this.exchangeName = options.exchangeName;
|
|
38
|
+
this.extensions = options.extensions;
|
|
39
|
+
Object.setPrototypeOf(this, ExchangeError.prototype);
|
|
40
|
+
}
|
|
41
|
+
toJSON() {
|
|
42
|
+
const json = {
|
|
43
|
+
message: this.message,
|
|
44
|
+
exchangeName: this.exchangeName
|
|
45
|
+
};
|
|
46
|
+
if (this.extensions !== void 0) json.extensions = this.extensions;
|
|
47
|
+
return json;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
*/
|
|
53
|
+
var AggregatedError = class AggregatedError extends AggregateError {
|
|
54
|
+
constructor(errors, message = `${errors.length} error(s) occurred`) {
|
|
55
|
+
super([...errors], message);
|
|
56
|
+
this.name = "AggregatedError";
|
|
57
|
+
Object.setPrototypeOf(this, AggregatedError.prototype);
|
|
58
|
+
}
|
|
59
|
+
toJSON() {
|
|
60
|
+
return {
|
|
61
|
+
message: this.message,
|
|
62
|
+
errors: this.errors.map((error) => error.toJSON())
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const isGraphQLError = (error) => {
|
|
67
|
+
return error instanceof GraphQLError;
|
|
68
|
+
};
|
|
69
|
+
function isExchangeError(error, exchangeName) {
|
|
70
|
+
if (!(error instanceof ExchangeError)) return false;
|
|
71
|
+
if (exchangeName !== void 0) return error.exchangeName === exchangeName;
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
const isAggregatedError = (error) => {
|
|
75
|
+
return error instanceof AggregatedError;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/exchanges/http.ts
|
|
80
|
+
const executeFetch = async ({ url, fetchFn, fetchOptions, operation, signal }) => {
|
|
81
|
+
const { artifact, variables } = operation;
|
|
82
|
+
let response;
|
|
83
|
+
try {
|
|
84
|
+
await Promise.resolve();
|
|
85
|
+
response = await fetchFn(url, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
mode: fetchOptions.mode,
|
|
88
|
+
credentials: fetchOptions.credentials,
|
|
89
|
+
headers: {
|
|
90
|
+
"Content-Type": "application/json",
|
|
91
|
+
...fetchOptions.headers
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({
|
|
94
|
+
operationName: artifact.name,
|
|
95
|
+
query: artifact.body,
|
|
96
|
+
variables
|
|
97
|
+
}),
|
|
98
|
+
signal
|
|
99
|
+
});
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error instanceof Error && error.name === "AbortError") return null;
|
|
102
|
+
return {
|
|
103
|
+
operation,
|
|
104
|
+
errors: [new ExchangeError(error instanceof Error ? error.message : "Network error", {
|
|
105
|
+
exchangeName: "http",
|
|
106
|
+
cause: error
|
|
107
|
+
})]
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (!response.ok) return {
|
|
111
|
+
operation,
|
|
112
|
+
errors: [new ExchangeError(`HTTP ${response.status}: ${response.statusText}`, {
|
|
113
|
+
exchangeName: "http",
|
|
114
|
+
extensions: { statusCode: response.status }
|
|
115
|
+
})]
|
|
116
|
+
};
|
|
117
|
+
let json;
|
|
118
|
+
try {
|
|
119
|
+
json = await response.json();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
operation,
|
|
123
|
+
errors: [new ExchangeError(error instanceof Error ? error.message : "JSON parse error", {
|
|
124
|
+
exchangeName: "http",
|
|
125
|
+
cause: error
|
|
126
|
+
})]
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
operation,
|
|
131
|
+
data: json.data,
|
|
132
|
+
errors: json.errors?.map((err) => new GraphQLError(err.message, {
|
|
133
|
+
path: err.path,
|
|
134
|
+
locations: err.locations,
|
|
135
|
+
extensions: err.extensions
|
|
136
|
+
})),
|
|
137
|
+
extensions: json.extensions
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
const httpExchange = (options) => {
|
|
141
|
+
const { url, headers, mode, credentials, fetch: fetchFn = globalThis.fetch } = options;
|
|
142
|
+
return ({ forward }) => ({
|
|
143
|
+
name: "http",
|
|
144
|
+
io: (ops$) => {
|
|
145
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
146
|
+
return require_make.merge(require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && (op.artifact.kind === "query" || op.artifact.kind === "mutation")), require_make.mergeMap((op) => {
|
|
147
|
+
inflight.get(op.key)?.abort();
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
inflight.set(op.key, controller);
|
|
150
|
+
return require_make.fromPromise(executeFetch({
|
|
151
|
+
url,
|
|
152
|
+
fetchFn,
|
|
153
|
+
fetchOptions: {
|
|
154
|
+
mode,
|
|
155
|
+
credentials,
|
|
156
|
+
headers
|
|
157
|
+
},
|
|
158
|
+
operation: op,
|
|
159
|
+
signal: controller.signal
|
|
160
|
+
}).then((result) => {
|
|
161
|
+
inflight.delete(op.key);
|
|
162
|
+
return result;
|
|
163
|
+
}));
|
|
164
|
+
}), require_make.filter((result) => result !== null)), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown" || op.variant === "request" && (op.artifact.kind === "subscription" || op.artifact.kind === "fragment")), require_make.tap((op) => {
|
|
165
|
+
if (op.variant === "teardown") {
|
|
166
|
+
inflight.get(op.key)?.abort();
|
|
167
|
+
inflight.delete(op.key);
|
|
168
|
+
}
|
|
169
|
+
}), forward));
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/stream/operators/delay.ts
|
|
176
|
+
/**
|
|
177
|
+
* Delays each value emitted by a source by the specified time.
|
|
178
|
+
* @param ms - The time (in milliseconds) to delay each value.
|
|
179
|
+
* @returns An operator that delays values.
|
|
180
|
+
*/
|
|
181
|
+
const delay = (ms) => {
|
|
182
|
+
return (source) => {
|
|
183
|
+
return (sink) => {
|
|
184
|
+
let cancelled = false;
|
|
185
|
+
const timeouts = [];
|
|
186
|
+
const upstreamSubscription = source({
|
|
187
|
+
next(value) {
|
|
188
|
+
const timeout = setTimeout(() => {
|
|
189
|
+
if (!cancelled) sink.next(value);
|
|
190
|
+
}, ms);
|
|
191
|
+
timeouts.push(timeout);
|
|
192
|
+
},
|
|
193
|
+
complete() {
|
|
194
|
+
const timeout = setTimeout(() => {
|
|
195
|
+
if (!cancelled) sink.complete();
|
|
196
|
+
}, ms);
|
|
197
|
+
timeouts.push(timeout);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
return { unsubscribe() {
|
|
201
|
+
cancelled = true;
|
|
202
|
+
for (const timeout of timeouts) clearTimeout(timeout);
|
|
203
|
+
timeouts.length = 0;
|
|
204
|
+
upstreamSubscription.unsubscribe();
|
|
205
|
+
} };
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
//#endregion
|
|
211
|
+
//#region src/utils.ts
|
|
212
|
+
/**
|
|
213
|
+
* Stable JSON serialization with sorted keys.
|
|
214
|
+
* Used for both variables and field arguments.
|
|
215
|
+
* @internal
|
|
216
|
+
* @param value - The value to stringify.
|
|
217
|
+
* @returns The stable JSON string.
|
|
218
|
+
*/
|
|
219
|
+
const stringify = (value) => {
|
|
220
|
+
if (value === null) return "null";
|
|
221
|
+
if (value === void 0) return "undefined";
|
|
222
|
+
const type = typeof value;
|
|
223
|
+
if (type === "string") return JSON.stringify(value);
|
|
224
|
+
if (type === "number" || type === "boolean") return String(value);
|
|
225
|
+
if (Array.isArray(value)) return "[" + value.map((v) => stringify(v)).join(",") + "]";
|
|
226
|
+
if (type === "object") {
|
|
227
|
+
const obj = value;
|
|
228
|
+
return "{" + Object.keys(obj).toSorted().filter((k) => obj[k] !== void 0).map((k) => `"${k}":${stringify(obj[k])}`).join(",") + "}";
|
|
229
|
+
}
|
|
230
|
+
return JSON.stringify(value) ?? "";
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Type guard to check if a value is nullish.
|
|
234
|
+
* @internal
|
|
235
|
+
* @param value - Value to check.
|
|
236
|
+
* @returns True if the value is nullish.
|
|
237
|
+
*/
|
|
238
|
+
const isNullish$1 = (value) => {
|
|
239
|
+
return value === void 0 || value === null;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region src/exchanges/dedup.ts
|
|
244
|
+
const makeDedupKey = (op) => {
|
|
245
|
+
return `${op.artifact.name}:${stringify(op.variables)}`;
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* Prevents duplicate in-flight operations by deduplicating requests with identical artifact names and variables.
|
|
249
|
+
*
|
|
250
|
+
* Operations are considered identical if they have the same artifact name and serialized variables.
|
|
251
|
+
* Mutations are never deduplicated. An operation is "in-flight" from when it's first seen until all subscribers tear down.
|
|
252
|
+
*
|
|
253
|
+
* Caveats:
|
|
254
|
+
*
|
|
255
|
+
* 1. Upstream metadata is lost when operations are deduplicated. The result will contain the metadata
|
|
256
|
+
* from the operation that actually went through the pipeline, not from deduplicated operations.
|
|
257
|
+
* This preserves downstream metadata (retry attempts, cache status) but means custom upstream metadata
|
|
258
|
+
* from deduplicated operations will not appear in results.
|
|
259
|
+
* @internal
|
|
260
|
+
* @returns An exchange that deduplicates in-flight operations.
|
|
261
|
+
*/
|
|
262
|
+
const dedupExchange = () => {
|
|
263
|
+
return ({ forward }) => ({
|
|
264
|
+
name: "dedup",
|
|
265
|
+
io: (ops$) => {
|
|
266
|
+
const operations = /* @__PURE__ */ new Map();
|
|
267
|
+
const resolved = /* @__PURE__ */ new Set();
|
|
268
|
+
return require_make.pipe(require_make.merge(require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "fragment"))), require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind !== "mutation" && op.artifact.kind !== "fragment"), require_make.filter((op) => {
|
|
269
|
+
const dedupKey = makeDedupKey(op);
|
|
270
|
+
const isInflight = operations.has(dedupKey) && !resolved.has(dedupKey);
|
|
271
|
+
if (isInflight) operations.get(dedupKey).add(op.key);
|
|
272
|
+
else operations.set(dedupKey, new Set([op.key]));
|
|
273
|
+
if (!isInflight) resolved.delete(dedupKey);
|
|
274
|
+
return (op.metadata.dedup?.skip ?? false) || !isInflight;
|
|
275
|
+
}), delay(0)), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown"), require_make.filter((teardown) => {
|
|
276
|
+
for (const [dedupKey, subs] of operations.entries()) if (subs.delete(teardown.key)) {
|
|
277
|
+
if (subs.size === 0) {
|
|
278
|
+
operations.delete(dedupKey);
|
|
279
|
+
resolved.delete(dedupKey);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
return true;
|
|
285
|
+
}))), forward, require_make.mergeMap((result) => {
|
|
286
|
+
if (result.operation.variant !== "request" || result.operation.artifact.kind === "mutation" || result.operation.artifact.kind === "fragment") return require_make.fromValue(result);
|
|
287
|
+
const dedupKey = makeDedupKey(result.operation);
|
|
288
|
+
resolved.add(dedupKey);
|
|
289
|
+
return require_make.fromArray([...operations.get(dedupKey) ?? /* @__PURE__ */ new Set()].map((key) => ({
|
|
290
|
+
...result,
|
|
291
|
+
operation: {
|
|
292
|
+
...result.operation,
|
|
293
|
+
key
|
|
294
|
+
}
|
|
295
|
+
})));
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/cache/constants.ts
|
|
303
|
+
/**
|
|
304
|
+
* Special key used to mark normalized entity references in the cache.
|
|
305
|
+
* When an entity is normalized, it's replaced with an object containing this key
|
|
306
|
+
* with the entity key as the value.
|
|
307
|
+
* @internal
|
|
308
|
+
*/
|
|
309
|
+
const EntityLinkKey = "__ref";
|
|
310
|
+
/**
|
|
311
|
+
* Special key for storing root query fields.
|
|
312
|
+
* @internal
|
|
313
|
+
*/
|
|
314
|
+
const RootFieldKey = "__root";
|
|
315
|
+
/**
|
|
316
|
+
* Special key used to mark fragment references in entities.
|
|
317
|
+
* Used for cache-agnostic fragment system.
|
|
318
|
+
* @internal
|
|
319
|
+
*/
|
|
320
|
+
const FragmentRefKey = "__fragmentRef";
|
|
321
|
+
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/cache/utils.ts
|
|
324
|
+
/**
|
|
325
|
+
* Generates a unique cache key for an entity based on its typename and key field values.
|
|
326
|
+
* @internal
|
|
327
|
+
* @param typename - The GraphQL typename of the entity.
|
|
328
|
+
* @param keyValues - Array of key field values used to identify the entity.
|
|
329
|
+
* @returns A unique entity key in the format "typename:value1:value2:...".
|
|
330
|
+
*/
|
|
331
|
+
const makeEntityKey = (typename, keyValues) => {
|
|
332
|
+
return `${typename}:${keyValues.join(":")}`;
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* Resolves GraphQL arguments by replacing variable references with their actual values.
|
|
336
|
+
* @internal
|
|
337
|
+
* @param args - Argument definitions from the query, containing either literal values or variable references.
|
|
338
|
+
* @param variables - Object containing the actual variable values.
|
|
339
|
+
* @returns Object with all arguments resolved to their actual values.
|
|
340
|
+
*/
|
|
341
|
+
const resolveArguments = (args, variables) => {
|
|
342
|
+
return Object.fromEntries(Object.entries(args).map(([key, value]) => [key, value.kind === "literal" ? value.value : variables[value.name]]));
|
|
343
|
+
};
|
|
344
|
+
/**
|
|
345
|
+
* Generates a cache key for a GraphQL field selection.
|
|
346
|
+
* Always uses the actual field name (not alias) with a stringified representation of the arguments.
|
|
347
|
+
* @internal
|
|
348
|
+
* @param selection - The field selection node containing field information.
|
|
349
|
+
* @param variables - Variable values for resolving argument references.
|
|
350
|
+
* @returns Field cache key string in "fieldName@argsString" format.
|
|
351
|
+
*/
|
|
352
|
+
const makeFieldKey = (selection, variables) => {
|
|
353
|
+
const args = selection.args && Object.keys(selection.args).length > 0 ? stringify(resolveArguments(selection.args, variables)) : "{}";
|
|
354
|
+
return `${selection.name}@${args}`;
|
|
355
|
+
};
|
|
356
|
+
/**
|
|
357
|
+
* Generates a unique key for tracking memoized denormalized results for structural sharing.
|
|
358
|
+
* @internal
|
|
359
|
+
* @param kind - The operation kind ('query', 'fragment', 'fragments').
|
|
360
|
+
* @param name - The artifact name.
|
|
361
|
+
* @param id - Serialized identifier (variables, entity key, etc.).
|
|
362
|
+
* @returns A unique memo key.
|
|
363
|
+
*/
|
|
364
|
+
const makeMemoKey = (kind, name, id) => `${kind}:${name}:${id}`;
|
|
365
|
+
/**
|
|
366
|
+
* Gets a unique key for tracking a field dependency.
|
|
367
|
+
* @internal
|
|
368
|
+
* @param storageKey Storage key (entity or root query key).
|
|
369
|
+
* @param fieldKey Field key.
|
|
370
|
+
* @returns Unique dependency key in the format "storageKey.field".
|
|
371
|
+
*/
|
|
372
|
+
const makeDependencyKey = (storageKey, fieldKey) => {
|
|
373
|
+
return `${storageKey}.${fieldKey}`;
|
|
374
|
+
};
|
|
375
|
+
/**
|
|
376
|
+
* Type guard to check if a value is an entity link.
|
|
377
|
+
* @internal
|
|
378
|
+
* @param value - Value to check.
|
|
379
|
+
* @returns True if the value is an EntityLink.
|
|
380
|
+
*/
|
|
381
|
+
const isEntityLink = (value) => {
|
|
382
|
+
return typeof value === "object" && value !== null && EntityLinkKey in value;
|
|
383
|
+
};
|
|
384
|
+
/**
|
|
385
|
+
* Type guard to check if a value is a fragment reference.
|
|
386
|
+
* @internal
|
|
387
|
+
* @param value - Value to check.
|
|
388
|
+
* @returns True if the value is a FragmentRef.
|
|
389
|
+
*/
|
|
390
|
+
const isFragmentRef = (value) => {
|
|
391
|
+
return typeof value === "object" && value !== null && FragmentRefKey in value;
|
|
392
|
+
};
|
|
393
|
+
/**
|
|
394
|
+
* Type guard to check if a value is an array of fragment references.
|
|
395
|
+
* @internal
|
|
396
|
+
* @param value - Value to check.
|
|
397
|
+
* @returns True if the value is a FragmentRef array.
|
|
398
|
+
*/
|
|
399
|
+
const isFragmentRefArray = (value) => {
|
|
400
|
+
return Array.isArray(value) && value.length > 0 && isFragmentRef(value[0]);
|
|
401
|
+
};
|
|
402
|
+
/**
|
|
403
|
+
* Type guard to check if a value is nullish.
|
|
404
|
+
* @internal
|
|
405
|
+
* @param value - Value to check.
|
|
406
|
+
* @returns True if the value is nullish.
|
|
407
|
+
*/
|
|
408
|
+
const isNullish = (value) => {
|
|
409
|
+
return value === void 0 || value === null;
|
|
410
|
+
};
|
|
411
|
+
/**
|
|
412
|
+
* Deep equality check for normalized cache values.
|
|
413
|
+
* Handles scalars, arrays, and plain objects (entity links, value objects).
|
|
414
|
+
* @internal
|
|
415
|
+
*/
|
|
416
|
+
const isEqual = (a, b) => {
|
|
417
|
+
if (a === b) return true;
|
|
418
|
+
if (typeof a !== typeof b || a === null || b === null) return false;
|
|
419
|
+
if (Array.isArray(a)) {
|
|
420
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
421
|
+
for (const [i, item] of a.entries()) if (!isEqual(item, b[i])) return false;
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
if (typeof a === "object") {
|
|
425
|
+
const aObj = a;
|
|
426
|
+
const bObj = b;
|
|
427
|
+
const aKeys = Object.keys(aObj);
|
|
428
|
+
if (aKeys.length !== Object.keys(bObj).length) return false;
|
|
429
|
+
for (const key of aKeys) if (!isEqual(aObj[key], bObj[key])) return false;
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
return false;
|
|
433
|
+
};
|
|
434
|
+
/**
|
|
435
|
+
* Recursively replaces a new value tree with the previous one wherever structurally equal,
|
|
436
|
+
* preserving referential identity for unchanged subtrees.
|
|
437
|
+
*
|
|
438
|
+
* Returns `prev` (same reference) when the entire subtree is structurally equal.
|
|
439
|
+
* @internal
|
|
440
|
+
*/
|
|
441
|
+
const replaceEqualDeep = (prev, next) => {
|
|
442
|
+
if (prev === next) return prev;
|
|
443
|
+
if (typeof prev !== typeof next || prev === null || next === null || typeof prev !== "object") return next;
|
|
444
|
+
if (Array.isArray(prev)) {
|
|
445
|
+
if (!Array.isArray(next)) return next;
|
|
446
|
+
let allSame = prev.length === next.length;
|
|
447
|
+
const result = [];
|
|
448
|
+
for (const [i, item] of next.entries()) {
|
|
449
|
+
const shared = i < prev.length ? replaceEqualDeep(prev[i], item) : item;
|
|
450
|
+
result.push(shared);
|
|
451
|
+
if (shared !== prev[i]) allSame = false;
|
|
452
|
+
}
|
|
453
|
+
return allSame ? prev : result;
|
|
454
|
+
}
|
|
455
|
+
if (Array.isArray(next)) return next;
|
|
456
|
+
const prevObj = prev;
|
|
457
|
+
const nextObj = next;
|
|
458
|
+
const nextKeys = Object.keys(nextObj);
|
|
459
|
+
const prevKeys = Object.keys(prevObj);
|
|
460
|
+
let allSame = nextKeys.length === prevKeys.length;
|
|
461
|
+
const result = {};
|
|
462
|
+
for (const key of nextKeys) if (key in prevObj) {
|
|
463
|
+
result[key] = replaceEqualDeep(prevObj[key], nextObj[key]);
|
|
464
|
+
if (result[key] !== prevObj[key]) allSame = false;
|
|
465
|
+
} else {
|
|
466
|
+
result[key] = nextObj[key];
|
|
467
|
+
allSame = false;
|
|
468
|
+
}
|
|
469
|
+
return allSame ? prev : result;
|
|
470
|
+
};
|
|
471
|
+
/**
|
|
472
|
+
* Deeply merges two values. Objects are recursively merged, arrays are element-wise merged,
|
|
473
|
+
* entity links and primitives use last-write-wins.
|
|
474
|
+
* @internal
|
|
475
|
+
*/
|
|
476
|
+
const mergeFieldValue = (existing, incoming) => {
|
|
477
|
+
if (isNullish(existing) || isNullish(incoming)) return incoming;
|
|
478
|
+
if (typeof existing !== "object" || typeof incoming !== "object") return incoming;
|
|
479
|
+
if (isEntityLink(existing) || isEntityLink(incoming)) return incoming;
|
|
480
|
+
if (Array.isArray(existing) && Array.isArray(incoming)) return incoming.map((item, i) => i < existing.length ? mergeFieldValue(existing[i], item) : item);
|
|
481
|
+
if (Array.isArray(existing) || Array.isArray(incoming)) return incoming;
|
|
482
|
+
mergeFields(existing, incoming);
|
|
483
|
+
return existing;
|
|
484
|
+
};
|
|
485
|
+
/**
|
|
486
|
+
* Deeply merges source fields into target. Objects are recursively merged,
|
|
487
|
+
* arrays are element-wise merged, entity links and primitives use last-write-wins.
|
|
488
|
+
* @internal
|
|
489
|
+
*/
|
|
490
|
+
const mergeFields = (target, source) => {
|
|
491
|
+
if (isNullish(source) || typeof source !== "object" || Array.isArray(source)) return;
|
|
492
|
+
for (const key of Object.keys(source)) target[key] = mergeFieldValue(target[key], source[key]);
|
|
493
|
+
};
|
|
494
|
+
/**
|
|
495
|
+
* Creates a FieldKey from a raw field name and optional arguments.
|
|
496
|
+
* @internal
|
|
497
|
+
* @param field - The field name.
|
|
498
|
+
* @param args - Optional argument values.
|
|
499
|
+
* @returns A FieldKey in "field@args" format.
|
|
500
|
+
*/
|
|
501
|
+
const makeFieldKeyFromArgs = (field, args) => {
|
|
502
|
+
return `${field}@${args && Object.keys(args).length > 0 ? stringify(args) : "{}"}`;
|
|
503
|
+
};
|
|
504
|
+
/**
|
|
505
|
+
* Converts an EntityId to an EntityKey.
|
|
506
|
+
* @internal
|
|
507
|
+
* @param typename - The GraphQL typename of the entity.
|
|
508
|
+
* @param id - The entity identifier (string, number, or composite key record).
|
|
509
|
+
* @param keyFields - Optional ordered list of key field names for composite keys.
|
|
510
|
+
* @returns An EntityKey.
|
|
511
|
+
*/
|
|
512
|
+
const resolveEntityKey = (typename, id, keyFields) => {
|
|
513
|
+
if (typeof id === "string" || typeof id === "number") return makeEntityKey(typename, [id]);
|
|
514
|
+
return makeEntityKey(typename, keyFields ? keyFields.map((f) => id[f]) : Object.values(id));
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
//#endregion
|
|
518
|
+
//#region src/cache/normalize.ts
|
|
519
|
+
const SKIP = Symbol();
|
|
520
|
+
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
521
|
+
const normalizeField = (storageKey, selections, value) => {
|
|
522
|
+
if (isNullish(value)) return value;
|
|
523
|
+
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
|
|
524
|
+
const data = value;
|
|
525
|
+
const typename = data.__typename;
|
|
526
|
+
const entityMeta = schemaMeta.entities[typename];
|
|
527
|
+
if (entityMeta) {
|
|
528
|
+
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
529
|
+
if (!keys.every((k) => k !== void 0 && k !== null)) return SKIP;
|
|
530
|
+
storageKey = makeEntityKey(typename, keys);
|
|
531
|
+
}
|
|
532
|
+
const fields = {};
|
|
533
|
+
for (const selection of selections) if (selection.kind === "Field") {
|
|
534
|
+
const fieldKey = makeFieldKey(selection, variables);
|
|
535
|
+
const fieldValue = data[selection.alias ?? selection.name];
|
|
536
|
+
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
537
|
+
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
538
|
+
const normalized = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
539
|
+
if (normalized === SKIP) continue;
|
|
540
|
+
fields[fieldKey] = normalized;
|
|
541
|
+
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
542
|
+
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
543
|
+
const inner = normalizeField(storageKey, selection.selections, value);
|
|
544
|
+
if (inner !== SKIP && !isEntityLink(inner)) mergeFields(fields, inner);
|
|
545
|
+
}
|
|
546
|
+
if (entityMeta && storageKey !== null) {
|
|
547
|
+
const existing = storage[storageKey];
|
|
548
|
+
if (existing) mergeFields(existing, fields);
|
|
549
|
+
else storage[storageKey] = fields;
|
|
550
|
+
return { [EntityLinkKey]: storageKey };
|
|
551
|
+
}
|
|
552
|
+
return fields;
|
|
553
|
+
};
|
|
554
|
+
const fields = normalizeField(RootFieldKey, selections, data);
|
|
555
|
+
storage[RootFieldKey] = {
|
|
556
|
+
...storage[RootFieldKey],
|
|
557
|
+
...fields
|
|
558
|
+
};
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/cache/denormalize.ts
|
|
563
|
+
const typenameFieldKey = makeFieldKey({
|
|
564
|
+
kind: "Field",
|
|
565
|
+
name: "__typename",
|
|
566
|
+
type: "String"
|
|
567
|
+
}, {});
|
|
568
|
+
const denormalize = (selections, storage, value, variables, accessor) => {
|
|
569
|
+
let partial = false;
|
|
570
|
+
const denormalizeField = (storageKey, selections, value) => {
|
|
571
|
+
if (isNullish(value)) return value;
|
|
572
|
+
if (Array.isArray(value)) return value.map((item) => denormalizeField(storageKey, selections, item));
|
|
573
|
+
const data = value;
|
|
574
|
+
if (isEntityLink(data)) {
|
|
575
|
+
const entityKey = data[EntityLinkKey];
|
|
576
|
+
const entity = storage[entityKey];
|
|
577
|
+
if (!entity) {
|
|
578
|
+
accessor?.(entityKey, typenameFieldKey);
|
|
579
|
+
partial = true;
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
return denormalizeField(entityKey, selections, entity);
|
|
583
|
+
}
|
|
584
|
+
const fields = {};
|
|
585
|
+
for (const selection of selections) if (selection.kind === "Field") {
|
|
586
|
+
const fieldKey = makeFieldKey(selection, variables);
|
|
587
|
+
const fieldValue = data[fieldKey];
|
|
588
|
+
if (storageKey !== null) accessor?.(storageKey, fieldKey);
|
|
589
|
+
if (fieldValue === void 0) {
|
|
590
|
+
partial = true;
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
const name = selection.alias ?? selection.name;
|
|
594
|
+
const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
595
|
+
if (name in fields) mergeFields(fields, { [name]: value });
|
|
596
|
+
else fields[name] = value;
|
|
597
|
+
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) fields[FragmentRefKey] = storageKey;
|
|
598
|
+
else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
599
|
+
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
600
|
+
return fields;
|
|
601
|
+
};
|
|
602
|
+
return {
|
|
603
|
+
data: denormalizeField(RootFieldKey, selections, value),
|
|
604
|
+
partial
|
|
605
|
+
};
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
//#endregion
|
|
609
|
+
//#region src/cache/cache.ts
|
|
610
|
+
/**
|
|
611
|
+
* A normalized cache that stores and manages GraphQL query results and entities.
|
|
612
|
+
* Supports entity normalization, cache invalidation, and reactive updates through subscriptions.
|
|
613
|
+
*/
|
|
614
|
+
var Cache = class {
|
|
615
|
+
#schemaMeta;
|
|
616
|
+
#storage = { [RootFieldKey]: {} };
|
|
617
|
+
#subscriptions = /* @__PURE__ */ new Map();
|
|
618
|
+
#memo = /* @__PURE__ */ new Map();
|
|
619
|
+
constructor(schemaMetadata) {
|
|
620
|
+
this.#schemaMeta = schemaMetadata;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Writes a query result to the cache, normalizing entities.
|
|
624
|
+
* @param artifact - GraphQL document artifact.
|
|
625
|
+
* @param variables - Query variables.
|
|
626
|
+
* @param data - Query result data.
|
|
627
|
+
*/
|
|
628
|
+
writeQuery(artifact, variables, data) {
|
|
629
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
630
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
631
|
+
normalize(this.#schemaMeta, artifact.selections, this.#storage, data, variables, (storageKey, fieldKey, oldValue, newValue) => {
|
|
632
|
+
if (oldValue !== newValue) {
|
|
633
|
+
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
634
|
+
dependencies.add(dependencyKey);
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
for (const dependency of dependencies) {
|
|
638
|
+
const ss = this.#subscriptions.get(dependency);
|
|
639
|
+
if (ss) for (const s of ss) subscriptions.add(s);
|
|
640
|
+
}
|
|
641
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Reads a query result from the cache, denormalizing entities if available.
|
|
645
|
+
* Uses structural sharing to preserve referential identity for unchanged subtrees.
|
|
646
|
+
* @param artifact - GraphQL document artifact.
|
|
647
|
+
* @param variables - Query variables.
|
|
648
|
+
* @returns Denormalized query result or null if not found.
|
|
649
|
+
*/
|
|
650
|
+
readQuery(artifact, variables) {
|
|
651
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables);
|
|
652
|
+
if (partial) return null;
|
|
653
|
+
const key = makeMemoKey("query", artifact.name, stringify(variables));
|
|
654
|
+
const prev = this.#memo.get(key);
|
|
655
|
+
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
656
|
+
this.#memo.set(key, result);
|
|
657
|
+
return result;
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Subscribes to cache invalidations for a specific query.
|
|
661
|
+
* @param artifact - GraphQL document artifact.
|
|
662
|
+
* @param variables - Query variables.
|
|
663
|
+
* @param listener - Callback function to invoke on cache invalidation.
|
|
664
|
+
* @returns Unsubscribe function.
|
|
665
|
+
*/
|
|
666
|
+
subscribeQuery(artifact, variables, listener) {
|
|
667
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
668
|
+
denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
669
|
+
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
670
|
+
dependencies.add(dependencyKey);
|
|
671
|
+
});
|
|
672
|
+
return this.#subscribe(dependencies, listener);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Reads a fragment from the cache for a specific entity.
|
|
676
|
+
* Uses structural sharing to preserve referential identity for unchanged subtrees.
|
|
677
|
+
* @param artifact - GraphQL fragment artifact.
|
|
678
|
+
* @param fragmentRef - Fragment reference containing entity key.
|
|
679
|
+
* @returns Denormalized fragment data or null if not found or invalid.
|
|
680
|
+
*/
|
|
681
|
+
readFragment(artifact, fragmentRef) {
|
|
682
|
+
const entityKey = fragmentRef[FragmentRefKey];
|
|
683
|
+
if (!this.#storage[entityKey]) return null;
|
|
684
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {});
|
|
685
|
+
if (partial) return null;
|
|
686
|
+
const key = makeMemoKey("fragment", artifact.name, entityKey);
|
|
687
|
+
const prev = this.#memo.get(key);
|
|
688
|
+
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
689
|
+
this.#memo.set(key, result);
|
|
690
|
+
return result;
|
|
691
|
+
}
|
|
692
|
+
subscribeFragment(artifact, fragmentRef, listener) {
|
|
693
|
+
const entityKey = fragmentRef[FragmentRefKey];
|
|
694
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
695
|
+
denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {}, (storageKey, fieldKey) => {
|
|
696
|
+
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
697
|
+
dependencies.add(dependencyKey);
|
|
698
|
+
});
|
|
699
|
+
return this.#subscribe(dependencies, listener);
|
|
700
|
+
}
|
|
701
|
+
readFragments(artifact, fragmentRefs) {
|
|
702
|
+
const results = [];
|
|
703
|
+
for (const ref of fragmentRefs) {
|
|
704
|
+
const data = this.readFragment(artifact, ref);
|
|
705
|
+
if (data === null) return null;
|
|
706
|
+
results.push(data);
|
|
707
|
+
}
|
|
708
|
+
const entityKeys = fragmentRefs.map((ref) => ref[FragmentRefKey]);
|
|
709
|
+
const key = makeMemoKey("fragments", artifact.name, entityKeys.join(","));
|
|
710
|
+
const prev = this.#memo.get(key);
|
|
711
|
+
const result = prev === void 0 ? results : replaceEqualDeep(prev, results);
|
|
712
|
+
this.#memo.set(key, result);
|
|
713
|
+
return result;
|
|
714
|
+
}
|
|
715
|
+
subscribeFragments(artifact, fragmentRefs, listener) {
|
|
716
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
717
|
+
for (const ref of fragmentRefs) {
|
|
718
|
+
const entityKey = ref[FragmentRefKey];
|
|
719
|
+
denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {}, (storageKey, fieldKey) => {
|
|
720
|
+
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
return this.#subscribe(dependencies, listener);
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Invalidates one or more cache entries and notifies affected subscribers.
|
|
727
|
+
* @param targets - Cache entries to invalidate.
|
|
728
|
+
*/
|
|
729
|
+
invalidate(...targets) {
|
|
730
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
731
|
+
for (const target of targets) if (target.__typename === "Query") if ("field" in target) {
|
|
732
|
+
const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
|
|
733
|
+
delete this.#storage[RootFieldKey]?.[fieldKey];
|
|
734
|
+
this.#collectSubscriptions(RootFieldKey, fieldKey, subscriptions);
|
|
735
|
+
} else {
|
|
736
|
+
this.#storage[RootFieldKey] = {};
|
|
737
|
+
this.#collectSubscriptions(RootFieldKey, void 0, subscriptions);
|
|
738
|
+
}
|
|
739
|
+
else if ("id" in target) {
|
|
740
|
+
const entityKey = resolveEntityKey(target.__typename, target.id, this.#schemaMeta.entities[target.__typename]?.keyFields);
|
|
741
|
+
if ("field" in target) {
|
|
742
|
+
const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
|
|
743
|
+
delete this.#storage[entityKey]?.[fieldKey];
|
|
744
|
+
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
745
|
+
} else {
|
|
746
|
+
delete this.#storage[entityKey];
|
|
747
|
+
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
748
|
+
}
|
|
749
|
+
} else {
|
|
750
|
+
const prefix = `${target.__typename}:`;
|
|
751
|
+
for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
|
|
752
|
+
const entityKey = key;
|
|
753
|
+
if ("field" in target) {
|
|
754
|
+
const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
|
|
755
|
+
delete this.#storage[entityKey]?.[fieldKey];
|
|
756
|
+
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
757
|
+
} else {
|
|
758
|
+
delete this.#storage[entityKey];
|
|
759
|
+
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
764
|
+
}
|
|
765
|
+
#collectSubscriptions(storageKey, fieldKey, out) {
|
|
766
|
+
if (fieldKey === void 0) {
|
|
767
|
+
const prefix = `${storageKey}.`;
|
|
768
|
+
for (const [depKey, ss] of this.#subscriptions) if (depKey.startsWith(prefix)) for (const s of ss) out.add(s);
|
|
769
|
+
} else {
|
|
770
|
+
const depKey = makeDependencyKey(storageKey, fieldKey);
|
|
771
|
+
const ss = this.#subscriptions.get(depKey);
|
|
772
|
+
if (ss) for (const s of ss) out.add(s);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
#subscribe(dependencies, listener) {
|
|
776
|
+
const subscription = { listener };
|
|
777
|
+
for (const dependency of dependencies) {
|
|
778
|
+
const subscriptions = this.#subscriptions.get(dependency) ?? /* @__PURE__ */ new Set();
|
|
779
|
+
subscriptions.add(subscription);
|
|
780
|
+
this.#subscriptions.set(dependency, subscriptions);
|
|
781
|
+
}
|
|
782
|
+
return () => {
|
|
783
|
+
for (const dependency of dependencies) {
|
|
784
|
+
const subscriptions = this.#subscriptions.get(dependency);
|
|
785
|
+
subscriptions?.delete(subscription);
|
|
786
|
+
if (subscriptions?.size === 0) this.#subscriptions.delete(dependency);
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Extracts a serializable snapshot of the cache storage and structural sharing state.
|
|
792
|
+
*/
|
|
793
|
+
extract() {
|
|
794
|
+
return {
|
|
795
|
+
storage: structuredClone(this.#storage),
|
|
796
|
+
memo: Object.fromEntries(this.#memo)
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Hydrates the cache with a previously extracted snapshot.
|
|
801
|
+
*/
|
|
802
|
+
hydrate(snapshot) {
|
|
803
|
+
const { storage, memo } = snapshot;
|
|
804
|
+
for (const [key, fields] of Object.entries(storage)) this.#storage[key] = {
|
|
805
|
+
...this.#storage[key],
|
|
806
|
+
...fields
|
|
807
|
+
};
|
|
808
|
+
for (const [key, value] of Object.entries(memo)) this.#memo.set(key, value);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Clears all cache data.
|
|
812
|
+
*/
|
|
813
|
+
clear() {
|
|
814
|
+
this.#storage = { [RootFieldKey]: {} };
|
|
815
|
+
this.#subscriptions.clear();
|
|
816
|
+
this.#memo.clear();
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
//#endregion
|
|
821
|
+
//#region src/stream/sources/empty.ts
|
|
822
|
+
/**
|
|
823
|
+
* Creates a source that completes immediately without emitting any values.
|
|
824
|
+
* @returns An empty source.
|
|
825
|
+
*/
|
|
826
|
+
const empty = () => {
|
|
827
|
+
return (sink) => {
|
|
828
|
+
sink.complete();
|
|
829
|
+
return { unsubscribe() {} };
|
|
830
|
+
};
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
//#endregion
|
|
834
|
+
//#region src/exchanges/cache.ts
|
|
835
|
+
const cacheExchange = (options = {}) => {
|
|
836
|
+
const { fetchPolicy = "cache-first" } = options;
|
|
837
|
+
return ({ forward, client }) => {
|
|
838
|
+
const cache = new Cache(client.schema);
|
|
839
|
+
return {
|
|
840
|
+
name: "cache",
|
|
841
|
+
extension: {
|
|
842
|
+
extract: () => cache.extract(),
|
|
843
|
+
hydrate: (snapshot) => cache.hydrate(snapshot),
|
|
844
|
+
invalidate: (...targets) => cache.invalidate(...targets),
|
|
845
|
+
clear: () => cache.clear()
|
|
846
|
+
},
|
|
847
|
+
io: (ops$) => {
|
|
848
|
+
const fragment$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), require_make.mergeMap((op) => {
|
|
849
|
+
const fragmentRef = op.metadata?.fragmentRef;
|
|
850
|
+
if (!fragmentRef) return require_make.fromValue({
|
|
851
|
+
operation: op,
|
|
852
|
+
errors: [new ExchangeError("Fragment operation missing fragmentRef in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "cache" })]
|
|
853
|
+
});
|
|
854
|
+
if (isFragmentRefArray(fragmentRef)) {
|
|
855
|
+
const trigger = require_make.makeSubject();
|
|
856
|
+
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => trigger.complete()));
|
|
857
|
+
return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readFragments(op.artifact, fragmentRef), () => cache.subscribeFragments(op.artifact, fragmentRef, async () => {
|
|
858
|
+
await Promise.resolve();
|
|
859
|
+
trigger.next();
|
|
860
|
+
}))), require_make.takeUntil(teardown$), require_make.map((data) => ({
|
|
861
|
+
operation: op,
|
|
862
|
+
data,
|
|
863
|
+
errors: []
|
|
864
|
+
})));
|
|
865
|
+
}
|
|
866
|
+
if (!isFragmentRef(fragmentRef)) return require_make.fromValue({
|
|
867
|
+
operation: op,
|
|
868
|
+
data: fragmentRef,
|
|
869
|
+
errors: []
|
|
870
|
+
});
|
|
871
|
+
const trigger = require_make.makeSubject();
|
|
872
|
+
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => trigger.complete()));
|
|
873
|
+
return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readFragment(op.artifact, fragmentRef), () => cache.subscribeFragment(op.artifact, fragmentRef, async () => {
|
|
874
|
+
await Promise.resolve();
|
|
875
|
+
trigger.next();
|
|
876
|
+
}))), require_make.takeUntil(teardown$), require_make.map((data) => ({
|
|
877
|
+
operation: op,
|
|
878
|
+
data,
|
|
879
|
+
errors: []
|
|
880
|
+
})));
|
|
881
|
+
}));
|
|
882
|
+
const nonCache$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")));
|
|
883
|
+
const query$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), require_make.share());
|
|
884
|
+
const refetch$ = require_make.makeSubject();
|
|
885
|
+
return require_make.merge(fragment$, require_make.pipe(query$, require_make.mergeMap((op) => {
|
|
886
|
+
const trigger = require_make.makeSubject();
|
|
887
|
+
let hasData = false;
|
|
888
|
+
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key), require_make.tap(() => trigger.complete()));
|
|
889
|
+
return require_make.pipe(require_make.merge(require_make.fromValue(void 0), trigger.source), require_make.switchMap(() => require_make.fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async () => {
|
|
890
|
+
await Promise.resolve();
|
|
891
|
+
trigger.next();
|
|
892
|
+
}))), require_make.takeUntil(teardown$), require_make.mergeMap((data) => {
|
|
893
|
+
if (data !== null) {
|
|
894
|
+
hasData = true;
|
|
895
|
+
return require_make.fromValue({
|
|
896
|
+
operation: op,
|
|
897
|
+
data,
|
|
898
|
+
errors: []
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
if (hasData) {
|
|
902
|
+
refetch$.next(op);
|
|
903
|
+
return empty();
|
|
904
|
+
}
|
|
905
|
+
if (fetchPolicy === "cache-only") return require_make.fromValue({
|
|
906
|
+
operation: op,
|
|
907
|
+
data: null,
|
|
908
|
+
errors: []
|
|
909
|
+
});
|
|
910
|
+
return empty();
|
|
911
|
+
}));
|
|
912
|
+
}), require_make.filter(() => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" || fetchPolicy === "cache-first")), require_make.pipe(require_make.merge(nonCache$, require_make.pipe(query$, require_make.filter((op) => {
|
|
913
|
+
const cached = cache.readQuery(op.artifact, op.variables);
|
|
914
|
+
return fetchPolicy === "cache-and-network" || cached === null;
|
|
915
|
+
})), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown")), refetch$.source), forward, require_make.tap((result) => {
|
|
916
|
+
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
917
|
+
}), require_make.filter((result) => result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0))));
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
};
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
//#endregion
|
|
924
|
+
//#region src/exchanges/retry.ts
|
|
925
|
+
const defaultShouldRetry = (error) => isExchangeError(error, "http") && error.extensions?.statusCode !== void 0 && error.extensions.statusCode >= 500;
|
|
926
|
+
const retryExchange = (options = {}) => {
|
|
927
|
+
const { maxAttempts = 3, backoff = (attempt) => Math.min(1e3 * 2 ** attempt, 3e4), shouldRetry = defaultShouldRetry } = options;
|
|
928
|
+
return ({ forward }) => ({
|
|
929
|
+
name: "retry",
|
|
930
|
+
io: (ops$) => {
|
|
931
|
+
const { source: retries$, next } = require_make.makeSubject();
|
|
932
|
+
const tornDown = /* @__PURE__ */ new Set();
|
|
933
|
+
const teardown$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown"), require_make.tap((op) => {
|
|
934
|
+
tornDown.add(op.key);
|
|
935
|
+
}));
|
|
936
|
+
return require_make.pipe(require_make.merge(require_make.pipe(ops$, require_make.filter((op) => op.variant === "request")), require_make.pipe(retries$, require_make.filter((op) => !tornDown.has(op.key)), require_make.mergeMap((op) => {
|
|
937
|
+
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key));
|
|
938
|
+
return require_make.pipe(require_make.fromValue(op), delay(op.metadata.retry.delay), require_make.takeUntil(teardown$));
|
|
939
|
+
})), teardown$), forward, require_make.filter((result) => {
|
|
940
|
+
if (!result.errors || result.errors.length === 0) return true;
|
|
941
|
+
if (result.operation.variant === "request" && result.operation.artifact.kind === "mutation") return true;
|
|
942
|
+
const attempt = result.operation.metadata.retry?.attempt ?? 0;
|
|
943
|
+
if (attempt >= maxAttempts - 1) return true;
|
|
944
|
+
if (!result.errors.some((error) => shouldRetry(error))) return true;
|
|
945
|
+
next({
|
|
946
|
+
...result.operation,
|
|
947
|
+
metadata: {
|
|
948
|
+
...result.operation.metadata,
|
|
949
|
+
dedup: { skip: true },
|
|
950
|
+
retry: {
|
|
951
|
+
attempt: attempt + 1,
|
|
952
|
+
delay: backoff(attempt)
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
return false;
|
|
957
|
+
}));
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
//#endregion
|
|
963
|
+
//#region src/exchanges/fragment.ts
|
|
964
|
+
const fragmentExchange = () => {
|
|
965
|
+
return ({ forward }) => ({
|
|
966
|
+
name: "fragment",
|
|
967
|
+
io: (ops$) => {
|
|
968
|
+
return require_make.merge(require_make.pipe(ops$, require_make.filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), require_make.map((op) => {
|
|
969
|
+
const fragmentRef = op.metadata.fragmentRef;
|
|
970
|
+
if (!fragmentRef) return {
|
|
971
|
+
operation: op,
|
|
972
|
+
errors: [new ExchangeError("Fragment operation missing fragmentRef in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "fragment" })]
|
|
973
|
+
};
|
|
974
|
+
return {
|
|
975
|
+
operation: op,
|
|
976
|
+
data: fragmentRef
|
|
977
|
+
};
|
|
978
|
+
})), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown" || op.artifact.kind !== "fragment"), forward));
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
//#endregion
|
|
984
|
+
//#region src/required.ts
|
|
985
|
+
const CASCADE_NULL = Symbol("CASCADE_NULL");
|
|
986
|
+
var RequiredFieldError = class extends Error {
|
|
987
|
+
fieldPath;
|
|
988
|
+
fieldName;
|
|
989
|
+
constructor(fieldPath, fieldName) {
|
|
990
|
+
super(`Required field '${fieldPath.join(".")}.${fieldName}' is null`);
|
|
991
|
+
this.name = "RequiredFieldError";
|
|
992
|
+
this.fieldPath = fieldPath;
|
|
993
|
+
this.fieldName = fieldName;
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
const getRequiredAction = (directives) => {
|
|
997
|
+
if (!directives) return null;
|
|
998
|
+
const requiredDirective = directives.find((d) => d.name === "required");
|
|
999
|
+
if (!requiredDirective) return null;
|
|
1000
|
+
if (requiredDirective.args?.action === "CASCADE") return "CASCADE";
|
|
1001
|
+
return "THROW";
|
|
1002
|
+
};
|
|
1003
|
+
const validateRequiredInner = (selections, data, fieldPath, validatedMap) => {
|
|
1004
|
+
if (data === null || data === void 0) return data;
|
|
1005
|
+
if (typeof data !== "object") return data;
|
|
1006
|
+
if (Array.isArray(data)) return data.map((item, index) => {
|
|
1007
|
+
const result = validateRequiredInner(selections, item, [...fieldPath, `[${index}]`], validatedMap);
|
|
1008
|
+
return result === CASCADE_NULL ? null : result;
|
|
1009
|
+
});
|
|
1010
|
+
const obj = data;
|
|
1011
|
+
validatedMap ??= /* @__PURE__ */ new WeakMap();
|
|
1012
|
+
let validated = validatedMap.get(obj);
|
|
1013
|
+
if (!validated) {
|
|
1014
|
+
validated = /* @__PURE__ */ new Set();
|
|
1015
|
+
validatedMap.set(obj, validated);
|
|
1016
|
+
}
|
|
1017
|
+
for (const selection of selections) if (selection.kind === "Field") {
|
|
1018
|
+
const fieldName = selection.alias ?? selection.name;
|
|
1019
|
+
if (!(fieldName in obj)) continue;
|
|
1020
|
+
const fieldValue = obj[fieldName];
|
|
1021
|
+
const action = getRequiredAction(selection.directives);
|
|
1022
|
+
if (selection.selections) {
|
|
1023
|
+
if (action && fieldValue === null) {
|
|
1024
|
+
if (action === "THROW") throw new RequiredFieldError(fieldPath, fieldName);
|
|
1025
|
+
else if (action === "CASCADE") return CASCADE_NULL;
|
|
1026
|
+
}
|
|
1027
|
+
if (fieldValue !== null && fieldValue !== void 0) {
|
|
1028
|
+
if (validateRequiredInner(selection.selections, fieldValue, [...fieldPath, fieldName], validatedMap) === CASCADE_NULL) if (selection.nullable && !getRequiredAction(selection.directives)) obj[fieldName] = null;
|
|
1029
|
+
else return CASCADE_NULL;
|
|
1030
|
+
}
|
|
1031
|
+
} else {
|
|
1032
|
+
if (validated.has(fieldName)) continue;
|
|
1033
|
+
validated.add(fieldName);
|
|
1034
|
+
if (action && fieldValue === null) {
|
|
1035
|
+
if (action === "THROW") throw new RequiredFieldError(fieldPath, fieldName);
|
|
1036
|
+
else if (action === "CASCADE") return CASCADE_NULL;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment") {
|
|
1040
|
+
if (validateRequiredInner(selection.selections, data, fieldPath, validatedMap) === CASCADE_NULL) return CASCADE_NULL;
|
|
1041
|
+
}
|
|
1042
|
+
return data;
|
|
1043
|
+
};
|
|
1044
|
+
const validateRequired = (selections, data, fieldPath = []) => {
|
|
1045
|
+
const result = validateRequiredInner(selections, data, fieldPath);
|
|
1046
|
+
return result === CASCADE_NULL ? null : result;
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
//#endregion
|
|
1050
|
+
//#region src/exchanges/required.ts
|
|
1051
|
+
const requiredExchange = () => {
|
|
1052
|
+
return ({ forward }) => ({
|
|
1053
|
+
name: "required",
|
|
1054
|
+
io: (ops$) => {
|
|
1055
|
+
return require_make.pipe(ops$, forward, require_make.map((result) => {
|
|
1056
|
+
if (result.operation.variant !== "request" || !result.data) return result;
|
|
1057
|
+
try {
|
|
1058
|
+
return {
|
|
1059
|
+
...result,
|
|
1060
|
+
data: validateRequired(result.operation.artifact.selections, result.data)
|
|
1061
|
+
};
|
|
1062
|
+
} catch (error) {
|
|
1063
|
+
return {
|
|
1064
|
+
...result,
|
|
1065
|
+
errors: [new ExchangeError(error instanceof Error ? error.message : String(error), { exchangeName: "required" })]
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
}));
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
//#endregion
|
|
1074
|
+
//#region src/exchanges/subscription.ts
|
|
1075
|
+
const shouldHandle = (op) => op.variant === "request" && (op.artifact.kind === "subscription" || op.metadata.subscription?.transport === true);
|
|
1076
|
+
/**
|
|
1077
|
+
* Creates an exchange for handling GraphQL subscriptions using a subscription client.
|
|
1078
|
+
*
|
|
1079
|
+
* This exchange accepts subscription clients from graphql-ws, graphql-sse, or any client
|
|
1080
|
+
* implementing the Observer pattern.
|
|
1081
|
+
* @internal
|
|
1082
|
+
* @param options - Configuration options for the subscription exchange.
|
|
1083
|
+
* @returns An exchange that handles subscription operations.
|
|
1084
|
+
* @example
|
|
1085
|
+
* // With graphql-ws
|
|
1086
|
+
* import { createClient } from 'graphql-ws';
|
|
1087
|
+
*
|
|
1088
|
+
* const wsClient = createClient({
|
|
1089
|
+
* url: 'ws://localhost:4000/graphql',
|
|
1090
|
+
* });
|
|
1091
|
+
*
|
|
1092
|
+
* subscriptionExchange({ client: wsClient })
|
|
1093
|
+
* @example
|
|
1094
|
+
* // With graphql-sse
|
|
1095
|
+
* import { createClient } from 'graphql-sse';
|
|
1096
|
+
*
|
|
1097
|
+
* const sseClient = createClient({
|
|
1098
|
+
* url: 'http://localhost:4000/graphql/stream',
|
|
1099
|
+
* });
|
|
1100
|
+
*
|
|
1101
|
+
* subscriptionExchange({ client: sseClient })
|
|
1102
|
+
*/
|
|
1103
|
+
const subscriptionExchange = (options) => {
|
|
1104
|
+
const { client } = options;
|
|
1105
|
+
return ({ forward }) => ({
|
|
1106
|
+
name: "subscription",
|
|
1107
|
+
io: (ops$) => {
|
|
1108
|
+
return require_make.merge(require_make.pipe(ops$, require_make.filter(shouldHandle), require_make.mergeMap((op) => {
|
|
1109
|
+
const teardown$ = require_make.pipe(ops$, require_make.filter((operation) => operation.variant === "teardown" && operation.key === op.key));
|
|
1110
|
+
return require_make.pipe(require_make.make((observer) => {
|
|
1111
|
+
let unsubscribe;
|
|
1112
|
+
let completed = false;
|
|
1113
|
+
Promise.resolve().then(() => {
|
|
1114
|
+
if (completed) return;
|
|
1115
|
+
unsubscribe = client.subscribe({
|
|
1116
|
+
operationName: op.artifact.name,
|
|
1117
|
+
query: op.artifact.body,
|
|
1118
|
+
variables: op.variables
|
|
1119
|
+
}, {
|
|
1120
|
+
next: (result) => {
|
|
1121
|
+
const response = result;
|
|
1122
|
+
observer.next({
|
|
1123
|
+
operation: op,
|
|
1124
|
+
data: response.data,
|
|
1125
|
+
errors: response.errors?.map((err) => new GraphQLError(err.message, {
|
|
1126
|
+
path: err.path,
|
|
1127
|
+
locations: err.locations,
|
|
1128
|
+
extensions: err.extensions
|
|
1129
|
+
})),
|
|
1130
|
+
extensions: response.extensions
|
|
1131
|
+
});
|
|
1132
|
+
},
|
|
1133
|
+
error: (error) => {
|
|
1134
|
+
observer.next({
|
|
1135
|
+
operation: op,
|
|
1136
|
+
errors: [new ExchangeError(error instanceof Error ? error.message : String(error), {
|
|
1137
|
+
exchangeName: "subscription",
|
|
1138
|
+
cause: error
|
|
1139
|
+
})]
|
|
1140
|
+
});
|
|
1141
|
+
observer.complete();
|
|
1142
|
+
},
|
|
1143
|
+
complete: observer.complete
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1146
|
+
return () => {
|
|
1147
|
+
completed = true;
|
|
1148
|
+
unsubscribe?.();
|
|
1149
|
+
};
|
|
1150
|
+
}), require_make.takeUntil(teardown$));
|
|
1151
|
+
})), require_make.pipe(ops$, require_make.filter((op) => !shouldHandle(op)), forward));
|
|
1152
|
+
}
|
|
1153
|
+
});
|
|
1154
|
+
};
|
|
1155
|
+
|
|
1156
|
+
//#endregion
|
|
1157
|
+
//#region src/compose.ts
|
|
1158
|
+
/** @internal */
|
|
1159
|
+
const composeExchanges = (options, input) => {
|
|
1160
|
+
const { exchanges } = options;
|
|
1161
|
+
const { client } = input;
|
|
1162
|
+
const extensions = /* @__PURE__ */ new Map();
|
|
1163
|
+
return {
|
|
1164
|
+
io: exchanges.reduceRight((forward, exchange) => {
|
|
1165
|
+
const result = exchange({
|
|
1166
|
+
forward,
|
|
1167
|
+
client
|
|
1168
|
+
});
|
|
1169
|
+
if ("extension" in result) extensions.set(result.name, result.extension);
|
|
1170
|
+
return (ops$) => {
|
|
1171
|
+
return require_make.pipe(ops$, require_make.share(), result.io, require_make.share());
|
|
1172
|
+
};
|
|
1173
|
+
}, input.forward),
|
|
1174
|
+
extensions
|
|
1175
|
+
};
|
|
1176
|
+
};
|
|
1177
|
+
|
|
1178
|
+
//#endregion
|
|
1179
|
+
//#region src/scalars.ts
|
|
1180
|
+
const parse = (selections, scalars, value) => {
|
|
1181
|
+
const parseValue = (selection, value, parsedMap) => {
|
|
1182
|
+
if (isNullish$1(value)) return value;
|
|
1183
|
+
if (selection.array && Array.isArray(value)) return value.map((item) => parseValue({
|
|
1184
|
+
...selection,
|
|
1185
|
+
array: false
|
|
1186
|
+
}, item, parsedMap));
|
|
1187
|
+
if (selection.selections) return parseField(selection.selections, value, parsedMap);
|
|
1188
|
+
const transformer = scalars[selection.type];
|
|
1189
|
+
if (transformer) return transformer.parse(value);
|
|
1190
|
+
return value;
|
|
1191
|
+
};
|
|
1192
|
+
const parseField = (selections, value, parsedMap) => {
|
|
1193
|
+
if (isNullish$1(value)) return value;
|
|
1194
|
+
const data = value;
|
|
1195
|
+
parsedMap ??= /* @__PURE__ */ new WeakMap();
|
|
1196
|
+
let parsed = parsedMap.get(data);
|
|
1197
|
+
if (!parsed) {
|
|
1198
|
+
parsed = /* @__PURE__ */ new Set();
|
|
1199
|
+
parsedMap.set(data, parsed);
|
|
1200
|
+
}
|
|
1201
|
+
for (const selection of selections) if (selection.kind === "Field") {
|
|
1202
|
+
const fieldName = selection.alias ?? selection.name;
|
|
1203
|
+
if (!(fieldName in data)) continue;
|
|
1204
|
+
if (selection.selections) data[fieldName] = parseValue(selection, data[fieldName], parsedMap);
|
|
1205
|
+
else {
|
|
1206
|
+
if (parsed.has(fieldName)) continue;
|
|
1207
|
+
parsed.add(fieldName);
|
|
1208
|
+
data[fieldName] = parseValue(selection, data[fieldName], parsedMap);
|
|
1209
|
+
}
|
|
1210
|
+
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === data.__typename) parseField(selection.selections, value, parsedMap);
|
|
1211
|
+
return data;
|
|
1212
|
+
};
|
|
1213
|
+
return parseField(selections, value);
|
|
1214
|
+
};
|
|
1215
|
+
const serialize = (schemaMeta, variableDefs, scalars, variables) => {
|
|
1216
|
+
const serializeValue = (variableDef, value) => {
|
|
1217
|
+
if (isNullish$1(value)) return value;
|
|
1218
|
+
if (variableDef.array && Array.isArray(value)) return value.map((item) => serializeValue({
|
|
1219
|
+
...variableDef,
|
|
1220
|
+
array: false
|
|
1221
|
+
}, item));
|
|
1222
|
+
const input = schemaMeta.inputs[variableDef.type];
|
|
1223
|
+
if (input) return serializeField(input.fields, value);
|
|
1224
|
+
const transformer = scalars[variableDef.type];
|
|
1225
|
+
if (transformer) return transformer.serialize(value);
|
|
1226
|
+
return value;
|
|
1227
|
+
};
|
|
1228
|
+
const serializeField = (variableDefs, value) => {
|
|
1229
|
+
if (isNullish$1(value)) return value;
|
|
1230
|
+
const data = value;
|
|
1231
|
+
const fields = {};
|
|
1232
|
+
for (const variableDef of variableDefs) {
|
|
1233
|
+
const variableValue = data[variableDef.name];
|
|
1234
|
+
fields[variableDef.name] = serializeValue(variableDef, variableValue);
|
|
1235
|
+
}
|
|
1236
|
+
return fields;
|
|
1237
|
+
};
|
|
1238
|
+
return serializeField(variableDefs, variables);
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
//#endregion
|
|
1242
|
+
//#region src/exchanges/scalar.ts
|
|
1243
|
+
const scalarExchange = () => {
|
|
1244
|
+
return ({ forward, client }) => ({
|
|
1245
|
+
name: "scalar",
|
|
1246
|
+
io: (ops$) => {
|
|
1247
|
+
return require_make.pipe(ops$, require_make.map((op) => {
|
|
1248
|
+
if (op.variant !== "request" || !op.artifact.variableDefs || !client.scalars) return op;
|
|
1249
|
+
return {
|
|
1250
|
+
...op,
|
|
1251
|
+
variables: serialize(client.schema, op.artifact.variableDefs, client.scalars, op.variables)
|
|
1252
|
+
};
|
|
1253
|
+
}), forward, require_make.map((result) => {
|
|
1254
|
+
if (result.operation.variant !== "request" || !result.data || !client.scalars) return result;
|
|
1255
|
+
return {
|
|
1256
|
+
...result,
|
|
1257
|
+
data: parse(result.operation.artifact.selections, client.scalars, result.data)
|
|
1258
|
+
};
|
|
1259
|
+
}));
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
//#endregion
|
|
1265
|
+
//#region src/exchanges/terminal.ts
|
|
1266
|
+
const terminalExchange = () => {
|
|
1267
|
+
return () => ({
|
|
1268
|
+
name: "terminal",
|
|
1269
|
+
io: (ops$) => {
|
|
1270
|
+
return require_make.pipe(ops$, require_make.filter((op) => op.variant !== "teardown"), require_make.mergeMap((op) => require_make.fromValue({
|
|
1271
|
+
operation: op,
|
|
1272
|
+
errors: [new ExchangeError("No terminal exchange found in exchange chain. Did you forget to add httpExchange to your exchanges array?", { exchangeName: "terminal" })]
|
|
1273
|
+
})));
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
//#endregion
|
|
1279
|
+
//#region src/stream/sources/never.ts
|
|
1280
|
+
/**
|
|
1281
|
+
* Creates a source that never emits any values.
|
|
1282
|
+
* @returns A never source.
|
|
1283
|
+
*/
|
|
1284
|
+
const never = () => {
|
|
1285
|
+
return () => {
|
|
1286
|
+
return { unsubscribe() {} };
|
|
1287
|
+
};
|
|
1288
|
+
};
|
|
1289
|
+
|
|
1290
|
+
//#endregion
|
|
1291
|
+
//#region src/client.ts
|
|
1292
|
+
var Client = class {
|
|
1293
|
+
#schema;
|
|
1294
|
+
#scalars;
|
|
1295
|
+
#extensions;
|
|
1296
|
+
operations$;
|
|
1297
|
+
results$;
|
|
1298
|
+
constructor(config) {
|
|
1299
|
+
this.#schema = config.schema;
|
|
1300
|
+
this.#scalars = config.scalars;
|
|
1301
|
+
const { io, extensions } = composeExchanges({ exchanges: [
|
|
1302
|
+
requiredExchange(),
|
|
1303
|
+
scalarExchange(),
|
|
1304
|
+
...config.exchanges,
|
|
1305
|
+
fragmentExchange(),
|
|
1306
|
+
terminalExchange()
|
|
1307
|
+
] }, {
|
|
1308
|
+
forward: never,
|
|
1309
|
+
client: this
|
|
1310
|
+
});
|
|
1311
|
+
this.#extensions = extensions;
|
|
1312
|
+
this.operations$ = require_make.makeSubject();
|
|
1313
|
+
this.results$ = io(this.operations$.source);
|
|
1314
|
+
}
|
|
1315
|
+
get schema() {
|
|
1316
|
+
return this.#schema;
|
|
1317
|
+
}
|
|
1318
|
+
get scalars() {
|
|
1319
|
+
return this.#scalars;
|
|
1320
|
+
}
|
|
1321
|
+
createOperationKey() {
|
|
1322
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
1323
|
+
}
|
|
1324
|
+
createOperation(artifact, variables, metadata) {
|
|
1325
|
+
return {
|
|
1326
|
+
variant: "request",
|
|
1327
|
+
key: this.createOperationKey(),
|
|
1328
|
+
metadata: { ...metadata },
|
|
1329
|
+
artifact,
|
|
1330
|
+
variables: variables ?? {}
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
executeOperation(operation) {
|
|
1334
|
+
return require_make.pipe(this.results$, require_make.initialize(() => this.operations$.next(operation)), require_make.filter((result) => result.operation.key === operation.key), require_make.finalize(() => this.operations$.next({
|
|
1335
|
+
variant: "teardown",
|
|
1336
|
+
key: operation.key,
|
|
1337
|
+
metadata: {}
|
|
1338
|
+
})), require_make.share());
|
|
1339
|
+
}
|
|
1340
|
+
executeQuery(artifact, ...[variables, options]) {
|
|
1341
|
+
const operation = this.createOperation(artifact, variables, options?.metadata);
|
|
1342
|
+
return this.executeOperation(operation);
|
|
1343
|
+
}
|
|
1344
|
+
executeMutation(artifact, ...[variables, options]) {
|
|
1345
|
+
const operation = this.createOperation(artifact, variables, options?.metadata);
|
|
1346
|
+
return this.executeOperation(operation);
|
|
1347
|
+
}
|
|
1348
|
+
executeSubscription(artifact, ...[variables, options]) {
|
|
1349
|
+
const operation = this.createOperation(artifact, variables, options?.metadata);
|
|
1350
|
+
return this.executeOperation(operation);
|
|
1351
|
+
}
|
|
1352
|
+
executeFragment(artifact, fragmentRef, options) {
|
|
1353
|
+
const operation = {
|
|
1354
|
+
variant: "request",
|
|
1355
|
+
key: this.createOperationKey(),
|
|
1356
|
+
metadata: {
|
|
1357
|
+
...options?.metadata,
|
|
1358
|
+
fragmentRef
|
|
1359
|
+
},
|
|
1360
|
+
artifact,
|
|
1361
|
+
variables: {}
|
|
1362
|
+
};
|
|
1363
|
+
return this.executeOperation(operation);
|
|
1364
|
+
}
|
|
1365
|
+
async query(artifact, ...[variables, options]) {
|
|
1366
|
+
const operation = this.createOperation(artifact, variables, options?.metadata);
|
|
1367
|
+
const result = await require_make.pipe(this.executeOperation(operation), require_make.take(1), require_make.collect);
|
|
1368
|
+
if (result.errors && result.errors.length > 0) throw new AggregatedError(result.errors);
|
|
1369
|
+
return result.data;
|
|
1370
|
+
}
|
|
1371
|
+
async mutation(artifact, ...[variables, options]) {
|
|
1372
|
+
const operation = this.createOperation(artifact, variables, options?.metadata);
|
|
1373
|
+
const result = await require_make.pipe(this.executeOperation(operation), require_make.take(1), require_make.collect);
|
|
1374
|
+
if (result.errors && result.errors.length > 0) throw new AggregatedError(result.errors);
|
|
1375
|
+
return result.data;
|
|
1376
|
+
}
|
|
1377
|
+
extension(name) {
|
|
1378
|
+
const ext = this.#extensions.get(name);
|
|
1379
|
+
if (!ext) throw new Error(`Exchange extension '${name}' is not registered. Check your exchange configuration.`);
|
|
1380
|
+
return ext;
|
|
1381
|
+
}
|
|
1382
|
+
maybeExtension(name) {
|
|
1383
|
+
return this.#extensions.get(name);
|
|
1384
|
+
}
|
|
1385
|
+
dispose() {
|
|
1386
|
+
this.operations$.complete();
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
const createClient = (config) => {
|
|
1390
|
+
return new Client(config);
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1393
|
+
//#endregion
|
|
1394
|
+
exports.AggregatedError = AggregatedError;
|
|
1395
|
+
exports.Client = Client;
|
|
1396
|
+
exports.ExchangeError = ExchangeError;
|
|
1397
|
+
exports.GraphQLError = GraphQLError;
|
|
1398
|
+
exports.RequiredFieldError = RequiredFieldError;
|
|
1399
|
+
exports.cacheExchange = cacheExchange;
|
|
1400
|
+
exports.createClient = createClient;
|
|
1401
|
+
exports.dedupExchange = dedupExchange;
|
|
1402
|
+
exports.fragmentExchange = fragmentExchange;
|
|
1403
|
+
exports.httpExchange = httpExchange;
|
|
1404
|
+
exports.isAggregatedError = isAggregatedError;
|
|
1405
|
+
exports.isExchangeError = isExchangeError;
|
|
1406
|
+
exports.isGraphQLError = isGraphQLError;
|
|
1407
|
+
exports.requiredExchange = requiredExchange;
|
|
1408
|
+
exports.retryExchange = retryExchange;
|
|
1409
|
+
exports.stringify = stringify;
|
|
1410
|
+
exports.subscriptionExchange = subscriptionExchange;
|