@mearie/core 0.0.1-next.3 → 0.0.1-next.4

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