@nkzw/fate 0.1.2 → 1.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +810 -129
- package/docs/api/classes/FateClient.md +832 -0
- package/docs/api/functions/clientRoot.md +27 -0
- package/docs/api/functions/createClient.md +21 -0
- package/docs/api/functions/createHTTPTransport.md +51 -0
- package/docs/api/functions/createTRPCTransport.md +46 -0
- package/docs/api/functions/getListEntries.md +15 -0
- package/docs/api/functions/getSelectionPlan.md +36 -0
- package/docs/api/functions/isRecord.md +15 -0
- package/docs/api/functions/isViewTag.md +17 -0
- package/docs/api/functions/liveConnectionTopic.md +19 -0
- package/docs/api/functions/liveEntityTopic.md +19 -0
- package/docs/api/functions/liveGlobalConnectionTopic.md +15 -0
- package/docs/api/functions/mutation.md +32 -0
- package/docs/api/functions/toEntityId.md +21 -0
- package/docs/api/functions/view.md +26 -0
- package/docs/api/index.md +81 -0
- package/docs/api/interfaces/FateThenable.md +31 -0
- package/docs/api/interfaces/Transport.md +199 -0
- package/docs/api/server/classes/FateRequestError.md +67 -0
- package/docs/api/server/drizzle/functions/createDrizzleFate.md +431 -0
- package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +21 -0
- package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +21 -0
- package/docs/api/server/drizzle/index.md +23 -0
- package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +15 -0
- package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +5 -0
- package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +21 -0
- package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +337 -0
- package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +29 -0
- package/docs/api/server/functions/bindSourceProcedures.md +32 -0
- package/docs/api/server/functions/computed.md +43 -0
- package/docs/api/server/functions/count.md +21 -0
- package/docs/api/server/functions/createFateFetchHandler.md +21 -0
- package/docs/api/server/functions/createFateServer.md +41 -0
- package/docs/api/server/functions/createHonoFateHandler.md +21 -0
- package/docs/api/server/functions/createLiveEventBus.md +17 -0
- package/docs/api/server/functions/createNestedSourcePlan.md +103 -0
- package/docs/api/server/functions/createResolver.md +74 -0
- package/docs/api/server/functions/createSourcePlan.md +95 -0
- package/docs/api/server/functions/createSourceProcedures.md +46 -0
- package/docs/api/server/functions/dataView.md +32 -0
- package/docs/api/server/functions/field.md +15 -0
- package/docs/api/server/functions/getNestedSelection.md +19 -0
- package/docs/api/server/functions/getScopedArgs.md +21 -0
- package/docs/api/server/functions/hasNestedSelection.md +19 -0
- package/docs/api/server/functions/list.md +28 -0
- package/docs/api/server/functions/refetchSourceById.md +55 -0
- package/docs/api/server/functions/resolveSourceById.md +55 -0
- package/docs/api/server/functions/resolveSourceByIds.md +55 -0
- package/docs/api/server/functions/resolveSourceConnection.md +67 -0
- package/docs/api/server/functions/resolver.md +42 -0
- package/docs/api/server/functions/toPrismaSelect.md +25 -0
- package/docs/api/server/functions/withConnection.md +24 -0
- package/docs/api/server/index.md +109 -0
- package/docs/api/server/prisma/functions/createPrismaFate.md +435 -0
- package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +21 -0
- package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +21 -0
- package/docs/api/server/prisma/functions/prismaConnectionArgs.md +57 -0
- package/docs/api/server/prisma/index.md +23 -0
- package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +59 -0
- package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +5 -0
- package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +343 -0
- package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +31 -0
- package/docs/api/server/type-aliases/ComputedField.md +55 -0
- package/docs/api/server/type-aliases/ComputedSelection.md +5 -0
- package/docs/api/server/type-aliases/ConnectionItem.md +29 -0
- package/docs/api/server/type-aliases/ConnectionPagination.md +39 -0
- package/docs/api/server/type-aliases/ConnectionResult.md +29 -0
- package/docs/api/server/type-aliases/CountSelection.md +29 -0
- package/docs/api/server/type-aliases/CountWhere.md +5 -0
- package/docs/api/server/type-aliases/DataViewListOptions.md +13 -0
- package/docs/api/server/type-aliases/DataViewOrderBy.md +5 -0
- package/docs/api/server/type-aliases/DataViewOrderDirection.md +5 -0
- package/docs/api/server/type-aliases/DataViewResult.md +11 -0
- package/docs/api/server/type-aliases/Entity.md +21 -0
- package/docs/api/server/type-aliases/FateServer.md +75 -0
- package/docs/api/server/type-aliases/FateServerManifest.md +5 -0
- package/docs/api/server/type-aliases/FieldSelection.md +21 -0
- package/docs/api/server/type-aliases/LiveConnectionEventType.md +5 -0
- package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +5 -0
- package/docs/api/server/type-aliases/LiveEventBus.md +5 -0
- package/docs/api/server/type-aliases/LiveEventType.md +5 -0
- package/docs/api/server/type-aliases/LiveSourceEvent.md +5 -0
- package/docs/api/server/type-aliases/NativeFateAPI.md +47 -0
- package/docs/api/server/type-aliases/OrderDirection.md +5 -0
- package/docs/api/server/type-aliases/SourceConfig.md +55 -0
- package/docs/api/server/type-aliases/SourceDefinition.md +55 -0
- package/docs/api/server/type-aliases/SourceOrder.md +5 -0
- package/docs/api/server/type-aliases/SourceOrderField.md +21 -0
- package/docs/api/server/type-aliases/SourcePlan.md +29 -0
- package/docs/api/server/type-aliases/SourcePlanNode.md +29 -0
- package/docs/api/server/type-aliases/SourceRegistry.md +11 -0
- package/docs/api/server/type-aliases/SourceRelation.md +15 -0
- package/docs/api/server/type-aliases/SourceRelationConfig.md +23 -0
- package/docs/api/server/variables/byIdInput.md +5 -0
- package/docs/api/server/variables/connectionArgs.md +7 -0
- package/docs/api/type-aliases/ConnectionMetadata.md +7 -0
- package/docs/api/type-aliases/ConnectionRef.md +13 -0
- package/docs/api/type-aliases/Entity.md +15 -0
- package/docs/api/type-aliases/EntityId.md +7 -0
- package/docs/api/type-aliases/FateLiveConnectionEvent.md +5 -0
- package/docs/api/type-aliases/FateLiveEvent.md +5 -0
- package/docs/api/type-aliases/FateMutations.md +7 -0
- package/docs/api/type-aliases/FateOperation.md +5 -0
- package/docs/api/type-aliases/FateProtocolRequest.md +5 -0
- package/docs/api/type-aliases/FateProtocolResponse.md +5 -0
- package/docs/api/type-aliases/FateRecord.md +7 -0
- package/docs/api/type-aliases/FateRoots.md +7 -0
- package/docs/api/type-aliases/InferFateAPI.md +11 -0
- package/docs/api/type-aliases/List.md +5 -0
- package/docs/api/type-aliases/ListEntry.md +5 -0
- package/docs/api/type-aliases/ListItem.md +13 -0
- package/docs/api/type-aliases/Mask.md +15 -0
- package/docs/api/type-aliases/MutationDefinition.md +21 -0
- package/docs/api/type-aliases/MutationEntity.md +13 -0
- package/docs/api/type-aliases/MutationIdentifier.md +19 -0
- package/docs/api/type-aliases/MutationInput.md +13 -0
- package/docs/api/type-aliases/MutationResult.md +13 -0
- package/docs/api/type-aliases/NodesItem.md +13 -0
- package/docs/api/type-aliases/Pagination.md +39 -0
- package/docs/api/type-aliases/Request.md +7 -0
- package/docs/api/type-aliases/RequestMode.md +7 -0
- package/docs/api/type-aliases/RequestOptions.md +7 -0
- package/docs/api/type-aliases/RequestResult.md +18 -0
- package/docs/api/type-aliases/Selection.md +13 -0
- package/docs/api/type-aliases/Snapshot.md +7 -0
- package/docs/api/type-aliases/TypeConfig.md +41 -0
- package/docs/api/type-aliases/View.md +17 -0
- package/docs/api/type-aliases/ViewData.md +18 -0
- package/docs/api/type-aliases/ViewEntity.md +13 -0
- package/docs/api/type-aliases/ViewEntityName.md +13 -0
- package/docs/api/type-aliases/ViewRef.md +13 -0
- package/docs/api/type-aliases/ViewSelection.md +13 -0
- package/docs/api/type-aliases/ViewSnapshot.md +18 -0
- package/docs/api/type-aliases/ViewTag.md +7 -0
- package/docs/api/variables/ConnectionTag.md +7 -0
- package/docs/guide/actions.md +263 -0
- package/docs/guide/core-concepts.md +22 -0
- package/docs/guide/getting-started.md +57 -0
- package/docs/guide/list-views.md +86 -0
- package/docs/guide/live-views.md +245 -0
- package/docs/guide/requests.md +130 -0
- package/docs/guide/server-integration.md +630 -0
- package/docs/guide/views.md +278 -0
- package/docs/guide/void-integration.md +167 -0
- package/docs/index.md +4 -0
- package/lib/cli.mjs +17 -250
- package/lib/clientStub.d.mts +6 -0
- package/lib/clientStub.mjs +6 -0
- package/lib/executor-BqwHdN3n.d.mts +468 -0
- package/lib/index.d.mts +64 -3
- package/lib/index.mjs +1965 -559
- package/lib/list-4wRNSVgI.d.mts +10 -0
- package/lib/list-C_mi6GnE.mjs +16 -0
- package/lib/list.d.mts +2 -0
- package/lib/list.mjs +2 -0
- package/lib/liveTopics-DzNtJaBD.mjs +197 -0
- package/lib/{record-DnhZuvUe.mjs → record-AeZJC9fd.mjs} +1 -2
- package/lib/record-ENh92gyb.d.mts +315 -0
- package/lib/server/drizzle.d.mts +304 -0
- package/lib/server/drizzle.mjs +688 -0
- package/lib/server/prisma.d.mts +309 -0
- package/lib/server/prisma.mjs +345 -0
- package/lib/server.d.mts +82 -184
- package/lib/server.mjs +870 -416
- package/lib/sourceRouter-Bwpkr062.mjs +972 -0
- package/lib/{types-Djlh7mMH.d.mts → types-Dz46PXr3.d.mts} +327 -197
- package/lib/vite.d.mts +17 -0
- package/lib/vite.mjs +592 -0
- package/package.json +44 -6
package/lib/index.mjs
CHANGED
|
@@ -1,130 +1,6 @@
|
|
|
1
|
-
import { t as isRecord } from "./record-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
1
|
+
import { t as isRecord } from "./record-AeZJC9fd.mjs";
|
|
2
|
+
import { a as errorCodeFromStatus, c as cloneArgs, d as hashArgs, f as paginationArgKeys, i as FateRequestError, l as combineArgsPayload, m as scopeArgsPayload, n as liveEntityTopic, p as resolvedArgsFromPlan, r as liveGlobalConnectionTopic, s as applyArgsPayloadToPlan, t as liveConnectionTopic, u as filterConnectionArgs } from "./liveTopics-DzNtJaBD.mjs";
|
|
3
|
+
import { t as getListEntries } from "./list-C_mi6GnE.mjs";
|
|
128
4
|
//#region src/cache.ts
|
|
129
5
|
var ViewDataCache = class {
|
|
130
6
|
constructor() {
|
|
@@ -132,19 +8,19 @@ var ViewDataCache = class {
|
|
|
132
8
|
this.rootDependencies = /* @__PURE__ */ new Map();
|
|
133
9
|
this.dependencyIndex = /* @__PURE__ */ new Map();
|
|
134
10
|
}
|
|
135
|
-
get(entityId, view
|
|
136
|
-
return this.cache.get(entityId)?.get(view
|
|
11
|
+
get(entityId, view, ref) {
|
|
12
|
+
return this.cache.get(entityId)?.get(view)?.get(ref) ?? null;
|
|
137
13
|
}
|
|
138
|
-
set(entityId, view
|
|
14
|
+
set(entityId, view, ref, thenable, dependencies) {
|
|
139
15
|
let entityMap = this.cache.get(entityId);
|
|
140
16
|
if (!entityMap) {
|
|
141
17
|
entityMap = /* @__PURE__ */ new WeakMap();
|
|
142
18
|
this.cache.set(entityId, entityMap);
|
|
143
19
|
}
|
|
144
|
-
let viewMap = entityMap.get(view
|
|
20
|
+
let viewMap = entityMap.get(view);
|
|
145
21
|
if (!viewMap) {
|
|
146
22
|
viewMap = /* @__PURE__ */ new WeakMap();
|
|
147
|
-
entityMap.set(view
|
|
23
|
+
entityMap.set(view, viewMap);
|
|
148
24
|
}
|
|
149
25
|
viewMap.set(ref, thenable);
|
|
150
26
|
let roots = this.rootDependencies.get(entityId);
|
|
@@ -186,7 +62,6 @@ var ViewDataCache = class {
|
|
|
186
62
|
this.cache.delete(entityId);
|
|
187
63
|
}
|
|
188
64
|
};
|
|
189
|
-
|
|
190
65
|
//#endregion
|
|
191
66
|
//#region src/mask.ts
|
|
192
67
|
function emptyMask() {
|
|
@@ -262,9 +137,8 @@ function intersects(a, b) {
|
|
|
262
137
|
}
|
|
263
138
|
return false;
|
|
264
139
|
}
|
|
265
|
-
|
|
266
140
|
//#endregion
|
|
267
|
-
//#region ../../node_modules/.pnpm/@trpc+server@11.
|
|
141
|
+
//#region ../../node_modules/.pnpm/@trpc+server@11.17.0_typescript@5.9.3/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
|
|
268
142
|
var __create = Object.create;
|
|
269
143
|
var __defProp = Object.defineProperty;
|
|
270
144
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -364,7 +238,7 @@ var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project
|
|
|
364
238
|
}
|
|
365
239
|
module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
366
240
|
} });
|
|
367
|
-
|
|
241
|
+
__toESM(__commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/objectSpread2.js"(exports, module) {
|
|
368
242
|
var defineProperty = require_defineProperty();
|
|
369
243
|
function ownKeys(e, r) {
|
|
370
244
|
var t = Object.keys(e);
|
|
@@ -388,9 +262,7 @@ var require_objectSpread2 = __commonJS({ "../../node_modules/.pnpm/@oxc-project+
|
|
|
388
262
|
return e;
|
|
389
263
|
}
|
|
390
264
|
module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
391
|
-
} });
|
|
392
|
-
var import_objectSpread2 = __toESM(require_objectSpread2(), 1);
|
|
393
|
-
|
|
265
|
+
} })(), 1);
|
|
394
266
|
//#endregion
|
|
395
267
|
//#region src/types.ts
|
|
396
268
|
/** Internal marker added to objects that represent a view payload. */
|
|
@@ -403,8 +275,8 @@ const NodeRefTag = Symbol("__fate__node-ref");
|
|
|
403
275
|
const ConnectionTag = Symbol("__fate__connection");
|
|
404
276
|
const viewTag = "__fate-view__";
|
|
405
277
|
/** Generates a stable view tag for a view definition. */
|
|
406
|
-
function getViewTag(id
|
|
407
|
-
return `${viewTag}${id
|
|
278
|
+
function getViewTag(id) {
|
|
279
|
+
return `${viewTag}${id}`;
|
|
408
280
|
}
|
|
409
281
|
/** Determines whether a property key is a fate view tag. */
|
|
410
282
|
function isViewTag(key) {
|
|
@@ -426,23 +298,22 @@ function isQueryItem(item) {
|
|
|
426
298
|
const RootKind = "__fate__root";
|
|
427
299
|
/** Brand used on mutation definitions to mark their identity in the d.ts output. */
|
|
428
300
|
const MutationKind = "__fate__mutation";
|
|
429
|
-
|
|
430
301
|
//#endregion
|
|
431
302
|
//#region src/view.ts
|
|
432
303
|
/**
|
|
433
304
|
* Collects all view payloads that apply to the given ref.
|
|
434
305
|
*/
|
|
435
|
-
const getViewPayloads = (view
|
|
306
|
+
const getViewPayloads = (view, ref) => {
|
|
436
307
|
const result = [];
|
|
437
|
-
for (const [key, value] of Object.entries(view
|
|
308
|
+
for (const [key, value] of Object.entries(view)) if (isViewTag(key) && (!ref || ref[ViewsTag]?.has(key))) result.push(value);
|
|
438
309
|
return result;
|
|
439
310
|
};
|
|
440
311
|
/**
|
|
441
312
|
* Returns the set of view tags defined on a view composition.
|
|
442
313
|
*/
|
|
443
|
-
const getViewNames = (view
|
|
314
|
+
const getViewNames = (view) => {
|
|
444
315
|
const result = /* @__PURE__ */ new Set();
|
|
445
|
-
for (const key of Object.keys(view
|
|
316
|
+
for (const key of Object.keys(view)) if (isViewTag(key)) result.add(key);
|
|
446
317
|
return result;
|
|
447
318
|
};
|
|
448
319
|
/**
|
|
@@ -452,7 +323,8 @@ const getSelectionViewNames = (selection) => {
|
|
|
452
323
|
return getViewNames(selection);
|
|
453
324
|
};
|
|
454
325
|
let id = 0;
|
|
455
|
-
const
|
|
326
|
+
const importMetaEnvironment = import.meta.env;
|
|
327
|
+
const isDevelopment = typeof importMetaEnvironment?.DEV === "boolean" ? importMetaEnvironment.DEV : typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : false;
|
|
456
328
|
let viewModulePath = null;
|
|
457
329
|
const getStableId = () => {
|
|
458
330
|
if (isDevelopment) try {
|
|
@@ -495,7 +367,6 @@ function view() {
|
|
|
495
367
|
return Object.freeze(viewComposition);
|
|
496
368
|
};
|
|
497
369
|
}
|
|
498
|
-
|
|
499
370
|
//#endregion
|
|
500
371
|
//#region src/ref.ts
|
|
501
372
|
/**
|
|
@@ -505,14 +376,14 @@ const toEntityId = (type, rawId) => `${type}:${String(rawId)}`;
|
|
|
505
376
|
/**
|
|
506
377
|
* Splits a cache entity ID back into its type and raw identifier.
|
|
507
378
|
*/
|
|
508
|
-
function parseEntityId(id
|
|
509
|
-
const idx = id
|
|
379
|
+
function parseEntityId(id) {
|
|
380
|
+
const idx = id.indexOf(":");
|
|
510
381
|
return idx < 0 ? {
|
|
511
|
-
id
|
|
382
|
+
id,
|
|
512
383
|
type: ""
|
|
513
384
|
} : {
|
|
514
|
-
id: id
|
|
515
|
-
type: id
|
|
385
|
+
id: id.slice(idx + 1),
|
|
386
|
+
type: id.slice(0, idx)
|
|
516
387
|
};
|
|
517
388
|
}
|
|
518
389
|
/**
|
|
@@ -526,32 +397,22 @@ function assignViewTag(target, value) {
|
|
|
526
397
|
writable: false
|
|
527
398
|
});
|
|
528
399
|
}
|
|
529
|
-
const getRootViewNames = (view
|
|
530
|
-
const names = new Set(getViewNames(view
|
|
531
|
-
const payloads = getViewPayloads(view
|
|
400
|
+
const getRootViewNames = (view) => {
|
|
401
|
+
const names = new Set(getViewNames(view));
|
|
402
|
+
const payloads = getViewPayloads(view, null);
|
|
532
403
|
for (const payload of payloads) for (const name of getSelectionViewNames(payload.select)) names.add(name);
|
|
533
404
|
return names;
|
|
534
405
|
};
|
|
535
|
-
|
|
536
|
-
* Creates an immutable `ViewRef` for an entity, tagging it with all views from
|
|
537
|
-
* the provided composition so `useView` can resolve the ref against a view.
|
|
538
|
-
*/
|
|
539
|
-
function createRef(__typename, id$1, view$1, options) {
|
|
406
|
+
function createRefWithViewNames(__typename, id, names) {
|
|
540
407
|
const ref = {
|
|
541
408
|
__typename,
|
|
542
|
-
id
|
|
409
|
+
id
|
|
543
410
|
};
|
|
544
|
-
assignViewTag(ref,
|
|
411
|
+
assignViewTag(ref, names);
|
|
545
412
|
return Object.freeze(ref);
|
|
546
413
|
}
|
|
547
|
-
|
|
548
414
|
//#endregion
|
|
549
415
|
//#region src/selection.ts
|
|
550
|
-
const paginationKeys = new Set([
|
|
551
|
-
"after",
|
|
552
|
-
"before",
|
|
553
|
-
"cursor"
|
|
554
|
-
]);
|
|
555
416
|
const isConnectionSelection = (value) => isRecord(value.items) && "node" in value.items;
|
|
556
417
|
/**
|
|
557
418
|
* Flattens a view into a `SelectionPlan`, expanding composed views and
|
|
@@ -559,6 +420,7 @@ const isConnectionSelection = (value) => isRecord(value.items) && "node" in valu
|
|
|
559
420
|
*/
|
|
560
421
|
const getSelectionPlan = (viewComposition, ref) => {
|
|
561
422
|
const args = /* @__PURE__ */ new Map();
|
|
423
|
+
const live = /* @__PURE__ */ new Map();
|
|
562
424
|
const paths = /* @__PURE__ */ new Set();
|
|
563
425
|
const assignArgs = (path, value, ignoreKeys) => {
|
|
564
426
|
const hash = hashArgs(value, { ignoreKeys });
|
|
@@ -570,8 +432,12 @@ const getSelectionPlan = (viewComposition, ref) => {
|
|
|
570
432
|
};
|
|
571
433
|
const walk = (selection, prefix, context = "default") => {
|
|
572
434
|
if (prefix === null && context !== "connection" && isConnectionSelection(selection)) {
|
|
573
|
-
if (selection.
|
|
574
|
-
|
|
435
|
+
if (selection.live && isRecord(selection.live)) live.set("", {
|
|
436
|
+
append: selection.live.append === "visible" ? "visible" : "edge",
|
|
437
|
+
prepend: selection.live.prepend === "visible" ? "visible" : "edge"
|
|
438
|
+
});
|
|
439
|
+
if (selection.args && isRecord(selection.args)) assignArgs("", cloneArgs(selection.args, "args"), isRecord(selection.items) && isRecord(selection.items.node) ? paginationArgKeys : void 0);
|
|
440
|
+
const { args: _args, live: _live, ...withoutArgs } = selection;
|
|
575
441
|
walk(withoutArgs, prefix, "connection");
|
|
576
442
|
return;
|
|
577
443
|
}
|
|
@@ -579,7 +445,7 @@ const getSelectionPlan = (viewComposition, ref) => {
|
|
|
579
445
|
const valueType = typeof value;
|
|
580
446
|
const path = prefix ? `${prefix}.${key}` : key;
|
|
581
447
|
if (context === "connection") {
|
|
582
|
-
if (key === "args" || key === "pagination") continue;
|
|
448
|
+
if (key === "args" || key === "live" || key === "pagination") continue;
|
|
583
449
|
if (key === "items" && isRecord(value)) {
|
|
584
450
|
if (isRecord(value.node)) walk(value.node, prefix);
|
|
585
451
|
continue;
|
|
@@ -596,16 +462,20 @@ const getSelectionPlan = (viewComposition, ref) => {
|
|
|
596
462
|
if (isRecord(value)) {
|
|
597
463
|
const selectionObject = value;
|
|
598
464
|
if (isConnectionSelection(selectionObject)) {
|
|
599
|
-
if (selectionObject.
|
|
600
|
-
|
|
465
|
+
if (selectionObject.live && isRecord(selectionObject.live)) live.set(path, {
|
|
466
|
+
append: selectionObject.live.append === "visible" ? "visible" : "edge",
|
|
467
|
+
prepend: selectionObject.live.prepend === "visible" ? "visible" : "edge"
|
|
468
|
+
});
|
|
469
|
+
if (selectionObject.args && isRecord(selectionObject.args)) assignArgs(path, cloneArgs(selectionObject.args, path), isRecord(selectionObject.items) && isRecord(selectionObject.items.node) ? paginationArgKeys : void 0);
|
|
470
|
+
const { args: _ignored, live: _live, ...withoutArgs } = selectionObject;
|
|
601
471
|
walk(withoutArgs, path, "connection");
|
|
602
472
|
continue;
|
|
603
473
|
}
|
|
604
474
|
const hasArgs = selectionObject.args && isRecord(selectionObject.args);
|
|
605
475
|
let selectionWithoutArgs = selectionObject;
|
|
606
476
|
if (hasArgs) {
|
|
607
|
-
assignArgs(path, cloneArgs(selectionObject.args, path), isRecord(selectionObject.items) && isRecord(selectionObject.items?.node) ?
|
|
608
|
-
const { args:
|
|
477
|
+
assignArgs(path, cloneArgs(selectionObject.args, path), isRecord(selectionObject.items) && isRecord(selectionObject.items?.node) ? paginationArgKeys : void 0);
|
|
478
|
+
const { args: _args, ...rest } = selectionObject;
|
|
609
479
|
selectionWithoutArgs = rest;
|
|
610
480
|
}
|
|
611
481
|
if (Object.keys(selectionWithoutArgs).length > 0) walk(selectionWithoutArgs, path);
|
|
@@ -618,10 +488,10 @@ const getSelectionPlan = (viewComposition, ref) => {
|
|
|
618
488
|
else for (const payload of payloads) walk(payload.select, null);
|
|
619
489
|
return {
|
|
620
490
|
args,
|
|
491
|
+
live,
|
|
621
492
|
paths
|
|
622
493
|
};
|
|
623
494
|
};
|
|
624
|
-
|
|
625
495
|
//#endregion
|
|
626
496
|
//#region src/mutation.ts
|
|
627
497
|
/**
|
|
@@ -655,9 +525,9 @@ const collectImplicitSelectedPaths = (value) => {
|
|
|
655
525
|
walk(value, null);
|
|
656
526
|
return paths;
|
|
657
527
|
};
|
|
658
|
-
const maybeGetId = (getId
|
|
528
|
+
const maybeGetId = (getId, input) => {
|
|
659
529
|
try {
|
|
660
|
-
return getId
|
|
530
|
+
return getId(input);
|
|
661
531
|
} catch {
|
|
662
532
|
return null;
|
|
663
533
|
}
|
|
@@ -669,38 +539,36 @@ const emptySet$1 = /* @__PURE__ */ new Set();
|
|
|
669
539
|
*/
|
|
670
540
|
function wrapMutation(client, identifier) {
|
|
671
541
|
const config = client.getTypeConfig(identifier.entity);
|
|
672
|
-
return async ({ args, delete: deleteRecord, input, insert = "after", optimistic, view
|
|
673
|
-
const id
|
|
674
|
-
const plan = view
|
|
542
|
+
return async ({ args, delete: deleteRecord, input, insert = "after", optimistic, view }) => {
|
|
543
|
+
const id = maybeGetId(config.getId, input);
|
|
544
|
+
const plan = view ? getSelectionPlan(view, null) : void 0;
|
|
675
545
|
const viewSelection = plan?.paths;
|
|
676
|
-
const optimisticRecord = optimistic ? id
|
|
677
|
-
id
|
|
546
|
+
const optimisticRecord = optimistic ? id != null ? {
|
|
547
|
+
id,
|
|
678
548
|
...optimistic
|
|
679
549
|
} : optimistic : void 0;
|
|
680
550
|
const optimisticRecordId = optimisticRecord !== void 0 ? maybeGetId(config.getId, optimisticRecord) : null;
|
|
681
|
-
const optimisticEntityId = id
|
|
551
|
+
const optimisticEntityId = id != null ? toEntityId(identifier.entity, id) : optimisticRecordId != null ? toEntityId(identifier.entity, optimisticRecordId) : null;
|
|
682
552
|
const snapshots = /* @__PURE__ */ new Map();
|
|
683
|
-
const listSnapshots = deleteRecord ? /* @__PURE__ */ new Map() : void 0;
|
|
553
|
+
const listSnapshots = deleteRecord || optimisticRecord ? /* @__PURE__ */ new Map() : void 0;
|
|
684
554
|
const optimisticSelection = optimisticRecord ? collectImplicitSelectedPaths(optimisticRecord) : void 0;
|
|
685
555
|
const selection = viewSelection || optimisticSelection ? new Set([...viewSelection ? [...viewSelection] : [], ...optimisticSelection ? [...optimisticSelection] : []]) : /* @__PURE__ */ new Set();
|
|
556
|
+
if (deleteRecord && id == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
|
|
686
557
|
const optimisticToken = optimisticEntityId ? client.registerOptimisticUpdate(optimisticEntityId, optimisticSelection ?? emptySet$1) : null;
|
|
687
|
-
if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan, null, null, insert);
|
|
688
|
-
if (deleteRecord)
|
|
689
|
-
if (id$1 == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
|
|
690
|
-
client.deleteRecord(identifier.entity, id$1, snapshots, listSnapshots);
|
|
691
|
-
}
|
|
558
|
+
if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, snapshots, plan, null, null, insert, listSnapshots);
|
|
559
|
+
if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id, snapshots, listSnapshots);
|
|
692
560
|
const performMutation = async () => {
|
|
693
561
|
try {
|
|
694
562
|
const result = await client.executeMutation(identifier.key, input, selection, {
|
|
695
563
|
args,
|
|
696
564
|
plan
|
|
697
565
|
});
|
|
698
|
-
if (result && typeof result === "object" && (!deleteRecord || Boolean(view
|
|
566
|
+
if (result && typeof result === "object" && (!deleteRecord || Boolean(view))) {
|
|
699
567
|
const select = collectImplicitSelectedPaths(result);
|
|
700
568
|
const pendingMask = optimisticEntityId ? client.getPendingOptimisticMask(optimisticEntityId, { excludeToken: optimisticToken }) : null;
|
|
701
569
|
const filteredSelection = optimisticEntityId ? client.filterSelectionForPendingOptimistics(optimisticEntityId, select, { excludeToken: optimisticToken }) : select;
|
|
702
570
|
client.write(identifier.entity, result, filteredSelection, void 0, plan, null, pendingMask, insert);
|
|
703
|
-
if (deleteRecord && id
|
|
571
|
+
if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id);
|
|
704
572
|
const resultId = maybeGetId(config.getId, result);
|
|
705
573
|
if (optimisticEntityId && resultId != null) client.resolveOptimisticEntity(optimisticEntityId, toEntityId(identifier.entity, resultId));
|
|
706
574
|
if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(identifier.entity, optimisticRecordId);
|
|
@@ -711,11 +579,11 @@ function wrapMutation(client, identifier) {
|
|
|
711
579
|
};
|
|
712
580
|
} catch (error) {
|
|
713
581
|
client.clearOptimisticUpdate(optimisticToken);
|
|
714
|
-
if (snapshots.size > 0) for (const [id
|
|
582
|
+
if (snapshots.size > 0) for (const [id, snapshot] of snapshots) client.restore(id, snapshot);
|
|
715
583
|
if (listSnapshots && listSnapshots.size > 0) for (const [name, list] of listSnapshots) client.restoreList(name, list);
|
|
716
584
|
if (error instanceof Error) {
|
|
717
|
-
const
|
|
718
|
-
if ((
|
|
585
|
+
const statusCode = getErrorStatusCode(error);
|
|
586
|
+
if ((statusCode ? categorizeHTTPErrorStatus(statusCode) : "boundary") === "boundary") throw error;
|
|
719
587
|
return {
|
|
720
588
|
error,
|
|
721
589
|
result: void 0
|
|
@@ -730,7 +598,13 @@ function wrapMutation(client, identifier) {
|
|
|
730
598
|
return mutationPromise;
|
|
731
599
|
};
|
|
732
600
|
}
|
|
733
|
-
|
|
601
|
+
const getErrorStatusCode = (error) => {
|
|
602
|
+
if (error instanceof FateRequestError) return error.status;
|
|
603
|
+
const { data } = error;
|
|
604
|
+
if (!data || typeof data !== "object") return;
|
|
605
|
+
return getHTTPStatusCodeFromError(data);
|
|
606
|
+
};
|
|
607
|
+
const categorizeHTTPErrorStatus = (statusCode) => {
|
|
734
608
|
switch (statusCode) {
|
|
735
609
|
case 400:
|
|
736
610
|
case 402:
|
|
@@ -743,27 +617,17 @@ function categorizeTRPCError(statusCode) {
|
|
|
743
617
|
case 422:
|
|
744
618
|
case 429:
|
|
745
619
|
case 499: return "callSite";
|
|
746
|
-
case 401:
|
|
747
|
-
case 403:
|
|
748
|
-
case 405:
|
|
749
|
-
case 428:
|
|
750
|
-
case 500:
|
|
751
|
-
case 501:
|
|
752
|
-
case 502:
|
|
753
|
-
case 503:
|
|
754
|
-
case 504:
|
|
755
620
|
default: return "boundary";
|
|
756
621
|
}
|
|
757
|
-
}
|
|
758
|
-
|
|
622
|
+
};
|
|
759
623
|
//#endregion
|
|
760
624
|
//#region src/node-ref.ts
|
|
761
|
-
function createNodeRef(id
|
|
625
|
+
function createNodeRef(id) {
|
|
762
626
|
const ref = {};
|
|
763
627
|
Object.defineProperty(ref, NodeRefTag, {
|
|
764
628
|
configurable: false,
|
|
765
629
|
enumerable: false,
|
|
766
|
-
value: id
|
|
630
|
+
value: id,
|
|
767
631
|
writable: false
|
|
768
632
|
});
|
|
769
633
|
return Object.freeze(ref);
|
|
@@ -774,10 +638,84 @@ function isNodeRef(value) {
|
|
|
774
638
|
function getNodeRefId(ref) {
|
|
775
639
|
return ref[NodeRefTag];
|
|
776
640
|
}
|
|
777
|
-
|
|
641
|
+
//#endregion
|
|
642
|
+
//#region src/operation-lifetime.ts
|
|
643
|
+
var OperationLifetime = class {
|
|
644
|
+
constructor(releaseBufferSize) {
|
|
645
|
+
this.releaseBufferSize = releaseBufferSize;
|
|
646
|
+
this.operations = /* @__PURE__ */ new Map();
|
|
647
|
+
this.releaseBuffer = [];
|
|
648
|
+
}
|
|
649
|
+
retain(descriptor, onDispose) {
|
|
650
|
+
let operation = this.operations.get(descriptor.key);
|
|
651
|
+
if (!operation) {
|
|
652
|
+
operation = {
|
|
653
|
+
descriptor,
|
|
654
|
+
refCount: 0
|
|
655
|
+
};
|
|
656
|
+
this.operations.set(descriptor.key, operation);
|
|
657
|
+
}
|
|
658
|
+
operation.refCount += 1;
|
|
659
|
+
this.removeFromReleaseBuffer(descriptor.key);
|
|
660
|
+
let disposed = false;
|
|
661
|
+
return { dispose: () => {
|
|
662
|
+
if (disposed) return;
|
|
663
|
+
disposed = true;
|
|
664
|
+
if (this.release(descriptor.key)) onDispose?.();
|
|
665
|
+
} };
|
|
666
|
+
}
|
|
667
|
+
getDescriptors() {
|
|
668
|
+
return [...this.operations.values()].map((operation) => operation.descriptor);
|
|
669
|
+
}
|
|
670
|
+
release(key) {
|
|
671
|
+
const operation = this.operations.get(key);
|
|
672
|
+
if (!operation) return false;
|
|
673
|
+
operation.refCount -= 1;
|
|
674
|
+
if (operation.refCount > 0) return false;
|
|
675
|
+
if (this.releaseBufferSize <= 0) {
|
|
676
|
+
this.operations.delete(key);
|
|
677
|
+
return true;
|
|
678
|
+
}
|
|
679
|
+
this.releaseBuffer.push(key);
|
|
680
|
+
let released = false;
|
|
681
|
+
while (this.releaseBuffer.length > this.releaseBufferSize) {
|
|
682
|
+
const releasedKey = this.releaseBuffer.shift();
|
|
683
|
+
if (!releasedKey) continue;
|
|
684
|
+
const releasedOperation = this.operations.get(releasedKey);
|
|
685
|
+
if (releasedOperation && releasedOperation.refCount <= 0) {
|
|
686
|
+
this.operations.delete(releasedKey);
|
|
687
|
+
released = true;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return released;
|
|
691
|
+
}
|
|
692
|
+
removeFromReleaseBuffer(key) {
|
|
693
|
+
const index = this.releaseBuffer.indexOf(key);
|
|
694
|
+
if (index !== -1) this.releaseBuffer.splice(index, 1);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
778
697
|
//#endregion
|
|
779
698
|
//#region src/store.ts
|
|
780
|
-
const
|
|
699
|
+
const listKeySeparator = " __fate__ ";
|
|
700
|
+
const decodeListKeyPart = (part) => {
|
|
701
|
+
try {
|
|
702
|
+
return decodeURIComponent(part);
|
|
703
|
+
} catch {
|
|
704
|
+
return part;
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
const encodeListKeyPart = (part) => encodeURIComponent(part);
|
|
708
|
+
const getListKey = (ownerId, field, hash = "default") => `${encodeListKeyPart(ownerId)}${listKeySeparator}${encodeListKeyPart(field)}${listKeySeparator}${encodeListKeyPart(hash)}`;
|
|
709
|
+
const getOwnerFieldKey = (ownerId, field) => JSON.stringify([ownerId, field]);
|
|
710
|
+
const parseListKey = (key) => {
|
|
711
|
+
const parts = key.split(listKeySeparator);
|
|
712
|
+
if (parts.length !== 3) return null;
|
|
713
|
+
const [ownerId, field] = parts;
|
|
714
|
+
return {
|
|
715
|
+
field: decodeListKeyPart(field),
|
|
716
|
+
ownerId: decodeListKeyPart(ownerId)
|
|
717
|
+
};
|
|
718
|
+
};
|
|
781
719
|
const cloneValue = (value) => {
|
|
782
720
|
if (Array.isArray(value)) return value.map(cloneValue);
|
|
783
721
|
if (isNodeRef(value)) return value;
|
|
@@ -792,25 +730,29 @@ const emptyFunction = () => {};
|
|
|
792
730
|
var Store = class {
|
|
793
731
|
constructor() {
|
|
794
732
|
this.coverage = /* @__PURE__ */ new Map();
|
|
733
|
+
this.listKeysByOwnerField = /* @__PURE__ */ new Map();
|
|
734
|
+
this.listKeysByReferencedEntity = /* @__PURE__ */ new Map();
|
|
795
735
|
this.lists = /* @__PURE__ */ new Map();
|
|
736
|
+
this.recordReferenceFields = /* @__PURE__ */ new Map();
|
|
737
|
+
this.recordReferencesByTarget = /* @__PURE__ */ new Map();
|
|
796
738
|
this.records = /* @__PURE__ */ new Map();
|
|
797
739
|
this.subscriptions = /* @__PURE__ */ new Map();
|
|
798
740
|
this.listSubscriptions = /* @__PURE__ */ new Map();
|
|
799
741
|
}
|
|
800
|
-
read(id
|
|
801
|
-
return this.records.get(id
|
|
742
|
+
read(id) {
|
|
743
|
+
return this.records.get(id);
|
|
802
744
|
}
|
|
803
|
-
merge(id
|
|
804
|
-
const changedPaths = this.mergeInternal(id
|
|
805
|
-
if (changedPaths) this.notify(id
|
|
745
|
+
merge(id, partial, paths) {
|
|
746
|
+
const changedPaths = this.mergeInternal(id, partial, paths);
|
|
747
|
+
if (changedPaths) this.notify(id, changedPaths);
|
|
806
748
|
}
|
|
807
|
-
mergeInternal(id
|
|
808
|
-
const previous = this.records.get(id
|
|
749
|
+
mergeInternal(id, partial, paths) {
|
|
750
|
+
const previous = this.records.get(id);
|
|
809
751
|
const changedPaths = /* @__PURE__ */ new Set();
|
|
810
|
-
let mask = this.coverage.get(id
|
|
752
|
+
let mask = this.coverage.get(id);
|
|
811
753
|
if (!mask) {
|
|
812
754
|
mask = emptyMask();
|
|
813
|
-
this.coverage.set(id
|
|
755
|
+
this.coverage.set(id, mask);
|
|
814
756
|
}
|
|
815
757
|
union(mask, fromPaths(paths));
|
|
816
758
|
if (previous) {
|
|
@@ -820,25 +762,33 @@ var Store = class {
|
|
|
820
762
|
changedPaths.add(key);
|
|
821
763
|
}
|
|
822
764
|
if (!hasChanges) return null;
|
|
823
|
-
|
|
765
|
+
const nextRecord = {
|
|
824
766
|
...previous,
|
|
825
767
|
...partial
|
|
826
|
-
}
|
|
827
|
-
|
|
768
|
+
};
|
|
769
|
+
this.records.set(id, nextRecord);
|
|
770
|
+
this.updateRecordReferenceIndexes(id, previous, nextRecord, changedPaths);
|
|
771
|
+
} else {
|
|
772
|
+
const nextRecord = { ...partial };
|
|
773
|
+
this.records.set(id, nextRecord);
|
|
774
|
+
this.updateRecordReferenceIndexes(id, void 0, nextRecord, Object.keys(nextRecord));
|
|
775
|
+
}
|
|
828
776
|
return changedPaths;
|
|
829
777
|
}
|
|
830
|
-
deleteRecord(id
|
|
831
|
-
this.records.
|
|
832
|
-
this.
|
|
778
|
+
deleteRecord(id) {
|
|
779
|
+
const record = this.records.get(id);
|
|
780
|
+
if (record) this.removeRecordReferenceIndexes(id, record);
|
|
781
|
+
this.records.delete(id);
|
|
782
|
+
this.coverage.delete(id);
|
|
833
783
|
}
|
|
834
|
-
missingForSelection(id
|
|
784
|
+
missingForSelection(id, paths) {
|
|
835
785
|
const requested = new Set(paths);
|
|
836
|
-
if (!this.records.has(id
|
|
837
|
-
const mask = this.coverage.get(id
|
|
786
|
+
if (!this.records.has(id)) return requested;
|
|
787
|
+
const mask = this.coverage.get(id);
|
|
838
788
|
if (!mask) return requested;
|
|
839
789
|
return diffPaths(requested, mask);
|
|
840
790
|
}
|
|
841
|
-
subscribe(id
|
|
791
|
+
subscribe(id, selectionOrFn, callback) {
|
|
842
792
|
let mask = null;
|
|
843
793
|
let fn = emptyFunction;
|
|
844
794
|
if (typeof selectionOrFn === "function") fn = selectionOrFn;
|
|
@@ -846,10 +796,10 @@ var Store = class {
|
|
|
846
796
|
mask = selectionOrFn ? fromPaths(selectionOrFn) : null;
|
|
847
797
|
fn = callback;
|
|
848
798
|
}
|
|
849
|
-
let subscribers = this.subscriptions.get(id
|
|
799
|
+
let subscribers = this.subscriptions.get(id);
|
|
850
800
|
if (!subscribers) {
|
|
851
801
|
subscribers = /* @__PURE__ */ new Set();
|
|
852
|
-
this.subscriptions.set(id
|
|
802
|
+
this.subscriptions.set(id, subscribers);
|
|
853
803
|
}
|
|
854
804
|
const subscription = {
|
|
855
805
|
fn,
|
|
@@ -857,14 +807,14 @@ var Store = class {
|
|
|
857
807
|
};
|
|
858
808
|
subscribers.add(subscription);
|
|
859
809
|
return () => {
|
|
860
|
-
const set = this.subscriptions.get(id
|
|
810
|
+
const set = this.subscriptions.get(id);
|
|
861
811
|
if (!set) return;
|
|
862
812
|
set.delete(subscription);
|
|
863
|
-
if (set.size === 0) this.subscriptions.delete(id
|
|
813
|
+
if (set.size === 0) this.subscriptions.delete(id);
|
|
864
814
|
};
|
|
865
815
|
}
|
|
866
|
-
notify(id
|
|
867
|
-
const set = this.subscriptions.get(id
|
|
816
|
+
notify(id, paths) {
|
|
817
|
+
const set = this.subscriptions.get(id);
|
|
868
818
|
if (!set) return;
|
|
869
819
|
const changedPaths = paths ? [...paths] : [];
|
|
870
820
|
const changedMask = changedPaths.length > 0 ? fromPaths(changedPaths) : null;
|
|
@@ -890,18 +840,110 @@ var Store = class {
|
|
|
890
840
|
}
|
|
891
841
|
getListsForField(ownerId, field) {
|
|
892
842
|
const entries = [];
|
|
893
|
-
const
|
|
894
|
-
|
|
843
|
+
const keys = this.listKeysByOwnerField.get(getOwnerFieldKey(ownerId, field));
|
|
844
|
+
if (!keys) return entries;
|
|
845
|
+
for (const key of keys) {
|
|
846
|
+
const list = this.lists.get(key);
|
|
847
|
+
if (list) entries.push([key, list]);
|
|
848
|
+
}
|
|
895
849
|
return entries;
|
|
896
850
|
}
|
|
897
851
|
setList(key, state) {
|
|
852
|
+
const previous = this.lists.get(key);
|
|
853
|
+
if (previous) this.removeListIndexes(key, previous);
|
|
898
854
|
this.lists.set(key, state);
|
|
855
|
+
this.addListIndexes(key, state);
|
|
899
856
|
this.notifyListSubscribers(key);
|
|
900
857
|
}
|
|
858
|
+
replaceListEntityId(previousId, nextId) {
|
|
859
|
+
const keys = [...this.listKeysByReferencedEntity.get(previousId) ?? []];
|
|
860
|
+
for (const key of keys) {
|
|
861
|
+
const list = this.lists.get(key);
|
|
862
|
+
if (!list) continue;
|
|
863
|
+
let changed = false;
|
|
864
|
+
let ids = list.ids;
|
|
865
|
+
let cursors = list.cursors;
|
|
866
|
+
if (list.ids.includes(previousId)) {
|
|
867
|
+
const nextIds = [];
|
|
868
|
+
const nextCursors = list.cursors ? [] : void 0;
|
|
869
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
870
|
+
list.ids.forEach((id, index) => {
|
|
871
|
+
const resolved = id === previousId ? nextId : id;
|
|
872
|
+
if (seenIds.has(resolved)) return;
|
|
873
|
+
seenIds.add(resolved);
|
|
874
|
+
nextIds.push(resolved);
|
|
875
|
+
if (nextCursors) nextCursors.push(list.cursors?.[index]);
|
|
876
|
+
});
|
|
877
|
+
changed = true;
|
|
878
|
+
ids = nextIds;
|
|
879
|
+
cursors = nextCursors;
|
|
880
|
+
}
|
|
881
|
+
const dedupe = (values) => {
|
|
882
|
+
if (!values || !values.includes(previousId)) return;
|
|
883
|
+
const seen = /* @__PURE__ */ new Set();
|
|
884
|
+
const next = [];
|
|
885
|
+
for (const value of values) {
|
|
886
|
+
const resolved = value === previousId ? nextId : value;
|
|
887
|
+
if (seen.has(resolved)) continue;
|
|
888
|
+
seen.add(resolved);
|
|
889
|
+
next.push(resolved);
|
|
890
|
+
}
|
|
891
|
+
changed = true;
|
|
892
|
+
return next;
|
|
893
|
+
};
|
|
894
|
+
const pendingBeforeIds = dedupe(list.pendingBeforeIds) ?? list.pendingBeforeIds;
|
|
895
|
+
const pendingAfterIds = dedupe(list.pendingAfterIds) ?? list.pendingAfterIds;
|
|
896
|
+
const liveBeforeIds = dedupe(list.liveBeforeIds) ?? list.liveBeforeIds;
|
|
897
|
+
const liveAfterIds = dedupe(list.liveAfterIds) ?? list.liveAfterIds;
|
|
898
|
+
if (!changed) continue;
|
|
899
|
+
const canonicalIds = new Set(ids);
|
|
900
|
+
this.setList(key, {
|
|
901
|
+
backwardPageLimit: list.backwardPageLimit,
|
|
902
|
+
cursors,
|
|
903
|
+
forwardPageLimit: list.forwardPageLimit,
|
|
904
|
+
ids,
|
|
905
|
+
liveAfterIds: liveAfterIds?.filter((id) => !canonicalIds.has(id)),
|
|
906
|
+
liveBeforeIds: liveBeforeIds?.filter((id) => !canonicalIds.has(id)),
|
|
907
|
+
pagination: list.pagination,
|
|
908
|
+
pendingAfterIds: pendingAfterIds?.filter((id) => !canonicalIds.has(id)),
|
|
909
|
+
pendingBeforeIds: pendingBeforeIds?.filter((id) => !canonicalIds.has(id))
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}
|
|
901
913
|
restoreList(key, list) {
|
|
902
|
-
if (list == null) this.
|
|
914
|
+
if (list == null) this.deleteList(key);
|
|
903
915
|
else this.setList(key, list);
|
|
904
916
|
}
|
|
917
|
+
collectGarbage(markedRecords, markedLists, options = {}) {
|
|
918
|
+
const records = /* @__PURE__ */ new Set();
|
|
919
|
+
const lists = /* @__PURE__ */ new Set();
|
|
920
|
+
for (const id of this.records.keys()) {
|
|
921
|
+
if (markedRecords.has(id)) continue;
|
|
922
|
+
records.add(id);
|
|
923
|
+
}
|
|
924
|
+
for (const key of this.lists.keys()) {
|
|
925
|
+
if (markedLists.has(key)) continue;
|
|
926
|
+
lists.add(key);
|
|
927
|
+
}
|
|
928
|
+
for (const id of records) {
|
|
929
|
+
const record = this.records.get(id);
|
|
930
|
+
if (record) this.removeRecordReferenceIndexes(id, record);
|
|
931
|
+
this.records.delete(id);
|
|
932
|
+
this.coverage.delete(id);
|
|
933
|
+
options.onRecordDeleted?.(id);
|
|
934
|
+
}
|
|
935
|
+
for (const key of lists) this.deleteList(key);
|
|
936
|
+
return {
|
|
937
|
+
lists,
|
|
938
|
+
records
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
deleteList(key) {
|
|
942
|
+
const previous = this.lists.get(key);
|
|
943
|
+
if (previous) this.removeListIndexes(key, previous);
|
|
944
|
+
this.lists.delete(key);
|
|
945
|
+
this.notifyListSubscribers(key);
|
|
946
|
+
}
|
|
905
947
|
subscribeList(key, fn) {
|
|
906
948
|
let set = this.listSubscriptions.get(key);
|
|
907
949
|
if (!set) {
|
|
@@ -917,66 +959,373 @@ var Store = class {
|
|
|
917
959
|
};
|
|
918
960
|
}
|
|
919
961
|
removeReferencesTo(targetId, viewDataCache, snapshots, listSnapshots) {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
962
|
+
const listKeys = [...this.listKeysByReferencedEntity.get(targetId) ?? []];
|
|
963
|
+
for (const key of listKeys) {
|
|
964
|
+
const list = this.lists.get(key);
|
|
965
|
+
if (!list) continue;
|
|
966
|
+
const { ids } = list;
|
|
967
|
+
const hasLiveAfter = Boolean(list.liveAfterIds?.includes(targetId));
|
|
968
|
+
const hasLiveBefore = Boolean(list.liveBeforeIds?.includes(targetId));
|
|
969
|
+
const hasPendingAfter = Boolean(list.pendingAfterIds?.includes(targetId));
|
|
970
|
+
const hasPendingBefore = Boolean(list.pendingBeforeIds?.includes(targetId));
|
|
971
|
+
if (!ids.includes(targetId) && !hasLiveAfter && !hasLiveBefore && !hasPendingAfter && !hasPendingBefore) continue;
|
|
923
972
|
if (listSnapshots && !listSnapshots.has(key)) listSnapshots.set(key, list);
|
|
924
973
|
const entityIds = [];
|
|
925
974
|
const cursors = list.cursors ? [] : void 0;
|
|
926
|
-
for (let index = 0; index < ids
|
|
927
|
-
const id
|
|
928
|
-
if (id
|
|
929
|
-
entityIds.push(id
|
|
975
|
+
for (let index = 0; index < ids.length; index++) {
|
|
976
|
+
const id = ids[index];
|
|
977
|
+
if (id === targetId) continue;
|
|
978
|
+
entityIds.push(id);
|
|
930
979
|
if (cursors) cursors.push(list.cursors?.[index]);
|
|
931
980
|
}
|
|
932
981
|
this.setList(key, {
|
|
982
|
+
backwardPageLimit: list.backwardPageLimit,
|
|
933
983
|
cursors,
|
|
984
|
+
forwardPageLimit: list.forwardPageLimit,
|
|
934
985
|
ids: entityIds,
|
|
935
|
-
|
|
986
|
+
liveAfterIds: list.liveAfterIds?.filter((id) => id !== targetId),
|
|
987
|
+
liveBeforeIds: list.liveBeforeIds?.filter((id) => id !== targetId),
|
|
988
|
+
pagination: list.pagination,
|
|
989
|
+
pendingAfterIds: list.pendingAfterIds?.filter((id) => id !== targetId),
|
|
990
|
+
pendingBeforeIds: list.pendingBeforeIds?.filter((id) => id !== targetId)
|
|
936
991
|
});
|
|
937
992
|
}
|
|
938
993
|
const ids = /* @__PURE__ */ new Map();
|
|
939
|
-
|
|
994
|
+
const recordEntries = [...this.recordReferencesByTarget.get(targetId)?.entries() ?? []];
|
|
995
|
+
for (const [id, fields] of recordEntries) {
|
|
996
|
+
const record = this.records.get(id);
|
|
997
|
+
if (!record) continue;
|
|
940
998
|
let updated = false;
|
|
941
999
|
const next = {};
|
|
942
1000
|
const paths = /* @__PURE__ */ new Set();
|
|
943
|
-
for (const
|
|
944
|
-
const
|
|
945
|
-
if (
|
|
1001
|
+
for (const key of fields) {
|
|
1002
|
+
const value = record[key];
|
|
1003
|
+
if (Array.isArray(value)) {
|
|
1004
|
+
const filtered = value.filter((item) => !(isNodeRef(item) && getNodeRefId(item) === targetId));
|
|
1005
|
+
if (filtered.length !== value.length) {
|
|
1006
|
+
updated = true;
|
|
1007
|
+
paths.add(key);
|
|
1008
|
+
next[key] = filtered;
|
|
1009
|
+
}
|
|
1010
|
+
} else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
|
|
946
1011
|
updated = true;
|
|
947
1012
|
paths.add(key);
|
|
948
|
-
next[key] =
|
|
1013
|
+
next[key] = null;
|
|
949
1014
|
}
|
|
950
|
-
} else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
|
|
951
|
-
updated = true;
|
|
952
|
-
paths.add(key);
|
|
953
|
-
next[key] = null;
|
|
954
1015
|
}
|
|
955
1016
|
if (!updated) continue;
|
|
956
|
-
if (snapshots && !snapshots.has(id
|
|
957
|
-
viewDataCache.invalidate(id
|
|
958
|
-
this.mergeInternal(id
|
|
959
|
-
ids.set(id
|
|
1017
|
+
if (snapshots && !snapshots.has(id)) snapshots.set(id, this.snapshot(id));
|
|
1018
|
+
viewDataCache.invalidate(id);
|
|
1019
|
+
this.mergeInternal(id, next, paths);
|
|
1020
|
+
ids.set(id, paths);
|
|
960
1021
|
}
|
|
961
|
-
for (const [id
|
|
1022
|
+
for (const [id, paths] of ids) this.notify(id, paths);
|
|
962
1023
|
}
|
|
963
|
-
snapshot(id
|
|
964
|
-
const record = this.records.get(id
|
|
965
|
-
const mask = this.coverage.get(id
|
|
1024
|
+
snapshot(id) {
|
|
1025
|
+
const record = this.records.get(id);
|
|
1026
|
+
const mask = this.coverage.get(id);
|
|
966
1027
|
return {
|
|
967
1028
|
mask: mask ? cloneMask(mask) : void 0,
|
|
968
1029
|
record: record ? cloneValue(record) : void 0
|
|
969
1030
|
};
|
|
970
1031
|
}
|
|
971
|
-
restore(id
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
if (snapshot.
|
|
975
|
-
else
|
|
976
|
-
|
|
1032
|
+
restore(id, snapshot) {
|
|
1033
|
+
const previous = this.records.get(id);
|
|
1034
|
+
if (previous) this.removeRecordReferenceIndexes(id, previous);
|
|
1035
|
+
if (snapshot.record === void 0) this.records.delete(id);
|
|
1036
|
+
else {
|
|
1037
|
+
this.records.set(id, snapshot.record);
|
|
1038
|
+
this.addRecordReferenceIndexes(id, snapshot.record);
|
|
1039
|
+
}
|
|
1040
|
+
if (snapshot.mask === void 0) this.coverage.delete(id);
|
|
1041
|
+
else this.coverage.set(id, snapshot.mask);
|
|
1042
|
+
this.notify(id);
|
|
1043
|
+
}
|
|
1044
|
+
addListIndexes(key, list) {
|
|
1045
|
+
const parsed = parseListKey(key);
|
|
1046
|
+
if (parsed) {
|
|
1047
|
+
const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
|
|
1048
|
+
let keys = this.listKeysByOwnerField.get(ownerFieldKey);
|
|
1049
|
+
if (!keys) {
|
|
1050
|
+
keys = /* @__PURE__ */ new Set();
|
|
1051
|
+
this.listKeysByOwnerField.set(ownerFieldKey, keys);
|
|
1052
|
+
}
|
|
1053
|
+
keys.add(key);
|
|
1054
|
+
}
|
|
1055
|
+
for (const id of this.getListReferencedEntityIds(list)) {
|
|
1056
|
+
let keys = this.listKeysByReferencedEntity.get(id);
|
|
1057
|
+
if (!keys) {
|
|
1058
|
+
keys = /* @__PURE__ */ new Set();
|
|
1059
|
+
this.listKeysByReferencedEntity.set(id, keys);
|
|
1060
|
+
}
|
|
1061
|
+
keys.add(key);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
removeListIndexes(key, list) {
|
|
1065
|
+
const parsed = parseListKey(key);
|
|
1066
|
+
if (parsed) {
|
|
1067
|
+
const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
|
|
1068
|
+
const keys = this.listKeysByOwnerField.get(ownerFieldKey);
|
|
1069
|
+
if (keys) {
|
|
1070
|
+
keys.delete(key);
|
|
1071
|
+
if (keys.size === 0) this.listKeysByOwnerField.delete(ownerFieldKey);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
for (const id of this.getListReferencedEntityIds(list)) {
|
|
1075
|
+
const keys = this.listKeysByReferencedEntity.get(id);
|
|
1076
|
+
if (!keys) continue;
|
|
1077
|
+
keys.delete(key);
|
|
1078
|
+
if (keys.size === 0) this.listKeysByReferencedEntity.delete(id);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
getListReferencedEntityIds(list) {
|
|
1082
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1083
|
+
for (const id of list.ids) ids.add(id);
|
|
1084
|
+
for (const id of list.liveAfterIds ?? []) ids.add(id);
|
|
1085
|
+
for (const id of list.liveBeforeIds ?? []) ids.add(id);
|
|
1086
|
+
for (const id of list.pendingAfterIds ?? []) ids.add(id);
|
|
1087
|
+
for (const id of list.pendingBeforeIds ?? []) ids.add(id);
|
|
1088
|
+
return ids;
|
|
1089
|
+
}
|
|
1090
|
+
getRecordFieldReferences(value) {
|
|
1091
|
+
if (Array.isArray(value)) {
|
|
1092
|
+
let ids = null;
|
|
1093
|
+
for (const item of value) {
|
|
1094
|
+
if (!isNodeRef(item)) continue;
|
|
1095
|
+
if (!ids) ids = /* @__PURE__ */ new Set();
|
|
1096
|
+
ids.add(getNodeRefId(item));
|
|
1097
|
+
}
|
|
1098
|
+
return ids;
|
|
1099
|
+
}
|
|
1100
|
+
if (isNodeRef(value)) return new Set([getNodeRefId(value)]);
|
|
1101
|
+
return null;
|
|
1102
|
+
}
|
|
1103
|
+
addRecordFieldReferenceIndex(id, field, value) {
|
|
1104
|
+
const targets = this.getRecordFieldReferences(value);
|
|
1105
|
+
if (!targets || targets.size === 0) return;
|
|
1106
|
+
let fields = this.recordReferenceFields.get(id);
|
|
1107
|
+
if (!fields) {
|
|
1108
|
+
fields = /* @__PURE__ */ new Map();
|
|
1109
|
+
this.recordReferenceFields.set(id, fields);
|
|
1110
|
+
}
|
|
1111
|
+
fields.set(field, targets);
|
|
1112
|
+
for (const targetId of targets) {
|
|
1113
|
+
let records = this.recordReferencesByTarget.get(targetId);
|
|
1114
|
+
if (!records) {
|
|
1115
|
+
records = /* @__PURE__ */ new Map();
|
|
1116
|
+
this.recordReferencesByTarget.set(targetId, records);
|
|
1117
|
+
}
|
|
1118
|
+
let targetFields = records.get(id);
|
|
1119
|
+
if (!targetFields) {
|
|
1120
|
+
targetFields = /* @__PURE__ */ new Set();
|
|
1121
|
+
records.set(id, targetFields);
|
|
1122
|
+
}
|
|
1123
|
+
targetFields.add(field);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
removeRecordFieldReferenceIndex(id, field) {
|
|
1127
|
+
const fields = this.recordReferenceFields.get(id);
|
|
1128
|
+
const targets = fields?.get(field);
|
|
1129
|
+
if (!fields || !targets) return;
|
|
1130
|
+
for (const targetId of targets) {
|
|
1131
|
+
const records = this.recordReferencesByTarget.get(targetId);
|
|
1132
|
+
const targetFields = records?.get(id);
|
|
1133
|
+
if (!records || !targetFields) continue;
|
|
1134
|
+
targetFields.delete(field);
|
|
1135
|
+
if (targetFields.size === 0) records.delete(id);
|
|
1136
|
+
if (records.size === 0) this.recordReferencesByTarget.delete(targetId);
|
|
1137
|
+
}
|
|
1138
|
+
fields.delete(field);
|
|
1139
|
+
if (fields.size === 0) this.recordReferenceFields.delete(id);
|
|
1140
|
+
}
|
|
1141
|
+
addRecordReferenceIndexes(id, record) {
|
|
1142
|
+
for (const [field, value] of Object.entries(record)) this.addRecordFieldReferenceIndex(id, field, value);
|
|
1143
|
+
}
|
|
1144
|
+
removeRecordReferenceIndexes(id, record) {
|
|
1145
|
+
for (const field of Object.keys(record)) this.removeRecordFieldReferenceIndex(id, field);
|
|
1146
|
+
}
|
|
1147
|
+
updateRecordReferenceIndexes(id, previous, next, fields) {
|
|
1148
|
+
for (const field of fields) {
|
|
1149
|
+
if (previous) this.removeRecordFieldReferenceIndex(id, field);
|
|
1150
|
+
this.addRecordFieldReferenceIndex(id, field, next[field]);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
//#endregion
|
|
1155
|
+
//#region src/request-descriptor.ts
|
|
1156
|
+
const serializeId = (value) => `${typeof value}:${String(value)}`;
|
|
1157
|
+
const getViewSignature = (view) => {
|
|
1158
|
+
const viewNames = /* @__PURE__ */ new Set();
|
|
1159
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1160
|
+
const collect = (value) => {
|
|
1161
|
+
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
1162
|
+
seen.add(value);
|
|
1163
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
1164
|
+
if (isViewTag(key)) viewNames.add(key);
|
|
1165
|
+
collect(entry);
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
collect(view);
|
|
1169
|
+
return viewNames.size ? [...viewNames].sort().join(",") : "";
|
|
1170
|
+
};
|
|
1171
|
+
const getRootDescriptorKey = (name, argsPayload, plan) => {
|
|
1172
|
+
if (!argsPayload) return name;
|
|
1173
|
+
return getListKey("__root__", name, plan.args.get("")?.hash ?? hashArgs(argsPayload));
|
|
1174
|
+
};
|
|
1175
|
+
const getRootListDescriptorKey = (name, argsPayload, plan) => {
|
|
1176
|
+
if (!argsPayload) return name;
|
|
1177
|
+
return getListKey("__root__", name, plan.args.get("")?.hash ?? hashArgs(filterConnectionArgs(argsPayload) ?? {}));
|
|
1178
|
+
};
|
|
1179
|
+
const hasCursorArgs = (argsPayload) => Boolean(argsPayload && ("after" in argsPayload || "before" in argsPayload || "cursor" in argsPayload));
|
|
1180
|
+
const getPaginationArgsInfo = (argsPayload) => ({
|
|
1181
|
+
direction: argsPayload?.before !== void 0 || argsPayload?.last !== void 0 ? "backward" : "forward",
|
|
1182
|
+
hasCursorArg: hasCursorArgs(argsPayload)
|
|
1183
|
+
});
|
|
1184
|
+
const getRequestDescriptorKey = (items) => {
|
|
1185
|
+
const parts = [];
|
|
1186
|
+
const sorted = [...items].sort((a, b) => a.name.localeCompare(b.name));
|
|
1187
|
+
for (const item of sorted) {
|
|
1188
|
+
if (item.kind === "node") {
|
|
1189
|
+
parts.push(`node:${item.name}:${item.viewSignature}:${item.ids[0]}`);
|
|
1190
|
+
continue;
|
|
1191
|
+
}
|
|
1192
|
+
if (item.kind === "nodes") {
|
|
1193
|
+
parts.push(`node:${item.name}:${item.viewSignature}:${item.ids.map(serializeId).join(",")}`);
|
|
1194
|
+
continue;
|
|
1195
|
+
}
|
|
1196
|
+
if (item.kind === "query") {
|
|
1197
|
+
parts.push(`query:${item.name}:${item.viewSignature}:${item.argsPayload ? hashArgs(item.argsPayload) : ""}`);
|
|
1198
|
+
continue;
|
|
1199
|
+
}
|
|
1200
|
+
if (item.kind === "list") parts.push(`list:${item.name}:${item.viewSignature}:${item.argsPayload ? hashArgs(item.argsPayload) : ""}`);
|
|
1201
|
+
}
|
|
1202
|
+
return parts.join("$");
|
|
1203
|
+
};
|
|
1204
|
+
const createRequestDescriptor = (request, getRootType) => {
|
|
1205
|
+
const items = [];
|
|
1206
|
+
for (const [name, item] of Object.entries(request)) {
|
|
1207
|
+
const type = getRootType(name);
|
|
1208
|
+
if (isNodeItem(item)) {
|
|
1209
|
+
items.push({
|
|
1210
|
+
ids: [item.id],
|
|
1211
|
+
kind: "node",
|
|
1212
|
+
name,
|
|
1213
|
+
plan: getSelectionPlan(item.view, null),
|
|
1214
|
+
refViewNames: new Set(getViewNames(item.view)),
|
|
1215
|
+
type,
|
|
1216
|
+
viewSignature: getViewSignature(item.view)
|
|
1217
|
+
});
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
if (isNodesItem(item)) {
|
|
1221
|
+
items.push({
|
|
1222
|
+
ids: item.ids,
|
|
1223
|
+
kind: "nodes",
|
|
1224
|
+
name,
|
|
1225
|
+
plan: getSelectionPlan(item.view, null),
|
|
1226
|
+
refViewNames: new Set(getViewNames(item.view)),
|
|
1227
|
+
type,
|
|
1228
|
+
viewSignature: getViewSignature(item.view)
|
|
1229
|
+
});
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
if (isQueryItem(item)) {
|
|
1233
|
+
const { argsPayload, plan } = resolveSelectionPlan(item.view, item.args);
|
|
1234
|
+
items.push({
|
|
1235
|
+
argsPayload,
|
|
1236
|
+
kind: "query",
|
|
1237
|
+
name,
|
|
1238
|
+
plan,
|
|
1239
|
+
queryKey: getRootDescriptorKey(name, argsPayload, plan),
|
|
1240
|
+
refViewNames: getRootViewNames(item.view),
|
|
1241
|
+
type,
|
|
1242
|
+
viewSignature: getViewSignature(item.view)
|
|
1243
|
+
});
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
const { argsPayload, plan } = resolveSelectionPlan(item.list, item.args);
|
|
1247
|
+
const hasItems = item.list && typeof item.list === "object" && "items" in item.list;
|
|
1248
|
+
const nodeView = hasItems && item.list.items ? item.list.items.node ?? item.list : item.list;
|
|
1249
|
+
items.push({
|
|
1250
|
+
argsPayload,
|
|
1251
|
+
hasItems,
|
|
1252
|
+
kind: "list",
|
|
1253
|
+
listKey: getRootListDescriptorKey(name, argsPayload, plan),
|
|
1254
|
+
name,
|
|
1255
|
+
nodeRefViewNames: getRootViewNames(nodeView),
|
|
1256
|
+
plan,
|
|
1257
|
+
type,
|
|
1258
|
+
viewSignature: getViewSignature(item.list)
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
return {
|
|
1262
|
+
items,
|
|
1263
|
+
key: getRequestDescriptorKey(items)
|
|
1264
|
+
};
|
|
1265
|
+
};
|
|
1266
|
+
const resolveSelectionPlan = (view, args) => {
|
|
1267
|
+
const plan = getSelectionPlan(view, null);
|
|
1268
|
+
const argsPayload = combineArgsPayload(args, resolvedArgsFromPlan(plan));
|
|
1269
|
+
if (argsPayload) applyArgsPayloadToPlan(plan, argsPayload);
|
|
1270
|
+
return {
|
|
1271
|
+
argsPayload,
|
|
1272
|
+
plan
|
|
1273
|
+
};
|
|
1274
|
+
};
|
|
1275
|
+
//#endregion
|
|
1276
|
+
//#region src/request-promise.ts
|
|
1277
|
+
let _Symbol$toStringTag;
|
|
1278
|
+
var FateRequestPromise = class {
|
|
1279
|
+
static {
|
|
1280
|
+
_Symbol$toStringTag = Symbol.toStringTag;
|
|
1281
|
+
}
|
|
1282
|
+
constructor(descriptor, readValue) {
|
|
1283
|
+
this.descriptor = descriptor;
|
|
1284
|
+
this.readValue = readValue;
|
|
1285
|
+
this[_Symbol$toStringTag] = "Promise";
|
|
1286
|
+
this.status = "pending";
|
|
1287
|
+
this.execution = null;
|
|
1288
|
+
}
|
|
1289
|
+
get value() {
|
|
1290
|
+
return this.readValue();
|
|
1291
|
+
}
|
|
1292
|
+
start(execution) {
|
|
1293
|
+
this.execution = execution;
|
|
1294
|
+
this.reason = void 0;
|
|
1295
|
+
this.status = "pending";
|
|
1296
|
+
execution.then(() => {
|
|
1297
|
+
if (this.execution !== execution) return;
|
|
1298
|
+
this.execution = null;
|
|
1299
|
+
this.status = "fulfilled";
|
|
1300
|
+
}, (error) => {
|
|
1301
|
+
if (this.execution !== execution) return;
|
|
1302
|
+
this.execution = null;
|
|
1303
|
+
this.reason = error;
|
|
1304
|
+
this.status = "rejected";
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
then(onfulfilled, onrejected) {
|
|
1308
|
+
switch (this.status) {
|
|
1309
|
+
case "fulfilled": return Promise.resolve().then(() => this.readValue()).then(onfulfilled ?? void 0, onrejected ?? void 0);
|
|
1310
|
+
case "rejected": return Promise.reject(this.reason).then(onfulfilled ?? void 0, onrejected ?? void 0);
|
|
1311
|
+
default:
|
|
1312
|
+
if (!this.execution) throw new Error(`fate: Pending request is missing its execution.`);
|
|
1313
|
+
return this.execution.then(onfulfilled ?? void 0, onrejected ?? void 0);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
catch(onrejected) {
|
|
1317
|
+
return this.then(void 0, onrejected ?? void 0);
|
|
1318
|
+
}
|
|
1319
|
+
finally(onfinally) {
|
|
1320
|
+
return this.then(async (value) => {
|
|
1321
|
+
await onfinally?.();
|
|
1322
|
+
return value;
|
|
1323
|
+
}, async (error) => {
|
|
1324
|
+
await onfinally?.();
|
|
1325
|
+
throw error;
|
|
1326
|
+
});
|
|
977
1327
|
}
|
|
978
1328
|
};
|
|
979
|
-
|
|
980
1329
|
//#endregion
|
|
981
1330
|
//#region src/client.ts
|
|
982
1331
|
const getId = (record) => {
|
|
@@ -987,6 +1336,88 @@ const getId = (record) => {
|
|
|
987
1336
|
return value;
|
|
988
1337
|
};
|
|
989
1338
|
const emptySet = /* @__PURE__ */ new Set();
|
|
1339
|
+
const emptyDispose = () => void 0;
|
|
1340
|
+
const dropCanonicalListIds = (listIds, ids) => {
|
|
1341
|
+
if (!listIds || listIds.length === 0) return;
|
|
1342
|
+
const canonicalIds = new Set(ids);
|
|
1343
|
+
const next = listIds.filter((id) => !canonicalIds.has(id));
|
|
1344
|
+
return next.length > 0 ? next : void 0;
|
|
1345
|
+
};
|
|
1346
|
+
const removeListId = (listIds, entityId) => listIds?.includes(entityId) ? listIds.filter((id) => id !== entityId) : listIds;
|
|
1347
|
+
const areArraysEqual = (left, right) => {
|
|
1348
|
+
if (left === right) return true;
|
|
1349
|
+
if (!left || !right || left.length !== right.length) return false;
|
|
1350
|
+
return left.every((value, index) => value === right[index]);
|
|
1351
|
+
};
|
|
1352
|
+
const arePaginationStatesEqual = (left, right) => left === right || (!left || !right ? false : left.hasNext === right.hasNext && left.hasPrevious === right.hasPrevious && left.nextCursor === right.nextCursor && left.previousCursor === right.previousCursor);
|
|
1353
|
+
const areListStatesEqual = (left, right) => Boolean(left) && areArraysEqual(left?.ids, right.ids) && areArraysEqual(left?.cursors, right.cursors) && areArraysEqual(left?.liveAfterIds, right.liveAfterIds) && areArraysEqual(left?.liveBeforeIds, right.liveBeforeIds) && areArraysEqual(left?.pendingAfterIds, right.pendingAfterIds) && areArraysEqual(left?.pendingBeforeIds, right.pendingBeforeIds) && left?.backwardPageLimit === right.backwardPageLimit && left?.forwardPageLimit === right.forwardPageLimit && arePaginationStatesEqual(left?.pagination, right.pagination);
|
|
1354
|
+
const createNodeRefsForIds = (ids, current, options = {}) => {
|
|
1355
|
+
if (options.reuseCurrentArray !== false && Array.isArray(current) && current.length === ids.length) {
|
|
1356
|
+
let matches = true;
|
|
1357
|
+
for (let index = 0; index < ids.length; index += 1) {
|
|
1358
|
+
const value = current[index];
|
|
1359
|
+
if (!isNodeRef(value) || getNodeRefId(value) !== ids[index]) {
|
|
1360
|
+
matches = false;
|
|
1361
|
+
break;
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
if (matches) return current;
|
|
1365
|
+
}
|
|
1366
|
+
const existingRefsById = /* @__PURE__ */ new Map();
|
|
1367
|
+
if (Array.isArray(current)) {
|
|
1368
|
+
for (const value of current) if (isNodeRef(value)) existingRefsById.set(getNodeRefId(value), value);
|
|
1369
|
+
}
|
|
1370
|
+
return ids.map((id) => existingRefsById.get(id) ?? createNodeRef(id));
|
|
1371
|
+
};
|
|
1372
|
+
const getConnectionPageLimit = (args, direction) => {
|
|
1373
|
+
const value = direction === "forward" ? args?.first ?? args?.last : args?.last ?? args?.first;
|
|
1374
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
|
|
1375
|
+
};
|
|
1376
|
+
const isConnectionWindowFull = (listState, connection, direction) => {
|
|
1377
|
+
const limit = getConnectionPageLimit(connection.args, direction);
|
|
1378
|
+
return limit != null && listState.ids.length >= limit;
|
|
1379
|
+
};
|
|
1380
|
+
const hasConnectionPageCoverage = (listState, args, direction) => {
|
|
1381
|
+
const limit = getConnectionPageLimit(args, direction);
|
|
1382
|
+
const pagination = listState.pagination;
|
|
1383
|
+
if (direction === "forward") {
|
|
1384
|
+
if (pagination?.hasPrevious) return false;
|
|
1385
|
+
return limit == null || listState.ids.length >= limit || listState.forwardPageLimit != null && listState.forwardPageLimit >= limit || pagination?.hasNext === false;
|
|
1386
|
+
}
|
|
1387
|
+
if (pagination?.hasNext) return false;
|
|
1388
|
+
return limit == null || listState.ids.length >= limit || listState.backwardPageLimit != null && listState.backwardPageLimit >= limit || pagination?.hasPrevious === false;
|
|
1389
|
+
};
|
|
1390
|
+
const getPaginationMergeInfo = (args) => {
|
|
1391
|
+
const paginationArgs = getPaginationArgsInfo(args);
|
|
1392
|
+
return {
|
|
1393
|
+
...paginationArgs,
|
|
1394
|
+
pageLimit: getConnectionPageLimit(args, paginationArgs.direction)
|
|
1395
|
+
};
|
|
1396
|
+
};
|
|
1397
|
+
const getConnectionBoundaryCursor = (listState, direction) => {
|
|
1398
|
+
if (!listState.cursors || listState.cursors.length === 0) return;
|
|
1399
|
+
return direction === "forward" ? listState.cursors.at(-1) : listState.cursors[0];
|
|
1400
|
+
};
|
|
1401
|
+
const markConnectionPageAvailable = (listState, direction) => {
|
|
1402
|
+
const pagination = {
|
|
1403
|
+
hasNext: Boolean(listState.pagination?.hasNext),
|
|
1404
|
+
hasPrevious: Boolean(listState.pagination?.hasPrevious),
|
|
1405
|
+
nextCursor: listState.pagination?.nextCursor,
|
|
1406
|
+
previousCursor: listState.pagination?.previousCursor
|
|
1407
|
+
};
|
|
1408
|
+
const cursor = getConnectionBoundaryCursor(listState, direction);
|
|
1409
|
+
if (direction === "forward") {
|
|
1410
|
+
pagination.hasNext = true;
|
|
1411
|
+
pagination.nextCursor = cursor ?? pagination.nextCursor;
|
|
1412
|
+
} else {
|
|
1413
|
+
pagination.hasPrevious = true;
|
|
1414
|
+
pagination.previousCursor = cursor ?? pagination.previousCursor;
|
|
1415
|
+
}
|
|
1416
|
+
return {
|
|
1417
|
+
...listState,
|
|
1418
|
+
pagination
|
|
1419
|
+
};
|
|
1420
|
+
};
|
|
990
1421
|
const setNestedValue = (target, key, value) => {
|
|
991
1422
|
const path = key.split(".");
|
|
992
1423
|
let current = target;
|
|
@@ -1002,32 +1433,10 @@ const setNestedValue = (target, key, value) => {
|
|
|
1002
1433
|
current = current[segment];
|
|
1003
1434
|
}
|
|
1004
1435
|
};
|
|
1005
|
-
const
|
|
1006
|
-
const
|
|
1007
|
-
const
|
|
1008
|
-
return
|
|
1009
|
-
};
|
|
1010
|
-
const getRequestCacheKey = (request) => {
|
|
1011
|
-
const parts = [];
|
|
1012
|
-
const names = Object.keys(request).sort();
|
|
1013
|
-
for (const name of names) {
|
|
1014
|
-
const item = request[name];
|
|
1015
|
-
if (!item) continue;
|
|
1016
|
-
if (isNodeItem(item)) {
|
|
1017
|
-
parts.push(`node:${name}:${getViewSignature(item.view)}:${item.id}`);
|
|
1018
|
-
continue;
|
|
1019
|
-
}
|
|
1020
|
-
if (isNodesItem(item)) {
|
|
1021
|
-
parts.push(`node:${name}:${getViewSignature(item.view)}:${item.ids.map(serializeId).join(",")}`);
|
|
1022
|
-
continue;
|
|
1023
|
-
}
|
|
1024
|
-
if (isQueryItem(item)) {
|
|
1025
|
-
parts.push(`query:${name}:${getViewSignature(item.view)}:${item.args ? hashArgs(item.args) : ""}`);
|
|
1026
|
-
continue;
|
|
1027
|
-
}
|
|
1028
|
-
parts.push(`list:${name}:${getViewSignature(item.list)}:${item.args ? hashArgs(item.args) : ""}`);
|
|
1029
|
-
}
|
|
1030
|
-
return parts.join("$");
|
|
1436
|
+
const omitUndefinedValues = (record) => {
|
|
1437
|
+
const result = {};
|
|
1438
|
+
for (const [key, value] of Object.entries(record)) if (value !== void 0) result[key] = value;
|
|
1439
|
+
return result;
|
|
1031
1440
|
};
|
|
1032
1441
|
const groupSelectionByPrefix = (select) => {
|
|
1033
1442
|
if (select.size === 0) return /* @__PURE__ */ new Map();
|
|
@@ -1060,14 +1469,22 @@ var FateClient = class {
|
|
|
1060
1469
|
this.optimisticByEntity = /* @__PURE__ */ new Map();
|
|
1061
1470
|
this.optimisticEntityResolutions = /* @__PURE__ */ new Map();
|
|
1062
1471
|
this.optimisticTokenCounter = 0;
|
|
1472
|
+
this.liveSubscriptions = /* @__PURE__ */ new Map();
|
|
1473
|
+
this.liveConnectionInvalidations = /* @__PURE__ */ new Map();
|
|
1474
|
+
this.stableRefs = /* @__PURE__ */ new Map();
|
|
1063
1475
|
this.requests = /* @__PURE__ */ new Map();
|
|
1064
1476
|
this.rootRequests = /* @__PURE__ */ new Map();
|
|
1065
1477
|
this.stalledRequests = /* @__PURE__ */ new Set();
|
|
1478
|
+
this.gcPending = false;
|
|
1479
|
+
this.gcScheduled = false;
|
|
1066
1480
|
this.store = new Store();
|
|
1067
1481
|
this.viewDataCache = new ViewDataCache();
|
|
1482
|
+
this.liveConnectionInvalidationToken = 0;
|
|
1068
1483
|
this.actions = Object.create(null);
|
|
1069
1484
|
this.mutationMap = Object.create(null);
|
|
1070
1485
|
this.mutations = Object.create(null);
|
|
1486
|
+
this.onLiveError = options.onLiveError;
|
|
1487
|
+
this.operationLifetime = new OperationLifetime(options.gcReleaseBufferSize ?? 10);
|
|
1071
1488
|
this.roots = options.roots;
|
|
1072
1489
|
this.transport = options.transport;
|
|
1073
1490
|
this.types = new Map(options.types.map((entity) => [entity.type, {
|
|
@@ -1075,12 +1492,12 @@ var FateClient = class {
|
|
|
1075
1492
|
...entity
|
|
1076
1493
|
}]));
|
|
1077
1494
|
if (options.mutations) for (const [key, definition] of Object.entries(options.mutations)) {
|
|
1078
|
-
const mutation
|
|
1495
|
+
const mutation = wrapMutation(this, {
|
|
1079
1496
|
...definition,
|
|
1080
1497
|
key
|
|
1081
1498
|
});
|
|
1082
|
-
this.mutationMap[key] = mutation
|
|
1083
|
-
setNestedValue(this.mutations, key, mutation
|
|
1499
|
+
this.mutationMap[key] = mutation;
|
|
1500
|
+
setNestedValue(this.mutations, key, mutation);
|
|
1084
1501
|
setNestedValue(this.actions, key, async (_previousState, data) => data === "reset" ? null : await this.mutationMap[key](data));
|
|
1085
1502
|
}
|
|
1086
1503
|
this.initializeParentLists();
|
|
@@ -1120,20 +1537,50 @@ var FateClient = class {
|
|
|
1120
1537
|
}
|
|
1121
1538
|
lists.add(key);
|
|
1122
1539
|
}
|
|
1123
|
-
|
|
1540
|
+
snapshotList(key, listSnapshots) {
|
|
1541
|
+
if (!listSnapshots || listSnapshots.has(key)) return;
|
|
1542
|
+
const listState = this.store.getListState(key);
|
|
1543
|
+
if (listState) listSnapshots.set(key, listState);
|
|
1544
|
+
}
|
|
1545
|
+
applyListInsert(listState, entityId, insert) {
|
|
1546
|
+
const pendingBeforeIds = listState.pendingBeforeIds ?? [];
|
|
1547
|
+
const pendingAfterIds = listState.pendingAfterIds ?? [];
|
|
1548
|
+
if (listState.ids.includes(entityId) || pendingBeforeIds.includes(entityId) || pendingAfterIds.includes(entityId)) return {
|
|
1549
|
+
list: listState,
|
|
1550
|
+
pending: false
|
|
1551
|
+
};
|
|
1552
|
+
if (insert === "before" ? Boolean(listState.pagination?.hasPrevious) : Boolean(listState.pagination?.hasNext)) return {
|
|
1553
|
+
list: insert === "before" ? {
|
|
1554
|
+
...listState,
|
|
1555
|
+
pendingBeforeIds: [entityId, ...pendingBeforeIds]
|
|
1556
|
+
} : {
|
|
1557
|
+
...listState,
|
|
1558
|
+
pendingAfterIds: [...pendingAfterIds, entityId]
|
|
1559
|
+
},
|
|
1560
|
+
pending: true
|
|
1561
|
+
};
|
|
1562
|
+
const ids = insert === "before" ? [entityId, ...listState.ids] : [...listState.ids, entityId];
|
|
1563
|
+
const cursors = listState.cursors ? insert === "before" ? [void 0, ...listState.cursors] : [...listState.cursors, void 0] : listState.cursors;
|
|
1564
|
+
return {
|
|
1565
|
+
list: {
|
|
1566
|
+
...listState,
|
|
1567
|
+
cursors,
|
|
1568
|
+
ids
|
|
1569
|
+
},
|
|
1570
|
+
pending: false
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
insertIntoRootLists(type, entityId, insert, listSnapshots) {
|
|
1124
1574
|
if (insert === "none") return;
|
|
1125
1575
|
const listNames = this.rootLists.get(type);
|
|
1126
1576
|
if (!listNames) return;
|
|
1127
1577
|
for (const key of listNames) {
|
|
1128
1578
|
const listState = this.store.getListState(key);
|
|
1129
|
-
if (!listState
|
|
1130
|
-
const
|
|
1131
|
-
|
|
1132
|
-
this.
|
|
1133
|
-
|
|
1134
|
-
ids,
|
|
1135
|
-
pagination: listState.pagination
|
|
1136
|
-
});
|
|
1579
|
+
if (!listState) continue;
|
|
1580
|
+
const next = this.applyListInsert(listState, entityId, insert).list;
|
|
1581
|
+
if (next === listState) continue;
|
|
1582
|
+
this.snapshotList(key, listSnapshots);
|
|
1583
|
+
this.store.setList(key, next);
|
|
1137
1584
|
}
|
|
1138
1585
|
}
|
|
1139
1586
|
getTypeConfig(type) {
|
|
@@ -1152,63 +1599,78 @@ var FateClient = class {
|
|
|
1152
1599
|
} : argsPayload ? { args: argsPayload } : input;
|
|
1153
1600
|
return await this.transport.mutate(key, requestInput, select);
|
|
1154
1601
|
}
|
|
1155
|
-
write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert) {
|
|
1156
|
-
return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert);
|
|
1602
|
+
write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
|
|
1603
|
+
return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert, listSnapshots);
|
|
1157
1604
|
}
|
|
1158
|
-
deleteRecord(type, id
|
|
1159
|
-
const entityId = toEntityId(type, id
|
|
1605
|
+
deleteRecord(type, id, snapshots, listSnapshots) {
|
|
1606
|
+
const entityId = toEntityId(type, id);
|
|
1160
1607
|
if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
|
|
1161
1608
|
this.viewDataCache.invalidate(entityId);
|
|
1609
|
+
this.stableRefs.delete(entityId);
|
|
1162
1610
|
this.store.deleteRecord(entityId);
|
|
1163
1611
|
this.store.removeReferencesTo(entityId, this.viewDataCache, snapshots, listSnapshots);
|
|
1164
1612
|
}
|
|
1165
|
-
restore(id
|
|
1166
|
-
this.viewDataCache.invalidate(id
|
|
1167
|
-
this.store.restore(id
|
|
1613
|
+
restore(id, snapshot) {
|
|
1614
|
+
this.viewDataCache.invalidate(id);
|
|
1615
|
+
this.store.restore(id, snapshot);
|
|
1168
1616
|
}
|
|
1169
1617
|
restoreList(name, list) {
|
|
1170
1618
|
this.store.restoreList(name, list);
|
|
1171
1619
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1620
|
+
stableRefWithViewNames(type, id, viewNames) {
|
|
1621
|
+
const entityId = toEntityId(type, id);
|
|
1622
|
+
const key = `${typeof id}:${String(id)}:${[...viewNames].sort().join(",")}`;
|
|
1623
|
+
let refs = this.stableRefs.get(entityId);
|
|
1624
|
+
if (!refs) {
|
|
1625
|
+
refs = /* @__PURE__ */ new Map();
|
|
1626
|
+
this.stableRefs.set(entityId, refs);
|
|
1627
|
+
}
|
|
1628
|
+
const existing = refs.get(key);
|
|
1629
|
+
if (existing) return existing;
|
|
1630
|
+
const ref = createRefWithViewNames(type, id, viewNames);
|
|
1631
|
+
refs.set(key, ref);
|
|
1632
|
+
return ref;
|
|
1633
|
+
}
|
|
1634
|
+
ref(type, id, view) {
|
|
1635
|
+
return this.stableRefWithViewNames(type, id, getViewNames(view));
|
|
1174
1636
|
}
|
|
1175
1637
|
rootListRef(entityId, rootView) {
|
|
1176
|
-
const { id
|
|
1177
|
-
return
|
|
1638
|
+
const { id, type } = parseEntityId(entityId);
|
|
1639
|
+
return this.stableRefWithViewNames(type, id, getRootViewNames(rootView));
|
|
1178
1640
|
}
|
|
1179
|
-
readView(view
|
|
1180
|
-
const id
|
|
1641
|
+
readView(view, ref) {
|
|
1642
|
+
const id = ref.id;
|
|
1181
1643
|
const type = ref.__typename;
|
|
1182
|
-
if (id
|
|
1644
|
+
if (id == null) {
|
|
1183
1645
|
const received = Object.keys(ref).length > 0 ? `'${JSON.stringify(ref)}'` : "an empty object";
|
|
1184
1646
|
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'?`);
|
|
1185
1647
|
}
|
|
1186
1648
|
if (type == null) throw new Error(`fate: Invalid view reference. Expected '__typename' to be provided as part of the reference, received '${JSON.stringify(ref)}'.`);
|
|
1187
|
-
const entityId = toEntityId(type, id
|
|
1649
|
+
const entityId = toEntityId(type, id);
|
|
1188
1650
|
const resolvedEntityId = this.optimisticEntityResolutions.get(entityId) ?? null;
|
|
1189
1651
|
if (resolvedEntityId && resolvedEntityId !== entityId) {
|
|
1190
1652
|
const { id: resolvedId, type: resolvedType } = parseEntityId(resolvedEntityId);
|
|
1191
|
-
return this.readView(view
|
|
1653
|
+
return this.readView(view, this.stableRefWithViewNames(resolvedType, resolvedId, getViewNames(view)));
|
|
1192
1654
|
}
|
|
1193
|
-
const viewNames = getViewNames(view
|
|
1655
|
+
const viewNames = getViewNames(view);
|
|
1194
1656
|
const refViews = ref[ViewsTag];
|
|
1195
1657
|
if (!refViews || ![...viewNames].every((name) => refViews.has(name))) {
|
|
1196
1658
|
const received = refViews ? [...refViews].join(", ") : JSON.stringify(ref);
|
|
1197
1659
|
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.`);
|
|
1198
1660
|
}
|
|
1199
|
-
const cached = this.viewDataCache.get(entityId, view
|
|
1661
|
+
const cached = this.viewDataCache.get(entityId, view, ref);
|
|
1200
1662
|
if (cached) return cached;
|
|
1201
|
-
const plan = getSelectionPlan(view
|
|
1663
|
+
const plan = getSelectionPlan(view, ref);
|
|
1202
1664
|
const selectedPaths = plan.paths;
|
|
1203
1665
|
const missing = this.store.missingForSelection(entityId, selectedPaths);
|
|
1204
1666
|
const resolveSnapshot = () => {
|
|
1205
|
-
const resolvedView = this.readViewSelection(view
|
|
1667
|
+
const resolvedView = this.readViewSelection(view, ref, entityId, plan);
|
|
1206
1668
|
const thenable = {
|
|
1207
1669
|
status: "fulfilled",
|
|
1208
1670
|
then: (onfulfilled, onrejected) => Promise.resolve(resolvedView).then(onfulfilled, onrejected),
|
|
1209
1671
|
value: resolvedView
|
|
1210
1672
|
};
|
|
1211
|
-
this.viewDataCache.set(entityId, view
|
|
1673
|
+
this.viewDataCache.set(entityId, view, ref, thenable, new Set(resolvedView.coverage.map(([id]) => id)));
|
|
1212
1674
|
return thenable;
|
|
1213
1675
|
};
|
|
1214
1676
|
if (missing.size === 0) {
|
|
@@ -1221,38 +1683,350 @@ var FateClient = class {
|
|
|
1221
1683
|
if (pendingOptimistic) {
|
|
1222
1684
|
const pending = this.pending.get(key);
|
|
1223
1685
|
if (pending) return pending;
|
|
1224
|
-
const promise
|
|
1225
|
-
this.pending.set(key, promise
|
|
1226
|
-
return promise
|
|
1686
|
+
const promise = Promise.all(pendingOptimistic).then(() => this.readView(view, ref)).finally(() => this.pending.delete(key));
|
|
1687
|
+
this.pending.set(key, promise);
|
|
1688
|
+
return promise;
|
|
1227
1689
|
}
|
|
1228
1690
|
if (this.stalledRequests.has(key)) return resolveSnapshot();
|
|
1229
1691
|
const pendingPromise = this.pending.get(key) || null;
|
|
1230
1692
|
if (pendingPromise) return pendingPromise;
|
|
1231
|
-
const promise = this.fetchByIdAndNormalize(type, [id
|
|
1693
|
+
const promise = this.fetchByIdAndNormalize(type, [id], missing, plan).finally(() => this.pending.delete(key)).then(() => {
|
|
1232
1694
|
if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
|
|
1233
1695
|
this.stalledRequests.add(key);
|
|
1234
1696
|
return resolveSnapshot();
|
|
1235
1697
|
}
|
|
1236
1698
|
this.stalledRequests.delete(key);
|
|
1237
|
-
return this.readView(view
|
|
1699
|
+
return this.readView(view, ref);
|
|
1238
1700
|
});
|
|
1239
1701
|
this.pending.set(key, promise);
|
|
1240
1702
|
return promise;
|
|
1241
1703
|
}
|
|
1242
1704
|
return resolveSnapshot();
|
|
1243
1705
|
}
|
|
1706
|
+
assertLiveViewSupport() {
|
|
1707
|
+
if (!this.transport.subscribeById) throw new Error(`fate: transport does not support live views. Please provide a 'subscribeById' implementation in your transport.`);
|
|
1708
|
+
}
|
|
1709
|
+
assertLiveConnectionSupport() {
|
|
1710
|
+
if (!this.transport.subscribeConnection) throw new Error(`fate: transport does not support live list views. Please provide a 'subscribeConnection' implementation in your transport.`);
|
|
1711
|
+
}
|
|
1712
|
+
subscribeLiveView(view, ref) {
|
|
1713
|
+
this.assertLiveViewSupport();
|
|
1714
|
+
const id = ref.id;
|
|
1715
|
+
const type = ref.__typename;
|
|
1716
|
+
if (id == null) throw new Error(`fate: Invalid live view reference. Expected 'id' to be provided as part of the reference.`);
|
|
1717
|
+
if (type == null) throw new Error(`fate: Invalid live view reference. Expected '__typename' to be provided as part of the reference.`);
|
|
1718
|
+
const viewNames = getViewNames(view);
|
|
1719
|
+
const refViews = ref[ViewsTag];
|
|
1720
|
+
if (!refViews || ![...viewNames].every((name) => refViews.has(name))) {
|
|
1721
|
+
const received = refViews ? [...refViews].join(", ") : JSON.stringify(ref);
|
|
1722
|
+
throw new Error(`fate: Invalid live view reference. Expected the provided ref to include the view(s) '${[...viewNames].join("', '")}', received '${received}'.`);
|
|
1723
|
+
}
|
|
1724
|
+
const entityId = toEntityId(type, id);
|
|
1725
|
+
const plan = getSelectionPlan(view, ref);
|
|
1726
|
+
const key = this.liveSubscriptionKey(entityId, plan);
|
|
1727
|
+
const existing = this.liveSubscriptions.get(key);
|
|
1728
|
+
if (existing) {
|
|
1729
|
+
existing.count += 1;
|
|
1730
|
+
existing.releaseScheduled = false;
|
|
1731
|
+
return () => {
|
|
1732
|
+
this.releaseLiveSubscription(key);
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
const args = resolvedArgsFromPlan(plan);
|
|
1736
|
+
let unsubscribe;
|
|
1737
|
+
try {
|
|
1738
|
+
unsubscribe = this.transport.subscribeById(type, id, plan.paths, args, {
|
|
1739
|
+
onData: (record, liveSelect) => {
|
|
1740
|
+
if (!record || typeof record !== "object") return;
|
|
1741
|
+
const pendingMask = this.getPendingOptimisticMask(entityId);
|
|
1742
|
+
const select = new Set(liveSelect ?? plan.paths);
|
|
1743
|
+
const selection = this.filterSelectionForPendingOptimistics(entityId, select);
|
|
1744
|
+
this.write(type, record, selection, void 0, plan, null, pendingMask, "none");
|
|
1745
|
+
},
|
|
1746
|
+
onDelete: (deletedId) => {
|
|
1747
|
+
this.deleteRecord(type, deletedId ?? id);
|
|
1748
|
+
},
|
|
1749
|
+
onError: (error) => this.reportLiveError(error)
|
|
1750
|
+
});
|
|
1751
|
+
} catch (error) {
|
|
1752
|
+
this.reportLiveError(error);
|
|
1753
|
+
return emptyDispose;
|
|
1754
|
+
}
|
|
1755
|
+
this.liveSubscriptions.set(key, {
|
|
1756
|
+
count: 1,
|
|
1757
|
+
unsubscribe
|
|
1758
|
+
});
|
|
1759
|
+
return () => {
|
|
1760
|
+
this.releaseLiveSubscription(key);
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
subscribeLiveListView(view, connection) {
|
|
1764
|
+
this.assertLiveConnectionSupport();
|
|
1765
|
+
const plan = getSelectionPlan(view, null);
|
|
1766
|
+
const connectionArgs = filterConnectionArgs(connection.args);
|
|
1767
|
+
const selectionArgs = resolvedArgsFromPlan(plan);
|
|
1768
|
+
const key = this.liveConnectionSubscriptionKey(connection, plan, connectionArgs);
|
|
1769
|
+
const existing = this.liveSubscriptions.get(key);
|
|
1770
|
+
if (existing) {
|
|
1771
|
+
existing.count += 1;
|
|
1772
|
+
existing.releaseScheduled = false;
|
|
1773
|
+
return () => {
|
|
1774
|
+
this.releaseLiveSubscription(key);
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
let unsubscribe;
|
|
1778
|
+
try {
|
|
1779
|
+
unsubscribe = this.transport.subscribeConnection(connection.procedure, connection.type, connectionArgs, plan.paths, selectionArgs, {
|
|
1780
|
+
onError: (error) => this.reportLiveError(error),
|
|
1781
|
+
onEvent: (event) => {
|
|
1782
|
+
this.applyLiveConnectionEvent(view, connection, plan, event);
|
|
1783
|
+
}
|
|
1784
|
+
});
|
|
1785
|
+
} catch (error) {
|
|
1786
|
+
this.reportLiveError(error);
|
|
1787
|
+
return emptyDispose;
|
|
1788
|
+
}
|
|
1789
|
+
this.liveSubscriptions.set(key, {
|
|
1790
|
+
count: 1,
|
|
1791
|
+
unsubscribe
|
|
1792
|
+
});
|
|
1793
|
+
return () => {
|
|
1794
|
+
this.releaseLiveSubscription(key);
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
applyLiveConnectionEvent(view, connection, plan, event) {
|
|
1798
|
+
if (event.type === "invalidate") {
|
|
1799
|
+
this.invalidateLiveConnection(view, connection);
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1802
|
+
if (event.type === "deleteEdge") {
|
|
1803
|
+
this.deleteConnectionEdge(connection, toEntityId(event.nodeType, event.id));
|
|
1804
|
+
return;
|
|
1805
|
+
}
|
|
1806
|
+
const node = event.edge.node;
|
|
1807
|
+
if (!node || typeof node !== "object") return;
|
|
1808
|
+
const nodeRecord = node;
|
|
1809
|
+
const nodeType = event.nodeType;
|
|
1810
|
+
if (!nodeType) {
|
|
1811
|
+
this.reportLiveError(/* @__PURE__ */ new Error(`fate: Live connection event for '${connection.procedure}' did not include a node type.`));
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
const entityId = this.write(nodeType, nodeRecord, plan.paths, void 0, plan, null, null, "none");
|
|
1815
|
+
this.insertConnectionEdge(connection.key, entityId, event.edge.cursor, event.type, event.targetCursor, view, connection);
|
|
1816
|
+
}
|
|
1817
|
+
invalidateLiveConnection(view, connection) {
|
|
1818
|
+
const token = ++this.liveConnectionInvalidationToken;
|
|
1819
|
+
this.liveConnectionInvalidations.set(connection.key, token);
|
|
1820
|
+
const previous = this.store.getListState(connection.key);
|
|
1821
|
+
this.store.restoreList(connection.key, void 0);
|
|
1822
|
+
this.loadConnection(view, connection, connection.args ?? {}).then(() => {
|
|
1823
|
+
if (this.liveConnectionInvalidations.get(connection.key) === token) this.liveConnectionInvalidations.delete(connection.key);
|
|
1824
|
+
}).catch((error) => {
|
|
1825
|
+
if (this.liveConnectionInvalidations.get(connection.key) === token) {
|
|
1826
|
+
this.store.restoreList(connection.key, previous);
|
|
1827
|
+
this.liveConnectionInvalidations.delete(connection.key);
|
|
1828
|
+
}
|
|
1829
|
+
this.reportLiveError(error);
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
deleteConnectionEdge(connection, entityId) {
|
|
1833
|
+
const listState = this.store.getListState(connection.key);
|
|
1834
|
+
if (!listState) {
|
|
1835
|
+
this.syncNestedConnectionOwnerField(connection, entityId, void 0, "delete");
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
const ids = [];
|
|
1839
|
+
const cursors = listState.cursors ? [] : void 0;
|
|
1840
|
+
let changed = false;
|
|
1841
|
+
listState.ids.forEach((id, index) => {
|
|
1842
|
+
if (id === entityId) {
|
|
1843
|
+
changed = true;
|
|
1844
|
+
return;
|
|
1845
|
+
}
|
|
1846
|
+
ids.push(id);
|
|
1847
|
+
if (cursors) cursors.push(listState.cursors?.[index]);
|
|
1848
|
+
});
|
|
1849
|
+
const liveAfterIds = removeListId(listState.liveAfterIds, entityId);
|
|
1850
|
+
const liveBeforeIds = removeListId(listState.liveBeforeIds, entityId);
|
|
1851
|
+
const pendingAfterIds = removeListId(listState.pendingAfterIds, entityId);
|
|
1852
|
+
const pendingBeforeIds = removeListId(listState.pendingBeforeIds, entityId);
|
|
1853
|
+
changed = changed || liveAfterIds?.length !== listState.liveAfterIds?.length || liveBeforeIds?.length !== listState.liveBeforeIds?.length || pendingAfterIds?.length !== listState.pendingAfterIds?.length || pendingBeforeIds?.length !== listState.pendingBeforeIds?.length;
|
|
1854
|
+
if (changed) {
|
|
1855
|
+
const nextListState = {
|
|
1856
|
+
...listState,
|
|
1857
|
+
cursors,
|
|
1858
|
+
ids,
|
|
1859
|
+
liveAfterIds,
|
|
1860
|
+
liveBeforeIds,
|
|
1861
|
+
pendingAfterIds,
|
|
1862
|
+
pendingBeforeIds
|
|
1863
|
+
};
|
|
1864
|
+
this.store.setList(connection.key, nextListState);
|
|
1865
|
+
this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "delete");
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
this.syncNestedConnectionOwnerField(connection, entityId, listState, "delete");
|
|
1869
|
+
}
|
|
1870
|
+
insertConnectionEdge(key, entityId, cursor, type, targetCursor, view, connection) {
|
|
1871
|
+
const listState = this.store.getListState(key) ?? { ids: [] };
|
|
1872
|
+
const isAppendEvent = type === "appendEdge" || type === "appendNode";
|
|
1873
|
+
const isPrependEvent = type === "prependEdge" || type === "prependNode";
|
|
1874
|
+
const appendMode = connection.live?.append ?? "edge";
|
|
1875
|
+
const prependMode = connection.live?.prepend ?? "edge";
|
|
1876
|
+
if (isAppendEvent && listState.pendingAfterIds?.includes(entityId) || isPrependEvent && listState.pendingBeforeIds?.includes(entityId)) {
|
|
1877
|
+
this.syncNestedConnectionOwnerField(connection, entityId, listState, "insert");
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
const visibleIndex = listState.ids.indexOf(entityId);
|
|
1881
|
+
if (visibleIndex >= 0 && (isAppendEvent && appendMode === "visible" || isPrependEvent && prependMode === "visible")) {
|
|
1882
|
+
const nextListState = cursor === void 0 ? listState : {
|
|
1883
|
+
...listState,
|
|
1884
|
+
cursors: listState.ids.map((_, index) => index === visibleIndex ? cursor : listState.cursors?.[index])
|
|
1885
|
+
};
|
|
1886
|
+
if (nextListState !== listState) this.store.setList(key, nextListState);
|
|
1887
|
+
this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1890
|
+
const withoutExisting = this.removeEntityFromListState(listState, entityId);
|
|
1891
|
+
const keepLiveBeforeHidden = isPrependEvent && prependMode !== "visible" && (Boolean(withoutExisting.pagination?.hasPrevious) || isConnectionWindowFull(withoutExisting, connection, "backward"));
|
|
1892
|
+
const keepLiveAfterHidden = isAppendEvent && appendMode !== "visible" && (Boolean(withoutExisting.pagination?.hasNext) || isConnectionWindowFull(withoutExisting, connection, "forward"));
|
|
1893
|
+
const keepVisibleBeforePending = isPrependEvent && prependMode === "visible" && (withoutExisting.pagination?.hasPrevious === true || withoutExisting.pagination?.hasPrevious === void 0 && isConnectionWindowFull(withoutExisting, connection, "backward"));
|
|
1894
|
+
const keepVisibleAfterPending = isAppendEvent && appendMode === "visible" && (withoutExisting.pagination?.hasNext === true || withoutExisting.pagination?.hasNext === void 0 && isConnectionWindowFull(withoutExisting, connection, "forward"));
|
|
1895
|
+
if (keepLiveBeforeHidden) {
|
|
1896
|
+
const nextList = markConnectionPageAvailable(withoutExisting, "backward");
|
|
1897
|
+
const nextListState = {
|
|
1898
|
+
...nextList,
|
|
1899
|
+
liveBeforeIds: [entityId, ...nextList.liveBeforeIds ?? []]
|
|
1900
|
+
};
|
|
1901
|
+
this.store.setList(key, nextListState);
|
|
1902
|
+
this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
if (keepLiveAfterHidden) {
|
|
1906
|
+
const nextList = markConnectionPageAvailable(withoutExisting, "forward");
|
|
1907
|
+
const nextListState = {
|
|
1908
|
+
...nextList,
|
|
1909
|
+
liveAfterIds: [...nextList.liveAfterIds ?? [], entityId]
|
|
1910
|
+
};
|
|
1911
|
+
this.store.setList(key, nextListState);
|
|
1912
|
+
this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
if (keepVisibleBeforePending) {
|
|
1916
|
+
const nextList = markConnectionPageAvailable(withoutExisting, "backward");
|
|
1917
|
+
const nextListState = {
|
|
1918
|
+
...nextList,
|
|
1919
|
+
pendingBeforeIds: [entityId, ...nextList.pendingBeforeIds ?? []]
|
|
1920
|
+
};
|
|
1921
|
+
this.store.setList(key, nextListState);
|
|
1922
|
+
this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
if (keepVisibleAfterPending) {
|
|
1926
|
+
const nextList = markConnectionPageAvailable(withoutExisting, "forward");
|
|
1927
|
+
const nextListState = {
|
|
1928
|
+
...nextList,
|
|
1929
|
+
pendingAfterIds: [...nextList.pendingAfterIds ?? [], entityId]
|
|
1930
|
+
};
|
|
1931
|
+
this.store.setList(key, nextListState);
|
|
1932
|
+
this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
const index = this.getConnectionInsertIndex(withoutExisting, type, targetCursor);
|
|
1936
|
+
if (index == null) {
|
|
1937
|
+
this.invalidateLiveConnection(view, connection);
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
const ids = [...withoutExisting.ids];
|
|
1941
|
+
ids.splice(index, 0, entityId);
|
|
1942
|
+
const cursors = Boolean(withoutExisting.cursors) || cursor !== void 0 ? [...withoutExisting.cursors ?? []] : void 0;
|
|
1943
|
+
if (cursors) {
|
|
1944
|
+
while (cursors.length < withoutExisting.ids.length) cursors.push(void 0);
|
|
1945
|
+
cursors.splice(index, 0, cursor);
|
|
1946
|
+
}
|
|
1947
|
+
const nextListState = {
|
|
1948
|
+
...withoutExisting,
|
|
1949
|
+
cursors,
|
|
1950
|
+
ids,
|
|
1951
|
+
liveAfterIds: removeListId(withoutExisting.liveAfterIds, entityId),
|
|
1952
|
+
liveBeforeIds: removeListId(withoutExisting.liveBeforeIds, entityId)
|
|
1953
|
+
};
|
|
1954
|
+
this.store.setList(key, nextListState);
|
|
1955
|
+
this.syncNestedConnectionOwnerField(connection, entityId, nextListState, "insert");
|
|
1956
|
+
}
|
|
1957
|
+
syncNestedConnectionOwnerField(connection, entityId, listState, action) {
|
|
1958
|
+
if (connection.root) return;
|
|
1959
|
+
const owner = this.store.read(connection.owner);
|
|
1960
|
+
const current = Array.isArray(owner?.[connection.field]) ? owner?.[connection.field] : [];
|
|
1961
|
+
const currentIds = current.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id) => id != null);
|
|
1962
|
+
const nextIds = currentIds.filter((id) => id !== entityId);
|
|
1963
|
+
if (action === "insert" && listState) {
|
|
1964
|
+
const visibleIds = getListEntries(listState).map(({ id }) => id);
|
|
1965
|
+
const visibleIndex = visibleIds.indexOf(entityId);
|
|
1966
|
+
if (visibleIndex >= 0) {
|
|
1967
|
+
const nextIndexById = new Map(nextIds.map((id, index) => [id, index]));
|
|
1968
|
+
let insertIndex = null;
|
|
1969
|
+
for (let index = visibleIndex - 1; index >= 0; index -= 1) {
|
|
1970
|
+
const currentIndex = nextIndexById.get(visibleIds[index]);
|
|
1971
|
+
if (currentIndex != null) {
|
|
1972
|
+
insertIndex = currentIndex + 1;
|
|
1973
|
+
break;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
if (insertIndex == null) for (let index = visibleIndex + 1; index < visibleIds.length; index += 1) {
|
|
1977
|
+
const currentIndex = nextIndexById.get(visibleIds[index]);
|
|
1978
|
+
if (currentIndex != null) {
|
|
1979
|
+
insertIndex = currentIndex;
|
|
1980
|
+
break;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
nextIds.splice(insertIndex ?? (visibleIndex === 0 ? 0 : nextIds.length), 0, entityId);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
if (currentIds.length === nextIds.length && currentIds.every((id, index) => id === nextIds[index])) return;
|
|
1987
|
+
this.viewDataCache.invalidate(connection.owner);
|
|
1988
|
+
this.store.merge(connection.owner, { [connection.field]: createNodeRefsForIds(nextIds, current) }, [connection.field]);
|
|
1989
|
+
}
|
|
1990
|
+
getConnectionInsertIndex(listState, type, targetCursor) {
|
|
1991
|
+
if (type === "prependEdge" || type === "prependNode") return 0;
|
|
1992
|
+
if (type === "appendEdge" || type === "appendNode") return listState.ids.length;
|
|
1993
|
+
if (targetCursor && listState.cursors) {
|
|
1994
|
+
const index = listState.cursors.indexOf(targetCursor);
|
|
1995
|
+
if (index >= 0) return type === "insertEdgeBefore" ? index : index + 1;
|
|
1996
|
+
}
|
|
1997
|
+
return null;
|
|
1998
|
+
}
|
|
1999
|
+
removeEntityFromListState(listState, entityId) {
|
|
2000
|
+
const ids = [];
|
|
2001
|
+
const cursors = listState.cursors ? [] : void 0;
|
|
2002
|
+
listState.ids.forEach((id, index) => {
|
|
2003
|
+
if (id === entityId) return;
|
|
2004
|
+
ids.push(id);
|
|
2005
|
+
if (cursors) cursors.push(listState.cursors?.[index]);
|
|
2006
|
+
});
|
|
2007
|
+
return {
|
|
2008
|
+
...listState,
|
|
2009
|
+
cursors,
|
|
2010
|
+
ids,
|
|
2011
|
+
liveAfterIds: removeListId(listState.liveAfterIds, entityId),
|
|
2012
|
+
liveBeforeIds: removeListId(listState.liveBeforeIds, entityId),
|
|
2013
|
+
pendingAfterIds: removeListId(listState.pendingAfterIds, entityId),
|
|
2014
|
+
pendingBeforeIds: removeListId(listState.pendingBeforeIds, entityId)
|
|
2015
|
+
};
|
|
2016
|
+
}
|
|
1244
2017
|
mergeListState(previous, incomingIds, incomingCursors, incomingPagination, options) {
|
|
1245
2018
|
const existingIds = previous?.ids ?? [];
|
|
1246
2019
|
const existingSet = new Set(existingIds);
|
|
1247
2020
|
const isBackward = options.direction === "backward";
|
|
1248
2021
|
const mergeIds = () => {
|
|
1249
2022
|
if (!previous) return [...incomingIds];
|
|
2023
|
+
if (!options.hasCursorArg && options.replace) return [...incomingIds];
|
|
1250
2024
|
if (!options.hasCursorArg) {
|
|
1251
2025
|
const incomingSet = new Set(incomingIds);
|
|
1252
|
-
const remaining = existingIds.filter((id
|
|
2026
|
+
const remaining = existingIds.filter((id) => !incomingSet.has(id));
|
|
1253
2027
|
return isBackward ? [...remaining, ...incomingIds] : [...incomingIds, ...remaining];
|
|
1254
2028
|
}
|
|
1255
|
-
const newIds = incomingIds.filter((id
|
|
2029
|
+
const newIds = incomingIds.filter((id) => !existingSet.has(id));
|
|
1256
2030
|
return isBackward ? [...newIds, ...existingIds] : [...existingIds, ...newIds];
|
|
1257
2031
|
};
|
|
1258
2032
|
const ids = mergeIds();
|
|
@@ -1264,18 +2038,25 @@ var FateClient = class {
|
|
|
1264
2038
|
incomingCursors.forEach((cursor, index) => {
|
|
1265
2039
|
if (cursor !== void 0) cursorMap.set(incomingIds[index], cursor);
|
|
1266
2040
|
});
|
|
1267
|
-
const cursors = hasIncomingCursor || Boolean(previous?.cursors) || options.hasCursorArg ? ids.map((id
|
|
2041
|
+
const cursors = hasIncomingCursor || Boolean(previous?.cursors) || options.hasCursorArg ? ids.map((id) => cursorMap.get(id)) : void 0;
|
|
1268
2042
|
const previousPagination = previous?.pagination;
|
|
1269
2043
|
const newPagination = incomingPagination;
|
|
2044
|
+
const pagination = previousPagination || newPagination ? {
|
|
2045
|
+
hasNext: !!(newPagination?.hasNext ?? previousPagination?.hasNext),
|
|
2046
|
+
hasPrevious: !!(newPagination?.hasPrevious ?? previousPagination?.hasPrevious),
|
|
2047
|
+
nextCursor: newPagination?.nextCursor ?? previousPagination?.nextCursor,
|
|
2048
|
+
previousCursor: newPagination?.previousCursor ?? previousPagination?.previousCursor
|
|
2049
|
+
} : void 0;
|
|
1270
2050
|
return {
|
|
2051
|
+
backwardPageLimit: !options.hasCursorArg && isBackward && options.pageLimit != null ? Math.max(previous?.backwardPageLimit ?? 0, options.pageLimit) : previous?.backwardPageLimit,
|
|
1271
2052
|
cursors,
|
|
2053
|
+
forwardPageLimit: !options.hasCursorArg && !isBackward && options.pageLimit != null ? Math.max(previous?.forwardPageLimit ?? 0, options.pageLimit) : previous?.forwardPageLimit,
|
|
1272
2054
|
ids,
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
} : void 0
|
|
2055
|
+
liveAfterIds: dropCanonicalListIds(previous?.liveAfterIds, ids),
|
|
2056
|
+
liveBeforeIds: dropCanonicalListIds(previous?.liveBeforeIds, ids),
|
|
2057
|
+
pagination,
|
|
2058
|
+
pendingAfterIds: dropCanonicalListIds(previous?.pendingAfterIds, ids),
|
|
2059
|
+
pendingBeforeIds: dropCanonicalListIds(previous?.pendingBeforeIds, ids)
|
|
1279
2060
|
};
|
|
1280
2061
|
}
|
|
1281
2062
|
registerPendingOptimisticMutation(entityId, promise) {
|
|
@@ -1291,10 +2072,12 @@ var FateClient = class {
|
|
|
1291
2072
|
if (!current) return;
|
|
1292
2073
|
current.delete(trackedPromise);
|
|
1293
2074
|
if (current.size === 0) this.pendingOptimisticMutations.delete(entityId);
|
|
2075
|
+
this.runPendingGarbageCollection();
|
|
1294
2076
|
});
|
|
1295
2077
|
}
|
|
1296
2078
|
resolveOptimisticEntity(optimisticEntityId, resolvedEntityId) {
|
|
1297
2079
|
this.optimisticEntityResolutions.set(optimisticEntityId, resolvedEntityId);
|
|
2080
|
+
this.store.replaceListEntityId(optimisticEntityId, resolvedEntityId);
|
|
1298
2081
|
}
|
|
1299
2082
|
registerOptimisticUpdate(entityId, select) {
|
|
1300
2083
|
if (!entityId || select.size === 0) return null;
|
|
@@ -1322,6 +2105,7 @@ var FateClient = class {
|
|
|
1322
2105
|
entityTokens.delete(token);
|
|
1323
2106
|
if (entityTokens.size === 0) this.optimisticByEntity.delete(entry.entityId);
|
|
1324
2107
|
}
|
|
2108
|
+
this.runPendingGarbageCollection();
|
|
1325
2109
|
}
|
|
1326
2110
|
getPendingOptimisticMask(entityId, options = {}) {
|
|
1327
2111
|
if (!entityId) return null;
|
|
@@ -1349,43 +2133,40 @@ var FateClient = class {
|
|
|
1349
2133
|
for (const path of select) if (!isCovered(pendingMask, path)) filtered.add(path);
|
|
1350
2134
|
return filtered;
|
|
1351
2135
|
}
|
|
1352
|
-
async loadConnection(view
|
|
2136
|
+
async loadConnection(view, connection, args, options = {}) {
|
|
1353
2137
|
const direction = options.direction ?? "forward";
|
|
1354
2138
|
if (connection.root) {
|
|
1355
2139
|
if (!this.transport.fetchList) throw new Error(`fate: transport does not support list fetching. Please add support for 'fetchList' in your transport.`);
|
|
1356
|
-
const requestArgs
|
|
2140
|
+
const requestArgs = omitUndefinedValues({
|
|
1357
2141
|
...connection.args,
|
|
1358
2142
|
...args
|
|
1359
|
-
};
|
|
1360
|
-
const { argsPayload
|
|
1361
|
-
const { items, pagination } = await this.transport.fetchList(connection.field, plan
|
|
2143
|
+
});
|
|
2144
|
+
const { argsPayload, plan } = resolveSelectionPlan(view, requestArgs);
|
|
2145
|
+
const { items, pagination } = await this.transport.fetchList(connection.field, plan.paths, argsPayload);
|
|
1362
2146
|
if (!items) return this.store.getListState(connection.key);
|
|
1363
|
-
const incomingIds
|
|
1364
|
-
const incomingCursors
|
|
2147
|
+
const incomingIds = [];
|
|
2148
|
+
const incomingCursors = [];
|
|
1365
2149
|
for (const entry of items) {
|
|
1366
|
-
const id
|
|
1367
|
-
incomingIds
|
|
1368
|
-
incomingCursors
|
|
2150
|
+
const id = this.write(connection.type, entry.node, plan.paths, void 0, plan, null, null, "none");
|
|
2151
|
+
incomingIds.push(id);
|
|
2152
|
+
incomingCursors.push(entry.cursor);
|
|
1369
2153
|
}
|
|
1370
|
-
const previous
|
|
1371
|
-
const
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
const nextListState$1 = this.mergeListState(previous$1, incomingIds$1, incomingCursors$1, pagination, {
|
|
1375
|
-
direction: isBackward ? "backward" : "forward",
|
|
1376
|
-
hasCursorArg
|
|
2154
|
+
const previous = this.store.getListState(connection.key);
|
|
2155
|
+
const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, pagination, {
|
|
2156
|
+
...getPaginationMergeInfo(requestArgs),
|
|
2157
|
+
replace: true
|
|
1377
2158
|
});
|
|
1378
|
-
this.registerRootList(connection.type, connection.key);
|
|
1379
|
-
this.store.setList(connection.key, nextListState
|
|
1380
|
-
return nextListState
|
|
2159
|
+
if (!connection.args) this.registerRootList(connection.type, connection.key);
|
|
2160
|
+
this.store.setList(connection.key, nextListState);
|
|
2161
|
+
return nextListState;
|
|
1381
2162
|
}
|
|
1382
2163
|
const owner = parseEntityId(connection.owner);
|
|
1383
|
-
const requestArgs = {
|
|
2164
|
+
const requestArgs = omitUndefinedValues({
|
|
1384
2165
|
...connection.args,
|
|
1385
2166
|
...args
|
|
1386
|
-
};
|
|
2167
|
+
});
|
|
1387
2168
|
if (requestArgs.id === void 0 && owner.id) requestArgs.id = owner.id;
|
|
1388
|
-
const { argsPayload, plan } =
|
|
2169
|
+
const { argsPayload, plan } = resolveSelectionPlan(view, requestArgs);
|
|
1389
2170
|
const nodeSelection = plan.paths;
|
|
1390
2171
|
const scopedArgsPayload = argsPayload ? scopeArgsPayload(argsPayload, connection.field) : void 0;
|
|
1391
2172
|
const parentSelection = /* @__PURE__ */ new Set();
|
|
@@ -1408,22 +2189,24 @@ var FateClient = class {
|
|
|
1408
2189
|
if (!nodeType) throw new Error(`fate: Could not find node type for '${owner.type}.${connection.field}'.`);
|
|
1409
2190
|
for (const entry of connectionPayload.items) {
|
|
1410
2191
|
const { node } = entry;
|
|
1411
|
-
const id
|
|
1412
|
-
incomingIds.push(id
|
|
2192
|
+
const id = this.write(nodeType, node, nodeSelection, void 0, plan);
|
|
2193
|
+
incomingIds.push(id);
|
|
1413
2194
|
incomingCursors.push(entry.cursor);
|
|
1414
2195
|
}
|
|
1415
2196
|
const previous = this.store.getListState(connection.key);
|
|
1416
2197
|
const previousIds = previous?.ids ?? [];
|
|
1417
2198
|
const previousSet = new Set(previousIds);
|
|
1418
|
-
const newIds = incomingIds.filter((id
|
|
2199
|
+
const newIds = incomingIds.filter((id) => !previousSet.has(id));
|
|
1419
2200
|
const nextListState = this.mergeListState(previous, incomingIds, incomingCursors, connectionPayload.pagination, {
|
|
2201
|
+
...getPaginationMergeInfo(argsPayload),
|
|
1420
2202
|
direction,
|
|
1421
|
-
hasCursorArg: true
|
|
2203
|
+
hasCursorArg: true,
|
|
2204
|
+
replace: true
|
|
1422
2205
|
});
|
|
1423
2206
|
this.store.setList(connection.key, nextListState);
|
|
1424
2207
|
const current = this.store.read(connection.owner);
|
|
1425
2208
|
const existingField = Array.isArray(current?.[connection.field]) ? current?.[connection.field] || [] : [];
|
|
1426
|
-
const nodeRefs = newIds
|
|
2209
|
+
const nodeRefs = createNodeRefsForIds(newIds, void 0);
|
|
1427
2210
|
const nextField = direction === "forward" ? [...existingField, ...nodeRefs] : [...nodeRefs, ...existingField];
|
|
1428
2211
|
this.viewDataCache.invalidate(connection.owner);
|
|
1429
2212
|
this.store.merge(connection.owner, { [connection.field]: nextField }, [connection.field]);
|
|
@@ -1431,41 +2214,179 @@ var FateClient = class {
|
|
|
1431
2214
|
}
|
|
1432
2215
|
request(request, options) {
|
|
1433
2216
|
const mode = options?.mode ?? "cache-first";
|
|
1434
|
-
|
|
2217
|
+
return this.requestWithDescriptor(this.createRequestDescriptor(request), mode, { revalidateExisting: true });
|
|
2218
|
+
}
|
|
2219
|
+
requestForRender(request, options) {
|
|
2220
|
+
return this.prepareRequestForRender(request, options).promise;
|
|
2221
|
+
}
|
|
2222
|
+
prepareRequestForRender(request, options) {
|
|
2223
|
+
const mode = options?.mode ?? "cache-first";
|
|
2224
|
+
const descriptor = this.createRequestDescriptor(request);
|
|
2225
|
+
return {
|
|
2226
|
+
promise: this.requestWithDescriptor(descriptor, mode, { revalidateExisting: false }),
|
|
2227
|
+
requestKey: descriptor.key
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
requestWithDescriptor(descriptor, mode, { revalidateExisting }) {
|
|
2231
|
+
const requestKey = descriptor.key;
|
|
1435
2232
|
const existingRequest = this.requests.get(requestKey)?.get(mode);
|
|
1436
|
-
if (existingRequest)
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
case "stale-while-revalidate":
|
|
1440
|
-
promise = this.handleStoreAndNetworkRequest(request);
|
|
1441
|
-
break;
|
|
1442
|
-
case "cache-first":
|
|
1443
|
-
case "network-only":
|
|
1444
|
-
default:
|
|
1445
|
-
promise = this.executeRequest(request, mode === "network-only" ? { fetchAll: true } : void 0).then(() => this.getRequestResult(request));
|
|
1446
|
-
break;
|
|
2233
|
+
if (existingRequest) {
|
|
2234
|
+
if (revalidateExisting && existingRequest.status !== "pending" && (mode === "network-only" || mode === "stale-while-revalidate" || mode === "cache-first" && (existingRequest.status === "rejected" || existingRequest.status === "fulfilled" && !this.hasRequestData(existingRequest.descriptor)))) this.executeRequestHandle(existingRequest, mode);
|
|
2235
|
+
return existingRequest;
|
|
1447
2236
|
}
|
|
2237
|
+
const handle = new FateRequestPromise(descriptor, () => this.getRequestResultFromDescriptor(descriptor));
|
|
1448
2238
|
let requests = this.requests.get(requestKey);
|
|
1449
2239
|
if (!requests) {
|
|
1450
2240
|
requests = /* @__PURE__ */ new Map();
|
|
1451
2241
|
this.requests.set(requestKey, requests);
|
|
1452
2242
|
}
|
|
1453
|
-
requests.set(mode,
|
|
1454
|
-
|
|
2243
|
+
requests.set(mode, handle);
|
|
2244
|
+
this.executeRequestHandle(handle, mode);
|
|
2245
|
+
return handle;
|
|
1455
2246
|
}
|
|
1456
2247
|
releaseRequest(request, mode) {
|
|
1457
|
-
|
|
2248
|
+
this.releaseRequestKey(this.createRequestDescriptor(request).key, mode);
|
|
2249
|
+
}
|
|
2250
|
+
releaseRequestKey(requestKey, mode) {
|
|
1458
2251
|
const requests = this.requests.get(requestKey);
|
|
1459
2252
|
if (!requests) return;
|
|
1460
2253
|
requests.delete(mode);
|
|
1461
2254
|
if (requests.size === 0) this.requests.delete(requestKey);
|
|
1462
2255
|
}
|
|
2256
|
+
retainRequestKey(requestKey, mode) {
|
|
2257
|
+
const requests = this.requests.get(requestKey);
|
|
2258
|
+
const descriptor = (mode ? requests?.get(mode)?.descriptor : void 0) ?? requests?.values().next().value?.descriptor;
|
|
2259
|
+
return descriptor ? this.operationLifetime.retain(descriptor, () => this.scheduleGarbageCollection()) : { dispose: emptyDispose };
|
|
2260
|
+
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Keeps the records and lists reachable from a request alive until the
|
|
2263
|
+
* returned handle is disposed.
|
|
2264
|
+
*
|
|
2265
|
+
* `useRequest` calls this automatically while the component is mounted.
|
|
2266
|
+
* Use it directly for non-React request lifetimes, tests, or preloaded data
|
|
2267
|
+
* that must survive manual garbage collection.
|
|
2268
|
+
*/
|
|
2269
|
+
retain(request) {
|
|
2270
|
+
return this.operationLifetime.retain(this.createRequestDescriptor(request), () => this.scheduleGarbageCollection());
|
|
2271
|
+
}
|
|
2272
|
+
scheduleGarbageCollection() {
|
|
2273
|
+
if (this.gcScheduled) return;
|
|
2274
|
+
this.gcScheduled = true;
|
|
2275
|
+
queueMicrotask(() => {
|
|
2276
|
+
this.gcScheduled = false;
|
|
2277
|
+
this.gc();
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
/**
|
|
2281
|
+
* Removes records and lists that are not reachable from retained requests or
|
|
2282
|
+
* the release buffer.
|
|
2283
|
+
*
|
|
2284
|
+
* Garbage collection is scheduled automatically when retained requests are
|
|
2285
|
+
* released. Calling this method manually is mainly useful in tests and custom
|
|
2286
|
+
* memory-management flows.
|
|
2287
|
+
*/
|
|
2288
|
+
gc() {
|
|
2289
|
+
if (this.hasActiveOptimisticUpdates()) {
|
|
2290
|
+
this.gcPending = true;
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
this.gcPending = false;
|
|
2294
|
+
const markedLists = /* @__PURE__ */ new Set();
|
|
2295
|
+
const markedRecords = /* @__PURE__ */ new Set();
|
|
2296
|
+
const queue = [];
|
|
2297
|
+
const markRecord = (entityId) => {
|
|
2298
|
+
if (!entityId || markedRecords.has(entityId)) return;
|
|
2299
|
+
markedRecords.add(entityId);
|
|
2300
|
+
queue.push(entityId);
|
|
2301
|
+
};
|
|
2302
|
+
const markList = (key) => {
|
|
2303
|
+
markedLists.add(key);
|
|
2304
|
+
const listState = this.store.getListState(key);
|
|
2305
|
+
if (!listState) return;
|
|
2306
|
+
for (const id of listState.pendingBeforeIds ?? []) markRecord(id);
|
|
2307
|
+
for (const id of listState.ids) markRecord(id);
|
|
2308
|
+
for (const id of listState.pendingAfterIds ?? []) markRecord(id);
|
|
2309
|
+
};
|
|
2310
|
+
for (const descriptor of this.operationLifetime.getDescriptors()) for (const item of descriptor.items) {
|
|
2311
|
+
if (item.kind === "node" || item.kind === "nodes") {
|
|
2312
|
+
for (const raw of item.ids) markRecord(toEntityId(item.type, raw));
|
|
2313
|
+
continue;
|
|
2314
|
+
}
|
|
2315
|
+
if (item.kind === "query") {
|
|
2316
|
+
markRecord(this.rootRequests.get(item.queryKey));
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
if (item.kind === "list") markList(item.listKey);
|
|
2320
|
+
}
|
|
2321
|
+
for (let queueIndex = 0; queueIndex < queue.length; queueIndex += 1) {
|
|
2322
|
+
const entityId = queue[queueIndex];
|
|
2323
|
+
if (!entityId) continue;
|
|
2324
|
+
const record = this.store.read(entityId);
|
|
2325
|
+
if (!record) continue;
|
|
2326
|
+
this.markRecordReferences(record, markRecord);
|
|
2327
|
+
const { type } = parseEntityId(entityId);
|
|
2328
|
+
const config = this.types.get(type);
|
|
2329
|
+
if (!config?.fields) continue;
|
|
2330
|
+
for (const [field, descriptor] of Object.entries(config.fields)) {
|
|
2331
|
+
if (!descriptor || descriptor === "scalar" || !("listOf" in descriptor)) continue;
|
|
2332
|
+
for (const [listKey] of this.store.getListsForField(entityId, field)) markList(listKey);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
const swept = this.store.collectGarbage(markedRecords, markedLists, { onRecordDeleted: (id) => {
|
|
2336
|
+
this.viewDataCache.invalidate(id);
|
|
2337
|
+
this.stableRefs.delete(id);
|
|
2338
|
+
this.clearStalledRequestsForEntity(id);
|
|
2339
|
+
} });
|
|
2340
|
+
if (swept.records.size > 0) {
|
|
2341
|
+
for (const [key, entityId] of this.rootRequests.entries()) if (entityId && swept.records.has(entityId)) this.rootRequests.delete(key);
|
|
2342
|
+
}
|
|
2343
|
+
if (swept.lists.size > 0) for (const [type, keys] of this.rootLists.entries()) {
|
|
2344
|
+
for (const key of swept.lists) keys.delete(key);
|
|
2345
|
+
if (keys.size === 0) this.rootLists.delete(type);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
executeRequestHandle(handle, mode) {
|
|
2349
|
+
let execution;
|
|
2350
|
+
switch (mode) {
|
|
2351
|
+
case "stale-while-revalidate":
|
|
2352
|
+
execution = this.handleStoreAndNetworkRequest(handle.descriptor);
|
|
2353
|
+
break;
|
|
2354
|
+
default:
|
|
2355
|
+
execution = this.executeRequest(handle.descriptor, mode === "network-only" ? { fetchAll: true } : void 0).then(() => this.getRequestResultFromDescriptor(handle.descriptor));
|
|
2356
|
+
break;
|
|
2357
|
+
}
|
|
2358
|
+
handle.start(execution);
|
|
2359
|
+
}
|
|
2360
|
+
markRecordReferences(record, markRecord) {
|
|
2361
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2362
|
+
const walk = (value) => {
|
|
2363
|
+
if (isNodeRef(value)) {
|
|
2364
|
+
markRecord(getNodeRefId(value));
|
|
2365
|
+
return;
|
|
2366
|
+
}
|
|
2367
|
+
if (!value || typeof value !== "object") return;
|
|
2368
|
+
if (seen.has(value)) return;
|
|
2369
|
+
seen.add(value);
|
|
2370
|
+
if (Array.isArray(value)) {
|
|
2371
|
+
for (const item of value) walk(item);
|
|
2372
|
+
return;
|
|
2373
|
+
}
|
|
2374
|
+
for (const child of Object.values(value)) walk(child);
|
|
2375
|
+
};
|
|
2376
|
+
walk(record);
|
|
2377
|
+
}
|
|
2378
|
+
hasActiveOptimisticUpdates() {
|
|
2379
|
+
return this.optimisticMasks.size > 0 || this.optimisticByEntity.size > 0 || this.pendingOptimisticMutations.size > 0;
|
|
2380
|
+
}
|
|
2381
|
+
runPendingGarbageCollection() {
|
|
2382
|
+
if (this.gcPending && !this.hasActiveOptimisticUpdates()) this.gc();
|
|
2383
|
+
}
|
|
1463
2384
|
async handleStoreAndNetworkRequest(request) {
|
|
1464
2385
|
if (!this.hasRequestData(request)) {
|
|
1465
2386
|
await this.executeRequest(request, { fetchAll: true });
|
|
1466
|
-
return this.
|
|
2387
|
+
return this.getRequestResultFromDescriptor(request);
|
|
1467
2388
|
}
|
|
1468
|
-
const result = this.
|
|
2389
|
+
const result = this.getRequestResultFromDescriptor(request);
|
|
1469
2390
|
this.executeRequest(request, { fetchAll: true }).catch(() => {});
|
|
1470
2391
|
return result;
|
|
1471
2392
|
}
|
|
@@ -1474,104 +2395,125 @@ var FateClient = class {
|
|
|
1474
2395
|
if (!root) throw new Error(`fate: Unknown root request '${name}'.`);
|
|
1475
2396
|
return root.type;
|
|
1476
2397
|
}
|
|
2398
|
+
createRequestDescriptor(request) {
|
|
2399
|
+
return createRequestDescriptor(request, (name) => this.getRootType(name));
|
|
2400
|
+
}
|
|
2401
|
+
getRequestKey(request) {
|
|
2402
|
+
return this.createRequestDescriptor(request).key;
|
|
2403
|
+
}
|
|
2404
|
+
hasRootListData(item) {
|
|
2405
|
+
const listState = this.store.getListState(item.listKey);
|
|
2406
|
+
if (!listState) return false;
|
|
2407
|
+
const paginationArgs = getPaginationArgsInfo(item.argsPayload);
|
|
2408
|
+
if (!paginationArgs.hasCursorArg && !hasConnectionPageCoverage(listState, item.argsPayload, paginationArgs.direction)) return false;
|
|
2409
|
+
for (const id of listState.ids) if (this.store.missingForSelection(id, item.plan.paths).size > 0) return false;
|
|
2410
|
+
return true;
|
|
2411
|
+
}
|
|
1477
2412
|
async executeRequest(request, options = {}) {
|
|
1478
2413
|
const fetchAll = options.fetchAll ?? false;
|
|
1479
2414
|
const groups = /* @__PURE__ */ new Map();
|
|
1480
2415
|
const promises = [];
|
|
1481
|
-
for (const
|
|
1482
|
-
const
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
const
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
2416
|
+
for (const item of request.items) if (item.kind === "node" || item.kind === "nodes") {
|
|
2417
|
+
const fields = item.plan.paths;
|
|
2418
|
+
const fieldsSignature = [...fields].slice().sort().join(",");
|
|
2419
|
+
const argsSignature = [...item.plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join(",");
|
|
2420
|
+
const groupKey = `${item.type}#${fieldsSignature}|${argsSignature}`;
|
|
2421
|
+
let group = groups.get(groupKey);
|
|
2422
|
+
if (!group) {
|
|
2423
|
+
group = {
|
|
2424
|
+
fields,
|
|
2425
|
+
ids: [],
|
|
2426
|
+
plan: item.plan,
|
|
2427
|
+
type: item.type
|
|
2428
|
+
};
|
|
2429
|
+
groups.set(groupKey, group);
|
|
2430
|
+
}
|
|
2431
|
+
for (const raw of item.ids) {
|
|
2432
|
+
const entityId = toEntityId(item.type, raw);
|
|
2433
|
+
const missing = this.store.missingForSelection(entityId, fields);
|
|
2434
|
+
if (fetchAll || missing.size > 0) group.ids.push(raw);
|
|
2435
|
+
}
|
|
2436
|
+
} else {
|
|
2437
|
+
if (item.kind === "query") {
|
|
2438
|
+
const hasResult = this.rootRequests.has(item.queryKey);
|
|
2439
|
+
const entityId = this.rootRequests.get(item.queryKey);
|
|
2440
|
+
const missing = hasResult && entityId ? this.store.missingForSelection(entityId, item.plan.paths) : item.plan.paths;
|
|
2441
|
+
if (fetchAll || !hasResult || entityId && missing.size > 0) promises.push(this.fetchQueryAndNormalize(item));
|
|
2442
|
+
}
|
|
2443
|
+
if (item.kind !== "list") continue;
|
|
2444
|
+
if (!fetchAll && !getPaginationArgsInfo(item.argsPayload).hasCursorArg && this.hasRootListData(item)) continue;
|
|
2445
|
+
promises.push(this.fetchListAndNormalize(item));
|
|
1509
2446
|
}
|
|
1510
2447
|
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())]);
|
|
1511
2448
|
}
|
|
1512
2449
|
hasRequestData(request) {
|
|
1513
|
-
for (const
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
for (const raw of isNode ? [item.id] : item.ids) {
|
|
1519
|
-
const entityId = toEntityId(type, raw);
|
|
2450
|
+
for (const item of request.items) {
|
|
2451
|
+
if (item.kind === "node" || item.kind === "nodes") {
|
|
2452
|
+
const fields = item.plan.paths;
|
|
2453
|
+
for (const raw of item.ids) {
|
|
2454
|
+
const entityId = toEntityId(item.type, raw);
|
|
1520
2455
|
if (this.store.missingForSelection(entityId, fields).size > 0) return false;
|
|
1521
2456
|
}
|
|
1522
2457
|
continue;
|
|
1523
2458
|
}
|
|
1524
|
-
if (
|
|
1525
|
-
const
|
|
1526
|
-
const entityId = this.rootRequests.get(
|
|
1527
|
-
|
|
2459
|
+
if (item.kind === "query") {
|
|
2460
|
+
const hasResult = this.rootRequests.has(item.queryKey);
|
|
2461
|
+
const entityId = this.rootRequests.get(item.queryKey);
|
|
2462
|
+
const missing = hasResult && entityId ? this.store.missingForSelection(entityId, item.plan.paths) : item.plan.paths;
|
|
2463
|
+
if (!hasResult || entityId && missing.size > 0) return false;
|
|
1528
2464
|
continue;
|
|
1529
2465
|
}
|
|
1530
|
-
if (
|
|
2466
|
+
if (item.kind !== "list") continue;
|
|
2467
|
+
if (!this.hasRootListData(item)) return false;
|
|
1531
2468
|
}
|
|
1532
2469
|
return true;
|
|
1533
2470
|
}
|
|
1534
2471
|
getRequestResult(request) {
|
|
2472
|
+
return this.getRequestResultFromDescriptor(this.createRequestDescriptor(request));
|
|
2473
|
+
}
|
|
2474
|
+
getRequestResultFromDescriptor(request) {
|
|
1535
2475
|
const result = {};
|
|
1536
|
-
for (const
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
result[name] = this.ref(type, item.id, item.view);
|
|
2476
|
+
for (const item of request.items) {
|
|
2477
|
+
if (item.kind === "node") {
|
|
2478
|
+
result[item.name] = this.stableRefWithViewNames(item.type, item.ids[0], item.refViewNames);
|
|
1540
2479
|
continue;
|
|
1541
2480
|
}
|
|
1542
|
-
if (
|
|
1543
|
-
result[name] = item.ids.map((id
|
|
2481
|
+
if (item.kind === "nodes") {
|
|
2482
|
+
result[item.name] = item.ids.map((id) => this.stableRefWithViewNames(item.type, id, item.refViewNames));
|
|
1544
2483
|
continue;
|
|
1545
2484
|
}
|
|
1546
|
-
if (
|
|
1547
|
-
const entityId = this.rootRequests.get(
|
|
2485
|
+
if (item.kind === "query") {
|
|
2486
|
+
const entityId = this.rootRequests.get(item.queryKey);
|
|
1548
2487
|
if (entityId) {
|
|
1549
|
-
const { id
|
|
1550
|
-
result[name] =
|
|
1551
|
-
} else result[name] = null;
|
|
2488
|
+
const { id } = parseEntityId(entityId);
|
|
2489
|
+
result[item.name] = this.stableRefWithViewNames(item.type, id, item.refViewNames);
|
|
2490
|
+
} else result[item.name] = null;
|
|
1552
2491
|
continue;
|
|
1553
2492
|
}
|
|
1554
|
-
|
|
1555
|
-
const
|
|
1556
|
-
const
|
|
1557
|
-
const nodes =
|
|
1558
|
-
|
|
2493
|
+
if (item.kind !== "list") continue;
|
|
2494
|
+
const listState = this.store.getListState(item.listKey);
|
|
2495
|
+
const entries = getListEntries(listState);
|
|
2496
|
+
const nodes = entries.map(({ id }) => {
|
|
2497
|
+
const { id: rawId, type } = parseEntityId(id);
|
|
2498
|
+
return this.stableRefWithViewNames(type, rawId, item.nodeRefViewNames);
|
|
2499
|
+
});
|
|
2500
|
+
if (item.hasItems) {
|
|
1559
2501
|
const connection = {
|
|
1560
2502
|
items: nodes.map((node, index) => ({
|
|
1561
|
-
cursor:
|
|
2503
|
+
cursor: entries[index]?.cursor,
|
|
1562
2504
|
node
|
|
1563
2505
|
})),
|
|
1564
2506
|
pagination: listState?.pagination
|
|
1565
2507
|
};
|
|
1566
|
-
const { argsPayload } = this.resolveSelection(item.list, item.args);
|
|
1567
2508
|
const metadata = {
|
|
1568
|
-
args: argsPayload,
|
|
1569
|
-
field: name,
|
|
1570
|
-
key:
|
|
1571
|
-
|
|
1572
|
-
|
|
2509
|
+
args: item.argsPayload,
|
|
2510
|
+
field: item.name,
|
|
2511
|
+
key: item.listKey,
|
|
2512
|
+
live: item.plan.live.get(""),
|
|
2513
|
+
owner: item.name,
|
|
2514
|
+
procedure: item.name,
|
|
1573
2515
|
root: true,
|
|
1574
|
-
type
|
|
2516
|
+
type: item.type
|
|
1575
2517
|
};
|
|
1576
2518
|
Object.defineProperty(connection, ConnectionTag, {
|
|
1577
2519
|
configurable: false,
|
|
@@ -1579,10 +2521,10 @@ var FateClient = class {
|
|
|
1579
2521
|
value: metadata,
|
|
1580
2522
|
writable: false
|
|
1581
2523
|
});
|
|
1582
|
-
result[name] = connection;
|
|
2524
|
+
result[item.name] = connection;
|
|
1583
2525
|
continue;
|
|
1584
2526
|
}
|
|
1585
|
-
result[name] = nodes;
|
|
2527
|
+
result[item.name] = nodes;
|
|
1586
2528
|
}
|
|
1587
2529
|
return result;
|
|
1588
2530
|
}
|
|
@@ -1591,42 +2533,34 @@ var FateClient = class {
|
|
|
1591
2533
|
const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
|
|
1592
2534
|
for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
|
|
1593
2535
|
}
|
|
1594
|
-
async fetchQueryAndNormalize(
|
|
1595
|
-
if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${name}'.`);
|
|
1596
|
-
const record = await this.transport.fetchQuery(name, plan.paths, argsPayload);
|
|
1597
|
-
if (!record || typeof record !== "object")
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
2536
|
+
async fetchQueryAndNormalize(item) {
|
|
2537
|
+
if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${item.name}'.`);
|
|
2538
|
+
const record = await this.transport.fetchQuery(item.name, item.plan.paths, item.argsPayload);
|
|
2539
|
+
if (!record || typeof record !== "object") {
|
|
2540
|
+
this.rootRequests.set(item.queryKey, null);
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
const entityId = this.writeEntity(item.type, record, item.plan.paths, void 0, item.plan);
|
|
2544
|
+
this.rootRequests.set(item.queryKey, entityId);
|
|
2545
|
+
}
|
|
2546
|
+
async fetchListAndNormalize(item) {
|
|
2547
|
+
if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${item.name}'.`);
|
|
2548
|
+
const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
|
|
1606
2549
|
const ids = [];
|
|
1607
2550
|
const cursors = [];
|
|
1608
2551
|
for (const entry of items) {
|
|
1609
|
-
const id
|
|
1610
|
-
ids.push(id
|
|
2552
|
+
const id = this.writeEntity(item.type, entry.node, item.plan.paths, void 0, item.plan);
|
|
2553
|
+
ids.push(id);
|
|
1611
2554
|
cursors.push(entry.cursor);
|
|
1612
2555
|
}
|
|
1613
|
-
this.registerRootList(type,
|
|
1614
|
-
this.store.
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
});
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
const plan = getSelectionPlan(view$1, null);
|
|
1622
|
-
const argsPayload = combineArgsPayload(args, resolvedArgsFromPlan(plan));
|
|
1623
|
-
if (argsPayload) applyArgsPayloadToPlan(plan, argsPayload);
|
|
1624
|
-
return {
|
|
1625
|
-
argsPayload,
|
|
1626
|
-
plan
|
|
1627
|
-
};
|
|
1628
|
-
}
|
|
1629
|
-
writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert) {
|
|
2556
|
+
if (!item.argsPayload) this.registerRootList(item.type, item.listKey);
|
|
2557
|
+
const previous = this.store.getListState(item.listKey);
|
|
2558
|
+
this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
|
|
2559
|
+
...getPaginationMergeInfo(item.argsPayload),
|
|
2560
|
+
replace: true
|
|
2561
|
+
}));
|
|
2562
|
+
}
|
|
2563
|
+
writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
|
|
1630
2564
|
const config = this.types.get(type);
|
|
1631
2565
|
if (!config) throw new Error(`fate: Found unknown entity type '${type}' in normalization.`);
|
|
1632
2566
|
const entityId = toEntityId(type, config.getId(record));
|
|
@@ -1638,7 +2572,7 @@ var FateClient = class {
|
|
|
1638
2572
|
const fieldArgs = plan?.args.get(fieldPath);
|
|
1639
2573
|
const isFieldBlocked = blockedMask ? isCovered(blockedMask, fieldPath) : false;
|
|
1640
2574
|
if (relationDescriptor === "scalar") {
|
|
1641
|
-
if (isFieldBlocked) continue;
|
|
2575
|
+
if (isFieldBlocked || !Object.prototype.hasOwnProperty.call(record, key)) continue;
|
|
1642
2576
|
result[key] = value;
|
|
1643
2577
|
} else if (relationDescriptor && typeof relationDescriptor === "object" && "type" in relationDescriptor) {
|
|
1644
2578
|
if (isFieldBlocked) continue;
|
|
@@ -1652,7 +2586,7 @@ var FateClient = class {
|
|
|
1652
2586
|
const childConfig = this.types.get(childType);
|
|
1653
2587
|
if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
|
|
1654
2588
|
result[key] = createNodeRef(toEntityId(childType, childConfig.getId(value)));
|
|
1655
|
-
this.writeEntity(childType, value, childPaths, snapshots, plan, fieldPath, blockedMask);
|
|
2589
|
+
this.writeEntity(childType, value, childPaths, snapshots, plan, fieldPath, blockedMask, void 0, listSnapshots);
|
|
1656
2590
|
}
|
|
1657
2591
|
} else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
|
|
1658
2592
|
if (isFieldBlocked) continue;
|
|
@@ -1667,9 +2601,9 @@ var FateClient = class {
|
|
|
1667
2601
|
const connection = (() => {
|
|
1668
2602
|
if (Array.isArray(value)) return { items: value.map((item) => ({ node: item })) };
|
|
1669
2603
|
if (value && typeof value === "object") {
|
|
1670
|
-
const record
|
|
1671
|
-
if (Array.isArray(record
|
|
1672
|
-
items: record
|
|
2604
|
+
const record = value;
|
|
2605
|
+
if (Array.isArray(record.items)) return {
|
|
2606
|
+
items: record.items.map((node) => {
|
|
1673
2607
|
if (node && typeof node === "object" && "node" in node) {
|
|
1674
2608
|
const itemRecord = node;
|
|
1675
2609
|
return {
|
|
@@ -1679,7 +2613,7 @@ var FateClient = class {
|
|
|
1679
2613
|
}
|
|
1680
2614
|
return { node };
|
|
1681
2615
|
}),
|
|
1682
|
-
pagination: record
|
|
2616
|
+
pagination: record.pagination
|
|
1683
2617
|
};
|
|
1684
2618
|
}
|
|
1685
2619
|
return null;
|
|
@@ -1710,7 +2644,7 @@ var FateClient = class {
|
|
|
1710
2644
|
}
|
|
1711
2645
|
if (node && typeof node === "object") {
|
|
1712
2646
|
const childId = toEntityId(childType, childConfig.getId(node));
|
|
1713
|
-
this.writeEntity(childType, node, nodeSelection, snapshots, plan, fieldPath, blockedMask);
|
|
2647
|
+
this.writeEntity(childType, node, nodeSelection, snapshots, plan, fieldPath, blockedMask, void 0, listSnapshots);
|
|
1714
2648
|
ids.push(childId);
|
|
1715
2649
|
continue;
|
|
1716
2650
|
}
|
|
@@ -1718,13 +2652,9 @@ var FateClient = class {
|
|
|
1718
2652
|
const listKey = getListKey(entityId, key, fieldArgs?.hash);
|
|
1719
2653
|
const previousList = this.store.getListState(listKey);
|
|
1720
2654
|
const argsValue = fieldArgs?.value;
|
|
1721
|
-
const
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1724
|
-
direction: isBackward ? "backward" : "forward",
|
|
1725
|
-
hasCursorArg
|
|
1726
|
-
});
|
|
1727
|
-
result[key] = nextListState.ids.map((id$1) => createNodeRef(id$1));
|
|
2655
|
+
const nextListState = this.mergeListState(previousList, ids, cursors, connection.pagination, getPaginationMergeInfo(argsValue));
|
|
2656
|
+
const listChanged = !areListStatesEqual(previousList, nextListState);
|
|
2657
|
+
result[key] = createNodeRefsForIds(nextListState.ids, this.store.read(entityId)?.[key], { reuseCurrentArray: !listChanged });
|
|
1728
2658
|
this.store.setList(listKey, nextListState);
|
|
1729
2659
|
}
|
|
1730
2660
|
} else result[key] = value;
|
|
@@ -1737,11 +2667,11 @@ var FateClient = class {
|
|
|
1737
2667
|
if (snapshots && !snapshots.has(entityId)) snapshots.set(entityId, this.store.snapshot(entityId));
|
|
1738
2668
|
this.viewDataCache.invalidate(entityId);
|
|
1739
2669
|
this.store.merge(entityId, result, select);
|
|
1740
|
-
this.linkParentLists(type, entityId, result, snapshots, insert ?? "after");
|
|
1741
|
-
if (!pathPrefix && insert) this.insertIntoRootLists(type, entityId, insert);
|
|
2670
|
+
this.linkParentLists(type, entityId, result, snapshots, listSnapshots, insert ?? "after");
|
|
2671
|
+
if (!pathPrefix && insert) this.insertIntoRootLists(type, entityId, insert, listSnapshots);
|
|
1742
2672
|
return entityId;
|
|
1743
2673
|
}
|
|
1744
|
-
linkParentLists(type, entityId, record, snapshots, insert) {
|
|
2674
|
+
linkParentLists(type, entityId, record, snapshots, listSnapshots, insert) {
|
|
1745
2675
|
if (insert === "none") return;
|
|
1746
2676
|
const parents = this.parentLists.get(type);
|
|
1747
2677
|
if (!parents) return;
|
|
@@ -1756,28 +2686,31 @@ var FateClient = class {
|
|
|
1756
2686
|
if (current.some((item) => isNodeRef(item) && getNodeRefId(item) === entityId)) continue;
|
|
1757
2687
|
if (snapshots && !snapshots.has(parentId)) snapshots.set(parentId, this.store.snapshot(parentId));
|
|
1758
2688
|
this.viewDataCache.invalidate(parentId);
|
|
1759
|
-
const nextList = insert === "before" ? [createNodeRef(entityId), ...current] : [...current, createNodeRef(entityId)];
|
|
1760
|
-
const ids = nextList.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id$1) => id$1 != null);
|
|
1761
2689
|
const defaultListKey = getListKey(parentId, parent.field);
|
|
1762
2690
|
const defaultListState = this.store.getListState(defaultListKey);
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
2691
|
+
let nextField = current;
|
|
2692
|
+
if (defaultListState) {
|
|
2693
|
+
const { list: nextDefaultListState, pending } = this.applyListInsert(defaultListState, entityId, insert);
|
|
2694
|
+
if (nextDefaultListState !== defaultListState) {
|
|
2695
|
+
this.snapshotList(defaultListKey, listSnapshots);
|
|
2696
|
+
this.store.setList(defaultListKey, nextDefaultListState);
|
|
2697
|
+
}
|
|
2698
|
+
nextField = createNodeRefsForIds(pending ? getListEntries(nextDefaultListState).map(({ id }) => id) : nextDefaultListState.ids, current);
|
|
2699
|
+
} else {
|
|
2700
|
+
const currentIds = current.map((item) => isNodeRef(item) ? getNodeRefId(item) : null).filter((id) => id != null);
|
|
2701
|
+
const nextDefaultListState = { ids: insert === "before" ? [entityId, ...currentIds] : [...currentIds, entityId] };
|
|
2702
|
+
this.snapshotList(defaultListKey, listSnapshots);
|
|
2703
|
+
this.store.setList(defaultListKey, nextDefaultListState);
|
|
2704
|
+
nextField = createNodeRefsForIds(nextDefaultListState.ids, current);
|
|
2705
|
+
}
|
|
1769
2706
|
const registeredLists = this.store.getListsForField(parentId, parent.field).filter(([key]) => key !== defaultListKey);
|
|
1770
2707
|
for (const [listKey, listState] of registeredLists) {
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
this.store.setList(listKey,
|
|
1775
|
-
cursors: listCursors,
|
|
1776
|
-
ids: listIds,
|
|
1777
|
-
pagination: listState.pagination
|
|
1778
|
-
});
|
|
2708
|
+
const nextListState = this.applyListInsert(listState, entityId, insert).list;
|
|
2709
|
+
if (nextListState === listState) continue;
|
|
2710
|
+
this.snapshotList(listKey, listSnapshots);
|
|
2711
|
+
this.store.setList(listKey, nextListState);
|
|
1779
2712
|
}
|
|
1780
|
-
this.store.merge(parentId, { [parent.field]:
|
|
2713
|
+
this.store.merge(parentId, { [parent.field]: nextField }, [parent.field]);
|
|
1781
2714
|
}
|
|
1782
2715
|
}
|
|
1783
2716
|
readViewSelection(viewComposition, ref, entityId, plan, pathPrefix = null) {
|
|
@@ -1785,7 +2718,7 @@ var FateClient = class {
|
|
|
1785
2718
|
const ids = /* @__PURE__ */ new Set();
|
|
1786
2719
|
ids.add(entityId);
|
|
1787
2720
|
const coverageById = /* @__PURE__ */ new Map();
|
|
1788
|
-
const walk = (viewPayload, record
|
|
2721
|
+
const walk = (viewPayload, record, target, parentId, prefix) => {
|
|
1789
2722
|
for (const [key, selectionKind] of Object.entries(viewPayload)) {
|
|
1790
2723
|
if (isViewTag(key)) {
|
|
1791
2724
|
if (!target[ViewsTag]) assignViewTag(target, /* @__PURE__ */ new Set());
|
|
@@ -1795,17 +2728,17 @@ var FateClient = class {
|
|
|
1795
2728
|
coverageById.set(parentId, (coverageById.get(parentId) ?? /* @__PURE__ */ new Set()).add(key));
|
|
1796
2729
|
const fieldPath = prefix ? `${prefix}.${key}` : key;
|
|
1797
2730
|
const selectionType = typeof selectionKind;
|
|
1798
|
-
if (selectionType === "boolean" && selectionKind) target[key] = record
|
|
2731
|
+
if (selectionType === "boolean" && selectionKind) target[key] = record[key];
|
|
1799
2732
|
else if (selectionKind && selectionType === "object") {
|
|
1800
2733
|
const selectionValue = selectionKind;
|
|
1801
2734
|
const { args: selectionArgs, ...selectionWithoutArgs } = selectionValue;
|
|
1802
2735
|
if (Boolean(selectionArgs) && typeof selectionArgs === "object" && Object.keys(selectionWithoutArgs).length === 0) {
|
|
1803
|
-
target[key] = record
|
|
2736
|
+
target[key] = record[key];
|
|
1804
2737
|
continue;
|
|
1805
2738
|
}
|
|
1806
2739
|
if (!(key in target)) target[key] = {};
|
|
1807
2740
|
const nextSelection = Object.keys(selectionWithoutArgs).length ? selectionWithoutArgs : selectionValue;
|
|
1808
|
-
const value = record
|
|
2741
|
+
const value = record[key];
|
|
1809
2742
|
if (value == null) {
|
|
1810
2743
|
target[key] = null;
|
|
1811
2744
|
continue;
|
|
@@ -1815,23 +2748,26 @@ var FateClient = class {
|
|
|
1815
2748
|
const fieldArgs = plan.args.get(fieldPath);
|
|
1816
2749
|
const listKey = getListKey(parentId, key, fieldArgs?.hash);
|
|
1817
2750
|
const listState = this.store.getListState(listKey);
|
|
1818
|
-
const connection = { items: value.map((item
|
|
1819
|
-
|
|
1820
|
-
|
|
2751
|
+
const connection = { items: (listState ? getListEntries(listState) : value.map((item) => ({
|
|
2752
|
+
cursor: void 0,
|
|
2753
|
+
id: isNodeRef(item) ? getNodeRefId(item) : null
|
|
2754
|
+
}))).map((entry, index) => {
|
|
2755
|
+
const entityId = entry.id;
|
|
2756
|
+
if (!entityId) return {
|
|
1821
2757
|
cursor: listState?.cursors?.[index],
|
|
1822
2758
|
node: null
|
|
1823
2759
|
};
|
|
1824
|
-
ids.add(entityId
|
|
1825
|
-
const record
|
|
1826
|
-
const { id
|
|
2760
|
+
ids.add(entityId);
|
|
2761
|
+
const record = this.store.read(entityId);
|
|
2762
|
+
const { id, type } = parseEntityId(entityId);
|
|
1827
2763
|
const node = {
|
|
1828
2764
|
__typename: type,
|
|
1829
|
-
id
|
|
2765
|
+
id
|
|
1830
2766
|
};
|
|
1831
|
-
if (record
|
|
1832
|
-
const
|
|
1833
|
-
if (selection.cursor === true)
|
|
1834
|
-
return
|
|
2767
|
+
if (record) walk(selection.node, record, node, entityId, fieldPath);
|
|
2768
|
+
const itemResult = { node: record ? node : null };
|
|
2769
|
+
if (selection.cursor === true) itemResult.cursor = entry.cursor;
|
|
2770
|
+
return itemResult;
|
|
1835
2771
|
}) };
|
|
1836
2772
|
if ("pagination" in nextSelection && nextSelection.pagination) {
|
|
1837
2773
|
const paginationSelection = nextSelection.pagination;
|
|
@@ -1858,13 +2794,14 @@ var FateClient = class {
|
|
|
1858
2794
|
const metadata = {
|
|
1859
2795
|
args: (() => {
|
|
1860
2796
|
if (!fieldArgs?.value && ownerRawId === void 0) return;
|
|
1861
|
-
const value
|
|
1862
|
-
if (ownerRawId !== void 0) value
|
|
1863
|
-
return value
|
|
2797
|
+
const value = fieldArgs?.value ? { ...fieldArgs.value } : {};
|
|
2798
|
+
if (ownerRawId !== void 0) value.id = ownerRawId;
|
|
2799
|
+
return value;
|
|
1864
2800
|
})(),
|
|
1865
2801
|
field: key,
|
|
1866
2802
|
hash: fieldArgs?.hash,
|
|
1867
2803
|
key: listKey,
|
|
2804
|
+
live: plan.live.get(fieldPath),
|
|
1868
2805
|
owner: parentId,
|
|
1869
2806
|
procedure: `${parentType}.${key}`,
|
|
1870
2807
|
root: false,
|
|
@@ -1879,31 +2816,31 @@ var FateClient = class {
|
|
|
1879
2816
|
}
|
|
1880
2817
|
target[key] = connection;
|
|
1881
2818
|
} else target[key] = value.map((item) => {
|
|
1882
|
-
const entityId
|
|
1883
|
-
if (!entityId
|
|
1884
|
-
ids.add(entityId
|
|
1885
|
-
const record
|
|
1886
|
-
const { id
|
|
1887
|
-
if (record
|
|
2819
|
+
const entityId = isNodeRef(item) ? getNodeRefId(item) : null;
|
|
2820
|
+
if (!entityId) return item;
|
|
2821
|
+
ids.add(entityId);
|
|
2822
|
+
const record = this.store.read(entityId);
|
|
2823
|
+
const { id, type } = parseEntityId(entityId);
|
|
2824
|
+
if (record) {
|
|
1888
2825
|
const node = {
|
|
1889
2826
|
__typename: type,
|
|
1890
|
-
id
|
|
2827
|
+
id
|
|
1891
2828
|
};
|
|
1892
|
-
walk(nextSelection, record
|
|
2829
|
+
walk(nextSelection, record, node, entityId, fieldPath);
|
|
1893
2830
|
return node;
|
|
1894
2831
|
}
|
|
1895
2832
|
return null;
|
|
1896
2833
|
});
|
|
1897
2834
|
else if (isNodeRef(value)) {
|
|
1898
|
-
const entityId
|
|
1899
|
-
ids.add(entityId
|
|
1900
|
-
const relatedRecord = this.store.read(entityId
|
|
1901
|
-
const { id
|
|
2835
|
+
const entityId = getNodeRefId(value);
|
|
2836
|
+
ids.add(entityId);
|
|
2837
|
+
const relatedRecord = this.store.read(entityId);
|
|
2838
|
+
const { id, type } = parseEntityId(entityId);
|
|
1902
2839
|
const targetRecord = target[key];
|
|
1903
|
-
targetRecord.id = id
|
|
2840
|
+
targetRecord.id = id;
|
|
1904
2841
|
targetRecord.__typename = type;
|
|
1905
|
-
if (relatedRecord) walk(nextSelection, relatedRecord, targetRecord, entityId
|
|
1906
|
-
} else walk(nextSelection,
|
|
2842
|
+
if (relatedRecord) walk(nextSelection, relatedRecord, targetRecord, entityId, fieldPath);
|
|
2843
|
+
} else walk(nextSelection, value, target[key], entityId, fieldPath);
|
|
1907
2844
|
}
|
|
1908
2845
|
}
|
|
1909
2846
|
};
|
|
@@ -1924,11 +2861,39 @@ var FateClient = class {
|
|
|
1924
2861
|
pendingKey(entityId, missingFields) {
|
|
1925
2862
|
return `${this.pendingPrefix(entityId)}${[...missingFields].sort().join("|")}`;
|
|
1926
2863
|
}
|
|
2864
|
+
liveSubscriptionKey(entityId, plan) {
|
|
2865
|
+
return `${entityId}|${[...plan.paths].sort().join("|")}|${[...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join("|")}`;
|
|
2866
|
+
}
|
|
2867
|
+
liveConnectionSubscriptionKey(connection, plan, args) {
|
|
2868
|
+
const paths = [...plan.paths].sort().join("|");
|
|
2869
|
+
const selectionArgs = [...plan.args.entries()].map(([path, entry]) => `${path}:${entry.hash}`).sort().join("|");
|
|
2870
|
+
return `connection|${connection.procedure}|${connection.type}|${hashArgs(args ?? {})}|${paths}|${selectionArgs}`;
|
|
2871
|
+
}
|
|
2872
|
+
releaseLiveSubscription(key) {
|
|
2873
|
+
const subscription = this.liveSubscriptions.get(key);
|
|
2874
|
+
if (!subscription) return;
|
|
2875
|
+
if (subscription.count <= 0) return;
|
|
2876
|
+
subscription.count -= 1;
|
|
2877
|
+
if (subscription.count > 0) return;
|
|
2878
|
+
subscription.releaseScheduled = true;
|
|
2879
|
+
queueMicrotask(() => {
|
|
2880
|
+
const current = this.liveSubscriptions.get(key);
|
|
2881
|
+
if (!current || !current.releaseScheduled || current.count > 0) return;
|
|
2882
|
+
current.unsubscribe();
|
|
2883
|
+
this.liveSubscriptions.delete(key);
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
reportLiveError(error) {
|
|
2887
|
+
const onLiveError = this.onLiveError;
|
|
2888
|
+
if (!onLiveError) return;
|
|
2889
|
+
queueMicrotask(() => {
|
|
2890
|
+
onLiveError(error);
|
|
2891
|
+
});
|
|
2892
|
+
}
|
|
1927
2893
|
};
|
|
1928
2894
|
function createClient(options) {
|
|
1929
2895
|
return new FateClient(options);
|
|
1930
2896
|
}
|
|
1931
|
-
|
|
1932
2897
|
//#endregion
|
|
1933
2898
|
//#region src/transport.ts
|
|
1934
2899
|
/**
|
|
@@ -1950,7 +2915,7 @@ function createTRPCTransport({ byId, client, lists, mutations, queries }) {
|
|
|
1950
2915
|
if (!lists) throw new Error(`fate(trpc): No list resolvers configured; cannot call "${procedure}".`);
|
|
1951
2916
|
const resolver = lists[procedure];
|
|
1952
2917
|
if (!resolver) throw new Error(`fate(trpc): Missing list resolver for procedure "${procedure}"`);
|
|
1953
|
-
return resolver(client)({
|
|
2918
|
+
return await resolver(client)({
|
|
1954
2919
|
args,
|
|
1955
2920
|
select: [...select]
|
|
1956
2921
|
});
|
|
@@ -1975,7 +2940,449 @@ function createTRPCTransport({ byId, client, lists, mutations, queries }) {
|
|
|
1975
2940
|
};
|
|
1976
2941
|
return transport;
|
|
1977
2942
|
}
|
|
1978
|
-
|
|
2943
|
+
//#endregion
|
|
2944
|
+
//#region src/httpTransport.ts
|
|
2945
|
+
const reportSubscriptionError = (subscription, error) => {
|
|
2946
|
+
subscription.handlers.onError?.(error);
|
|
2947
|
+
};
|
|
2948
|
+
const notifyLiveData = (handlers, record, select) => {
|
|
2949
|
+
if (select) {
|
|
2950
|
+
handlers.onData(record, select);
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
handlers.onData(record);
|
|
2954
|
+
};
|
|
2955
|
+
const protocolError = (message) => new FateRequestError(message.error.code, message.error.message, { issues: message.error.issues });
|
|
2956
|
+
const pathsIntersect = (left, right) => left === right || left.startsWith(`${right}.`) || right.startsWith(`${left}.`);
|
|
2957
|
+
const filterLiveSelection = (select, changed) => {
|
|
2958
|
+
if (!changed) return null;
|
|
2959
|
+
const changedPaths = changed.filter((path) => path.length > 0);
|
|
2960
|
+
if (changedPaths.length === 0) return [];
|
|
2961
|
+
const selected = select.filter((path) => changedPaths.some((changedPath) => pathsIntersect(path, changedPath)));
|
|
2962
|
+
if (selected.length === 0) return [];
|
|
2963
|
+
const result = new Set(selected);
|
|
2964
|
+
result.add("id");
|
|
2965
|
+
for (const path of select) {
|
|
2966
|
+
if (!path.endsWith(".id")) continue;
|
|
2967
|
+
const parentPath = path.slice(0, -3);
|
|
2968
|
+
if (selected.some((selectedPath) => pathsIntersect(selectedPath, parentPath))) result.add(path);
|
|
2969
|
+
}
|
|
2970
|
+
return [...result];
|
|
2971
|
+
};
|
|
2972
|
+
const hasSelectedDataPath = (value, segments) => {
|
|
2973
|
+
if (segments.length === 0) return true;
|
|
2974
|
+
if (Array.isArray(value)) return value.every((entry) => hasSelectedDataPath(entry, segments));
|
|
2975
|
+
if (!isRecord(value)) return false;
|
|
2976
|
+
const [field, ...rest] = segments;
|
|
2977
|
+
return field in value && hasSelectedDataPath(value[field], rest);
|
|
2978
|
+
};
|
|
2979
|
+
const canUseLivePayloadData = (data, select) => {
|
|
2980
|
+
if (!isRecord(data)) return false;
|
|
2981
|
+
for (const path of select) if (!hasSelectedDataPath(data, path.split("."))) return false;
|
|
2982
|
+
return true;
|
|
2983
|
+
};
|
|
2984
|
+
const defaultFetch = (input, init) => globalThis.fetch(input, init);
|
|
2985
|
+
const resolveHeaders = async (headers) => typeof headers === "function" ? await headers() : headers ?? {};
|
|
2986
|
+
const requestHeaders = async (defaults, headers) => {
|
|
2987
|
+
const result = new Headers(defaults);
|
|
2988
|
+
new Headers(await resolveHeaders(headers)).forEach((value, key) => {
|
|
2989
|
+
result.set(key, value);
|
|
2990
|
+
});
|
|
2991
|
+
return result;
|
|
2992
|
+
};
|
|
2993
|
+
const normalizeEndpoint = (url) => String(url).replace(/\/$/, "");
|
|
2994
|
+
const liveEndpoint = (url) => `${normalizeEndpoint(url)}/live`;
|
|
2995
|
+
const responseError = async (response) => {
|
|
2996
|
+
let message = response.statusText || `HTTP ${response.status}`;
|
|
2997
|
+
try {
|
|
2998
|
+
const payload = await response.clone().json();
|
|
2999
|
+
if (isRecord(payload) && Array.isArray(payload.results) && isRecord(payload.results[0]) && isRecord(payload.results[0].error) && typeof payload.results[0].error.message === "string") message = payload.results[0].error.message;
|
|
3000
|
+
} catch {
|
|
3001
|
+
try {
|
|
3002
|
+
const text = await response.text();
|
|
3003
|
+
if (text) message = text;
|
|
3004
|
+
} catch {}
|
|
3005
|
+
}
|
|
3006
|
+
return new FateRequestError(errorCodeFromStatus(response.status), message, { status: response.status });
|
|
3007
|
+
};
|
|
3008
|
+
const assertProtocolResponse = (value) => {
|
|
3009
|
+
if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.results) || value.results.some((entry) => !isRecord(entry) || typeof entry.id !== "string")) throw new Error("fate(http): Invalid protocol response.");
|
|
3010
|
+
return value;
|
|
3011
|
+
};
|
|
3012
|
+
const resultValue = (result) => {
|
|
3013
|
+
if (result.ok) return result.data;
|
|
3014
|
+
throw new FateRequestError(result.error.code, result.error.message, { issues: result.error.issues });
|
|
3015
|
+
};
|
|
3016
|
+
function createHTTPTransport({ eventSource, fetch: fetchImpl = defaultFetch, headers, live = true, liveRetryMs = 1e3, liveUrl, url }) {
|
|
3017
|
+
const endpoint = normalizeEndpoint(url);
|
|
3018
|
+
const liveEndpointUrl = liveUrl ? String(liveUrl) : liveEndpoint(endpoint);
|
|
3019
|
+
const liveConnector = typeof live === "function" ? live : void 0;
|
|
3020
|
+
const liveEnabled = live !== false;
|
|
3021
|
+
let nextId = 0;
|
|
3022
|
+
let pending = [];
|
|
3023
|
+
let scheduled = false;
|
|
3024
|
+
const enqueue = (operation) => new Promise((resolve, reject) => {
|
|
3025
|
+
pending.push({
|
|
3026
|
+
operation: {
|
|
3027
|
+
...operation,
|
|
3028
|
+
id: String(++nextId)
|
|
3029
|
+
},
|
|
3030
|
+
reject,
|
|
3031
|
+
resolve
|
|
3032
|
+
});
|
|
3033
|
+
if (!scheduled) {
|
|
3034
|
+
scheduled = true;
|
|
3035
|
+
queueMicrotask(flush);
|
|
3036
|
+
}
|
|
3037
|
+
});
|
|
3038
|
+
const flush = async () => {
|
|
3039
|
+
scheduled = false;
|
|
3040
|
+
const batch = pending;
|
|
3041
|
+
pending = [];
|
|
3042
|
+
if (!batch.length) return;
|
|
3043
|
+
try {
|
|
3044
|
+
const response = await fetchImpl(endpoint, {
|
|
3045
|
+
body: JSON.stringify({
|
|
3046
|
+
operations: batch.map((entry) => entry.operation),
|
|
3047
|
+
version: 1
|
|
3048
|
+
}),
|
|
3049
|
+
headers: await requestHeaders({ "content-type": "application/json" }, headers),
|
|
3050
|
+
method: "POST"
|
|
3051
|
+
});
|
|
3052
|
+
if (!response.ok) throw await responseError(response);
|
|
3053
|
+
const payload = assertProtocolResponse(await response.json());
|
|
3054
|
+
const results = new Map(payload.results.map((result) => [result.id, result]));
|
|
3055
|
+
for (const entry of batch) {
|
|
3056
|
+
const result = results.get(entry.operation.id);
|
|
3057
|
+
if (!result) {
|
|
3058
|
+
entry.reject(/* @__PURE__ */ new Error(`fate(http): Missing result for operation '${entry.operation.id}'.`));
|
|
3059
|
+
continue;
|
|
3060
|
+
}
|
|
3061
|
+
try {
|
|
3062
|
+
entry.resolve(resultValue(result));
|
|
3063
|
+
} catch (error) {
|
|
3064
|
+
entry.reject(error);
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
} catch (error) {
|
|
3068
|
+
for (const entry of batch) entry.reject(error);
|
|
3069
|
+
}
|
|
3070
|
+
};
|
|
3071
|
+
const transport = {
|
|
3072
|
+
fetchById(type, ids, select, args) {
|
|
3073
|
+
return enqueue({
|
|
3074
|
+
args,
|
|
3075
|
+
ids,
|
|
3076
|
+
kind: "byId",
|
|
3077
|
+
select: [...select],
|
|
3078
|
+
type
|
|
3079
|
+
});
|
|
3080
|
+
},
|
|
3081
|
+
fetchList(name, select, args) {
|
|
3082
|
+
return enqueue({
|
|
3083
|
+
args,
|
|
3084
|
+
kind: "list",
|
|
3085
|
+
name,
|
|
3086
|
+
select: [...select]
|
|
3087
|
+
});
|
|
3088
|
+
},
|
|
3089
|
+
fetchQuery(name, select, args) {
|
|
3090
|
+
return enqueue({
|
|
3091
|
+
args,
|
|
3092
|
+
kind: "query",
|
|
3093
|
+
name,
|
|
3094
|
+
select: [...select]
|
|
3095
|
+
});
|
|
3096
|
+
},
|
|
3097
|
+
mutate(name, input, select) {
|
|
3098
|
+
return enqueue({
|
|
3099
|
+
input,
|
|
3100
|
+
kind: "mutation",
|
|
3101
|
+
name,
|
|
3102
|
+
select: [...select]
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3105
|
+
};
|
|
3106
|
+
if (liveEnabled) {
|
|
3107
|
+
const liveSubscriptions = /* @__PURE__ */ new Map();
|
|
3108
|
+
let liveClient;
|
|
3109
|
+
let nativeLiveClient;
|
|
3110
|
+
let liveNextId = 0;
|
|
3111
|
+
const getLiveClient = () => {
|
|
3112
|
+
if (!liveConnector) throw new Error("fate(http): A live connector is required for topic subscriptions.");
|
|
3113
|
+
return liveClient ??= liveConnector(liveEndpointUrl, {
|
|
3114
|
+
eventSource,
|
|
3115
|
+
fetch: fetchImpl,
|
|
3116
|
+
headers,
|
|
3117
|
+
onError(error) {
|
|
3118
|
+
for (const subscription of new Set(liveSubscriptions.values())) subscription.handlers.onError?.(error);
|
|
3119
|
+
},
|
|
3120
|
+
retryDelay: liveRetryMs,
|
|
3121
|
+
withCredentials: true
|
|
3122
|
+
});
|
|
3123
|
+
};
|
|
3124
|
+
const getNativeLiveClient = () => {
|
|
3125
|
+
if (nativeLiveClient) return nativeLiveClient;
|
|
3126
|
+
const EventSourceCtor = eventSource ?? globalThis.EventSource;
|
|
3127
|
+
if (!EventSourceCtor) throw new Error("fate(http): EventSource is required for live views.");
|
|
3128
|
+
const connectionId = globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
3129
|
+
const operations = /* @__PURE__ */ new Map();
|
|
3130
|
+
const lastEventIds = /* @__PURE__ */ new Map();
|
|
3131
|
+
let opened = false;
|
|
3132
|
+
let resolveOpen;
|
|
3133
|
+
let rejectOpen;
|
|
3134
|
+
const open = new Promise((resolve, reject) => {
|
|
3135
|
+
resolveOpen = resolve;
|
|
3136
|
+
rejectOpen = reject;
|
|
3137
|
+
});
|
|
3138
|
+
const sourceUrl = new URL(liveEndpointUrl, typeof globalThis.location === "object" ? globalThis.location.href : "http://local");
|
|
3139
|
+
sourceUrl.searchParams.set("connectionId", connectionId);
|
|
3140
|
+
const source = new EventSourceCtor(sourceUrl.href, { withCredentials: true });
|
|
3141
|
+
const control = async (controlOperations) => {
|
|
3142
|
+
const response = await fetchImpl(liveEndpointUrl, {
|
|
3143
|
+
body: JSON.stringify({
|
|
3144
|
+
connectionId,
|
|
3145
|
+
operations: controlOperations,
|
|
3146
|
+
version: 1
|
|
3147
|
+
}),
|
|
3148
|
+
headers: await requestHeaders({ "content-type": "application/json" }, headers),
|
|
3149
|
+
method: "POST"
|
|
3150
|
+
});
|
|
3151
|
+
if (!response.ok) throw await responseError(response);
|
|
3152
|
+
const payload = assertProtocolResponse(await response.json());
|
|
3153
|
+
for (const result of payload.results) resultValue(result);
|
|
3154
|
+
};
|
|
3155
|
+
const withLastEventId = (operation) => {
|
|
3156
|
+
const lastEventId = lastEventIds.get(operation.id);
|
|
3157
|
+
return lastEventId ? {
|
|
3158
|
+
...operation,
|
|
3159
|
+
lastEventId
|
|
3160
|
+
} : operation;
|
|
3161
|
+
};
|
|
3162
|
+
const reportError = (error) => {
|
|
3163
|
+
for (const subscription of new Set(liveSubscriptions.values())) reportSubscriptionError(subscription, error);
|
|
3164
|
+
};
|
|
3165
|
+
source.addEventListener("open", () => {
|
|
3166
|
+
if (!opened) {
|
|
3167
|
+
opened = true;
|
|
3168
|
+
resolveOpen?.();
|
|
3169
|
+
return;
|
|
3170
|
+
}
|
|
3171
|
+
const resubscribe = [...operations.values()].map(withLastEventId);
|
|
3172
|
+
if (resubscribe.length > 0) control(resubscribe).catch(reportError);
|
|
3173
|
+
});
|
|
3174
|
+
const handleLiveMessage = (event) => {
|
|
3175
|
+
const message = JSON.parse(event.data);
|
|
3176
|
+
const subscription = liveSubscriptions.get(message.id);
|
|
3177
|
+
if (!subscription) return;
|
|
3178
|
+
const lastEventId = event.lastEventId;
|
|
3179
|
+
if (lastEventId) lastEventIds.set(message.id, lastEventId);
|
|
3180
|
+
if (message.kind === "error") {
|
|
3181
|
+
reportSubscriptionError(subscription, protocolError(message));
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
if (message.kind === "connection") {
|
|
3185
|
+
if ("onEvent" in subscription.handlers) subscription.handlers.onEvent(message.event);
|
|
3186
|
+
return;
|
|
3187
|
+
}
|
|
3188
|
+
if ("delete" in message.event && message.event.delete) {
|
|
3189
|
+
if ("onDelete" in subscription.handlers) subscription.handlers.onDelete?.(message.event.id ?? subscription.targetId);
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
3192
|
+
if ("onData" in subscription.handlers) notifyLiveData(subscription.handlers, message.event.data, message.event.select);
|
|
3193
|
+
};
|
|
3194
|
+
source.addEventListener("error", (event) => {
|
|
3195
|
+
if ("data" in event) {
|
|
3196
|
+
handleLiveMessage(event);
|
|
3197
|
+
return;
|
|
3198
|
+
}
|
|
3199
|
+
if (!opened) rejectOpen?.(event);
|
|
3200
|
+
reportError(event);
|
|
3201
|
+
});
|
|
3202
|
+
source.addEventListener("message", handleLiveMessage);
|
|
3203
|
+
source.addEventListener("next", handleLiveMessage);
|
|
3204
|
+
source.addEventListener("connection", handleLiveMessage);
|
|
3205
|
+
nativeLiveClient = {
|
|
3206
|
+
add(operation) {
|
|
3207
|
+
operations.set(operation.id, operation);
|
|
3208
|
+
open.then(() => control([withLastEventId(operation)])).catch((error) => {
|
|
3209
|
+
operations.delete(operation.id);
|
|
3210
|
+
liveSubscriptions.delete(operation.id);
|
|
3211
|
+
reportError(error);
|
|
3212
|
+
});
|
|
3213
|
+
},
|
|
3214
|
+
remove(id) {
|
|
3215
|
+
operations.delete(id);
|
|
3216
|
+
lastEventIds.delete(id);
|
|
3217
|
+
open.then(() => control([{
|
|
3218
|
+
id,
|
|
3219
|
+
kind: "unsubscribe"
|
|
3220
|
+
}])).catch(reportError);
|
|
3221
|
+
if (operations.size === 0) {
|
|
3222
|
+
source.close();
|
|
3223
|
+
nativeLiveClient = void 0;
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
};
|
|
3227
|
+
return nativeLiveClient;
|
|
3228
|
+
};
|
|
3229
|
+
const fetchLiveRecord = async (subscription, select = subscription.select) => {
|
|
3230
|
+
const [record] = await transport.fetchById(subscription.type, [subscription.targetId], select, subscription.args);
|
|
3231
|
+
return record;
|
|
3232
|
+
};
|
|
3233
|
+
const resolveConnectionNode = async (subscription, id) => {
|
|
3234
|
+
const [record] = await transport.fetchById(subscription.type, [id], subscription.select, subscription.selectionArgs);
|
|
3235
|
+
return record;
|
|
3236
|
+
};
|
|
3237
|
+
transport.subscribeById = (type, id, select, args, handlers) => {
|
|
3238
|
+
const liveId = String(++liveNextId);
|
|
3239
|
+
const subscription = {
|
|
3240
|
+
args,
|
|
3241
|
+
handlers,
|
|
3242
|
+
select: [...select],
|
|
3243
|
+
targetId: id,
|
|
3244
|
+
type
|
|
3245
|
+
};
|
|
3246
|
+
liveSubscriptions.set(liveId, subscription);
|
|
3247
|
+
if (!liveConnector) {
|
|
3248
|
+
getNativeLiveClient().add({
|
|
3249
|
+
args,
|
|
3250
|
+
entityId: id,
|
|
3251
|
+
id: liveId,
|
|
3252
|
+
kind: "subscribe",
|
|
3253
|
+
select: [...select],
|
|
3254
|
+
type
|
|
3255
|
+
});
|
|
3256
|
+
return () => {
|
|
3257
|
+
if (!liveSubscriptions.delete(liveId)) return;
|
|
3258
|
+
getNativeLiveClient().remove(liveId);
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
const unsubscribePromise = getLiveClient().subscribe({
|
|
3262
|
+
id: liveId,
|
|
3263
|
+
onEvent(event) {
|
|
3264
|
+
if (event.type === "delete") {
|
|
3265
|
+
handlers.onDelete?.(event.data.id ?? id);
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
const liveSelect = (Array.isArray(event.data.select) ? event.data.select : null) ?? filterLiveSelection(subscription.select, event.data.changed);
|
|
3269
|
+
if (liveSelect?.length === 0) return;
|
|
3270
|
+
if ("data" in event.data && event.data.data !== void 0) {
|
|
3271
|
+
if (!liveSelect || canUseLivePayloadData(event.data.data, liveSelect)) {
|
|
3272
|
+
notifyLiveData(handlers, event.data.data, liveSelect ?? void 0);
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
fetchLiveRecord(subscription, liveSelect ?? subscription.select).then((record) => {
|
|
3277
|
+
if (record == null) handlers.onDelete?.(id);
|
|
3278
|
+
else notifyLiveData(handlers, record, liveSelect ?? void 0);
|
|
3279
|
+
}).catch((error) => reportSubscriptionError(subscription, error));
|
|
3280
|
+
},
|
|
3281
|
+
topic: liveEntityTopic(type, id)
|
|
3282
|
+
}).catch((error) => {
|
|
3283
|
+
liveSubscriptions.delete(liveId);
|
|
3284
|
+
reportSubscriptionError(subscription, error);
|
|
3285
|
+
return null;
|
|
3286
|
+
});
|
|
3287
|
+
return () => {
|
|
3288
|
+
if (!liveSubscriptions.delete(liveId)) return;
|
|
3289
|
+
unsubscribePromise.then((unsubscribe) => unsubscribe?.());
|
|
3290
|
+
};
|
|
3291
|
+
};
|
|
3292
|
+
transport.subscribeConnection = (procedure, type, args, select, selectionArgs, handlers) => {
|
|
3293
|
+
const liveId = String(++liveNextId);
|
|
3294
|
+
const globalLiveId = `${liveId}:global`;
|
|
3295
|
+
const subscription = {
|
|
3296
|
+
args,
|
|
3297
|
+
handlers,
|
|
3298
|
+
procedure,
|
|
3299
|
+
select: [...select],
|
|
3300
|
+
selectionArgs,
|
|
3301
|
+
type
|
|
3302
|
+
};
|
|
3303
|
+
const handleEvent = (event) => {
|
|
3304
|
+
const data = isRecord(event.data) ? event.data : {};
|
|
3305
|
+
if (event.type === "invalidate") {
|
|
3306
|
+
handlers.onEvent({ type: "invalidate" });
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
const nodeType = typeof data.nodeType === "string" ? data.nodeType : type;
|
|
3310
|
+
const id = data.id;
|
|
3311
|
+
if (event.type === "deleteEdge") {
|
|
3312
|
+
if (typeof id === "string" || typeof id === "number") handlers.onEvent({
|
|
3313
|
+
id,
|
|
3314
|
+
nodeType,
|
|
3315
|
+
type: "deleteEdge"
|
|
3316
|
+
});
|
|
3317
|
+
else reportSubscriptionError(subscription, /* @__PURE__ */ new Error(`fate(http): Live connection delete event for '${procedure}' missed an id.`));
|
|
3318
|
+
return;
|
|
3319
|
+
}
|
|
3320
|
+
if (event.type !== "appendEdge" && event.type !== "appendNode" && event.type !== "insertEdgeAfter" && event.type !== "insertEdgeBefore" && event.type !== "prependEdge" && event.type !== "prependNode") return;
|
|
3321
|
+
const eventType = event.type;
|
|
3322
|
+
const sendNode = (node) => {
|
|
3323
|
+
handlers.onEvent({
|
|
3324
|
+
edge: {
|
|
3325
|
+
cursor: typeof data.cursor === "string" ? data.cursor : void 0,
|
|
3326
|
+
node
|
|
3327
|
+
},
|
|
3328
|
+
nodeType,
|
|
3329
|
+
targetCursor: typeof data.targetCursor === "string" ? data.targetCursor : void 0,
|
|
3330
|
+
type: eventType
|
|
3331
|
+
});
|
|
3332
|
+
};
|
|
3333
|
+
if ("node" in data && data.node !== void 0) {
|
|
3334
|
+
sendNode(data.node);
|
|
3335
|
+
return;
|
|
3336
|
+
}
|
|
3337
|
+
if (typeof id !== "string" && typeof id !== "number") {
|
|
3338
|
+
reportSubscriptionError(subscription, /* @__PURE__ */ new Error(`fate(http): Live connection event for '${procedure}' missed a node id.`));
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3341
|
+
resolveConnectionNode(subscription, id).then(sendNode).catch((error) => reportSubscriptionError(subscription, error));
|
|
3342
|
+
};
|
|
3343
|
+
liveSubscriptions.set(liveId, subscription);
|
|
3344
|
+
if (!liveConnector) {
|
|
3345
|
+
getNativeLiveClient().add({
|
|
3346
|
+
args,
|
|
3347
|
+
id: liveId,
|
|
3348
|
+
kind: "subscribeConnection",
|
|
3349
|
+
procedure,
|
|
3350
|
+
select: [...select],
|
|
3351
|
+
selectionArgs,
|
|
3352
|
+
type
|
|
3353
|
+
});
|
|
3354
|
+
return () => {
|
|
3355
|
+
if (!liveSubscriptions.delete(liveId)) return;
|
|
3356
|
+
getNativeLiveClient().remove(liveId);
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
liveSubscriptions.set(globalLiveId, subscription);
|
|
3360
|
+
const client = getLiveClient();
|
|
3361
|
+
const unsubscribeSpecific = client.subscribe({
|
|
3362
|
+
id: liveId,
|
|
3363
|
+
onEvent: handleEvent,
|
|
3364
|
+
topic: liveConnectionTopic(procedure, args)
|
|
3365
|
+
});
|
|
3366
|
+
const unsubscribeGlobal = client.subscribe({
|
|
3367
|
+
id: globalLiveId,
|
|
3368
|
+
onEvent: handleEvent,
|
|
3369
|
+
topic: liveGlobalConnectionTopic(procedure)
|
|
3370
|
+
});
|
|
3371
|
+
const unsubscribePromise = Promise.all([unsubscribeSpecific, unsubscribeGlobal]).catch((error) => {
|
|
3372
|
+
liveSubscriptions.delete(liveId);
|
|
3373
|
+
liveSubscriptions.delete(globalLiveId);
|
|
3374
|
+
reportSubscriptionError(subscription, error);
|
|
3375
|
+
return [];
|
|
3376
|
+
});
|
|
3377
|
+
return () => {
|
|
3378
|
+
if (!(liveSubscriptions.delete(liveId) || liveSubscriptions.delete(globalLiveId))) return;
|
|
3379
|
+
liveSubscriptions.delete(globalLiveId);
|
|
3380
|
+
unsubscribePromise.then((unsubscribers) => Promise.all(unsubscribers.map((unsubscribe) => unsubscribe())));
|
|
3381
|
+
};
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
return transport;
|
|
3385
|
+
}
|
|
1979
3386
|
//#endregion
|
|
1980
3387
|
//#region src/root.ts
|
|
1981
3388
|
/**
|
|
@@ -1987,6 +3394,5 @@ function clientRoot(type) {
|
|
|
1987
3394
|
type
|
|
1988
3395
|
});
|
|
1989
3396
|
}
|
|
1990
|
-
|
|
1991
3397
|
//#endregion
|
|
1992
|
-
export { ConnectionTag, FateClient, clientRoot, createClient, createTRPCTransport, getSelectionPlan, isViewTag, mutation, toEntityId, view };
|
|
3398
|
+
export { ConnectionTag, FateClient, clientRoot, createClient, createHTTPTransport, createTRPCTransport, getListEntries, getSelectionPlan, isRecord, isViewTag, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, mutation, toEntityId, view };
|