@mearie/core 0.0.1-next.3 → 0.0.1-next.4
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/dist/index.cjs +1292 -504
- package/dist/index.d.cts +244 -266
- package/dist/index.d.ts +244 -266
- package/dist/index.js +1281 -468
- package/package.json +19 -17
package/dist/index.cjs
CHANGED
|
@@ -1,65 +1,465 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
var GraphQLError = class GraphQLError extends Error {
|
|
7
|
+
path;
|
|
8
|
+
locations;
|
|
9
|
+
extensions;
|
|
10
|
+
constructor(message, options) {
|
|
11
|
+
super(message, { cause: options?.cause });
|
|
12
|
+
this.name = "GraphQLError";
|
|
13
|
+
this.path = options?.path;
|
|
14
|
+
this.locations = options?.locations;
|
|
15
|
+
this.extensions = options?.extensions;
|
|
16
|
+
Object.setPrototypeOf(this, GraphQLError.prototype);
|
|
17
|
+
}
|
|
18
|
+
toJSON() {
|
|
19
|
+
const json = { message: this.message };
|
|
20
|
+
if (this.path) json.path = this.path;
|
|
21
|
+
if (this.locations) json.locations = this.locations;
|
|
22
|
+
if (this.extensions) json.extensions = this.extensions;
|
|
23
|
+
return json;
|
|
15
24
|
}
|
|
16
|
-
return to;
|
|
17
25
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
*/
|
|
29
|
+
var ExchangeError = class ExchangeError extends Error {
|
|
30
|
+
exchangeName;
|
|
31
|
+
extensions;
|
|
32
|
+
constructor(message, options) {
|
|
33
|
+
super(message, { cause: options.cause });
|
|
34
|
+
this.name = "ExchangeError";
|
|
35
|
+
this.exchangeName = options.exchangeName;
|
|
36
|
+
this.extensions = options.extensions;
|
|
37
|
+
Object.setPrototypeOf(this, ExchangeError.prototype);
|
|
38
|
+
}
|
|
39
|
+
toJSON() {
|
|
40
|
+
const json = {
|
|
41
|
+
message: this.message,
|
|
42
|
+
exchangeName: this.exchangeName
|
|
43
|
+
};
|
|
44
|
+
if (this.extensions !== void 0) json.extensions = this.extensions;
|
|
45
|
+
return json;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
*/
|
|
51
|
+
var AggregatedError = class AggregatedError extends AggregateError {
|
|
52
|
+
constructor(errors, message = `${errors.length} error(s) occurred`) {
|
|
53
|
+
super([...errors], message);
|
|
54
|
+
this.name = "AggregatedError";
|
|
55
|
+
Object.setPrototypeOf(this, AggregatedError.prototype);
|
|
56
|
+
}
|
|
57
|
+
toJSON() {
|
|
58
|
+
return {
|
|
59
|
+
message: this.message,
|
|
60
|
+
errors: this.errors.map((error) => error.toJSON())
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const isGraphQLError = (error) => {
|
|
65
|
+
return error instanceof GraphQLError;
|
|
66
|
+
};
|
|
67
|
+
function isExchangeError(error, exchangeName) {
|
|
68
|
+
if (!(error instanceof ExchangeError)) return false;
|
|
69
|
+
if (exchangeName !== void 0) return error.exchangeName === exchangeName;
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
const isAggregatedError = (error) => {
|
|
73
|
+
return error instanceof AggregatedError;
|
|
74
|
+
};
|
|
22
75
|
|
|
23
76
|
//#endregion
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
77
|
+
//#region src/stream/pipe.ts
|
|
78
|
+
/**
|
|
79
|
+
* @param source - The source stream.
|
|
80
|
+
* @param operators - The operators to apply.
|
|
81
|
+
* @returns The result of the last operator.
|
|
82
|
+
*/
|
|
83
|
+
function pipe(source, ...operators) {
|
|
84
|
+
return operators.reduce((src, operator) => operator(src), source);
|
|
85
|
+
}
|
|
28
86
|
|
|
29
|
-
//#
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/stream/operators/share.ts
|
|
89
|
+
/**
|
|
90
|
+
* Shares a single source across multiple subscribers (multicast).
|
|
91
|
+
* The source is only executed once, and all subscribers receive the same values.
|
|
92
|
+
* This is essential for deduplication and caching scenarios.
|
|
93
|
+
* @returns An operator that shares the source.
|
|
94
|
+
*/
|
|
95
|
+
const share = () => {
|
|
96
|
+
return (source) => {
|
|
97
|
+
const sinks = [];
|
|
98
|
+
let subscription = null;
|
|
99
|
+
let started = false;
|
|
100
|
+
let completed = false;
|
|
101
|
+
return (sink) => {
|
|
102
|
+
if (completed) {
|
|
103
|
+
sink.complete();
|
|
104
|
+
return { unsubscribe() {} };
|
|
105
|
+
}
|
|
106
|
+
sinks.push(sink);
|
|
107
|
+
if (!started) {
|
|
108
|
+
started = true;
|
|
109
|
+
subscription = source({
|
|
110
|
+
next(value) {
|
|
111
|
+
for (const s of [...sinks]) {
|
|
112
|
+
if (completed) break;
|
|
113
|
+
s.next(value);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
complete() {
|
|
117
|
+
if (!completed) {
|
|
118
|
+
completed = true;
|
|
119
|
+
for (const s of [...sinks]) s.complete();
|
|
120
|
+
sinks.length = 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return { unsubscribe() {
|
|
126
|
+
const idx = sinks.indexOf(sink);
|
|
127
|
+
if (idx !== -1) sinks.splice(idx, 1);
|
|
128
|
+
if (sinks.length === 0 && subscription) {
|
|
129
|
+
subscription.unsubscribe();
|
|
130
|
+
subscription = null;
|
|
131
|
+
started = false;
|
|
132
|
+
}
|
|
133
|
+
} };
|
|
134
|
+
};
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/exchanges/compose.ts
|
|
140
|
+
const composeExchange = (options) => {
|
|
141
|
+
const { exchanges } = options;
|
|
142
|
+
return ({ forward, client }) => {
|
|
143
|
+
return exchanges.reduceRight((forward$1, exchange) => {
|
|
144
|
+
return (ops$) => {
|
|
145
|
+
return pipe(ops$, share(), exchange({
|
|
146
|
+
forward: forward$1,
|
|
147
|
+
client
|
|
148
|
+
}), share());
|
|
149
|
+
};
|
|
150
|
+
}, forward);
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/stream/operators/merge-map.ts
|
|
30
156
|
/**
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
157
|
+
* Maps each value to a source and flattens all sources into a single output source.
|
|
158
|
+
* Similar to flatMap. Values from all inner sources are merged concurrently.
|
|
159
|
+
* @param fn - Function that returns a source for each value.
|
|
160
|
+
* @returns An operator that flattens mapped sources.
|
|
161
|
+
*/
|
|
162
|
+
const mergeMap = (fn) => {
|
|
163
|
+
return (source) => {
|
|
164
|
+
return (sink) => {
|
|
165
|
+
let outerCompleted = false;
|
|
166
|
+
let activeInner = 0;
|
|
167
|
+
let ended = false;
|
|
168
|
+
const innerSubscriptions = [];
|
|
169
|
+
const checkComplete = () => {
|
|
170
|
+
if (outerCompleted && activeInner === 0 && !ended) {
|
|
171
|
+
ended = true;
|
|
172
|
+
sink.complete();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const outerSubscription = source({
|
|
176
|
+
next(value) {
|
|
177
|
+
if (ended) return;
|
|
178
|
+
activeInner++;
|
|
179
|
+
const innerSubscription = fn(value)({
|
|
180
|
+
next(innerValue) {
|
|
181
|
+
if (!ended) sink.next(innerValue);
|
|
182
|
+
},
|
|
183
|
+
complete() {
|
|
184
|
+
activeInner--;
|
|
185
|
+
checkComplete();
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
innerSubscriptions.push(innerSubscription);
|
|
189
|
+
},
|
|
190
|
+
complete() {
|
|
191
|
+
outerCompleted = true;
|
|
192
|
+
checkComplete();
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
return { unsubscribe() {
|
|
196
|
+
ended = true;
|
|
197
|
+
outerSubscription.unsubscribe();
|
|
198
|
+
for (const sub of innerSubscriptions) sub.unsubscribe();
|
|
199
|
+
innerSubscriptions.length = 0;
|
|
200
|
+
} };
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/stream/operators/filter.ts
|
|
207
|
+
function filter(predicate) {
|
|
208
|
+
return (source) => {
|
|
209
|
+
return (sink) => {
|
|
210
|
+
return source({
|
|
211
|
+
next(value) {
|
|
212
|
+
if (predicate(value)) sink.next(value);
|
|
213
|
+
},
|
|
214
|
+
complete() {
|
|
215
|
+
sink.complete();
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/stream/sources/from-promise.ts
|
|
224
|
+
const fromPromise = (promise) => {
|
|
225
|
+
return (sink) => {
|
|
226
|
+
let cancelled = false;
|
|
227
|
+
promise.then((value) => {
|
|
228
|
+
if (!cancelled) {
|
|
229
|
+
sink.next(value);
|
|
230
|
+
sink.complete();
|
|
231
|
+
}
|
|
232
|
+
}, () => {
|
|
233
|
+
if (!cancelled) sink.complete();
|
|
234
|
+
});
|
|
235
|
+
return { unsubscribe() {
|
|
236
|
+
cancelled = true;
|
|
237
|
+
} };
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
//#endregion
|
|
242
|
+
//#region src/stream/operators/merge.ts
|
|
243
|
+
/**
|
|
244
|
+
* Merges multiple sources into a single source.
|
|
245
|
+
* Values are emitted as soon as they arrive from any source.
|
|
246
|
+
* Completes when all sources complete.
|
|
247
|
+
* @param sources - The sources to merge.
|
|
248
|
+
* @returns A merged source.
|
|
249
|
+
*/
|
|
250
|
+
const merge = (...sources) => {
|
|
251
|
+
return (sink) => {
|
|
252
|
+
if (sources.length === 0) {
|
|
253
|
+
sink.complete();
|
|
254
|
+
return { unsubscribe() {} };
|
|
255
|
+
}
|
|
256
|
+
let activeCount = sources.length;
|
|
257
|
+
const subscriptions = [];
|
|
258
|
+
let ended = false;
|
|
259
|
+
let ready = false;
|
|
260
|
+
const buffer = [];
|
|
261
|
+
const checkComplete = () => {
|
|
262
|
+
if (activeCount === 0 && !ended) {
|
|
263
|
+
ended = true;
|
|
264
|
+
sink.complete();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
for (const source of sources) {
|
|
268
|
+
const subscription = source({
|
|
269
|
+
next(value) {
|
|
270
|
+
if (!ended) if (ready) sink.next(value);
|
|
271
|
+
else buffer.push(value);
|
|
272
|
+
},
|
|
273
|
+
complete() {
|
|
274
|
+
activeCount--;
|
|
275
|
+
if (ready) checkComplete();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
subscriptions.push(subscription);
|
|
279
|
+
}
|
|
280
|
+
ready = true;
|
|
281
|
+
for (const value of buffer) if (!ended) sink.next(value);
|
|
282
|
+
buffer.length = 0;
|
|
283
|
+
checkComplete();
|
|
284
|
+
return { unsubscribe() {
|
|
285
|
+
ended = true;
|
|
286
|
+
for (const sub of subscriptions) sub.unsubscribe();
|
|
287
|
+
} };
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
//#endregion
|
|
292
|
+
//#region src/stream/operators/tap.ts
|
|
293
|
+
/**
|
|
294
|
+
* Executes a side effect for each value without modifying the stream.
|
|
295
|
+
* Useful for debugging, logging, or triggering side effects.
|
|
296
|
+
* @param fn - The side effect function.
|
|
297
|
+
* @returns An operator that taps into the stream.
|
|
298
|
+
*/
|
|
299
|
+
const tap = (fn) => {
|
|
300
|
+
return (source) => {
|
|
301
|
+
return (sink) => {
|
|
302
|
+
return source({
|
|
303
|
+
next(value) {
|
|
304
|
+
fn(value);
|
|
305
|
+
sink.next(value);
|
|
306
|
+
},
|
|
307
|
+
complete() {
|
|
308
|
+
sink.complete();
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/exchanges/http.ts
|
|
317
|
+
const executeFetch = async ({ url, fetchOptions, operation, signal }) => {
|
|
318
|
+
const { artifact, variables } = operation;
|
|
319
|
+
let response;
|
|
320
|
+
try {
|
|
321
|
+
await Promise.resolve();
|
|
322
|
+
response = await fetch(url, {
|
|
323
|
+
method: "POST",
|
|
324
|
+
mode: fetchOptions.mode,
|
|
325
|
+
credentials: fetchOptions.credentials,
|
|
326
|
+
headers: {
|
|
327
|
+
"Content-Type": "application/json",
|
|
328
|
+
...fetchOptions.headers
|
|
329
|
+
},
|
|
330
|
+
body: JSON.stringify({
|
|
331
|
+
query: artifact.body,
|
|
332
|
+
variables
|
|
333
|
+
}),
|
|
334
|
+
signal
|
|
335
|
+
});
|
|
336
|
+
} catch (error) {
|
|
337
|
+
if (error instanceof Error && error.name === "AbortError") return null;
|
|
338
|
+
return {
|
|
339
|
+
operation,
|
|
340
|
+
errors: [new ExchangeError(error instanceof Error ? error.message : "Network error", {
|
|
341
|
+
exchangeName: "http",
|
|
342
|
+
cause: error
|
|
343
|
+
})]
|
|
344
|
+
};
|
|
43
345
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
346
|
+
if (!response.ok) return {
|
|
347
|
+
operation,
|
|
348
|
+
errors: [new ExchangeError(`HTTP ${response.status}: ${response.statusText}`, {
|
|
349
|
+
exchangeName: "http",
|
|
350
|
+
extensions: { statusCode: response.status }
|
|
351
|
+
})]
|
|
352
|
+
};
|
|
353
|
+
let json;
|
|
354
|
+
try {
|
|
355
|
+
json = await response.json();
|
|
356
|
+
} catch (error) {
|
|
357
|
+
return {
|
|
358
|
+
operation,
|
|
359
|
+
errors: [new ExchangeError(error instanceof Error ? error.message : "JSON parse error", {
|
|
360
|
+
exchangeName: "http",
|
|
361
|
+
cause: error
|
|
362
|
+
})]
|
|
363
|
+
};
|
|
51
364
|
}
|
|
365
|
+
return {
|
|
366
|
+
operation,
|
|
367
|
+
data: json.data,
|
|
368
|
+
errors: json.errors?.map((err) => new GraphQLError(err.message, {
|
|
369
|
+
path: err.path,
|
|
370
|
+
locations: err.locations,
|
|
371
|
+
extensions: err.extensions
|
|
372
|
+
})),
|
|
373
|
+
extensions: json.extensions
|
|
374
|
+
};
|
|
52
375
|
};
|
|
376
|
+
const httpExchange = (options) => {
|
|
377
|
+
const { url, headers, mode, credentials } = options;
|
|
378
|
+
return ({ forward }) => {
|
|
379
|
+
return (ops$) => {
|
|
380
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
381
|
+
return merge(pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "query" || op.artifact.kind === "mutation")), mergeMap((op) => {
|
|
382
|
+
inflight.get(op.key)?.abort();
|
|
383
|
+
const controller = new AbortController();
|
|
384
|
+
inflight.set(op.key, controller);
|
|
385
|
+
return fromPromise(executeFetch({
|
|
386
|
+
url,
|
|
387
|
+
fetchOptions: {
|
|
388
|
+
mode,
|
|
389
|
+
credentials,
|
|
390
|
+
headers
|
|
391
|
+
},
|
|
392
|
+
operation: op,
|
|
393
|
+
signal: controller.signal
|
|
394
|
+
}).then((result) => {
|
|
395
|
+
inflight.delete(op.key);
|
|
396
|
+
return result;
|
|
397
|
+
}));
|
|
398
|
+
}), filter((result) => result !== null)), pipe(ops$, filter((op) => op.variant === "teardown" || op.variant === "request" && (op.artifact.kind === "subscription" || op.artifact.kind === "fragment")), tap((op) => {
|
|
399
|
+
if (op.variant === "teardown") {
|
|
400
|
+
inflight.get(op.key)?.abort();
|
|
401
|
+
inflight.delete(op.key);
|
|
402
|
+
}
|
|
403
|
+
}), forward));
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
//#endregion
|
|
409
|
+
//#region src/stream/operators/delay.ts
|
|
53
410
|
/**
|
|
54
|
-
*
|
|
411
|
+
* Delays each value emitted by a source by the specified time.
|
|
412
|
+
* @param ms - The time (in milliseconds) to delay each value.
|
|
413
|
+
* @returns An operator that delays values.
|
|
55
414
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
415
|
+
const delay = (ms) => {
|
|
416
|
+
return (source) => {
|
|
417
|
+
return (sink) => {
|
|
418
|
+
let cancelled = false;
|
|
419
|
+
const timeouts = [];
|
|
420
|
+
const upstreamSubscription = source({
|
|
421
|
+
next(value) {
|
|
422
|
+
const timeout = setTimeout(() => {
|
|
423
|
+
if (!cancelled) sink.next(value);
|
|
424
|
+
}, ms);
|
|
425
|
+
timeouts.push(timeout);
|
|
426
|
+
},
|
|
427
|
+
complete() {
|
|
428
|
+
const timeout = setTimeout(() => {
|
|
429
|
+
if (!cancelled) sink.complete();
|
|
430
|
+
}, ms);
|
|
431
|
+
timeouts.push(timeout);
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
return { unsubscribe() {
|
|
435
|
+
cancelled = true;
|
|
436
|
+
for (const timeout of timeouts) clearTimeout(timeout);
|
|
437
|
+
timeouts.length = 0;
|
|
438
|
+
upstreamSubscription.unsubscribe();
|
|
439
|
+
} };
|
|
440
|
+
};
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
//#endregion
|
|
445
|
+
//#region src/stream/sources/from-array.ts
|
|
446
|
+
/**
|
|
447
|
+
* Creates a source that emits values from an array and completes.
|
|
448
|
+
* @param values - The array of values to emit.
|
|
449
|
+
* @returns A source containing the array values.
|
|
450
|
+
*/
|
|
451
|
+
const fromArray = (values) => {
|
|
452
|
+
return (sink) => {
|
|
453
|
+
let cancelled = false;
|
|
454
|
+
for (const value of values) {
|
|
455
|
+
if (cancelled) break;
|
|
456
|
+
sink.next(value);
|
|
457
|
+
}
|
|
458
|
+
if (!cancelled) sink.complete();
|
|
459
|
+
return { unsubscribe() {
|
|
460
|
+
cancelled = true;
|
|
461
|
+
} };
|
|
462
|
+
};
|
|
63
463
|
};
|
|
64
464
|
|
|
65
465
|
//#endregion
|
|
@@ -71,264 +471,378 @@ var MearieAggregateError = class extends Error {
|
|
|
71
471
|
* @param value - The value to stringify.
|
|
72
472
|
* @returns The stable JSON string.
|
|
73
473
|
*/
|
|
74
|
-
const
|
|
474
|
+
const stringify = (value) => {
|
|
75
475
|
if (value === null) return "null";
|
|
76
476
|
if (value === void 0) return "undefined";
|
|
77
477
|
const type = typeof value;
|
|
78
478
|
if (type === "string") return JSON.stringify(value);
|
|
79
479
|
if (type === "number" || type === "boolean") return String(value);
|
|
80
|
-
if (Array.isArray(value)) return "[" + value.map((v) =>
|
|
480
|
+
if (Array.isArray(value)) return "[" + value.map((v) => stringify(v)).join(",") + "]";
|
|
81
481
|
if (type === "object") {
|
|
82
482
|
const obj = value;
|
|
83
|
-
return "{" + Object.keys(obj).toSorted().map((k) => `"${k}":${
|
|
483
|
+
return "{" + Object.keys(obj).toSorted().filter((k) => obj[k] !== void 0).map((k) => `"${k}":${stringify(obj[k])}`).join(",") + "}";
|
|
84
484
|
}
|
|
85
485
|
return JSON.stringify(value) ?? "";
|
|
86
486
|
};
|
|
87
487
|
/**
|
|
88
|
-
*
|
|
488
|
+
* Type guard to check if a value is nullish.
|
|
89
489
|
* @internal
|
|
90
|
-
* @param
|
|
91
|
-
* @returns
|
|
92
|
-
*/
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
const FNV_PRIME = 16777619;
|
|
96
|
-
let hash = FNV_OFFSET;
|
|
97
|
-
for (let i = 0; i < str.length; i++) {
|
|
98
|
-
hash ^= str.codePointAt(i) ?? 0;
|
|
99
|
-
hash = Math.imul(hash, FNV_PRIME);
|
|
100
|
-
}
|
|
101
|
-
return hash >>> 0;
|
|
490
|
+
* @param value - Value to check.
|
|
491
|
+
* @returns True if the value is nullish.
|
|
492
|
+
*/
|
|
493
|
+
const isNullish = (value) => {
|
|
494
|
+
return value === void 0 || value === null;
|
|
102
495
|
};
|
|
496
|
+
|
|
497
|
+
//#endregion
|
|
498
|
+
//#region src/stream/operators/map.ts
|
|
103
499
|
/**
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* @
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
500
|
+
* Maps each value from the source through a transformation function.
|
|
501
|
+
* @param fn - The transformation function.
|
|
502
|
+
* @returns An operator that maps values.
|
|
503
|
+
*/
|
|
504
|
+
const map = (fn) => {
|
|
505
|
+
return (source) => {
|
|
506
|
+
return (sink) => {
|
|
507
|
+
return source({
|
|
508
|
+
next(value) {
|
|
509
|
+
sink.next(fn(value));
|
|
510
|
+
},
|
|
511
|
+
complete() {
|
|
512
|
+
sink.complete();
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
};
|
|
516
|
+
};
|
|
119
517
|
};
|
|
120
518
|
|
|
121
519
|
//#endregion
|
|
122
|
-
//#region src/
|
|
123
|
-
(0, __logtape_logtape.configureSync)({
|
|
124
|
-
sinks: { console: (0, __logtape_logtape.getConsoleSink)({ formatter: (0, __logtape_logtape.getAnsiColorFormatter)({
|
|
125
|
-
level: "FULL",
|
|
126
|
-
timestamp: "time",
|
|
127
|
-
category: (category) => `💬 ${category.join("·")}`
|
|
128
|
-
}) }) },
|
|
129
|
-
loggers: [{
|
|
130
|
-
category: "mearie",
|
|
131
|
-
lowestLevel: "info",
|
|
132
|
-
sinks: ["console"]
|
|
133
|
-
}, {
|
|
134
|
-
category: ["logtape", "meta"],
|
|
135
|
-
lowestLevel: "warning",
|
|
136
|
-
sinks: ["console"]
|
|
137
|
-
}]
|
|
138
|
-
});
|
|
139
|
-
const logger = (0, __logtape_logtape.getLogger)(["mearie"]);
|
|
140
|
-
const formatMearieError = (error) => {
|
|
141
|
-
const parts = [
|
|
142
|
-
error.filePath,
|
|
143
|
-
error.line,
|
|
144
|
-
error.column
|
|
145
|
-
].filter((part) => part !== void 0 && part !== null).map(String);
|
|
146
|
-
const location = parts.length > 0 ? parts.join(":") : "";
|
|
147
|
-
if (location) return `${picocolors.default.bold(error.message)} ${picocolors.default.cyan(picocolors.default.underline(location))}`;
|
|
148
|
-
return picocolors.default.bold(error.message);
|
|
149
|
-
};
|
|
520
|
+
//#region src/stream/operators/take-until.ts
|
|
150
521
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
* @param
|
|
522
|
+
* Emits values from the source until the notifier source emits a value.
|
|
523
|
+
* When the notifier emits, the source is cancelled and completes immediately.
|
|
524
|
+
* @param notifier - Source that signals when to complete.
|
|
525
|
+
* @returns Operator that completes when notifier emits.
|
|
154
526
|
*/
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
527
|
+
const takeUntil = (notifier) => {
|
|
528
|
+
return (source) => {
|
|
529
|
+
return (sink) => {
|
|
530
|
+
let sourceSubscription = null;
|
|
531
|
+
let notifierSubscription = null;
|
|
532
|
+
let completed = false;
|
|
533
|
+
const complete = () => {
|
|
534
|
+
if (completed) return;
|
|
535
|
+
completed = true;
|
|
536
|
+
if (sourceSubscription) sourceSubscription.unsubscribe();
|
|
537
|
+
if (notifierSubscription) notifierSubscription.unsubscribe();
|
|
538
|
+
sink.complete();
|
|
539
|
+
};
|
|
540
|
+
notifierSubscription = notifier({
|
|
541
|
+
next() {
|
|
542
|
+
complete();
|
|
543
|
+
},
|
|
544
|
+
complete() {}
|
|
545
|
+
});
|
|
546
|
+
sourceSubscription = source({
|
|
547
|
+
next(value) {
|
|
548
|
+
if (!completed) sink.next(value);
|
|
549
|
+
},
|
|
550
|
+
complete() {
|
|
551
|
+
complete();
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
return { unsubscribe() {
|
|
555
|
+
complete();
|
|
556
|
+
} };
|
|
557
|
+
};
|
|
558
|
+
};
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/stream/operators/switch-map.ts
|
|
563
|
+
const switchMap = (fn) => {
|
|
564
|
+
return (source) => {
|
|
565
|
+
return (sink) => {
|
|
566
|
+
let outerCompleted = false;
|
|
567
|
+
let ended = false;
|
|
568
|
+
let innerSubscription = null;
|
|
569
|
+
let hasInner = false;
|
|
570
|
+
const checkComplete = () => {
|
|
571
|
+
if (outerCompleted && !hasInner && !ended) {
|
|
572
|
+
ended = true;
|
|
573
|
+
sink.complete();
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
const outerSubscription = source({
|
|
577
|
+
next(value) {
|
|
578
|
+
if (ended) return;
|
|
579
|
+
if (innerSubscription) {
|
|
580
|
+
innerSubscription.unsubscribe();
|
|
581
|
+
innerSubscription = null;
|
|
582
|
+
}
|
|
583
|
+
hasInner = true;
|
|
584
|
+
innerSubscription = fn(value)({
|
|
585
|
+
next(innerValue) {
|
|
586
|
+
if (!ended) sink.next(innerValue);
|
|
587
|
+
},
|
|
588
|
+
complete() {
|
|
589
|
+
hasInner = false;
|
|
590
|
+
innerSubscription = null;
|
|
591
|
+
checkComplete();
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
},
|
|
595
|
+
complete() {
|
|
596
|
+
outerCompleted = true;
|
|
597
|
+
checkComplete();
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
return { unsubscribe() {
|
|
601
|
+
ended = true;
|
|
602
|
+
outerSubscription.unsubscribe();
|
|
603
|
+
if (innerSubscription) {
|
|
604
|
+
innerSubscription.unsubscribe();
|
|
605
|
+
innerSubscription = null;
|
|
606
|
+
}
|
|
607
|
+
} };
|
|
608
|
+
};
|
|
609
|
+
};
|
|
159
610
|
};
|
|
160
611
|
|
|
161
612
|
//#endregion
|
|
162
|
-
//#region src/
|
|
613
|
+
//#region src/stream/operators/initialize.ts
|
|
163
614
|
/**
|
|
164
|
-
*
|
|
165
|
-
* @param
|
|
166
|
-
* @
|
|
167
|
-
* @returns The link result.
|
|
615
|
+
* Executes a side effect when the source is initialized (being subscribed to).
|
|
616
|
+
* @param fn - The side effect function.
|
|
617
|
+
* @returns An operator that executes the side effect when the source is initialized.
|
|
168
618
|
*/
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
619
|
+
const initialize = (fn) => {
|
|
620
|
+
return (source) => {
|
|
621
|
+
return (sink) => {
|
|
622
|
+
let completed = false;
|
|
623
|
+
const subscription = source({
|
|
624
|
+
next(value) {
|
|
625
|
+
if (!completed) sink.next(value);
|
|
626
|
+
},
|
|
627
|
+
complete() {
|
|
628
|
+
if (!completed) {
|
|
629
|
+
completed = true;
|
|
630
|
+
sink.complete();
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
fn();
|
|
635
|
+
return { unsubscribe() {
|
|
636
|
+
completed = true;
|
|
637
|
+
subscription.unsubscribe();
|
|
638
|
+
} };
|
|
639
|
+
};
|
|
176
640
|
};
|
|
177
|
-
return dispatch();
|
|
178
641
|
};
|
|
179
642
|
|
|
180
643
|
//#endregion
|
|
181
|
-
//#region src/
|
|
644
|
+
//#region src/stream/operators/finalize.ts
|
|
182
645
|
/**
|
|
183
|
-
*
|
|
646
|
+
* Executes a side effect when the source terminates (completes or unsubscribes).
|
|
647
|
+
* @param fn - The side effect function.
|
|
648
|
+
* @returns An operator that executes the side effect when the source terminates.
|
|
184
649
|
*/
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
650
|
+
const finalize = (fn) => {
|
|
651
|
+
return (source) => {
|
|
652
|
+
return (sink) => {
|
|
653
|
+
let completed = false;
|
|
654
|
+
const subscription = source({
|
|
655
|
+
next(value) {
|
|
656
|
+
if (!completed) sink.next(value);
|
|
657
|
+
},
|
|
658
|
+
complete() {
|
|
659
|
+
if (!completed) {
|
|
660
|
+
completed = true;
|
|
661
|
+
fn();
|
|
662
|
+
sink.complete();
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
return { unsubscribe() {
|
|
667
|
+
if (!completed) {
|
|
668
|
+
completed = true;
|
|
669
|
+
fn();
|
|
670
|
+
}
|
|
671
|
+
subscription.unsubscribe();
|
|
672
|
+
} };
|
|
673
|
+
};
|
|
203
674
|
};
|
|
204
675
|
};
|
|
205
|
-
const dedupLink = createDedupLink;
|
|
206
676
|
|
|
207
677
|
//#endregion
|
|
208
|
-
//#region src/
|
|
678
|
+
//#region src/stream/sources/from-value.ts
|
|
209
679
|
/**
|
|
210
|
-
*
|
|
211
|
-
* @
|
|
680
|
+
* Creates a source that emits a single value and completes.
|
|
681
|
+
* @param value - The value to emit.
|
|
682
|
+
* @returns A source containing the single value.
|
|
212
683
|
*/
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
for (let attempt = 0; attempt < maxAttempts; attempt++) try {
|
|
220
|
-
return await next();
|
|
221
|
-
} catch (error) {
|
|
222
|
-
lastError = error;
|
|
223
|
-
if (attempt === maxAttempts - 1 || !shouldRetry(error)) throw error;
|
|
224
|
-
const delay = backoff(attempt);
|
|
225
|
-
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
226
|
-
}
|
|
227
|
-
throw lastError;
|
|
684
|
+
const fromValue = (value) => {
|
|
685
|
+
return (sink) => {
|
|
686
|
+
let cancelled = false;
|
|
687
|
+
if (!cancelled) {
|
|
688
|
+
sink.next(value);
|
|
689
|
+
sink.complete();
|
|
228
690
|
}
|
|
691
|
+
return { unsubscribe() {
|
|
692
|
+
cancelled = true;
|
|
693
|
+
} };
|
|
229
694
|
};
|
|
230
695
|
};
|
|
231
|
-
const retryLink = createRetryLink;
|
|
232
696
|
|
|
233
697
|
//#endregion
|
|
234
|
-
//#region src/
|
|
698
|
+
//#region src/stream/sources/make-subject.ts
|
|
235
699
|
/**
|
|
236
|
-
*
|
|
237
|
-
* @returns
|
|
700
|
+
* Creates a new Subject which can be used as an IO event hub.
|
|
701
|
+
* @returns A new Subject.
|
|
238
702
|
*/
|
|
239
|
-
const
|
|
240
|
-
const
|
|
703
|
+
const makeSubject = () => {
|
|
704
|
+
const sinks = [];
|
|
705
|
+
const source = (sink) => {
|
|
706
|
+
sinks.push(sink);
|
|
707
|
+
return { unsubscribe() {
|
|
708
|
+
const idx = sinks.indexOf(sink);
|
|
709
|
+
if (idx !== -1) sinks.splice(idx, 1);
|
|
710
|
+
} };
|
|
711
|
+
};
|
|
712
|
+
const next = (value) => {
|
|
713
|
+
for (const sink of [...sinks]) sink.next(value);
|
|
714
|
+
};
|
|
715
|
+
const complete = () => {
|
|
716
|
+
for (const sink of [...sinks]) sink.complete();
|
|
717
|
+
sinks.length = 0;
|
|
718
|
+
};
|
|
241
719
|
return {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const response = await fetch(url, {
|
|
246
|
-
method: "POST",
|
|
247
|
-
credentials,
|
|
248
|
-
headers: {
|
|
249
|
-
"Content-Type": "application/json",
|
|
250
|
-
...headers,
|
|
251
|
-
...operationHeaders
|
|
252
|
-
},
|
|
253
|
-
body: JSON.stringify({
|
|
254
|
-
query: document.body,
|
|
255
|
-
variables
|
|
256
|
-
}),
|
|
257
|
-
signal: ctx.signal
|
|
258
|
-
});
|
|
259
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
260
|
-
return response.json();
|
|
261
|
-
}
|
|
720
|
+
source,
|
|
721
|
+
next,
|
|
722
|
+
complete
|
|
262
723
|
};
|
|
263
724
|
};
|
|
264
|
-
const httpLink = createHttpLink;
|
|
265
725
|
|
|
266
726
|
//#endregion
|
|
267
|
-
//#region src/
|
|
727
|
+
//#region src/stream/sources/from-subscription.ts
|
|
728
|
+
const fromSubscription = (pull, poke) => {
|
|
729
|
+
return (sink) => {
|
|
730
|
+
let teardown = null;
|
|
731
|
+
let cancelled = false;
|
|
732
|
+
const initialValue = pull();
|
|
733
|
+
sink.next(initialValue);
|
|
734
|
+
if (cancelled) return { unsubscribe() {
|
|
735
|
+
cancelled = true;
|
|
736
|
+
} };
|
|
737
|
+
teardown = poke(() => {
|
|
738
|
+
if (!cancelled) {
|
|
739
|
+
const value = pull();
|
|
740
|
+
sink.next(value);
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
return { unsubscribe() {
|
|
744
|
+
cancelled = true;
|
|
745
|
+
if (teardown) {
|
|
746
|
+
teardown();
|
|
747
|
+
teardown = null;
|
|
748
|
+
}
|
|
749
|
+
} };
|
|
750
|
+
};
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
//#endregion
|
|
754
|
+
//#region src/stream/sources/make.ts
|
|
268
755
|
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
756
|
+
* Creates a new Source from scratch from a passed subscriber function.
|
|
757
|
+
*
|
|
758
|
+
* The subscriber function receives an observer with next and complete callbacks.
|
|
759
|
+
* It must return a teardown function which is called when the source is cancelled.
|
|
760
|
+
* @internal
|
|
761
|
+
* @param subscriber - A callback that is called when the Source is subscribed to.
|
|
762
|
+
* @returns A Source created from the subscriber parameter.
|
|
271
763
|
*/
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
...ctx.operation.headers,
|
|
289
|
-
[header]: `Bearer ${newToken}`
|
|
290
|
-
};
|
|
291
|
-
return next();
|
|
764
|
+
const make = (subscriber) => {
|
|
765
|
+
return (sink) => {
|
|
766
|
+
let cancelled = false;
|
|
767
|
+
let teardown = null;
|
|
768
|
+
teardown = subscriber({
|
|
769
|
+
next: (value) => {
|
|
770
|
+
if (!cancelled) sink.next(value);
|
|
771
|
+
},
|
|
772
|
+
complete: () => {
|
|
773
|
+
if (!cancelled) {
|
|
774
|
+
cancelled = true;
|
|
775
|
+
if (teardown) {
|
|
776
|
+
teardown();
|
|
777
|
+
teardown = null;
|
|
778
|
+
}
|
|
779
|
+
sink.complete();
|
|
292
780
|
}
|
|
293
|
-
throw error;
|
|
294
781
|
}
|
|
295
|
-
}
|
|
782
|
+
});
|
|
783
|
+
return { unsubscribe() {
|
|
784
|
+
cancelled = true;
|
|
785
|
+
if (teardown) {
|
|
786
|
+
teardown();
|
|
787
|
+
teardown = null;
|
|
788
|
+
}
|
|
789
|
+
} };
|
|
296
790
|
};
|
|
297
791
|
};
|
|
298
|
-
const authLink = createAuthLink;
|
|
299
792
|
|
|
300
793
|
//#endregion
|
|
301
|
-
//#region src/
|
|
794
|
+
//#region src/exchanges/dedup.ts
|
|
795
|
+
const makeDedupKey = (op) => {
|
|
796
|
+
return `${op.artifact.name}:${stringify(op.variables)}`;
|
|
797
|
+
};
|
|
302
798
|
/**
|
|
303
|
-
*
|
|
304
|
-
*
|
|
799
|
+
* Prevents duplicate in-flight operations by deduplicating requests with identical artifact names and variables.
|
|
800
|
+
*
|
|
801
|
+
* Operations are considered identical if they have the same artifact name and serialized variables.
|
|
802
|
+
* Mutations are never deduplicated. An operation is "in-flight" from when it's first seen until all subscribers tear down.
|
|
803
|
+
*
|
|
804
|
+
* Caveats:
|
|
805
|
+
*
|
|
806
|
+
* 1. Upstream metadata is lost when operations are deduplicated. The result will contain the metadata
|
|
807
|
+
* from the operation that actually went through the pipeline, not from deduplicated operations.
|
|
808
|
+
* This preserves downstream metadata (retry attempts, cache status) but means custom upstream metadata
|
|
809
|
+
* from deduplicated operations will not appear in results.
|
|
810
|
+
* @internal
|
|
811
|
+
* @returns An exchange that deduplicates in-flight operations.
|
|
305
812
|
*/
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
return
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
813
|
+
const dedupExchange = () => {
|
|
814
|
+
return ({ forward }) => {
|
|
815
|
+
return (ops$) => {
|
|
816
|
+
const operations = /* @__PURE__ */ new Map();
|
|
817
|
+
return pipe(merge(pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "fragment"))), pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind !== "mutation" && op.artifact.kind !== "fragment"), filter((op) => {
|
|
818
|
+
const dedupKey = makeDedupKey(op);
|
|
819
|
+
const isInflight = operations.has(dedupKey);
|
|
820
|
+
if (isInflight) operations.get(dedupKey).add(op.key);
|
|
821
|
+
else operations.set(dedupKey, new Set([op.key]));
|
|
822
|
+
return (op.metadata.dedup?.skip ?? false) || !isInflight;
|
|
823
|
+
}), delay(0)), pipe(ops$, filter((op) => op.variant === "teardown"), filter((teardown) => {
|
|
824
|
+
for (const [dedupKey, subs] of operations.entries()) if (subs.delete(teardown.key)) {
|
|
825
|
+
if (subs.size === 0) {
|
|
826
|
+
operations.delete(dedupKey);
|
|
827
|
+
return true;
|
|
828
|
+
}
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
return true;
|
|
832
|
+
}))), forward, mergeMap((result) => {
|
|
833
|
+
if (result.operation.variant !== "request" || result.operation.artifact.kind === "mutation" || result.operation.artifact.kind === "fragment") return fromValue(result);
|
|
834
|
+
const dedupKey = makeDedupKey(result.operation);
|
|
835
|
+
return fromArray([...operations.get(dedupKey) ?? /* @__PURE__ */ new Set()].map((key) => ({
|
|
836
|
+
...result,
|
|
837
|
+
operation: {
|
|
838
|
+
...result.operation,
|
|
839
|
+
key
|
|
840
|
+
}
|
|
841
|
+
})));
|
|
842
|
+
}));
|
|
843
|
+
};
|
|
329
844
|
};
|
|
330
845
|
};
|
|
331
|
-
const cacheLink = createCacheLink;
|
|
332
846
|
|
|
333
847
|
//#endregion
|
|
334
848
|
//#region src/cache/constants.ts
|
|
@@ -344,6 +858,12 @@ const EntityLinkKey = "__ref";
|
|
|
344
858
|
* @internal
|
|
345
859
|
*/
|
|
346
860
|
const RootFieldKey = "__root";
|
|
861
|
+
/**
|
|
862
|
+
* Special key used to mark fragment references in entities.
|
|
863
|
+
* Used for cache-agnostic fragment system.
|
|
864
|
+
* @internal
|
|
865
|
+
*/
|
|
866
|
+
const FragmentRefKey = "__fragmentRef";
|
|
347
867
|
|
|
348
868
|
//#endregion
|
|
349
869
|
//#region src/cache/utils.ts
|
|
@@ -365,42 +885,19 @@ const makeEntityKey = (typename, keyValues) => {
|
|
|
365
885
|
* @returns Object with all arguments resolved to their actual values.
|
|
366
886
|
*/
|
|
367
887
|
const resolveArguments = (args, variables) => {
|
|
368
|
-
|
|
369
|
-
for (const [key, value] of Object.entries(args)) resolved[key] = value.kind === "literal" ? value.value : variables[value.name];
|
|
370
|
-
return resolved;
|
|
888
|
+
return Object.fromEntries(Object.entries(args).map(([key, value]) => [key, value.kind === "literal" ? value.value : variables[value.name]]));
|
|
371
889
|
};
|
|
372
890
|
/**
|
|
373
891
|
* Generates a cache key for a GraphQL field selection.
|
|
374
|
-
* Always uses the actual field name (not alias) with a
|
|
892
|
+
* Always uses the actual field name (not alias) with a stringified representation of the arguments.
|
|
375
893
|
* @internal
|
|
376
|
-
* @param selection - The selection node containing field information.
|
|
894
|
+
* @param selection - The field selection node containing field information.
|
|
377
895
|
* @param variables - Variable values for resolving argument references.
|
|
378
|
-
* @returns Field cache key string in "fieldName@
|
|
896
|
+
* @returns Field cache key string in "fieldName@argsString" format.
|
|
379
897
|
*/
|
|
380
898
|
const makeFieldKey = (selection, variables) => {
|
|
381
|
-
const
|
|
382
|
-
return `${selection.name}@${
|
|
383
|
-
};
|
|
384
|
-
/**
|
|
385
|
-
* Retrieves entity metadata from the schema for a given typename.
|
|
386
|
-
* @internal
|
|
387
|
-
* @param typename - The GraphQL typename to look up.
|
|
388
|
-
* @param schemaMetadata - The schema metadata containing entity configurations.
|
|
389
|
-
* @returns Entity metadata if found, undefined otherwise.
|
|
390
|
-
*/
|
|
391
|
-
const getEntityMetadata = (typename, schemaMetadata) => {
|
|
392
|
-
return typename ? schemaMetadata.entities[typename] : void 0;
|
|
393
|
-
};
|
|
394
|
-
/**
|
|
395
|
-
* Creates a unique query key combining document hash and variables.
|
|
396
|
-
* @internal
|
|
397
|
-
* @param hash - Document hash.
|
|
398
|
-
* @param variables - Query variables.
|
|
399
|
-
* @returns Unique query key.
|
|
400
|
-
*/
|
|
401
|
-
const makeQueryKey = (hash, variables) => {
|
|
402
|
-
if (!variables || typeof variables === "object" && Object.keys(variables).length === 0) return hash;
|
|
403
|
-
return combineHashes(hash, hashString(stableStringify(variables)));
|
|
899
|
+
const args = selection.args && Object.keys(selection.args).length > 0 ? stringify(resolveArguments(selection.args, variables)) : "{}";
|
|
900
|
+
return `${selection.name}@${args}`;
|
|
404
901
|
};
|
|
405
902
|
/**
|
|
406
903
|
* Gets a unique key for tracking a field dependency.
|
|
@@ -421,109 +918,105 @@ const makeDependencyKey = (storageKey, fieldKey) => {
|
|
|
421
918
|
const isEntityLink = (value) => {
|
|
422
919
|
return typeof value === "object" && value !== null && EntityLinkKey in value;
|
|
423
920
|
};
|
|
921
|
+
/**
|
|
922
|
+
* Type guard to check if a value is a fragment reference.
|
|
923
|
+
* @internal
|
|
924
|
+
* @param value - Value to check.
|
|
925
|
+
* @returns True if the value is a FragmentRef.
|
|
926
|
+
*/
|
|
927
|
+
const isFragmentRef = (value) => {
|
|
928
|
+
return typeof value === "object" && value !== null && FragmentRefKey in value;
|
|
929
|
+
};
|
|
930
|
+
/**
|
|
931
|
+
* Type guard to check if a value is nullish.
|
|
932
|
+
* @internal
|
|
933
|
+
* @param value - Value to check.
|
|
934
|
+
* @returns True if the value is nullish.
|
|
935
|
+
*/
|
|
936
|
+
const isNullish$1 = (value) => {
|
|
937
|
+
return value === void 0 || value === null;
|
|
938
|
+
};
|
|
424
939
|
|
|
425
940
|
//#endregion
|
|
426
941
|
//#region src/cache/normalize.ts
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
* @param storage - The normalized storage map.
|
|
432
|
-
* @param variables - The variable values.
|
|
433
|
-
* @param accessor - Callback invoked when a field dependency is encountered.
|
|
434
|
-
*/
|
|
435
|
-
const normalize = (data, selections, schemaMetadata, storage, variables, accessor) => {
|
|
436
|
-
const normalizeField = (parentKey, selections$1, value) => {
|
|
437
|
-
if (value === null || value === void 0) return value;
|
|
438
|
-
if (typeof value !== "object" || Array.isArray(value)) return value;
|
|
942
|
+
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
943
|
+
const normalizeField = (storageKey, selections$1, value) => {
|
|
944
|
+
if (isNullish$1(value)) return value;
|
|
945
|
+
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections$1, item));
|
|
439
946
|
const data$1 = value;
|
|
440
|
-
const
|
|
441
|
-
|
|
947
|
+
const typename = data$1.__typename;
|
|
948
|
+
const entityMeta = schemaMeta.entities[typename];
|
|
949
|
+
if (entityMeta) storageKey = makeEntityKey(typename, entityMeta.keyFields.map((field) => data$1[field]));
|
|
950
|
+
const fields$1 = {};
|
|
951
|
+
for (const selection of selections$1) if (selection.kind === "Field") {
|
|
442
952
|
const fieldKey = makeFieldKey(selection, variables);
|
|
443
953
|
const fieldValue = data$1[selection.alias ?? selection.name];
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
else if (selection.selections) fields[fieldKey] = normalizeField(parentKey, selection.selections, fieldValue);
|
|
448
|
-
else fields[fieldKey] = fieldValue;
|
|
449
|
-
}
|
|
450
|
-
const typename = data$1.__typename;
|
|
451
|
-
const entityMetadata = typename ? getEntityMetadata(typename, schemaMetadata) : void 0;
|
|
452
|
-
if (entityMetadata) {
|
|
453
|
-
const keyValues = [];
|
|
454
|
-
for (const field of entityMetadata.keyFields) {
|
|
455
|
-
const fieldValue = data$1[field];
|
|
456
|
-
if (fieldValue == null) return fields;
|
|
457
|
-
keyValues.push(fieldValue);
|
|
954
|
+
if (storageKey !== null) {
|
|
955
|
+
const oldValue = storage[storageKey]?.[fieldKey];
|
|
956
|
+
if (!selection.selections || isNullish$1(oldValue) || isNullish$1(fieldValue)) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
458
957
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
958
|
+
fields$1[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
959
|
+
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
960
|
+
const inner = normalizeField(storageKey, selection.selections, value);
|
|
961
|
+
if (!isEntityLink(inner)) Object.assign(fields$1, inner);
|
|
962
|
+
}
|
|
963
|
+
if (entityMeta && storageKey !== null) {
|
|
964
|
+
storage[storageKey] = {
|
|
965
|
+
...storage[storageKey],
|
|
966
|
+
...fields$1
|
|
463
967
|
};
|
|
464
|
-
|
|
465
|
-
return { [EntityLinkKey]: entityKey };
|
|
968
|
+
return { [EntityLinkKey]: storageKey };
|
|
466
969
|
}
|
|
467
|
-
return fields;
|
|
970
|
+
return fields$1;
|
|
971
|
+
};
|
|
972
|
+
const fields = normalizeField(RootFieldKey, selections, data);
|
|
973
|
+
storage[RootFieldKey] = {
|
|
974
|
+
...storage[RootFieldKey],
|
|
975
|
+
...fields
|
|
468
976
|
};
|
|
469
|
-
if (data === null || typeof data !== "object") return;
|
|
470
|
-
const existingRoot = storage.get(RootFieldKey) ?? {};
|
|
471
|
-
const result = normalizeField(RootFieldKey, selections, data);
|
|
472
|
-
storage.set(RootFieldKey, {
|
|
473
|
-
...existingRoot,
|
|
474
|
-
...result
|
|
475
|
-
});
|
|
476
977
|
};
|
|
477
978
|
|
|
478
979
|
//#endregion
|
|
479
980
|
//#region src/cache/denormalize.ts
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const result = {};
|
|
520
|
-
for (const selection of selections) {
|
|
521
|
-
const fieldKey = makeFieldKey(selection, variables);
|
|
522
|
-
const responseKey = selection.alias ?? selection.name;
|
|
523
|
-
const fieldValue = queryRoot[fieldKey];
|
|
524
|
-
if (fieldValue !== void 0) result[responseKey] = denormalizeValue(fieldValue, selection, storage, variables, queryRoot);
|
|
525
|
-
}
|
|
526
|
-
return Object.keys(result).length === 0 ? null : result;
|
|
981
|
+
const typenameFieldKey = makeFieldKey({
|
|
982
|
+
kind: "Field",
|
|
983
|
+
name: "__typename"
|
|
984
|
+
}, {});
|
|
985
|
+
const denormalize = (selections, storage, value, variables, accessor) => {
|
|
986
|
+
let partial = false;
|
|
987
|
+
const denormalizeField = (storageKey, selections$1, value$1) => {
|
|
988
|
+
if (isNullish$1(value$1)) return value$1;
|
|
989
|
+
if (Array.isArray(value$1)) return value$1.map((item) => denormalizeField(storageKey, selections$1, item));
|
|
990
|
+
const data = value$1;
|
|
991
|
+
if (isEntityLink(data)) {
|
|
992
|
+
const entityKey = data[EntityLinkKey];
|
|
993
|
+
const entity = storage[entityKey];
|
|
994
|
+
if (!entity) {
|
|
995
|
+
accessor?.(entityKey, typenameFieldKey);
|
|
996
|
+
partial = true;
|
|
997
|
+
return null;
|
|
998
|
+
}
|
|
999
|
+
return denormalizeField(entityKey, selections$1, entity);
|
|
1000
|
+
}
|
|
1001
|
+
const fields = {};
|
|
1002
|
+
for (const selection of selections$1) if (selection.kind === "Field") {
|
|
1003
|
+
const fieldKey = makeFieldKey(selection, variables);
|
|
1004
|
+
const fieldValue = data[fieldKey];
|
|
1005
|
+
if (storageKey !== null) accessor?.(storageKey, fieldKey);
|
|
1006
|
+
if (fieldValue === void 0) {
|
|
1007
|
+
partial = true;
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
fields[selection.alias ?? selection.name] = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
1011
|
+
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) fields[FragmentRefKey] = storageKey;
|
|
1012
|
+
else Object.assign(fields, denormalizeField(storageKey, selection.selections, value$1));
|
|
1013
|
+
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) Object.assign(fields, denormalizeField(storageKey, selection.selections, value$1));
|
|
1014
|
+
return fields;
|
|
1015
|
+
};
|
|
1016
|
+
return {
|
|
1017
|
+
data: denormalizeField(RootFieldKey, selections, value),
|
|
1018
|
+
partial
|
|
1019
|
+
};
|
|
527
1020
|
};
|
|
528
1021
|
|
|
529
1022
|
//#endregion
|
|
@@ -533,191 +1026,486 @@ const denormalize = (selections, storage, variables) => {
|
|
|
533
1026
|
* Supports entity normalization, cache invalidation, and reactive updates through subscriptions.
|
|
534
1027
|
*/
|
|
535
1028
|
var Cache = class {
|
|
536
|
-
#
|
|
537
|
-
#
|
|
538
|
-
#
|
|
539
|
-
#schemaMetadata;
|
|
1029
|
+
#schemaMeta;
|
|
1030
|
+
#storage = { [RootFieldKey]: {} };
|
|
1031
|
+
#subscriptions = /* @__PURE__ */ new Map();
|
|
540
1032
|
constructor(schemaMetadata) {
|
|
541
|
-
this.#
|
|
1033
|
+
this.#schemaMeta = schemaMetadata;
|
|
542
1034
|
}
|
|
543
1035
|
/**
|
|
544
|
-
* Writes a query result to the cache, normalizing entities
|
|
545
|
-
* @param
|
|
1036
|
+
* Writes a query result to the cache, normalizing entities.
|
|
1037
|
+
* @param artifact - GraphQL document artifact.
|
|
546
1038
|
* @param variables - Query variables.
|
|
547
|
-
* @param
|
|
1039
|
+
* @param data - Query result data.
|
|
548
1040
|
*/
|
|
549
|
-
writeQuery(
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
1041
|
+
writeQuery(artifact, variables, data) {
|
|
1042
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
1043
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
1044
|
+
normalize(this.#schemaMeta, artifact.selections, this.#storage, data, variables, (storageKey, fieldKey, oldValue, newValue) => {
|
|
1045
|
+
if (oldValue !== newValue) {
|
|
1046
|
+
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
1047
|
+
dependencies.add(dependencyKey);
|
|
1048
|
+
}
|
|
553
1049
|
});
|
|
554
|
-
|
|
1050
|
+
for (const dependency of dependencies) {
|
|
1051
|
+
const ss = this.#subscriptions.get(dependency);
|
|
1052
|
+
if (ss) for (const s of ss) subscriptions.add(s);
|
|
1053
|
+
}
|
|
1054
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
555
1055
|
}
|
|
556
1056
|
/**
|
|
557
1057
|
* Reads a query result from the cache, denormalizing entities if available.
|
|
558
|
-
* @param
|
|
1058
|
+
* @param artifact - GraphQL document artifact.
|
|
559
1059
|
* @param variables - Query variables.
|
|
560
1060
|
* @returns Denormalized query result or null if not found.
|
|
561
1061
|
*/
|
|
562
|
-
readQuery(
|
|
563
|
-
|
|
1062
|
+
readQuery(artifact, variables) {
|
|
1063
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables);
|
|
1064
|
+
return partial ? null : data;
|
|
564
1065
|
}
|
|
565
1066
|
/**
|
|
566
1067
|
* Subscribes to cache invalidations for a specific query.
|
|
567
|
-
* @param
|
|
1068
|
+
* @param artifact - GraphQL document artifact.
|
|
568
1069
|
* @param variables - Query variables.
|
|
569
|
-
* @param
|
|
1070
|
+
* @param listener - Callback function to invoke on cache invalidation.
|
|
570
1071
|
* @returns Unsubscribe function.
|
|
571
1072
|
*/
|
|
572
|
-
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
listeners.add(callback);
|
|
580
|
-
return () => {
|
|
581
|
-
listeners.delete(callback);
|
|
582
|
-
if (listeners.size === 0) this.#listeners.delete(queryKey);
|
|
583
|
-
};
|
|
1073
|
+
subscribeQuery(artifact, variables, listener) {
|
|
1074
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
1075
|
+
denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables, (storageKey, fieldKey) => {
|
|
1076
|
+
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
1077
|
+
dependencies.add(dependencyKey);
|
|
1078
|
+
});
|
|
1079
|
+
return this.#subscribe(dependencies, listener);
|
|
584
1080
|
}
|
|
585
1081
|
/**
|
|
586
|
-
*
|
|
587
|
-
*
|
|
588
|
-
*
|
|
1082
|
+
* Reads a fragment from the cache for a specific entity.
|
|
1083
|
+
* Returns null for invalid or missing fragment references, making it safe for
|
|
1084
|
+
* defensive reads. For subscriptions, use subscribeFragment which throws errors.
|
|
1085
|
+
* @param artifact - GraphQL fragment artifact.
|
|
1086
|
+
* @param fragmentRef - Fragment reference containing entity key.
|
|
1087
|
+
* @returns Denormalized fragment data or null if not found or invalid.
|
|
589
1088
|
*/
|
|
590
|
-
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
1089
|
+
readFragment(artifact, fragmentRef) {
|
|
1090
|
+
const entityKey = fragmentRef[FragmentRefKey];
|
|
1091
|
+
if (!this.#storage[entityKey]) return null;
|
|
1092
|
+
const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {});
|
|
1093
|
+
return partial ? null : data;
|
|
1094
|
+
}
|
|
1095
|
+
subscribeFragment(artifact, fragmentRef, listener) {
|
|
1096
|
+
const entityKey = fragmentRef[FragmentRefKey];
|
|
1097
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
1098
|
+
denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {}, (storageKey, fieldKey) => {
|
|
1099
|
+
const dependencyKey = makeDependencyKey(storageKey, fieldKey);
|
|
1100
|
+
dependencies.add(dependencyKey);
|
|
1101
|
+
});
|
|
1102
|
+
return this.#subscribe(dependencies, listener);
|
|
1103
|
+
}
|
|
1104
|
+
#subscribe(dependencies, listener) {
|
|
1105
|
+
const subscription = { listener };
|
|
1106
|
+
for (const dependency of dependencies) {
|
|
1107
|
+
const subscriptions = this.#subscriptions.get(dependency) ?? /* @__PURE__ */ new Set();
|
|
1108
|
+
subscriptions.add(subscription);
|
|
1109
|
+
this.#subscriptions.set(dependency, subscriptions);
|
|
596
1110
|
}
|
|
597
|
-
|
|
1111
|
+
return () => {
|
|
1112
|
+
for (const dependency of dependencies) {
|
|
1113
|
+
const subscriptions = this.#subscriptions.get(dependency);
|
|
1114
|
+
subscriptions?.delete(subscription);
|
|
1115
|
+
if (subscriptions?.size === 0) this.#subscriptions.delete(dependency);
|
|
1116
|
+
}
|
|
1117
|
+
};
|
|
598
1118
|
}
|
|
599
1119
|
/**
|
|
600
1120
|
* Clears all cache data.
|
|
601
1121
|
*/
|
|
602
1122
|
clear() {
|
|
603
|
-
this.#storage
|
|
604
|
-
this.#
|
|
605
|
-
this.#listeners.clear();
|
|
1123
|
+
this.#storage = { [RootFieldKey]: {} };
|
|
1124
|
+
this.#subscriptions.clear();
|
|
606
1125
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
//#endregion
|
|
1129
|
+
//#region src/exchanges/cache.ts
|
|
1130
|
+
const cacheExchange = (options = {}) => {
|
|
1131
|
+
const { fetchPolicy = "cache-first" } = options;
|
|
1132
|
+
return ({ forward, client }) => {
|
|
1133
|
+
const cache = new Cache(client.schema);
|
|
1134
|
+
return (ops$) => {
|
|
1135
|
+
const fragment$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), mergeMap((op) => {
|
|
1136
|
+
const fragmentRef = op.metadata?.fragmentRef;
|
|
1137
|
+
if (!fragmentRef) return fromValue({
|
|
1138
|
+
operation: op,
|
|
1139
|
+
errors: [new ExchangeError("Fragment operation missing fragmentRef in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "cache" })]
|
|
1140
|
+
});
|
|
1141
|
+
if (!isFragmentRef(fragmentRef)) return fromValue({
|
|
1142
|
+
operation: op,
|
|
1143
|
+
data: fragmentRef,
|
|
1144
|
+
errors: []
|
|
1145
|
+
});
|
|
1146
|
+
const trigger = makeSubject();
|
|
1147
|
+
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
|
|
1148
|
+
return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragment(op.artifact, fragmentRef), () => cache.subscribeFragment(op.artifact, fragmentRef, async () => {
|
|
1149
|
+
await Promise.resolve();
|
|
1150
|
+
trigger.next();
|
|
1151
|
+
}))), takeUntil(teardown$), map((data) => ({
|
|
1152
|
+
operation: op,
|
|
1153
|
+
data,
|
|
1154
|
+
errors: []
|
|
1155
|
+
})));
|
|
1156
|
+
}));
|
|
1157
|
+
const nonCache$ = pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")));
|
|
1158
|
+
const query$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), share());
|
|
1159
|
+
return merge(fragment$, pipe(query$, mergeMap((op) => {
|
|
1160
|
+
const trigger = makeSubject();
|
|
1161
|
+
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
|
|
1162
|
+
return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async () => {
|
|
1163
|
+
await Promise.resolve();
|
|
1164
|
+
trigger.next();
|
|
1165
|
+
}))), takeUntil(teardown$), map((data) => ({
|
|
1166
|
+
operation: op,
|
|
1167
|
+
data,
|
|
1168
|
+
errors: []
|
|
1169
|
+
})));
|
|
1170
|
+
}), filter((result) => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" && result.data !== null || fetchPolicy === "cache-first" && result.data !== null)), pipe(merge(nonCache$, pipe(query$, filter((op) => {
|
|
1171
|
+
const cached = cache.readQuery(op.artifact, op.variables);
|
|
1172
|
+
return fetchPolicy === "cache-and-network" || cached === null;
|
|
1173
|
+
})), pipe(ops$, filter((op) => op.variant === "teardown"))), forward, tap((result) => {
|
|
1174
|
+
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
1175
|
+
}), filter((result) => result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only")));
|
|
1176
|
+
};
|
|
1177
|
+
};
|
|
1178
|
+
};
|
|
1179
|
+
|
|
1180
|
+
//#endregion
|
|
1181
|
+
//#region src/exchanges/retry.ts
|
|
1182
|
+
const defaultShouldRetry = (error) => isExchangeError(error, "http") && error.extensions?.statusCode !== void 0 && error.extensions.statusCode >= 500;
|
|
1183
|
+
const retryExchange = (options = {}) => {
|
|
1184
|
+
const { maxAttempts = 3, backoff = (attempt) => Math.min(1e3 * 2 ** attempt, 3e4), shouldRetry = defaultShouldRetry } = options;
|
|
1185
|
+
return ({ forward }) => {
|
|
1186
|
+
return (ops$) => {
|
|
1187
|
+
const { source: retries$, next } = makeSubject();
|
|
1188
|
+
const tornDown = /* @__PURE__ */ new Set();
|
|
1189
|
+
const teardown$ = pipe(ops$, filter((op) => op.variant === "teardown"), tap((op) => {
|
|
1190
|
+
tornDown.add(op.key);
|
|
1191
|
+
}));
|
|
1192
|
+
return pipe(merge(pipe(ops$, filter((op) => op.variant === "request")), pipe(retries$, filter((op) => !tornDown.has(op.key)), mergeMap((op) => {
|
|
1193
|
+
const teardown$$1 = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key));
|
|
1194
|
+
return pipe(fromValue(op), delay(op.metadata.retry.delay), takeUntil(teardown$$1));
|
|
1195
|
+
})), teardown$), forward, filter((result) => {
|
|
1196
|
+
if (!result.errors || result.errors.length === 0) return true;
|
|
1197
|
+
if (result.operation.variant === "request" && result.operation.artifact.kind === "mutation") return true;
|
|
1198
|
+
const attempt = result.operation.metadata.retry?.attempt ?? 0;
|
|
1199
|
+
if (attempt >= maxAttempts - 1) return true;
|
|
1200
|
+
if (!result.errors.some((error) => shouldRetry(error))) return true;
|
|
1201
|
+
next({
|
|
1202
|
+
...result.operation,
|
|
1203
|
+
metadata: {
|
|
1204
|
+
...result.operation.metadata,
|
|
1205
|
+
dedup: { skip: true },
|
|
1206
|
+
retry: {
|
|
1207
|
+
attempt: attempt + 1,
|
|
1208
|
+
delay: backoff(attempt)
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
return false;
|
|
1213
|
+
}));
|
|
1214
|
+
};
|
|
1215
|
+
};
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
//#endregion
|
|
1219
|
+
//#region src/exchanges/fragment.ts
|
|
1220
|
+
const fragmentExchange = () => {
|
|
1221
|
+
return ({ forward }) => {
|
|
1222
|
+
return (ops$) => {
|
|
1223
|
+
return merge(pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), map((op) => {
|
|
1224
|
+
const fragmentRef = op.metadata.fragmentRef;
|
|
1225
|
+
if (!fragmentRef) return {
|
|
1226
|
+
operation: op,
|
|
1227
|
+
errors: [new ExchangeError("Fragment operation missing fragmentRef in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "fragment" })]
|
|
1228
|
+
};
|
|
1229
|
+
return {
|
|
1230
|
+
operation: op,
|
|
1231
|
+
data: fragmentRef
|
|
1232
|
+
};
|
|
1233
|
+
})), pipe(ops$, filter((op) => op.variant === "teardown" || op.artifact.kind !== "fragment"), forward));
|
|
1234
|
+
};
|
|
1235
|
+
};
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1238
|
+
//#endregion
|
|
1239
|
+
//#region src/exchanges/subscription.ts
|
|
1240
|
+
/**
|
|
1241
|
+
* Creates an exchange for handling GraphQL subscriptions using a subscription client.
|
|
1242
|
+
*
|
|
1243
|
+
* This exchange accepts subscription clients from graphql-ws, graphql-sse, or any client
|
|
1244
|
+
* implementing the Observer pattern.
|
|
1245
|
+
* @internal
|
|
1246
|
+
* @param options - Configuration options for the subscription exchange.
|
|
1247
|
+
* @returns An exchange that handles subscription operations.
|
|
1248
|
+
* @example
|
|
1249
|
+
* // With graphql-ws
|
|
1250
|
+
* import { createClient } from 'graphql-ws';
|
|
1251
|
+
*
|
|
1252
|
+
* const wsClient = createClient({
|
|
1253
|
+
* url: 'ws://localhost:4000/graphql',
|
|
1254
|
+
* });
|
|
1255
|
+
*
|
|
1256
|
+
* subscriptionExchange({ client: wsClient })
|
|
1257
|
+
* @example
|
|
1258
|
+
* // With graphql-sse
|
|
1259
|
+
* import { createClient } from 'graphql-sse';
|
|
1260
|
+
*
|
|
1261
|
+
* const sseClient = createClient({
|
|
1262
|
+
* url: 'http://localhost:4000/graphql/stream',
|
|
1263
|
+
* });
|
|
1264
|
+
*
|
|
1265
|
+
* subscriptionExchange({ client: sseClient })
|
|
1266
|
+
*/
|
|
1267
|
+
const subscriptionExchange = (options) => {
|
|
1268
|
+
const { client } = options;
|
|
1269
|
+
return ({ forward }) => {
|
|
1270
|
+
return (ops$) => {
|
|
1271
|
+
return merge(pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "subscription"), mergeMap((op) => {
|
|
1272
|
+
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key));
|
|
1273
|
+
return pipe(make((observer) => {
|
|
1274
|
+
let unsubscribe;
|
|
1275
|
+
let completed = false;
|
|
1276
|
+
Promise.resolve().then(() => {
|
|
1277
|
+
if (completed) return;
|
|
1278
|
+
unsubscribe = client.subscribe({
|
|
1279
|
+
query: op.artifact.body,
|
|
1280
|
+
variables: op.variables
|
|
1281
|
+
}, {
|
|
1282
|
+
next: (result) => {
|
|
1283
|
+
const response = result;
|
|
1284
|
+
observer.next({
|
|
1285
|
+
operation: op,
|
|
1286
|
+
data: response.data,
|
|
1287
|
+
errors: response.errors?.map((err) => new GraphQLError(err.message, {
|
|
1288
|
+
path: err.path,
|
|
1289
|
+
locations: err.locations,
|
|
1290
|
+
extensions: err.extensions
|
|
1291
|
+
})),
|
|
1292
|
+
extensions: response.extensions
|
|
1293
|
+
});
|
|
1294
|
+
},
|
|
1295
|
+
error: (error) => {
|
|
1296
|
+
observer.next({
|
|
1297
|
+
operation: op,
|
|
1298
|
+
errors: [new ExchangeError(error instanceof Error ? error.message : String(error), {
|
|
1299
|
+
exchangeName: "subscription",
|
|
1300
|
+
cause: error
|
|
1301
|
+
})]
|
|
1302
|
+
});
|
|
1303
|
+
observer.complete();
|
|
1304
|
+
},
|
|
1305
|
+
complete: observer.complete
|
|
1306
|
+
});
|
|
1307
|
+
});
|
|
1308
|
+
return () => {
|
|
1309
|
+
completed = true;
|
|
1310
|
+
unsubscribe?.();
|
|
1311
|
+
};
|
|
1312
|
+
}), takeUntil(teardown$));
|
|
1313
|
+
})), pipe(ops$, filter((op) => op.variant === "teardown" || op.artifact.kind !== "subscription"), forward));
|
|
1314
|
+
};
|
|
1315
|
+
};
|
|
1316
|
+
};
|
|
1317
|
+
|
|
1318
|
+
//#endregion
|
|
1319
|
+
//#region src/scalars.ts
|
|
1320
|
+
const parse = (selections, scalars, value) => {
|
|
1321
|
+
const parseValue = (selection, value$1) => {
|
|
1322
|
+
if (isNullish(value$1)) return value$1;
|
|
1323
|
+
if (selection.array && Array.isArray(value$1)) return value$1.map((item) => parseValue({
|
|
1324
|
+
...selection,
|
|
1325
|
+
array: false
|
|
1326
|
+
}, item));
|
|
1327
|
+
if (selection.selections) return parseField(selection.selections, value$1);
|
|
1328
|
+
const transformer = scalars[selection.type];
|
|
1329
|
+
if (transformer) return transformer.parse(value$1);
|
|
1330
|
+
return value$1;
|
|
1331
|
+
};
|
|
1332
|
+
const parseField = (selections$1, value$1) => {
|
|
1333
|
+
if (isNullish(value$1)) return value$1;
|
|
1334
|
+
const data = value$1;
|
|
1335
|
+
const fields = {};
|
|
1336
|
+
for (const selection of selections$1) if (selection.kind === "Field") {
|
|
1337
|
+
const fieldName = selection.alias ?? selection.name;
|
|
1338
|
+
const fieldValue = data[fieldName];
|
|
1339
|
+
fields[fieldName] = parseValue(selection, fieldValue);
|
|
1340
|
+
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === data.__typename) Object.assign(fields, parseField(selection.selections, value$1));
|
|
1341
|
+
return fields;
|
|
1342
|
+
};
|
|
1343
|
+
return parseField(selections, value);
|
|
1344
|
+
};
|
|
1345
|
+
const serialize = (schemaMeta, variableDefs, scalars, variables) => {
|
|
1346
|
+
const serializeValue = (variableDef, value) => {
|
|
1347
|
+
if (isNullish(value)) return value;
|
|
1348
|
+
if (variableDef.array && Array.isArray(value)) return value.map((item) => serializeValue({
|
|
1349
|
+
...variableDef,
|
|
1350
|
+
array: false
|
|
1351
|
+
}, item));
|
|
1352
|
+
const input = schemaMeta.inputs[variableDef.type];
|
|
1353
|
+
if (input) return serializeField(input.fields, value);
|
|
1354
|
+
const transformer = scalars[variableDef.type];
|
|
1355
|
+
if (transformer) return transformer.serialize(value);
|
|
1356
|
+
return value;
|
|
1357
|
+
};
|
|
1358
|
+
const serializeField = (variableDefs$1, value) => {
|
|
1359
|
+
if (isNullish(value)) return value;
|
|
1360
|
+
const data = value;
|
|
1361
|
+
const fields = {};
|
|
1362
|
+
for (const variableDef of variableDefs$1) {
|
|
1363
|
+
const variableValue = data[variableDef.name];
|
|
1364
|
+
fields[variableDef.name] = serializeValue(variableDef, variableValue);
|
|
619
1365
|
}
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
* Notifies all listeners subscribed to a query.
|
|
624
|
-
* @param queryKey - Query key to notify listeners for.
|
|
625
|
-
*/
|
|
626
|
-
#notifyListeners(queryKey) {
|
|
627
|
-
const listeners = this.#listeners.get(queryKey);
|
|
628
|
-
if (listeners) for (const listener of listeners) listener();
|
|
629
|
-
}
|
|
1366
|
+
return fields;
|
|
1367
|
+
};
|
|
1368
|
+
return serializeField(variableDefs, variables);
|
|
630
1369
|
};
|
|
631
1370
|
|
|
632
1371
|
//#endregion
|
|
633
|
-
//#region src/
|
|
1372
|
+
//#region src/exchanges/scalar.ts
|
|
1373
|
+
const scalarExchange = () => {
|
|
1374
|
+
return ({ forward, client }) => {
|
|
1375
|
+
return (ops$) => {
|
|
1376
|
+
return pipe(ops$, map((op) => {
|
|
1377
|
+
if (op.variant !== "request" || !op.artifact.variableDefs || !client.scalars) return op;
|
|
1378
|
+
return {
|
|
1379
|
+
...op,
|
|
1380
|
+
variables: serialize(client.schema, op.artifact.variableDefs, client.scalars, op.variables)
|
|
1381
|
+
};
|
|
1382
|
+
}), forward, map((result) => {
|
|
1383
|
+
if (result.operation.variant !== "request" || !result.data || !client.scalars) return result;
|
|
1384
|
+
return {
|
|
1385
|
+
...result,
|
|
1386
|
+
data: parse(result.operation.artifact.selections, client.scalars, result.data)
|
|
1387
|
+
};
|
|
1388
|
+
}));
|
|
1389
|
+
};
|
|
1390
|
+
};
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1393
|
+
//#endregion
|
|
1394
|
+
//#region src/exchanges/terminal.ts
|
|
1395
|
+
const terminalExchange = () => {
|
|
1396
|
+
return () => {
|
|
1397
|
+
return (ops$) => {
|
|
1398
|
+
return pipe(ops$, filter((op) => op.variant !== "teardown"), mergeMap((op) => fromValue({
|
|
1399
|
+
operation: op,
|
|
1400
|
+
errors: [new ExchangeError("No terminal exchange found in exchange chain. Did you forget to add httpExchange to your exchanges array?", { exchangeName: "terminal" })]
|
|
1401
|
+
})));
|
|
1402
|
+
};
|
|
1403
|
+
};
|
|
1404
|
+
};
|
|
1405
|
+
|
|
1406
|
+
//#endregion
|
|
1407
|
+
//#region src/stream/sources/never.ts
|
|
634
1408
|
/**
|
|
635
|
-
*
|
|
1409
|
+
* Creates a source that never emits any values.
|
|
1410
|
+
* @returns A never source.
|
|
636
1411
|
*/
|
|
1412
|
+
const never = () => {
|
|
1413
|
+
return () => {
|
|
1414
|
+
return { unsubscribe() {} };
|
|
1415
|
+
};
|
|
1416
|
+
};
|
|
1417
|
+
|
|
1418
|
+
//#endregion
|
|
1419
|
+
//#region src/client.ts
|
|
637
1420
|
var Client = class {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
*/
|
|
1421
|
+
#schema;
|
|
1422
|
+
#scalars;
|
|
1423
|
+
operations$;
|
|
1424
|
+
results$;
|
|
643
1425
|
constructor(config) {
|
|
644
|
-
this
|
|
645
|
-
this
|
|
1426
|
+
this.#schema = config.schema;
|
|
1427
|
+
this.#scalars = config.scalars;
|
|
1428
|
+
const exchange = composeExchange({ exchanges: [
|
|
1429
|
+
scalarExchange(),
|
|
1430
|
+
...config.exchanges,
|
|
1431
|
+
fragmentExchange(),
|
|
1432
|
+
terminalExchange()
|
|
1433
|
+
] });
|
|
1434
|
+
this.operations$ = makeSubject();
|
|
1435
|
+
this.results$ = exchange({
|
|
1436
|
+
forward: never,
|
|
1437
|
+
client: this
|
|
1438
|
+
})(this.operations$.source);
|
|
646
1439
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
* @param variables - The query variables.
|
|
650
|
-
* @returns The query result.
|
|
651
|
-
*/
|
|
652
|
-
async query(document, variables) {
|
|
653
|
-
return { data: {} };
|
|
1440
|
+
get schema() {
|
|
1441
|
+
return this.#schema;
|
|
654
1442
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
* @param variables - The mutation variables.
|
|
658
|
-
* @returns The mutation result.
|
|
659
|
-
*/
|
|
660
|
-
async mutate(document, variables) {
|
|
661
|
-
return { data: {} };
|
|
1443
|
+
get scalars() {
|
|
1444
|
+
return this.#scalars;
|
|
662
1445
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
* @param variables - The mutation variables.
|
|
666
|
-
* @returns The mutation result.
|
|
667
|
-
*/
|
|
668
|
-
async mutation(document, variables) {
|
|
669
|
-
return { data: {} };
|
|
1446
|
+
createOperationKey() {
|
|
1447
|
+
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
670
1448
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1449
|
+
createOperation(artifact, variables) {
|
|
1450
|
+
return {
|
|
1451
|
+
variant: "request",
|
|
1452
|
+
key: this.createOperationKey(),
|
|
1453
|
+
metadata: {},
|
|
1454
|
+
artifact,
|
|
1455
|
+
variables: variables ?? {}
|
|
1456
|
+
};
|
|
678
1457
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
return {};
|
|
1458
|
+
executeOperation(operation) {
|
|
1459
|
+
return pipe(this.results$, initialize(() => this.operations$.next(operation)), filter((result) => result.operation.key === operation.key), finalize(() => this.operations$.next({
|
|
1460
|
+
variant: "teardown",
|
|
1461
|
+
key: operation.key,
|
|
1462
|
+
metadata: {}
|
|
1463
|
+
})), share());
|
|
686
1464
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1465
|
+
executeQuery(artifact, ...[variables, options]) {
|
|
1466
|
+
const operation = this.createOperation(artifact, variables);
|
|
1467
|
+
return this.executeOperation(operation);
|
|
1468
|
+
}
|
|
1469
|
+
executeMutation(artifact, ...[variables, options]) {
|
|
1470
|
+
const operation = this.createOperation(artifact, variables);
|
|
1471
|
+
return this.executeOperation(operation);
|
|
1472
|
+
}
|
|
1473
|
+
executeSubscription(artifact, ...[variables, options]) {
|
|
1474
|
+
const operation = this.createOperation(artifact, variables);
|
|
1475
|
+
return this.executeOperation(operation);
|
|
1476
|
+
}
|
|
1477
|
+
executeFragment(artifact, fragmentRef, options) {
|
|
1478
|
+
const operation = {
|
|
1479
|
+
variant: "request",
|
|
1480
|
+
key: this.createOperationKey(),
|
|
1481
|
+
metadata: { fragmentRef },
|
|
1482
|
+
artifact,
|
|
1483
|
+
variables: {}
|
|
1484
|
+
};
|
|
1485
|
+
return this.executeOperation(operation);
|
|
1486
|
+
}
|
|
1487
|
+
dispose() {
|
|
1488
|
+
this.operations$.complete();
|
|
692
1489
|
}
|
|
693
1490
|
};
|
|
694
|
-
/**
|
|
695
|
-
* @param config - The client configuration.
|
|
696
|
-
* @returns A new client instance.
|
|
697
|
-
*/
|
|
698
1491
|
const createClient = (config) => {
|
|
699
1492
|
return new Client(config);
|
|
700
1493
|
};
|
|
701
1494
|
|
|
702
1495
|
//#endregion
|
|
703
|
-
|
|
704
|
-
const graphql = () => {};
|
|
705
|
-
|
|
706
|
-
//#endregion
|
|
1496
|
+
exports.AggregatedError = AggregatedError;
|
|
707
1497
|
exports.Client = Client;
|
|
708
|
-
exports.
|
|
709
|
-
exports.
|
|
710
|
-
exports.
|
|
711
|
-
exports.
|
|
712
|
-
exports.cacheLink = cacheLink;
|
|
713
|
-
exports.combineHashes = combineHashes;
|
|
1498
|
+
exports.ExchangeError = ExchangeError;
|
|
1499
|
+
exports.GraphQLError = GraphQLError;
|
|
1500
|
+
exports.cacheExchange = cacheExchange;
|
|
1501
|
+
exports.composeExchange = composeExchange;
|
|
714
1502
|
exports.createClient = createClient;
|
|
715
|
-
exports.
|
|
716
|
-
exports.
|
|
717
|
-
exports.
|
|
718
|
-
exports.
|
|
719
|
-
exports.
|
|
720
|
-
exports.
|
|
721
|
-
exports.
|
|
722
|
-
exports.
|
|
723
|
-
exports.
|
|
1503
|
+
exports.dedupExchange = dedupExchange;
|
|
1504
|
+
exports.fragmentExchange = fragmentExchange;
|
|
1505
|
+
exports.httpExchange = httpExchange;
|
|
1506
|
+
exports.isAggregatedError = isAggregatedError;
|
|
1507
|
+
exports.isExchangeError = isExchangeError;
|
|
1508
|
+
exports.isGraphQLError = isGraphQLError;
|
|
1509
|
+
exports.retryExchange = retryExchange;
|
|
1510
|
+
exports.stringify = stringify;
|
|
1511
|
+
exports.subscriptionExchange = subscriptionExchange;
|