@nkzw/fate 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +324 -0
- package/docs/api/classes/FateClient.md +51 -199
- package/docs/api/functions/clientRoot.md +1 -1
- package/docs/api/functions/createClient.md +1 -1
- package/docs/api/functions/createGraphQLTransport.md +1 -1
- package/docs/api/functions/createHTTPTransport.md +1 -1
- package/docs/api/functions/createTRPCTransport.md +5 -1
- package/docs/api/functions/defer.md +1 -1
- package/docs/api/functions/getDeferredMetadata.md +1 -1
- package/docs/api/functions/getListEntries.md +1 -1
- package/docs/api/functions/getSelectionPlan.md +1 -1
- package/docs/api/functions/graphqlMutation.md +1 -1
- package/docs/api/functions/isDeferred.md +1 -1
- package/docs/api/functions/isRecord.md +1 -1
- package/docs/api/functions/isViewTag.md +1 -1
- package/docs/api/functions/liveConnectionTopic.md +1 -1
- package/docs/api/functions/liveEntityTopic.md +1 -1
- package/docs/api/functions/liveGlobalConnectionTopic.md +1 -1
- package/docs/api/functions/mutation.md +1 -1
- package/docs/api/functions/toEntityId.md +1 -1
- package/docs/api/functions/view.md +1 -1
- package/docs/api/index.md +6 -1
- package/docs/api/interfaces/FateThenable.md +3 -3
- package/docs/api/interfaces/Persistence.md +5 -0
- package/docs/api/interfaces/PersistenceSession.md +109 -0
- package/docs/api/interfaces/Transport.md +46 -7
- package/docs/api/server/classes/FateRequestError.md +5 -5
- package/docs/api/server/drizzle/functions/createDrizzleFate.md +1 -1
- package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +1 -1
- package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +1 -1
- package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +1 -1
- package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +1 -1
- package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +3 -3
- package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +9 -9
- package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +1 -1
- package/docs/api/server/functions/bindSourceProcedures.md +1 -1
- package/docs/api/server/functions/computed.md +1 -1
- package/docs/api/server/functions/count.md +1 -1
- package/docs/api/server/functions/createFateFetchHandler.md +1 -1
- package/docs/api/server/functions/createFateServer.md +1 -1
- package/docs/api/server/functions/createHonoFateHandler.md +1 -1
- package/docs/api/server/functions/createLiveEventBus.md +1 -1
- package/docs/api/server/functions/createNestedSourcePlan.md +1 -1
- package/docs/api/server/functions/createResolver.md +1 -1
- package/docs/api/server/functions/createSourcePlan.md +1 -1
- package/docs/api/server/functions/createSourceProcedures.md +1 -1
- package/docs/api/server/functions/dataView.md +1 -1
- package/docs/api/server/functions/field.md +1 -1
- package/docs/api/server/functions/getNestedSelection.md +1 -1
- package/docs/api/server/functions/getScopedArgs.md +1 -1
- package/docs/api/server/functions/hasNestedSelection.md +1 -1
- package/docs/api/server/functions/list.md +1 -1
- package/docs/api/server/functions/refetchSourceById.md +1 -1
- package/docs/api/server/functions/resolveSourceById.md +1 -1
- package/docs/api/server/functions/resolveSourceByIds.md +1 -1
- package/docs/api/server/functions/resolveSourceConnection.md +1 -1
- package/docs/api/server/functions/resolver.md +1 -1
- package/docs/api/server/functions/toPrismaSelect.md +1 -1
- package/docs/api/server/functions/withConnection.md +1 -1
- package/docs/api/server/prisma/functions/createPrismaFate.md +1 -1
- package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +1 -1
- package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +1 -1
- package/docs/api/server/prisma/functions/prismaConnectionArgs.md +1 -1
- package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +4 -4
- package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +1 -1
- package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +9 -9
- package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +1 -1
- package/docs/api/server/type-aliases/ComputedField.md +5 -5
- package/docs/api/server/type-aliases/ComputedSelection.md +1 -1
- package/docs/api/server/type-aliases/ConnectionItem.md +3 -3
- package/docs/api/server/type-aliases/ConnectionPagination.md +5 -5
- package/docs/api/server/type-aliases/ConnectionResult.md +3 -3
- package/docs/api/server/type-aliases/CountSelection.md +4 -4
- package/docs/api/server/type-aliases/CountWhere.md +1 -1
- package/docs/api/server/type-aliases/DataViewListOptions.md +2 -2
- package/docs/api/server/type-aliases/DataViewOrderBy.md +1 -1
- package/docs/api/server/type-aliases/DataViewOrderDirection.md +1 -1
- package/docs/api/server/type-aliases/DataViewResult.md +1 -1
- package/docs/api/server/type-aliases/Entity.md +1 -1
- package/docs/api/server/type-aliases/FateServer.md +5 -5
- package/docs/api/server/type-aliases/FateServerManifest.md +1 -1
- package/docs/api/server/type-aliases/FieldSelection.md +3 -3
- package/docs/api/server/type-aliases/LiveConnectionEventType.md +1 -1
- package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +1 -1
- package/docs/api/server/type-aliases/LiveEventBus.md +1 -1
- package/docs/api/server/type-aliases/LiveEventType.md +1 -1
- package/docs/api/server/type-aliases/LiveSourceEvent.md +1 -1
- package/docs/api/server/type-aliases/NativeFateAPI.md +4 -4
- package/docs/api/server/type-aliases/OrderDirection.md +1 -1
- package/docs/api/server/type-aliases/SourceConfig.md +6 -6
- package/docs/api/server/type-aliases/SourceDefinition.md +6 -6
- package/docs/api/server/type-aliases/SourceOrder.md +1 -1
- package/docs/api/server/type-aliases/SourceOrderField.md +3 -3
- package/docs/api/server/type-aliases/SourcePlan.md +1 -1
- package/docs/api/server/type-aliases/SourcePlanNode.md +1 -1
- package/docs/api/server/type-aliases/SourceRegistry.md +1 -1
- package/docs/api/server/type-aliases/SourceRelation.md +1 -1
- package/docs/api/server/type-aliases/SourceRelationConfig.md +1 -1
- package/docs/api/server/variables/byIdInput.md +1 -1
- package/docs/api/server/variables/connectionArgs.md +1 -1
- package/docs/api/type-aliases/ConnectionMetadata.md +1 -1
- package/docs/api/type-aliases/ConnectionRef.md +1 -1
- package/docs/api/type-aliases/Deferred.md +1 -1
- package/docs/api/type-aliases/DeferredMetadata.md +1 -1
- package/docs/api/type-aliases/DeferredSelection.md +1 -1
- package/docs/api/type-aliases/DeferredSnapshot.md +1 -1
- package/docs/api/type-aliases/Entity.md +2 -2
- package/docs/api/type-aliases/EntityId.md +1 -1
- package/docs/api/type-aliases/FateDehydratedState.md +1 -1
- package/docs/api/type-aliases/FateLiveConnectionEvent.md +1 -1
- package/docs/api/type-aliases/FateLiveEvent.md +1 -1
- package/docs/api/type-aliases/FateMutations.md +1 -1
- package/docs/api/type-aliases/FateOperation.md +2 -2
- package/docs/api/type-aliases/FateProtocolRequest.md +1 -1
- package/docs/api/type-aliases/FateProtocolResponse.md +1 -1
- package/docs/api/type-aliases/FateRecord.md +1 -1
- package/docs/api/type-aliases/FateRoots.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationDefinition.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationInput.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationMap.md +1 -1
- package/docs/api/type-aliases/GraphQLMutationOutput.md +1 -1
- package/docs/api/type-aliases/GraphQLTransportOptions.md +19 -11
- package/docs/api/type-aliases/HydrateOptions.md +1 -1
- package/docs/api/type-aliases/HydrationLimits.md +1 -1
- package/docs/api/type-aliases/InferFateAPI.md +1 -1
- package/docs/api/type-aliases/List.md +1 -1
- package/docs/api/type-aliases/ListEntry.md +1 -1
- package/docs/api/type-aliases/ListItem.md +1 -1
- package/docs/api/type-aliases/Mask.md +1 -1
- package/docs/api/type-aliases/MutationDefinition.md +1 -1
- package/docs/api/type-aliases/MutationEntity.md +1 -1
- package/docs/api/type-aliases/MutationIdentifier.md +1 -1
- package/docs/api/type-aliases/MutationIdentity.md +7 -0
- package/docs/api/type-aliases/MutationInput.md +1 -1
- package/docs/api/type-aliases/MutationResult.md +1 -1
- package/docs/api/type-aliases/NodesItem.md +1 -1
- package/docs/api/type-aliases/Pagination.md +5 -5
- package/docs/api/type-aliases/PersistedMutationStatus.md +5 -0
- package/docs/api/type-aliases/PersistenceSnapshot.md +5 -0
- package/docs/api/type-aliases/Request.md +1 -1
- package/docs/api/type-aliases/RequestMode.md +1 -1
- package/docs/api/type-aliases/RequestOptions.md +2 -2
- package/docs/api/type-aliases/RequestPersistenceOptions.md +5 -0
- package/docs/api/type-aliases/RequestResult.md +1 -1
- package/docs/api/type-aliases/Selection.md +1 -1
- package/docs/api/type-aliases/TypeConfig.md +4 -4
- package/docs/api/type-aliases/View.md +1 -1
- package/docs/api/type-aliases/ViewData.md +1 -1
- package/docs/api/type-aliases/ViewEntity.md +1 -1
- package/docs/api/type-aliases/ViewEntityName.md +1 -1
- package/docs/api/type-aliases/ViewRef.md +1 -1
- package/docs/api/type-aliases/ViewSelection.md +1 -1
- package/docs/api/type-aliases/ViewSnapshot.md +1 -1
- package/docs/api/type-aliases/ViewTag.md +1 -1
- package/docs/api/variables/ConnectionTag.md +1 -1
- package/docs/api/variables/DeferTag.md +1 -1
- package/docs/api/variables/DeferredTag.md +1 -1
- package/docs/guide/getting-started.md +2 -0
- package/docs/guide/persistence.md +321 -0
- package/lib/{liveTopics-uVP0LG-Q.mjs → args-CRLIRl_v.mjs} +1 -59
- package/lib/{executor-BRudKWDY.d.mts → executor-oPTYqgQV.d.mts} +1 -1
- package/lib/{graphqlTransport-BDR1pnUQ.d.mts → graphqlTransport-CGYE4NdD.d.mts} +3 -2
- package/lib/{graphqlTransport-DSBVS2Fq.mjs → graphqlTransport-DmheG3AO.mjs} +3 -2
- package/lib/graphqlTransport.d.mts +1 -1
- package/lib/graphqlTransport.mjs +1 -1
- package/lib/hydration-DPZRJgnu.mjs +273 -0
- package/lib/idempotency-types-DuqJ7KSd.d.mts +16 -0
- package/lib/idempotency.d.mts +26 -0
- package/lib/idempotency.mjs +34 -0
- package/lib/index.d.mts +5 -5
- package/lib/index.mjs +275 -1567
- package/lib/{list-Bb1SeABn.d.mts → list-5hrTVoiB.d.mts} +1 -1
- package/lib/list.d.mts +1 -1
- package/lib/liveTopics-DuFkDZQm.mjs +17 -0
- package/lib/persistence.d.mts +40 -0
- package/lib/persistence.mjs +1730 -0
- package/lib/protocol-DS5sf6sd.mjs +44 -0
- package/lib/{record-BTUXkHQU.d.mts → record-Dxjvpsep.d.mts} +8 -4
- package/lib/server/drizzle.d.mts +2 -2
- package/lib/server/drizzle.mjs +1 -1
- package/lib/server/prisma.d.mts +2 -2
- package/lib/server/prisma.mjs +1 -6
- package/lib/server.d.mts +3 -3
- package/lib/server.mjs +19 -7
- package/lib/sortObjectKeys-yhuitEQZ.mjs +9 -0
- package/lib/{sourceRouter-DSaADXSf.mjs → sourceRouter-51POBlEE.mjs} +9 -6
- package/lib/store-CakBBzWG.mjs +1386 -0
- package/lib/{transport-Bo1Jgo9R.d.mts → transport-DR0fwleU.d.mts} +607 -412
- package/lib/vite.mjs +18 -10
- package/package.json +12 -2
- package/docs/api/type-aliases/Snapshot.md +0 -7
package/lib/index.mjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { t as isRecord } from "./record-B07VwXd-.mjs";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as hashArgs, c as scopeArgsPayload, i as filterConnectionArgs, r as combineArgsPayload, s as resolvedArgsFromPlan, t as applyArgsPayloadToPlan } from "./args-CRLIRl_v.mjs";
|
|
3
|
+
import { _ as isNodesItem, a as createNodeRef, c as ConnectionTag, f as RootKind, g as isNodeItem, i as resolveHydrationLimits, l as DeferTag, m as ViewsTag, o as getNodeRefId, r as encodeHydrationValue, s as isNodeRef, t as decodeClientHydrationState, u as DeferredTag, v as isQueryItem, y as isViewTag } from "./hydration-DPZRJgnu.mjs";
|
|
4
|
+
import { C as isDeferredSelection, S as isDeferred, _ as view, a as mutation, b as getDeferredMetadata, c as getDeferredSelectionPlan, d as createRefWithViewNames, f as getRootViewNames, g as getViewPayloads, h as getViewNames, l as getSelectionPlan, m as toEntityId, n as getListKey, p as parseEntityId, r as mergePreservingExisting, s as wrapMutation, t as Store, u as assignViewTag, v as createDeferred, x as getDeferredSelection, y as defer } from "./store-CakBBzWG.mjs";
|
|
3
5
|
import { t as getListEntries } from "./list-_6fXYLtJ.mjs";
|
|
4
|
-
import { n as
|
|
6
|
+
import { n as errorCodeFromStatus, t as FateRequestError } from "./protocol-DS5sf6sd.mjs";
|
|
7
|
+
import { n as graphqlMutation, t as createGraphQLTransport } from "./graphqlTransport-DmheG3AO.mjs";
|
|
8
|
+
import { n as liveEntityTopic, r as liveGlobalConnectionTopic, t as liveConnectionTopic } from "./liveTopics-DuFkDZQm.mjs";
|
|
5
9
|
//#region src/cache.ts
|
|
6
10
|
var ViewDataCache = class {
|
|
7
11
|
constructor(isEnabled = () => true) {
|
|
@@ -73,747 +77,6 @@ var ViewDataCache = class {
|
|
|
73
77
|
}
|
|
74
78
|
};
|
|
75
79
|
//#endregion
|
|
76
|
-
//#region src/types.ts
|
|
77
|
-
/** Internal marker added to objects that represent a view payload. */
|
|
78
|
-
const ViewKind = Symbol("__fate__view");
|
|
79
|
-
/** Symbol used to attach the set of view tags that were spread into a ref or masked record. */
|
|
80
|
-
const ViewsTag = Symbol("__fate__views");
|
|
81
|
-
/** Symbol used to brand a value as a node reference inside the cache. */
|
|
82
|
-
const NodeRefTag = Symbol("__fate__node-ref");
|
|
83
|
-
/** Symbol attached to connection results so pagination helpers can find their metadata. */
|
|
84
|
-
const ConnectionTag = Symbol("__fate__connection");
|
|
85
|
-
/** Symbol used to mark a selection as deferred. */
|
|
86
|
-
const DeferTag = Symbol("__fate__defer");
|
|
87
|
-
/** Symbol attached to deferred values returned from masked views. */
|
|
88
|
-
const DeferredTag = Symbol("__fate__deferred");
|
|
89
|
-
const viewTag = "__fate-view__";
|
|
90
|
-
/** Generates a stable view tag for a view definition. */
|
|
91
|
-
function getViewTag(id) {
|
|
92
|
-
return `${viewTag}${id}`;
|
|
93
|
-
}
|
|
94
|
-
/** Determines whether a property key is a fate view tag. */
|
|
95
|
-
function isViewTag(key) {
|
|
96
|
-
return key.startsWith(viewTag);
|
|
97
|
-
}
|
|
98
|
-
/** Indicates whether a request item represents an explicit node ID. */
|
|
99
|
-
function isNodeItem(item) {
|
|
100
|
-
return "id" in item;
|
|
101
|
-
}
|
|
102
|
-
/** Indicates whether a request item represents explicit node IDs. */
|
|
103
|
-
function isNodesItem(item) {
|
|
104
|
-
return "ids" in item;
|
|
105
|
-
}
|
|
106
|
-
/** Indicates whether a request item represents a root query. */
|
|
107
|
-
function isQueryItem(item) {
|
|
108
|
-
return "view" in item && !("id" in item) && !("ids" in item);
|
|
109
|
-
}
|
|
110
|
-
/** Brand used on root definitions to mark their identity in the d.ts output. */
|
|
111
|
-
const RootKind = "__fate__root";
|
|
112
|
-
/** Brand used on mutation definitions to mark their identity in the d.ts output. */
|
|
113
|
-
const MutationKind = "__fate__mutation";
|
|
114
|
-
//#endregion
|
|
115
|
-
//#region src/defer.ts
|
|
116
|
-
function defer(selection) {
|
|
117
|
-
return Object.freeze({ [DeferTag]: selection });
|
|
118
|
-
}
|
|
119
|
-
const isDeferredSelection = (value) => typeof value === "object" && value !== null && DeferTag in value;
|
|
120
|
-
const getDeferredSelection = (selection) => selection[DeferTag];
|
|
121
|
-
const createDeferred = (metadata) => Object.freeze({ [DeferredTag]: metadata });
|
|
122
|
-
const isDeferred = (value) => typeof value === "object" && value !== null && DeferredTag in value;
|
|
123
|
-
const getDeferredMetadata = (deferred) => deferred[DeferredTag];
|
|
124
|
-
//#endregion
|
|
125
|
-
//#region src/node-ref.ts
|
|
126
|
-
function createNodeRef(id) {
|
|
127
|
-
const ref = {};
|
|
128
|
-
Object.defineProperty(ref, NodeRefTag, {
|
|
129
|
-
configurable: false,
|
|
130
|
-
enumerable: false,
|
|
131
|
-
value: id,
|
|
132
|
-
writable: false
|
|
133
|
-
});
|
|
134
|
-
return Object.freeze(ref);
|
|
135
|
-
}
|
|
136
|
-
function isNodeRef(value) {
|
|
137
|
-
return !value || typeof value !== "object" ? false : NodeRefTag in value;
|
|
138
|
-
}
|
|
139
|
-
function getNodeRefId(ref) {
|
|
140
|
-
return ref[NodeRefTag];
|
|
141
|
-
}
|
|
142
|
-
//#endregion
|
|
143
|
-
//#region src/hydration.ts
|
|
144
|
-
const maxDepth = 64;
|
|
145
|
-
const defaultHydrationLimits = {
|
|
146
|
-
maxCollectionLength: 1e5,
|
|
147
|
-
maxNodes: 25e4,
|
|
148
|
-
maxStringLength: 1e6
|
|
149
|
-
};
|
|
150
|
-
const resolveHydrationLimits = (limits) => {
|
|
151
|
-
const result = {
|
|
152
|
-
...defaultHydrationLimits,
|
|
153
|
-
...limits
|
|
154
|
-
};
|
|
155
|
-
for (const [key, value] of Object.entries(result)) if (!Number.isInteger(value) || value < 1) throw new Error(`fate: Hydration limit '${key}' must be a positive integer.`);
|
|
156
|
-
return result;
|
|
157
|
-
};
|
|
158
|
-
const error = (path, message) => /* @__PURE__ */ new Error(`fate: Cannot serialize hydration value at '${path}': ${message}`);
|
|
159
|
-
const isPlainObject = (value) => {
|
|
160
|
-
const prototype = Object.getPrototypeOf(value);
|
|
161
|
-
return prototype === Object.prototype || prototype === null;
|
|
162
|
-
};
|
|
163
|
-
const isDenseArray = (value) => {
|
|
164
|
-
for (let index = 0; index < value.length; index += 1) if (!Object.hasOwn(value, index)) return false;
|
|
165
|
-
return true;
|
|
166
|
-
};
|
|
167
|
-
const isDate$1 = (value) => Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function" && typeof value.toISOString === "function";
|
|
168
|
-
const encodeHydrationValue = (value, limits) => {
|
|
169
|
-
const resolvedLimits = resolveHydrationLimits(limits);
|
|
170
|
-
const seen = /* @__PURE__ */ new Set();
|
|
171
|
-
let nodes = 0;
|
|
172
|
-
const encode = (entry, path, depth) => {
|
|
173
|
-
nodes += 1;
|
|
174
|
-
if (nodes > resolvedLimits.maxNodes) throw error(path, `Maximum node count of ${String(resolvedLimits.maxNodes)} exceeded.`);
|
|
175
|
-
if (depth > maxDepth) throw error(path, `Maximum depth of ${String(maxDepth)} exceeded.`);
|
|
176
|
-
if (entry === null) return ["null"];
|
|
177
|
-
const type = typeof entry;
|
|
178
|
-
if (typeof entry === "string" || typeof entry === "boolean") {
|
|
179
|
-
if (typeof entry === "string" && entry.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
180
|
-
return [typeof entry, entry];
|
|
181
|
-
}
|
|
182
|
-
if (type === "number") {
|
|
183
|
-
if (Number.isNaN(entry)) return ["number", "NaN"];
|
|
184
|
-
if (entry === Infinity) return ["number", "Infinity"];
|
|
185
|
-
if (entry === -Infinity) return ["number", "-Infinity"];
|
|
186
|
-
if (Object.is(entry, -0)) return ["number", "-0"];
|
|
187
|
-
return ["number", entry];
|
|
188
|
-
}
|
|
189
|
-
if (type === "undefined") return ["undefined"];
|
|
190
|
-
if (type === "bigint") {
|
|
191
|
-
const bigint = String(entry);
|
|
192
|
-
if (bigint.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
193
|
-
return ["bigint", bigint];
|
|
194
|
-
}
|
|
195
|
-
if (type === "function" || type === "symbol") throw error(path, `Received unsupported '${type}' value.`);
|
|
196
|
-
if (!entry || typeof entry !== "object") throw error(path, `Received unsupported '${type}' value.`);
|
|
197
|
-
if (isNodeRef(entry)) {
|
|
198
|
-
const id = getNodeRefId(entry);
|
|
199
|
-
if (id.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
200
|
-
return ["ref", id];
|
|
201
|
-
}
|
|
202
|
-
if (isDate$1(entry)) {
|
|
203
|
-
if (Number.isNaN(entry.getTime())) throw error(path, `Received an invalid Date.`);
|
|
204
|
-
return ["date", entry.toISOString()];
|
|
205
|
-
}
|
|
206
|
-
if (seen.has(entry)) throw error(path, `Circular references are not supported.`);
|
|
207
|
-
seen.add(entry);
|
|
208
|
-
try {
|
|
209
|
-
if (Array.isArray(entry)) {
|
|
210
|
-
if (!isDenseArray(entry)) throw error(path, `Sparse arrays are not supported.`);
|
|
211
|
-
if (entry.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
|
|
212
|
-
return ["array", entry.map((item, index) => encode(item, `${path}[${String(index)}]`, depth + 1))];
|
|
213
|
-
}
|
|
214
|
-
if (!isPlainObject(entry)) throw error(path, `Received unsupported '${entry.constructor?.name ?? "object"}' value.`);
|
|
215
|
-
if (Object.getOwnPropertySymbols(entry).length > 0) throw error(path, `Received an unsupported symbol-keyed property.`);
|
|
216
|
-
const entries = Object.entries(entry);
|
|
217
|
-
if (entries.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
|
|
218
|
-
return ["object", entries.map(([key, item]) => {
|
|
219
|
-
if (key.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
220
|
-
return [key, encode(item, `${path}.${key}`, depth + 1)];
|
|
221
|
-
})];
|
|
222
|
-
} finally {
|
|
223
|
-
seen.delete(entry);
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
return encode(value, "data", 0);
|
|
227
|
-
};
|
|
228
|
-
const invalidPayload = (path) => /* @__PURE__ */ new Error(`fate: Invalid hydration payload at '${path}'.`);
|
|
229
|
-
const tagLengths = {
|
|
230
|
-
array: 2,
|
|
231
|
-
bigint: 2,
|
|
232
|
-
boolean: 2,
|
|
233
|
-
date: 2,
|
|
234
|
-
null: 1,
|
|
235
|
-
number: 2,
|
|
236
|
-
object: 2,
|
|
237
|
-
ref: 2,
|
|
238
|
-
string: 2,
|
|
239
|
-
undefined: 1
|
|
240
|
-
};
|
|
241
|
-
const decodeHydrationValue = (value, limits) => {
|
|
242
|
-
const resolvedLimits = resolveHydrationLimits(limits);
|
|
243
|
-
let nodes = 0;
|
|
244
|
-
const decode = (entry, path, depth) => {
|
|
245
|
-
nodes += 1;
|
|
246
|
-
if (nodes > resolvedLimits.maxNodes || depth > maxDepth || !Array.isArray(entry) || typeof entry[0] !== "string") throw invalidPayload(path);
|
|
247
|
-
const [tag, payload] = entry;
|
|
248
|
-
if (entry.length !== tagLengths[tag]) throw invalidPayload(path);
|
|
249
|
-
switch (tag) {
|
|
250
|
-
case "null": return null;
|
|
251
|
-
case "undefined": return;
|
|
252
|
-
case "string":
|
|
253
|
-
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
254
|
-
return payload;
|
|
255
|
-
case "boolean":
|
|
256
|
-
if (typeof payload !== "boolean") throw invalidPayload(path);
|
|
257
|
-
return payload;
|
|
258
|
-
case "number":
|
|
259
|
-
if (typeof payload === "number") {
|
|
260
|
-
if (!Number.isFinite(payload) || Object.is(payload, -0)) throw invalidPayload(path);
|
|
261
|
-
return payload;
|
|
262
|
-
}
|
|
263
|
-
if (payload === "NaN") return NaN;
|
|
264
|
-
if (payload === "Infinity") return Infinity;
|
|
265
|
-
if (payload === "-Infinity") return -Infinity;
|
|
266
|
-
if (payload === "-0") return -0;
|
|
267
|
-
throw invalidPayload(path);
|
|
268
|
-
case "bigint":
|
|
269
|
-
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
270
|
-
try {
|
|
271
|
-
const bigint = BigInt(payload);
|
|
272
|
-
if (String(bigint) !== payload) throw invalidPayload(path);
|
|
273
|
-
return bigint;
|
|
274
|
-
} catch {
|
|
275
|
-
throw invalidPayload(path);
|
|
276
|
-
}
|
|
277
|
-
case "date": {
|
|
278
|
-
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
279
|
-
const date = new Date(payload);
|
|
280
|
-
if (Number.isNaN(date.getTime()) || date.toISOString() !== payload) throw invalidPayload(path);
|
|
281
|
-
return date;
|
|
282
|
-
}
|
|
283
|
-
case "ref":
|
|
284
|
-
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
285
|
-
return createNodeRef(payload);
|
|
286
|
-
case "array":
|
|
287
|
-
if (!Array.isArray(payload) || !isDenseArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
|
|
288
|
-
return payload.map((item, index) => decode(item, `${path}[${String(index)}]`, depth + 1));
|
|
289
|
-
case "object": {
|
|
290
|
-
if (!Array.isArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
|
|
291
|
-
const result = Object.create(null);
|
|
292
|
-
for (const item of payload) {
|
|
293
|
-
if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== "string" || item[0].length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
294
|
-
if (item[0] in result) throw invalidPayload(path);
|
|
295
|
-
result[item[0]] = decode(item[1], `${path}.${item[0]}`, depth + 1);
|
|
296
|
-
}
|
|
297
|
-
return result;
|
|
298
|
-
}
|
|
299
|
-
default: throw invalidPayload(path);
|
|
300
|
-
}
|
|
301
|
-
};
|
|
302
|
-
return decode(value, "data", 0);
|
|
303
|
-
};
|
|
304
|
-
const isRecord$1 = (value) => {
|
|
305
|
-
if (!value || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
|
|
306
|
-
const prototype = Object.getPrototypeOf(value);
|
|
307
|
-
return prototype === Object.prototype || prototype === null;
|
|
308
|
-
};
|
|
309
|
-
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
310
|
-
const hasDuplicates = (values) => new Set(values).size !== values.length;
|
|
311
|
-
const isUniqueStringArray = (value) => isStringArray(value) && !hasDuplicates(value);
|
|
312
|
-
const hasOnlyKeys = (value, keys) => Object.keys(value).every((key) => keys.has(key));
|
|
313
|
-
const listKeys = /* @__PURE__ */ new Set([
|
|
314
|
-
"backwardPageLimit",
|
|
315
|
-
"cursors",
|
|
316
|
-
"forwardPageLimit",
|
|
317
|
-
"ids",
|
|
318
|
-
"liveAfterIds",
|
|
319
|
-
"liveBeforeIds",
|
|
320
|
-
"pagination",
|
|
321
|
-
"pendingAfterIds",
|
|
322
|
-
"pendingBeforeIds"
|
|
323
|
-
]);
|
|
324
|
-
const paginationKeys = /* @__PURE__ */ new Set([
|
|
325
|
-
"hasNext",
|
|
326
|
-
"hasPrevious",
|
|
327
|
-
"nextCursor",
|
|
328
|
-
"previousCursor"
|
|
329
|
-
]);
|
|
330
|
-
const clientStateKeys = /* @__PURE__ */ new Set([
|
|
331
|
-
"rootLists",
|
|
332
|
-
"rootRequests",
|
|
333
|
-
"store"
|
|
334
|
-
]);
|
|
335
|
-
const storeStateKeys = /* @__PURE__ */ new Set([
|
|
336
|
-
"coverage",
|
|
337
|
-
"lists",
|
|
338
|
-
"records"
|
|
339
|
-
]);
|
|
340
|
-
const isEntry = (value) => Array.isArray(value) && value.length === 2 && typeof value[0] === "string";
|
|
341
|
-
const isOptionalLimit = (value) => value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
342
|
-
const isPagination = (value) => value === void 0 || isRecord$1(value) && hasOnlyKeys(value, paginationKeys) && typeof value.hasNext === "boolean" && typeof value.hasPrevious === "boolean" && (value.nextCursor === void 0 || typeof value.nextCursor === "string") && (value.previousCursor === void 0 || typeof value.previousCursor === "string");
|
|
343
|
-
const isList = (value) => {
|
|
344
|
-
if (!isRecord$1(value) || !hasOnlyKeys(value, listKeys) || !isStringArray(value.ids) || hasDuplicates(value.ids) || value.cursors !== void 0 && (!Array.isArray(value.cursors) || value.cursors.length !== value.ids.length)) return false;
|
|
345
|
-
return (value.cursors === void 0 || Array.isArray(value.cursors) && value.cursors.every((entry) => entry === void 0 || typeof entry === "string")) && (value.liveAfterIds === void 0 || isUniqueStringArray(value.liveAfterIds)) && (value.liveBeforeIds === void 0 || isUniqueStringArray(value.liveBeforeIds)) && isOptionalLimit(value.backwardPageLimit) && isOptionalLimit(value.forwardPageLimit) && isPagination(value.pagination) && (value.pendingAfterIds === void 0 || isUniqueStringArray(value.pendingAfterIds)) && (value.pendingBeforeIds === void 0 || isUniqueStringArray(value.pendingBeforeIds));
|
|
346
|
-
};
|
|
347
|
-
const isFieldPath = (value) => value.length > 0 && value.split(".").every((segment) => segment.length > 0);
|
|
348
|
-
const isFieldPathArray = (value) => isUniqueStringArray(value) && value.every(isFieldPath);
|
|
349
|
-
const hasDuplicateEntries = (entries) => hasDuplicates(entries.map((entry) => entry[0]));
|
|
350
|
-
const decodeClientHydrationState = (value, limits) => {
|
|
351
|
-
const decoded = decodeHydrationValue(value, limits);
|
|
352
|
-
if (!isRecord$1(decoded) || !hasOnlyKeys(decoded, clientStateKeys) || !Array.isArray(decoded.rootLists) || !Array.isArray(decoded.rootRequests) || !isRecord$1(decoded.store) || !hasOnlyKeys(decoded.store, storeStateKeys) || !Array.isArray(decoded.store.coverage) || !Array.isArray(decoded.store.lists) || !Array.isArray(decoded.store.records)) throw invalidPayload("data");
|
|
353
|
-
if (decoded.rootLists.some((entry) => !isEntry(entry) || !isStringArray(entry[1])) || decoded.rootRequests.some((entry) => !isEntry(entry) || entry[1] !== null && typeof entry[1] !== "string") || decoded.store.coverage.some((entry) => !isEntry(entry) || !isFieldPathArray(entry[1])) || decoded.store.lists.some((entry) => !isEntry(entry) || !isList(entry[1])) || decoded.store.records.some((entry) => !isEntry(entry) || !isRecord$1(entry[1]))) throw invalidPayload("data");
|
|
354
|
-
if (hasDuplicateEntries(decoded.rootLists) || hasDuplicateEntries(decoded.rootRequests) || hasDuplicateEntries(decoded.store.coverage) || hasDuplicateEntries(decoded.store.lists) || hasDuplicateEntries(decoded.store.records) || decoded.rootLists.some(([, keys]) => hasDuplicates(keys))) throw invalidPayload("data");
|
|
355
|
-
return decoded;
|
|
356
|
-
};
|
|
357
|
-
//#endregion
|
|
358
|
-
//#region ../../node_modules/.pnpm/@trpc+server@11.18.0_typescript@7.0.2/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
|
|
359
|
-
var __create = Object.create;
|
|
360
|
-
var __defProp = Object.defineProperty;
|
|
361
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
362
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
363
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
364
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
365
|
-
var __commonJS = (cb, mod) => function() {
|
|
366
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
367
|
-
};
|
|
368
|
-
var __copyProps = (to, from, except, desc) => {
|
|
369
|
-
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
370
|
-
key = keys[i];
|
|
371
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
372
|
-
get: ((k) => from[k]).bind(null, key),
|
|
373
|
-
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
return to;
|
|
377
|
-
};
|
|
378
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
379
|
-
value: mod,
|
|
380
|
-
enumerable: true
|
|
381
|
-
}) : target, mod));
|
|
382
|
-
const JSONRPC2_TO_HTTP_CODE = {
|
|
383
|
-
PARSE_ERROR: 400,
|
|
384
|
-
BAD_REQUEST: 400,
|
|
385
|
-
UNAUTHORIZED: 401,
|
|
386
|
-
PAYMENT_REQUIRED: 402,
|
|
387
|
-
FORBIDDEN: 403,
|
|
388
|
-
NOT_FOUND: 404,
|
|
389
|
-
METHOD_NOT_SUPPORTED: 405,
|
|
390
|
-
TIMEOUT: 408,
|
|
391
|
-
CONFLICT: 409,
|
|
392
|
-
PRECONDITION_FAILED: 412,
|
|
393
|
-
PAYLOAD_TOO_LARGE: 413,
|
|
394
|
-
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
395
|
-
UNPROCESSABLE_CONTENT: 422,
|
|
396
|
-
PRECONDITION_REQUIRED: 428,
|
|
397
|
-
TOO_MANY_REQUESTS: 429,
|
|
398
|
-
CLIENT_CLOSED_REQUEST: 499,
|
|
399
|
-
INTERNAL_SERVER_ERROR: 500,
|
|
400
|
-
NOT_IMPLEMENTED: 501,
|
|
401
|
-
BAD_GATEWAY: 502,
|
|
402
|
-
SERVICE_UNAVAILABLE: 503,
|
|
403
|
-
GATEWAY_TIMEOUT: 504
|
|
404
|
-
};
|
|
405
|
-
function getStatusCodeFromKey(code) {
|
|
406
|
-
var _JSONRPC2_TO_HTTP_COD;
|
|
407
|
-
return (_JSONRPC2_TO_HTTP_COD = JSONRPC2_TO_HTTP_CODE[code]) !== null && _JSONRPC2_TO_HTTP_COD !== void 0 ? _JSONRPC2_TO_HTTP_COD : 500;
|
|
408
|
-
}
|
|
409
|
-
function getHTTPStatusCodeFromError(error) {
|
|
410
|
-
return getStatusCodeFromKey(error.code);
|
|
411
|
-
}
|
|
412
|
-
var require_typeof = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/typeof.js"(exports, module) {
|
|
413
|
-
function _typeof$2(o) {
|
|
414
|
-
"@babel/helpers - typeof";
|
|
415
|
-
return module.exports = _typeof$2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
|
|
416
|
-
return typeof o$1;
|
|
417
|
-
} : function(o$1) {
|
|
418
|
-
return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
|
|
419
|
-
}, module.exports.__esModule = true, module.exports["default"] = module.exports, _typeof$2(o);
|
|
420
|
-
}
|
|
421
|
-
module.exports = _typeof$2, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
422
|
-
} });
|
|
423
|
-
var require_toPrimitive = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPrimitive.js"(exports, module) {
|
|
424
|
-
var _typeof$1 = require_typeof()["default"];
|
|
425
|
-
function toPrimitive$1(t, r) {
|
|
426
|
-
if ("object" != _typeof$1(t) || !t) return t;
|
|
427
|
-
var e = t[Symbol.toPrimitive];
|
|
428
|
-
if (void 0 !== e) {
|
|
429
|
-
var i = e.call(t, r || "default");
|
|
430
|
-
if ("object" != _typeof$1(i)) return i;
|
|
431
|
-
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
432
|
-
}
|
|
433
|
-
return ("string" === r ? String : Number)(t);
|
|
434
|
-
}
|
|
435
|
-
module.exports = toPrimitive$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
436
|
-
} });
|
|
437
|
-
var require_toPropertyKey = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/toPropertyKey.js"(exports, module) {
|
|
438
|
-
var _typeof = require_typeof()["default"];
|
|
439
|
-
var toPrimitive = require_toPrimitive();
|
|
440
|
-
function toPropertyKey$1(t) {
|
|
441
|
-
var i = toPrimitive(t, "string");
|
|
442
|
-
return "symbol" == _typeof(i) ? i : i + "";
|
|
443
|
-
}
|
|
444
|
-
module.exports = toPropertyKey$1, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
445
|
-
} });
|
|
446
|
-
var require_defineProperty = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/defineProperty.js"(exports, module) {
|
|
447
|
-
var toPropertyKey = require_toPropertyKey();
|
|
448
|
-
function _defineProperty(e, r, t) {
|
|
449
|
-
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
450
|
-
value: t,
|
|
451
|
-
enumerable: !0,
|
|
452
|
-
configurable: !0,
|
|
453
|
-
writable: !0
|
|
454
|
-
}) : e[r] = t, e;
|
|
455
|
-
}
|
|
456
|
-
module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
457
|
-
} });
|
|
458
|
-
__toESM(__commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_modules/@oxc-project/runtime/src/helpers/objectSpread2.js"(exports, module) {
|
|
459
|
-
var defineProperty = require_defineProperty();
|
|
460
|
-
function ownKeys(e, r) {
|
|
461
|
-
var t = Object.keys(e);
|
|
462
|
-
if (Object.getOwnPropertySymbols) {
|
|
463
|
-
var o = Object.getOwnPropertySymbols(e);
|
|
464
|
-
r && (o = o.filter(function(r$1) {
|
|
465
|
-
return Object.getOwnPropertyDescriptor(e, r$1).enumerable;
|
|
466
|
-
})), t.push.apply(t, o);
|
|
467
|
-
}
|
|
468
|
-
return t;
|
|
469
|
-
}
|
|
470
|
-
function _objectSpread2(e) {
|
|
471
|
-
for (var r = 1; r < arguments.length; r++) {
|
|
472
|
-
var t = null != arguments[r] ? arguments[r] : {};
|
|
473
|
-
r % 2 ? ownKeys(Object(t), !0).forEach(function(r$1) {
|
|
474
|
-
defineProperty(e, r$1, t[r$1]);
|
|
475
|
-
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r$1) {
|
|
476
|
-
Object.defineProperty(e, r$1, Object.getOwnPropertyDescriptor(t, r$1));
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
return e;
|
|
480
|
-
}
|
|
481
|
-
module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
482
|
-
} })(), 1);
|
|
483
|
-
//#endregion
|
|
484
|
-
//#region src/view.ts
|
|
485
|
-
/**
|
|
486
|
-
* Collects all view payloads that apply to the given ref.
|
|
487
|
-
*/
|
|
488
|
-
const getViewPayloads = (view, ref) => {
|
|
489
|
-
const result = [];
|
|
490
|
-
for (const [key, value] of Object.entries(view)) if (isViewTag(key) && (!ref || ref[ViewsTag]?.has(key))) result.push(value);
|
|
491
|
-
return result;
|
|
492
|
-
};
|
|
493
|
-
/**
|
|
494
|
-
* Returns the set of view tags defined on a view composition.
|
|
495
|
-
*/
|
|
496
|
-
const getViewNames = (view) => {
|
|
497
|
-
const result = /* @__PURE__ */ new Set();
|
|
498
|
-
for (const key of Object.keys(view)) if (isViewTag(key)) result.add(key);
|
|
499
|
-
return result;
|
|
500
|
-
};
|
|
501
|
-
/**
|
|
502
|
-
* Extracts view tags from a nested selection object.
|
|
503
|
-
*/
|
|
504
|
-
const getSelectionViewNames = (selection) => {
|
|
505
|
-
return getViewNames(selection);
|
|
506
|
-
};
|
|
507
|
-
let id = 0;
|
|
508
|
-
const importMetaEnvironment = import.meta.env;
|
|
509
|
-
const isDevelopment = typeof importMetaEnvironment?.DEV === "boolean" ? importMetaEnvironment.DEV : typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : false;
|
|
510
|
-
let viewModulePath = null;
|
|
511
|
-
const getStableId = () => {
|
|
512
|
-
if (isDevelopment) try {
|
|
513
|
-
if (viewModulePath == null) viewModulePath = new URL(import.meta.url).pathname;
|
|
514
|
-
const stack = (/* @__PURE__ */ new Error()).stack?.split("\n");
|
|
515
|
-
if (stack) for (let i = 1; i < stack.length; i++) {
|
|
516
|
-
const match = stack[i].trim().match(/\(?([^()]+):(\d+):(\d+)\)?$/);
|
|
517
|
-
if (!match) continue;
|
|
518
|
-
const [, source, line, column] = match;
|
|
519
|
-
if (!source.includes(viewModulePath)) {
|
|
520
|
-
const file = source.startsWith("at ") ? source.slice(3) : source;
|
|
521
|
-
return `${/^[A-Za-z][\d+.A-Za-z-]*:/.test(file) ? new URL(file).pathname : file}:${line}:${column}`;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
} catch {}
|
|
525
|
-
return String(id++);
|
|
526
|
-
};
|
|
527
|
-
/**
|
|
528
|
-
* Creates a reusable view for an object using the declared selection.
|
|
529
|
-
*
|
|
530
|
-
* @example
|
|
531
|
-
* const PostView = view<Post>()({
|
|
532
|
-
* id: true,
|
|
533
|
-
* title: true,
|
|
534
|
-
* });
|
|
535
|
-
*/
|
|
536
|
-
function view() {
|
|
537
|
-
const viewId = getStableId();
|
|
538
|
-
return (select) => {
|
|
539
|
-
const payload = Object.freeze({
|
|
540
|
-
select,
|
|
541
|
-
[ViewKind]: true
|
|
542
|
-
});
|
|
543
|
-
const viewComposition = Object.defineProperty({}, getViewTag(viewId), {
|
|
544
|
-
configurable: false,
|
|
545
|
-
enumerable: true,
|
|
546
|
-
value: payload,
|
|
547
|
-
writable: false
|
|
548
|
-
});
|
|
549
|
-
return Object.freeze(viewComposition);
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
//#endregion
|
|
553
|
-
//#region src/ref.ts
|
|
554
|
-
/**
|
|
555
|
-
* Builds the canonical cache ID for an entity.
|
|
556
|
-
*/
|
|
557
|
-
const toEntityId = (type, rawId) => `${type}:${String(rawId)}`;
|
|
558
|
-
/**
|
|
559
|
-
* Splits a cache entity ID back into its type and raw identifier.
|
|
560
|
-
*/
|
|
561
|
-
function parseEntityId(id) {
|
|
562
|
-
const idx = id.indexOf(":");
|
|
563
|
-
return idx < 0 ? {
|
|
564
|
-
id,
|
|
565
|
-
type: ""
|
|
566
|
-
} : {
|
|
567
|
-
id: id.slice(idx + 1),
|
|
568
|
-
type: id.slice(0, idx)
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Attaches view tags to a ref without leaking the symbol.
|
|
573
|
-
*/
|
|
574
|
-
function assignViewTag(target, value) {
|
|
575
|
-
Object.defineProperty(target, ViewsTag, {
|
|
576
|
-
configurable: false,
|
|
577
|
-
enumerable: false,
|
|
578
|
-
value,
|
|
579
|
-
writable: false
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
const getRootViewNames = (view) => {
|
|
583
|
-
const names = new Set(getViewNames(view));
|
|
584
|
-
const payloads = getViewPayloads(view, null);
|
|
585
|
-
for (const payload of payloads) for (const name of getSelectionViewNames(payload.select)) names.add(name);
|
|
586
|
-
return names;
|
|
587
|
-
};
|
|
588
|
-
function createRefWithViewNames(__typename, id, names) {
|
|
589
|
-
const ref = {
|
|
590
|
-
__typename,
|
|
591
|
-
id
|
|
592
|
-
};
|
|
593
|
-
assignViewTag(ref, names);
|
|
594
|
-
return Object.freeze(ref);
|
|
595
|
-
}
|
|
596
|
-
//#endregion
|
|
597
|
-
//#region src/selection.ts
|
|
598
|
-
const isConnectionSelection = (value) => isRecord(value.items) && "node" in value.items;
|
|
599
|
-
/**
|
|
600
|
-
* Flattens a view into a `SelectionPlan`, expanding composed views and
|
|
601
|
-
* partitioning nested args so the client can fetch exactly what is declared.
|
|
602
|
-
*/
|
|
603
|
-
const getSelectionPlan = (viewComposition, ref) => {
|
|
604
|
-
const args = /* @__PURE__ */ new Map();
|
|
605
|
-
const live = /* @__PURE__ */ new Map();
|
|
606
|
-
const paths = /* @__PURE__ */ new Set();
|
|
607
|
-
const assignArgs = (path, value, ignoreKeys) => {
|
|
608
|
-
const hash = hashArgs(value, { ignoreKeys });
|
|
609
|
-
args.set(path, {
|
|
610
|
-
hash,
|
|
611
|
-
ignoreKeys,
|
|
612
|
-
value
|
|
613
|
-
});
|
|
614
|
-
};
|
|
615
|
-
const walk = (selection, prefix, context = "default") => {
|
|
616
|
-
if (prefix === null && context !== "connection" && isConnectionSelection(selection)) {
|
|
617
|
-
if (selection.live && isRecord(selection.live)) live.set("", {
|
|
618
|
-
append: selection.live.append === "visible" ? "visible" : "edge",
|
|
619
|
-
prepend: selection.live.prepend === "visible" ? "visible" : "edge"
|
|
620
|
-
});
|
|
621
|
-
if (selection.args && isRecord(selection.args)) {
|
|
622
|
-
const clonedArgs = cloneArgs(selection.args, "args");
|
|
623
|
-
const ignoreKeys = isRecord(selection.items) && isRecord(selection.items.node) ? paginationArgKeys : void 0;
|
|
624
|
-
assignArgs("", clonedArgs, ignoreKeys);
|
|
625
|
-
}
|
|
626
|
-
const { args: _args, live: _live, ...withoutArgs } = selection;
|
|
627
|
-
walk(withoutArgs, prefix, "connection");
|
|
628
|
-
return;
|
|
629
|
-
}
|
|
630
|
-
for (const [key, value] of Object.entries(selection)) {
|
|
631
|
-
const valueType = typeof value;
|
|
632
|
-
const path = prefix ? `${prefix}.${key}` : key;
|
|
633
|
-
if (context === "connection") {
|
|
634
|
-
if (key === "args" || key === "live" || key === "pagination") continue;
|
|
635
|
-
if (key === "items" && isRecord(value)) {
|
|
636
|
-
if (isRecord(value.node)) walk(value.node, prefix);
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
if (valueType === "boolean") {
|
|
641
|
-
if (value) paths.add(path);
|
|
642
|
-
continue;
|
|
643
|
-
}
|
|
644
|
-
if (isDeferredSelection(value)) continue;
|
|
645
|
-
if (isViewTag(key)) {
|
|
646
|
-
if (!ref || ref[ViewsTag] && ref[ViewsTag].has(key)) walk(value.select, prefix);
|
|
647
|
-
continue;
|
|
648
|
-
}
|
|
649
|
-
if (isRecord(value)) {
|
|
650
|
-
const selectionObject = value;
|
|
651
|
-
if (isConnectionSelection(selectionObject)) {
|
|
652
|
-
if (selectionObject.live && isRecord(selectionObject.live)) live.set(path, {
|
|
653
|
-
append: selectionObject.live.append === "visible" ? "visible" : "edge",
|
|
654
|
-
prepend: selectionObject.live.prepend === "visible" ? "visible" : "edge"
|
|
655
|
-
});
|
|
656
|
-
if (selectionObject.args && isRecord(selectionObject.args)) {
|
|
657
|
-
const clonedArgs = cloneArgs(selectionObject.args, path);
|
|
658
|
-
const ignoreKeys = isRecord(selectionObject.items) && isRecord(selectionObject.items.node) ? paginationArgKeys : void 0;
|
|
659
|
-
assignArgs(path, clonedArgs, ignoreKeys);
|
|
660
|
-
}
|
|
661
|
-
const { args: _ignored, live: _live, ...withoutArgs } = selectionObject;
|
|
662
|
-
walk(withoutArgs, path, "connection");
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
const hasArgs = selectionObject.args && isRecord(selectionObject.args);
|
|
666
|
-
let selectionWithoutArgs = selectionObject;
|
|
667
|
-
if (hasArgs) {
|
|
668
|
-
const clonedArgs = cloneArgs(selectionObject.args, path);
|
|
669
|
-
const ignoreKeys = isRecord(selectionObject.items) && isRecord(selectionObject.items?.node) ? paginationArgKeys : void 0;
|
|
670
|
-
assignArgs(path, clonedArgs, ignoreKeys);
|
|
671
|
-
const { args: _args, ...rest } = selectionObject;
|
|
672
|
-
selectionWithoutArgs = rest;
|
|
673
|
-
}
|
|
674
|
-
if (Object.keys(selectionWithoutArgs).length > 0) walk(selectionWithoutArgs, path);
|
|
675
|
-
else if (hasArgs) paths.add(path);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
|
-
const payloads = getViewPayloads(viewComposition, ref);
|
|
680
|
-
if (payloads.length === 0 && isRecord(viewComposition)) walk(viewComposition, null);
|
|
681
|
-
else for (const payload of payloads) walk(payload.select, null);
|
|
682
|
-
return {
|
|
683
|
-
args,
|
|
684
|
-
live,
|
|
685
|
-
paths
|
|
686
|
-
};
|
|
687
|
-
};
|
|
688
|
-
const getDeferredSelectionPlan = (field, selection) => {
|
|
689
|
-
return getSelectionPlan({ [field]: selection }, null);
|
|
690
|
-
};
|
|
691
|
-
//#endregion
|
|
692
|
-
//#region src/mutation.ts
|
|
693
|
-
/**
|
|
694
|
-
* Defines a mutation for a given entity type, preserving the input and output
|
|
695
|
-
* types for transports.
|
|
696
|
-
*/
|
|
697
|
-
function mutation(entity) {
|
|
698
|
-
return Object.freeze({
|
|
699
|
-
entity,
|
|
700
|
-
[MutationKind]: true
|
|
701
|
-
});
|
|
702
|
-
}
|
|
703
|
-
const collectImplicitSelectedPaths = (value) => {
|
|
704
|
-
const paths = /* @__PURE__ */ new Set();
|
|
705
|
-
const walk = (current, prefix) => {
|
|
706
|
-
if (!current || typeof current !== "object") {
|
|
707
|
-
if (prefix) paths.add(prefix);
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
if (Array.isArray(current)) {
|
|
711
|
-
if (prefix) paths.add(prefix);
|
|
712
|
-
for (const child of current) walk(child, prefix);
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
for (const [key, child] of Object.entries(current)) {
|
|
716
|
-
const next = prefix ? `${prefix}.${key}` : key;
|
|
717
|
-
paths.add(next);
|
|
718
|
-
walk(child, next);
|
|
719
|
-
}
|
|
720
|
-
};
|
|
721
|
-
walk(value, null);
|
|
722
|
-
return paths;
|
|
723
|
-
};
|
|
724
|
-
const maybeGetId = (getId, input) => {
|
|
725
|
-
try {
|
|
726
|
-
return getId(input);
|
|
727
|
-
} catch {
|
|
728
|
-
return null;
|
|
729
|
-
}
|
|
730
|
-
};
|
|
731
|
-
const emptySet$1 = /* @__PURE__ */ new Set();
|
|
732
|
-
/**
|
|
733
|
-
* Binds a mutation definition to a `FateClient`, wiring up optimistic updates,
|
|
734
|
-
* cache writes, and error handling.
|
|
735
|
-
*/
|
|
736
|
-
function wrapMutation(client, identifier) {
|
|
737
|
-
const config = client.getTypeConfig(identifier.entity);
|
|
738
|
-
return async ({ args, delete: deleteRecord, input, insert = "after", optimistic, view }) => {
|
|
739
|
-
const id = maybeGetId(config.getId, input);
|
|
740
|
-
const plan = view ? getSelectionPlan(view, null) : void 0;
|
|
741
|
-
const viewSelection = plan?.paths;
|
|
742
|
-
const optimisticRecord = optimistic ? id != null ? {
|
|
743
|
-
id,
|
|
744
|
-
...optimistic
|
|
745
|
-
} : optimistic : void 0;
|
|
746
|
-
const optimisticRecordId = optimisticRecord !== void 0 ? maybeGetId(config.getId, optimisticRecord) : null;
|
|
747
|
-
const optimisticEntityId = id != null ? toEntityId(identifier.entity, id) : optimisticRecordId != null ? toEntityId(identifier.entity, optimisticRecordId) : null;
|
|
748
|
-
const optimisticSelection = optimisticRecord ? collectImplicitSelectedPaths(optimisticRecord) : void 0;
|
|
749
|
-
const selection = viewSelection || optimisticSelection ? /* @__PURE__ */ new Set([...viewSelection ? [...viewSelection] : [], ...optimisticSelection ? [...optimisticSelection] : []]) : /* @__PURE__ */ new Set();
|
|
750
|
-
if (deleteRecord && id == null) throw new Error(`fate: Mutation '${identifier.key}' requires an 'id' to delete.`);
|
|
751
|
-
const applyOptimistic = () => {
|
|
752
|
-
if (optimisticRecord && optimisticEntityId) client.write(identifier.entity, optimisticRecord, optimisticSelection ?? emptySet$1, plan, insert);
|
|
753
|
-
if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id);
|
|
754
|
-
};
|
|
755
|
-
const settle = client.store.optimisticUpdate(applyOptimistic);
|
|
756
|
-
const performMutation = async () => {
|
|
757
|
-
try {
|
|
758
|
-
const result = await client.executeMutation(identifier.key, input, selection, {
|
|
759
|
-
args,
|
|
760
|
-
plan
|
|
761
|
-
});
|
|
762
|
-
const shouldWriteResult = result && typeof result === "object" && (!deleteRecord || Boolean(view));
|
|
763
|
-
settle(() => {
|
|
764
|
-
if (shouldWriteResult) {
|
|
765
|
-
const select = collectImplicitSelectedPaths(result);
|
|
766
|
-
client.write(identifier.entity, result, select, plan, insert);
|
|
767
|
-
const resultId = maybeGetId(config.getId, result);
|
|
768
|
-
if (optimisticEntityId && resultId != null) client.resolveOptimisticEntity(optimisticEntityId, toEntityId(identifier.entity, resultId));
|
|
769
|
-
if (optimisticRecordId != null && resultId != null && optimisticRecordId !== resultId) client.deleteRecord(identifier.entity, optimisticRecordId);
|
|
770
|
-
}
|
|
771
|
-
if (deleteRecord && id != null) client.deleteRecord(identifier.entity, id);
|
|
772
|
-
});
|
|
773
|
-
return {
|
|
774
|
-
error: void 0,
|
|
775
|
-
result
|
|
776
|
-
};
|
|
777
|
-
} catch (error) {
|
|
778
|
-
settle();
|
|
779
|
-
if (error instanceof Error) {
|
|
780
|
-
const statusCode = getErrorStatusCode(error);
|
|
781
|
-
if ((statusCode ? categorizeHTTPErrorStatus(statusCode) : "boundary") === "boundary") throw error;
|
|
782
|
-
return {
|
|
783
|
-
error,
|
|
784
|
-
result: void 0
|
|
785
|
-
};
|
|
786
|
-
} else throw new Error(`fate: Mutation '${identifier.key}' failed.`, { cause: error });
|
|
787
|
-
}
|
|
788
|
-
};
|
|
789
|
-
const mutationPromise = client.trackPendingRequest(performMutation);
|
|
790
|
-
if (optimisticEntityId) client.registerPendingOptimisticMutation(optimisticEntityId, mutationPromise);
|
|
791
|
-
return mutationPromise;
|
|
792
|
-
};
|
|
793
|
-
}
|
|
794
|
-
const getErrorStatusCode = (error) => {
|
|
795
|
-
if (error instanceof FateRequestError) return error.status;
|
|
796
|
-
const { data } = error;
|
|
797
|
-
if (!data || typeof data !== "object") return;
|
|
798
|
-
return getHTTPStatusCodeFromError(data);
|
|
799
|
-
};
|
|
800
|
-
const categorizeHTTPErrorStatus = (statusCode) => {
|
|
801
|
-
switch (statusCode) {
|
|
802
|
-
case 400:
|
|
803
|
-
case 402:
|
|
804
|
-
case 404:
|
|
805
|
-
case 408:
|
|
806
|
-
case 409:
|
|
807
|
-
case 412:
|
|
808
|
-
case 413:
|
|
809
|
-
case 415:
|
|
810
|
-
case 422:
|
|
811
|
-
case 429:
|
|
812
|
-
case 499: return "callSite";
|
|
813
|
-
default: return "boundary";
|
|
814
|
-
}
|
|
815
|
-
};
|
|
816
|
-
//#endregion
|
|
817
80
|
//#region src/operation-lifetime.ts
|
|
818
81
|
var OperationLifetime = class {
|
|
819
82
|
constructor(releaseBufferSize) {
|
|
@@ -842,6 +105,15 @@ var OperationLifetime = class {
|
|
|
842
105
|
getDescriptors() {
|
|
843
106
|
return [...this.operations.values()].map((operation) => operation.descriptor);
|
|
844
107
|
}
|
|
108
|
+
restore(descriptors) {
|
|
109
|
+
for (const descriptor of descriptors.slice(-Math.max(1, this.releaseBufferSize))) if (!this.operations.has(descriptor.key)) {
|
|
110
|
+
this.operations.set(descriptor.key, {
|
|
111
|
+
descriptor,
|
|
112
|
+
refCount: 0
|
|
113
|
+
});
|
|
114
|
+
this.releaseBuffer.push(descriptor.key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
845
117
|
release(key) {
|
|
846
118
|
const operation = this.operations.get(key);
|
|
847
119
|
if (!operation) return false;
|
|
@@ -870,793 +142,6 @@ var OperationLifetime = class {
|
|
|
870
142
|
}
|
|
871
143
|
};
|
|
872
144
|
//#endregion
|
|
873
|
-
//#region src/mask.ts
|
|
874
|
-
function emptyMask() {
|
|
875
|
-
return {
|
|
876
|
-
all: false,
|
|
877
|
-
children: /* @__PURE__ */ new Map()
|
|
878
|
-
};
|
|
879
|
-
}
|
|
880
|
-
function cloneMask(m) {
|
|
881
|
-
const clone = {
|
|
882
|
-
all: m.all,
|
|
883
|
-
children: /* @__PURE__ */ new Map()
|
|
884
|
-
};
|
|
885
|
-
for (const [key, value] of m.children) clone.children.set(key, cloneMask(value));
|
|
886
|
-
return clone;
|
|
887
|
-
}
|
|
888
|
-
function addPath(mask, path) {
|
|
889
|
-
if (mask.all) return;
|
|
890
|
-
const parts = path.split(".");
|
|
891
|
-
let current = mask;
|
|
892
|
-
for (let i = 0; i < parts.length; i++) {
|
|
893
|
-
const seg = parts[i];
|
|
894
|
-
let child = current.children.get(seg);
|
|
895
|
-
if (!child) {
|
|
896
|
-
child = emptyMask();
|
|
897
|
-
current.children.set(seg, child);
|
|
898
|
-
}
|
|
899
|
-
current = child;
|
|
900
|
-
}
|
|
901
|
-
current.all = true;
|
|
902
|
-
current.children.clear();
|
|
903
|
-
}
|
|
904
|
-
function union(into, b) {
|
|
905
|
-
if (into.all || b.all) {
|
|
906
|
-
into.all = true;
|
|
907
|
-
into.children.clear();
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
for (const [key, child] of b.children) {
|
|
911
|
-
const exist = into.children.get(key);
|
|
912
|
-
if (!exist) into.children.set(key, cloneMask(child));
|
|
913
|
-
else union(exist, child);
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
function fromPaths(paths) {
|
|
917
|
-
const mask = emptyMask();
|
|
918
|
-
for (const path of paths) addPath(mask, path);
|
|
919
|
-
return mask;
|
|
920
|
-
}
|
|
921
|
-
function toPaths(mask) {
|
|
922
|
-
const paths = [];
|
|
923
|
-
const visit = (current, prefix) => {
|
|
924
|
-
if (current.all) {
|
|
925
|
-
if (prefix) paths.push(prefix);
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
for (const [key, child] of current.children) visit(child, prefix ? `${prefix}.${key}` : key);
|
|
929
|
-
};
|
|
930
|
-
visit(mask, null);
|
|
931
|
-
return paths;
|
|
932
|
-
}
|
|
933
|
-
function isCovered(mask, path) {
|
|
934
|
-
if (mask.all) return true;
|
|
935
|
-
const parts = path.split(".");
|
|
936
|
-
let current = mask;
|
|
937
|
-
for (let i = 0; i < parts.length; i++) {
|
|
938
|
-
if (!current) return false;
|
|
939
|
-
if (current.all) return true;
|
|
940
|
-
current = current.children.get(parts[i]);
|
|
941
|
-
}
|
|
942
|
-
return !!current && (current.all || current.children.size === 0);
|
|
943
|
-
}
|
|
944
|
-
function diffPaths(paths, mask) {
|
|
945
|
-
const missing = /* @__PURE__ */ new Set();
|
|
946
|
-
for (const path of paths) if (!isCovered(mask, path)) missing.add(path);
|
|
947
|
-
return missing;
|
|
948
|
-
}
|
|
949
|
-
function intersects(a, b) {
|
|
950
|
-
if (a.all || b.all) return true;
|
|
951
|
-
for (const [key, child] of a.children) {
|
|
952
|
-
const otherChild = b.children.get(key);
|
|
953
|
-
if (!otherChild) continue;
|
|
954
|
-
if (child.all || otherChild.all) return true;
|
|
955
|
-
if (intersects(child, otherChild)) return true;
|
|
956
|
-
}
|
|
957
|
-
return false;
|
|
958
|
-
}
|
|
959
|
-
//#endregion
|
|
960
|
-
//#region src/store.ts
|
|
961
|
-
const listKeySeparator = " __fate__ ";
|
|
962
|
-
const decodeListKeyPart = (part) => {
|
|
963
|
-
try {
|
|
964
|
-
return decodeURIComponent(part);
|
|
965
|
-
} catch {
|
|
966
|
-
return part;
|
|
967
|
-
}
|
|
968
|
-
};
|
|
969
|
-
const encodeListKeyPart = (part) => encodeURIComponent(part);
|
|
970
|
-
const getListKey = (ownerId, field, hash = "default") => `${encodeListKeyPart(ownerId)}${listKeySeparator}${encodeListKeyPart(field)}${listKeySeparator}${encodeListKeyPart(hash)}`;
|
|
971
|
-
const getOwnerFieldKey = (ownerId, field) => JSON.stringify([ownerId, field]);
|
|
972
|
-
const parseListKey = (key) => {
|
|
973
|
-
const parts = key.split(listKeySeparator);
|
|
974
|
-
if (parts.length !== 3) return null;
|
|
975
|
-
const [ownerId, field] = parts;
|
|
976
|
-
return {
|
|
977
|
-
field: decodeListKeyPart(field),
|
|
978
|
-
ownerId: decodeListKeyPart(ownerId)
|
|
979
|
-
};
|
|
980
|
-
};
|
|
981
|
-
const isPlainRecord = (value) => {
|
|
982
|
-
if (value == null || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
|
|
983
|
-
const prototype = Object.getPrototypeOf(value);
|
|
984
|
-
return prototype === Object.prototype || prototype === null;
|
|
985
|
-
};
|
|
986
|
-
const isDate = (value) => value != null && typeof value === "object" && Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function";
|
|
987
|
-
const mergePreservingExisting = (incoming, existing) => {
|
|
988
|
-
if (!isPlainRecord(incoming) || !isPlainRecord(existing)) return existing;
|
|
989
|
-
const result = Object.create(null);
|
|
990
|
-
for (const [key, value] of Object.entries(incoming)) Object.defineProperty(result, key, {
|
|
991
|
-
configurable: true,
|
|
992
|
-
enumerable: true,
|
|
993
|
-
value,
|
|
994
|
-
writable: true
|
|
995
|
-
});
|
|
996
|
-
for (const [key, value] of Object.entries(existing)) Object.defineProperty(result, key, {
|
|
997
|
-
configurable: true,
|
|
998
|
-
enumerable: true,
|
|
999
|
-
value: Object.hasOwn(result, key) ? mergePreservingExisting(result[key], value) : value,
|
|
1000
|
-
writable: true
|
|
1001
|
-
});
|
|
1002
|
-
return result;
|
|
1003
|
-
};
|
|
1004
|
-
const areHydrationValuesEqual = (left, right) => {
|
|
1005
|
-
if (Object.is(left, right)) return true;
|
|
1006
|
-
if (isNodeRef(left) || isNodeRef(right)) return isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right);
|
|
1007
|
-
if (isDate(left) || isDate(right)) return isDate(left) && isDate(right) && left.getTime() === right.getTime();
|
|
1008
|
-
if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areHydrationValuesEqual(value, right[index]));
|
|
1009
|
-
if (!isPlainRecord(left) || !isPlainRecord(right)) return false;
|
|
1010
|
-
const leftKeys = Object.keys(left);
|
|
1011
|
-
const rightKeys = Object.keys(right);
|
|
1012
|
-
return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && areHydrationValuesEqual(left[key], right[key]));
|
|
1013
|
-
};
|
|
1014
|
-
const areMasksEqual = (left, right) => {
|
|
1015
|
-
if (left === right || left?.all && right?.all) return true;
|
|
1016
|
-
if (!left || !right || left.all !== right.all || left.children.size !== right.children.size) return false;
|
|
1017
|
-
for (const [key, child] of left.children) if (!areMasksEqual(child, right.children.get(key))) return false;
|
|
1018
|
-
return true;
|
|
1019
|
-
};
|
|
1020
|
-
const emptyFunction = () => {};
|
|
1021
|
-
const hasReferences = (value) => isNodeRef(value) || Array.isArray(value) && value.some(isNodeRef);
|
|
1022
|
-
const areNormalizedValuesEqual = (left, right) => Object.is(left, right) || isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right) || Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => areNormalizedValuesEqual(value, right[index]));
|
|
1023
|
-
var Store = class {
|
|
1024
|
-
constructor(onRebase) {
|
|
1025
|
-
this.onRebase = onRebase;
|
|
1026
|
-
this.coverage = /* @__PURE__ */ new Map();
|
|
1027
|
-
this.listKeysByOwnerField = /* @__PURE__ */ new Map();
|
|
1028
|
-
this.listKeysByReferencedEntity = /* @__PURE__ */ new Map();
|
|
1029
|
-
this.lists = /* @__PURE__ */ new Map();
|
|
1030
|
-
this.recordReferenceFields = /* @__PURE__ */ new Map();
|
|
1031
|
-
this.recordReferencesByTarget = /* @__PURE__ */ new Map();
|
|
1032
|
-
this.records = /* @__PURE__ */ new Map();
|
|
1033
|
-
this.subscriptions = /* @__PURE__ */ new Map();
|
|
1034
|
-
this.listSubscriptions = /* @__PURE__ */ new Map();
|
|
1035
|
-
this.optimisticLayers = /* @__PURE__ */ new Set();
|
|
1036
|
-
this.optimisticBase = {
|
|
1037
|
-
lists: /* @__PURE__ */ new Map(),
|
|
1038
|
-
records: /* @__PURE__ */ new Map()
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
get hasOptimisticUpdates() {
|
|
1042
|
-
return this.optimisticLayers.size > 0;
|
|
1043
|
-
}
|
|
1044
|
-
get isRebasing() {
|
|
1045
|
-
return this.rebase !== void 0;
|
|
1046
|
-
}
|
|
1047
|
-
/** Apply a synchronous cache update beneath the pending optimistic layers. */
|
|
1048
|
-
update(apply) {
|
|
1049
|
-
if (this.rebase || this.optimisticLayers.size === 0) return apply();
|
|
1050
|
-
const rebase = {
|
|
1051
|
-
lists: /* @__PURE__ */ new Map(),
|
|
1052
|
-
records: /* @__PURE__ */ new Map()
|
|
1053
|
-
};
|
|
1054
|
-
this.rebase = rebase;
|
|
1055
|
-
try {
|
|
1056
|
-
for (const [id, snapshot] of this.optimisticBase.records) this.restore(id, snapshot);
|
|
1057
|
-
for (const [key, list] of this.optimisticBase.lists) this.restoreList(key, list);
|
|
1058
|
-
this.optimisticBase.records.clear();
|
|
1059
|
-
this.optimisticBase.lists.clear();
|
|
1060
|
-
return apply();
|
|
1061
|
-
} finally {
|
|
1062
|
-
try {
|
|
1063
|
-
for (const layer of this.optimisticLayers) {
|
|
1064
|
-
layer.writes.clear();
|
|
1065
|
-
this.recordingLayer = layer;
|
|
1066
|
-
layer.apply();
|
|
1067
|
-
}
|
|
1068
|
-
} finally {
|
|
1069
|
-
this.recordingLayer = void 0;
|
|
1070
|
-
const records = this.getRebaseChanges(rebase);
|
|
1071
|
-
const lists = /* @__PURE__ */ new Set();
|
|
1072
|
-
for (const [key, before] of rebase.lists) if (!areHydrationValuesEqual(before, this.lists.get(key))) {
|
|
1073
|
-
lists.add(key);
|
|
1074
|
-
const owner = parseListKey(key);
|
|
1075
|
-
if (owner) {
|
|
1076
|
-
if (!records.has(owner.ownerId)) records.set(owner.ownerId, /* @__PURE__ */ new Set([owner.field]));
|
|
1077
|
-
else records.get(owner.ownerId)?.add(owner.field);
|
|
1078
|
-
}
|
|
1079
|
-
} else if (before) this.lists.set(key, before);
|
|
1080
|
-
this.rebase = void 0;
|
|
1081
|
-
this.onRebase?.(new Set(records.keys()));
|
|
1082
|
-
for (const [id, paths] of records) this.notify(id, paths);
|
|
1083
|
-
for (const key of lists) this.notifyListSubscribers(key);
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
getRebaseChanges(rebase) {
|
|
1088
|
-
const changes = /* @__PURE__ */ new Map();
|
|
1089
|
-
for (const [id, before] of rebase.records) {
|
|
1090
|
-
const after = this.records.get(id);
|
|
1091
|
-
const mask = this.coverage.get(id);
|
|
1092
|
-
if (Boolean(before.record) !== Boolean(after) || before.mask?.all !== mask?.all) {
|
|
1093
|
-
changes.set(id, void 0);
|
|
1094
|
-
continue;
|
|
1095
|
-
}
|
|
1096
|
-
const paths = /* @__PURE__ */ new Set();
|
|
1097
|
-
for (const key of /* @__PURE__ */ new Set([...Object.keys(before.record ?? {}), ...Object.keys(after ?? {})])) if (Object.hasOwn(before.record ?? {}, key) !== Object.hasOwn(after ?? {}, key) || !areNormalizedValuesEqual(before.record?.[key], after?.[key])) paths.add(key);
|
|
1098
|
-
if (!areMasksEqual(before.mask, mask)) {
|
|
1099
|
-
for (const path of before.mask ? toPaths(before.mask) : []) if (!mask || !isCovered(mask, path)) paths.add(path);
|
|
1100
|
-
for (const path of mask ? toPaths(mask) : []) if (!before.mask || !isCovered(before.mask, path)) paths.add(path);
|
|
1101
|
-
}
|
|
1102
|
-
if (paths.size > 0) changes.set(id, paths);
|
|
1103
|
-
else if (before.record) this.records.set(id, before.record);
|
|
1104
|
-
}
|
|
1105
|
-
return changes;
|
|
1106
|
-
}
|
|
1107
|
-
/** Returns an idempotent settlement function; omit its update to roll back. */
|
|
1108
|
-
optimisticUpdate(apply) {
|
|
1109
|
-
const layer = {
|
|
1110
|
-
apply,
|
|
1111
|
-
writes: /* @__PURE__ */ new Map()
|
|
1112
|
-
};
|
|
1113
|
-
const settle = (commit) => {
|
|
1114
|
-
if (!this.optimisticLayers.has(layer) || layer.settled) return;
|
|
1115
|
-
this.update(() => {
|
|
1116
|
-
const overlays = commit ? this.getPendingOverlap(layer, layer.writes) : /* @__PURE__ */ new Map();
|
|
1117
|
-
if (overlays.size > 0) {
|
|
1118
|
-
layer.settled = true;
|
|
1119
|
-
layer.apply = () => {
|
|
1120
|
-
for (const [id, { partial, paths }] of this.getPendingOverlap(layer, overlays)) this.merge(id, partial, paths);
|
|
1121
|
-
};
|
|
1122
|
-
} else this.optimisticLayers.delete(layer);
|
|
1123
|
-
for (const pending of this.optimisticLayers) {
|
|
1124
|
-
if (!pending.settled) break;
|
|
1125
|
-
this.optimisticLayers.delete(pending);
|
|
1126
|
-
}
|
|
1127
|
-
commit?.();
|
|
1128
|
-
});
|
|
1129
|
-
};
|
|
1130
|
-
this.optimisticLayers.add(layer);
|
|
1131
|
-
try {
|
|
1132
|
-
this.update(() => {});
|
|
1133
|
-
} catch (error) {
|
|
1134
|
-
settle();
|
|
1135
|
-
throw error;
|
|
1136
|
-
}
|
|
1137
|
-
return settle;
|
|
1138
|
-
}
|
|
1139
|
-
getPendingOverlap(layer, writes) {
|
|
1140
|
-
const earlier = [];
|
|
1141
|
-
for (const pending of this.optimisticLayers) {
|
|
1142
|
-
if (pending === layer) break;
|
|
1143
|
-
if (!pending.settled) earlier.push(pending);
|
|
1144
|
-
}
|
|
1145
|
-
const overlaps = /* @__PURE__ */ new Map();
|
|
1146
|
-
for (const [id, write] of writes) {
|
|
1147
|
-
const partial = Object.fromEntries(Object.entries(write.partial).filter(([key, value]) => !hasReferences(value) && earlier.some(({ writes }) => {
|
|
1148
|
-
const previous = writes.get(id)?.partial;
|
|
1149
|
-
return previous && Object.hasOwn(previous, key) && !hasReferences(previous[key]);
|
|
1150
|
-
})));
|
|
1151
|
-
if (Object.keys(partial).length) overlaps.set(id, {
|
|
1152
|
-
partial,
|
|
1153
|
-
paths: new Set([...write.paths].filter((path) => Object.hasOwn(partial, path.split(".")[0])))
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
return overlaps;
|
|
1157
|
-
}
|
|
1158
|
-
captureRecord(id) {
|
|
1159
|
-
if (!this.rebase) return;
|
|
1160
|
-
for (const target of [this.rebase, this.recordingLayer && this.optimisticBase]) if (target && !target.records.has(id)) {
|
|
1161
|
-
const mask = this.coverage.get(id);
|
|
1162
|
-
target.records.set(id, {
|
|
1163
|
-
mask: mask ? cloneMask(mask) : void 0,
|
|
1164
|
-
record: this.records.get(id)
|
|
1165
|
-
});
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
captureList(key) {
|
|
1169
|
-
if (!this.rebase) return;
|
|
1170
|
-
for (const target of [this.rebase, this.recordingLayer && this.optimisticBase]) if (target && !target.lists.has(key)) target.lists.set(key, this.lists.get(key));
|
|
1171
|
-
}
|
|
1172
|
-
dehydrate() {
|
|
1173
|
-
return {
|
|
1174
|
-
coverage: [...this.coverage].map(([id, mask]) => [id, toPaths(mask)]),
|
|
1175
|
-
lists: [...this.lists],
|
|
1176
|
-
records: [...this.records]
|
|
1177
|
-
};
|
|
1178
|
-
}
|
|
1179
|
-
hydrate(state, mode, options = {}) {
|
|
1180
|
-
const previousCoverage = this.coverage;
|
|
1181
|
-
const previousLists = this.lists;
|
|
1182
|
-
const previousRecords = this.records;
|
|
1183
|
-
const coverage = mode === "replace" ? /* @__PURE__ */ new Map() : new Map([...previousCoverage].map(([id, mask]) => [id, cloneMask(mask)]));
|
|
1184
|
-
const lists = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousLists);
|
|
1185
|
-
const records = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousRecords);
|
|
1186
|
-
for (const [id, incoming] of state.records) {
|
|
1187
|
-
const previous = records.get(id);
|
|
1188
|
-
const next = previous && mode === "preserve-existing" ? mergePreservingExisting(incoming, previous) : incoming;
|
|
1189
|
-
records.set(id, previous && areHydrationValuesEqual(previous, next) ? previous : next);
|
|
1190
|
-
}
|
|
1191
|
-
for (const [id, paths] of state.coverage) {
|
|
1192
|
-
const incoming = fromPaths(paths);
|
|
1193
|
-
const previous = coverage.get(id);
|
|
1194
|
-
if (previous && mode === "preserve-existing") union(previous, incoming);
|
|
1195
|
-
else coverage.set(id, incoming);
|
|
1196
|
-
}
|
|
1197
|
-
for (const [key, list] of state.lists) {
|
|
1198
|
-
const previous = lists.get(key);
|
|
1199
|
-
if (mode === "replace" || !previous) lists.set(key, previous && areHydrationValuesEqual(previous, list) ? previous : list);
|
|
1200
|
-
}
|
|
1201
|
-
this.coverage = coverage;
|
|
1202
|
-
this.lists = lists;
|
|
1203
|
-
this.records = records;
|
|
1204
|
-
this.rebuildIndexes();
|
|
1205
|
-
const changedRecordIds = /* @__PURE__ */ new Set();
|
|
1206
|
-
for (const id of /* @__PURE__ */ new Set([...previousRecords.keys(), ...records.keys()])) if (!areHydrationValuesEqual(previousRecords.get(id), records.get(id)) || !areMasksEqual(previousCoverage.get(id), coverage.get(id))) changedRecordIds.add(id);
|
|
1207
|
-
const changedListKeys = /* @__PURE__ */ new Set();
|
|
1208
|
-
for (const key of /* @__PURE__ */ new Set([...previousLists.keys(), ...lists.keys()])) if (!areHydrationValuesEqual(previousLists.get(key), lists.get(key))) changedListKeys.add(key);
|
|
1209
|
-
const notify = () => {
|
|
1210
|
-
for (const id of changedRecordIds) this.notify(id);
|
|
1211
|
-
for (const key of changedListKeys) this.notifyListSubscribers(key);
|
|
1212
|
-
};
|
|
1213
|
-
if (!options.deferNotifications) notify();
|
|
1214
|
-
return notify;
|
|
1215
|
-
}
|
|
1216
|
-
rebuildIndexes() {
|
|
1217
|
-
this.listKeysByOwnerField.clear();
|
|
1218
|
-
this.listKeysByReferencedEntity.clear();
|
|
1219
|
-
this.recordReferenceFields.clear();
|
|
1220
|
-
this.recordReferencesByTarget.clear();
|
|
1221
|
-
for (const [id, record] of this.records) this.addRecordReferenceIndexes(id, record);
|
|
1222
|
-
for (const [key, list] of this.lists) this.addListIndexes(key, list);
|
|
1223
|
-
}
|
|
1224
|
-
read(id) {
|
|
1225
|
-
return this.records.get(id);
|
|
1226
|
-
}
|
|
1227
|
-
merge(id, partial, paths) {
|
|
1228
|
-
return this.update(() => {
|
|
1229
|
-
const changedPaths = this.mergeInternal(id, partial, paths);
|
|
1230
|
-
if (changedPaths) this.notify(id, changedPaths);
|
|
1231
|
-
});
|
|
1232
|
-
}
|
|
1233
|
-
mergeInternal(id, partial, paths) {
|
|
1234
|
-
this.captureRecord(id);
|
|
1235
|
-
const selectedPaths = this.recordingLayer ? new Set(paths) : paths;
|
|
1236
|
-
if (this.recordingLayer) {
|
|
1237
|
-
const write = this.recordingLayer.writes.get(id) ?? {
|
|
1238
|
-
partial: {},
|
|
1239
|
-
paths: /* @__PURE__ */ new Set()
|
|
1240
|
-
};
|
|
1241
|
-
Object.assign(write.partial, partial);
|
|
1242
|
-
for (const path of selectedPaths) write.paths.add(path);
|
|
1243
|
-
this.recordingLayer.writes.set(id, write);
|
|
1244
|
-
}
|
|
1245
|
-
const previous = this.records.get(id);
|
|
1246
|
-
const changedPaths = /* @__PURE__ */ new Set();
|
|
1247
|
-
let mask = this.coverage.get(id);
|
|
1248
|
-
if (!mask) {
|
|
1249
|
-
mask = emptyMask();
|
|
1250
|
-
this.coverage.set(id, mask);
|
|
1251
|
-
}
|
|
1252
|
-
union(mask, fromPaths(selectedPaths));
|
|
1253
|
-
if (previous) {
|
|
1254
|
-
let hasChanges = false;
|
|
1255
|
-
for (const [key, value] of Object.entries(partial)) if (previous[key] !== value) {
|
|
1256
|
-
hasChanges = true;
|
|
1257
|
-
changedPaths.add(key);
|
|
1258
|
-
}
|
|
1259
|
-
if (!hasChanges) return null;
|
|
1260
|
-
const nextRecord = {
|
|
1261
|
-
...previous,
|
|
1262
|
-
...partial
|
|
1263
|
-
};
|
|
1264
|
-
this.records.set(id, nextRecord);
|
|
1265
|
-
this.updateRecordReferenceIndexes(id, previous, nextRecord, changedPaths);
|
|
1266
|
-
} else {
|
|
1267
|
-
const nextRecord = { ...partial };
|
|
1268
|
-
this.records.set(id, nextRecord);
|
|
1269
|
-
this.updateRecordReferenceIndexes(id, void 0, nextRecord, Object.keys(nextRecord));
|
|
1270
|
-
}
|
|
1271
|
-
return changedPaths;
|
|
1272
|
-
}
|
|
1273
|
-
deleteRecord(id) {
|
|
1274
|
-
return this.update(() => {
|
|
1275
|
-
this.captureRecord(id);
|
|
1276
|
-
const record = this.records.get(id);
|
|
1277
|
-
if (record) this.removeRecordReferenceIndexes(id, record);
|
|
1278
|
-
this.records.delete(id);
|
|
1279
|
-
this.coverage.delete(id);
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
missingForSelection(id, paths) {
|
|
1283
|
-
const requested = new Set(paths);
|
|
1284
|
-
if (!this.records.has(id)) return requested;
|
|
1285
|
-
const mask = this.coverage.get(id);
|
|
1286
|
-
if (!mask) return requested;
|
|
1287
|
-
return diffPaths(requested, mask);
|
|
1288
|
-
}
|
|
1289
|
-
subscribe(id, selectionOrFn, callback) {
|
|
1290
|
-
let mask = null;
|
|
1291
|
-
let fn = emptyFunction;
|
|
1292
|
-
if (typeof selectionOrFn === "function") fn = selectionOrFn;
|
|
1293
|
-
else if (callback) {
|
|
1294
|
-
mask = selectionOrFn ? fromPaths(selectionOrFn) : null;
|
|
1295
|
-
fn = callback;
|
|
1296
|
-
}
|
|
1297
|
-
let subscribers = this.subscriptions.get(id);
|
|
1298
|
-
if (!subscribers) {
|
|
1299
|
-
subscribers = /* @__PURE__ */ new Set();
|
|
1300
|
-
this.subscriptions.set(id, subscribers);
|
|
1301
|
-
}
|
|
1302
|
-
const subscription = {
|
|
1303
|
-
fn,
|
|
1304
|
-
mask
|
|
1305
|
-
};
|
|
1306
|
-
subscribers.add(subscription);
|
|
1307
|
-
return () => {
|
|
1308
|
-
const set = this.subscriptions.get(id);
|
|
1309
|
-
if (!set) return;
|
|
1310
|
-
set.delete(subscription);
|
|
1311
|
-
if (set.size === 0) this.subscriptions.delete(id);
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
notify(id, paths) {
|
|
1315
|
-
if (this.rebase) return;
|
|
1316
|
-
const set = this.subscriptions.get(id);
|
|
1317
|
-
if (!set) return;
|
|
1318
|
-
const changedPaths = paths ? [...paths] : [];
|
|
1319
|
-
const changedMask = changedPaths.length > 0 ? fromPaths(changedPaths) : null;
|
|
1320
|
-
for (const { fn, mask } of set) {
|
|
1321
|
-
if (mask && changedMask && !intersects(changedMask, mask)) continue;
|
|
1322
|
-
try {
|
|
1323
|
-
fn();
|
|
1324
|
-
} catch {}
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
notifyListSubscribers(key) {
|
|
1328
|
-
if (this.rebase) return;
|
|
1329
|
-
const set = this.listSubscriptions.get(key);
|
|
1330
|
-
if (!set) return;
|
|
1331
|
-
for (const fn of set) try {
|
|
1332
|
-
fn();
|
|
1333
|
-
} catch {}
|
|
1334
|
-
}
|
|
1335
|
-
getList(key) {
|
|
1336
|
-
return this.lists.get(key)?.ids;
|
|
1337
|
-
}
|
|
1338
|
-
getListState(key) {
|
|
1339
|
-
return this.lists.get(key);
|
|
1340
|
-
}
|
|
1341
|
-
getListsForField(ownerId, field) {
|
|
1342
|
-
const entries = [];
|
|
1343
|
-
const keys = this.listKeysByOwnerField.get(getOwnerFieldKey(ownerId, field));
|
|
1344
|
-
if (!keys) return entries;
|
|
1345
|
-
for (const key of keys) {
|
|
1346
|
-
const list = this.lists.get(key);
|
|
1347
|
-
if (list) entries.push([key, list]);
|
|
1348
|
-
}
|
|
1349
|
-
return entries;
|
|
1350
|
-
}
|
|
1351
|
-
setList(key, state) {
|
|
1352
|
-
return this.update(() => {
|
|
1353
|
-
this.captureList(key);
|
|
1354
|
-
const previous = this.lists.get(key);
|
|
1355
|
-
if (previous) this.removeListIndexes(key, previous);
|
|
1356
|
-
this.lists.set(key, state);
|
|
1357
|
-
this.addListIndexes(key, state);
|
|
1358
|
-
this.notifyListSubscribers(key);
|
|
1359
|
-
});
|
|
1360
|
-
}
|
|
1361
|
-
replaceListEntityId(previousId, nextId) {
|
|
1362
|
-
return this.update(() => {
|
|
1363
|
-
const keys = [...this.listKeysByReferencedEntity.get(previousId) ?? []];
|
|
1364
|
-
for (const key of keys) {
|
|
1365
|
-
const list = this.lists.get(key);
|
|
1366
|
-
if (!list) continue;
|
|
1367
|
-
let changed = false;
|
|
1368
|
-
let ids = list.ids;
|
|
1369
|
-
let cursors = list.cursors;
|
|
1370
|
-
if (list.ids.includes(previousId)) {
|
|
1371
|
-
const nextIds = [];
|
|
1372
|
-
const nextCursors = list.cursors ? [] : void 0;
|
|
1373
|
-
const seenIds = /* @__PURE__ */ new Set();
|
|
1374
|
-
list.ids.forEach((id, index) => {
|
|
1375
|
-
const resolved = id === previousId ? nextId : id;
|
|
1376
|
-
if (seenIds.has(resolved)) return;
|
|
1377
|
-
seenIds.add(resolved);
|
|
1378
|
-
nextIds.push(resolved);
|
|
1379
|
-
if (nextCursors) nextCursors.push(list.cursors?.[index]);
|
|
1380
|
-
});
|
|
1381
|
-
changed = true;
|
|
1382
|
-
ids = nextIds;
|
|
1383
|
-
cursors = nextCursors;
|
|
1384
|
-
}
|
|
1385
|
-
const dedupe = (values) => {
|
|
1386
|
-
if (!values || !values.includes(previousId)) return;
|
|
1387
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1388
|
-
const next = [];
|
|
1389
|
-
for (const value of values) {
|
|
1390
|
-
const resolved = value === previousId ? nextId : value;
|
|
1391
|
-
if (seen.has(resolved)) continue;
|
|
1392
|
-
seen.add(resolved);
|
|
1393
|
-
next.push(resolved);
|
|
1394
|
-
}
|
|
1395
|
-
changed = true;
|
|
1396
|
-
return next;
|
|
1397
|
-
};
|
|
1398
|
-
const pendingBeforeIds = dedupe(list.pendingBeforeIds) ?? list.pendingBeforeIds;
|
|
1399
|
-
const pendingAfterIds = dedupe(list.pendingAfterIds) ?? list.pendingAfterIds;
|
|
1400
|
-
const liveBeforeIds = dedupe(list.liveBeforeIds) ?? list.liveBeforeIds;
|
|
1401
|
-
const liveAfterIds = dedupe(list.liveAfterIds) ?? list.liveAfterIds;
|
|
1402
|
-
if (!changed) continue;
|
|
1403
|
-
const canonicalIds = new Set(ids);
|
|
1404
|
-
this.setList(key, {
|
|
1405
|
-
backwardPageLimit: list.backwardPageLimit,
|
|
1406
|
-
cursors,
|
|
1407
|
-
forwardPageLimit: list.forwardPageLimit,
|
|
1408
|
-
ids,
|
|
1409
|
-
liveAfterIds: liveAfterIds?.filter((id) => !canonicalIds.has(id)),
|
|
1410
|
-
liveBeforeIds: liveBeforeIds?.filter((id) => !canonicalIds.has(id)),
|
|
1411
|
-
pagination: list.pagination,
|
|
1412
|
-
pendingAfterIds: pendingAfterIds?.filter((id) => !canonicalIds.has(id)),
|
|
1413
|
-
pendingBeforeIds: pendingBeforeIds?.filter((id) => !canonicalIds.has(id))
|
|
1414
|
-
});
|
|
1415
|
-
}
|
|
1416
|
-
});
|
|
1417
|
-
}
|
|
1418
|
-
restoreList(key, list) {
|
|
1419
|
-
return this.update(() => {
|
|
1420
|
-
if (list == null) this.deleteList(key);
|
|
1421
|
-
else this.setList(key, list);
|
|
1422
|
-
});
|
|
1423
|
-
}
|
|
1424
|
-
collectGarbage(markedRecords, markedLists, options = {}) {
|
|
1425
|
-
const records = /* @__PURE__ */ new Set();
|
|
1426
|
-
const lists = /* @__PURE__ */ new Set();
|
|
1427
|
-
for (const id of this.records.keys()) {
|
|
1428
|
-
if (markedRecords.has(id)) continue;
|
|
1429
|
-
records.add(id);
|
|
1430
|
-
}
|
|
1431
|
-
for (const key of this.lists.keys()) {
|
|
1432
|
-
if (markedLists.has(key)) continue;
|
|
1433
|
-
lists.add(key);
|
|
1434
|
-
}
|
|
1435
|
-
for (const id of records) {
|
|
1436
|
-
const record = this.records.get(id);
|
|
1437
|
-
if (record) this.removeRecordReferenceIndexes(id, record);
|
|
1438
|
-
this.records.delete(id);
|
|
1439
|
-
this.coverage.delete(id);
|
|
1440
|
-
options.onRecordDeleted?.(id);
|
|
1441
|
-
}
|
|
1442
|
-
for (const key of lists) this.deleteList(key);
|
|
1443
|
-
return {
|
|
1444
|
-
lists,
|
|
1445
|
-
records
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1448
|
-
deleteList(key) {
|
|
1449
|
-
return this.update(() => {
|
|
1450
|
-
this.captureList(key);
|
|
1451
|
-
const previous = this.lists.get(key);
|
|
1452
|
-
if (previous) this.removeListIndexes(key, previous);
|
|
1453
|
-
this.lists.delete(key);
|
|
1454
|
-
this.notifyListSubscribers(key);
|
|
1455
|
-
});
|
|
1456
|
-
}
|
|
1457
|
-
subscribeList(key, fn) {
|
|
1458
|
-
let set = this.listSubscriptions.get(key);
|
|
1459
|
-
if (!set) {
|
|
1460
|
-
set = /* @__PURE__ */ new Set();
|
|
1461
|
-
this.listSubscriptions.set(key, set);
|
|
1462
|
-
}
|
|
1463
|
-
set.add(fn);
|
|
1464
|
-
return () => {
|
|
1465
|
-
const subscribers = this.listSubscriptions.get(key);
|
|
1466
|
-
if (!subscribers) return;
|
|
1467
|
-
subscribers.delete(fn);
|
|
1468
|
-
if (subscribers.size === 0) this.listSubscriptions.delete(key);
|
|
1469
|
-
};
|
|
1470
|
-
}
|
|
1471
|
-
removeReferencesTo(targetId, viewDataCache) {
|
|
1472
|
-
return this.update(() => {
|
|
1473
|
-
const listKeys = [...this.listKeysByReferencedEntity.get(targetId) ?? []];
|
|
1474
|
-
for (const key of listKeys) {
|
|
1475
|
-
const list = this.lists.get(key);
|
|
1476
|
-
if (!list) continue;
|
|
1477
|
-
const { ids } = list;
|
|
1478
|
-
const hasLiveAfter = Boolean(list.liveAfterIds?.includes(targetId));
|
|
1479
|
-
const hasLiveBefore = Boolean(list.liveBeforeIds?.includes(targetId));
|
|
1480
|
-
const hasPendingAfter = Boolean(list.pendingAfterIds?.includes(targetId));
|
|
1481
|
-
const hasPendingBefore = Boolean(list.pendingBeforeIds?.includes(targetId));
|
|
1482
|
-
if (!ids.includes(targetId) && !hasLiveAfter && !hasLiveBefore && !hasPendingAfter && !hasPendingBefore) continue;
|
|
1483
|
-
const entityIds = [];
|
|
1484
|
-
const cursors = list.cursors ? [] : void 0;
|
|
1485
|
-
for (let index = 0; index < ids.length; index++) {
|
|
1486
|
-
const id = ids[index];
|
|
1487
|
-
if (id === targetId) continue;
|
|
1488
|
-
entityIds.push(id);
|
|
1489
|
-
if (cursors) cursors.push(list.cursors?.[index]);
|
|
1490
|
-
}
|
|
1491
|
-
this.setList(key, {
|
|
1492
|
-
backwardPageLimit: list.backwardPageLimit,
|
|
1493
|
-
cursors,
|
|
1494
|
-
forwardPageLimit: list.forwardPageLimit,
|
|
1495
|
-
ids: entityIds,
|
|
1496
|
-
liveAfterIds: list.liveAfterIds?.filter((id) => id !== targetId),
|
|
1497
|
-
liveBeforeIds: list.liveBeforeIds?.filter((id) => id !== targetId),
|
|
1498
|
-
pagination: list.pagination,
|
|
1499
|
-
pendingAfterIds: list.pendingAfterIds?.filter((id) => id !== targetId),
|
|
1500
|
-
pendingBeforeIds: list.pendingBeforeIds?.filter((id) => id !== targetId)
|
|
1501
|
-
});
|
|
1502
|
-
}
|
|
1503
|
-
const ids = /* @__PURE__ */ new Map();
|
|
1504
|
-
const recordEntries = [...this.recordReferencesByTarget.get(targetId)?.entries() ?? []];
|
|
1505
|
-
for (const [id, fields] of recordEntries) {
|
|
1506
|
-
const record = this.records.get(id);
|
|
1507
|
-
if (!record) continue;
|
|
1508
|
-
let updated = false;
|
|
1509
|
-
const next = {};
|
|
1510
|
-
const paths = /* @__PURE__ */ new Set();
|
|
1511
|
-
for (const key of fields) {
|
|
1512
|
-
const value = record[key];
|
|
1513
|
-
if (Array.isArray(value)) {
|
|
1514
|
-
const filtered = value.filter((item) => !(isNodeRef(item) && getNodeRefId(item) === targetId));
|
|
1515
|
-
if (filtered.length !== value.length) {
|
|
1516
|
-
updated = true;
|
|
1517
|
-
paths.add(key);
|
|
1518
|
-
next[key] = filtered;
|
|
1519
|
-
}
|
|
1520
|
-
} else if (isNodeRef(value) && getNodeRefId(value) === targetId) {
|
|
1521
|
-
updated = true;
|
|
1522
|
-
paths.add(key);
|
|
1523
|
-
next[key] = null;
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
if (!updated) continue;
|
|
1527
|
-
viewDataCache.invalidate(id);
|
|
1528
|
-
this.mergeInternal(id, next, paths);
|
|
1529
|
-
ids.set(id, paths);
|
|
1530
|
-
}
|
|
1531
|
-
for (const [id, paths] of ids) this.notify(id, paths);
|
|
1532
|
-
});
|
|
1533
|
-
}
|
|
1534
|
-
restore(id, snapshot) {
|
|
1535
|
-
return this.update(() => {
|
|
1536
|
-
this.captureRecord(id);
|
|
1537
|
-
const previous = this.records.get(id);
|
|
1538
|
-
if (previous) this.removeRecordReferenceIndexes(id, previous);
|
|
1539
|
-
if (snapshot.record === void 0) this.records.delete(id);
|
|
1540
|
-
else {
|
|
1541
|
-
this.records.set(id, snapshot.record);
|
|
1542
|
-
this.addRecordReferenceIndexes(id, snapshot.record);
|
|
1543
|
-
}
|
|
1544
|
-
if (snapshot.mask === void 0) this.coverage.delete(id);
|
|
1545
|
-
else this.coverage.set(id, snapshot.mask);
|
|
1546
|
-
this.notify(id);
|
|
1547
|
-
});
|
|
1548
|
-
}
|
|
1549
|
-
addListIndexes(key, list) {
|
|
1550
|
-
const parsed = parseListKey(key);
|
|
1551
|
-
if (parsed) {
|
|
1552
|
-
const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
|
|
1553
|
-
let keys = this.listKeysByOwnerField.get(ownerFieldKey);
|
|
1554
|
-
if (!keys) {
|
|
1555
|
-
keys = /* @__PURE__ */ new Set();
|
|
1556
|
-
this.listKeysByOwnerField.set(ownerFieldKey, keys);
|
|
1557
|
-
}
|
|
1558
|
-
keys.add(key);
|
|
1559
|
-
}
|
|
1560
|
-
for (const id of this.getListReferencedEntityIds(list)) {
|
|
1561
|
-
let keys = this.listKeysByReferencedEntity.get(id);
|
|
1562
|
-
if (!keys) {
|
|
1563
|
-
keys = /* @__PURE__ */ new Set();
|
|
1564
|
-
this.listKeysByReferencedEntity.set(id, keys);
|
|
1565
|
-
}
|
|
1566
|
-
keys.add(key);
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
removeListIndexes(key, list) {
|
|
1570
|
-
const parsed = parseListKey(key);
|
|
1571
|
-
if (parsed) {
|
|
1572
|
-
const ownerFieldKey = getOwnerFieldKey(parsed.ownerId, parsed.field);
|
|
1573
|
-
const keys = this.listKeysByOwnerField.get(ownerFieldKey);
|
|
1574
|
-
if (keys) {
|
|
1575
|
-
keys.delete(key);
|
|
1576
|
-
if (keys.size === 0) this.listKeysByOwnerField.delete(ownerFieldKey);
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
for (const id of this.getListReferencedEntityIds(list)) {
|
|
1580
|
-
const keys = this.listKeysByReferencedEntity.get(id);
|
|
1581
|
-
if (!keys) continue;
|
|
1582
|
-
keys.delete(key);
|
|
1583
|
-
if (keys.size === 0) this.listKeysByReferencedEntity.delete(id);
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
getListReferencedEntityIds(list) {
|
|
1587
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1588
|
-
for (const id of list.ids) ids.add(id);
|
|
1589
|
-
for (const id of list.liveAfterIds ?? []) ids.add(id);
|
|
1590
|
-
for (const id of list.liveBeforeIds ?? []) ids.add(id);
|
|
1591
|
-
for (const id of list.pendingAfterIds ?? []) ids.add(id);
|
|
1592
|
-
for (const id of list.pendingBeforeIds ?? []) ids.add(id);
|
|
1593
|
-
return ids;
|
|
1594
|
-
}
|
|
1595
|
-
getRecordFieldReferences(value) {
|
|
1596
|
-
if (Array.isArray(value)) {
|
|
1597
|
-
let ids = null;
|
|
1598
|
-
for (const item of value) {
|
|
1599
|
-
if (!isNodeRef(item)) continue;
|
|
1600
|
-
if (!ids) ids = /* @__PURE__ */ new Set();
|
|
1601
|
-
ids.add(getNodeRefId(item));
|
|
1602
|
-
}
|
|
1603
|
-
return ids;
|
|
1604
|
-
}
|
|
1605
|
-
if (isNodeRef(value)) return /* @__PURE__ */ new Set([getNodeRefId(value)]);
|
|
1606
|
-
return null;
|
|
1607
|
-
}
|
|
1608
|
-
addRecordFieldReferenceIndex(id, field, value) {
|
|
1609
|
-
const targets = this.getRecordFieldReferences(value);
|
|
1610
|
-
if (!targets || targets.size === 0) return;
|
|
1611
|
-
let fields = this.recordReferenceFields.get(id);
|
|
1612
|
-
if (!fields) {
|
|
1613
|
-
fields = /* @__PURE__ */ new Map();
|
|
1614
|
-
this.recordReferenceFields.set(id, fields);
|
|
1615
|
-
}
|
|
1616
|
-
fields.set(field, targets);
|
|
1617
|
-
for (const targetId of targets) {
|
|
1618
|
-
let records = this.recordReferencesByTarget.get(targetId);
|
|
1619
|
-
if (!records) {
|
|
1620
|
-
records = /* @__PURE__ */ new Map();
|
|
1621
|
-
this.recordReferencesByTarget.set(targetId, records);
|
|
1622
|
-
}
|
|
1623
|
-
let targetFields = records.get(id);
|
|
1624
|
-
if (!targetFields) {
|
|
1625
|
-
targetFields = /* @__PURE__ */ new Set();
|
|
1626
|
-
records.set(id, targetFields);
|
|
1627
|
-
}
|
|
1628
|
-
targetFields.add(field);
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
removeRecordFieldReferenceIndex(id, field) {
|
|
1632
|
-
const fields = this.recordReferenceFields.get(id);
|
|
1633
|
-
const targets = fields?.get(field);
|
|
1634
|
-
if (!fields || !targets) return;
|
|
1635
|
-
for (const targetId of targets) {
|
|
1636
|
-
const records = this.recordReferencesByTarget.get(targetId);
|
|
1637
|
-
const targetFields = records?.get(id);
|
|
1638
|
-
if (!records || !targetFields) continue;
|
|
1639
|
-
targetFields.delete(field);
|
|
1640
|
-
if (targetFields.size === 0) records.delete(id);
|
|
1641
|
-
if (records.size === 0) this.recordReferencesByTarget.delete(targetId);
|
|
1642
|
-
}
|
|
1643
|
-
fields.delete(field);
|
|
1644
|
-
if (fields.size === 0) this.recordReferenceFields.delete(id);
|
|
1645
|
-
}
|
|
1646
|
-
addRecordReferenceIndexes(id, record) {
|
|
1647
|
-
for (const [field, value] of Object.entries(record)) this.addRecordFieldReferenceIndex(id, field, value);
|
|
1648
|
-
}
|
|
1649
|
-
removeRecordReferenceIndexes(id, record) {
|
|
1650
|
-
for (const field of Object.keys(record)) this.removeRecordFieldReferenceIndex(id, field);
|
|
1651
|
-
}
|
|
1652
|
-
updateRecordReferenceIndexes(id, previous, next, fields) {
|
|
1653
|
-
for (const field of fields) {
|
|
1654
|
-
if (previous) this.removeRecordFieldReferenceIndex(id, field);
|
|
1655
|
-
this.addRecordFieldReferenceIndex(id, field, next[field]);
|
|
1656
|
-
}
|
|
1657
|
-
}
|
|
1658
|
-
};
|
|
1659
|
-
//#endregion
|
|
1660
145
|
//#region src/request-descriptor.ts
|
|
1661
146
|
const serializeId = (value) => `${typeof value}:${String(value)}`;
|
|
1662
147
|
const getViewSignature = (view) => {
|
|
@@ -1984,6 +469,7 @@ var FateClient = class {
|
|
|
1984
469
|
this.liveConnectionInvalidations = /* @__PURE__ */ new Map();
|
|
1985
470
|
this.stableRefs = /* @__PURE__ */ new Map();
|
|
1986
471
|
this.requests = /* @__PURE__ */ new Map();
|
|
472
|
+
this.requestExecutionOptions = /* @__PURE__ */ new WeakMap();
|
|
1987
473
|
this.rootRequests = /* @__PURE__ */ new Map();
|
|
1988
474
|
this.stalledRequests = /* @__PURE__ */ new Set();
|
|
1989
475
|
this.gcPending = false;
|
|
@@ -1992,11 +478,13 @@ var FateClient = class {
|
|
|
1992
478
|
this.store = new Store((ids) => {
|
|
1993
479
|
for (const id of ids) this.viewDataCache.invalidate(id);
|
|
1994
480
|
this.runPendingGarbageCollection();
|
|
1995
|
-
});
|
|
481
|
+
}, (change) => this.persistenceRuntime?.changed(change));
|
|
1996
482
|
this.viewDataCache = new ViewDataCache(() => !this.store.isRebasing);
|
|
1997
483
|
this.liveConnectionInvalidationToken = 0;
|
|
484
|
+
this.persistenceDisposal = options.persistence ? new AbortController() : void 0;
|
|
1998
485
|
this.actions = Object.create(null);
|
|
1999
486
|
this.mutationMap = Object.create(null);
|
|
487
|
+
if (options.persistence) this.mutationEntities = new Map(Object.entries(options.mutations ?? {}).map(([key, definition]) => [key, definition.entity]));
|
|
2000
488
|
this.mutations = Object.create(null);
|
|
2001
489
|
this.onLiveError = options.onLiveError;
|
|
2002
490
|
this.operationLifetime = new OperationLifetime(options.gcReleaseBufferSize ?? 10);
|
|
@@ -2020,6 +508,8 @@ var FateClient = class {
|
|
|
2020
508
|
setNestedValue(this.actions, key, async (_previousState, data) => data === "reset" ? null : await this.mutationMap[key](data));
|
|
2021
509
|
}
|
|
2022
510
|
this.initializeParentLists();
|
|
511
|
+
this.persistenceRuntime = options.persistence?.attach(this);
|
|
512
|
+
this.persistence = this.persistenceRuntime;
|
|
2023
513
|
}
|
|
2024
514
|
initializeParentLists() {
|
|
2025
515
|
for (const config of this.types.values()) {
|
|
@@ -2098,6 +588,60 @@ var FateClient = class {
|
|
|
2098
588
|
this.viewDataCache.clear();
|
|
2099
589
|
notify();
|
|
2100
590
|
}
|
|
591
|
+
/** @internal */
|
|
592
|
+
exportPersistenceState() {
|
|
593
|
+
return {
|
|
594
|
+
data: encodeHydrationValue({
|
|
595
|
+
rootLists: [...this.rootLists].map(([type, keys]) => [type, [...keys]]),
|
|
596
|
+
rootRequests: [...this.rootRequests],
|
|
597
|
+
store: this.store.dehydrateConfirmed()
|
|
598
|
+
}, this.hydrationLimits),
|
|
599
|
+
scope: this.hydrationScope,
|
|
600
|
+
version: 1
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
/** @internal */
|
|
604
|
+
retainPersistenceRequest(request) {
|
|
605
|
+
return this.operationLifetime.retain(request, () => this.scheduleGarbageCollection());
|
|
606
|
+
}
|
|
607
|
+
/** @internal */
|
|
608
|
+
getPersistenceScope() {
|
|
609
|
+
return this.hydrationScope;
|
|
610
|
+
}
|
|
611
|
+
/** @internal */
|
|
612
|
+
getPersistenceRootLists(type) {
|
|
613
|
+
return this.rootLists.get(type) ?? /* @__PURE__ */ new Set();
|
|
614
|
+
}
|
|
615
|
+
/** @internal */
|
|
616
|
+
getPersistenceQuery(key) {
|
|
617
|
+
return this.rootRequests.get(key);
|
|
618
|
+
}
|
|
619
|
+
/** @internal Merge a small disk read beneath optimistic layers. */
|
|
620
|
+
restorePersistenceData(state) {
|
|
621
|
+
this.store.update(() => {
|
|
622
|
+
const coverage = new Map(state.store.coverage);
|
|
623
|
+
for (const [id, record] of state.store.records) {
|
|
624
|
+
const existing = this.store.read(id);
|
|
625
|
+
const paths = coverage.get(id) ?? [];
|
|
626
|
+
this.store.merge(id, existing ? mergePreservingExisting(record, existing) : record, paths);
|
|
627
|
+
}
|
|
628
|
+
for (const [key, list] of state.store.lists) if (!this.store.getListState(key)) this.store.setList(key, list);
|
|
629
|
+
for (const [key, id] of state.rootRequests) if (!this.rootRequests.has(key)) this.rootRequests.set(key, id);
|
|
630
|
+
for (const [type, keys] of state.rootLists) for (const key of keys) this.registerRootList(type, key);
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
/** @internal */
|
|
634
|
+
getRetainedRequests() {
|
|
635
|
+
return this.operationLifetime.getDescriptors();
|
|
636
|
+
}
|
|
637
|
+
/** @internal */
|
|
638
|
+
restoreRetainedRequests(requests) {
|
|
639
|
+
this.operationLifetime.restore(requests);
|
|
640
|
+
}
|
|
641
|
+
/** @internal */
|
|
642
|
+
hasMutation(key, entity) {
|
|
643
|
+
return this.mutationEntities?.get(key) === entity && this.types.has(entity);
|
|
644
|
+
}
|
|
2101
645
|
registerRootList(type, key) {
|
|
2102
646
|
let lists = this.rootLists.get(type);
|
|
2103
647
|
if (!lists) {
|
|
@@ -2156,8 +700,33 @@ var FateClient = class {
|
|
|
2156
700
|
if (!descriptor || descriptor === "scalar" || !("listOf" in descriptor)) throw new Error(`fate: Could not find node type for '${type}.${field}'.`);
|
|
2157
701
|
return descriptor.listOf;
|
|
2158
702
|
}
|
|
703
|
+
/** @internal Validates local wiring without requiring network connectivity. */
|
|
704
|
+
validatePersistence() {
|
|
705
|
+
if (this.mutationEntities?.size) this.requireDurableMutationTransport();
|
|
706
|
+
}
|
|
707
|
+
/** @internal */
|
|
708
|
+
persistMutation(command) {
|
|
709
|
+
if (!this.persistenceRuntime) throw new Error("fate: Persistence is not configured.");
|
|
710
|
+
return this.persistenceRuntime.mutate(command);
|
|
711
|
+
}
|
|
712
|
+
/** @internal */
|
|
713
|
+
assertPersistenceActive() {
|
|
714
|
+
if (this.persistenceDisposal?.signal.aborted || this.persistence?.getSnapshot().status === "disposed") throw new Error("fate: Persistence was disposed.");
|
|
715
|
+
}
|
|
716
|
+
/** @internal */
|
|
717
|
+
disposePersistence() {
|
|
718
|
+
this.persistenceDisposal?.abort();
|
|
719
|
+
for (const { unsubscribe } of this.liveSubscriptions.values()) try {
|
|
720
|
+
unsubscribe();
|
|
721
|
+
} catch {}
|
|
722
|
+
this.liveSubscriptions.clear();
|
|
723
|
+
}
|
|
724
|
+
requireDurableMutationTransport() {
|
|
725
|
+
if (!this.transport.mutateDurably) throw new FateRequestError("BAD_REQUEST", "fate: Persisted mutations require transport.mutateDurably connected to a server idempotency handler. Configure that adapter, or use persist: false for immediate calls. Cache persistence remains available.");
|
|
726
|
+
}
|
|
2159
727
|
async executeMutation(key, input, select, options = {}) {
|
|
2160
|
-
if (
|
|
728
|
+
if (options.identity) this.requireDurableMutationTransport();
|
|
729
|
+
else if (!this.transport.mutate) throw new Error(`fate: transport does not support mutations. Please provide a 'mutate' implementation in your transport.`);
|
|
2161
730
|
const baseRecord = input && typeof input === "object" ? input : void 0;
|
|
2162
731
|
const inputArgs = baseRecord && typeof baseRecord.args === "object" ? baseRecord.args : void 0;
|
|
2163
732
|
const argsPayload = combineArgsPayload(options.plan ? resolvedArgsFromPlan(options.plan) : void 0, combineArgsPayload(inputArgs, options.args));
|
|
@@ -2165,7 +734,7 @@ var FateClient = class {
|
|
|
2165
734
|
...baseRecord,
|
|
2166
735
|
args: argsPayload
|
|
2167
736
|
} : argsPayload ? { args: argsPayload } : input;
|
|
2168
|
-
return await this.trackPendingRequest(() => this.transport.mutate(key, requestInput, select));
|
|
737
|
+
return await this.trackPendingRequest(() => options.identity ? this.transport.mutateDurably(key, requestInput, select, options.identity) : this.transport.mutate(key, requestInput, select));
|
|
2169
738
|
}
|
|
2170
739
|
write(type, data, select, plan, insert) {
|
|
2171
740
|
return this.writeEntity(type, data, select, plan, null, insert);
|
|
@@ -2201,6 +770,18 @@ var FateClient = class {
|
|
|
2201
770
|
return this.stableRefWithViewNames(type, id, getRootViewNames(rootView));
|
|
2202
771
|
}
|
|
2203
772
|
readView(view, ref) {
|
|
773
|
+
if (this.persistence && this.persistence.getSnapshot().status !== "ready") {
|
|
774
|
+
const key = `restore:${ref.__typename}:${ref.id}:${[...getViewNames(view)].sort().join(",")}`;
|
|
775
|
+
let pending = this.pending.get(key);
|
|
776
|
+
if (!pending) {
|
|
777
|
+
pending = this.persistence.ready.then(() => {
|
|
778
|
+
if (this.persistence?.getSnapshot().status === "disposed") throw new Error("fate: Persistence was disposed.");
|
|
779
|
+
return this.readView(view, ref);
|
|
780
|
+
}).finally(() => this.pending.delete(key));
|
|
781
|
+
this.pending.set(key, pending);
|
|
782
|
+
}
|
|
783
|
+
return pending;
|
|
784
|
+
}
|
|
2204
785
|
const id = ref.id;
|
|
2205
786
|
const type = ref.__typename;
|
|
2206
787
|
if (id == null) {
|
|
@@ -2253,7 +834,37 @@ var FateClient = class {
|
|
|
2253
834
|
const pendingPromise = this.pending.get(key) || null;
|
|
2254
835
|
if (pendingPromise) return pendingPromise;
|
|
2255
836
|
const promise = this.trackPendingRequest(async () => {
|
|
2256
|
-
|
|
837
|
+
const persistence = this.persistenceRuntime;
|
|
838
|
+
if (persistence) {
|
|
839
|
+
const persistedRequest = {
|
|
840
|
+
items: [{
|
|
841
|
+
ids: [id],
|
|
842
|
+
kind: "node",
|
|
843
|
+
name: type,
|
|
844
|
+
plan,
|
|
845
|
+
refViewNames: /* @__PURE__ */ new Set(),
|
|
846
|
+
type,
|
|
847
|
+
viewSignature: ""
|
|
848
|
+
}],
|
|
849
|
+
key: `view:${key}`
|
|
850
|
+
};
|
|
851
|
+
await persistence.restoreRequest(persistedRequest);
|
|
852
|
+
const remaining = this.store.missingForSelection(entityId, selectedPaths);
|
|
853
|
+
if (remaining.size) {
|
|
854
|
+
await this.fetchByIdAndNormalize(type, [id], remaining, plan);
|
|
855
|
+
persistence.fetched({
|
|
856
|
+
...persistedRequest,
|
|
857
|
+
items: persistedRequest.items.map((item) => ({
|
|
858
|
+
...item,
|
|
859
|
+
plan: {
|
|
860
|
+
...item.plan,
|
|
861
|
+
paths: remaining
|
|
862
|
+
}
|
|
863
|
+
})),
|
|
864
|
+
key: `${persistedRequest.key}:${JSON.stringify([...remaining].sort())}`
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
} else await this.fetchByIdAndNormalize(type, [id], missing, plan);
|
|
2257
868
|
try {
|
|
2258
869
|
if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
|
|
2259
870
|
this.stalledRequests.add(key);
|
|
@@ -2334,6 +945,7 @@ var FateClient = class {
|
|
|
2334
945
|
if (!this.transport.subscribeConnection) throw new Error(`fate: transport does not support live list views. Please provide a 'subscribeConnection' implementation in your transport.`);
|
|
2335
946
|
}
|
|
2336
947
|
subscribeLiveView(view, ref) {
|
|
948
|
+
this.assertPersistenceActive();
|
|
2337
949
|
this.assertLiveViewSupport();
|
|
2338
950
|
const id = ref.id;
|
|
2339
951
|
const type = ref.__typename;
|
|
@@ -2361,19 +973,26 @@ var FateClient = class {
|
|
|
2361
973
|
try {
|
|
2362
974
|
unsubscribe = this.transport.subscribeById(type, id, plan.paths, args, {
|
|
2363
975
|
onData: (record, liveSelect) => {
|
|
2364
|
-
if (!record || typeof record !== "object") return;
|
|
976
|
+
if (this.persistenceDisposal?.signal.aborted || !record || typeof record !== "object") return;
|
|
2365
977
|
const select = new Set(liveSelect ?? plan.paths);
|
|
2366
978
|
this.write(type, record, select, plan, "none");
|
|
2367
979
|
},
|
|
2368
980
|
onDelete: (deletedId) => {
|
|
981
|
+
if (this.persistenceDisposal?.signal.aborted) return;
|
|
2369
982
|
this.deleteRecord(type, deletedId ?? id);
|
|
2370
983
|
},
|
|
2371
|
-
onError: (error) =>
|
|
984
|
+
onError: (error) => {
|
|
985
|
+
if (!this.persistenceDisposal?.signal.aborted) this.reportLiveError(error);
|
|
986
|
+
}
|
|
2372
987
|
});
|
|
2373
988
|
} catch (error) {
|
|
2374
989
|
this.reportLiveError(error);
|
|
2375
990
|
return emptyDispose;
|
|
2376
991
|
}
|
|
992
|
+
if (this.persistenceDisposal?.signal.aborted) {
|
|
993
|
+
unsubscribe();
|
|
994
|
+
return emptyDispose;
|
|
995
|
+
}
|
|
2377
996
|
this.liveSubscriptions.set(key, {
|
|
2378
997
|
count: 1,
|
|
2379
998
|
unsubscribe
|
|
@@ -2383,6 +1002,7 @@ var FateClient = class {
|
|
|
2383
1002
|
};
|
|
2384
1003
|
}
|
|
2385
1004
|
subscribeLiveListView(view, connection) {
|
|
1005
|
+
this.assertPersistenceActive();
|
|
2386
1006
|
this.assertLiveConnectionSupport();
|
|
2387
1007
|
const plan = getSelectionPlan(view, null);
|
|
2388
1008
|
const connectionArgs = filterConnectionArgs(connection.args);
|
|
@@ -2399,8 +1019,11 @@ var FateClient = class {
|
|
|
2399
1019
|
let unsubscribe;
|
|
2400
1020
|
try {
|
|
2401
1021
|
unsubscribe = this.transport.subscribeConnection(connection.procedure, connection.type, connectionArgs, plan.paths, selectionArgs, {
|
|
2402
|
-
onError: (error) =>
|
|
1022
|
+
onError: (error) => {
|
|
1023
|
+
if (!this.persistenceDisposal?.signal.aborted) this.reportLiveError(error);
|
|
1024
|
+
},
|
|
2403
1025
|
onEvent: (event) => {
|
|
1026
|
+
if (this.persistenceDisposal?.signal.aborted) return;
|
|
2404
1027
|
this.applyLiveConnectionEvent(view, connection, plan, event);
|
|
2405
1028
|
}
|
|
2406
1029
|
});
|
|
@@ -2408,6 +1031,10 @@ var FateClient = class {
|
|
|
2408
1031
|
this.reportLiveError(error);
|
|
2409
1032
|
return emptyDispose;
|
|
2410
1033
|
}
|
|
1034
|
+
if (this.persistenceDisposal?.signal.aborted) {
|
|
1035
|
+
unsubscribe();
|
|
1036
|
+
return emptyDispose;
|
|
1037
|
+
}
|
|
2411
1038
|
this.liveSubscriptions.set(key, {
|
|
2412
1039
|
count: 1,
|
|
2413
1040
|
unsubscribe
|
|
@@ -2723,6 +1350,7 @@ var FateClient = class {
|
|
|
2723
1350
|
});
|
|
2724
1351
|
const { argsPayload, plan } = resolveSelectionPlan(view, requestArgs);
|
|
2725
1352
|
const { items, pagination } = await this.transport.fetchList(connection.field, plan.paths, argsPayload);
|
|
1353
|
+
this.assertPersistenceActive();
|
|
2726
1354
|
if (!items) return this.store.getListState(connection.key);
|
|
2727
1355
|
this.store.update(() => {
|
|
2728
1356
|
const incomingIds = [];
|
|
@@ -2754,6 +1382,7 @@ var FateClient = class {
|
|
|
2754
1382
|
const parentSelection = /* @__PURE__ */ new Set();
|
|
2755
1383
|
for (const path of nodeSelection) parentSelection.add(`${connection.field}.${path}`);
|
|
2756
1384
|
const [parentRecord] = await this.transport.fetchById(owner.type, [owner.id], parentSelection, scopedArgsPayload);
|
|
1385
|
+
this.assertPersistenceActive();
|
|
2757
1386
|
if (!parentRecord || typeof parentRecord !== "object") return this.store.getListState(connection.key);
|
|
2758
1387
|
const list = parentRecord[connection.field];
|
|
2759
1388
|
const connectionPayload = Array.isArray(list) ? {
|
|
@@ -2796,7 +1425,7 @@ var FateClient = class {
|
|
|
2796
1425
|
}
|
|
2797
1426
|
request(request, options) {
|
|
2798
1427
|
const mode = options?.mode ?? "cache-first";
|
|
2799
|
-
return this.requestWithDescriptor(this.createRequestDescriptor(request), mode, { revalidateExisting: true });
|
|
1428
|
+
return this.requestWithDescriptor(this.createRequestDescriptor(request), mode, { revalidateExisting: true }, options);
|
|
2800
1429
|
}
|
|
2801
1430
|
requestForRender(request, options) {
|
|
2802
1431
|
return this.prepareRequestForRender(request, options).promise;
|
|
@@ -2805,16 +1434,24 @@ var FateClient = class {
|
|
|
2805
1434
|
const mode = options?.mode ?? "cache-first";
|
|
2806
1435
|
const descriptor = this.createRequestDescriptor(request);
|
|
2807
1436
|
return {
|
|
2808
|
-
promise: this.requestWithDescriptor(descriptor, mode, { revalidateExisting: false }),
|
|
1437
|
+
promise: this.requestWithDescriptor(descriptor, mode, { revalidateExisting: false }, options),
|
|
2809
1438
|
requestKey: descriptor.key
|
|
2810
1439
|
};
|
|
2811
1440
|
}
|
|
2812
|
-
requestWithDescriptor(descriptor, mode, { revalidateExisting }) {
|
|
1441
|
+
requestWithDescriptor(descriptor, mode, { revalidateExisting }, options) {
|
|
1442
|
+
this.assertPersistenceActive();
|
|
1443
|
+
const persistence = this.persistenceRuntime;
|
|
1444
|
+
if (persistence && options?.persist && (!Number.isSafeInteger(options.persist.maxAge) || options.persist.maxAge < 0 || options.persist.maxAge > Number.MAX_SAFE_INTEGER - Date.now())) throw new Error("fate: Persistence maxAge must be a non-negative finite duration.");
|
|
2813
1445
|
const requestKey = descriptor.key;
|
|
2814
1446
|
const existingRequest = this.requests.get(requestKey)?.get(mode);
|
|
2815
1447
|
if (existingRequest) {
|
|
1448
|
+
if (persistence && options?.persist) {
|
|
1449
|
+
const executionOptions = this.requestExecutionOptions.get(existingRequest.descriptor);
|
|
1450
|
+
if (executionOptions) executionOptions.persist = { ...options.persist };
|
|
1451
|
+
}
|
|
1452
|
+
if (persistence && options?.persist && existingRequest.status === "fulfilled") persistence.used(existingRequest.descriptor, options.persist);
|
|
2816
1453
|
const isFulfilledCacheFirstWithMissingData = mode === "cache-first" && existingRequest.status === "fulfilled" && !this.hasRequestData(existingRequest.descriptor);
|
|
2817
|
-
if (existingRequest.status !== "pending" && (isFulfilledCacheFirstWithMissingData || revalidateExisting && (mode === "network-only" || mode === "stale-while-revalidate" || mode === "cache-first" && existingRequest.status === "rejected"))) this.executeRequestHandle(existingRequest, mode);
|
|
1454
|
+
if (existingRequest.status !== "pending" && (isFulfilledCacheFirstWithMissingData || revalidateExisting && (mode === "network-only" || mode === "stale-while-revalidate" || mode === "cache-first" && existingRequest.status === "rejected"))) this.executeRequestHandle(existingRequest, mode, options);
|
|
2818
1455
|
return existingRequest;
|
|
2819
1456
|
}
|
|
2820
1457
|
const handle = new FateRequestPromise(descriptor, () => this.getRequestResultFromDescriptor(descriptor));
|
|
@@ -2824,7 +1461,7 @@ var FateClient = class {
|
|
|
2824
1461
|
this.requests.set(requestKey, requests);
|
|
2825
1462
|
}
|
|
2826
1463
|
requests.set(mode, handle);
|
|
2827
|
-
this.executeRequestHandle(handle, mode);
|
|
1464
|
+
this.executeRequestHandle(handle, mode, options);
|
|
2828
1465
|
return handle;
|
|
2829
1466
|
}
|
|
2830
1467
|
releaseRequest(request, mode) {
|
|
@@ -2869,7 +1506,7 @@ var FateClient = class {
|
|
|
2869
1506
|
* memory-management flows.
|
|
2870
1507
|
*/
|
|
2871
1508
|
gc() {
|
|
2872
|
-
if (this.hasActiveOptimisticUpdates()) {
|
|
1509
|
+
if (this.persistence ? this.store.hasTransientOptimisticUpdates : this.hasActiveOptimisticUpdates()) {
|
|
2873
1510
|
this.gcPending = true;
|
|
2874
1511
|
return;
|
|
2875
1512
|
}
|
|
@@ -2890,6 +1527,10 @@ var FateClient = class {
|
|
|
2890
1527
|
for (const id of listState.ids) markRecord(id);
|
|
2891
1528
|
for (const id of listState.pendingAfterIds ?? []) markRecord(id);
|
|
2892
1529
|
};
|
|
1530
|
+
const optimisticRoots = this.store.getOptimisticRoots();
|
|
1531
|
+
optimisticRoots.baseRecords.forEach((record) => this.markRecordReferences(record, markRecord));
|
|
1532
|
+
optimisticRoots.records.forEach(markRecord);
|
|
1533
|
+
optimisticRoots.lists.forEach(markList);
|
|
2893
1534
|
for (const descriptor of this.operationLifetime.getDescriptors()) for (const item of descriptor.items) {
|
|
2894
1535
|
if (item.kind === "node" || item.kind === "nodes") {
|
|
2895
1536
|
for (const raw of item.ids) markRecord(toEntityId(item.type, raw));
|
|
@@ -2928,15 +1569,19 @@ var FateClient = class {
|
|
|
2928
1569
|
if (keys.size === 0) this.rootLists.delete(type);
|
|
2929
1570
|
}
|
|
2930
1571
|
}
|
|
2931
|
-
executeRequestHandle(handle, mode) {
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
1572
|
+
executeRequestHandle(handle, mode, options) {
|
|
1573
|
+
const persistence = this.persistenceRuntime;
|
|
1574
|
+
const executionOptions = {
|
|
1575
|
+
fetchAll: mode !== "cache-first",
|
|
1576
|
+
persist: options?.persist ? { ...options.persist } : void 0
|
|
1577
|
+
};
|
|
1578
|
+
if (persistence) this.requestExecutionOptions.set(handle.descriptor, executionOptions);
|
|
1579
|
+
const execute = () => {
|
|
1580
|
+
this.assertPersistenceActive();
|
|
1581
|
+
if (mode === "stale-while-revalidate") return this.handleStoreAndNetworkRequest(handle.descriptor, executionOptions);
|
|
1582
|
+
return this.executeRequest(handle.descriptor, executionOptions).then(() => this.getRequestResultFromDescriptor(handle.descriptor));
|
|
1583
|
+
};
|
|
1584
|
+
handle.start(persistence ? this.withPersistenceLifecycle(() => persistence.ready.then(() => mode === "network-only" ? void 0 : persistence.restoreRequest(handle.descriptor)).then(execute)) : execute());
|
|
2940
1585
|
}
|
|
2941
1586
|
markRecordReferences(record, markRecord) {
|
|
2942
1587
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2962,24 +1607,47 @@ var FateClient = class {
|
|
|
2962
1607
|
hasPendingRequests() {
|
|
2963
1608
|
return this.pendingNetworkRequests > 0;
|
|
2964
1609
|
}
|
|
2965
|
-
|
|
2966
|
-
|
|
1610
|
+
/** @internal */
|
|
1611
|
+
async withPersistenceLifecycle(request) {
|
|
1612
|
+
this.assertPersistenceActive();
|
|
1613
|
+
const signal = this.persistenceDisposal?.signal;
|
|
1614
|
+
let onDispose;
|
|
2967
1615
|
try {
|
|
2968
|
-
|
|
1616
|
+
const execution = request();
|
|
1617
|
+
const result = signal ? await Promise.race([execution, new Promise((_, reject) => {
|
|
1618
|
+
onDispose = () => reject(/* @__PURE__ */ new Error("fate: Persistence was disposed."));
|
|
1619
|
+
signal.addEventListener("abort", onDispose, { once: true });
|
|
1620
|
+
if (signal.aborted) onDispose();
|
|
1621
|
+
})]) : await execution;
|
|
1622
|
+
this.assertPersistenceActive();
|
|
1623
|
+
return result;
|
|
2969
1624
|
} finally {
|
|
2970
|
-
|
|
1625
|
+
if (onDispose) signal?.removeEventListener("abort", onDispose);
|
|
2971
1626
|
}
|
|
2972
1627
|
}
|
|
1628
|
+
async trackPendingRequest(request) {
|
|
1629
|
+
return this.withPersistenceLifecycle(async () => {
|
|
1630
|
+
if (this.persistence) await this.persistence.ready;
|
|
1631
|
+
this.assertPersistenceActive();
|
|
1632
|
+
this.pendingNetworkRequests += 1;
|
|
1633
|
+
try {
|
|
1634
|
+
return await this.withPersistenceLifecycle(request);
|
|
1635
|
+
} finally {
|
|
1636
|
+
this.pendingNetworkRequests -= 1;
|
|
1637
|
+
this.persistenceRuntime?.changed();
|
|
1638
|
+
}
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
2973
1641
|
runPendingGarbageCollection() {
|
|
2974
1642
|
if (this.gcPending && !this.hasActiveOptimisticUpdates()) this.gc();
|
|
2975
1643
|
}
|
|
2976
|
-
async handleStoreAndNetworkRequest(request) {
|
|
1644
|
+
async handleStoreAndNetworkRequest(request, options) {
|
|
2977
1645
|
if (!this.hasRequestData(request)) {
|
|
2978
|
-
await this.executeRequest(request,
|
|
1646
|
+
await this.executeRequest(request, options);
|
|
2979
1647
|
return this.getRequestResultFromDescriptor(request);
|
|
2980
1648
|
}
|
|
2981
1649
|
const result = this.getRequestResultFromDescriptor(request);
|
|
2982
|
-
this.executeRequest(request,
|
|
1650
|
+
this.executeRequest(request, options).catch(() => {});
|
|
2983
1651
|
return result;
|
|
2984
1652
|
}
|
|
2985
1653
|
getRootType(name) {
|
|
@@ -3005,6 +1673,8 @@ var FateClient = class {
|
|
|
3005
1673
|
const fetchAll = options.fetchAll ?? false;
|
|
3006
1674
|
const groups = /* @__PURE__ */ new Map();
|
|
3007
1675
|
const promises = [];
|
|
1676
|
+
const fetchedItems = [];
|
|
1677
|
+
let fetchedAll = true;
|
|
3008
1678
|
for (const item of request.items) if (item.kind === "node" || item.kind === "nodes") {
|
|
3009
1679
|
const fields = item.plan.paths;
|
|
3010
1680
|
const fieldsSignature = [...fields].slice().sort().join(",");
|
|
@@ -3020,23 +1690,47 @@ var FateClient = class {
|
|
|
3020
1690
|
};
|
|
3021
1691
|
groups.set(groupKey, group);
|
|
3022
1692
|
}
|
|
1693
|
+
const fetchedIds = [];
|
|
3023
1694
|
for (const raw of item.ids) {
|
|
3024
1695
|
const entityId = toEntityId(item.type, raw);
|
|
3025
1696
|
const missing = this.store.missingForSelection(entityId, fields);
|
|
3026
|
-
if (fetchAll || missing.size > 0)
|
|
1697
|
+
if (fetchAll || missing.size > 0) {
|
|
1698
|
+
group.ids.push(raw);
|
|
1699
|
+
fetchedIds.push(raw);
|
|
1700
|
+
}
|
|
3027
1701
|
}
|
|
1702
|
+
if (fetchedIds.length) fetchedItems.push({
|
|
1703
|
+
...item,
|
|
1704
|
+
ids: fetchedIds
|
|
1705
|
+
});
|
|
1706
|
+
fetchedAll &&= fetchedIds.length === item.ids.length;
|
|
3028
1707
|
} else {
|
|
3029
1708
|
if (item.kind === "query") {
|
|
3030
1709
|
const hasResult = this.rootRequests.has(item.queryKey);
|
|
3031
1710
|
const entityId = this.rootRequests.get(item.queryKey);
|
|
3032
1711
|
const missing = hasResult && entityId ? this.store.missingForSelection(entityId, item.plan.paths) : item.plan.paths;
|
|
3033
|
-
if (fetchAll || !hasResult || entityId && missing.size > 0)
|
|
1712
|
+
if (fetchAll || !hasResult || entityId && missing.size > 0) {
|
|
1713
|
+
promises.push(this.fetchQueryAndNormalize(item));
|
|
1714
|
+
fetchedItems.push(item);
|
|
1715
|
+
} else fetchedAll = false;
|
|
3034
1716
|
}
|
|
3035
1717
|
if (item.kind !== "list") continue;
|
|
3036
|
-
if (!fetchAll && !getPaginationArgsInfo(item.argsPayload).hasCursorArg && this.hasRootListData(item))
|
|
1718
|
+
if (!fetchAll && !getPaginationArgsInfo(item.argsPayload).hasCursorArg && this.hasRootListData(item)) {
|
|
1719
|
+
fetchedAll = false;
|
|
1720
|
+
continue;
|
|
1721
|
+
}
|
|
3037
1722
|
promises.push(this.fetchListAndNormalize(item));
|
|
1723
|
+
fetchedItems.push(item);
|
|
3038
1724
|
}
|
|
3039
1725
|
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())]);
|
|
1726
|
+
const persistence = this.persistenceRuntime;
|
|
1727
|
+
if (persistence) {
|
|
1728
|
+
if (fetchedItems.length) persistence.fetched(fetchedAll ? request : {
|
|
1729
|
+
items: fetchedItems,
|
|
1730
|
+
key: `partial:${JSON.stringify([request.key, fetchedItems.map((item) => [item.name, "ids" in item ? item.ids : null])])}`
|
|
1731
|
+
}, options.persist);
|
|
1732
|
+
if (!fetchedItems.length || !fetchedAll) persistence.used(request, options.persist);
|
|
1733
|
+
}
|
|
3040
1734
|
}
|
|
3041
1735
|
hasRequestData(request) {
|
|
3042
1736
|
for (const item of request.items) {
|
|
@@ -3124,6 +1818,7 @@ var FateClient = class {
|
|
|
3124
1818
|
const resolvedArgs = resolvedArgsFromPlan(plan);
|
|
3125
1819
|
await this.trackPendingRequest(async () => {
|
|
3126
1820
|
const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
|
|
1821
|
+
this.assertPersistenceActive();
|
|
3127
1822
|
for (const record of records) this.writeEntity(type, record, select, plan, prefix);
|
|
3128
1823
|
});
|
|
3129
1824
|
}
|
|
@@ -3131,6 +1826,7 @@ var FateClient = class {
|
|
|
3131
1826
|
if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${item.name}'.`);
|
|
3132
1827
|
await this.trackPendingRequest(async () => {
|
|
3133
1828
|
const record = await this.transport.fetchQuery(item.name, item.plan.paths, item.argsPayload);
|
|
1829
|
+
this.assertPersistenceActive();
|
|
3134
1830
|
if (!record || typeof record !== "object") {
|
|
3135
1831
|
this.rootRequests.set(item.queryKey, null);
|
|
3136
1832
|
return;
|
|
@@ -3143,6 +1839,7 @@ var FateClient = class {
|
|
|
3143
1839
|
if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${item.name}'.`);
|
|
3144
1840
|
await this.trackPendingRequest(async () => {
|
|
3145
1841
|
const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
|
|
1842
|
+
this.assertPersistenceActive();
|
|
3146
1843
|
this.store.update(() => {
|
|
3147
1844
|
const ids = [];
|
|
3148
1845
|
const cursors = [];
|
|
@@ -3185,7 +1882,8 @@ var FateClient = class {
|
|
|
3185
1882
|
const childType = relationDescriptor.type;
|
|
3186
1883
|
const childConfig = this.types.get(childType);
|
|
3187
1884
|
if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
|
|
3188
|
-
|
|
1885
|
+
const childId = toEntityId(childType, childConfig.getId(value));
|
|
1886
|
+
result[key] = createNodeRef(childId);
|
|
3189
1887
|
this.writeEntity(childType, value, childPaths, plan, fieldPath);
|
|
3190
1888
|
}
|
|
3191
1889
|
} else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
|
|
@@ -3528,7 +2226,7 @@ function createClient(options) {
|
|
|
3528
2226
|
* Builds a `Transport` backed by a tRPC client using the configured resolvers
|
|
3529
2227
|
* for by-id queries, lists, and mutations.
|
|
3530
2228
|
*/
|
|
3531
|
-
function createTRPCTransport({ byId, client, lists, mutations, queries }) {
|
|
2229
|
+
function createTRPCTransport({ byId, client, lists, mutateDurably, mutations, queries }) {
|
|
3532
2230
|
const transport = {
|
|
3533
2231
|
async fetchById(type, ids, select, args) {
|
|
3534
2232
|
const resolver = byId[type];
|
|
@@ -3556,7 +2254,8 @@ function createTRPCTransport({ byId, client, lists, mutations, queries }) {
|
|
|
3556
2254
|
args,
|
|
3557
2255
|
select: [...select]
|
|
3558
2256
|
});
|
|
3559
|
-
}
|
|
2257
|
+
},
|
|
2258
|
+
mutateDurably
|
|
3560
2259
|
};
|
|
3561
2260
|
transport.mutate = async (procedure, input, select) => {
|
|
3562
2261
|
const resolver = mutations?.[procedure];
|
|
@@ -3634,7 +2333,7 @@ const responseError = async (response) => {
|
|
|
3634
2333
|
return new FateRequestError(errorCodeFromStatus(response.status), message, { status: response.status });
|
|
3635
2334
|
};
|
|
3636
2335
|
const assertProtocolResponse = (value) => {
|
|
3637
|
-
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.");
|
|
2336
|
+
if (!isRecord(value) || value.version !== 1 && value.version !== 2 || !Array.isArray(value.results) || value.results.some((entry) => !isRecord(entry) || typeof entry.id !== "string")) throw new Error("fate(http): Invalid protocol response.");
|
|
3638
2337
|
return value;
|
|
3639
2338
|
};
|
|
3640
2339
|
const resultValue = (result) => {
|
|
@@ -3672,7 +2371,7 @@ function createHTTPTransport({ eventSource, fetch: fetchImpl = defaultFetch, hea
|
|
|
3672
2371
|
const response = await fetchImpl(endpoint, {
|
|
3673
2372
|
body: JSON.stringify({
|
|
3674
2373
|
operations: batch.map((entry) => entry.operation),
|
|
3675
|
-
version: 1
|
|
2374
|
+
version: batch.some((entry) => entry.operation.mutation) ? 2 : 1
|
|
3676
2375
|
}),
|
|
3677
2376
|
headers: await requestHeaders({ "content-type": "application/json" }, headers),
|
|
3678
2377
|
method: "POST"
|
|
@@ -3729,6 +2428,15 @@ function createHTTPTransport({ eventSource, fetch: fetchImpl = defaultFetch, hea
|
|
|
3729
2428
|
name,
|
|
3730
2429
|
select: [...select]
|
|
3731
2430
|
});
|
|
2431
|
+
},
|
|
2432
|
+
mutateDurably(name, input, select, identity) {
|
|
2433
|
+
return enqueue({
|
|
2434
|
+
input,
|
|
2435
|
+
kind: "mutation",
|
|
2436
|
+
mutation: identity,
|
|
2437
|
+
name,
|
|
2438
|
+
select: [...select]
|
|
2439
|
+
});
|
|
3732
2440
|
}
|
|
3733
2441
|
};
|
|
3734
2442
|
if (liveEnabled) {
|