@mearie/core 0.1.2 → 0.2.0
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 +611 -179
- package/dist/index.d.cts +117 -26
- package/dist/{index.d.ts → index.d.mts} +117 -26
- package/dist/{index.js → index.mjs} +587 -157
- package/dist/{stream-DiNE6b2z.js → make-C7I1YIXm.mjs} +52 -52
- package/dist/{stream-DXHFB0xP.cjs → make-DxW2Pxe-.cjs} +51 -51
- package/dist/stream/index.cjs +26 -25
- package/dist/stream/index.d.cts +2 -2
- package/dist/stream/{index.d.ts → index.d.mts} +2 -2
- package/dist/stream/index.mjs +3 -0
- package/package.json +3 -3
- package/dist/stream/index.js +0 -3
- /package/dist/{index-BJ3Ktp8q.d.ts → make-BhxZYW3l.d.cts} +0 -0
- /package/dist/{index-CrxalFAt.d.cts → make-Ve8TkMHJ.d.mts} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
1
|
+
import { C as mergeMap, S as filter, a as finalize, b as merge, c as share, d as map, i as fromValue, l as takeUntil, m as collect, n as fromSubscription, o as initialize, r as makeSubject, s as switchMap, t as make, u as take, v as fromArray, w as pipe, x as fromPromise, y as tap } from "./make-C7I1YIXm.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/errors.ts
|
|
4
4
|
/**
|
|
@@ -74,30 +74,14 @@ const isAggregatedError = (error) => {
|
|
|
74
74
|
return error instanceof AggregatedError;
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
//#endregion
|
|
78
|
-
//#region src/exchanges/compose.ts
|
|
79
|
-
const composeExchange = (options) => {
|
|
80
|
-
const { exchanges } = options;
|
|
81
|
-
return ({ forward, client }) => {
|
|
82
|
-
return exchanges.reduceRight((forward$1, exchange) => {
|
|
83
|
-
return (ops$) => {
|
|
84
|
-
return pipe(ops$, share(), exchange({
|
|
85
|
-
forward: forward$1,
|
|
86
|
-
client
|
|
87
|
-
}), share());
|
|
88
|
-
};
|
|
89
|
-
}, forward);
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
|
|
93
77
|
//#endregion
|
|
94
78
|
//#region src/exchanges/http.ts
|
|
95
|
-
const executeFetch = async ({ url, fetchOptions, operation, signal }) => {
|
|
79
|
+
const executeFetch = async ({ url, fetchFn, fetchOptions, operation, signal }) => {
|
|
96
80
|
const { artifact, variables } = operation;
|
|
97
81
|
let response;
|
|
98
82
|
try {
|
|
99
83
|
await Promise.resolve();
|
|
100
|
-
response = await
|
|
84
|
+
response = await fetchFn(url, {
|
|
101
85
|
method: "POST",
|
|
102
86
|
mode: fetchOptions.mode,
|
|
103
87
|
credentials: fetchOptions.credentials,
|
|
@@ -152,9 +136,10 @@ const executeFetch = async ({ url, fetchOptions, operation, signal }) => {
|
|
|
152
136
|
};
|
|
153
137
|
};
|
|
154
138
|
const httpExchange = (options) => {
|
|
155
|
-
const { url, headers, mode, credentials } = options;
|
|
156
|
-
return ({ forward }) => {
|
|
157
|
-
|
|
139
|
+
const { url, headers, mode, credentials, fetch: fetchFn = globalThis.fetch } = options;
|
|
140
|
+
return ({ forward }) => ({
|
|
141
|
+
name: "http",
|
|
142
|
+
io: (ops$) => {
|
|
158
143
|
const inflight = /* @__PURE__ */ new Map();
|
|
159
144
|
return merge(pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "query" || op.artifact.kind === "mutation")), mergeMap((op) => {
|
|
160
145
|
inflight.get(op.key)?.abort();
|
|
@@ -162,6 +147,7 @@ const httpExchange = (options) => {
|
|
|
162
147
|
inflight.set(op.key, controller);
|
|
163
148
|
return fromPromise(executeFetch({
|
|
164
149
|
url,
|
|
150
|
+
fetchFn,
|
|
165
151
|
fetchOptions: {
|
|
166
152
|
mode,
|
|
167
153
|
credentials,
|
|
@@ -179,8 +165,8 @@ const httpExchange = (options) => {
|
|
|
179
165
|
inflight.delete(op.key);
|
|
180
166
|
}
|
|
181
167
|
}), forward));
|
|
182
|
-
}
|
|
183
|
-
};
|
|
168
|
+
}
|
|
169
|
+
});
|
|
184
170
|
};
|
|
185
171
|
|
|
186
172
|
//#endregion
|
|
@@ -247,7 +233,7 @@ const stringify = (value) => {
|
|
|
247
233
|
* @param value - Value to check.
|
|
248
234
|
* @returns True if the value is nullish.
|
|
249
235
|
*/
|
|
250
|
-
const isNullish = (value) => {
|
|
236
|
+
const isNullish$1 = (value) => {
|
|
251
237
|
return value === void 0 || value === null;
|
|
252
238
|
};
|
|
253
239
|
|
|
@@ -272,8 +258,9 @@ const makeDedupKey = (op) => {
|
|
|
272
258
|
* @returns An exchange that deduplicates in-flight operations.
|
|
273
259
|
*/
|
|
274
260
|
const dedupExchange = () => {
|
|
275
|
-
return ({ forward }) => {
|
|
276
|
-
|
|
261
|
+
return ({ forward }) => ({
|
|
262
|
+
name: "dedup",
|
|
263
|
+
io: (ops$) => {
|
|
277
264
|
const operations = /* @__PURE__ */ new Map();
|
|
278
265
|
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) => {
|
|
279
266
|
const dedupKey = makeDedupKey(op);
|
|
@@ -301,8 +288,8 @@ const dedupExchange = () => {
|
|
|
301
288
|
}
|
|
302
289
|
})));
|
|
303
290
|
}));
|
|
304
|
-
}
|
|
305
|
-
};
|
|
291
|
+
}
|
|
292
|
+
});
|
|
306
293
|
};
|
|
307
294
|
|
|
308
295
|
//#endregion
|
|
@@ -361,6 +348,15 @@ const makeFieldKey = (selection, variables) => {
|
|
|
361
348
|
return `${selection.name}@${args}`;
|
|
362
349
|
};
|
|
363
350
|
/**
|
|
351
|
+
* Generates a unique key for tracking memoized denormalized results for structural sharing.
|
|
352
|
+
* @internal
|
|
353
|
+
* @param kind - The operation kind ('query', 'fragment', 'fragments').
|
|
354
|
+
* @param name - The artifact name.
|
|
355
|
+
* @param id - Serialized identifier (variables, entity key, etc.).
|
|
356
|
+
* @returns A unique memo key.
|
|
357
|
+
*/
|
|
358
|
+
const makeMemoKey = (kind, name, id) => `${kind}:${name}:${id}`;
|
|
359
|
+
/**
|
|
364
360
|
* Gets a unique key for tracking a field dependency.
|
|
365
361
|
* @internal
|
|
366
362
|
* @param storageKey Storage key (entity or root query key).
|
|
@@ -389,46 +385,166 @@ const isFragmentRef = (value) => {
|
|
|
389
385
|
return typeof value === "object" && value !== null && FragmentRefKey in value;
|
|
390
386
|
};
|
|
391
387
|
/**
|
|
388
|
+
* Type guard to check if a value is an array of fragment references.
|
|
389
|
+
* @internal
|
|
390
|
+
* @param value - Value to check.
|
|
391
|
+
* @returns True if the value is a FragmentRef array.
|
|
392
|
+
*/
|
|
393
|
+
const isFragmentRefArray = (value) => {
|
|
394
|
+
return Array.isArray(value) && value.length > 0 && isFragmentRef(value[0]);
|
|
395
|
+
};
|
|
396
|
+
/**
|
|
392
397
|
* Type guard to check if a value is nullish.
|
|
393
398
|
* @internal
|
|
394
399
|
* @param value - Value to check.
|
|
395
400
|
* @returns True if the value is nullish.
|
|
396
401
|
*/
|
|
397
|
-
const isNullish
|
|
402
|
+
const isNullish = (value) => {
|
|
398
403
|
return value === void 0 || value === null;
|
|
399
404
|
};
|
|
405
|
+
/**
|
|
406
|
+
* Deep equality check for normalized cache values.
|
|
407
|
+
* Handles scalars, arrays, and plain objects (entity links, value objects).
|
|
408
|
+
* @internal
|
|
409
|
+
*/
|
|
410
|
+
const isEqual = (a, b) => {
|
|
411
|
+
if (a === b) return true;
|
|
412
|
+
if (typeof a !== typeof b || a === null || b === null) return false;
|
|
413
|
+
if (Array.isArray(a)) {
|
|
414
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
415
|
+
for (const [i, item] of a.entries()) if (!isEqual(item, b[i])) return false;
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
if (typeof a === "object") {
|
|
419
|
+
const aObj = a;
|
|
420
|
+
const bObj = b;
|
|
421
|
+
const aKeys = Object.keys(aObj);
|
|
422
|
+
if (aKeys.length !== Object.keys(bObj).length) return false;
|
|
423
|
+
for (const key of aKeys) if (!isEqual(aObj[key], bObj[key])) return false;
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
return false;
|
|
427
|
+
};
|
|
428
|
+
/**
|
|
429
|
+
* Recursively replaces a new value tree with the previous one wherever structurally equal,
|
|
430
|
+
* preserving referential identity for unchanged subtrees.
|
|
431
|
+
*
|
|
432
|
+
* Returns `prev` (same reference) when the entire subtree is structurally equal.
|
|
433
|
+
* @internal
|
|
434
|
+
*/
|
|
435
|
+
const replaceEqualDeep = (prev, next) => {
|
|
436
|
+
if (prev === next) return prev;
|
|
437
|
+
if (typeof prev !== typeof next || prev === null || next === null || typeof prev !== "object") return next;
|
|
438
|
+
if (Array.isArray(prev)) {
|
|
439
|
+
if (!Array.isArray(next)) return next;
|
|
440
|
+
let allSame = prev.length === next.length;
|
|
441
|
+
const result = [];
|
|
442
|
+
for (const [i, item] of next.entries()) {
|
|
443
|
+
const shared = i < prev.length ? replaceEqualDeep(prev[i], item) : item;
|
|
444
|
+
result.push(shared);
|
|
445
|
+
if (shared !== prev[i]) allSame = false;
|
|
446
|
+
}
|
|
447
|
+
return allSame ? prev : result;
|
|
448
|
+
}
|
|
449
|
+
if (Array.isArray(next)) return next;
|
|
450
|
+
const prevObj = prev;
|
|
451
|
+
const nextObj = next;
|
|
452
|
+
const nextKeys = Object.keys(nextObj);
|
|
453
|
+
const prevKeys = Object.keys(prevObj);
|
|
454
|
+
let allSame = nextKeys.length === prevKeys.length;
|
|
455
|
+
const result = {};
|
|
456
|
+
for (const key of nextKeys) if (key in prevObj) {
|
|
457
|
+
result[key] = replaceEqualDeep(prevObj[key], nextObj[key]);
|
|
458
|
+
if (result[key] !== prevObj[key]) allSame = false;
|
|
459
|
+
} else {
|
|
460
|
+
result[key] = nextObj[key];
|
|
461
|
+
allSame = false;
|
|
462
|
+
}
|
|
463
|
+
return allSame ? prev : result;
|
|
464
|
+
};
|
|
465
|
+
/**
|
|
466
|
+
* Deeply merges two values. Objects are recursively merged, arrays are element-wise merged,
|
|
467
|
+
* entity links and primitives use last-write-wins.
|
|
468
|
+
* @internal
|
|
469
|
+
*/
|
|
470
|
+
const mergeFieldValue = (existing, incoming) => {
|
|
471
|
+
if (isNullish(existing) || isNullish(incoming)) return incoming;
|
|
472
|
+
if (typeof existing !== "object" || typeof incoming !== "object") return incoming;
|
|
473
|
+
if (isEntityLink(existing) || isEntityLink(incoming)) return incoming;
|
|
474
|
+
if (Array.isArray(existing) && Array.isArray(incoming)) return incoming.map((item, i) => i < existing.length ? mergeFieldValue(existing[i], item) : item);
|
|
475
|
+
if (Array.isArray(existing) || Array.isArray(incoming)) return incoming;
|
|
476
|
+
mergeFields(existing, incoming);
|
|
477
|
+
return existing;
|
|
478
|
+
};
|
|
479
|
+
/**
|
|
480
|
+
* Deeply merges source fields into target. Objects are recursively merged,
|
|
481
|
+
* arrays are element-wise merged, entity links and primitives use last-write-wins.
|
|
482
|
+
* @internal
|
|
483
|
+
*/
|
|
484
|
+
const mergeFields = (target, source) => {
|
|
485
|
+
if (isNullish(source) || typeof source !== "object" || Array.isArray(source)) return;
|
|
486
|
+
for (const key of Object.keys(source)) target[key] = mergeFieldValue(target[key], source[key]);
|
|
487
|
+
};
|
|
488
|
+
/**
|
|
489
|
+
* Creates a FieldKey from a raw field name and optional arguments.
|
|
490
|
+
* @internal
|
|
491
|
+
* @param field - The field name.
|
|
492
|
+
* @param args - Optional argument values.
|
|
493
|
+
* @returns A FieldKey in "field@args" format.
|
|
494
|
+
*/
|
|
495
|
+
const makeFieldKeyFromArgs = (field, args) => {
|
|
496
|
+
return `${field}@${args && Object.keys(args).length > 0 ? stringify(args) : "{}"}`;
|
|
497
|
+
};
|
|
498
|
+
/**
|
|
499
|
+
* Converts an EntityId to an EntityKey.
|
|
500
|
+
* @internal
|
|
501
|
+
* @param typename - The GraphQL typename of the entity.
|
|
502
|
+
* @param id - The entity identifier (string, number, or composite key record).
|
|
503
|
+
* @param keyFields - Optional ordered list of key field names for composite keys.
|
|
504
|
+
* @returns An EntityKey.
|
|
505
|
+
*/
|
|
506
|
+
const resolveEntityKey = (typename, id, keyFields) => {
|
|
507
|
+
if (typeof id === "string" || typeof id === "number") return makeEntityKey(typename, [id]);
|
|
508
|
+
return makeEntityKey(typename, keyFields ? keyFields.map((f) => id[f]) : Object.values(id));
|
|
509
|
+
};
|
|
400
510
|
|
|
401
511
|
//#endregion
|
|
402
512
|
//#region src/cache/normalize.ts
|
|
513
|
+
const SKIP = Symbol();
|
|
403
514
|
const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
|
|
404
|
-
const normalizeField = (storageKey, selections
|
|
405
|
-
if (isNullish
|
|
406
|
-
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections
|
|
407
|
-
const data
|
|
408
|
-
const typename = data
|
|
515
|
+
const normalizeField = (storageKey, selections, value) => {
|
|
516
|
+
if (isNullish(value)) return value;
|
|
517
|
+
if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
|
|
518
|
+
const data = value;
|
|
519
|
+
const typename = data.__typename;
|
|
409
520
|
const entityMeta = schemaMeta.entities[typename];
|
|
410
|
-
if (entityMeta)
|
|
411
|
-
|
|
412
|
-
|
|
521
|
+
if (entityMeta) {
|
|
522
|
+
const keys = entityMeta.keyFields.map((field) => data[field]);
|
|
523
|
+
if (!keys.every((k) => k !== void 0 && k !== null)) return SKIP;
|
|
524
|
+
storageKey = makeEntityKey(typename, keys);
|
|
525
|
+
}
|
|
526
|
+
const fields = {};
|
|
527
|
+
for (const selection of selections) if (selection.kind === "Field") {
|
|
413
528
|
const fieldKey = makeFieldKey(selection, variables);
|
|
414
|
-
const fieldValue = data
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
fields
|
|
529
|
+
const fieldValue = data[selection.alias ?? selection.name];
|
|
530
|
+
const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
|
|
531
|
+
if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
|
|
532
|
+
const normalized = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
533
|
+
if (normalized === SKIP) continue;
|
|
534
|
+
fields[fieldKey] = normalized;
|
|
535
|
+
if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
|
|
420
536
|
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
|
|
421
537
|
const inner = normalizeField(storageKey, selection.selections, value);
|
|
422
|
-
if (!isEntityLink(inner))
|
|
538
|
+
if (inner !== SKIP && !isEntityLink(inner)) mergeFields(fields, inner);
|
|
423
539
|
}
|
|
424
540
|
if (entityMeta && storageKey !== null) {
|
|
425
541
|
storage[storageKey] = {
|
|
426
542
|
...storage[storageKey],
|
|
427
|
-
...fields
|
|
543
|
+
...fields
|
|
428
544
|
};
|
|
429
545
|
return { [EntityLinkKey]: storageKey };
|
|
430
546
|
}
|
|
431
|
-
return fields
|
|
547
|
+
return fields;
|
|
432
548
|
};
|
|
433
549
|
const fields = normalizeField(RootFieldKey, selections, data);
|
|
434
550
|
storage[RootFieldKey] = {
|
|
@@ -446,10 +562,10 @@ const typenameFieldKey = makeFieldKey({
|
|
|
446
562
|
}, {});
|
|
447
563
|
const denormalize = (selections, storage, value, variables, accessor) => {
|
|
448
564
|
let partial = false;
|
|
449
|
-
const denormalizeField = (storageKey, selections
|
|
450
|
-
if (isNullish
|
|
451
|
-
if (Array.isArray(value
|
|
452
|
-
const data = value
|
|
565
|
+
const denormalizeField = (storageKey, selections, value) => {
|
|
566
|
+
if (isNullish(value)) return value;
|
|
567
|
+
if (Array.isArray(value)) return value.map((item) => denormalizeField(storageKey, selections, item));
|
|
568
|
+
const data = value;
|
|
453
569
|
if (isEntityLink(data)) {
|
|
454
570
|
const entityKey = data[EntityLinkKey];
|
|
455
571
|
const entity = storage[entityKey];
|
|
@@ -458,10 +574,10 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
458
574
|
partial = true;
|
|
459
575
|
return null;
|
|
460
576
|
}
|
|
461
|
-
return denormalizeField(entityKey, selections
|
|
577
|
+
return denormalizeField(entityKey, selections, entity);
|
|
462
578
|
}
|
|
463
579
|
const fields = {};
|
|
464
|
-
for (const selection of selections
|
|
580
|
+
for (const selection of selections) if (selection.kind === "Field") {
|
|
465
581
|
const fieldKey = makeFieldKey(selection, variables);
|
|
466
582
|
const fieldValue = data[fieldKey];
|
|
467
583
|
if (storageKey !== null) accessor?.(storageKey, fieldKey);
|
|
@@ -469,10 +585,13 @@ const denormalize = (selections, storage, value, variables, accessor) => {
|
|
|
469
585
|
partial = true;
|
|
470
586
|
continue;
|
|
471
587
|
}
|
|
472
|
-
|
|
588
|
+
const name = selection.alias ?? selection.name;
|
|
589
|
+
const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
|
|
590
|
+
if (name in fields) mergeFields(fields, { [name]: value });
|
|
591
|
+
else fields[name] = value;
|
|
473
592
|
} else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) fields[FragmentRefKey] = storageKey;
|
|
474
|
-
else
|
|
475
|
-
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey])
|
|
593
|
+
else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
594
|
+
else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
|
|
476
595
|
return fields;
|
|
477
596
|
};
|
|
478
597
|
return {
|
|
@@ -491,6 +610,7 @@ var Cache = class {
|
|
|
491
610
|
#schemaMeta;
|
|
492
611
|
#storage = { [RootFieldKey]: {} };
|
|
493
612
|
#subscriptions = /* @__PURE__ */ new Map();
|
|
613
|
+
#memo = /* @__PURE__ */ new Map();
|
|
494
614
|
constructor(schemaMetadata) {
|
|
495
615
|
this.#schemaMeta = schemaMetadata;
|
|
496
616
|
}
|
|
@@ -517,13 +637,19 @@ var Cache = class {
|
|
|
517
637
|
}
|
|
518
638
|
/**
|
|
519
639
|
* Reads a query result from the cache, denormalizing entities if available.
|
|
640
|
+
* Uses structural sharing to preserve referential identity for unchanged subtrees.
|
|
520
641
|
* @param artifact - GraphQL document artifact.
|
|
521
642
|
* @param variables - Query variables.
|
|
522
643
|
* @returns Denormalized query result or null if not found.
|
|
523
644
|
*/
|
|
524
645
|
readQuery(artifact, variables) {
|
|
525
646
|
const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables);
|
|
526
|
-
|
|
647
|
+
if (partial) return null;
|
|
648
|
+
const key = makeMemoKey("query", artifact.name, stringify(variables));
|
|
649
|
+
const prev = this.#memo.get(key);
|
|
650
|
+
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
651
|
+
this.#memo.set(key, result);
|
|
652
|
+
return result;
|
|
527
653
|
}
|
|
528
654
|
/**
|
|
529
655
|
* Subscribes to cache invalidations for a specific query.
|
|
@@ -542,8 +668,7 @@ var Cache = class {
|
|
|
542
668
|
}
|
|
543
669
|
/**
|
|
544
670
|
* Reads a fragment from the cache for a specific entity.
|
|
545
|
-
*
|
|
546
|
-
* defensive reads. For subscriptions, use subscribeFragment which throws errors.
|
|
671
|
+
* Uses structural sharing to preserve referential identity for unchanged subtrees.
|
|
547
672
|
* @param artifact - GraphQL fragment artifact.
|
|
548
673
|
* @param fragmentRef - Fragment reference containing entity key.
|
|
549
674
|
* @returns Denormalized fragment data or null if not found or invalid.
|
|
@@ -552,7 +677,12 @@ var Cache = class {
|
|
|
552
677
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
553
678
|
if (!this.#storage[entityKey]) return null;
|
|
554
679
|
const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {});
|
|
555
|
-
|
|
680
|
+
if (partial) return null;
|
|
681
|
+
const key = makeMemoKey("fragment", artifact.name, entityKey);
|
|
682
|
+
const prev = this.#memo.get(key);
|
|
683
|
+
const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
|
|
684
|
+
this.#memo.set(key, result);
|
|
685
|
+
return result;
|
|
556
686
|
}
|
|
557
687
|
subscribeFragment(artifact, fragmentRef, listener) {
|
|
558
688
|
const entityKey = fragmentRef[FragmentRefKey];
|
|
@@ -563,6 +693,80 @@ var Cache = class {
|
|
|
563
693
|
});
|
|
564
694
|
return this.#subscribe(dependencies, listener);
|
|
565
695
|
}
|
|
696
|
+
readFragments(artifact, fragmentRefs) {
|
|
697
|
+
const results = [];
|
|
698
|
+
for (const ref of fragmentRefs) {
|
|
699
|
+
const data = this.readFragment(artifact, ref);
|
|
700
|
+
if (data === null) return null;
|
|
701
|
+
results.push(data);
|
|
702
|
+
}
|
|
703
|
+
const entityKeys = fragmentRefs.map((ref) => ref[FragmentRefKey]);
|
|
704
|
+
const key = makeMemoKey("fragments", artifact.name, entityKeys.join(","));
|
|
705
|
+
const prev = this.#memo.get(key);
|
|
706
|
+
const result = prev === void 0 ? results : replaceEqualDeep(prev, results);
|
|
707
|
+
this.#memo.set(key, result);
|
|
708
|
+
return result;
|
|
709
|
+
}
|
|
710
|
+
subscribeFragments(artifact, fragmentRefs, listener) {
|
|
711
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
712
|
+
for (const ref of fragmentRefs) {
|
|
713
|
+
const entityKey = ref[FragmentRefKey];
|
|
714
|
+
denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {}, (storageKey, fieldKey) => {
|
|
715
|
+
dependencies.add(makeDependencyKey(storageKey, fieldKey));
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
return this.#subscribe(dependencies, listener);
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Invalidates one or more cache entries and notifies affected subscribers.
|
|
722
|
+
* @param targets - Cache entries to invalidate.
|
|
723
|
+
*/
|
|
724
|
+
invalidate(...targets) {
|
|
725
|
+
const subscriptions = /* @__PURE__ */ new Set();
|
|
726
|
+
for (const target of targets) if (target.__typename === "Query") if ("field" in target) {
|
|
727
|
+
const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
|
|
728
|
+
delete this.#storage[RootFieldKey]?.[fieldKey];
|
|
729
|
+
this.#collectSubscriptions(RootFieldKey, fieldKey, subscriptions);
|
|
730
|
+
} else {
|
|
731
|
+
this.#storage[RootFieldKey] = {};
|
|
732
|
+
this.#collectSubscriptions(RootFieldKey, void 0, subscriptions);
|
|
733
|
+
}
|
|
734
|
+
else if ("id" in target) {
|
|
735
|
+
const entityKey = resolveEntityKey(target.__typename, target.id, this.#schemaMeta.entities[target.__typename]?.keyFields);
|
|
736
|
+
if ("field" in target) {
|
|
737
|
+
const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
|
|
738
|
+
delete this.#storage[entityKey]?.[fieldKey];
|
|
739
|
+
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
740
|
+
} else {
|
|
741
|
+
delete this.#storage[entityKey];
|
|
742
|
+
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
const prefix = `${target.__typename}:`;
|
|
746
|
+
for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
|
|
747
|
+
const entityKey = key;
|
|
748
|
+
if ("field" in target) {
|
|
749
|
+
const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
|
|
750
|
+
delete this.#storage[entityKey]?.[fieldKey];
|
|
751
|
+
this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
|
|
752
|
+
} else {
|
|
753
|
+
delete this.#storage[entityKey];
|
|
754
|
+
this.#collectSubscriptions(entityKey, void 0, subscriptions);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
for (const subscription of subscriptions) subscription.listener();
|
|
759
|
+
}
|
|
760
|
+
#collectSubscriptions(storageKey, fieldKey, out) {
|
|
761
|
+
if (fieldKey === void 0) {
|
|
762
|
+
const prefix = `${storageKey}.`;
|
|
763
|
+
for (const [depKey, ss] of this.#subscriptions) if (depKey.startsWith(prefix)) for (const s of ss) out.add(s);
|
|
764
|
+
} else {
|
|
765
|
+
const depKey = makeDependencyKey(storageKey, fieldKey);
|
|
766
|
+
const ss = this.#subscriptions.get(depKey);
|
|
767
|
+
if (ss) for (const s of ss) out.add(s);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
566
770
|
#subscribe(dependencies, listener) {
|
|
567
771
|
const subscription = { listener };
|
|
568
772
|
for (const dependency of dependencies) {
|
|
@@ -579,62 +783,134 @@ var Cache = class {
|
|
|
579
783
|
};
|
|
580
784
|
}
|
|
581
785
|
/**
|
|
786
|
+
* Extracts a serializable snapshot of the cache storage and structural sharing state.
|
|
787
|
+
*/
|
|
788
|
+
extract() {
|
|
789
|
+
return {
|
|
790
|
+
storage: structuredClone(this.#storage),
|
|
791
|
+
memo: Object.fromEntries(this.#memo)
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Hydrates the cache with a previously extracted snapshot.
|
|
796
|
+
*/
|
|
797
|
+
hydrate(snapshot) {
|
|
798
|
+
const { storage, memo } = snapshot;
|
|
799
|
+
for (const [key, fields] of Object.entries(storage)) this.#storage[key] = {
|
|
800
|
+
...this.#storage[key],
|
|
801
|
+
...fields
|
|
802
|
+
};
|
|
803
|
+
for (const [key, value] of Object.entries(memo)) this.#memo.set(key, value);
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
582
806
|
* Clears all cache data.
|
|
583
807
|
*/
|
|
584
808
|
clear() {
|
|
585
809
|
this.#storage = { [RootFieldKey]: {} };
|
|
586
810
|
this.#subscriptions.clear();
|
|
811
|
+
this.#memo.clear();
|
|
587
812
|
}
|
|
588
813
|
};
|
|
589
814
|
|
|
815
|
+
//#endregion
|
|
816
|
+
//#region src/stream/sources/empty.ts
|
|
817
|
+
/**
|
|
818
|
+
* Creates a source that completes immediately without emitting any values.
|
|
819
|
+
* @returns An empty source.
|
|
820
|
+
*/
|
|
821
|
+
const empty = () => {
|
|
822
|
+
return (sink) => {
|
|
823
|
+
sink.complete();
|
|
824
|
+
return { unsubscribe() {} };
|
|
825
|
+
};
|
|
826
|
+
};
|
|
827
|
+
|
|
590
828
|
//#endregion
|
|
591
829
|
//#region src/exchanges/cache.ts
|
|
592
830
|
const cacheExchange = (options = {}) => {
|
|
593
831
|
const { fetchPolicy = "cache-first" } = options;
|
|
594
832
|
return ({ forward, client }) => {
|
|
595
833
|
const cache = new Cache(client.schema);
|
|
596
|
-
return
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
834
|
+
return {
|
|
835
|
+
name: "cache",
|
|
836
|
+
extension: {
|
|
837
|
+
extract: () => cache.extract(),
|
|
838
|
+
hydrate: (snapshot) => cache.hydrate(snapshot),
|
|
839
|
+
invalidate: (...targets) => cache.invalidate(...targets),
|
|
840
|
+
clear: () => cache.clear()
|
|
841
|
+
},
|
|
842
|
+
io: (ops$) => {
|
|
843
|
+
const fragment$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), mergeMap((op) => {
|
|
844
|
+
const fragmentRef = op.metadata?.fragmentRef;
|
|
845
|
+
if (!fragmentRef) return fromValue({
|
|
846
|
+
operation: op,
|
|
847
|
+
errors: [new ExchangeError("Fragment operation missing fragmentRef in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "cache" })]
|
|
848
|
+
});
|
|
849
|
+
if (isFragmentRefArray(fragmentRef)) {
|
|
850
|
+
const trigger = makeSubject();
|
|
851
|
+
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
|
|
852
|
+
return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragments(op.artifact, fragmentRef), () => cache.subscribeFragments(op.artifact, fragmentRef, async () => {
|
|
853
|
+
await Promise.resolve();
|
|
854
|
+
trigger.next();
|
|
855
|
+
}))), takeUntil(teardown$), map((data) => ({
|
|
856
|
+
operation: op,
|
|
857
|
+
data,
|
|
858
|
+
errors: []
|
|
859
|
+
})));
|
|
860
|
+
}
|
|
861
|
+
if (!isFragmentRef(fragmentRef)) return fromValue({
|
|
862
|
+
operation: op,
|
|
863
|
+
data: fragmentRef,
|
|
864
|
+
errors: []
|
|
865
|
+
});
|
|
866
|
+
const trigger = makeSubject();
|
|
867
|
+
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
|
|
868
|
+
return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragment(op.artifact, fragmentRef), () => cache.subscribeFragment(op.artifact, fragmentRef, async () => {
|
|
869
|
+
await Promise.resolve();
|
|
870
|
+
trigger.next();
|
|
871
|
+
}))), takeUntil(teardown$), map((data) => ({
|
|
872
|
+
operation: op,
|
|
873
|
+
data,
|
|
874
|
+
errors: []
|
|
875
|
+
})));
|
|
876
|
+
}));
|
|
877
|
+
const nonCache$ = pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")));
|
|
878
|
+
const query$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), share());
|
|
879
|
+
const refetch$ = makeSubject();
|
|
880
|
+
return merge(fragment$, pipe(query$, mergeMap((op) => {
|
|
881
|
+
const trigger = makeSubject();
|
|
882
|
+
let hasData = false;
|
|
883
|
+
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
|
|
884
|
+
return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async () => {
|
|
885
|
+
await Promise.resolve();
|
|
886
|
+
trigger.next();
|
|
887
|
+
}))), takeUntil(teardown$), mergeMap((data) => {
|
|
888
|
+
if (data !== null) {
|
|
889
|
+
hasData = true;
|
|
890
|
+
return fromValue({
|
|
891
|
+
operation: op,
|
|
892
|
+
data,
|
|
893
|
+
errors: []
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
if (hasData) {
|
|
897
|
+
refetch$.next(op);
|
|
898
|
+
return empty();
|
|
899
|
+
}
|
|
900
|
+
if (fetchPolicy === "cache-only") return fromValue({
|
|
901
|
+
operation: op,
|
|
902
|
+
data: null,
|
|
903
|
+
errors: []
|
|
904
|
+
});
|
|
905
|
+
return empty();
|
|
906
|
+
}));
|
|
907
|
+
}), filter(() => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" || fetchPolicy === "cache-first")), pipe(merge(nonCache$, pipe(query$, filter((op) => {
|
|
908
|
+
const cached = cache.readQuery(op.artifact, op.variables);
|
|
909
|
+
return fetchPolicy === "cache-and-network" || cached === null;
|
|
910
|
+
})), pipe(ops$, filter((op) => op.variant === "teardown")), refetch$.source), forward, tap((result) => {
|
|
911
|
+
if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
|
|
912
|
+
}), filter((result) => result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0))));
|
|
913
|
+
}
|
|
638
914
|
};
|
|
639
915
|
};
|
|
640
916
|
};
|
|
@@ -644,16 +920,17 @@ const cacheExchange = (options = {}) => {
|
|
|
644
920
|
const defaultShouldRetry = (error) => isExchangeError(error, "http") && error.extensions?.statusCode !== void 0 && error.extensions.statusCode >= 500;
|
|
645
921
|
const retryExchange = (options = {}) => {
|
|
646
922
|
const { maxAttempts = 3, backoff = (attempt) => Math.min(1e3 * 2 ** attempt, 3e4), shouldRetry = defaultShouldRetry } = options;
|
|
647
|
-
return ({ forward }) => {
|
|
648
|
-
|
|
923
|
+
return ({ forward }) => ({
|
|
924
|
+
name: "retry",
|
|
925
|
+
io: (ops$) => {
|
|
649
926
|
const { source: retries$, next } = makeSubject();
|
|
650
927
|
const tornDown = /* @__PURE__ */ new Set();
|
|
651
928
|
const teardown$ = pipe(ops$, filter((op) => op.variant === "teardown"), tap((op) => {
|
|
652
929
|
tornDown.add(op.key);
|
|
653
930
|
}));
|
|
654
931
|
return pipe(merge(pipe(ops$, filter((op) => op.variant === "request")), pipe(retries$, filter((op) => !tornDown.has(op.key)), mergeMap((op) => {
|
|
655
|
-
const teardown
|
|
656
|
-
return pipe(fromValue(op), delay(op.metadata.retry.delay), takeUntil(teardown
|
|
932
|
+
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key));
|
|
933
|
+
return pipe(fromValue(op), delay(op.metadata.retry.delay), takeUntil(teardown$));
|
|
657
934
|
})), teardown$), forward, filter((result) => {
|
|
658
935
|
if (!result.errors || result.errors.length === 0) return true;
|
|
659
936
|
if (result.operation.variant === "request" && result.operation.artifact.kind === "mutation") return true;
|
|
@@ -673,15 +950,16 @@ const retryExchange = (options = {}) => {
|
|
|
673
950
|
});
|
|
674
951
|
return false;
|
|
675
952
|
}));
|
|
676
|
-
}
|
|
677
|
-
};
|
|
953
|
+
}
|
|
954
|
+
});
|
|
678
955
|
};
|
|
679
956
|
|
|
680
957
|
//#endregion
|
|
681
958
|
//#region src/exchanges/fragment.ts
|
|
682
959
|
const fragmentExchange = () => {
|
|
683
|
-
return ({ forward }) => {
|
|
684
|
-
|
|
960
|
+
return ({ forward }) => ({
|
|
961
|
+
name: "fragment",
|
|
962
|
+
io: (ops$) => {
|
|
685
963
|
return merge(pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), map((op) => {
|
|
686
964
|
const fragmentRef = op.metadata.fragmentRef;
|
|
687
965
|
if (!fragmentRef) return {
|
|
@@ -693,12 +971,103 @@ const fragmentExchange = () => {
|
|
|
693
971
|
data: fragmentRef
|
|
694
972
|
};
|
|
695
973
|
})), pipe(ops$, filter((op) => op.variant === "teardown" || op.artifact.kind !== "fragment"), forward));
|
|
696
|
-
}
|
|
697
|
-
};
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
//#endregion
|
|
979
|
+
//#region src/required.ts
|
|
980
|
+
const CASCADE_NULL = Symbol("CASCADE_NULL");
|
|
981
|
+
var RequiredFieldError = class extends Error {
|
|
982
|
+
fieldPath;
|
|
983
|
+
fieldName;
|
|
984
|
+
constructor(fieldPath, fieldName) {
|
|
985
|
+
super(`Required field '${fieldPath.join(".")}.${fieldName}' is null`);
|
|
986
|
+
this.name = "RequiredFieldError";
|
|
987
|
+
this.fieldPath = fieldPath;
|
|
988
|
+
this.fieldName = fieldName;
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
const getRequiredAction = (directives) => {
|
|
992
|
+
if (!directives) return null;
|
|
993
|
+
const requiredDirective = directives.find((d) => d.name === "required");
|
|
994
|
+
if (!requiredDirective) return null;
|
|
995
|
+
if (requiredDirective.args?.action === "CASCADE") return "CASCADE";
|
|
996
|
+
return "THROW";
|
|
997
|
+
};
|
|
998
|
+
const validateRequiredInner = (selections, data, fieldPath, validatedMap) => {
|
|
999
|
+
if (data === null || data === void 0) return data;
|
|
1000
|
+
if (typeof data !== "object") return data;
|
|
1001
|
+
if (Array.isArray(data)) return data.map((item, index) => {
|
|
1002
|
+
const result = validateRequiredInner(selections, item, [...fieldPath, `[${index}]`], validatedMap);
|
|
1003
|
+
return result === CASCADE_NULL ? null : result;
|
|
1004
|
+
});
|
|
1005
|
+
const obj = data;
|
|
1006
|
+
validatedMap ??= /* @__PURE__ */ new WeakMap();
|
|
1007
|
+
let validated = validatedMap.get(obj);
|
|
1008
|
+
if (!validated) {
|
|
1009
|
+
validated = /* @__PURE__ */ new Set();
|
|
1010
|
+
validatedMap.set(obj, validated);
|
|
1011
|
+
}
|
|
1012
|
+
for (const selection of selections) if (selection.kind === "Field") {
|
|
1013
|
+
const fieldName = selection.alias ?? selection.name;
|
|
1014
|
+
if (!(fieldName in obj)) continue;
|
|
1015
|
+
const fieldValue = obj[fieldName];
|
|
1016
|
+
const action = getRequiredAction(selection.directives);
|
|
1017
|
+
if (selection.selections) {
|
|
1018
|
+
if (action && fieldValue === null) {
|
|
1019
|
+
if (action === "THROW") throw new RequiredFieldError(fieldPath, fieldName);
|
|
1020
|
+
else if (action === "CASCADE") return CASCADE_NULL;
|
|
1021
|
+
}
|
|
1022
|
+
if (fieldValue !== null && fieldValue !== void 0) {
|
|
1023
|
+
if (validateRequiredInner(selection.selections, fieldValue, [...fieldPath, fieldName], validatedMap) === CASCADE_NULL) if (selection.nullable && !getRequiredAction(selection.directives)) obj[fieldName] = null;
|
|
1024
|
+
else return CASCADE_NULL;
|
|
1025
|
+
}
|
|
1026
|
+
} else {
|
|
1027
|
+
if (validated.has(fieldName)) continue;
|
|
1028
|
+
validated.add(fieldName);
|
|
1029
|
+
if (action && fieldValue === null) {
|
|
1030
|
+
if (action === "THROW") throw new RequiredFieldError(fieldPath, fieldName);
|
|
1031
|
+
else if (action === "CASCADE") return CASCADE_NULL;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment") {
|
|
1035
|
+
if (validateRequiredInner(selection.selections, data, fieldPath, validatedMap) === CASCADE_NULL) return CASCADE_NULL;
|
|
1036
|
+
}
|
|
1037
|
+
return data;
|
|
1038
|
+
};
|
|
1039
|
+
const validateRequired = (selections, data, fieldPath = []) => {
|
|
1040
|
+
const result = validateRequiredInner(selections, data, fieldPath);
|
|
1041
|
+
return result === CASCADE_NULL ? null : result;
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
//#endregion
|
|
1045
|
+
//#region src/exchanges/required.ts
|
|
1046
|
+
const requiredExchange = () => {
|
|
1047
|
+
return ({ forward }) => ({
|
|
1048
|
+
name: "required",
|
|
1049
|
+
io: (ops$) => {
|
|
1050
|
+
return pipe(ops$, forward, map((result) => {
|
|
1051
|
+
if (result.operation.variant !== "request" || !result.data) return result;
|
|
1052
|
+
try {
|
|
1053
|
+
return {
|
|
1054
|
+
...result,
|
|
1055
|
+
data: validateRequired(result.operation.artifact.selections, result.data)
|
|
1056
|
+
};
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
return {
|
|
1059
|
+
...result,
|
|
1060
|
+
errors: [new ExchangeError(error instanceof Error ? error.message : String(error), { exchangeName: "required" })]
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
}));
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
698
1066
|
};
|
|
699
1067
|
|
|
700
1068
|
//#endregion
|
|
701
1069
|
//#region src/exchanges/subscription.ts
|
|
1070
|
+
const shouldHandle = (op) => op.variant === "request" && (op.artifact.kind === "subscription" || op.metadata.subscription?.transport === true);
|
|
702
1071
|
/**
|
|
703
1072
|
* Creates an exchange for handling GraphQL subscriptions using a subscription client.
|
|
704
1073
|
*
|
|
@@ -728,9 +1097,10 @@ const fragmentExchange = () => {
|
|
|
728
1097
|
*/
|
|
729
1098
|
const subscriptionExchange = (options) => {
|
|
730
1099
|
const { client } = options;
|
|
731
|
-
return ({ forward }) => {
|
|
732
|
-
|
|
733
|
-
|
|
1100
|
+
return ({ forward }) => ({
|
|
1101
|
+
name: "subscription",
|
|
1102
|
+
io: (ops$) => {
|
|
1103
|
+
return merge(pipe(ops$, filter(shouldHandle), mergeMap((op) => {
|
|
734
1104
|
const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key));
|
|
735
1105
|
return pipe(make((observer) => {
|
|
736
1106
|
let unsubscribe;
|
|
@@ -772,41 +1142,73 @@ const subscriptionExchange = (options) => {
|
|
|
772
1142
|
unsubscribe?.();
|
|
773
1143
|
};
|
|
774
1144
|
}), takeUntil(teardown$));
|
|
775
|
-
})), pipe(ops$, filter((op) => op
|
|
776
|
-
}
|
|
1145
|
+
})), pipe(ops$, filter((op) => !shouldHandle(op)), forward));
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region src/compose.ts
|
|
1152
|
+
/** @internal */
|
|
1153
|
+
const composeExchanges = (options, input) => {
|
|
1154
|
+
const { exchanges } = options;
|
|
1155
|
+
const { client } = input;
|
|
1156
|
+
const extensions = /* @__PURE__ */ new Map();
|
|
1157
|
+
return {
|
|
1158
|
+
io: exchanges.reduceRight((forward, exchange) => {
|
|
1159
|
+
const result = exchange({
|
|
1160
|
+
forward,
|
|
1161
|
+
client
|
|
1162
|
+
});
|
|
1163
|
+
if ("extension" in result) extensions.set(result.name, result.extension);
|
|
1164
|
+
return (ops$) => {
|
|
1165
|
+
return pipe(ops$, share(), result.io, share());
|
|
1166
|
+
};
|
|
1167
|
+
}, input.forward),
|
|
1168
|
+
extensions
|
|
777
1169
|
};
|
|
778
1170
|
};
|
|
779
1171
|
|
|
780
1172
|
//#endregion
|
|
781
1173
|
//#region src/scalars.ts
|
|
782
1174
|
const parse = (selections, scalars, value) => {
|
|
783
|
-
const parseValue = (selection, value
|
|
784
|
-
if (isNullish(value
|
|
785
|
-
if (selection.array && Array.isArray(value
|
|
1175
|
+
const parseValue = (selection, value, parsedMap) => {
|
|
1176
|
+
if (isNullish$1(value)) return value;
|
|
1177
|
+
if (selection.array && Array.isArray(value)) return value.map((item) => parseValue({
|
|
786
1178
|
...selection,
|
|
787
1179
|
array: false
|
|
788
|
-
}, item));
|
|
789
|
-
if (selection.selections) return parseField(selection.selections, value
|
|
1180
|
+
}, item, parsedMap));
|
|
1181
|
+
if (selection.selections) return parseField(selection.selections, value, parsedMap);
|
|
790
1182
|
const transformer = scalars[selection.type];
|
|
791
|
-
if (transformer) return transformer.parse(value
|
|
792
|
-
return value
|
|
1183
|
+
if (transformer) return transformer.parse(value);
|
|
1184
|
+
return value;
|
|
793
1185
|
};
|
|
794
|
-
const parseField = (selections
|
|
795
|
-
if (isNullish(value
|
|
796
|
-
const data = value
|
|
797
|
-
|
|
798
|
-
|
|
1186
|
+
const parseField = (selections, value, parsedMap) => {
|
|
1187
|
+
if (isNullish$1(value)) return value;
|
|
1188
|
+
const data = value;
|
|
1189
|
+
parsedMap ??= /* @__PURE__ */ new WeakMap();
|
|
1190
|
+
let parsed = parsedMap.get(data);
|
|
1191
|
+
if (!parsed) {
|
|
1192
|
+
parsed = /* @__PURE__ */ new Set();
|
|
1193
|
+
parsedMap.set(data, parsed);
|
|
1194
|
+
}
|
|
1195
|
+
for (const selection of selections) if (selection.kind === "Field") {
|
|
799
1196
|
const fieldName = selection.alias ?? selection.name;
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
1197
|
+
if (!(fieldName in data)) continue;
|
|
1198
|
+
if (selection.selections) data[fieldName] = parseValue(selection, data[fieldName], parsedMap);
|
|
1199
|
+
else {
|
|
1200
|
+
if (parsed.has(fieldName)) continue;
|
|
1201
|
+
parsed.add(fieldName);
|
|
1202
|
+
data[fieldName] = parseValue(selection, data[fieldName], parsedMap);
|
|
1203
|
+
}
|
|
1204
|
+
} else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === data.__typename) parseField(selection.selections, value, parsedMap);
|
|
1205
|
+
return data;
|
|
804
1206
|
};
|
|
805
1207
|
return parseField(selections, value);
|
|
806
1208
|
};
|
|
807
1209
|
const serialize = (schemaMeta, variableDefs, scalars, variables) => {
|
|
808
1210
|
const serializeValue = (variableDef, value) => {
|
|
809
|
-
if (isNullish(value)) return value;
|
|
1211
|
+
if (isNullish$1(value)) return value;
|
|
810
1212
|
if (variableDef.array && Array.isArray(value)) return value.map((item) => serializeValue({
|
|
811
1213
|
...variableDef,
|
|
812
1214
|
array: false
|
|
@@ -817,11 +1219,11 @@ const serialize = (schemaMeta, variableDefs, scalars, variables) => {
|
|
|
817
1219
|
if (transformer) return transformer.serialize(value);
|
|
818
1220
|
return value;
|
|
819
1221
|
};
|
|
820
|
-
const serializeField = (variableDefs
|
|
821
|
-
if (isNullish(value)) return value;
|
|
1222
|
+
const serializeField = (variableDefs, value) => {
|
|
1223
|
+
if (isNullish$1(value)) return value;
|
|
822
1224
|
const data = value;
|
|
823
1225
|
const fields = {};
|
|
824
|
-
for (const variableDef of variableDefs
|
|
1226
|
+
for (const variableDef of variableDefs) {
|
|
825
1227
|
const variableValue = data[variableDef.name];
|
|
826
1228
|
fields[variableDef.name] = serializeValue(variableDef, variableValue);
|
|
827
1229
|
}
|
|
@@ -833,8 +1235,9 @@ const serialize = (schemaMeta, variableDefs, scalars, variables) => {
|
|
|
833
1235
|
//#endregion
|
|
834
1236
|
//#region src/exchanges/scalar.ts
|
|
835
1237
|
const scalarExchange = () => {
|
|
836
|
-
return ({ forward, client }) => {
|
|
837
|
-
|
|
1238
|
+
return ({ forward, client }) => ({
|
|
1239
|
+
name: "scalar",
|
|
1240
|
+
io: (ops$) => {
|
|
838
1241
|
return pipe(ops$, map((op) => {
|
|
839
1242
|
if (op.variant !== "request" || !op.artifact.variableDefs || !client.scalars) return op;
|
|
840
1243
|
return {
|
|
@@ -848,21 +1251,22 @@ const scalarExchange = () => {
|
|
|
848
1251
|
data: parse(result.operation.artifact.selections, client.scalars, result.data)
|
|
849
1252
|
};
|
|
850
1253
|
}));
|
|
851
|
-
}
|
|
852
|
-
};
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
853
1256
|
};
|
|
854
1257
|
|
|
855
1258
|
//#endregion
|
|
856
1259
|
//#region src/exchanges/terminal.ts
|
|
857
1260
|
const terminalExchange = () => {
|
|
858
|
-
return () => {
|
|
859
|
-
|
|
1261
|
+
return () => ({
|
|
1262
|
+
name: "terminal",
|
|
1263
|
+
io: (ops$) => {
|
|
860
1264
|
return pipe(ops$, filter((op) => op.variant !== "teardown"), mergeMap((op) => fromValue({
|
|
861
1265
|
operation: op,
|
|
862
1266
|
errors: [new ExchangeError("No terminal exchange found in exchange chain. Did you forget to add httpExchange to your exchanges array?", { exchangeName: "terminal" })]
|
|
863
1267
|
})));
|
|
864
|
-
}
|
|
865
|
-
};
|
|
1268
|
+
}
|
|
1269
|
+
});
|
|
866
1270
|
};
|
|
867
1271
|
|
|
868
1272
|
//#endregion
|
|
@@ -882,22 +1286,25 @@ const never = () => {
|
|
|
882
1286
|
var Client = class {
|
|
883
1287
|
#schema;
|
|
884
1288
|
#scalars;
|
|
1289
|
+
#extensions;
|
|
885
1290
|
operations$;
|
|
886
1291
|
results$;
|
|
887
1292
|
constructor(config) {
|
|
888
1293
|
this.#schema = config.schema;
|
|
889
1294
|
this.#scalars = config.scalars;
|
|
890
|
-
const
|
|
1295
|
+
const { io, extensions } = composeExchanges({ exchanges: [
|
|
1296
|
+
requiredExchange(),
|
|
891
1297
|
scalarExchange(),
|
|
892
1298
|
...config.exchanges,
|
|
893
1299
|
fragmentExchange(),
|
|
894
1300
|
terminalExchange()
|
|
895
|
-
] }
|
|
896
|
-
this.operations$ = makeSubject();
|
|
897
|
-
this.results$ = exchange({
|
|
1301
|
+
] }, {
|
|
898
1302
|
forward: never,
|
|
899
1303
|
client: this
|
|
900
|
-
})
|
|
1304
|
+
});
|
|
1305
|
+
this.#extensions = extensions;
|
|
1306
|
+
this.operations$ = makeSubject();
|
|
1307
|
+
this.results$ = io(this.operations$.source);
|
|
901
1308
|
}
|
|
902
1309
|
get schema() {
|
|
903
1310
|
return this.#schema;
|
|
@@ -908,11 +1315,11 @@ var Client = class {
|
|
|
908
1315
|
createOperationKey() {
|
|
909
1316
|
return Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
910
1317
|
}
|
|
911
|
-
createOperation(artifact, variables) {
|
|
1318
|
+
createOperation(artifact, variables, metadata) {
|
|
912
1319
|
return {
|
|
913
1320
|
variant: "request",
|
|
914
1321
|
key: this.createOperationKey(),
|
|
915
|
-
metadata: {},
|
|
1322
|
+
metadata: { ...metadata },
|
|
916
1323
|
artifact,
|
|
917
1324
|
variables: variables ?? {}
|
|
918
1325
|
};
|
|
@@ -925,27 +1332,50 @@ var Client = class {
|
|
|
925
1332
|
})), share());
|
|
926
1333
|
}
|
|
927
1334
|
executeQuery(artifact, ...[variables, options]) {
|
|
928
|
-
const operation = this.createOperation(artifact, variables);
|
|
1335
|
+
const operation = this.createOperation(artifact, variables, options?.metadata);
|
|
929
1336
|
return this.executeOperation(operation);
|
|
930
1337
|
}
|
|
931
1338
|
executeMutation(artifact, ...[variables, options]) {
|
|
932
|
-
const operation = this.createOperation(artifact, variables);
|
|
1339
|
+
const operation = this.createOperation(artifact, variables, options?.metadata);
|
|
933
1340
|
return this.executeOperation(operation);
|
|
934
1341
|
}
|
|
935
1342
|
executeSubscription(artifact, ...[variables, options]) {
|
|
936
|
-
const operation = this.createOperation(artifact, variables);
|
|
1343
|
+
const operation = this.createOperation(artifact, variables, options?.metadata);
|
|
937
1344
|
return this.executeOperation(operation);
|
|
938
1345
|
}
|
|
939
1346
|
executeFragment(artifact, fragmentRef, options) {
|
|
940
1347
|
const operation = {
|
|
941
1348
|
variant: "request",
|
|
942
1349
|
key: this.createOperationKey(),
|
|
943
|
-
metadata: {
|
|
1350
|
+
metadata: {
|
|
1351
|
+
...options?.metadata,
|
|
1352
|
+
fragmentRef
|
|
1353
|
+
},
|
|
944
1354
|
artifact,
|
|
945
1355
|
variables: {}
|
|
946
1356
|
};
|
|
947
1357
|
return this.executeOperation(operation);
|
|
948
1358
|
}
|
|
1359
|
+
async query(artifact, ...[variables, options]) {
|
|
1360
|
+
const operation = this.createOperation(artifact, variables, options?.metadata);
|
|
1361
|
+
const result = await pipe(this.executeOperation(operation), take(1), collect);
|
|
1362
|
+
if (result.errors && result.errors.length > 0) throw new AggregatedError(result.errors);
|
|
1363
|
+
return result.data;
|
|
1364
|
+
}
|
|
1365
|
+
async mutation(artifact, ...[variables, options]) {
|
|
1366
|
+
const operation = this.createOperation(artifact, variables, options?.metadata);
|
|
1367
|
+
const result = await pipe(this.executeOperation(operation), take(1), collect);
|
|
1368
|
+
if (result.errors && result.errors.length > 0) throw new AggregatedError(result.errors);
|
|
1369
|
+
return result.data;
|
|
1370
|
+
}
|
|
1371
|
+
extension(name) {
|
|
1372
|
+
const ext = this.#extensions.get(name);
|
|
1373
|
+
if (!ext) throw new Error(`Exchange extension '${name}' is not registered. Check your exchange configuration.`);
|
|
1374
|
+
return ext;
|
|
1375
|
+
}
|
|
1376
|
+
maybeExtension(name) {
|
|
1377
|
+
return this.#extensions.get(name);
|
|
1378
|
+
}
|
|
949
1379
|
dispose() {
|
|
950
1380
|
this.operations$.complete();
|
|
951
1381
|
}
|
|
@@ -955,4 +1385,4 @@ const createClient = (config) => {
|
|
|
955
1385
|
};
|
|
956
1386
|
|
|
957
1387
|
//#endregion
|
|
958
|
-
export { AggregatedError, Client, ExchangeError, GraphQLError,
|
|
1388
|
+
export { AggregatedError, Client, ExchangeError, GraphQLError, RequiredFieldError, cacheExchange, createClient, dedupExchange, fragmentExchange, httpExchange, isAggregatedError, isExchangeError, isGraphQLError, requiredExchange, retryExchange, stringify, subscriptionExchange };
|