@nkzw/fate 0.0.1

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/lib/index.mjs ADDED
@@ -0,0 +1,1842 @@
1
+ import { t as isRecord } from "./record-DnhZuvUe.mjs";
2
+
3
+ //#region src/args.ts
4
+ const ensureSerializable = (value, path) => {
5
+ const type = typeof value;
6
+ if (type === "function" || type === "symbol") throw new Error(`fate: Argument '${path}' must be serializable. Received '${type}'.`);
7
+ };
8
+ const cloneArgs = (value, path) => {
9
+ const cloneValue$1 = (entry, currentPath) => {
10
+ if (Array.isArray(entry)) return entry.map((item, index) => cloneValue$1(item, `${currentPath}[${index}]`));
11
+ if (isRecord(entry)) {
12
+ const result = {};
13
+ for (const [key, child] of Object.entries(entry)) result[key] = cloneValue$1(child, `${currentPath}.${key}`);
14
+ return result;
15
+ }
16
+ ensureSerializable(entry, currentPath);
17
+ return entry;
18
+ };
19
+ return cloneValue$1(value, path);
20
+ };
21
+ const stableSerialize = (value) => {
22
+ if (value === null) return "null";
23
+ const type = typeof value;
24
+ if (type === "number" || type === "boolean" || type === "bigint") return `${type}:${String(value)}`;
25
+ if (type === "string") return `string:${JSON.stringify(value)}`;
26
+ if (type === "undefined") return "undefined";
27
+ if (Array.isArray(value)) return `array:[${value.map(stableSerialize).join(",")}]`;
28
+ if (typeof value === "object") return `object:{${Object.entries(value).map(([key, entry]) => [key, entry]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([_key, entry]) => `${JSON.stringify(_key)}:${stableSerialize(entry)}`).join(",")}}`;
29
+ throw new Error(`fate: Unable to serialize argument value of type '${type}'.`);
30
+ };
31
+ const hashArgs = (argsValue, options = {}) => {
32
+ const keys = {};
33
+ for (const [key, value] of Object.entries(argsValue)) {
34
+ if (options.ignoreKeys && options.ignoreKeys.has(key)) continue;
35
+ keys[key] = value;
36
+ }
37
+ return stableSerialize(keys);
38
+ };
39
+ const mergeArgs = (target, source) => {
40
+ for (const [key, value] of Object.entries(source)) {
41
+ if (isRecord(value)) {
42
+ const existing = target[key];
43
+ if (isRecord(existing)) mergeArgs(existing, value);
44
+ else target[key] = { ...value };
45
+ continue;
46
+ }
47
+ target[key] = value;
48
+ }
49
+ };
50
+ const resolvedArgsFromPlan = (plan) => {
51
+ if (!plan || plan.args.size === 0) return;
52
+ const result = {};
53
+ for (const [path, entry] of plan.args.entries()) {
54
+ if (path === "") {
55
+ mergeArgs(result, entry.value);
56
+ continue;
57
+ }
58
+ const segments = path.split(".");
59
+ let current = result;
60
+ segments.forEach((segment, index) => {
61
+ if (index === segments.length - 1) {
62
+ const existing$1 = current[segment];
63
+ if (isRecord(existing$1)) mergeArgs(existing$1, entry.value);
64
+ else current[segment] = { ...entry.value };
65
+ return;
66
+ }
67
+ const existing = current[segment];
68
+ if (isRecord(existing)) {
69
+ current = existing;
70
+ return;
71
+ }
72
+ const next = {};
73
+ current[segment] = next;
74
+ current = next;
75
+ });
76
+ }
77
+ return result;
78
+ };
79
+ const hasEntries = (value) => Boolean(value && Object.keys(value).length > 0);
80
+ const combineArgsPayload = (base, scoped) => {
81
+ if (!hasEntries(base) && !hasEntries(scoped)) return;
82
+ const result = hasEntries(base) ? { ...base } : {};
83
+ if (hasEntries(scoped)) mergeArgs(result, scoped);
84
+ return result;
85
+ };
86
+ const getArgsAtPath = (payload, path) => {
87
+ if (!payload) return;
88
+ if (path === "") return payload;
89
+ const segments = path.split(".");
90
+ let current = payload;
91
+ for (const segment of segments) {
92
+ if (!isRecord(current)) return;
93
+ current = current[segment];
94
+ if (current === void 0) return;
95
+ }
96
+ return isRecord(current) ? current : void 0;
97
+ };
98
+ const applyArgsPayloadToPlan = (plan, payload) => {
99
+ for (const [path, entry] of plan.args.entries()) {
100
+ const actual = path === "" ? payload : getArgsAtPath(payload, path);
101
+ if (!actual) continue;
102
+ const cloned = cloneArgs(actual, path);
103
+ const hash = hashArgs(cloned, { ignoreKeys: entry.ignoreKeys });
104
+ plan.args.set(path, {
105
+ hash,
106
+ ignoreKeys: entry.ignoreKeys,
107
+ value: cloned
108
+ });
109
+ }
110
+ };
111
+ const scopeArgsPayload = (args, scope) => {
112
+ const segments = scope.split(".");
113
+ const result = {};
114
+ let current = result;
115
+ segments.forEach((segment, index) => {
116
+ if (index === segments.length - 1) {
117
+ current[segment] = args;
118
+ return;
119
+ }
120
+ const next = {};
121
+ current[segment] = next;
122
+ current = next;
123
+ });
124
+ return result;
125
+ };
126
+
127
+ //#endregion
128
+ //#region src/cache.ts
129
+ var ViewDataCache = class {
130
+ constructor() {
131
+ this.cache = /* @__PURE__ */ new Map();
132
+ this.rootDependencies = /* @__PURE__ */ new Map();
133
+ this.dependencyIndex = /* @__PURE__ */ new Map();
134
+ }
135
+ get(entityId, view$1, ref) {
136
+ return this.cache.get(entityId)?.get(view$1)?.get(ref) ?? null;
137
+ }
138
+ set(entityId, view$1, ref, thenable, dependencies) {
139
+ let entityMap = this.cache.get(entityId);
140
+ if (!entityMap) {
141
+ entityMap = /* @__PURE__ */ new WeakMap();
142
+ this.cache.set(entityId, entityMap);
143
+ }
144
+ let viewMap = entityMap.get(view$1);
145
+ if (!viewMap) {
146
+ viewMap = /* @__PURE__ */ new WeakMap();
147
+ entityMap.set(view$1, viewMap);
148
+ }
149
+ viewMap.set(ref, thenable);
150
+ let roots = this.rootDependencies.get(entityId);
151
+ if (!roots) {
152
+ roots = /* @__PURE__ */ new Set();
153
+ this.rootDependencies.set(entityId, roots);
154
+ }
155
+ for (const dependency of dependencies) if (!roots.has(dependency)) {
156
+ roots.add(dependency);
157
+ let dependents = this.dependencyIndex.get(dependency);
158
+ if (!dependents) {
159
+ dependents = /* @__PURE__ */ new Set();
160
+ this.dependencyIndex.set(dependency, dependents);
161
+ }
162
+ dependents.add(entityId);
163
+ }
164
+ }
165
+ invalidate(entityId) {
166
+ this.invalidateDependents(entityId, /* @__PURE__ */ new Set());
167
+ }
168
+ invalidateDependents(entityId, visited) {
169
+ if (visited.has(entityId)) return;
170
+ visited.add(entityId);
171
+ const dependents = this.dependencyIndex.get(entityId);
172
+ if (dependents) for (const dependent of dependents) this.invalidateDependents(dependent, visited);
173
+ this.delete(entityId);
174
+ }
175
+ delete(entityId) {
176
+ const roots = this.rootDependencies.get(entityId);
177
+ if (roots) {
178
+ for (const dependency of roots) {
179
+ const dependents = this.dependencyIndex.get(dependency);
180
+ if (!dependents) continue;
181
+ dependents.delete(entityId);
182
+ if (dependents.size === 0) this.dependencyIndex.delete(dependency);
183
+ }
184
+ this.rootDependencies.delete(entityId);
185
+ }
186
+ this.cache.delete(entityId);
187
+ }
188
+ };
189
+
190
+ //#endregion
191
+ //#region src/mask.ts
192
+ function emptyMask() {
193
+ return {
194
+ all: false,
195
+ children: /* @__PURE__ */ new Map()
196
+ };
197
+ }
198
+ function cloneMask(m) {
199
+ const clone = {
200
+ all: m.all,
201
+ children: /* @__PURE__ */ new Map()
202
+ };
203
+ for (const [key, value] of m.children) clone.children.set(key, cloneMask(value));
204
+ return clone;
205
+ }
206
+ function addPath(mask, path) {
207
+ if (mask.all) return;
208
+ const parts = path.split(".");
209
+ let current = mask;
210
+ for (let i = 0; i < parts.length; i++) {
211
+ const seg = parts[i];
212
+ let child = current.children.get(seg);
213
+ if (!child) {
214
+ child = emptyMask();
215
+ current.children.set(seg, child);
216
+ }
217
+ current = child;
218
+ }
219
+ current.all = true;
220
+ current.children.clear();
221
+ }
222
+ function union(into, b) {
223
+ if (into.all || b.all) {
224
+ into.all = true;
225
+ into.children.clear();
226
+ return;
227
+ }
228
+ for (const [key, child] of b.children) {
229
+ const exist = into.children.get(key);
230
+ if (!exist) into.children.set(key, cloneMask(child));
231
+ else union(exist, child);
232
+ }
233
+ }
234
+ function fromPaths(paths) {
235
+ const mask = emptyMask();
236
+ for (const path of paths) addPath(mask, path);
237
+ return mask;
238
+ }
239
+ function isCovered(mask, path) {
240
+ if (mask.all) return true;
241
+ const parts = path.split(".");
242
+ let current = mask;
243
+ for (let i = 0; i < parts.length; i++) {
244
+ if (!current) return false;
245
+ if (current.all) return true;
246
+ current = current.children.get(parts[i]);
247
+ }
248
+ return !!current && (current.all || current.children.size === 0);
249
+ }
250
+ function diffPaths(paths, mask) {
251
+ const missing = /* @__PURE__ */ new Set();
252
+ for (const path of paths) if (!isCovered(mask, path)) missing.add(path);
253
+ return missing;
254
+ }
255
+ function intersects(a, b) {
256
+ if (a.all || b.all) return true;
257
+ for (const [key, child] of a.children) {
258
+ const otherChild = b.children.get(key);
259
+ if (!otherChild) continue;
260
+ if (child.all || otherChild.all) return true;
261
+ if (intersects(child, otherChild)) return true;
262
+ }
263
+ return false;
264
+ }
265
+
266
+ //#endregion
267
+ //#region ../../node_modules/.pnpm/@trpc+server@11.7.2_typescript@5.9.3/node_modules/@trpc/server/dist/getErrorShape-BH60iMC2.mjs
268
+ var __create = Object.create;
269
+ var __defProp = Object.defineProperty;
270
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
271
+ var __getOwnPropNames = Object.getOwnPropertyNames;
272
+ var __getProtoOf = Object.getPrototypeOf;
273
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
274
+ var __commonJS = (cb, mod) => function() {
275
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
276
+ };
277
+ var __copyProps = (to, from, except, desc) => {
278
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
279
+ key = keys[i];
280
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
281
+ get: ((k) => from[k]).bind(null, key),
282
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
283
+ });
284
+ }
285
+ return to;
286
+ };
287
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
288
+ value: mod,
289
+ enumerable: true
290
+ }) : target, mod));
291
+ const JSONRPC2_TO_HTTP_CODE = {
292
+ PARSE_ERROR: 400,
293
+ BAD_REQUEST: 400,
294
+ UNAUTHORIZED: 401,
295
+ PAYMENT_REQUIRED: 402,
296
+ FORBIDDEN: 403,
297
+ NOT_FOUND: 404,
298
+ METHOD_NOT_SUPPORTED: 405,
299
+ TIMEOUT: 408,
300
+ CONFLICT: 409,
301
+ PRECONDITION_FAILED: 412,
302
+ PAYLOAD_TOO_LARGE: 413,
303
+ UNSUPPORTED_MEDIA_TYPE: 415,
304
+ UNPROCESSABLE_CONTENT: 422,
305
+ PRECONDITION_REQUIRED: 428,
306
+ TOO_MANY_REQUESTS: 429,
307
+ CLIENT_CLOSED_REQUEST: 499,
308
+ INTERNAL_SERVER_ERROR: 500,
309
+ NOT_IMPLEMENTED: 501,
310
+ BAD_GATEWAY: 502,
311
+ SERVICE_UNAVAILABLE: 503,
312
+ GATEWAY_TIMEOUT: 504
313
+ };
314
+ function getStatusCodeFromKey(code) {
315
+ var _JSONRPC2_TO_HTTP_COD;
316
+ return (_JSONRPC2_TO_HTTP_COD = JSONRPC2_TO_HTTP_CODE[code]) !== null && _JSONRPC2_TO_HTTP_COD !== void 0 ? _JSONRPC2_TO_HTTP_COD : 500;
317
+ }
318
+ function getHTTPStatusCodeFromError(error) {
319
+ return getStatusCodeFromKey(error.code);
320
+ }
321
+ var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
322
+ function _typeof$2(o) {
323
+ "@babel/helpers - typeof";
324
+ return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
325
+ return typeof o$1;
326
+ } : function(o$1) {
327
+ return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
328
+ }, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
329
+ }
330
+ module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
331
+ } });
332
+ var require_toPrimitive = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js"(exports, module) {
333
+ var _typeof$1 = require_typeof()["default"];
334
+ function toPrimitive$1(t, r) {
335
+ if ("object" != _typeof$1(t) || !t) return t;
336
+ var e = t[Symbol.toPrimitive];
337
+ if (void 0 !== e) {
338
+ var i = e.call(t, r || "default");
339
+ if ("object" != _typeof$1(i)) return i;
340
+ throw new TypeError("@@toPrimitive must return a primitive value.");
341
+ }
342
+ return ("string" === r ? String : Number)(t);
343
+ }
344
+ module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
345
+ } });
346
+ var require_toPropertyKey = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js"(exports, module) {
347
+ var _typeof = require_typeof()["default"];
348
+ var toPrimitive = require_toPrimitive();
349
+ function toPropertyKey$1(t) {
350
+ var i = toPrimitive(t, "string");
351
+ return "symbol" == _typeof(i) ? i : i + "";
352
+ }
353
+ module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
354
+ } });
355
+ var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js"(exports, module) {
356
+ var toPropertyKey = require_toPropertyKey();
357
+ function _defineProperty(e, r, t) {
358
+ return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
359
+ value: t,
360
+ enumerable: !0,
361
+ configurable: !0,
362
+ writable: !0
363
+ }) : e[r] = t, e;
364
+ }
365
+ module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports;
366
+ } });
367
+ var require_objectSpread2 = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/objectSpread2.js"(exports, module) {
368
+ var defineProperty = require_defineProperty();
369
+ function ownKeys(e, r) {
370
+ var t = Object.keys(e);
371
+ if (Object.getOwnPropertySymbols) {
372
+ var o = Object.getOwnPropertySymbols(e);
373
+ r && (o = o.filter(function(r$1) {
374
+ return Object.getOwnPropertyDescriptor(e, r$1).enumerable;
375
+ })), t.push.apply(t, o);
376
+ }
377
+ return t;
378
+ }
379
+ function _objectSpread2(e) {
380
+ for (var r = 1; r < arguments.length; r++) {
381
+ var t = null != arguments[r] ? arguments[r] : {};
382
+ r % 2 ? ownKeys(Object(t), !0).forEach(function(r$1) {
383
+ defineProperty(e, r$1, t[r$1]);
384
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r$1) {
385
+ Object.defineProperty(e, r$1, Object.getOwnPropertyDescriptor(t, r$1));
386
+ });
387
+ }
388
+ return e;
389
+ }
390
+ module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
391
+ } });
392
+ var import_objectSpread2 = __toESM(require_objectSpread2(), 1);
393
+
394
+ //#endregion
395
+ //#region src/types.ts
396
+ /** Internal marker added to objects that represent a view payload. */
397
+ const ViewKind = Symbol("__fate__view");
398
+ /** Symbol used to attach the set of view tags that were spread into a ref or masked record. */
399
+ const ViewsTag = Symbol("__fate__views");
400
+ /** Symbol used to brand a value as a node reference inside the cache. */
401
+ const NodeRefTag = Symbol("__fate__node-ref");
402
+ /** Symbol attached to connection results so pagination helpers can find their metadata. */
403
+ const ConnectionTag = Symbol("__fate__connection");
404
+ const viewTag = "__fate-view__";
405
+ /** Generates a stable view tag for a view definition. */
406
+ function getViewTag(id$1) {
407
+ return `${viewTag}${id$1}`;
408
+ }
409
+ /** Determines whether a property key is a fate view tag. */
410
+ function isViewTag(key) {
411
+ return key.startsWith(viewTag);
412
+ }
413
+ /** Indicates whether a request item represents an explicit node ID. */
414
+ function isNodeItem(item) {
415
+ return "id" in item;
416
+ }
417
+ /** Indicates whether a request item represents explicit node IDs. */
418
+ function isNodesItem(item) {
419
+ return "ids" in item;
420
+ }
421
+ /** Brand used on mutation definitions to mark their identity in the d.ts output. */
422
+ const MutationKind = "__fate__mutation";
423
+
424
+ //#endregion
425
+ //#region src/view.ts
426
+ /**
427
+ * Collects all view payloads that apply to the given ref.
428
+ */
429
+ const getViewPayloads = (view$1, ref) => {
430
+ const result = [];
431
+ for (const [key, value] of Object.entries(view$1)) if (isViewTag(key) && (!ref || ref[ViewsTag]?.has(key))) result.push(value);
432
+ return result;
433
+ };
434
+ /**
435
+ * Returns the set of view tags defined on a view composition.
436
+ */
437
+ const getViewNames = (view$1) => {
438
+ const result = /* @__PURE__ */ new Set();
439
+ for (const key of Object.keys(view$1)) if (isViewTag(key)) result.add(key);
440
+ return result;
441
+ };
442
+ /**
443
+ * Extracts view tags from a nested selection object.
444
+ */
445
+ const getSelectionViewNames = (selection) => {
446
+ return getViewNames(selection);
447
+ };
448
+ let id = 0;
449
+ const isDevelopment = import.meta?.env?.DEV || import.meta?.env?.NODE_ENV !== "production";
450
+ let viewModulePath = null;
451
+ const getStableId = () => {
452
+ if (isDevelopment) try {
453
+ if (viewModulePath == null) viewModulePath = new URL(import.meta.url).pathname;
454
+ const stack = (/* @__PURE__ */ new Error()).stack?.split("\n");
455
+ if (stack) for (let i = 1; i < stack.length; i++) {
456
+ const match = stack[i].trim().match(/\(?([^()]+):(\d+):(\d+)\)?$/);
457
+ if (!match) continue;
458
+ const [, source, line, column] = match;
459
+ if (!source.includes(viewModulePath)) {
460
+ const file = source.startsWith("at ") ? source.slice(3) : source;
461
+ return `${/^[A-Za-z][\d+.A-Za-z-]*:/.test(file) ? new URL(file).pathname : file}:${line}:${column}`;
462
+ }
463
+ }
464
+ } catch {}
465
+ return String(id++);
466
+ };
467
+ /**
468
+ * Creates a reusable view for an object using the declared selection.
469
+ *
470
+ * @example
471
+ * const PostView = view<Post>()({
472
+ * id: true,
473
+ * title: true,
474
+ * });
475
+ */
476
+ function view() {
477
+ const viewId = getStableId();
478
+ return (select) => {
479
+ const payload = Object.freeze({
480
+ select,
481
+ [ViewKind]: true
482
+ });
483
+ const viewComposition = Object.defineProperty({}, getViewTag(viewId), {
484
+ configurable: false,
485
+ enumerable: true,
486
+ value: payload,
487
+ writable: false
488
+ });
489
+ return Object.freeze(viewComposition);
490
+ };
491
+ }
492
+
493
+ //#endregion
494
+ //#region src/ref.ts
495
+ /**
496
+ * Builds the canonical cache ID for an entity.
497
+ */
498
+ const toEntityId = (type, rawId) => `${type}:${String(rawId)}`;
499
+ /**
500
+ * Splits a cache entity ID back into its type and raw identifier.
501
+ */
502
+ function parseEntityId(id$1) {
503
+ const idx = id$1.indexOf(":");
504
+ return idx < 0 ? {
505
+ id: id$1,
506
+ type: ""
507
+ } : {
508
+ id: id$1.slice(idx + 1),
509
+ type: id$1.slice(0, idx)
510
+ };
511
+ }
512
+ /**
513
+ * Attaches view tags to a ref without leaking the symbol.
514
+ */
515
+ function assignViewTag(target, value) {
516
+ Object.defineProperty(target, ViewsTag, {
517
+ configurable: false,
518
+ enumerable: false,
519
+ value,
520
+ writable: false
521
+ });
522
+ }
523
+ const getRootViewNames = (view$1) => {
524
+ const names = new Set(getViewNames(view$1));
525
+ const payloads = getViewPayloads(view$1, null);
526
+ for (const payload of payloads) for (const name of getSelectionViewNames(payload.select)) names.add(name);
527
+ return names;
528
+ };
529
+ /**
530
+ * Creates an immutable `ViewRef` for an entity, tagging it with all views from
531
+ * the provided composition so `useView` can resolve the ref against a view.
532
+ */
533
+ function createRef(__typename, id$1, view$1, options) {
534
+ const ref = {
535
+ __typename,
536
+ id: id$1
537
+ };
538
+ assignViewTag(ref, options?.root ? getRootViewNames(view$1) : getViewNames(view$1));
539
+ return Object.freeze(ref);
540
+ }
541
+
542
+ //#endregion
543
+ //#region src/selection.ts
544
+ const paginationKeys = new Set([
545
+ "after",
546
+ "before",
547
+ "cursor"
548
+ ]);
549
+ const isConnectionSelection = (value) => isRecord(value.items) && "node" in value.items;
550
+ /**
551
+ * Flattens a view into a `SelectionPlan`, expanding composed views and
552
+ * partitioning nested args so the client can fetch exactly what is declared.
553
+ */
554
+ const getSelectionPlan = (viewComposition, ref) => {
555
+ const args = /* @__PURE__ */ new Map();
556
+ const paths = /* @__PURE__ */ new Set();
557
+ const assignArgs = (path, value, ignoreKeys) => {
558
+ const hash = hashArgs(value, { ignoreKeys });
559
+ args.set(path, {
560
+ hash,
561
+ ignoreKeys,
562
+ value
563
+ });
564
+ };
565
+ const walk = (selection, prefix, context = "default") => {
566
+ if (prefix === null && context !== "connection" && isConnectionSelection(selection)) {
567
+ if (selection.args && isRecord(selection.args)) assignArgs("", cloneArgs(selection.args, "args"), isRecord(selection.items) && isRecord(selection.items.node) ? paginationKeys : void 0);
568
+ const { args: args$1, ...withoutArgs } = selection;
569
+ walk(withoutArgs, prefix, "connection");
570
+ return;
571
+ }
572
+ for (const [key, value] of Object.entries(selection)) {
573
+ const valueType = typeof value;
574
+ const path = prefix ? `${prefix}.${key}` : key;
575
+ if (context === "connection") {
576
+ if (key === "args" || key === "pagination") continue;
577
+ if (key === "items" && isRecord(value)) {
578
+ if (isRecord(value.node)) walk(value.node, prefix);
579
+ continue;
580
+ }
581
+ }
582
+ if (valueType === "boolean") {
583
+ if (value) paths.add(path);
584
+ continue;
585
+ }
586
+ if (isViewTag(key)) {
587
+ if (!ref || ref[ViewsTag] && ref[ViewsTag].has(key)) walk(value.select, prefix);
588
+ continue;
589
+ }
590
+ if (isRecord(value)) {
591
+ const selectionObject = value;
592
+ if (isConnectionSelection(selectionObject)) {
593
+ if (selectionObject.args && isRecord(selectionObject.args)) assignArgs(path, cloneArgs(selectionObject.args, path), isRecord(selectionObject.items) && isRecord(selectionObject.items.node) ? paginationKeys : void 0);
594
+ const { args: _ignored, ...withoutArgs } = selectionObject;
595
+ walk(withoutArgs, path, "connection");
596
+ continue;
597
+ }
598
+ const hasArgs = selectionObject.args && isRecord(selectionObject.args);
599
+ let selectionWithoutArgs = selectionObject;
600
+ if (hasArgs) {
601
+ assignArgs(path, cloneArgs(selectionObject.args, path), isRecord(selectionObject.items) && isRecord(selectionObject.items?.node) ? paginationKeys : void 0);
602
+ const { args: args$1, ...rest } = selectionObject;
603
+ selectionWithoutArgs = rest;
604
+ }
605
+ if (Object.keys(selectionWithoutArgs).length > 0) walk(selectionWithoutArgs, path);
606
+ else if (hasArgs) paths.add(path);
607
+ }
608
+ }
609
+ };
610
+ const payloads = getViewPayloads(viewComposition, ref);
611
+ if (payloads.length === 0 && isRecord(viewComposition)) walk(viewComposition, null);
612
+ else for (const payload of payloads) walk(payload.select, null);
613
+ return {
614
+ args,
615
+ paths
616
+ };
617
+ };
618
+
619
+ //#endregion
620
+ //#region src/mutation.ts
621
+ /**
622
+ * Defines a mutation for a given entity type, preserving the input and output
623
+ * types for transports.
624
+ */
625
+ function mutation(entity) {
626
+ return Object.freeze({
627
+ entity,
628
+ [MutationKind]: true
629
+ });
630
+ }
631
+ const collectImplicitSelectedPaths = (value) => {
632
+ const paths = /* @__PURE__ */ new Set();
633
+ const walk = (current, prefix) => {
634
+ if (!current || typeof current !== "object") {
635
+ if (prefix) paths.add(prefix);
636
+ return;
637
+ }
638
+ if (Array.isArray(current)) {
639
+ if (prefix) paths.add(prefix);
640
+ for (const child of current) walk(child, prefix);
641
+ return;
642
+ }
643
+ for (const [key, child] of Object.entries(current)) {
644
+ const next = prefix ? `${prefix}.${key}` : key;
645
+ paths.add(next);
646
+ walk(child, next);
647
+ }
648
+ };
649
+ walk(value, null);
650
+ return paths;
651
+ };
652
+ const maybeGetId = (getId$1, input) => {
653
+ try {
654
+ return getId$1(input);
655
+ } catch {
656
+ return null;
657
+ }
658
+ };
659
+ const emptySet$1 = /* @__PURE__ */ new Set();
660
+ /**
661
+ * Binds a mutation definition to a `FateClient`, wiring up optimistic updates,
662
+ * cache writes, and error handling.
663
+ */
664
+ function wrapMutation(client, identifier) {
665
+ const config = client.getTypeConfig(identifier.entity);
666
+ return async ({ args, delete: deleteRecord, input, optimistic, view: view$1 }) => {
667
+ const id$1 = maybeGetId(config.getId, input);
668
+ const plan = view$1 ? getSelectionPlan(view$1, null) : void 0;
669
+ const viewSelection = plan?.paths;
670
+ const optimisticRecord = optimistic ? id$1 != null ? {
671
+ id: id$1,
672
+ ...optimistic
673
+ } : optimistic : void 0;
674
+ const optimisticRecordId = optimisticRecord !== void 0 ? maybeGetId(config.getId, optimisticRecord) : null;
675
+ const optimisticEntityId = id$1 != null ? toEntityId(identifier.entity, id$1) : optimisticRecordId != null ? toEntityId(identifier.entity, optimisticRecordId) : null;
676
+ const snapshots = /* @__PURE__ */ new Map();
677
+ const listSnapshots = deleteRecord ? /* @__PURE__ */ new Map() : void 0;
678
+ const optimisticSelection = optimisticRecord ? collectImplicitSelectedPaths(optimisticRecord) : void 0;
679
+ const selection = viewSelection || optimisticSelection ? new Set([...viewSelection ? [...viewSelection] : [], ...optimisticSelection ? [...optimisticSelection] : []]) : /* @__PURE__ */ new Set();
680
+ const optimisticToken = optimisticEntityId ? client.registerOptimisticUpdate(optimisticEntityId, optimisticSelection ?? emptySet$1) : null;
681
+ if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan);
682
+ if (deleteRecord) {
683
+ if (id$1 == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
684
+ client.deleteRecord(identifier.entity, id$1, snapshots, listSnapshots);
685
+ }
686
+ try {
687
+ const result = await client.executeMutation(identifier.key, input, selection, {
688
+ args,
689
+ plan
690
+ });
691
+ if (result && typeof result === "object" && (!deleteRecord || Boolean(view$1))) {
692
+ const select = collectImplicitSelectedPaths(result);
693
+ const pendingMask = optimisticEntityId ? client.getPendingOptimisticMask(optimisticEntityId, { excludeToken: optimisticToken }) : null;
694
+ const filteredSelection = optimisticEntityId ? client.filterSelectionForPendingOptimistics(optimisticEntityId, select, { excludeToken: optimisticToken }) : select;
695
+ client.write(identifier.entity, result, filteredSelection, void 0, plan, null, pendingMask);
696
+ if (deleteRecord && id$1 != null) client.deleteRecord(identifier.entity, id$1);
697
+ const resultId = maybeGetId(config.getId, result);
698
+ if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(identifier.entity, optimisticRecordId);
699
+ }
700
+ return {
701
+ error: void 0,
702
+ result
703
+ };
704
+ } catch (error) {
705
+ client.clearOptimisticUpdate(optimisticToken);
706
+ if (snapshots.size > 0) for (const [id$2, snapshot] of snapshots) client.restore(id$2, snapshot);
707
+ if (listSnapshots && listSnapshots.size > 0) for (const [name, list] of listSnapshots) client.restoreList(name, list);
708
+ if (error instanceof Error) {
709
+ const { data } = error;
710
+ if ((data ? categorizeTRPCError(getHTTPStatusCodeFromError(data)) : "boundary") === "boundary") throw error;
711
+ return {
712
+ error,
713
+ result: void 0
714
+ };
715
+ } else throw new Error(`fate: Mutation '${identifier.key}' failed.`);
716
+ } finally {
717
+ client.clearOptimisticUpdate(optimisticToken);
718
+ }
719
+ };
720
+ }
721
+ function categorizeTRPCError(statusCode) {
722
+ switch (statusCode) {
723
+ case 400:
724
+ case 402:
725
+ case 404:
726
+ case 408:
727
+ case 409:
728
+ case 412:
729
+ case 413:
730
+ case 415:
731
+ case 422:
732
+ case 429:
733
+ case 499: return "callSite";
734
+ case 401:
735
+ case 403:
736
+ case 405:
737
+ case 428:
738
+ case 500:
739
+ case 501:
740
+ case 502:
741
+ case 503:
742
+ case 504:
743
+ default: return "boundary";
744
+ }
745
+ }
746
+
747
+ //#endregion
748
+ //#region src/node-ref.ts
749
+ function createNodeRef(id$1) {
750
+ const ref = {};
751
+ Object.defineProperty(ref, NodeRefTag, {
752
+ configurable: false,
753
+ enumerable: false,
754
+ value: id$1,
755
+ writable: false
756
+ });
757
+ return Object.freeze(ref);
758
+ }
759
+ function isNodeRef(value) {
760
+ return !value || typeof value !== "object" ? false : NodeRefTag in value;
761
+ }
762
+ function getNodeRefId(ref) {
763
+ return ref[NodeRefTag];
764
+ }
765
+
766
+ //#endregion
767
+ //#region src/store.ts
768
+ const getListKey = (ownerId, field, hash = "default") => `${ownerId} __fate__ ${field} __fate__ ${hash}`;
769
+ const cloneValue = (value) => {
770
+ if (Array.isArray(value)) return value.map(cloneValue);
771
+ if (isNodeRef(value)) return value;
772
+ if (value != null && typeof value === "object") {
773
+ const result = {};
774
+ for (const [key, record] of Object.entries(value)) result[key] = cloneValue(record);
775
+ return result;
776
+ }
777
+ return value;
778
+ };
779
+ const emptyFunction = () => {};
780
+ var Store = class {
781
+ constructor() {
782
+ this.coverage = /* @__PURE__ */ new Map();
783
+ this.lists = /* @__PURE__ */ new Map();
784
+ this.records = /* @__PURE__ */ new Map();
785
+ this.subscriptions = /* @__PURE__ */ new Map();
786
+ this.listSubscriptions = /* @__PURE__ */ new Map();
787
+ }
788
+ read(id$1) {
789
+ return this.records.get(id$1);
790
+ }
791
+ merge(id$1, partial, paths) {
792
+ const changedPaths = this.mergeInternal(id$1, partial, paths);
793
+ if (changedPaths) this.notify(id$1, changedPaths);
794
+ }
795
+ mergeInternal(id$1, partial, paths) {
796
+ const previous = this.records.get(id$1);
797
+ const changedPaths = /* @__PURE__ */ new Set();
798
+ let mask = this.coverage.get(id$1);
799
+ if (!mask) {
800
+ mask = emptyMask();
801
+ this.coverage.set(id$1, mask);
802
+ }
803
+ union(mask, fromPaths(paths));
804
+ if (previous) {
805
+ let hasChanges = false;
806
+ for (const [key, value] of Object.entries(partial)) if (previous[key] !== value) {
807
+ hasChanges = true;
808
+ changedPaths.add(key);
809
+ }
810
+ if (!hasChanges) return null;
811
+ this.records.set(id$1, {
812
+ ...previous,
813
+ ...partial
814
+ });
815
+ } else this.records.set(id$1, { ...partial });
816
+ return changedPaths;
817
+ }
818
+ deleteRecord(id$1) {
819
+ this.records.delete(id$1);
820
+ this.coverage.delete(id$1);
821
+ }
822
+ missingForSelection(id$1, paths) {
823
+ const requested = new Set(paths);
824
+ if (!this.records.has(id$1)) return requested;
825
+ const mask = this.coverage.get(id$1);
826
+ if (!mask) return requested;
827
+ return diffPaths(requested, mask);
828
+ }
829
+ subscribe(id$1, selectionOrFn, callback) {
830
+ let mask = null;
831
+ let fn = emptyFunction;
832
+ if (typeof selectionOrFn === "function") fn = selectionOrFn;
833
+ else if (callback) {
834
+ mask = selectionOrFn ? fromPaths(selectionOrFn) : null;
835
+ fn = callback;
836
+ }
837
+ let subscribers = this.subscriptions.get(id$1);
838
+ if (!subscribers) {
839
+ subscribers = /* @__PURE__ */ new Set();
840
+ this.subscriptions.set(id$1, subscribers);
841
+ }
842
+ const subscription = {
843
+ fn,
844
+ mask
845
+ };
846
+ subscribers.add(subscription);
847
+ return () => {
848
+ const set = this.subscriptions.get(id$1);
849
+ if (!set) return;
850
+ set.delete(subscription);
851
+ if (set.size === 0) this.subscriptions.delete(id$1);
852
+ };
853
+ }
854
+ notify(id$1, paths) {
855
+ const set = this.subscriptions.get(id$1);
856
+ if (!set) return;
857
+ const changedPaths = paths ? [...paths] : [];
858
+ const changedMask = changedPaths.length > 0 ? fromPaths(changedPaths) : null;
859
+ for (const { fn, mask } of set) {
860
+ if (mask && changedMask && !intersects(changedMask, mask)) continue;
861
+ try {
862
+ fn();
863
+ } catch {}
864
+ }
865
+ }
866
+ notifyListSubscribers(key) {
867
+ const set = this.listSubscriptions.get(key);
868
+ if (!set) return;
869
+ for (const fn of set) try {
870
+ fn();
871
+ } catch {}
872
+ }
873
+ getList(key) {
874
+ return this.lists.get(key)?.ids;
875
+ }
876
+ getListState(key) {
877
+ return this.lists.get(key);
878
+ }
879
+ getListsForField(ownerId, field) {
880
+ const entries = [];
881
+ const prefix = getListKey(ownerId, field, "");
882
+ for (const entry of this.lists.entries()) if (entry[0].startsWith(prefix)) entries.push(entry);
883
+ return entries;
884
+ }
885
+ setList(key, state) {
886
+ this.lists.set(key, state);
887
+ this.notifyListSubscribers(key);
888
+ }
889
+ restoreList(key, list) {
890
+ if (list == null) this.lists.delete(key);
891
+ else this.setList(key, list);
892
+ }
893
+ subscribeList(key, fn) {
894
+ let set = this.listSubscriptions.get(key);
895
+ if (!set) {
896
+ set = /* @__PURE__ */ new Set();
897
+ this.listSubscriptions.set(key, set);
898
+ }
899
+ set.add(fn);
900
+ return () => {
901
+ const subscribers = this.listSubscriptions.get(key);
902
+ if (!subscribers) return;
903
+ subscribers.delete(fn);
904
+ if (subscribers.size === 0) this.listSubscriptions.delete(key);
905
+ };
906
+ }
907
+ removeReferencesTo(targetId, viewDataCache, snapshots, listSnapshots) {
908
+ for (const [key, list] of this.lists.entries()) {
909
+ const { ids: ids$1 } = list;
910
+ if (!ids$1.includes(targetId)) continue;
911
+ if (listSnapshots && !listSnapshots.has(key)) listSnapshots.set(key, list);
912
+ const entityIds = [];
913
+ const cursors = list.cursors ? [] : void 0;
914
+ for (let index = 0; index < ids$1.length; index++) {
915
+ const id$1 = ids$1[index];
916
+ if (id$1 === targetId) continue;
917
+ entityIds.push(id$1);
918
+ if (cursors) cursors.push(list.cursors?.[index]);
919
+ }
920
+ this.setList(key, {
921
+ cursors,
922
+ ids: entityIds,
923
+ pagination: list.pagination
924
+ });
925
+ }
926
+ const ids = /* @__PURE__ */ new Map();
927
+ for (const [id$1, record] of this.records.entries()) {
928
+ let updated = false;
929
+ const next = {};
930
+ const paths = /* @__PURE__ */ new Set();
931
+ for (const [key, value] of Object.entries(record)) if (Array.isArray(value)) {
932
+ const filtered = value.filter((item) => !(isNodeRef(item) && getNodeRefId(item) === targetId));
933
+ if (filtered.length !== value.length) {
934
+ updated = true;
935
+ paths.add(key);
936
+ next[key] = filtered;
937
+ }
938
+ } else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
939
+ updated = true;
940
+ paths.add(key);
941
+ next[key] = null;
942
+ }
943
+ if (!updated) continue;
944
+ if (snapshots && !snapshots.has(id$1)) snapshots.set(id$1, this.snapshot(id$1));
945
+ viewDataCache.invalidate(id$1);
946
+ this.mergeInternal(id$1, next, paths);
947
+ ids.set(id$1, paths);
948
+ }
949
+ for (const [id$1, paths] of ids) this.notify(id$1, paths);
950
+ }
951
+ snapshot(id$1) {
952
+ const record = this.records.get(id$1);
953
+ const mask = this.coverage.get(id$1);
954
+ return {
955
+ mask: mask ? cloneMask(mask) : void 0,
956
+ record: record ? cloneValue(record) : void 0
957
+ };
958
+ }
959
+ restore(id$1, snapshot) {
960
+ if (snapshot.record === void 0) this.records.delete(id$1);
961
+ else this.records.set(id$1, snapshot.record);
962
+ if (snapshot.mask === void 0) this.coverage.delete(id$1);
963
+ else this.coverage.set(id$1, snapshot.mask);
964
+ this.notify(id$1);
965
+ }
966
+ };
967
+
968
+ //#endregion
969
+ //#region src/client.ts
970
+ const getId = (record) => {
971
+ if (!record || typeof record !== "object" || !("id" in record)) throw new Error(`fate: Missing 'id' on entity record.`);
972
+ const value = record.id;
973
+ const valueType = typeof value;
974
+ if (valueType !== "string" && valueType !== "number") throw new Error(`fate: Entity id must be a string or number, received '${valueType}'.`);
975
+ return value;
976
+ };
977
+ const emptySet = /* @__PURE__ */ new Set();
978
+ const setNestedValue = (target, key, value) => {
979
+ const path = key.split(".");
980
+ let current = target;
981
+ for (let index = 0; index < path.length; index += 1) {
982
+ const segment = path[index];
983
+ const isLeaf = index === path.length - 1;
984
+ if (!segment) continue;
985
+ if (isLeaf) {
986
+ current[segment] = value;
987
+ return;
988
+ }
989
+ current[segment] = current[segment] ?? Object.create(null);
990
+ current = current[segment];
991
+ }
992
+ };
993
+ const serializeId = (value) => `${typeof value}:${String(value)}`;
994
+ const getViewSignature = (view$1) => {
995
+ const viewNames = getViewNames(view$1);
996
+ return viewNames.size ? [...viewNames].sort().join(",") : "";
997
+ };
998
+ const getRequestCacheKey = (request) => {
999
+ const parts = [];
1000
+ const names = Object.keys(request).sort();
1001
+ for (const name of names) {
1002
+ const item = request[name];
1003
+ if (!item) continue;
1004
+ const viewSignature = getViewSignature(item.root);
1005
+ if (isNodeItem(item)) {
1006
+ parts.push(`node:${name}:${item.type}:${viewSignature}:${item.id}`);
1007
+ continue;
1008
+ }
1009
+ if (isNodesItem(item)) {
1010
+ parts.push(`node:${name}:${item.type}:${viewSignature}:${item.ids.map(serializeId).join(",")}`);
1011
+ continue;
1012
+ }
1013
+ parts.push(`list:${name}:${item.type}:${viewSignature}:${item.args ? hashArgs(item.args) : ""}`);
1014
+ }
1015
+ return parts.join("$");
1016
+ };
1017
+ const groupSelectionByPrefix = (select) => {
1018
+ if (select.size === 0) return /* @__PURE__ */ new Map();
1019
+ const result = /* @__PURE__ */ new Map();
1020
+ for (const path of select) {
1021
+ const separatorIndex = path.indexOf(".");
1022
+ if (separatorIndex === -1) continue;
1023
+ const prefix = path.slice(0, separatorIndex);
1024
+ const remainder = path.slice(separatorIndex + 1);
1025
+ let bucket = result.get(prefix);
1026
+ if (!bucket) {
1027
+ bucket = /* @__PURE__ */ new Set();
1028
+ result.set(prefix, bucket);
1029
+ }
1030
+ bucket.add(remainder);
1031
+ }
1032
+ return result;
1033
+ };
1034
+ /**
1035
+ * Core client that normalizes records, manages the view cache, and coordinates
1036
+ * data fetching.
1037
+ */
1038
+ var FateClient = class {
1039
+ constructor(options) {
1040
+ this.parentLists = /* @__PURE__ */ new Map();
1041
+ this.pending = /* @__PURE__ */ new Map();
1042
+ this.optimisticMasks = /* @__PURE__ */ new Map();
1043
+ this.optimisticByEntity = /* @__PURE__ */ new Map();
1044
+ this.optimisticTokenCounter = 0;
1045
+ this.requests = /* @__PURE__ */ new Map();
1046
+ this.stalledRequests = /* @__PURE__ */ new Set();
1047
+ this.store = new Store();
1048
+ this.viewDataCache = new ViewDataCache();
1049
+ this.transport = options.transport;
1050
+ this.types = new Map(options.types.map((entity) => [entity.type, {
1051
+ getId,
1052
+ ...entity
1053
+ }]));
1054
+ this.mutationMap = Object.create(null);
1055
+ this.mutations = Object.create(null);
1056
+ this.actions = Object.create(null);
1057
+ if (options.mutations) for (const [key, definition] of Object.entries(options.mutations)) {
1058
+ const mutation$1 = wrapMutation(this, {
1059
+ ...definition,
1060
+ key
1061
+ });
1062
+ this.mutationMap[key] = mutation$1;
1063
+ setNestedValue(this.mutations, key, mutation$1);
1064
+ setNestedValue(this.actions, key, async (_previousState, data) => data === "reset" ? null : await this.mutationMap[key](data));
1065
+ }
1066
+ this.initializeParentLists();
1067
+ }
1068
+ initializeParentLists() {
1069
+ for (const config of this.types.values()) {
1070
+ if (!config.fields) continue;
1071
+ for (const [field, descriptor] of Object.entries(config.fields)) if (descriptor && typeof descriptor === "object" && "listOf" in descriptor) {
1072
+ const childType = descriptor.listOf;
1073
+ const childConfig = this.types.get(childType);
1074
+ if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${config.type}.${field}').`);
1075
+ if (!childConfig.fields) continue;
1076
+ let via;
1077
+ for (const [childField, childDescriptor] of Object.entries(childConfig.fields)) if (childDescriptor && typeof childDescriptor === "object" && "type" in childDescriptor && childDescriptor.type === config.type) {
1078
+ via = childField;
1079
+ break;
1080
+ }
1081
+ if (!via) continue;
1082
+ let list = this.parentLists.get(childType);
1083
+ if (!list) {
1084
+ list = [];
1085
+ this.parentLists.set(childType, list);
1086
+ }
1087
+ list.push({
1088
+ field,
1089
+ parentType: config.type,
1090
+ via
1091
+ });
1092
+ }
1093
+ }
1094
+ }
1095
+ getTypeConfig(type) {
1096
+ const config = this.types.get(type);
1097
+ if (!config) throw new Error(`fate: Unknown entity type '${type}'.`);
1098
+ return config;
1099
+ }
1100
+ async executeMutation(key, input, select, options = {}) {
1101
+ if (!this.transport.mutate) throw new Error(`fate: transport does not support mutations. Please provide a 'mutate' implementation in your transport.`);
1102
+ const baseRecord = input && typeof input === "object" ? input : void 0;
1103
+ const inputArgs = baseRecord && typeof baseRecord.args === "object" ? baseRecord.args : void 0;
1104
+ const argsPayload = combineArgsPayload(options.plan ? resolvedArgsFromPlan(options.plan) : void 0, combineArgsPayload(inputArgs, options.args));
1105
+ const requestInput = argsPayload && baseRecord ? {
1106
+ ...baseRecord,
1107
+ args: argsPayload
1108
+ } : argsPayload ? { args: argsPayload } : input;
1109
+ return await this.transport.mutate(key, requestInput, select);
1110
+ }
1111
+ write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask) {
1112
+ return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask);
1113
+ }
1114
+ deleteRecord(type, id$1, snapshots, listSnapshots) {
1115
+ const entityId = toEntityId(type, id$1);
1116
+ if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
1117
+ this.viewDataCache.invalidate(entityId);
1118
+ this.store.deleteRecord(entityId);
1119
+ this.store.removeReferencesTo(entityId, this.viewDataCache, snapshots, listSnapshots);
1120
+ }
1121
+ restore(id$1, snapshot) {
1122
+ this.viewDataCache.invalidate(id$1);
1123
+ this.store.restore(id$1, snapshot);
1124
+ }
1125
+ restoreList(name, list) {
1126
+ this.store.restoreList(name, list);
1127
+ }
1128
+ ref(type, id$1, view$1) {
1129
+ return createRef(type, id$1, view$1);
1130
+ }
1131
+ rootListRef(entityId, rootView) {
1132
+ const { id: id$1, type } = parseEntityId(entityId);
1133
+ return createRef(type, id$1, rootView, { root: true });
1134
+ }
1135
+ readView(view$1, ref) {
1136
+ const id$1 = ref.id;
1137
+ const type = ref.__typename;
1138
+ if (id$1 == null) {
1139
+ const received = Object.keys(ref).length > 0 ? `'${JSON.stringify(ref)}'` : "an empty object";
1140
+ throw new Error(`fate: Invalid view reference. Expected 'id' to be provided as part of the reference, received ${received}. Did you forget to spread the correct view into its parent or pass the wrong ref to 'useView'?`);
1141
+ }
1142
+ if (type == null) throw new Error(`fate: Invalid view reference. Expected '__typename' to be provided as part of the reference, received '${JSON.stringify(ref)}'.`);
1143
+ const entityId = toEntityId(type, id$1);
1144
+ const viewNames = getViewNames(view$1);
1145
+ const refViews = ref[ViewsTag];
1146
+ if (!refViews || ![...viewNames].every((name) => refViews.has(name))) {
1147
+ const received = refViews ? [...refViews].join(", ") : JSON.stringify(ref);
1148
+ throw new Error(`fate: Invalid view reference. Expected the provided ref to include the view(s) '${[...viewNames].join("', '")}', received '${received}'. You can fix this issue by spreading the correct view into its parent so fate can create the correct view refs for you.`);
1149
+ }
1150
+ const cached = this.viewDataCache.get(entityId, view$1, ref);
1151
+ if (cached) return cached;
1152
+ const plan = getSelectionPlan(view$1, ref);
1153
+ const selectedPaths = plan.paths;
1154
+ const missing = this.store.missingForSelection(entityId, selectedPaths);
1155
+ const resolveSnapshot = () => {
1156
+ const resolvedView = this.readViewSelection(view$1, ref, entityId, plan);
1157
+ const thenable = {
1158
+ status: "fulfilled",
1159
+ then: (onfulfilled, onrejected) => Promise.resolve(resolvedView).then(onfulfilled, onrejected),
1160
+ value: resolvedView
1161
+ };
1162
+ this.viewDataCache.set(entityId, view$1, ref, thenable, new Set(resolvedView.coverage.map(([id$2]) => id$2)));
1163
+ return thenable;
1164
+ };
1165
+ if (missing.size === 0) {
1166
+ this.clearStalledRequestsForEntity(entityId);
1167
+ return resolveSnapshot();
1168
+ }
1169
+ if (missing.size > 0) {
1170
+ const key = this.pendingKey(entityId, missing);
1171
+ if (this.stalledRequests.has(key)) return resolveSnapshot();
1172
+ const pendingPromise = this.pending.get(key) || null;
1173
+ if (pendingPromise) return pendingPromise;
1174
+ const promise = this.fetchByIdAndNormalize(type, [id$1], missing, plan).finally(() => this.pending.delete(key)).then(() => {
1175
+ if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
1176
+ this.stalledRequests.add(key);
1177
+ return resolveSnapshot();
1178
+ }
1179
+ this.stalledRequests.delete(key);
1180
+ return this.readView(view$1, ref);
1181
+ });
1182
+ this.pending.set(key, promise);
1183
+ return promise;
1184
+ }
1185
+ return resolveSnapshot();
1186
+ }
1187
+ mergeListState(previous, incomingIds, incomingCursors, incomingPagination, options) {
1188
+ const existingIds = previous?.ids ?? [];
1189
+ const existingSet = new Set(existingIds);
1190
+ const isBackward = options.direction === "backward";
1191
+ const mergeIds = () => {
1192
+ if (!previous) return [...incomingIds];
1193
+ if (!options.hasCursorArg) {
1194
+ const incomingSet = new Set(incomingIds);
1195
+ const remaining = existingIds.filter((id$1) => !incomingSet.has(id$1));
1196
+ return isBackward ? [...remaining, ...incomingIds] : [...incomingIds, ...remaining];
1197
+ }
1198
+ const newIds = incomingIds.filter((id$1) => !existingSet.has(id$1));
1199
+ return isBackward ? [...newIds, ...existingIds] : [...existingIds, ...newIds];
1200
+ };
1201
+ const ids = mergeIds();
1202
+ const hasIncomingCursor = incomingCursors.some((cursor) => cursor !== void 0);
1203
+ const cursorMap = /* @__PURE__ */ new Map();
1204
+ if (previous?.cursors) previous.cursors.forEach((cursor, index) => {
1205
+ cursorMap.set(existingIds[index], cursor);
1206
+ });
1207
+ incomingCursors.forEach((cursor, index) => {
1208
+ if (cursor !== void 0) cursorMap.set(incomingIds[index], cursor);
1209
+ });
1210
+ const cursors = hasIncomingCursor || Boolean(previous?.cursors) || options.hasCursorArg ? ids.map((id$1) => cursorMap.get(id$1)) : void 0;
1211
+ const previousPagination = previous?.pagination;
1212
+ const newPagination = incomingPagination;
1213
+ return {
1214
+ cursors,
1215
+ ids,
1216
+ pagination: previousPagination || newPagination ? {
1217
+ hasNext: !!(newPagination?.hasNext ?? previousPagination?.hasNext),
1218
+ hasPrevious: !!(newPagination?.hasPrevious ?? previousPagination?.hasPrevious),
1219
+ nextCursor: newPagination?.nextCursor ?? previousPagination?.nextCursor,
1220
+ previousCursor: newPagination?.previousCursor ?? previousPagination?.previousCursor
1221
+ } : void 0
1222
+ };
1223
+ }
1224
+ registerOptimisticUpdate(entityId, select) {
1225
+ if (!entityId || select.size === 0) return null;
1226
+ const mask = fromPaths(select);
1227
+ const token = ++this.optimisticTokenCounter;
1228
+ this.optimisticMasks.set(token, {
1229
+ entityId,
1230
+ mask
1231
+ });
1232
+ let entries = this.optimisticByEntity.get(entityId);
1233
+ if (!entries) {
1234
+ entries = /* @__PURE__ */ new Set();
1235
+ this.optimisticByEntity.set(entityId, entries);
1236
+ }
1237
+ entries.add(token);
1238
+ return token;
1239
+ }
1240
+ clearOptimisticUpdate(token) {
1241
+ if (token == null) return;
1242
+ const entry = this.optimisticMasks.get(token);
1243
+ if (!entry) return;
1244
+ this.optimisticMasks.delete(token);
1245
+ const entityTokens = this.optimisticByEntity.get(entry.entityId);
1246
+ if (entityTokens) {
1247
+ entityTokens.delete(token);
1248
+ if (entityTokens.size === 0) this.optimisticByEntity.delete(entry.entityId);
1249
+ }
1250
+ }
1251
+ getPendingOptimisticMask(entityId, options = {}) {
1252
+ if (!entityId) return null;
1253
+ const tokens = this.optimisticByEntity.get(entityId);
1254
+ if (!tokens || tokens.size === 0) return null;
1255
+ let mask = null;
1256
+ for (const token of tokens) {
1257
+ if (options.excludeToken != null && token === options.excludeToken) continue;
1258
+ const entry = this.optimisticMasks.get(token);
1259
+ if (!entry) continue;
1260
+ if (!mask) mask = cloneMask(entry.mask);
1261
+ else union(mask, entry.mask);
1262
+ }
1263
+ return mask;
1264
+ }
1265
+ filterSelectionForPendingOptimistics(entityId, select, options = {}) {
1266
+ if (!entityId || select.size === 0) return select;
1267
+ const pendingMask = this.getPendingOptimisticMask(entityId, options);
1268
+ if (!pendingMask) return select;
1269
+ const filtered = /* @__PURE__ */ new Set();
1270
+ for (const path of select) if (!isCovered(pendingMask, path)) filtered.add(path);
1271
+ return filtered;
1272
+ }
1273
+ async loadConnection(view$1, connection, args, options = {}) {
1274
+ const direction = options.direction ?? "forward";
1275
+ if (connection.root) {
1276
+ if (!this.transport.fetchList) throw new Error(`fate: transport does not support list fetching. Please add support for 'fetchList' in your transport.`);
1277
+ const requestArgs$1 = {
1278
+ ...connection.args,
1279
+ ...args
1280
+ };
1281
+ const { argsPayload: argsPayload$1, plan: plan$1 } = this.resolveListSelection(view$1, requestArgs$1);
1282
+ const { items, pagination } = await this.transport.fetchList(connection.field, plan$1.paths, argsPayload$1);
1283
+ if (!items) return this.store.getListState(connection.key);
1284
+ const incomingIds$1 = [];
1285
+ const incomingCursors$1 = [];
1286
+ for (const entry of items) {
1287
+ const id$1 = this.write(connection.type, entry.node, plan$1.paths, void 0, plan$1);
1288
+ incomingIds$1.push(id$1);
1289
+ incomingCursors$1.push(entry.cursor);
1290
+ }
1291
+ const previous$1 = this.store.getListState(connection.key);
1292
+ const argsValue = args;
1293
+ const hasCursorArg = Boolean(argsValue && ("after" in argsValue || "before" in argsValue || "cursor" in argsValue));
1294
+ const isBackward = Boolean(argsValue && (argsValue.before !== void 0 || argsValue.last !== void 0));
1295
+ const nextListState$1 = this.mergeListState(previous$1, incomingIds$1, incomingCursors$1, pagination, {
1296
+ direction: isBackward ? "backward" : "forward",
1297
+ hasCursorArg
1298
+ });
1299
+ this.store.setList(connection.key, nextListState$1);
1300
+ return nextListState$1;
1301
+ }
1302
+ const owner = parseEntityId(connection.owner);
1303
+ const requestArgs = {
1304
+ ...connection.args,
1305
+ ...args
1306
+ };
1307
+ if (requestArgs.id === void 0 && owner.id) requestArgs.id = owner.id;
1308
+ const { argsPayload, plan } = this.resolveListSelection(view$1, requestArgs);
1309
+ const nodeSelection = plan.paths;
1310
+ const scopedArgsPayload = argsPayload ? scopeArgsPayload(argsPayload, connection.field) : void 0;
1311
+ const parentSelection = /* @__PURE__ */ new Set();
1312
+ for (const path of nodeSelection) parentSelection.add(`${connection.field}.${path}`);
1313
+ const [parentRecord] = await this.transport.fetchById(owner.type, [owner.id], parentSelection, scopedArgsPayload);
1314
+ if (!parentRecord || typeof parentRecord !== "object") return this.store.getListState(connection.key);
1315
+ const list = parentRecord[connection.field];
1316
+ const connectionPayload = Array.isArray(list) ? {
1317
+ items: list.map((item) => ({
1318
+ cursor: void 0,
1319
+ node: item
1320
+ })),
1321
+ pagination: void 0
1322
+ } : list;
1323
+ if (!connectionPayload) return this.store.getListState(connection.key);
1324
+ const incomingIds = [];
1325
+ const incomingCursors = [];
1326
+ const fieldConfig = this.getTypeConfig(owner.type).fields?.[connection.field];
1327
+ const nodeType = fieldConfig && (fieldConfig === "scalar" ? null : "listOf" in fieldConfig ? fieldConfig.listOf : null) || null;
1328
+ if (!nodeType) throw new Error(`fate: Could not find node type for '${owner.type}.${connection.field}'.`);
1329
+ for (const entry of connectionPayload.items) {
1330
+ const { node } = entry;
1331
+ const id$1 = this.write(nodeType, node, nodeSelection, void 0, plan);
1332
+ incomingIds.push(id$1);
1333
+ incomingCursors.push(entry.cursor);
1334
+ }
1335
+ const previous = this.store.getListState(connection.key);
1336
+ const previousIds = previous?.ids ?? [];
1337
+ const previousSet = new Set(previousIds);
1338
+ const newIds = incomingIds.filter((id$1) => !previousSet.has(id$1));
1339
+ const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, connectionPayload.pagination, {
1340
+ direction,
1341
+ hasCursorArg: true
1342
+ });
1343
+ this.store.setList(connection.key, nextListState);
1344
+ const current = this.store.read(connection.owner);
1345
+ const existingField = Array.isArray(current?.[connection.field]) ? current?.[connection.field] || [] : [];
1346
+ const nodeRefs = newIds.map((id$1) => createNodeRef(id$1));
1347
+ const nextField = direction === "forward" ? [...existingField, ...nodeRefs] : [...nodeRefs, ...existingField];
1348
+ this.viewDataCache.invalidate(connection.owner);
1349
+ this.store.merge(connection.owner, { [connection.field]: nextField }, [connection.field]);
1350
+ return this.store.getListState(connection.key);
1351
+ }
1352
+ request(request, options) {
1353
+ const mode = options?.mode ?? "cache-first";
1354
+ const requestKey = getRequestCacheKey(request);
1355
+ const existingRequest = this.requests.get(requestKey)?.get(mode);
1356
+ if (existingRequest) return existingRequest;
1357
+ let promise;
1358
+ switch (mode) {
1359
+ case "stale-while-revalidate":
1360
+ promise = this.handleStoreAndNetworkRequest(request);
1361
+ break;
1362
+ case "cache-first":
1363
+ case "network-only":
1364
+ default:
1365
+ promise = this.executeRequest(request, mode === "network-only" ? { fetchAll: true } : void 0).then(() => this.getRequestResult(request));
1366
+ break;
1367
+ }
1368
+ let requests = this.requests.get(requestKey);
1369
+ if (!requests) {
1370
+ requests = /* @__PURE__ */ new Map();
1371
+ this.requests.set(requestKey, requests);
1372
+ }
1373
+ requests.set(mode, promise);
1374
+ return promise;
1375
+ }
1376
+ releaseRequest(request, mode) {
1377
+ const requestKey = getRequestCacheKey(request);
1378
+ const requests = this.requests.get(requestKey);
1379
+ if (!requests) return;
1380
+ requests.delete(mode);
1381
+ if (requests.size === 0) this.requests.delete(requestKey);
1382
+ }
1383
+ async handleStoreAndNetworkRequest(request) {
1384
+ if (!this.hasRequestData(request)) {
1385
+ await this.executeRequest(request, { fetchAll: true });
1386
+ return this.getRequestResult(request);
1387
+ }
1388
+ const result = this.getRequestResult(request);
1389
+ this.executeRequest(request, { fetchAll: true }).catch(() => {});
1390
+ return result;
1391
+ }
1392
+ async executeRequest(request, options = {}) {
1393
+ const fetchAll = options.fetchAll ?? false;
1394
+ const groups = /* @__PURE__ */ new Map();
1395
+ const promises = [];
1396
+ for (const [name, item] of Object.entries(request)) {
1397
+ const isNode = isNodeItem(item);
1398
+ if (isNode || isNodesItem(item)) {
1399
+ const plan = getSelectionPlan(item.root, null);
1400
+ const fields = plan.paths;
1401
+ const fieldsSignature = [...fields].slice().sort().join(",");
1402
+ const argsSignature = [...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join(",");
1403
+ const groupKey = `${item.type}#${fieldsSignature}|${argsSignature}`;
1404
+ let group = groups.get(groupKey);
1405
+ if (!group) {
1406
+ group = {
1407
+ fields,
1408
+ ids: [],
1409
+ plan,
1410
+ type: item.type
1411
+ };
1412
+ groups.set(groupKey, group);
1413
+ }
1414
+ for (const raw of isNode ? [item.id] : item.ids) {
1415
+ const entityId = toEntityId(item.type, raw);
1416
+ const missing = this.store.missingForSelection(entityId, fields);
1417
+ if (fetchAll || missing.size > 0) group.ids.push(raw);
1418
+ }
1419
+ } else promises.push(this.fetchListAndNormalize(name, item));
1420
+ }
1421
+ await Promise.all([...promises, ...Array.from(groups.values()).map((group) => group.ids.length ? this.fetchByIdAndNormalize(group.type, group.ids, group.fields, group.plan) : Promise.resolve())]);
1422
+ }
1423
+ hasRequestData(request) {
1424
+ for (const [name, item] of Object.entries(request)) {
1425
+ const isNode = isNodeItem(item);
1426
+ if (isNode || isNodesItem(item)) {
1427
+ const fields = getSelectionPlan(item.root, null).paths;
1428
+ for (const raw of isNode ? [item.id] : item.ids) {
1429
+ const entityId = toEntityId(item.type, raw);
1430
+ if (this.store.missingForSelection(entityId, fields).size > 0) return false;
1431
+ }
1432
+ continue;
1433
+ }
1434
+ if (!this.store.getList(name)) return false;
1435
+ }
1436
+ return true;
1437
+ }
1438
+ getRequestResult(request) {
1439
+ const result = {};
1440
+ for (const [name, item] of Object.entries(request)) {
1441
+ if (isNodeItem(item)) {
1442
+ result[name] = this.ref(item.type, item.id, item.root);
1443
+ continue;
1444
+ }
1445
+ if (isNodesItem(item)) {
1446
+ result[name] = item.ids.map((id$1) => this.ref(item.type, id$1, item.root));
1447
+ continue;
1448
+ }
1449
+ const listState = this.store.getListState(name);
1450
+ const nodeView = item.root && typeof item.root === "object" && "items" in item.root && item.root.items ? item.root.items.node ?? item.root : item.root;
1451
+ const nodes = (listState?.ids ?? []).map((id$1) => this.rootListRef(id$1, nodeView));
1452
+ if (item.root && typeof item.root === "object" && "items" in item.root) {
1453
+ const connection = {
1454
+ items: nodes.map((node, index) => ({
1455
+ cursor: listState?.cursors?.[index],
1456
+ node
1457
+ })),
1458
+ pagination: listState?.pagination
1459
+ };
1460
+ const { argsPayload } = this.resolveListSelection(item.root, item.args);
1461
+ const metadata = {
1462
+ args: argsPayload,
1463
+ field: name,
1464
+ key: name,
1465
+ owner: name,
1466
+ procedure: `request.${name}`,
1467
+ root: true,
1468
+ type: item.type
1469
+ };
1470
+ Object.defineProperty(connection, ConnectionTag, {
1471
+ configurable: false,
1472
+ enumerable: false,
1473
+ value: metadata,
1474
+ writable: false
1475
+ });
1476
+ result[name] = connection;
1477
+ continue;
1478
+ }
1479
+ result[name] = nodes;
1480
+ }
1481
+ return result;
1482
+ }
1483
+ async fetchByIdAndNormalize(type, ids, select, plan, prefix = null) {
1484
+ const resolvedArgs = resolvedArgsFromPlan(plan);
1485
+ const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
1486
+ for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
1487
+ }
1488
+ async fetchListAndNormalize(name, item) {
1489
+ if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${name}'.`);
1490
+ const { argsPayload, plan } = this.resolveListSelection(item.root, item.args);
1491
+ const { items, pagination } = await this.transport.fetchList(name, plan.paths, argsPayload);
1492
+ const ids = [];
1493
+ const cursors = [];
1494
+ for (const entry of items) {
1495
+ const id$1 = this.writeEntity(item.type, entry.node, plan.paths, void 0, plan);
1496
+ ids.push(id$1);
1497
+ cursors.push(entry.cursor);
1498
+ }
1499
+ this.store.setList(name, {
1500
+ cursors,
1501
+ ids,
1502
+ pagination
1503
+ });
1504
+ }
1505
+ resolveListSelection(view$1, args) {
1506
+ const plan = getSelectionPlan(view$1, null);
1507
+ const argsPayload = combineArgsPayload(args, resolvedArgsFromPlan(plan));
1508
+ if (argsPayload) applyArgsPayloadToPlan(plan, argsPayload);
1509
+ return {
1510
+ argsPayload,
1511
+ plan
1512
+ };
1513
+ }
1514
+ writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask) {
1515
+ const config = this.types.get(type);
1516
+ if (!config) throw new Error(`fate: Found unknown entity type '${type}' in normalization.`);
1517
+ const entityId = toEntityId(type, config.getId(record));
1518
+ const result = {};
1519
+ const selectionTree = groupSelectionByPrefix(select);
1520
+ if (config.fields) for (const [key, relationDescriptor] of Object.entries(config.fields)) {
1521
+ const value = record[key];
1522
+ const fieldPath = pathPrefix ? `${pathPrefix}.${key}` : key;
1523
+ const fieldArgs = plan?.args.get(fieldPath);
1524
+ const isFieldBlocked = blockedMask ? isCovered(blockedMask, fieldPath) : false;
1525
+ if (relationDescriptor === "scalar") {
1526
+ if (isFieldBlocked) continue;
1527
+ result[key] = value;
1528
+ } else if (relationDescriptor && typeof relationDescriptor === "object" && "type" in relationDescriptor) {
1529
+ if (isFieldBlocked) continue;
1530
+ const childPaths = selectionTree.get(key) ?? emptySet;
1531
+ if (value && typeof value === "object" && !isNodeRef(value)) {
1532
+ const childType = relationDescriptor.type;
1533
+ const childConfig = this.types.get(childType);
1534
+ if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
1535
+ result[key] = createNodeRef(toEntityId(childType, childConfig.getId(value)));
1536
+ this.writeEntity(childType, value, childPaths, snapshots, plan, fieldPath, blockedMask);
1537
+ }
1538
+ } else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
1539
+ if (isFieldBlocked) continue;
1540
+ const childPaths = selectionTree.get(key) ?? emptySet;
1541
+ const childType = relationDescriptor.listOf;
1542
+ const childConfig = this.types.get(childType);
1543
+ if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
1544
+ const connection = (() => {
1545
+ if (Array.isArray(value)) return { items: value.map((item) => ({ node: item })) };
1546
+ if (value && typeof value === "object") {
1547
+ const record$1 = value;
1548
+ if (Array.isArray(record$1.items)) return {
1549
+ items: record$1.items.map((node) => {
1550
+ if (node && typeof node === "object" && "node" in node) {
1551
+ const itemRecord = node;
1552
+ return {
1553
+ cursor: itemRecord.cursor,
1554
+ node: itemRecord.node
1555
+ };
1556
+ }
1557
+ return { node };
1558
+ }),
1559
+ pagination: record$1.pagination
1560
+ };
1561
+ }
1562
+ return null;
1563
+ })();
1564
+ if (connection) {
1565
+ const ids = [];
1566
+ const cursors = [];
1567
+ const nodeSelection = childPaths.size > 0 ? /* @__PURE__ */ new Set() : childPaths;
1568
+ if (childPaths.size > 0) for (const path of childPaths) {
1569
+ if (path.startsWith("items.node.")) {
1570
+ nodeSelection.add(path.slice(11));
1571
+ continue;
1572
+ }
1573
+ if (path.startsWith("node.")) {
1574
+ nodeSelection.add(path.slice(5));
1575
+ continue;
1576
+ }
1577
+ if (path === "items.node" || path.startsWith("items.")) continue;
1578
+ nodeSelection.add(path);
1579
+ }
1580
+ for (const entry of connection.items) {
1581
+ const node = entry.node;
1582
+ const cursor = "cursor" in entry ? entry.cursor : void 0;
1583
+ cursors.push(cursor);
1584
+ if (isNodeRef(node)) {
1585
+ ids.push(getNodeRefId(node));
1586
+ continue;
1587
+ }
1588
+ if (node && typeof node === "object") {
1589
+ const childId = toEntityId(childType, childConfig.getId(node));
1590
+ this.writeEntity(childType, node, nodeSelection, snapshots, plan, fieldPath, blockedMask);
1591
+ ids.push(childId);
1592
+ continue;
1593
+ }
1594
+ }
1595
+ const listKey = getListKey(entityId, key, fieldArgs?.hash);
1596
+ const previousList = this.store.getListState(listKey);
1597
+ const argsValue = fieldArgs?.value;
1598
+ const hasCursorArg = Boolean(argsValue && ("after" in argsValue || "before" in argsValue || "cursor" in argsValue));
1599
+ const isBackward = Boolean(argsValue && (argsValue.before !== void 0 || argsValue.last !== void 0));
1600
+ const nextListState = this.mergeListState(previousList, ids, cursors, connection.pagination, {
1601
+ direction: isBackward ? "backward" : "forward",
1602
+ hasCursorArg
1603
+ });
1604
+ result[key] = nextListState.ids.map((id$1) => createNodeRef(id$1));
1605
+ this.store.setList(listKey, nextListState);
1606
+ }
1607
+ } else result[key] = value;
1608
+ }
1609
+ for (const [key, value] of Object.entries(record)) if (!(key in (config.fields ?? {}))) {
1610
+ const fieldPath = pathPrefix ? `${pathPrefix}.${key}` : key;
1611
+ if (blockedMask && isCovered(blockedMask, fieldPath)) continue;
1612
+ result[key] = value;
1613
+ }
1614
+ if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
1615
+ this.viewDataCache.invalidate(entityId);
1616
+ this.store.merge(entityId, result, select);
1617
+ this.linkParentLists(type, entityId, result, snapshots);
1618
+ return entityId;
1619
+ }
1620
+ linkParentLists(type, entityId, record, snapshots) {
1621
+ const parents = this.parentLists.get(type);
1622
+ if (!parents) return;
1623
+ for (const parent of parents) {
1624
+ if (!parent.via) continue;
1625
+ const parentRef = record[parent.via];
1626
+ const parentId = isNodeRef(parentRef) ? getNodeRefId(parentRef) : null;
1627
+ if (!parentId) continue;
1628
+ const existing = this.store.read(parentId);
1629
+ if (!existing) continue;
1630
+ const current = Array.isArray(existing[parent.field]) ? existing[parent.field] : [];
1631
+ if (current.some((item) => isNodeRef(item) && getNodeRefId(item) === entityId)) continue;
1632
+ if (snapshots && !snapshots.has(parentId)) snapshots.set(parentId, this.store.snapshot(parentId));
1633
+ this.viewDataCache.invalidate(parentId);
1634
+ const nextList = [...current, createNodeRef(entityId)];
1635
+ const ids = nextList.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id$1) => id$1 != null);
1636
+ const defaultListKey = getListKey(parentId, parent.field);
1637
+ const defaultListState = this.store.getListState(defaultListKey);
1638
+ const nextDefaultCursors = defaultListState?.cursors && ids.length > defaultListState.cursors.length ? [...defaultListState.cursors, ...new Array(ids.length - defaultListState.cursors.length).fill(void 0)] : defaultListState?.cursors;
1639
+ this.store.setList(defaultListKey, {
1640
+ cursors: nextDefaultCursors,
1641
+ ids,
1642
+ pagination: defaultListState?.pagination
1643
+ });
1644
+ const registeredLists = this.store.getListsForField(parentId, parent.field).filter(([key]) => key !== defaultListKey);
1645
+ for (const [listKey, listState] of registeredLists) {
1646
+ if (listState.ids.includes(entityId)) continue;
1647
+ const listIds = [...listState.ids, entityId];
1648
+ const listCursors = listState.cursors ? [...listState.cursors, void 0] : void 0;
1649
+ this.store.setList(listKey, {
1650
+ cursors: listCursors,
1651
+ ids: listIds,
1652
+ pagination: listState.pagination
1653
+ });
1654
+ }
1655
+ this.store.merge(parentId, { [parent.field]: nextList }, [parent.field]);
1656
+ }
1657
+ }
1658
+ readViewSelection(viewComposition, ref, entityId, plan, pathPrefix = null) {
1659
+ const record = this.store.read(entityId) || { id: entityId };
1660
+ const ids = /* @__PURE__ */ new Set();
1661
+ ids.add(entityId);
1662
+ const coverageById = /* @__PURE__ */ new Map();
1663
+ const walk = (viewPayload, record$1, target, parentId, prefix) => {
1664
+ for (const [key, selectionKind] of Object.entries(viewPayload)) {
1665
+ if (isViewTag(key)) {
1666
+ if (!target[ViewsTag]) assignViewTag(target, /* @__PURE__ */ new Set());
1667
+ target[ViewsTag].add(key);
1668
+ continue;
1669
+ }
1670
+ coverageById.set(parentId, (coverageById.get(parentId) ?? /* @__PURE__ */ new Set()).add(key));
1671
+ const fieldPath = prefix ? `${prefix}.${key}` : key;
1672
+ const selectionType = typeof selectionKind;
1673
+ if (selectionType === "boolean" && selectionKind) target[key] = record$1[key];
1674
+ else if (selectionKind && selectionType === "object") {
1675
+ const selectionValue = selectionKind;
1676
+ const { args: selectionArgs, ...selectionWithoutArgs } = selectionValue;
1677
+ if (Boolean(selectionArgs) && typeof selectionArgs === "object" && Object.keys(selectionWithoutArgs).length === 0) {
1678
+ target[key] = record$1[key];
1679
+ continue;
1680
+ }
1681
+ if (!(key in target)) target[key] = {};
1682
+ const nextSelection = Object.keys(selectionWithoutArgs).length ? selectionWithoutArgs : selectionValue;
1683
+ const value = record$1[key];
1684
+ if (Array.isArray(value)) if (nextSelection.items && typeof nextSelection.items === "object") {
1685
+ const selection = nextSelection.items;
1686
+ const fieldArgs = plan.args.get(fieldPath);
1687
+ const listKey = getListKey(parentId, key, fieldArgs?.hash);
1688
+ const listState = this.store.getListState(listKey);
1689
+ const connection = { items: value.map((item, index) => {
1690
+ const entityId$1 = isNodeRef(item) ? getNodeRefId(item) : null;
1691
+ if (!entityId$1) return {
1692
+ cursor: listState?.cursors?.[index],
1693
+ node: null
1694
+ };
1695
+ ids.add(entityId$1);
1696
+ const record$2 = this.store.read(entityId$1);
1697
+ const { id: id$1, type } = parseEntityId(entityId$1);
1698
+ const node = {
1699
+ __typename: type,
1700
+ id: id$1
1701
+ };
1702
+ if (record$2) walk(selection.node, record$2, node, entityId$1, fieldPath);
1703
+ const entry = { node: record$2 ? node : null };
1704
+ if (selection.cursor === true) entry.cursor = listState?.cursors?.[index];
1705
+ return entry;
1706
+ }) };
1707
+ if ("pagination" in nextSelection && nextSelection.pagination) {
1708
+ const paginationSelection = nextSelection.pagination;
1709
+ const storedPagination = listState?.pagination;
1710
+ if (storedPagination) {
1711
+ const pagination = {};
1712
+ if (paginationSelection.nextCursor === true) {
1713
+ if (storedPagination.nextCursor !== void 0) pagination.nextCursor = storedPagination.nextCursor;
1714
+ }
1715
+ if (paginationSelection.previousCursor === true) {
1716
+ if (storedPagination.previousCursor !== void 0) pagination.previousCursor = storedPagination.previousCursor;
1717
+ }
1718
+ if (paginationSelection.hasNext === true) pagination.hasNext = storedPagination.hasNext;
1719
+ if (paginationSelection.hasPrevious === true) pagination.hasPrevious = storedPagination.hasPrevious;
1720
+ if (Object.keys(pagination).length > 0) connection.pagination = pagination;
1721
+ } else connection.pagination = void 0;
1722
+ }
1723
+ const { id: ownerRawId, type: parentType } = parseEntityId(parentId);
1724
+ const childType = (() => {
1725
+ for (const item of value) if (isNodeRef(item)) return parseEntityId(getNodeRefId(item)).type;
1726
+ return "";
1727
+ })();
1728
+ if (parentType) {
1729
+ const metadata = {
1730
+ args: (() => {
1731
+ if (!fieldArgs?.value && ownerRawId === void 0) return;
1732
+ const value$1 = fieldArgs?.value ? { ...fieldArgs.value } : {};
1733
+ if (ownerRawId !== void 0) value$1.id = ownerRawId;
1734
+ return value$1;
1735
+ })(),
1736
+ field: key,
1737
+ hash: fieldArgs?.hash,
1738
+ key: listKey,
1739
+ owner: parentId,
1740
+ procedure: `${parentType}.${key}`,
1741
+ root: false,
1742
+ type: childType
1743
+ };
1744
+ Object.defineProperty(connection, ConnectionTag, {
1745
+ configurable: false,
1746
+ enumerable: false,
1747
+ value: metadata,
1748
+ writable: false
1749
+ });
1750
+ }
1751
+ target[key] = connection;
1752
+ } else target[key] = value.map((item) => {
1753
+ const entityId$1 = isNodeRef(item) ? getNodeRefId(item) : null;
1754
+ if (!entityId$1) return item;
1755
+ ids.add(entityId$1);
1756
+ const record$2 = this.store.read(entityId$1);
1757
+ const { id: id$1, type } = parseEntityId(entityId$1);
1758
+ if (record$2) {
1759
+ const node = {
1760
+ __typename: type,
1761
+ id: id$1
1762
+ };
1763
+ walk(nextSelection, record$2, node, entityId$1, fieldPath);
1764
+ return node;
1765
+ }
1766
+ return null;
1767
+ });
1768
+ else if (isNodeRef(value)) {
1769
+ const entityId$1 = getNodeRefId(value);
1770
+ ids.add(entityId$1);
1771
+ const relatedRecord = this.store.read(entityId$1);
1772
+ const { id: id$1, type } = parseEntityId(entityId$1);
1773
+ const targetRecord = target[key];
1774
+ targetRecord.id = id$1;
1775
+ targetRecord.__typename = type;
1776
+ if (relatedRecord) walk(nextSelection, relatedRecord, targetRecord, entityId$1, fieldPath);
1777
+ } else walk(nextSelection, record$1, target[key], entityId, fieldPath);
1778
+ }
1779
+ }
1780
+ };
1781
+ const data = { __typename: parseEntityId(entityId).type };
1782
+ for (const viewPayload of getViewPayloads(viewComposition, ref)) walk(viewPayload.select, record, data, entityId, pathPrefix);
1783
+ return {
1784
+ coverage: [...coverageById.entries()],
1785
+ data
1786
+ };
1787
+ }
1788
+ clearStalledRequestsForEntity(entityId) {
1789
+ const prefix = this.pendingPrefix(entityId);
1790
+ for (const key of this.stalledRequests) if (key.startsWith(prefix)) this.stalledRequests.delete(key);
1791
+ }
1792
+ pendingPrefix(entityId) {
1793
+ return `__fate__|${entityId}|`;
1794
+ }
1795
+ pendingKey(entityId, missingFields) {
1796
+ return `${this.pendingPrefix(entityId)}${[...missingFields].sort().join("|")}`;
1797
+ }
1798
+ };
1799
+ function createClient(options) {
1800
+ return new FateClient(options);
1801
+ }
1802
+
1803
+ //#endregion
1804
+ //#region src/transport.ts
1805
+ /**
1806
+ * Builds a `Transport` backed by a tRPC client using the configured resolvers
1807
+ * for by-id queries, lists, and mutations.
1808
+ */
1809
+ function createTRPCTransport({ byId, client, lists, mutations }) {
1810
+ const transport = {
1811
+ async fetchById(type, ids, select, args) {
1812
+ const resolver = byId[type];
1813
+ if (!resolver) throw new Error(`fate(trpc): No 'byId' resolver configured for entity type '${type}'.`);
1814
+ return await resolver(client)({
1815
+ args,
1816
+ ids,
1817
+ select: [...select]
1818
+ });
1819
+ },
1820
+ async fetchList(procedure, select, args) {
1821
+ if (!lists) throw new Error(`fate(trpc): No list resolvers configured; cannot call "${procedure}".`);
1822
+ const resolver = lists[procedure];
1823
+ if (!resolver) throw new Error(`fate(trpc): Missing list resolver for procedure "${procedure}"`);
1824
+ return resolver(client)({
1825
+ args,
1826
+ select: [...select]
1827
+ });
1828
+ }
1829
+ };
1830
+ transport.mutate = async (procedure, input, select) => {
1831
+ const resolver = mutations?.[procedure];
1832
+ if (!resolver) throw new Error(`fate(trpc): Missing mutation resolver for procedure '${procedure}'.`);
1833
+ return await resolver(client)({
1834
+ ...input,
1835
+ select: [...select]
1836
+ });
1837
+ };
1838
+ return transport;
1839
+ }
1840
+
1841
+ //#endregion
1842
+ export { ConnectionTag, FateClient, createClient, createTRPCTransport, getSelectionPlan, isViewTag, mutation, toEntityId, view };