@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/LICENSE +21 -0
- package/README.md +995 -0
- package/lib/cli.d.mts +1 -0
- package/lib/cli.mjs +195 -0
- package/lib/index.d.mts +585 -0
- package/lib/index.mjs +1842 -0
- package/lib/record-DnhZuvUe.mjs +5 -0
- package/lib/server.d.mts +163 -0
- package/lib/server.mjs +439 -0
- package/package.json +55 -0
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 };
|