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