@mearie/core 0.0.0-next-20260228035926

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.mjs ADDED
@@ -0,0 +1,1393 @@
1
+ import { C as mergeMap, S as filter, a as finalize, b as merge, c as share, d as map, i as fromValue, l as takeUntil, m as collect, n as fromSubscription, o as initialize, r as makeSubject, s as switchMap, t as make, u as take, v as fromArray, w as pipe, x as fromPromise, y as tap } from "./make-C7I1YIXm.mjs";
2
+
3
+ //#region src/errors.ts
4
+ /**
5
+ *
6
+ */
7
+ var GraphQLError = class GraphQLError extends Error {
8
+ path;
9
+ locations;
10
+ extensions;
11
+ constructor(message, options) {
12
+ super(message, { cause: options?.cause });
13
+ this.name = "GraphQLError";
14
+ this.path = options?.path;
15
+ this.locations = options?.locations;
16
+ this.extensions = options?.extensions;
17
+ Object.setPrototypeOf(this, GraphQLError.prototype);
18
+ }
19
+ toJSON() {
20
+ const json = { message: this.message };
21
+ if (this.path) json.path = this.path;
22
+ if (this.locations) json.locations = this.locations;
23
+ if (this.extensions) json.extensions = this.extensions;
24
+ return json;
25
+ }
26
+ };
27
+ /**
28
+ *
29
+ */
30
+ var ExchangeError = class ExchangeError extends Error {
31
+ exchangeName;
32
+ extensions;
33
+ constructor(message, options) {
34
+ super(message, { cause: options.cause });
35
+ this.name = "ExchangeError";
36
+ this.exchangeName = options.exchangeName;
37
+ this.extensions = options.extensions;
38
+ Object.setPrototypeOf(this, ExchangeError.prototype);
39
+ }
40
+ toJSON() {
41
+ const json = {
42
+ message: this.message,
43
+ exchangeName: this.exchangeName
44
+ };
45
+ if (this.extensions !== void 0) json.extensions = this.extensions;
46
+ return json;
47
+ }
48
+ };
49
+ /**
50
+ *
51
+ */
52
+ var AggregatedError = class AggregatedError extends AggregateError {
53
+ constructor(errors, message = `${errors.length} error(s) occurred`) {
54
+ super([...errors], message);
55
+ this.name = "AggregatedError";
56
+ Object.setPrototypeOf(this, AggregatedError.prototype);
57
+ }
58
+ toJSON() {
59
+ return {
60
+ message: this.message,
61
+ errors: this.errors.map((error) => error.toJSON())
62
+ };
63
+ }
64
+ };
65
+ const isGraphQLError = (error) => {
66
+ return error instanceof GraphQLError;
67
+ };
68
+ function isExchangeError(error, exchangeName) {
69
+ if (!(error instanceof ExchangeError)) return false;
70
+ if (exchangeName !== void 0) return error.exchangeName === exchangeName;
71
+ return true;
72
+ }
73
+ const isAggregatedError = (error) => {
74
+ return error instanceof AggregatedError;
75
+ };
76
+
77
+ //#endregion
78
+ //#region src/exchanges/http.ts
79
+ const executeFetch = async ({ url, fetchFn, fetchOptions, operation, signal }) => {
80
+ const { artifact, variables } = operation;
81
+ let response;
82
+ try {
83
+ await Promise.resolve();
84
+ response = await fetchFn(url, {
85
+ method: "POST",
86
+ mode: fetchOptions.mode,
87
+ credentials: fetchOptions.credentials,
88
+ headers: {
89
+ "Content-Type": "application/json",
90
+ ...fetchOptions.headers
91
+ },
92
+ body: JSON.stringify({
93
+ operationName: artifact.name,
94
+ query: artifact.body,
95
+ variables
96
+ }),
97
+ signal
98
+ });
99
+ } catch (error) {
100
+ if (error instanceof Error && error.name === "AbortError") return null;
101
+ return {
102
+ operation,
103
+ errors: [new ExchangeError(error instanceof Error ? error.message : "Network error", {
104
+ exchangeName: "http",
105
+ cause: error
106
+ })]
107
+ };
108
+ }
109
+ if (!response.ok) return {
110
+ operation,
111
+ errors: [new ExchangeError(`HTTP ${response.status}: ${response.statusText}`, {
112
+ exchangeName: "http",
113
+ extensions: { statusCode: response.status }
114
+ })]
115
+ };
116
+ let json;
117
+ try {
118
+ json = await response.json();
119
+ } catch (error) {
120
+ return {
121
+ operation,
122
+ errors: [new ExchangeError(error instanceof Error ? error.message : "JSON parse error", {
123
+ exchangeName: "http",
124
+ cause: error
125
+ })]
126
+ };
127
+ }
128
+ return {
129
+ operation,
130
+ data: json.data,
131
+ errors: json.errors?.map((err) => new GraphQLError(err.message, {
132
+ path: err.path,
133
+ locations: err.locations,
134
+ extensions: err.extensions
135
+ })),
136
+ extensions: json.extensions
137
+ };
138
+ };
139
+ const httpExchange = (options) => {
140
+ const { url, headers, mode, credentials, fetch: fetchFn = globalThis.fetch } = options;
141
+ return ({ forward }) => ({
142
+ name: "http",
143
+ io: (ops$) => {
144
+ const inflight = /* @__PURE__ */ new Map();
145
+ return merge(pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "query" || op.artifact.kind === "mutation")), mergeMap((op) => {
146
+ inflight.get(op.key)?.abort();
147
+ const controller = new AbortController();
148
+ inflight.set(op.key, controller);
149
+ return fromPromise(executeFetch({
150
+ url,
151
+ fetchFn,
152
+ fetchOptions: {
153
+ mode,
154
+ credentials,
155
+ headers
156
+ },
157
+ operation: op,
158
+ signal: controller.signal
159
+ }).then((result) => {
160
+ inflight.delete(op.key);
161
+ return result;
162
+ }));
163
+ }), filter((result) => result !== null)), pipe(ops$, filter((op) => op.variant === "teardown" || op.variant === "request" && (op.artifact.kind === "subscription" || op.artifact.kind === "fragment")), tap((op) => {
164
+ if (op.variant === "teardown") {
165
+ inflight.get(op.key)?.abort();
166
+ inflight.delete(op.key);
167
+ }
168
+ }), forward));
169
+ }
170
+ });
171
+ };
172
+
173
+ //#endregion
174
+ //#region src/stream/operators/delay.ts
175
+ /**
176
+ * Delays each value emitted by a source by the specified time.
177
+ * @param ms - The time (in milliseconds) to delay each value.
178
+ * @returns An operator that delays values.
179
+ */
180
+ const delay = (ms) => {
181
+ return (source) => {
182
+ return (sink) => {
183
+ let cancelled = false;
184
+ const timeouts = [];
185
+ const upstreamSubscription = source({
186
+ next(value) {
187
+ const timeout = setTimeout(() => {
188
+ if (!cancelled) sink.next(value);
189
+ }, ms);
190
+ timeouts.push(timeout);
191
+ },
192
+ complete() {
193
+ const timeout = setTimeout(() => {
194
+ if (!cancelled) sink.complete();
195
+ }, ms);
196
+ timeouts.push(timeout);
197
+ }
198
+ });
199
+ return { unsubscribe() {
200
+ cancelled = true;
201
+ for (const timeout of timeouts) clearTimeout(timeout);
202
+ timeouts.length = 0;
203
+ upstreamSubscription.unsubscribe();
204
+ } };
205
+ };
206
+ };
207
+ };
208
+
209
+ //#endregion
210
+ //#region src/utils.ts
211
+ /**
212
+ * Stable JSON serialization with sorted keys.
213
+ * Used for both variables and field arguments.
214
+ * @internal
215
+ * @param value - The value to stringify.
216
+ * @returns The stable JSON string.
217
+ */
218
+ const stringify = (value) => {
219
+ if (value === null) return "null";
220
+ if (value === void 0) return "undefined";
221
+ const type = typeof value;
222
+ if (type === "string") return JSON.stringify(value);
223
+ if (type === "number" || type === "boolean") return String(value);
224
+ if (Array.isArray(value)) return "[" + value.map((v) => stringify(v)).join(",") + "]";
225
+ if (type === "object") {
226
+ const obj = value;
227
+ return "{" + Object.keys(obj).toSorted().filter((k) => obj[k] !== void 0).map((k) => `"${k}":${stringify(obj[k])}`).join(",") + "}";
228
+ }
229
+ return JSON.stringify(value) ?? "";
230
+ };
231
+ /**
232
+ * Type guard to check if a value is nullish.
233
+ * @internal
234
+ * @param value - Value to check.
235
+ * @returns True if the value is nullish.
236
+ */
237
+ const isNullish$1 = (value) => {
238
+ return value === void 0 || value === null;
239
+ };
240
+
241
+ //#endregion
242
+ //#region src/exchanges/dedup.ts
243
+ const makeDedupKey = (op) => {
244
+ return `${op.artifact.name}:${stringify(op.variables)}`;
245
+ };
246
+ /**
247
+ * Prevents duplicate in-flight operations by deduplicating requests with identical artifact names and variables.
248
+ *
249
+ * Operations are considered identical if they have the same artifact name and serialized variables.
250
+ * Mutations are never deduplicated. An operation is "in-flight" from when it's first seen until all subscribers tear down.
251
+ *
252
+ * Caveats:
253
+ *
254
+ * 1. Upstream metadata is lost when operations are deduplicated. The result will contain the metadata
255
+ * from the operation that actually went through the pipeline, not from deduplicated operations.
256
+ * This preserves downstream metadata (retry attempts, cache status) but means custom upstream metadata
257
+ * from deduplicated operations will not appear in results.
258
+ * @internal
259
+ * @returns An exchange that deduplicates in-flight operations.
260
+ */
261
+ const dedupExchange = () => {
262
+ return ({ forward }) => ({
263
+ name: "dedup",
264
+ io: (ops$) => {
265
+ const operations = /* @__PURE__ */ new Map();
266
+ const resolved = /* @__PURE__ */ new Set();
267
+ 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) => {
268
+ const dedupKey = makeDedupKey(op);
269
+ const isInflight = operations.has(dedupKey) && !resolved.has(dedupKey);
270
+ if (isInflight) operations.get(dedupKey).add(op.key);
271
+ else operations.set(dedupKey, new Set([op.key]));
272
+ if (!isInflight) resolved.delete(dedupKey);
273
+ return (op.metadata.dedup?.skip ?? false) || !isInflight;
274
+ }), delay(0)), pipe(ops$, filter((op) => op.variant === "teardown"), filter((teardown) => {
275
+ for (const [dedupKey, subs] of operations.entries()) if (subs.delete(teardown.key)) {
276
+ if (subs.size === 0) {
277
+ operations.delete(dedupKey);
278
+ resolved.delete(dedupKey);
279
+ return true;
280
+ }
281
+ return false;
282
+ }
283
+ return true;
284
+ }))), forward, mergeMap((result) => {
285
+ if (result.operation.variant !== "request" || result.operation.artifact.kind === "mutation" || result.operation.artifact.kind === "fragment") return fromValue(result);
286
+ const dedupKey = makeDedupKey(result.operation);
287
+ resolved.add(dedupKey);
288
+ return fromArray([...operations.get(dedupKey) ?? /* @__PURE__ */ new Set()].map((key) => ({
289
+ ...result,
290
+ operation: {
291
+ ...result.operation,
292
+ key
293
+ }
294
+ })));
295
+ }));
296
+ }
297
+ });
298
+ };
299
+
300
+ //#endregion
301
+ //#region src/cache/constants.ts
302
+ /**
303
+ * Special key used to mark normalized entity references in the cache.
304
+ * When an entity is normalized, it's replaced with an object containing this key
305
+ * with the entity key as the value.
306
+ * @internal
307
+ */
308
+ const EntityLinkKey = "__ref";
309
+ /**
310
+ * Special key for storing root query fields.
311
+ * @internal
312
+ */
313
+ const RootFieldKey = "__root";
314
+ /**
315
+ * Special key used to mark fragment references in entities.
316
+ * Used for cache-agnostic fragment system.
317
+ * @internal
318
+ */
319
+ const FragmentRefKey = "__fragmentRef";
320
+
321
+ //#endregion
322
+ //#region src/cache/utils.ts
323
+ /**
324
+ * Generates a unique cache key for an entity based on its typename and key field values.
325
+ * @internal
326
+ * @param typename - The GraphQL typename of the entity.
327
+ * @param keyValues - Array of key field values used to identify the entity.
328
+ * @returns A unique entity key in the format "typename:value1:value2:...".
329
+ */
330
+ const makeEntityKey = (typename, keyValues) => {
331
+ return `${typename}:${keyValues.join(":")}`;
332
+ };
333
+ /**
334
+ * Resolves GraphQL arguments by replacing variable references with their actual values.
335
+ * @internal
336
+ * @param args - Argument definitions from the query, containing either literal values or variable references.
337
+ * @param variables - Object containing the actual variable values.
338
+ * @returns Object with all arguments resolved to their actual values.
339
+ */
340
+ const resolveArguments = (args, variables) => {
341
+ return Object.fromEntries(Object.entries(args).map(([key, value]) => [key, value.kind === "literal" ? value.value : variables[value.name]]));
342
+ };
343
+ /**
344
+ * Generates a cache key for a GraphQL field selection.
345
+ * Always uses the actual field name (not alias) with a stringified representation of the arguments.
346
+ * @internal
347
+ * @param selection - The field selection node containing field information.
348
+ * @param variables - Variable values for resolving argument references.
349
+ * @returns Field cache key string in "fieldName@argsString" format.
350
+ */
351
+ const makeFieldKey = (selection, variables) => {
352
+ const args = selection.args && Object.keys(selection.args).length > 0 ? stringify(resolveArguments(selection.args, variables)) : "{}";
353
+ return `${selection.name}@${args}`;
354
+ };
355
+ /**
356
+ * Generates a unique key for tracking memoized denormalized results for structural sharing.
357
+ * @internal
358
+ * @param kind - The operation kind ('query', 'fragment', 'fragments').
359
+ * @param name - The artifact name.
360
+ * @param id - Serialized identifier (variables, entity key, etc.).
361
+ * @returns A unique memo key.
362
+ */
363
+ const makeMemoKey = (kind, name, id) => `${kind}:${name}:${id}`;
364
+ /**
365
+ * Gets a unique key for tracking a field dependency.
366
+ * @internal
367
+ * @param storageKey Storage key (entity or root query key).
368
+ * @param fieldKey Field key.
369
+ * @returns Unique dependency key in the format "storageKey.field".
370
+ */
371
+ const makeDependencyKey = (storageKey, fieldKey) => {
372
+ return `${storageKey}.${fieldKey}`;
373
+ };
374
+ /**
375
+ * Type guard to check if a value is an entity link.
376
+ * @internal
377
+ * @param value - Value to check.
378
+ * @returns True if the value is an EntityLink.
379
+ */
380
+ const isEntityLink = (value) => {
381
+ return typeof value === "object" && value !== null && EntityLinkKey in value;
382
+ };
383
+ /**
384
+ * Type guard to check if a value is a fragment reference.
385
+ * @internal
386
+ * @param value - Value to check.
387
+ * @returns True if the value is a FragmentRef.
388
+ */
389
+ const isFragmentRef = (value) => {
390
+ return typeof value === "object" && value !== null && FragmentRefKey in value;
391
+ };
392
+ /**
393
+ * Type guard to check if a value is an array of fragment references.
394
+ * @internal
395
+ * @param value - Value to check.
396
+ * @returns True if the value is a FragmentRef array.
397
+ */
398
+ const isFragmentRefArray = (value) => {
399
+ return Array.isArray(value) && value.length > 0 && isFragmentRef(value[0]);
400
+ };
401
+ /**
402
+ * Type guard to check if a value is nullish.
403
+ * @internal
404
+ * @param value - Value to check.
405
+ * @returns True if the value is nullish.
406
+ */
407
+ const isNullish = (value) => {
408
+ return value === void 0 || value === null;
409
+ };
410
+ /**
411
+ * Deep equality check for normalized cache values.
412
+ * Handles scalars, arrays, and plain objects (entity links, value objects).
413
+ * @internal
414
+ */
415
+ const isEqual = (a, b) => {
416
+ if (a === b) return true;
417
+ if (typeof a !== typeof b || a === null || b === null) return false;
418
+ if (Array.isArray(a)) {
419
+ if (!Array.isArray(b) || a.length !== b.length) return false;
420
+ for (const [i, item] of a.entries()) if (!isEqual(item, b[i])) return false;
421
+ return true;
422
+ }
423
+ if (typeof a === "object") {
424
+ const aObj = a;
425
+ const bObj = b;
426
+ const aKeys = Object.keys(aObj);
427
+ if (aKeys.length !== Object.keys(bObj).length) return false;
428
+ for (const key of aKeys) if (!isEqual(aObj[key], bObj[key])) return false;
429
+ return true;
430
+ }
431
+ return false;
432
+ };
433
+ /**
434
+ * Recursively replaces a new value tree with the previous one wherever structurally equal,
435
+ * preserving referential identity for unchanged subtrees.
436
+ *
437
+ * Returns `prev` (same reference) when the entire subtree is structurally equal.
438
+ * @internal
439
+ */
440
+ const replaceEqualDeep = (prev, next) => {
441
+ if (prev === next) return prev;
442
+ if (typeof prev !== typeof next || prev === null || next === null || typeof prev !== "object") return next;
443
+ if (Array.isArray(prev)) {
444
+ if (!Array.isArray(next)) return next;
445
+ let allSame = prev.length === next.length;
446
+ const result = [];
447
+ for (const [i, item] of next.entries()) {
448
+ const shared = i < prev.length ? replaceEqualDeep(prev[i], item) : item;
449
+ result.push(shared);
450
+ if (shared !== prev[i]) allSame = false;
451
+ }
452
+ return allSame ? prev : result;
453
+ }
454
+ if (Array.isArray(next)) return next;
455
+ const prevObj = prev;
456
+ const nextObj = next;
457
+ const nextKeys = Object.keys(nextObj);
458
+ const prevKeys = Object.keys(prevObj);
459
+ let allSame = nextKeys.length === prevKeys.length;
460
+ const result = {};
461
+ for (const key of nextKeys) if (key in prevObj) {
462
+ result[key] = replaceEqualDeep(prevObj[key], nextObj[key]);
463
+ if (result[key] !== prevObj[key]) allSame = false;
464
+ } else {
465
+ result[key] = nextObj[key];
466
+ allSame = false;
467
+ }
468
+ return allSame ? prev : result;
469
+ };
470
+ /**
471
+ * Deeply merges two values. Objects are recursively merged, arrays are element-wise merged,
472
+ * entity links and primitives use last-write-wins.
473
+ * @internal
474
+ */
475
+ const mergeFieldValue = (existing, incoming) => {
476
+ if (isNullish(existing) || isNullish(incoming)) return incoming;
477
+ if (typeof existing !== "object" || typeof incoming !== "object") return incoming;
478
+ if (isEntityLink(existing) || isEntityLink(incoming)) return incoming;
479
+ if (Array.isArray(existing) && Array.isArray(incoming)) return incoming.map((item, i) => i < existing.length ? mergeFieldValue(existing[i], item) : item);
480
+ if (Array.isArray(existing) || Array.isArray(incoming)) return incoming;
481
+ mergeFields(existing, incoming);
482
+ return existing;
483
+ };
484
+ /**
485
+ * Deeply merges source fields into target. Objects are recursively merged,
486
+ * arrays are element-wise merged, entity links and primitives use last-write-wins.
487
+ * @internal
488
+ */
489
+ const mergeFields = (target, source) => {
490
+ if (isNullish(source) || typeof source !== "object" || Array.isArray(source)) return;
491
+ for (const key of Object.keys(source)) target[key] = mergeFieldValue(target[key], source[key]);
492
+ };
493
+ /**
494
+ * Creates a FieldKey from a raw field name and optional arguments.
495
+ * @internal
496
+ * @param field - The field name.
497
+ * @param args - Optional argument values.
498
+ * @returns A FieldKey in "field@args" format.
499
+ */
500
+ const makeFieldKeyFromArgs = (field, args) => {
501
+ return `${field}@${args && Object.keys(args).length > 0 ? stringify(args) : "{}"}`;
502
+ };
503
+ /**
504
+ * Converts an EntityId to an EntityKey.
505
+ * @internal
506
+ * @param typename - The GraphQL typename of the entity.
507
+ * @param id - The entity identifier (string, number, or composite key record).
508
+ * @param keyFields - Optional ordered list of key field names for composite keys.
509
+ * @returns An EntityKey.
510
+ */
511
+ const resolveEntityKey = (typename, id, keyFields) => {
512
+ if (typeof id === "string" || typeof id === "number") return makeEntityKey(typename, [id]);
513
+ return makeEntityKey(typename, keyFields ? keyFields.map((f) => id[f]) : Object.values(id));
514
+ };
515
+
516
+ //#endregion
517
+ //#region src/cache/normalize.ts
518
+ const SKIP = Symbol();
519
+ const normalize = (schemaMeta, selections, storage, data, variables, accessor) => {
520
+ const normalizeField = (storageKey, selections, value) => {
521
+ if (isNullish(value)) return value;
522
+ if (Array.isArray(value)) return value.map((item) => normalizeField(storageKey, selections, item));
523
+ const data = value;
524
+ const typename = data.__typename;
525
+ const entityMeta = schemaMeta.entities[typename];
526
+ if (entityMeta) {
527
+ const keys = entityMeta.keyFields.map((field) => data[field]);
528
+ if (!keys.every((k) => k !== void 0 && k !== null)) return SKIP;
529
+ storageKey = makeEntityKey(typename, keys);
530
+ }
531
+ const fields = {};
532
+ for (const selection of selections) if (selection.kind === "Field") {
533
+ const fieldKey = makeFieldKey(selection, variables);
534
+ const fieldValue = data[selection.alias ?? selection.name];
535
+ const oldValue = storageKey === null ? void 0 : storage[storageKey]?.[fieldKey];
536
+ if (storageKey !== null && (!selection.selections || isNullish(oldValue) || isNullish(fieldValue))) accessor?.(storageKey, fieldKey, oldValue, fieldValue);
537
+ const normalized = selection.selections ? normalizeField(null, selection.selections, fieldValue) : fieldValue;
538
+ if (normalized === SKIP) continue;
539
+ fields[fieldKey] = normalized;
540
+ if (storageKey !== null && selection.selections && !isNullish(oldValue) && !isNullish(fieldValue) && !isEntityLink(fields[fieldKey]) && !isEqual(oldValue, fields[fieldKey])) accessor?.(storageKey, fieldKey, oldValue, fields[fieldKey]);
541
+ } else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === typename) {
542
+ const inner = normalizeField(storageKey, selection.selections, value);
543
+ if (inner !== SKIP && !isEntityLink(inner)) mergeFields(fields, inner);
544
+ }
545
+ if (entityMeta && storageKey !== null) {
546
+ const existing = storage[storageKey];
547
+ if (existing) mergeFields(existing, fields);
548
+ else storage[storageKey] = fields;
549
+ return { [EntityLinkKey]: storageKey };
550
+ }
551
+ return fields;
552
+ };
553
+ const fields = normalizeField(RootFieldKey, selections, data);
554
+ storage[RootFieldKey] = {
555
+ ...storage[RootFieldKey],
556
+ ...fields
557
+ };
558
+ };
559
+
560
+ //#endregion
561
+ //#region src/cache/denormalize.ts
562
+ const typenameFieldKey = makeFieldKey({
563
+ kind: "Field",
564
+ name: "__typename",
565
+ type: "String"
566
+ }, {});
567
+ const denormalize = (selections, storage, value, variables, accessor) => {
568
+ let partial = false;
569
+ const denormalizeField = (storageKey, selections, value) => {
570
+ if (isNullish(value)) return value;
571
+ if (Array.isArray(value)) return value.map((item) => denormalizeField(storageKey, selections, item));
572
+ const data = value;
573
+ if (isEntityLink(data)) {
574
+ const entityKey = data[EntityLinkKey];
575
+ const entity = storage[entityKey];
576
+ if (!entity) {
577
+ accessor?.(entityKey, typenameFieldKey);
578
+ partial = true;
579
+ return null;
580
+ }
581
+ return denormalizeField(entityKey, selections, entity);
582
+ }
583
+ const fields = {};
584
+ for (const selection of selections) if (selection.kind === "Field") {
585
+ const fieldKey = makeFieldKey(selection, variables);
586
+ const fieldValue = data[fieldKey];
587
+ if (storageKey !== null) accessor?.(storageKey, fieldKey);
588
+ if (fieldValue === void 0) {
589
+ partial = true;
590
+ continue;
591
+ }
592
+ const name = selection.alias ?? selection.name;
593
+ const value = selection.selections ? denormalizeField(null, selection.selections, fieldValue) : fieldValue;
594
+ if (name in fields) mergeFields(fields, { [name]: value });
595
+ else fields[name] = value;
596
+ } else if (selection.kind === "FragmentSpread") if (storageKey !== null && storageKey !== RootFieldKey) fields[FragmentRefKey] = storageKey;
597
+ else mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
598
+ else if (selection.kind === "InlineFragment" && selection.on === data[typenameFieldKey]) mergeFields(fields, denormalizeField(storageKey, selection.selections, value));
599
+ return fields;
600
+ };
601
+ return {
602
+ data: denormalizeField(RootFieldKey, selections, value),
603
+ partial
604
+ };
605
+ };
606
+
607
+ //#endregion
608
+ //#region src/cache/cache.ts
609
+ /**
610
+ * A normalized cache that stores and manages GraphQL query results and entities.
611
+ * Supports entity normalization, cache invalidation, and reactive updates through subscriptions.
612
+ */
613
+ var Cache = class {
614
+ #schemaMeta;
615
+ #storage = { [RootFieldKey]: {} };
616
+ #subscriptions = /* @__PURE__ */ new Map();
617
+ #memo = /* @__PURE__ */ new Map();
618
+ constructor(schemaMetadata) {
619
+ this.#schemaMeta = schemaMetadata;
620
+ }
621
+ /**
622
+ * Writes a query result to the cache, normalizing entities.
623
+ * @param artifact - GraphQL document artifact.
624
+ * @param variables - Query variables.
625
+ * @param data - Query result data.
626
+ */
627
+ writeQuery(artifact, variables, data) {
628
+ const dependencies = /* @__PURE__ */ new Set();
629
+ const subscriptions = /* @__PURE__ */ new Set();
630
+ normalize(this.#schemaMeta, artifact.selections, this.#storage, data, variables, (storageKey, fieldKey, oldValue, newValue) => {
631
+ if (oldValue !== newValue) {
632
+ const dependencyKey = makeDependencyKey(storageKey, fieldKey);
633
+ dependencies.add(dependencyKey);
634
+ }
635
+ });
636
+ for (const dependency of dependencies) {
637
+ const ss = this.#subscriptions.get(dependency);
638
+ if (ss) for (const s of ss) subscriptions.add(s);
639
+ }
640
+ for (const subscription of subscriptions) subscription.listener();
641
+ }
642
+ /**
643
+ * Reads a query result from the cache, denormalizing entities if available.
644
+ * Uses structural sharing to preserve referential identity for unchanged subtrees.
645
+ * @param artifact - GraphQL document artifact.
646
+ * @param variables - Query variables.
647
+ * @returns Denormalized query result or null if not found.
648
+ */
649
+ readQuery(artifact, variables) {
650
+ const { data, partial } = denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables);
651
+ if (partial) return null;
652
+ const key = makeMemoKey("query", artifact.name, stringify(variables));
653
+ const prev = this.#memo.get(key);
654
+ const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
655
+ this.#memo.set(key, result);
656
+ return result;
657
+ }
658
+ /**
659
+ * Subscribes to cache invalidations for a specific query.
660
+ * @param artifact - GraphQL document artifact.
661
+ * @param variables - Query variables.
662
+ * @param listener - Callback function to invoke on cache invalidation.
663
+ * @returns Unsubscribe function.
664
+ */
665
+ subscribeQuery(artifact, variables, listener) {
666
+ const dependencies = /* @__PURE__ */ new Set();
667
+ denormalize(artifact.selections, this.#storage, this.#storage[RootFieldKey], variables, (storageKey, fieldKey) => {
668
+ const dependencyKey = makeDependencyKey(storageKey, fieldKey);
669
+ dependencies.add(dependencyKey);
670
+ });
671
+ return this.#subscribe(dependencies, listener);
672
+ }
673
+ /**
674
+ * Reads a fragment from the cache for a specific entity.
675
+ * Uses structural sharing to preserve referential identity for unchanged subtrees.
676
+ * @param artifact - GraphQL fragment artifact.
677
+ * @param fragmentRef - Fragment reference containing entity key.
678
+ * @returns Denormalized fragment data or null if not found or invalid.
679
+ */
680
+ readFragment(artifact, fragmentRef) {
681
+ const entityKey = fragmentRef[FragmentRefKey];
682
+ if (!this.#storage[entityKey]) return null;
683
+ const { data, partial } = denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {});
684
+ if (partial) return null;
685
+ const key = makeMemoKey("fragment", artifact.name, entityKey);
686
+ const prev = this.#memo.get(key);
687
+ const result = prev === void 0 ? data : replaceEqualDeep(prev, data);
688
+ this.#memo.set(key, result);
689
+ return result;
690
+ }
691
+ subscribeFragment(artifact, fragmentRef, listener) {
692
+ const entityKey = fragmentRef[FragmentRefKey];
693
+ const dependencies = /* @__PURE__ */ new Set();
694
+ denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {}, (storageKey, fieldKey) => {
695
+ const dependencyKey = makeDependencyKey(storageKey, fieldKey);
696
+ dependencies.add(dependencyKey);
697
+ });
698
+ return this.#subscribe(dependencies, listener);
699
+ }
700
+ readFragments(artifact, fragmentRefs) {
701
+ const results = [];
702
+ for (const ref of fragmentRefs) {
703
+ const data = this.readFragment(artifact, ref);
704
+ if (data === null) return null;
705
+ results.push(data);
706
+ }
707
+ const entityKeys = fragmentRefs.map((ref) => ref[FragmentRefKey]);
708
+ const key = makeMemoKey("fragments", artifact.name, entityKeys.join(","));
709
+ const prev = this.#memo.get(key);
710
+ const result = prev === void 0 ? results : replaceEqualDeep(prev, results);
711
+ this.#memo.set(key, result);
712
+ return result;
713
+ }
714
+ subscribeFragments(artifact, fragmentRefs, listener) {
715
+ const dependencies = /* @__PURE__ */ new Set();
716
+ for (const ref of fragmentRefs) {
717
+ const entityKey = ref[FragmentRefKey];
718
+ denormalize(artifact.selections, this.#storage, { [EntityLinkKey]: entityKey }, {}, (storageKey, fieldKey) => {
719
+ dependencies.add(makeDependencyKey(storageKey, fieldKey));
720
+ });
721
+ }
722
+ return this.#subscribe(dependencies, listener);
723
+ }
724
+ /**
725
+ * Invalidates one or more cache entries and notifies affected subscribers.
726
+ * @param targets - Cache entries to invalidate.
727
+ */
728
+ invalidate(...targets) {
729
+ const subscriptions = /* @__PURE__ */ new Set();
730
+ for (const target of targets) if (target.__typename === "Query") if ("field" in target) {
731
+ const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
732
+ delete this.#storage[RootFieldKey]?.[fieldKey];
733
+ this.#collectSubscriptions(RootFieldKey, fieldKey, subscriptions);
734
+ } else {
735
+ this.#storage[RootFieldKey] = {};
736
+ this.#collectSubscriptions(RootFieldKey, void 0, subscriptions);
737
+ }
738
+ else if ("id" in target) {
739
+ const entityKey = resolveEntityKey(target.__typename, target.id, this.#schemaMeta.entities[target.__typename]?.keyFields);
740
+ if ("field" in target) {
741
+ const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
742
+ delete this.#storage[entityKey]?.[fieldKey];
743
+ this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
744
+ } else {
745
+ delete this.#storage[entityKey];
746
+ this.#collectSubscriptions(entityKey, void 0, subscriptions);
747
+ }
748
+ } else {
749
+ const prefix = `${target.__typename}:`;
750
+ for (const key of Object.keys(this.#storage)) if (key.startsWith(prefix)) {
751
+ const entityKey = key;
752
+ if ("field" in target) {
753
+ const fieldKey = makeFieldKeyFromArgs(target.field, target.args);
754
+ delete this.#storage[entityKey]?.[fieldKey];
755
+ this.#collectSubscriptions(entityKey, fieldKey, subscriptions);
756
+ } else {
757
+ delete this.#storage[entityKey];
758
+ this.#collectSubscriptions(entityKey, void 0, subscriptions);
759
+ }
760
+ }
761
+ }
762
+ for (const subscription of subscriptions) subscription.listener();
763
+ }
764
+ #collectSubscriptions(storageKey, fieldKey, out) {
765
+ if (fieldKey === void 0) {
766
+ const prefix = `${storageKey}.`;
767
+ for (const [depKey, ss] of this.#subscriptions) if (depKey.startsWith(prefix)) for (const s of ss) out.add(s);
768
+ } else {
769
+ const depKey = makeDependencyKey(storageKey, fieldKey);
770
+ const ss = this.#subscriptions.get(depKey);
771
+ if (ss) for (const s of ss) out.add(s);
772
+ }
773
+ }
774
+ #subscribe(dependencies, listener) {
775
+ const subscription = { listener };
776
+ for (const dependency of dependencies) {
777
+ const subscriptions = this.#subscriptions.get(dependency) ?? /* @__PURE__ */ new Set();
778
+ subscriptions.add(subscription);
779
+ this.#subscriptions.set(dependency, subscriptions);
780
+ }
781
+ return () => {
782
+ for (const dependency of dependencies) {
783
+ const subscriptions = this.#subscriptions.get(dependency);
784
+ subscriptions?.delete(subscription);
785
+ if (subscriptions?.size === 0) this.#subscriptions.delete(dependency);
786
+ }
787
+ };
788
+ }
789
+ /**
790
+ * Extracts a serializable snapshot of the cache storage and structural sharing state.
791
+ */
792
+ extract() {
793
+ return {
794
+ storage: structuredClone(this.#storage),
795
+ memo: Object.fromEntries(this.#memo)
796
+ };
797
+ }
798
+ /**
799
+ * Hydrates the cache with a previously extracted snapshot.
800
+ */
801
+ hydrate(snapshot) {
802
+ const { storage, memo } = snapshot;
803
+ for (const [key, fields] of Object.entries(storage)) this.#storage[key] = {
804
+ ...this.#storage[key],
805
+ ...fields
806
+ };
807
+ for (const [key, value] of Object.entries(memo)) this.#memo.set(key, value);
808
+ }
809
+ /**
810
+ * Clears all cache data.
811
+ */
812
+ clear() {
813
+ this.#storage = { [RootFieldKey]: {} };
814
+ this.#subscriptions.clear();
815
+ this.#memo.clear();
816
+ }
817
+ };
818
+
819
+ //#endregion
820
+ //#region src/stream/sources/empty.ts
821
+ /**
822
+ * Creates a source that completes immediately without emitting any values.
823
+ * @returns An empty source.
824
+ */
825
+ const empty = () => {
826
+ return (sink) => {
827
+ sink.complete();
828
+ return { unsubscribe() {} };
829
+ };
830
+ };
831
+
832
+ //#endregion
833
+ //#region src/exchanges/cache.ts
834
+ const cacheExchange = (options = {}) => {
835
+ const { fetchPolicy = "cache-first" } = options;
836
+ return ({ forward, client }) => {
837
+ const cache = new Cache(client.schema);
838
+ return {
839
+ name: "cache",
840
+ extension: {
841
+ extract: () => cache.extract(),
842
+ hydrate: (snapshot) => cache.hydrate(snapshot),
843
+ invalidate: (...targets) => cache.invalidate(...targets),
844
+ clear: () => cache.clear()
845
+ },
846
+ io: (ops$) => {
847
+ const fragment$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), mergeMap((op) => {
848
+ const fragmentRef = op.metadata?.fragmentRef;
849
+ if (!fragmentRef) return fromValue({
850
+ operation: op,
851
+ errors: [new ExchangeError("Fragment operation missing fragmentRef in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "cache" })]
852
+ });
853
+ if (isFragmentRefArray(fragmentRef)) {
854
+ const trigger = makeSubject();
855
+ const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
856
+ return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragments(op.artifact, fragmentRef), () => cache.subscribeFragments(op.artifact, fragmentRef, async () => {
857
+ await Promise.resolve();
858
+ trigger.next();
859
+ }))), takeUntil(teardown$), map((data) => ({
860
+ operation: op,
861
+ data,
862
+ errors: []
863
+ })));
864
+ }
865
+ if (!isFragmentRef(fragmentRef)) return fromValue({
866
+ operation: op,
867
+ data: fragmentRef,
868
+ errors: []
869
+ });
870
+ const trigger = makeSubject();
871
+ const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
872
+ return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readFragment(op.artifact, fragmentRef), () => cache.subscribeFragment(op.artifact, fragmentRef, async () => {
873
+ await Promise.resolve();
874
+ trigger.next();
875
+ }))), takeUntil(teardown$), map((data) => ({
876
+ operation: op,
877
+ data,
878
+ errors: []
879
+ })));
880
+ }));
881
+ const nonCache$ = pipe(ops$, filter((op) => op.variant === "request" && (op.artifact.kind === "mutation" || op.artifact.kind === "subscription" || op.artifact.kind === "query" && fetchPolicy === "network-only")));
882
+ const query$ = pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "query" && fetchPolicy !== "network-only"), share());
883
+ const refetch$ = makeSubject();
884
+ return merge(fragment$, pipe(query$, mergeMap((op) => {
885
+ const trigger = makeSubject();
886
+ let hasData = false;
887
+ const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key), tap(() => trigger.complete()));
888
+ return pipe(merge(fromValue(void 0), trigger.source), switchMap(() => fromSubscription(() => cache.readQuery(op.artifact, op.variables), () => cache.subscribeQuery(op.artifact, op.variables, async () => {
889
+ await Promise.resolve();
890
+ trigger.next();
891
+ }))), takeUntil(teardown$), mergeMap((data) => {
892
+ if (data !== null) {
893
+ hasData = true;
894
+ return fromValue({
895
+ operation: op,
896
+ data,
897
+ errors: []
898
+ });
899
+ }
900
+ if (hasData) {
901
+ refetch$.next(op);
902
+ return empty();
903
+ }
904
+ if (fetchPolicy === "cache-only") return fromValue({
905
+ operation: op,
906
+ data: null,
907
+ errors: []
908
+ });
909
+ return empty();
910
+ }));
911
+ }), filter(() => fetchPolicy === "cache-only" || fetchPolicy === "cache-and-network" || fetchPolicy === "cache-first")), pipe(merge(nonCache$, pipe(query$, filter((op) => {
912
+ const cached = cache.readQuery(op.artifact, op.variables);
913
+ return fetchPolicy === "cache-and-network" || cached === null;
914
+ })), pipe(ops$, filter((op) => op.variant === "teardown")), refetch$.source), forward, tap((result) => {
915
+ if (result.operation.variant === "request" && result.data) cache.writeQuery(result.operation.artifact, result.operation.variables, result.data);
916
+ }), filter((result) => result.operation.variant !== "request" || result.operation.artifact.kind !== "query" || fetchPolicy === "network-only" || !!(result.errors && result.errors.length > 0))));
917
+ }
918
+ };
919
+ };
920
+ };
921
+
922
+ //#endregion
923
+ //#region src/exchanges/retry.ts
924
+ const defaultShouldRetry = (error) => isExchangeError(error, "http") && error.extensions?.statusCode !== void 0 && error.extensions.statusCode >= 500;
925
+ const retryExchange = (options = {}) => {
926
+ const { maxAttempts = 3, backoff = (attempt) => Math.min(1e3 * 2 ** attempt, 3e4), shouldRetry = defaultShouldRetry } = options;
927
+ return ({ forward }) => ({
928
+ name: "retry",
929
+ io: (ops$) => {
930
+ const { source: retries$, next } = makeSubject();
931
+ const tornDown = /* @__PURE__ */ new Set();
932
+ const teardown$ = pipe(ops$, filter((op) => op.variant === "teardown"), tap((op) => {
933
+ tornDown.add(op.key);
934
+ }));
935
+ return pipe(merge(pipe(ops$, filter((op) => op.variant === "request")), pipe(retries$, filter((op) => !tornDown.has(op.key)), mergeMap((op) => {
936
+ const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key));
937
+ return pipe(fromValue(op), delay(op.metadata.retry.delay), takeUntil(teardown$));
938
+ })), teardown$), forward, filter((result) => {
939
+ if (!result.errors || result.errors.length === 0) return true;
940
+ if (result.operation.variant === "request" && result.operation.artifact.kind === "mutation") return true;
941
+ const attempt = result.operation.metadata.retry?.attempt ?? 0;
942
+ if (attempt >= maxAttempts - 1) return true;
943
+ if (!result.errors.some((error) => shouldRetry(error))) return true;
944
+ next({
945
+ ...result.operation,
946
+ metadata: {
947
+ ...result.operation.metadata,
948
+ dedup: { skip: true },
949
+ retry: {
950
+ attempt: attempt + 1,
951
+ delay: backoff(attempt)
952
+ }
953
+ }
954
+ });
955
+ return false;
956
+ }));
957
+ }
958
+ });
959
+ };
960
+
961
+ //#endregion
962
+ //#region src/exchanges/fragment.ts
963
+ const fragmentExchange = () => {
964
+ return ({ forward }) => ({
965
+ name: "fragment",
966
+ io: (ops$) => {
967
+ return merge(pipe(ops$, filter((op) => op.variant === "request" && op.artifact.kind === "fragment"), map((op) => {
968
+ const fragmentRef = op.metadata.fragmentRef;
969
+ if (!fragmentRef) return {
970
+ operation: op,
971
+ errors: [new ExchangeError("Fragment operation missing fragmentRef in metadata. This usually happens when the wrong fragment reference was passed.", { exchangeName: "fragment" })]
972
+ };
973
+ return {
974
+ operation: op,
975
+ data: fragmentRef
976
+ };
977
+ })), pipe(ops$, filter((op) => op.variant === "teardown" || op.artifact.kind !== "fragment"), forward));
978
+ }
979
+ });
980
+ };
981
+
982
+ //#endregion
983
+ //#region src/required.ts
984
+ const CASCADE_NULL = Symbol("CASCADE_NULL");
985
+ var RequiredFieldError = class extends Error {
986
+ fieldPath;
987
+ fieldName;
988
+ constructor(fieldPath, fieldName) {
989
+ super(`Required field '${fieldPath.join(".")}.${fieldName}' is null`);
990
+ this.name = "RequiredFieldError";
991
+ this.fieldPath = fieldPath;
992
+ this.fieldName = fieldName;
993
+ }
994
+ };
995
+ const getRequiredAction = (directives) => {
996
+ if (!directives) return null;
997
+ const requiredDirective = directives.find((d) => d.name === "required");
998
+ if (!requiredDirective) return null;
999
+ if (requiredDirective.args?.action === "CASCADE") return "CASCADE";
1000
+ return "THROW";
1001
+ };
1002
+ const validateRequiredInner = (selections, data, fieldPath, validatedMap) => {
1003
+ if (data === null || data === void 0) return data;
1004
+ if (typeof data !== "object") return data;
1005
+ if (Array.isArray(data)) return data.map((item, index) => {
1006
+ const result = validateRequiredInner(selections, item, [...fieldPath, `[${index}]`], validatedMap);
1007
+ return result === CASCADE_NULL ? null : result;
1008
+ });
1009
+ const obj = data;
1010
+ validatedMap ??= /* @__PURE__ */ new WeakMap();
1011
+ let validated = validatedMap.get(obj);
1012
+ if (!validated) {
1013
+ validated = /* @__PURE__ */ new Set();
1014
+ validatedMap.set(obj, validated);
1015
+ }
1016
+ for (const selection of selections) if (selection.kind === "Field") {
1017
+ const fieldName = selection.alias ?? selection.name;
1018
+ if (!(fieldName in obj)) continue;
1019
+ const fieldValue = obj[fieldName];
1020
+ const action = getRequiredAction(selection.directives);
1021
+ if (selection.selections) {
1022
+ if (action && fieldValue === null) {
1023
+ if (action === "THROW") throw new RequiredFieldError(fieldPath, fieldName);
1024
+ else if (action === "CASCADE") return CASCADE_NULL;
1025
+ }
1026
+ if (fieldValue !== null && fieldValue !== void 0) {
1027
+ if (validateRequiredInner(selection.selections, fieldValue, [...fieldPath, fieldName], validatedMap) === CASCADE_NULL) if (selection.nullable && !getRequiredAction(selection.directives)) obj[fieldName] = null;
1028
+ else return CASCADE_NULL;
1029
+ }
1030
+ } else {
1031
+ if (validated.has(fieldName)) continue;
1032
+ validated.add(fieldName);
1033
+ if (action && fieldValue === null) {
1034
+ if (action === "THROW") throw new RequiredFieldError(fieldPath, fieldName);
1035
+ else if (action === "CASCADE") return CASCADE_NULL;
1036
+ }
1037
+ }
1038
+ } else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment") {
1039
+ if (validateRequiredInner(selection.selections, data, fieldPath, validatedMap) === CASCADE_NULL) return CASCADE_NULL;
1040
+ }
1041
+ return data;
1042
+ };
1043
+ const validateRequired = (selections, data, fieldPath = []) => {
1044
+ const result = validateRequiredInner(selections, data, fieldPath);
1045
+ return result === CASCADE_NULL ? null : result;
1046
+ };
1047
+
1048
+ //#endregion
1049
+ //#region src/exchanges/required.ts
1050
+ const requiredExchange = () => {
1051
+ return ({ forward }) => ({
1052
+ name: "required",
1053
+ io: (ops$) => {
1054
+ return pipe(ops$, forward, map((result) => {
1055
+ if (result.operation.variant !== "request" || !result.data) return result;
1056
+ try {
1057
+ return {
1058
+ ...result,
1059
+ data: validateRequired(result.operation.artifact.selections, result.data)
1060
+ };
1061
+ } catch (error) {
1062
+ return {
1063
+ ...result,
1064
+ errors: [new ExchangeError(error instanceof Error ? error.message : String(error), { exchangeName: "required" })]
1065
+ };
1066
+ }
1067
+ }));
1068
+ }
1069
+ });
1070
+ };
1071
+
1072
+ //#endregion
1073
+ //#region src/exchanges/subscription.ts
1074
+ const shouldHandle = (op) => op.variant === "request" && (op.artifact.kind === "subscription" || op.metadata.subscription?.transport === true);
1075
+ /**
1076
+ * Creates an exchange for handling GraphQL subscriptions using a subscription client.
1077
+ *
1078
+ * This exchange accepts subscription clients from graphql-ws, graphql-sse, or any client
1079
+ * implementing the Observer pattern.
1080
+ * @internal
1081
+ * @param options - Configuration options for the subscription exchange.
1082
+ * @returns An exchange that handles subscription operations.
1083
+ * @example
1084
+ * // With graphql-ws
1085
+ * import { createClient } from 'graphql-ws';
1086
+ *
1087
+ * const wsClient = createClient({
1088
+ * url: 'ws://localhost:4000/graphql',
1089
+ * });
1090
+ *
1091
+ * subscriptionExchange({ client: wsClient })
1092
+ * @example
1093
+ * // With graphql-sse
1094
+ * import { createClient } from 'graphql-sse';
1095
+ *
1096
+ * const sseClient = createClient({
1097
+ * url: 'http://localhost:4000/graphql/stream',
1098
+ * });
1099
+ *
1100
+ * subscriptionExchange({ client: sseClient })
1101
+ */
1102
+ const subscriptionExchange = (options) => {
1103
+ const { client } = options;
1104
+ return ({ forward }) => ({
1105
+ name: "subscription",
1106
+ io: (ops$) => {
1107
+ return merge(pipe(ops$, filter(shouldHandle), mergeMap((op) => {
1108
+ const teardown$ = pipe(ops$, filter((operation) => operation.variant === "teardown" && operation.key === op.key));
1109
+ return pipe(make((observer) => {
1110
+ let unsubscribe;
1111
+ let completed = false;
1112
+ Promise.resolve().then(() => {
1113
+ if (completed) return;
1114
+ unsubscribe = client.subscribe({
1115
+ operationName: op.artifact.name,
1116
+ query: op.artifact.body,
1117
+ variables: op.variables
1118
+ }, {
1119
+ next: (result) => {
1120
+ const response = result;
1121
+ observer.next({
1122
+ operation: op,
1123
+ data: response.data,
1124
+ errors: response.errors?.map((err) => new GraphQLError(err.message, {
1125
+ path: err.path,
1126
+ locations: err.locations,
1127
+ extensions: err.extensions
1128
+ })),
1129
+ extensions: response.extensions
1130
+ });
1131
+ },
1132
+ error: (error) => {
1133
+ observer.next({
1134
+ operation: op,
1135
+ errors: [new ExchangeError(error instanceof Error ? error.message : String(error), {
1136
+ exchangeName: "subscription",
1137
+ cause: error
1138
+ })]
1139
+ });
1140
+ observer.complete();
1141
+ },
1142
+ complete: observer.complete
1143
+ });
1144
+ });
1145
+ return () => {
1146
+ completed = true;
1147
+ unsubscribe?.();
1148
+ };
1149
+ }), takeUntil(teardown$));
1150
+ })), pipe(ops$, filter((op) => !shouldHandle(op)), forward));
1151
+ }
1152
+ });
1153
+ };
1154
+
1155
+ //#endregion
1156
+ //#region src/compose.ts
1157
+ /** @internal */
1158
+ const composeExchanges = (options, input) => {
1159
+ const { exchanges } = options;
1160
+ const { client } = input;
1161
+ const extensions = /* @__PURE__ */ new Map();
1162
+ return {
1163
+ io: exchanges.reduceRight((forward, exchange) => {
1164
+ const result = exchange({
1165
+ forward,
1166
+ client
1167
+ });
1168
+ if ("extension" in result) extensions.set(result.name, result.extension);
1169
+ return (ops$) => {
1170
+ return pipe(ops$, share(), result.io, share());
1171
+ };
1172
+ }, input.forward),
1173
+ extensions
1174
+ };
1175
+ };
1176
+
1177
+ //#endregion
1178
+ //#region src/scalars.ts
1179
+ const parse = (selections, scalars, value) => {
1180
+ const parseValue = (selection, value, parsedMap) => {
1181
+ if (isNullish$1(value)) return value;
1182
+ if (selection.array && Array.isArray(value)) return value.map((item) => parseValue({
1183
+ ...selection,
1184
+ array: false
1185
+ }, item, parsedMap));
1186
+ if (selection.selections) return parseField(selection.selections, value, parsedMap);
1187
+ const transformer = scalars[selection.type];
1188
+ if (transformer) return transformer.parse(value);
1189
+ return value;
1190
+ };
1191
+ const parseField = (selections, value, parsedMap) => {
1192
+ if (isNullish$1(value)) return value;
1193
+ const data = value;
1194
+ parsedMap ??= /* @__PURE__ */ new WeakMap();
1195
+ let parsed = parsedMap.get(data);
1196
+ if (!parsed) {
1197
+ parsed = /* @__PURE__ */ new Set();
1198
+ parsedMap.set(data, parsed);
1199
+ }
1200
+ for (const selection of selections) if (selection.kind === "Field") {
1201
+ const fieldName = selection.alias ?? selection.name;
1202
+ if (!(fieldName in data)) continue;
1203
+ if (selection.selections) data[fieldName] = parseValue(selection, data[fieldName], parsedMap);
1204
+ else {
1205
+ if (parsed.has(fieldName)) continue;
1206
+ parsed.add(fieldName);
1207
+ data[fieldName] = parseValue(selection, data[fieldName], parsedMap);
1208
+ }
1209
+ } else if (selection.kind === "FragmentSpread" || selection.kind === "InlineFragment" && selection.on === data.__typename) parseField(selection.selections, value, parsedMap);
1210
+ return data;
1211
+ };
1212
+ return parseField(selections, value);
1213
+ };
1214
+ const serialize = (schemaMeta, variableDefs, scalars, variables) => {
1215
+ const serializeValue = (variableDef, value) => {
1216
+ if (isNullish$1(value)) return value;
1217
+ if (variableDef.array && Array.isArray(value)) return value.map((item) => serializeValue({
1218
+ ...variableDef,
1219
+ array: false
1220
+ }, item));
1221
+ const input = schemaMeta.inputs[variableDef.type];
1222
+ if (input) return serializeField(input.fields, value);
1223
+ const transformer = scalars[variableDef.type];
1224
+ if (transformer) return transformer.serialize(value);
1225
+ return value;
1226
+ };
1227
+ const serializeField = (variableDefs, value) => {
1228
+ if (isNullish$1(value)) return value;
1229
+ const data = value;
1230
+ const fields = {};
1231
+ for (const variableDef of variableDefs) {
1232
+ const variableValue = data[variableDef.name];
1233
+ fields[variableDef.name] = serializeValue(variableDef, variableValue);
1234
+ }
1235
+ return fields;
1236
+ };
1237
+ return serializeField(variableDefs, variables);
1238
+ };
1239
+
1240
+ //#endregion
1241
+ //#region src/exchanges/scalar.ts
1242
+ const scalarExchange = () => {
1243
+ return ({ forward, client }) => ({
1244
+ name: "scalar",
1245
+ io: (ops$) => {
1246
+ return pipe(ops$, map((op) => {
1247
+ if (op.variant !== "request" || !op.artifact.variableDefs || !client.scalars) return op;
1248
+ return {
1249
+ ...op,
1250
+ variables: serialize(client.schema, op.artifact.variableDefs, client.scalars, op.variables)
1251
+ };
1252
+ }), forward, map((result) => {
1253
+ if (result.operation.variant !== "request" || !result.data || !client.scalars) return result;
1254
+ return {
1255
+ ...result,
1256
+ data: parse(result.operation.artifact.selections, client.scalars, result.data)
1257
+ };
1258
+ }));
1259
+ }
1260
+ });
1261
+ };
1262
+
1263
+ //#endregion
1264
+ //#region src/exchanges/terminal.ts
1265
+ const terminalExchange = () => {
1266
+ return () => ({
1267
+ name: "terminal",
1268
+ io: (ops$) => {
1269
+ return pipe(ops$, filter((op) => op.variant !== "teardown"), mergeMap((op) => fromValue({
1270
+ operation: op,
1271
+ errors: [new ExchangeError("No terminal exchange found in exchange chain. Did you forget to add httpExchange to your exchanges array?", { exchangeName: "terminal" })]
1272
+ })));
1273
+ }
1274
+ });
1275
+ };
1276
+
1277
+ //#endregion
1278
+ //#region src/stream/sources/never.ts
1279
+ /**
1280
+ * Creates a source that never emits any values.
1281
+ * @returns A never source.
1282
+ */
1283
+ const never = () => {
1284
+ return () => {
1285
+ return { unsubscribe() {} };
1286
+ };
1287
+ };
1288
+
1289
+ //#endregion
1290
+ //#region src/client.ts
1291
+ var Client = class {
1292
+ #schema;
1293
+ #scalars;
1294
+ #extensions;
1295
+ operations$;
1296
+ results$;
1297
+ constructor(config) {
1298
+ this.#schema = config.schema;
1299
+ this.#scalars = config.scalars;
1300
+ const { io, extensions } = composeExchanges({ exchanges: [
1301
+ requiredExchange(),
1302
+ scalarExchange(),
1303
+ ...config.exchanges,
1304
+ fragmentExchange(),
1305
+ terminalExchange()
1306
+ ] }, {
1307
+ forward: never,
1308
+ client: this
1309
+ });
1310
+ this.#extensions = extensions;
1311
+ this.operations$ = makeSubject();
1312
+ this.results$ = io(this.operations$.source);
1313
+ }
1314
+ get schema() {
1315
+ return this.#schema;
1316
+ }
1317
+ get scalars() {
1318
+ return this.#scalars;
1319
+ }
1320
+ createOperationKey() {
1321
+ return Math.random().toString(36).slice(2) + Date.now().toString(36);
1322
+ }
1323
+ createOperation(artifact, variables, metadata) {
1324
+ return {
1325
+ variant: "request",
1326
+ key: this.createOperationKey(),
1327
+ metadata: { ...metadata },
1328
+ artifact,
1329
+ variables: variables ?? {}
1330
+ };
1331
+ }
1332
+ executeOperation(operation) {
1333
+ return pipe(this.results$, initialize(() => this.operations$.next(operation)), filter((result) => result.operation.key === operation.key), finalize(() => this.operations$.next({
1334
+ variant: "teardown",
1335
+ key: operation.key,
1336
+ metadata: {}
1337
+ })), share());
1338
+ }
1339
+ executeQuery(artifact, ...[variables, options]) {
1340
+ const operation = this.createOperation(artifact, variables, options?.metadata);
1341
+ return this.executeOperation(operation);
1342
+ }
1343
+ executeMutation(artifact, ...[variables, options]) {
1344
+ const operation = this.createOperation(artifact, variables, options?.metadata);
1345
+ return this.executeOperation(operation);
1346
+ }
1347
+ executeSubscription(artifact, ...[variables, options]) {
1348
+ const operation = this.createOperation(artifact, variables, options?.metadata);
1349
+ return this.executeOperation(operation);
1350
+ }
1351
+ executeFragment(artifact, fragmentRef, options) {
1352
+ const operation = {
1353
+ variant: "request",
1354
+ key: this.createOperationKey(),
1355
+ metadata: {
1356
+ ...options?.metadata,
1357
+ fragmentRef
1358
+ },
1359
+ artifact,
1360
+ variables: {}
1361
+ };
1362
+ return this.executeOperation(operation);
1363
+ }
1364
+ async query(artifact, ...[variables, options]) {
1365
+ const operation = this.createOperation(artifact, variables, options?.metadata);
1366
+ const result = await pipe(this.executeOperation(operation), take(1), collect);
1367
+ if (result.errors && result.errors.length > 0) throw new AggregatedError(result.errors);
1368
+ return result.data;
1369
+ }
1370
+ async mutation(artifact, ...[variables, options]) {
1371
+ const operation = this.createOperation(artifact, variables, options?.metadata);
1372
+ const result = await pipe(this.executeOperation(operation), take(1), collect);
1373
+ if (result.errors && result.errors.length > 0) throw new AggregatedError(result.errors);
1374
+ return result.data;
1375
+ }
1376
+ extension(name) {
1377
+ const ext = this.#extensions.get(name);
1378
+ if (!ext) throw new Error(`Exchange extension '${name}' is not registered. Check your exchange configuration.`);
1379
+ return ext;
1380
+ }
1381
+ maybeExtension(name) {
1382
+ return this.#extensions.get(name);
1383
+ }
1384
+ dispose() {
1385
+ this.operations$.complete();
1386
+ }
1387
+ };
1388
+ const createClient = (config) => {
1389
+ return new Client(config);
1390
+ };
1391
+
1392
+ //#endregion
1393
+ export { AggregatedError, Client, ExchangeError, GraphQLError, RequiredFieldError, cacheExchange, createClient, dedupExchange, fragmentExchange, httpExchange, isAggregatedError, isExchangeError, isGraphQLError, requiredExchange, retryExchange, stringify, subscriptionExchange };