@mearie/core 0.1.0 → 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 CHANGED
@@ -1,3 +1,5 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_make = require('./make-DxW2Pxe-.cjs');
1
3
 
2
4
  //#region src/errors.ts
3
5
  /**
@@ -73,253 +75,14 @@ const isAggregatedError = (error) => {
73
75
  return error instanceof AggregatedError;
74
76
  };
75
77
 
76
- //#endregion
77
- //#region src/stream/pipe.ts
78
- /**
79
- * @param source - The source stream.
80
- * @param operators - The operators to apply.
81
- * @returns The result of the last operator.
82
- */
83
- function pipe(source, ...operators) {
84
- return operators.reduce((src, operator) => operator(src), source);
85
- }
86
-
87
- //#endregion
88
- //#region src/stream/operators/share.ts
89
- /**
90
- * Shares a single source across multiple subscribers (multicast).
91
- * The source is only executed once, and all subscribers receive the same values.
92
- * This is essential for deduplication and caching scenarios.
93
- * @returns An operator that shares the source.
94
- */
95
- const share = () => {
96
- return (source) => {
97
- const sinks = [];
98
- let subscription = null;
99
- let started = false;
100
- let completed = false;
101
- return (sink) => {
102
- if (completed) {
103
- sink.complete();
104
- return { unsubscribe() {} };
105
- }
106
- sinks.push(sink);
107
- if (!started) {
108
- started = true;
109
- subscription = source({
110
- next(value) {
111
- for (const s of [...sinks]) {
112
- if (completed) break;
113
- s.next(value);
114
- }
115
- },
116
- complete() {
117
- if (!completed) {
118
- completed = true;
119
- for (const s of [...sinks]) s.complete();
120
- sinks.length = 0;
121
- }
122
- }
123
- });
124
- }
125
- return { unsubscribe() {
126
- const idx = sinks.indexOf(sink);
127
- if (idx !== -1) sinks.splice(idx, 1);
128
- if (sinks.length === 0 && subscription) {
129
- subscription.unsubscribe();
130
- subscription = null;
131
- started = false;
132
- }
133
- } };
134
- };
135
- };
136
- };
137
-
138
- //#endregion
139
- //#region src/exchanges/compose.ts
140
- const composeExchange = (options) => {
141
- const { exchanges } = options;
142
- return ({ forward, client }) => {
143
- return exchanges.reduceRight((forward$1, exchange) => {
144
- return (ops$) => {
145
- return pipe(ops$, share(), exchange({
146
- forward: forward$1,
147
- client
148
- }), share());
149
- };
150
- }, forward);
151
- };
152
- };
153
-
154
- //#endregion
155
- //#region src/stream/operators/merge-map.ts
156
- /**
157
- * Maps each value to a source and flattens all sources into a single output source.
158
- * Similar to flatMap. Values from all inner sources are merged concurrently.
159
- * @param fn - Function that returns a source for each value.
160
- * @returns An operator that flattens mapped sources.
161
- */
162
- const mergeMap = (fn) => {
163
- return (source) => {
164
- return (sink) => {
165
- let outerCompleted = false;
166
- let activeInner = 0;
167
- let ended = false;
168
- const innerSubscriptions = [];
169
- const checkComplete = () => {
170
- if (outerCompleted && activeInner === 0 && !ended) {
171
- ended = true;
172
- sink.complete();
173
- }
174
- };
175
- const outerSubscription = source({
176
- next(value) {
177
- if (ended) return;
178
- activeInner++;
179
- const innerSubscription = fn(value)({
180
- next(innerValue) {
181
- if (!ended) sink.next(innerValue);
182
- },
183
- complete() {
184
- activeInner--;
185
- checkComplete();
186
- }
187
- });
188
- innerSubscriptions.push(innerSubscription);
189
- },
190
- complete() {
191
- outerCompleted = true;
192
- checkComplete();
193
- }
194
- });
195
- return { unsubscribe() {
196
- ended = true;
197
- outerSubscription.unsubscribe();
198
- for (const sub of innerSubscriptions) sub.unsubscribe();
199
- innerSubscriptions.length = 0;
200
- } };
201
- };
202
- };
203
- };
204
-
205
- //#endregion
206
- //#region src/stream/operators/filter.ts
207
- function filter(predicate) {
208
- return (source) => {
209
- return (sink) => {
210
- return source({
211
- next(value) {
212
- if (predicate(value)) sink.next(value);
213
- },
214
- complete() {
215
- sink.complete();
216
- }
217
- });
218
- };
219
- };
220
- }
221
-
222
- //#endregion
223
- //#region src/stream/sources/from-promise.ts
224
- const fromPromise = (promise) => {
225
- return (sink) => {
226
- let cancelled = false;
227
- promise.then((value) => {
228
- if (!cancelled) {
229
- sink.next(value);
230
- sink.complete();
231
- }
232
- }, () => {
233
- if (!cancelled) sink.complete();
234
- });
235
- return { unsubscribe() {
236
- cancelled = true;
237
- } };
238
- };
239
- };
240
-
241
- //#endregion
242
- //#region src/stream/operators/merge.ts
243
- /**
244
- * Merges multiple sources into a single source.
245
- * Values are emitted as soon as they arrive from any source.
246
- * Completes when all sources complete.
247
- * @param sources - The sources to merge.
248
- * @returns A merged source.
249
- */
250
- const merge = (...sources) => {
251
- return (sink) => {
252
- if (sources.length === 0) {
253
- sink.complete();
254
- return { unsubscribe() {} };
255
- }
256
- let activeCount = sources.length;
257
- const subscriptions = [];
258
- let ended = false;
259
- let ready = false;
260
- const buffer = [];
261
- const checkComplete = () => {
262
- if (activeCount === 0 && !ended) {
263
- ended = true;
264
- sink.complete();
265
- }
266
- };
267
- for (const source of sources) {
268
- const subscription = source({
269
- next(value) {
270
- if (!ended) if (ready) sink.next(value);
271
- else buffer.push(value);
272
- },
273
- complete() {
274
- activeCount--;
275
- if (ready) checkComplete();
276
- }
277
- });
278
- subscriptions.push(subscription);
279
- }
280
- ready = true;
281
- for (const value of buffer) if (!ended) sink.next(value);
282
- buffer.length = 0;
283
- checkComplete();
284
- return { unsubscribe() {
285
- ended = true;
286
- for (const sub of subscriptions) sub.unsubscribe();
287
- } };
288
- };
289
- };
290
-
291
- //#endregion
292
- //#region src/stream/operators/tap.ts
293
- /**
294
- * Executes a side effect for each value without modifying the stream.
295
- * Useful for debugging, logging, or triggering side effects.
296
- * @param fn - The side effect function.
297
- * @returns An operator that taps into the stream.
298
- */
299
- const tap = (fn) => {
300
- return (source) => {
301
- return (sink) => {
302
- return source({
303
- next(value) {
304
- fn(value);
305
- sink.next(value);
306
- },
307
- complete() {
308
- sink.complete();
309
- }
310
- });
311
- };
312
- };
313
- };
314
-
315
78
  //#endregion
316
79
  //#region src/exchanges/http.ts
317
- const executeFetch = async ({ url, fetchOptions, operation, signal }) => {
80
+ const executeFetch = async ({ url, fetchFn, fetchOptions, operation, signal }) => {
318
81
  const { artifact, variables } = operation;
319
82
  let response;
320
83
  try {
321
84
  await Promise.resolve();
322
- response = await fetch(url, {
85
+ response = await fetchFn(url, {
323
86
  method: "POST",
324
87
  mode: fetchOptions.mode,
325
88
  credentials: fetchOptions.credentials,
@@ -374,16 +137,18 @@ const executeFetch = async ({ url, fetchOptions, operation, signal }) => {
374
137
  };
375
138
  };
376
139
  const httpExchange = (options) => {
377
- const { url, headers, mode, credentials } = options;
378
- return ({ forward }) => {
379
- return (ops$) => {
140
+ const { url, headers, mode, credentials, fetch: fetchFn = globalThis.fetch } = options;
141
+ return ({ forward }) => ({
142
+ name: "http",
143
+ io: (ops$) => {
380
144
  const inflight = /* @__PURE__ */ new Map();
381
- return merge(pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "query" || op.artifact.kind === "mutation")), mergeMap((op) => {
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) => {
382
146
  inflight.get(op.key)?.abort();
383
147
  const controller = new AbortController();
384
148
  inflight.set(op.key, controller);
385
- return fromPromise(executeFetch({
149
+ return require_make.fromPromise(executeFetch({
386
150
  url,
151
+ fetchFn,
387
152
  fetchOptions: {
388
153
  mode,
389
154
  credentials,
@@ -395,14 +160,14 @@ const httpExchange = (options) => {
395
160
  inflight.delete(op.key);
396
161
  return result;
397
162
  }));
398
- }), filter((result) => result !== null)), pipe(ops$, filter((op) => op.variant === "teardown" || op.variant === "request" && (op.artifact.kind === "subscription" || op.artifact.kind === "fragment")), tap((op) => {
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) => {
399
164
  if (op.variant === "teardown") {
400
165
  inflight.get(op.key)?.abort();
401
166
  inflight.delete(op.key);
402
167
  }
403
168
  }), forward));
404
- };
405
- };
169
+ }
170
+ });
406
171
  };
407
172
 
408
173
  //#endregion
@@ -441,27 +206,6 @@ const delay = (ms) => {
441
206
  };
442
207
  };
443
208
 
444
- //#endregion
445
- //#region src/stream/sources/from-array.ts
446
- /**
447
- * Creates a source that emits values from an array and completes.
448
- * @param values - The array of values to emit.
449
- * @returns A source containing the array values.
450
- */
451
- const fromArray = (values) => {
452
- return (sink) => {
453
- let cancelled = false;
454
- for (const value of values) {
455
- if (cancelled) break;
456
- sink.next(value);
457
- }
458
- if (!cancelled) sink.complete();
459
- return { unsubscribe() {
460
- cancelled = true;
461
- } };
462
- };
463
- };
464
-
465
209
  //#endregion
466
210
  //#region src/utils.ts
467
211
  /**
@@ -490,306 +234,10 @@ const stringify = (value) => {
490
234
  * @param value - Value to check.
491
235
  * @returns True if the value is nullish.
492
236
  */
493
- const isNullish = (value) => {
237
+ const isNullish$1 = (value) => {
494
238
  return value === void 0 || value === null;
495
239
  };
496
240
 
497
- //#endregion
498
- //#region src/stream/operators/map.ts
499
- /**
500
- * Maps each value from the source through a transformation function.
501
- * @param fn - The transformation function.
502
- * @returns An operator that maps values.
503
- */
504
- const map = (fn) => {
505
- return (source) => {
506
- return (sink) => {
507
- return source({
508
- next(value) {
509
- sink.next(fn(value));
510
- },
511
- complete() {
512
- sink.complete();
513
- }
514
- });
515
- };
516
- };
517
- };
518
-
519
- //#endregion
520
- //#region src/stream/operators/take-until.ts
521
- /**
522
- * Emits values from the source until the notifier source emits a value.
523
- * When the notifier emits, the source is cancelled and completes immediately.
524
- * @param notifier - Source that signals when to complete.
525
- * @returns Operator that completes when notifier emits.
526
- */
527
- const takeUntil = (notifier) => {
528
- return (source) => {
529
- return (sink) => {
530
- let sourceSubscription = null;
531
- let notifierSubscription = null;
532
- let completed = false;
533
- const complete = () => {
534
- if (completed) return;
535
- completed = true;
536
- if (sourceSubscription) sourceSubscription.unsubscribe();
537
- if (notifierSubscription) notifierSubscription.unsubscribe();
538
- sink.complete();
539
- };
540
- notifierSubscription = notifier({
541
- next() {
542
- complete();
543
- },
544
- complete() {}
545
- });
546
- sourceSubscription = source({
547
- next(value) {
548
- if (!completed) sink.next(value);
549
- },
550
- complete() {
551
- complete();
552
- }
553
- });
554
- return { unsubscribe() {
555
- complete();
556
- } };
557
- };
558
- };
559
- };
560
-
561
- //#endregion
562
- //#region src/stream/operators/switch-map.ts
563
- const switchMap = (fn) => {
564
- return (source) => {
565
- return (sink) => {
566
- let outerCompleted = false;
567
- let ended = false;
568
- let innerSubscription = null;
569
- let hasInner = false;
570
- const checkComplete = () => {
571
- if (outerCompleted && !hasInner && !ended) {
572
- ended = true;
573
- sink.complete();
574
- }
575
- };
576
- const outerSubscription = source({
577
- next(value) {
578
- if (ended) return;
579
- if (innerSubscription) {
580
- innerSubscription.unsubscribe();
581
- innerSubscription = null;
582
- }
583
- hasInner = true;
584
- innerSubscription = fn(value)({
585
- next(innerValue) {
586
- if (!ended) sink.next(innerValue);
587
- },
588
- complete() {
589
- hasInner = false;
590
- innerSubscription = null;
591
- checkComplete();
592
- }
593
- });
594
- },
595
- complete() {
596
- outerCompleted = true;
597
- checkComplete();
598
- }
599
- });
600
- return { unsubscribe() {
601
- ended = true;
602
- outerSubscription.unsubscribe();
603
- if (innerSubscription) {
604
- innerSubscription.unsubscribe();
605
- innerSubscription = null;
606
- }
607
- } };
608
- };
609
- };
610
- };
611
-
612
- //#endregion
613
- //#region src/stream/operators/initialize.ts
614
- /**
615
- * Executes a side effect when the source is initialized (being subscribed to).
616
- * @param fn - The side effect function.
617
- * @returns An operator that executes the side effect when the source is initialized.
618
- */
619
- const initialize = (fn) => {
620
- return (source) => {
621
- return (sink) => {
622
- let completed = false;
623
- const subscription = source({
624
- next(value) {
625
- if (!completed) sink.next(value);
626
- },
627
- complete() {
628
- if (!completed) {
629
- completed = true;
630
- sink.complete();
631
- }
632
- }
633
- });
634
- fn();
635
- return { unsubscribe() {
636
- completed = true;
637
- subscription.unsubscribe();
638
- } };
639
- };
640
- };
641
- };
642
-
643
- //#endregion
644
- //#region src/stream/operators/finalize.ts
645
- /**
646
- * Executes a side effect when the source terminates (completes or unsubscribes).
647
- * @param fn - The side effect function.
648
- * @returns An operator that executes the side effect when the source terminates.
649
- */
650
- const finalize = (fn) => {
651
- return (source) => {
652
- return (sink) => {
653
- let completed = false;
654
- const subscription = source({
655
- next(value) {
656
- if (!completed) sink.next(value);
657
- },
658
- complete() {
659
- if (!completed) {
660
- completed = true;
661
- fn();
662
- sink.complete();
663
- }
664
- }
665
- });
666
- return { unsubscribe() {
667
- if (!completed) {
668
- completed = true;
669
- fn();
670
- }
671
- subscription.unsubscribe();
672
- } };
673
- };
674
- };
675
- };
676
-
677
- //#endregion
678
- //#region src/stream/sources/from-value.ts
679
- /**
680
- * Creates a source that emits a single value and completes.
681
- * @param value - The value to emit.
682
- * @returns A source containing the single value.
683
- */
684
- const fromValue = (value) => {
685
- return (sink) => {
686
- let cancelled = false;
687
- if (!cancelled) {
688
- sink.next(value);
689
- sink.complete();
690
- }
691
- return { unsubscribe() {
692
- cancelled = true;
693
- } };
694
- };
695
- };
696
-
697
- //#endregion
698
- //#region src/stream/sources/make-subject.ts
699
- /**
700
- * Creates a new Subject which can be used as an IO event hub.
701
- * @returns A new Subject.
702
- */
703
- const makeSubject = () => {
704
- const sinks = [];
705
- const source = (sink) => {
706
- sinks.push(sink);
707
- return { unsubscribe() {
708
- const idx = sinks.indexOf(sink);
709
- if (idx !== -1) sinks.splice(idx, 1);
710
- } };
711
- };
712
- const next = (value) => {
713
- for (const sink of [...sinks]) sink.next(value);
714
- };
715
- const complete = () => {
716
- for (const sink of [...sinks]) sink.complete();
717
- sinks.length = 0;
718
- };
719
- return {
720
- source,
721
- next,
722
- complete
723
- };
724
- };
725
-
726
- //#endregion
727
- //#region src/stream/sources/from-subscription.ts
728
- const fromSubscription = (pull, poke) => {
729
- return (sink) => {
730
- let teardown = null;
731
- let cancelled = false;
732
- const initialValue = pull();
733
- sink.next(initialValue);
734
- if (cancelled) return { unsubscribe() {
735
- cancelled = true;
736
- } };
737
- teardown = poke(() => {
738
- if (!cancelled) {
739
- const value = pull();
740
- sink.next(value);
741
- }
742
- });
743
- return { unsubscribe() {
744
- cancelled = true;
745
- if (teardown) {
746
- teardown();
747
- teardown = null;
748
- }
749
- } };
750
- };
751
- };
752
-
753
- //#endregion
754
- //#region src/stream/sources/make.ts
755
- /**
756
- * Creates a new Source from scratch from a passed subscriber function.
757
- *
758
- * The subscriber function receives an observer with next and complete callbacks.
759
- * It must return a teardown function which is called when the source is cancelled.
760
- * @internal
761
- * @param subscriber - A callback that is called when the Source is subscribed to.
762
- * @returns A Source created from the subscriber parameter.
763
- */
764
- const make = (subscriber) => {
765
- return (sink) => {
766
- let cancelled = false;
767
- let teardown = null;
768
- teardown = subscriber({
769
- next: (value) => {
770
- if (!cancelled) sink.next(value);
771
- },
772
- complete: () => {
773
- if (!cancelled) {
774
- cancelled = true;
775
- if (teardown) {
776
- teardown();
777
- teardown = null;
778
- }
779
- sink.complete();
780
- }
781
- }
782
- });
783
- return { unsubscribe() {
784
- cancelled = true;
785
- if (teardown) {
786
- teardown();
787
- teardown = null;
788
- }
789
- } };
790
- };
791
- };
792
-
793
241
  //#endregion
794
242
  //#region src/exchanges/dedup.ts
795
243
  const makeDedupKey = (op) => {
@@ -811,16 +259,17 @@ const makeDedupKey = (op) => {
811
259
  * @returns An exchange that deduplicates in-flight operations.
812
260
  */
813
261
  const dedupExchange = () => {
814
- return ({ forward }) => {
815
- return (ops$) => {
262
+ return ({ forward }) => ({
263
+ name: "dedup",
264
+ io: (ops$) => {
816
265
  const operations = /* @__PURE__ */ new Map();
817
- return pipe(merge(pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "fragment"))), pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind !== "mutation" && op.artifact.kind !== "fragment"), filter((op) => {
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) => {
818
267
  const dedupKey = makeDedupKey(op);
819
268
  const isInflight = operations.has(dedupKey);
820
269
  if (isInflight) operations.get(dedupKey).add(op.key);
821
270
  else operations.set(dedupKey, new Set([op.key]));
822
271
  return (op.metadata.dedup?.skip ?? false) || !isInflight;
823
- }), delay(0)), pipe(ops$, filter((op) => op.variant === "teardown"), filter((teardown) => {
272
+ }), delay(0)), require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown"), require_make.filter((teardown) => {
824
273
  for (const [dedupKey, subs] of operations.entries()) if (subs.delete(teardown.key)) {
825
274
  if (subs.size === 0) {
826
275
  operations.delete(dedupKey);
@@ -829,10 +278,10 @@ const dedupExchange = () => {
829
278
  return false;
830
279
  }
831
280
  return true;
832
- }))), forward, mergeMap((result) => {
833
- if (result.operation.variant !== "request" || result.operation.artifact.kind === "mutation" || result.operation.artifact.kind === "fragment") return fromValue(result);
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);
834
283
  const dedupKey = makeDedupKey(result.operation);
835
- return fromArray([...operations.get(dedupKey) ?? /* @__PURE__ */ new Set()].map((key) => ({
284
+ return require_make.fromArray([...operations.get(dedupKey) ?? /* @__PURE__ */ new Set()].map((key) => ({
836
285
  ...result,
837
286
  operation: {
838
287
  ...result.operation,
@@ -840,8 +289,8 @@ const dedupExchange = () => {
840
289
  }
841
290
  })));
842
291
  }));
843
- };
844
- };
292
+ }
293
+ });
845
294
  };
846
295
 
847
296
  //#endregion
@@ -900,6 +349,15 @@ const makeFieldKey = (selection, variables) => {
900
349
  return `${selection.name}@${args}`;
901
350
  };
902
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
+ /**
903
361
  * Gets a unique key for tracking a field dependency.
904
362
  * @internal
905
363
  * @param storageKey Storage key (entity or root query key).
@@ -928,46 +386,166 @@ const isFragmentRef = (value) => {
928
386
  return typeof value === "object" && value !== null && FragmentRefKey in value;
929
387
  };
930
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
+ /**
931
398
  * Type guard to check if a value is nullish.
932
399
  * @internal
933
400
  * @param value - Value to check.
934
401
  * @returns True if the value is nullish.
935
402
  */
936
- const isNullish$1 = (value) => {
403
+ const isNullish = (value) => {
937
404
  return value === void 0 || value === null;
938
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
+ };
939
511
 
940
512
  //#endregion
941
513
  //#region src/cache/normalize.ts
514
+ const SKIP = Symbol();
942
515
  const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
943
- const normalizeField = (storageKey, selections$1, value) => {
944
- if (isNullish$1(value)) return value;
945
- if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections$1, item));
946
- const data$1 = value;
947
- const typename = data$1.__typename;
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;
948
521
  const entityMeta = schemaMeta.entities[typename];
949
- if (entityMeta) storageKey = makeEntityKey(typename, entityMeta.keyFields.map((field) => data$1[field]));
950
- const fields$1 = {};
951
- for (const selection of selections$1) if (selection.kind === "Field") {
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") {
952
529
  const fieldKey = makeFieldKey(selection, variables);
953
- const fieldValue = data$1[selection.alias ?? selection.name];
954
- if (storageKey !== null) {
955
- const oldValue = storage[storageKey]?.[fieldKey];
956
- if (!selection.selections || isNullish$1(oldValue) || isNullish$1(fieldValue)) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
957
- }
958
- fields$1[fieldKey] = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
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]);
959
537
  } else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
960
538
  const inner = normalizeField(storageKey, selection.selections, value);
961
- if (!isEntityLink(inner)) Object.assign(fields$1, inner);
539
+ if (inner !== SKIP && !isEntityLink(inner)) mergeFields(fields, inner);
962
540
  }
963
541
  if (entityMeta && storageKey !== null) {
964
542
  storage[storageKey] = {
965
543
  ...storage[storageKey],
966
- ...fields$1
544
+ ...fields
967
545
  };
968
546
  return { [EntityLinkKey]: storageKey };
969
547
  }
970
- return fields$1;
548
+ return fields;
971
549
  };
972
550
  const fields = normalizeField(RootFieldKey, selections, data);
973
551
  storage[RootFieldKey] = {
@@ -985,10 +563,10 @@ const typenameFieldKey = makeFieldKey({
985
563
  }, {});
986
564
  const denormalize = (selections, storage, value, variables, accessor) => {
987
565
  let partial = false;
988
- const denormalizeField = (storageKey, selections$1, value$1) => {
989
- if (isNullish$1(value$1)) return value$1;
990
- if (Array.isArray(value$1)) return value$1.map((item) => denormalizeField(storageKey, selections$1, item));
991
- const data = value$1;
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;
992
570
  if (isEntityLink(data)) {
993
571
  const entityKey = data[EntityLinkKey];
994
572
  const entity = storage[entityKey];
@@ -997,10 +575,10 @@ const denormalize = (selections, storage, value, variables, accessor) => {
997
575
  partial = true;
998
576
  return null;
999
577
  }
1000
- return denormalizeField(entityKey, selections$1, entity);
578
+ return denormalizeField(entityKey, selections, entity);
1001
579
  }
1002
580
  const fields = {};
1003
- for (const selection of selections$1) if (selection.kind === "Field") {
581
+ for (const selection of selections) if (selection.kind === "Field") {
1004
582
  const fieldKey = makeFieldKey(selection, variables);
1005
583
  const fieldValue = data[fieldKey];
1006
584
  if (storageKey !== null) accessor?.(storageKey, fieldKey);
@@ -1008,10 +586,13 @@ const denormalize = (selections, storage, value, variables, accessor) => {
1008
586
  partial = true;
1009
587
  continue;
1010
588
  }
1011
- fields[selection.alias ?? selection.name] = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
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;
1012
593
  } else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) fields[FragmentRefKey] = storageKey;
1013
- else Object.assign(fields, denormalizeField(storageKey, selection.selections, value$1));
1014
- else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) Object.assign(fields, denormalizeField(storageKey, selection.selections, value$1));
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));
1015
596
  return fields;
1016
597
  };
1017
598
  return {
@@ -1030,6 +611,7 @@ var Cache = class {
1030
611
  #schemaMeta;
1031
612
  #storage = { [RootFieldKey]: {} };
1032
613
  #subscriptions = /* @__PURE__ */ new Map();
614
+ #memo = /* @__PURE__ */ new Map();
1033
615
  constructor(schemaMetadata) {
1034
616
  this.#schemaMeta = schemaMetadata;
1035
617
  }
@@ -1056,13 +638,19 @@ var Cache = class {
1056
638
  }
1057
639
  /**
1058
640
  * Reads a query result from the cache, denormalizing entities if available.
641
+ * Uses structural sharing to preserve referential identity for unchanged subtrees.
1059
642
  * @param artifact - GraphQL document artifact.
1060
643
  * @param variables - Query variables.
1061
644
  * @returns Denormalized query result or null if not found.
1062
645
  */
1063
646
  readQuery(artifact, variables) {
1064
647
  const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables);
1065
- return partial ? null : data;
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;
1066
654
  }
1067
655
  /**
1068
656
  * Subscribes to cache invalidations for a specific query.
@@ -1081,8 +669,7 @@ var Cache = class {
1081
669
  }
1082
670
  /**
1083
671
  * Reads a fragment from the cache for a specific entity.
1084
- * Returns null for invalid or missing fragment references, making it safe for
1085
- * defensive reads. For subscriptions, use subscribeFragment which throws errors.
672
+ * Uses structural sharing to preserve referential identity for unchanged subtrees.
1086
673
  * @param artifact - GraphQL fragment artifact.
1087
674
  * @param fragmentRef - Fragment reference containing entity key.
1088
675
  * @returns Denormalized fragment data or null if not found or invalid.
@@ -1091,7 +678,12 @@ var Cache = class {
1091
678
  const entityKey = fragmentRef[FragmentRefKey];
1092
679
  if (!this.#storage[entityKey]) return null;
1093
680
  const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {});
1094
- return partial ? null : data;
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;
1095
687
  }
1096
688
  subscribeFragment(artifact, fragmentRef, listener) {
1097
689
  const entityKey = fragmentRef[FragmentRefKey];
@@ -1102,6 +694,80 @@ var Cache = class {
1102
694
  });
1103
695
  return this.#subscribe(dependencies, listener);
1104
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
+ }
1105
771
  #subscribe(dependencies, listener) {
1106
772
  const subscription = { listener };
1107
773
  for (const dependency of dependencies) {
@@ -1118,62 +784,134 @@ var Cache = class {
1118
784
  };
1119
785
  }
1120
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
+ /**
1121
807
  * Clears all cache data.
1122
808
  */
1123
809
  clear() {
1124
810
  this.#storage = { [RootFieldKey]: {} };
1125
811
  this.#subscriptions.clear();
812
+ this.#memo.clear();
1126
813
  }
1127
814
  };
1128
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
+
1129
829
  //#endregion
1130
830
  //#region src/exchanges/cache.ts
1131
831
  const cacheExchange = (options = {}) => {
1132
832
  const { fetchPolicy = "cache-first" } = options;
1133
833
  return ({ forward, client }) => {
1134
834
  const cache = new Cache(client.schema);
1135
- return (ops$) => {
1136
- const fragment$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), mergeMap((op) => {
1137
- const fragmentRef = op.metadata?.fragmentRef;
1138
- if (!fragmentRef) return fromValue({
1139
- operation: op,
1140
- errors: [new ExchangeError("Fragment operation missing fragmentRef in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "cache" })]
1141
- });
1142
- if (!isFragmentRef(fragmentRef)) return fromValue({
1143
- operation: op,
1144
- data: fragmentRef,
1145
- errors: []
1146
- });
1147
- const trigger = makeSubject();
1148
- const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
1149
- return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragment(op.artifact, fragmentRef), () => cache.subscribeFragment(op.artifact, fragmentRef, async () => {
1150
- await Promise.resolve();
1151
- trigger.next();
1152
- }))), takeUntil(teardown$), map((data) => ({
1153
- operation: op,
1154
- data,
1155
- errors: []
1156
- })));
1157
- }));
1158
- const nonCache$ = pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")));
1159
- const query$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), share());
1160
- return merge(fragment$, pipe(query$, mergeMap((op) => {
1161
- const trigger = makeSubject();
1162
- const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
1163
- return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async () => {
1164
- await Promise.resolve();
1165
- trigger.next();
1166
- }))), takeUntil(teardown$), map((data) => ({
1167
- operation: op,
1168
- data,
1169
- errors: []
1170
- })));
1171
- }), filter((result) => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" && result.data !== null || fetchPolicy === "cache-first" && result.data !== null)), pipe(merge(nonCache$, pipe(query$, filter((op) => {
1172
- const cached = cache.readQuery(op.artifact, op.variables);
1173
- return fetchPolicy === "cache-and-network" || cached === null;
1174
- })), pipe(ops$, filter((op) => op.variant === "teardown"))), forward, tap((result) => {
1175
- if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
1176
- }), filter((result) => result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only")));
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
+ }
1177
915
  };
1178
916
  };
1179
917
  };
@@ -1183,17 +921,18 @@ const cacheExchange = (options = {}) => {
1183
921
  const defaultShouldRetry = (error) => isExchangeError(error, "http") && error.extensions?.statusCode !== void 0 && error.extensions.statusCode >= 500;
1184
922
  const retryExchange = (options = {}) => {
1185
923
  const { maxAttempts = 3, backoff = (attempt) => Math.min(1e3 * 2 ** attempt, 3e4), shouldRetry = defaultShouldRetry } = options;
1186
- return ({ forward }) => {
1187
- return (ops$) => {
1188
- const { source: retries$, next } = makeSubject();
924
+ return ({ forward }) => ({
925
+ name: "retry",
926
+ io: (ops$) => {
927
+ const { source: retries$, next } = require_make.makeSubject();
1189
928
  const tornDown = /* @__PURE__ */ new Set();
1190
- const teardown$ = pipe(ops$, filter((op) => op.variant === "teardown"), tap((op) => {
929
+ const teardown$ = require_make.pipe(ops$, require_make.filter((op) => op.variant === "teardown"), require_make.tap((op) => {
1191
930
  tornDown.add(op.key);
1192
931
  }));
1193
- return pipe(merge(pipe(ops$, filter((op) => op.variant === "request")), pipe(retries$, filter((op) => !tornDown.has(op.key)), mergeMap((op) => {
1194
- const teardown$$1 = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key));
1195
- return pipe(fromValue(op), delay(op.metadata.retry.delay), takeUntil(teardown$$1));
1196
- })), teardown$), forward, filter((result) => {
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) => {
1197
936
  if (!result.errors || result.errors.length === 0) return true;
1198
937
  if (result.operation.variant === "request" && result.operation.artifact.kind === "mutation") return true;
1199
938
  const attempt = result.operation.metadata.retry?.attempt ?? 0;
@@ -1212,16 +951,17 @@ const retryExchange = (options = {}) => {
1212
951
  });
1213
952
  return false;
1214
953
  }));
1215
- };
1216
- };
954
+ }
955
+ });
1217
956
  };
1218
957
 
1219
958
  //#endregion
1220
959
  //#region src/exchanges/fragment.ts
1221
960
  const fragmentExchange = () => {
1222
- return ({ forward }) => {
1223
- return (ops$) => {
1224
- return merge(pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), map((op) => {
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) => {
1225
965
  const fragmentRef = op.metadata.fragmentRef;
1226
966
  if (!fragmentRef) return {
1227
967
  operation: op,
@@ -1231,13 +971,104 @@ const fragmentExchange = () => {
1231
971
  operation: op,
1232
972
  data: fragmentRef
1233
973
  };
1234
- })), pipe(ops$, filter((op) => op.variant === "teardown" || op.artifact.kind !== "fragment"), forward));
1235
- };
1236
- };
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
+ });
1237
1067
  };
1238
1068
 
1239
1069
  //#endregion
1240
1070
  //#region src/exchanges/subscription.ts
1071
+ const shouldHandle = (op) => op.variant === "request" && (op.artifact.kind === "subscription" || op.metadata.subscription?.transport === true);
1241
1072
  /**
1242
1073
  * Creates an exchange for handling GraphQL subscriptions using a subscription client.
1243
1074
  *
@@ -1267,11 +1098,12 @@ const fragmentExchange = () => {
1267
1098
  */
1268
1099
  const subscriptionExchange = (options) => {
1269
1100
  const { client } = options;
1270
- return ({ forward }) => {
1271
- return (ops$) => {
1272
- return merge(pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "subscription"), mergeMap((op) => {
1273
- const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key));
1274
- return pipe(make((observer) => {
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) => {
1275
1107
  let unsubscribe;
1276
1108
  let completed = false;
1277
1109
  Promise.resolve().then(() => {
@@ -1310,42 +1142,74 @@ const subscriptionExchange = (options) => {
1310
1142
  completed = true;
1311
1143
  unsubscribe?.();
1312
1144
  };
1313
- }), takeUntil(teardown$));
1314
- })), pipe(ops$, filter((op) => op.variant === "teardown" || op.artifact.kind !== "subscription"), forward));
1315
- };
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
1316
1170
  };
1317
1171
  };
1318
1172
 
1319
1173
  //#endregion
1320
1174
  //#region src/scalars.ts
1321
1175
  const parse = (selections, scalars, value) => {
1322
- const parseValue = (selection, value$1) => {
1323
- if (isNullish(value$1)) return value$1;
1324
- if (selection.array && Array.isArray(value$1)) return value$1.map((item) => parseValue({
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({
1325
1179
  ...selection,
1326
1180
  array: false
1327
- }, item));
1328
- if (selection.selections) return parseField(selection.selections, value$1);
1181
+ }, item, parsedMap));
1182
+ if (selection.selections) return parseField(selection.selections, value, parsedMap);
1329
1183
  const transformer = scalars[selection.type];
1330
- if (transformer) return transformer.parse(value$1);
1331
- return value$1;
1184
+ if (transformer) return transformer.parse(value);
1185
+ return value;
1332
1186
  };
1333
- const parseField = (selections$1, value$1) => {
1334
- if (isNullish(value$1)) return value$1;
1335
- const data = value$1;
1336
- const fields = {};
1337
- for (const selection of selections$1) if (selection.kind === "Field") {
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") {
1338
1197
  const fieldName = selection.alias ?? selection.name;
1339
- const fieldValue = data[fieldName];
1340
- fields[fieldName] = parseValue(selection, fieldValue);
1341
- } else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === data.__typename) Object.assign(fields, parseField(selection.selections, value$1));
1342
- return fields;
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;
1343
1207
  };
1344
1208
  return parseField(selections, value);
1345
1209
  };
1346
1210
  const serialize = (schemaMeta, variableDefs, scalars, variables) => {
1347
1211
  const serializeValue = (variableDef, value) => {
1348
- if (isNullish(value)) return value;
1212
+ if (isNullish$1(value)) return value;
1349
1213
  if (variableDef.array && Array.isArray(value)) return value.map((item) => serializeValue({
1350
1214
  ...variableDef,
1351
1215
  array: false
@@ -1356,11 +1220,11 @@ const serialize = (schemaMeta, variableDefs, scalars, variables) => {
1356
1220
  if (transformer) return transformer.serialize(value);
1357
1221
  return value;
1358
1222
  };
1359
- const serializeField = (variableDefs$1, value) => {
1360
- if (isNullish(value)) return value;
1223
+ const serializeField = (variableDefs, value) => {
1224
+ if (isNullish$1(value)) return value;
1361
1225
  const data = value;
1362
1226
  const fields = {};
1363
- for (const variableDef of variableDefs$1) {
1227
+ for (const variableDef of variableDefs) {
1364
1228
  const variableValue = data[variableDef.name];
1365
1229
  fields[variableDef.name] = serializeValue(variableDef, variableValue);
1366
1230
  }
@@ -1372,36 +1236,38 @@ const serialize = (schemaMeta, variableDefs, scalars, variables) => {
1372
1236
  //#endregion
1373
1237
  //#region src/exchanges/scalar.ts
1374
1238
  const scalarExchange = () => {
1375
- return ({ forward, client }) => {
1376
- return (ops$) => {
1377
- return pipe(ops$, map((op) => {
1239
+ return ({ forward, client }) => ({
1240
+ name: "scalar",
1241
+ io: (ops$) => {
1242
+ return require_make.pipe(ops$, require_make.map((op) => {
1378
1243
  if (op.variant !== "request" || !op.artifact.variableDefs || !client.scalars) return op;
1379
1244
  return {
1380
1245
  ...op,
1381
1246
  variables: serialize(client.schema, op.artifact.variableDefs, client.scalars, op.variables)
1382
1247
  };
1383
- }), forward, map((result) => {
1248
+ }), forward, require_make.map((result) => {
1384
1249
  if (result.operation.variant !== "request" || !result.data || !client.scalars) return result;
1385
1250
  return {
1386
1251
  ...result,
1387
1252
  data: parse(result.operation.artifact.selections, client.scalars, result.data)
1388
1253
  };
1389
1254
  }));
1390
- };
1391
- };
1255
+ }
1256
+ });
1392
1257
  };
1393
1258
 
1394
1259
  //#endregion
1395
1260
  //#region src/exchanges/terminal.ts
1396
1261
  const terminalExchange = () => {
1397
- return () => {
1398
- return (ops$) => {
1399
- return pipe(ops$, filter((op) => op.variant !== "teardown"), mergeMap((op) => fromValue({
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({
1400
1266
  operation: op,
1401
1267
  errors: [new ExchangeError("No terminal exchange found in exchange chain. Did you forget to add httpExchange to your exchanges array?", { exchangeName: "terminal" })]
1402
1268
  })));
1403
- };
1404
- };
1269
+ }
1270
+ });
1405
1271
  };
1406
1272
 
1407
1273
  //#endregion
@@ -1421,22 +1287,25 @@ const never = () => {
1421
1287
  var Client = class {
1422
1288
  #schema;
1423
1289
  #scalars;
1290
+ #extensions;
1424
1291
  operations$;
1425
1292
  results$;
1426
1293
  constructor(config) {
1427
1294
  this.#schema = config.schema;
1428
1295
  this.#scalars = config.scalars;
1429
- const exchange = composeExchange({ exchanges: [
1296
+ const { io, extensions } = composeExchanges({ exchanges: [
1297
+ requiredExchange(),
1430
1298
  scalarExchange(),
1431
1299
  ...config.exchanges,
1432
1300
  fragmentExchange(),
1433
1301
  terminalExchange()
1434
- ] });
1435
- this.operations$ = makeSubject();
1436
- this.results$ = exchange({
1302
+ ] }, {
1437
1303
  forward: never,
1438
1304
  client: this
1439
- })(this.operations$.source);
1305
+ });
1306
+ this.#extensions = extensions;
1307
+ this.operations$ = require_make.makeSubject();
1308
+ this.results$ = io(this.operations$.source);
1440
1309
  }
1441
1310
  get schema() {
1442
1311
  return this.#schema;
@@ -1447,44 +1316,67 @@ var Client = class {
1447
1316
  createOperationKey() {
1448
1317
  return Math.random().toString(36).slice(2) + Date.now().toString(36);
1449
1318
  }
1450
- createOperation(artifact, variables) {
1319
+ createOperation(artifact, variables, metadata) {
1451
1320
  return {
1452
1321
  variant: "request",
1453
1322
  key: this.createOperationKey(),
1454
- metadata: {},
1323
+ metadata: { ...metadata },
1455
1324
  artifact,
1456
1325
  variables: variables ?? {}
1457
1326
  };
1458
1327
  }
1459
1328
  executeOperation(operation) {
1460
- return pipe(this.results$, initialize(() => this.operations$.next(operation)), filter((result) => result.operation.key === operation.key), finalize(() => this.operations$.next({
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({
1461
1330
  variant: "teardown",
1462
1331
  key: operation.key,
1463
1332
  metadata: {}
1464
- })), share());
1333
+ })), require_make.share());
1465
1334
  }
1466
1335
  executeQuery(artifact, ...[variables, options]) {
1467
- const operation = this.createOperation(artifact, variables);
1336
+ const operation = this.createOperation(artifact, variables, options?.metadata);
1468
1337
  return this.executeOperation(operation);
1469
1338
  }
1470
1339
  executeMutation(artifact, ...[variables, options]) {
1471
- const operation = this.createOperation(artifact, variables);
1340
+ const operation = this.createOperation(artifact, variables, options?.metadata);
1472
1341
  return this.executeOperation(operation);
1473
1342
  }
1474
1343
  executeSubscription(artifact, ...[variables, options]) {
1475
- const operation = this.createOperation(artifact, variables);
1344
+ const operation = this.createOperation(artifact, variables, options?.metadata);
1476
1345
  return this.executeOperation(operation);
1477
1346
  }
1478
1347
  executeFragment(artifact, fragmentRef, options) {
1479
1348
  const operation = {
1480
1349
  variant: "request",
1481
1350
  key: this.createOperationKey(),
1482
- metadata: { fragmentRef },
1351
+ metadata: {
1352
+ ...options?.metadata,
1353
+ fragmentRef
1354
+ },
1483
1355
  artifact,
1484
1356
  variables: {}
1485
1357
  };
1486
1358
  return this.executeOperation(operation);
1487
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
+ }
1488
1380
  dispose() {
1489
1381
  this.operations$.complete();
1490
1382
  }
@@ -1498,8 +1390,8 @@ exports.AggregatedError = AggregatedError;
1498
1390
  exports.Client = Client;
1499
1391
  exports.ExchangeError = ExchangeError;
1500
1392
  exports.GraphQLError = GraphQLError;
1393
+ exports.RequiredFieldError = RequiredFieldError;
1501
1394
  exports.cacheExchange = cacheExchange;
1502
- exports.composeExchange = composeExchange;
1503
1395
  exports.createClient = createClient;
1504
1396
  exports.dedupExchange = dedupExchange;
1505
1397
  exports.fragmentExchange = fragmentExchange;
@@ -1507,6 +1399,7 @@ exports.httpExchange = httpExchange;
1507
1399
  exports.isAggregatedError = isAggregatedError;
1508
1400
  exports.isExchangeError = isExchangeError;
1509
1401
  exports.isGraphQLError = isGraphQLError;
1402
+ exports.requiredExchange = requiredExchange;
1510
1403
  exports.retryExchange = retryExchange;
1511
1404
  exports.stringify = stringify;
1512
1405
  exports.subscriptionExchange = subscriptionExchange;