@nkzw/fate 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +326 -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 +4 -1
- package/docs/api/interfaces/FateThenable.md +3 -3
- package/docs/api/interfaces/Persistence.md +23 -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/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/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 +323 -0
- package/lib/{liveTopics-uVP0LG-Q.mjs → args-CRLIRl_v.mjs} +1 -59
- package/lib/{executor-BRudKWDY.d.mts → executor-DoiTBiVv.d.mts} +1 -1
- package/lib/{graphqlTransport-BDR1pnUQ.d.mts → graphqlTransport-DGLYyRxL.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-CqA-BFpn.d.mts +15 -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 +265 -1567
- package/lib/{list-Bb1SeABn.d.mts → list-BRT1VtiT.d.mts} +1 -1
- package/lib/list.d.mts +1 -1
- package/lib/liveTopics-DuFkDZQm.mjs +17 -0
- package/lib/persistence.d.mts +38 -0
- package/lib/persistence.mjs +1529 -0
- package/lib/protocol-DS5sf6sd.mjs +44 -0
- package/lib/{record-BTUXkHQU.d.mts → record-DITM4zb_.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-CeatPJeK.mjs +1350 -0
- package/lib/{transport-Bo1Jgo9R.d.mts → transport-DqjhjLfC.d.mts} +594 -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-CeatPJeK.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
|
+
}, (kind, key, paths, previousList) => this.persistence?.changed(kind, key, paths, previousList));
|
|
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,7 @@ 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.persistence = options.persistence?.attach(this);
|
|
2023
512
|
}
|
|
2024
513
|
initializeParentLists() {
|
|
2025
514
|
for (const config of this.types.values()) {
|
|
@@ -2098,6 +587,60 @@ var FateClient = class {
|
|
|
2098
587
|
this.viewDataCache.clear();
|
|
2099
588
|
notify();
|
|
2100
589
|
}
|
|
590
|
+
/** @internal */
|
|
591
|
+
exportPersistenceState() {
|
|
592
|
+
return {
|
|
593
|
+
data: encodeHydrationValue({
|
|
594
|
+
rootLists: [...this.rootLists].map(([type, keys]) => [type, [...keys]]),
|
|
595
|
+
rootRequests: [...this.rootRequests],
|
|
596
|
+
store: this.store.dehydrateConfirmed()
|
|
597
|
+
}, this.hydrationLimits),
|
|
598
|
+
scope: this.hydrationScope,
|
|
599
|
+
version: 1
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
/** @internal */
|
|
603
|
+
retainPersistenceRequest(request) {
|
|
604
|
+
return this.operationLifetime.retain(request, () => this.scheduleGarbageCollection());
|
|
605
|
+
}
|
|
606
|
+
/** @internal */
|
|
607
|
+
getPersistenceScope() {
|
|
608
|
+
return this.hydrationScope;
|
|
609
|
+
}
|
|
610
|
+
/** @internal */
|
|
611
|
+
getPersistenceRootLists(type) {
|
|
612
|
+
return this.rootLists.get(type) ?? /* @__PURE__ */ new Set();
|
|
613
|
+
}
|
|
614
|
+
/** @internal */
|
|
615
|
+
getPersistenceQuery(key) {
|
|
616
|
+
return this.rootRequests.get(key);
|
|
617
|
+
}
|
|
618
|
+
/** @internal Merge a small disk read beneath optimistic layers. */
|
|
619
|
+
restorePersistenceData(state) {
|
|
620
|
+
this.store.update(() => {
|
|
621
|
+
const coverage = new Map(state.store.coverage);
|
|
622
|
+
for (const [id, record] of state.store.records) {
|
|
623
|
+
const existing = this.store.read(id);
|
|
624
|
+
const paths = coverage.get(id) ?? [];
|
|
625
|
+
this.store.merge(id, existing ? mergePreservingExisting(record, existing) : record, paths);
|
|
626
|
+
}
|
|
627
|
+
for (const [key, list] of state.store.lists) if (!this.store.getListState(key)) this.store.setList(key, list);
|
|
628
|
+
for (const [key, id] of state.rootRequests) if (!this.rootRequests.has(key)) this.rootRequests.set(key, id);
|
|
629
|
+
for (const [type, keys] of state.rootLists) for (const key of keys) this.registerRootList(type, key);
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
/** @internal */
|
|
633
|
+
getRetainedRequests() {
|
|
634
|
+
return this.operationLifetime.getDescriptors();
|
|
635
|
+
}
|
|
636
|
+
/** @internal */
|
|
637
|
+
restoreRetainedRequests(requests) {
|
|
638
|
+
this.operationLifetime.restore(requests);
|
|
639
|
+
}
|
|
640
|
+
/** @internal */
|
|
641
|
+
hasMutation(key, entity) {
|
|
642
|
+
return this.mutationEntities?.get(key) === entity && this.types.has(entity);
|
|
643
|
+
}
|
|
2101
644
|
registerRootList(type, key) {
|
|
2102
645
|
let lists = this.rootLists.get(type);
|
|
2103
646
|
if (!lists) {
|
|
@@ -2156,8 +699,28 @@ var FateClient = class {
|
|
|
2156
699
|
if (!descriptor || descriptor === "scalar" || !("listOf" in descriptor)) throw new Error(`fate: Could not find node type for '${type}.${field}'.`);
|
|
2157
700
|
return descriptor.listOf;
|
|
2158
701
|
}
|
|
702
|
+
/** @internal Validates local wiring without requiring network connectivity. */
|
|
703
|
+
validatePersistence() {
|
|
704
|
+
if (this.mutationEntities?.size) this.requireDurableMutationTransport();
|
|
705
|
+
}
|
|
706
|
+
/** @internal */
|
|
707
|
+
assertPersistenceActive() {
|
|
708
|
+
if (this.persistenceDisposal?.signal.aborted || this.persistence?.getSnapshot().status === "disposed") throw new Error("fate: Persistence was disposed.");
|
|
709
|
+
}
|
|
710
|
+
/** @internal */
|
|
711
|
+
disposePersistence() {
|
|
712
|
+
this.persistenceDisposal?.abort();
|
|
713
|
+
for (const { unsubscribe } of this.liveSubscriptions.values()) try {
|
|
714
|
+
unsubscribe();
|
|
715
|
+
} catch {}
|
|
716
|
+
this.liveSubscriptions.clear();
|
|
717
|
+
}
|
|
718
|
+
requireDurableMutationTransport() {
|
|
719
|
+
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.");
|
|
720
|
+
}
|
|
2159
721
|
async executeMutation(key, input, select, options = {}) {
|
|
2160
|
-
if (
|
|
722
|
+
if (options.identity) this.requireDurableMutationTransport();
|
|
723
|
+
else if (!this.transport.mutate) throw new Error(`fate: transport does not support mutations. Please provide a 'mutate' implementation in your transport.`);
|
|
2161
724
|
const baseRecord = input && typeof input === "object" ? input : void 0;
|
|
2162
725
|
const inputArgs = baseRecord && typeof baseRecord.args === "object" ? baseRecord.args : void 0;
|
|
2163
726
|
const argsPayload = combineArgsPayload(options.plan ? resolvedArgsFromPlan(options.plan) : void 0, combineArgsPayload(inputArgs, options.args));
|
|
@@ -2165,7 +728,7 @@ var FateClient = class {
|
|
|
2165
728
|
...baseRecord,
|
|
2166
729
|
args: argsPayload
|
|
2167
730
|
} : argsPayload ? { args: argsPayload } : input;
|
|
2168
|
-
return await this.trackPendingRequest(() => this.transport.mutate(key, requestInput, select));
|
|
731
|
+
return await this.trackPendingRequest(() => options.identity ? this.transport.mutateDurably(key, requestInput, select, options.identity) : this.transport.mutate(key, requestInput, select));
|
|
2169
732
|
}
|
|
2170
733
|
write(type, data, select, plan, insert) {
|
|
2171
734
|
return this.writeEntity(type, data, select, plan, null, insert);
|
|
@@ -2201,6 +764,18 @@ var FateClient = class {
|
|
|
2201
764
|
return this.stableRefWithViewNames(type, id, getRootViewNames(rootView));
|
|
2202
765
|
}
|
|
2203
766
|
readView(view, ref) {
|
|
767
|
+
if (this.persistence && this.persistence.getSnapshot().status !== "ready") {
|
|
768
|
+
const key = `restore:${ref.__typename}:${ref.id}:${[...getViewNames(view)].sort().join(",")}`;
|
|
769
|
+
let pending = this.pending.get(key);
|
|
770
|
+
if (!pending) {
|
|
771
|
+
pending = this.persistence.ready.then(() => {
|
|
772
|
+
if (this.persistence?.getSnapshot().status === "disposed") throw new Error("fate: Persistence was disposed.");
|
|
773
|
+
return this.readView(view, ref);
|
|
774
|
+
}).finally(() => this.pending.delete(key));
|
|
775
|
+
this.pending.set(key, pending);
|
|
776
|
+
}
|
|
777
|
+
return pending;
|
|
778
|
+
}
|
|
2204
779
|
const id = ref.id;
|
|
2205
780
|
const type = ref.__typename;
|
|
2206
781
|
if (id == null) {
|
|
@@ -2253,7 +828,36 @@ var FateClient = class {
|
|
|
2253
828
|
const pendingPromise = this.pending.get(key) || null;
|
|
2254
829
|
if (pendingPromise) return pendingPromise;
|
|
2255
830
|
const promise = this.trackPendingRequest(async () => {
|
|
2256
|
-
|
|
831
|
+
if (this.persistence) {
|
|
832
|
+
const persistedRequest = {
|
|
833
|
+
items: [{
|
|
834
|
+
ids: [id],
|
|
835
|
+
kind: "node",
|
|
836
|
+
name: type,
|
|
837
|
+
plan,
|
|
838
|
+
refViewNames: /* @__PURE__ */ new Set(),
|
|
839
|
+
type,
|
|
840
|
+
viewSignature: ""
|
|
841
|
+
}],
|
|
842
|
+
key: `view:${key}`
|
|
843
|
+
};
|
|
844
|
+
await this.persistence.restoreRequest(persistedRequest);
|
|
845
|
+
const remaining = this.store.missingForSelection(entityId, selectedPaths);
|
|
846
|
+
if (remaining.size) {
|
|
847
|
+
await this.fetchByIdAndNormalize(type, [id], remaining, plan);
|
|
848
|
+
this.persistence.fetched({
|
|
849
|
+
...persistedRequest,
|
|
850
|
+
items: persistedRequest.items.map((item) => ({
|
|
851
|
+
...item,
|
|
852
|
+
plan: {
|
|
853
|
+
...item.plan,
|
|
854
|
+
paths: remaining
|
|
855
|
+
}
|
|
856
|
+
})),
|
|
857
|
+
key: `${persistedRequest.key}:${JSON.stringify([...remaining].sort())}`
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
} else await this.fetchByIdAndNormalize(type, [id], missing, plan);
|
|
2257
861
|
try {
|
|
2258
862
|
if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
|
|
2259
863
|
this.stalledRequests.add(key);
|
|
@@ -2334,6 +938,7 @@ var FateClient = class {
|
|
|
2334
938
|
if (!this.transport.subscribeConnection) throw new Error(`fate: transport does not support live list views. Please provide a 'subscribeConnection' implementation in your transport.`);
|
|
2335
939
|
}
|
|
2336
940
|
subscribeLiveView(view, ref) {
|
|
941
|
+
this.assertPersistenceActive();
|
|
2337
942
|
this.assertLiveViewSupport();
|
|
2338
943
|
const id = ref.id;
|
|
2339
944
|
const type = ref.__typename;
|
|
@@ -2361,19 +966,26 @@ var FateClient = class {
|
|
|
2361
966
|
try {
|
|
2362
967
|
unsubscribe = this.transport.subscribeById(type, id, plan.paths, args, {
|
|
2363
968
|
onData: (record, liveSelect) => {
|
|
2364
|
-
if (!record || typeof record !== "object") return;
|
|
969
|
+
if (this.persistenceDisposal?.signal.aborted || !record || typeof record !== "object") return;
|
|
2365
970
|
const select = new Set(liveSelect ?? plan.paths);
|
|
2366
971
|
this.write(type, record, select, plan, "none");
|
|
2367
972
|
},
|
|
2368
973
|
onDelete: (deletedId) => {
|
|
974
|
+
if (this.persistenceDisposal?.signal.aborted) return;
|
|
2369
975
|
this.deleteRecord(type, deletedId ?? id);
|
|
2370
976
|
},
|
|
2371
|
-
onError: (error) =>
|
|
977
|
+
onError: (error) => {
|
|
978
|
+
if (!this.persistenceDisposal?.signal.aborted) this.reportLiveError(error);
|
|
979
|
+
}
|
|
2372
980
|
});
|
|
2373
981
|
} catch (error) {
|
|
2374
982
|
this.reportLiveError(error);
|
|
2375
983
|
return emptyDispose;
|
|
2376
984
|
}
|
|
985
|
+
if (this.persistenceDisposal?.signal.aborted) {
|
|
986
|
+
unsubscribe();
|
|
987
|
+
return emptyDispose;
|
|
988
|
+
}
|
|
2377
989
|
this.liveSubscriptions.set(key, {
|
|
2378
990
|
count: 1,
|
|
2379
991
|
unsubscribe
|
|
@@ -2383,6 +995,7 @@ var FateClient = class {
|
|
|
2383
995
|
};
|
|
2384
996
|
}
|
|
2385
997
|
subscribeLiveListView(view, connection) {
|
|
998
|
+
this.assertPersistenceActive();
|
|
2386
999
|
this.assertLiveConnectionSupport();
|
|
2387
1000
|
const plan = getSelectionPlan(view, null);
|
|
2388
1001
|
const connectionArgs = filterConnectionArgs(connection.args);
|
|
@@ -2399,8 +1012,11 @@ var FateClient = class {
|
|
|
2399
1012
|
let unsubscribe;
|
|
2400
1013
|
try {
|
|
2401
1014
|
unsubscribe = this.transport.subscribeConnection(connection.procedure, connection.type, connectionArgs, plan.paths, selectionArgs, {
|
|
2402
|
-
onError: (error) =>
|
|
1015
|
+
onError: (error) => {
|
|
1016
|
+
if (!this.persistenceDisposal?.signal.aborted) this.reportLiveError(error);
|
|
1017
|
+
},
|
|
2403
1018
|
onEvent: (event) => {
|
|
1019
|
+
if (this.persistenceDisposal?.signal.aborted) return;
|
|
2404
1020
|
this.applyLiveConnectionEvent(view, connection, plan, event);
|
|
2405
1021
|
}
|
|
2406
1022
|
});
|
|
@@ -2408,6 +1024,10 @@ var FateClient = class {
|
|
|
2408
1024
|
this.reportLiveError(error);
|
|
2409
1025
|
return emptyDispose;
|
|
2410
1026
|
}
|
|
1027
|
+
if (this.persistenceDisposal?.signal.aborted) {
|
|
1028
|
+
unsubscribe();
|
|
1029
|
+
return emptyDispose;
|
|
1030
|
+
}
|
|
2411
1031
|
this.liveSubscriptions.set(key, {
|
|
2412
1032
|
count: 1,
|
|
2413
1033
|
unsubscribe
|
|
@@ -2723,6 +1343,7 @@ var FateClient = class {
|
|
|
2723
1343
|
});
|
|
2724
1344
|
const { argsPayload, plan } = resolveSelectionPlan(view, requestArgs);
|
|
2725
1345
|
const { items, pagination } = await this.transport.fetchList(connection.field, plan.paths, argsPayload);
|
|
1346
|
+
this.assertPersistenceActive();
|
|
2726
1347
|
if (!items) return this.store.getListState(connection.key);
|
|
2727
1348
|
this.store.update(() => {
|
|
2728
1349
|
const incomingIds = [];
|
|
@@ -2754,6 +1375,7 @@ var FateClient = class {
|
|
|
2754
1375
|
const parentSelection = /* @__PURE__ */ new Set();
|
|
2755
1376
|
for (const path of nodeSelection) parentSelection.add(`${connection.field}.${path}`);
|
|
2756
1377
|
const [parentRecord] = await this.transport.fetchById(owner.type, [owner.id], parentSelection, scopedArgsPayload);
|
|
1378
|
+
this.assertPersistenceActive();
|
|
2757
1379
|
if (!parentRecord || typeof parentRecord !== "object") return this.store.getListState(connection.key);
|
|
2758
1380
|
const list = parentRecord[connection.field];
|
|
2759
1381
|
const connectionPayload = Array.isArray(list) ? {
|
|
@@ -2796,7 +1418,7 @@ var FateClient = class {
|
|
|
2796
1418
|
}
|
|
2797
1419
|
request(request, options) {
|
|
2798
1420
|
const mode = options?.mode ?? "cache-first";
|
|
2799
|
-
return this.requestWithDescriptor(this.createRequestDescriptor(request), mode, { revalidateExisting: true });
|
|
1421
|
+
return this.requestWithDescriptor(this.createRequestDescriptor(request), mode, { revalidateExisting: true }, options);
|
|
2800
1422
|
}
|
|
2801
1423
|
requestForRender(request, options) {
|
|
2802
1424
|
return this.prepareRequestForRender(request, options).promise;
|
|
@@ -2805,16 +1427,23 @@ var FateClient = class {
|
|
|
2805
1427
|
const mode = options?.mode ?? "cache-first";
|
|
2806
1428
|
const descriptor = this.createRequestDescriptor(request);
|
|
2807
1429
|
return {
|
|
2808
|
-
promise: this.requestWithDescriptor(descriptor, mode, { revalidateExisting: false }),
|
|
1430
|
+
promise: this.requestWithDescriptor(descriptor, mode, { revalidateExisting: false }, options),
|
|
2809
1431
|
requestKey: descriptor.key
|
|
2810
1432
|
};
|
|
2811
1433
|
}
|
|
2812
|
-
requestWithDescriptor(descriptor, mode, { revalidateExisting }) {
|
|
1434
|
+
requestWithDescriptor(descriptor, mode, { revalidateExisting }, options) {
|
|
1435
|
+
this.assertPersistenceActive();
|
|
1436
|
+
if (this.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
1437
|
const requestKey = descriptor.key;
|
|
2814
1438
|
const existingRequest = this.requests.get(requestKey)?.get(mode);
|
|
2815
1439
|
if (existingRequest) {
|
|
1440
|
+
if (this.persistence && options?.persist) {
|
|
1441
|
+
const executionOptions = this.requestExecutionOptions.get(existingRequest.descriptor);
|
|
1442
|
+
if (executionOptions) executionOptions.persist = { ...options.persist };
|
|
1443
|
+
}
|
|
1444
|
+
if (this.persistence && options?.persist && existingRequest.status === "fulfilled") this.persistence.used(existingRequest.descriptor, options.persist);
|
|
2816
1445
|
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);
|
|
1446
|
+
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
1447
|
return existingRequest;
|
|
2819
1448
|
}
|
|
2820
1449
|
const handle = new FateRequestPromise(descriptor, () => this.getRequestResultFromDescriptor(descriptor));
|
|
@@ -2824,7 +1453,7 @@ var FateClient = class {
|
|
|
2824
1453
|
this.requests.set(requestKey, requests);
|
|
2825
1454
|
}
|
|
2826
1455
|
requests.set(mode, handle);
|
|
2827
|
-
this.executeRequestHandle(handle, mode);
|
|
1456
|
+
this.executeRequestHandle(handle, mode, options);
|
|
2828
1457
|
return handle;
|
|
2829
1458
|
}
|
|
2830
1459
|
releaseRequest(request, mode) {
|
|
@@ -2869,7 +1498,7 @@ var FateClient = class {
|
|
|
2869
1498
|
* memory-management flows.
|
|
2870
1499
|
*/
|
|
2871
1500
|
gc() {
|
|
2872
|
-
if (this.hasActiveOptimisticUpdates()) {
|
|
1501
|
+
if (this.persistence ? this.store.hasTransientOptimisticUpdates : this.hasActiveOptimisticUpdates()) {
|
|
2873
1502
|
this.gcPending = true;
|
|
2874
1503
|
return;
|
|
2875
1504
|
}
|
|
@@ -2890,6 +1519,10 @@ var FateClient = class {
|
|
|
2890
1519
|
for (const id of listState.ids) markRecord(id);
|
|
2891
1520
|
for (const id of listState.pendingAfterIds ?? []) markRecord(id);
|
|
2892
1521
|
};
|
|
1522
|
+
const optimisticRoots = this.store.getOptimisticRoots();
|
|
1523
|
+
optimisticRoots.baseRecords.forEach((record) => this.markRecordReferences(record, markRecord));
|
|
1524
|
+
optimisticRoots.records.forEach(markRecord);
|
|
1525
|
+
optimisticRoots.lists.forEach(markList);
|
|
2893
1526
|
for (const descriptor of this.operationLifetime.getDescriptors()) for (const item of descriptor.items) {
|
|
2894
1527
|
if (item.kind === "node" || item.kind === "nodes") {
|
|
2895
1528
|
for (const raw of item.ids) markRecord(toEntityId(item.type, raw));
|
|
@@ -2928,15 +1561,18 @@ var FateClient = class {
|
|
|
2928
1561
|
if (keys.size === 0) this.rootLists.delete(type);
|
|
2929
1562
|
}
|
|
2930
1563
|
}
|
|
2931
|
-
executeRequestHandle(handle, mode) {
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
1564
|
+
executeRequestHandle(handle, mode, options) {
|
|
1565
|
+
const executionOptions = {
|
|
1566
|
+
fetchAll: mode !== "cache-first",
|
|
1567
|
+
persist: options?.persist ? { ...options.persist } : void 0
|
|
1568
|
+
};
|
|
1569
|
+
if (this.persistence) this.requestExecutionOptions.set(handle.descriptor, executionOptions);
|
|
1570
|
+
const execute = () => {
|
|
1571
|
+
this.assertPersistenceActive();
|
|
1572
|
+
if (mode === "stale-while-revalidate") return this.handleStoreAndNetworkRequest(handle.descriptor, executionOptions);
|
|
1573
|
+
return this.executeRequest(handle.descriptor, executionOptions).then(() => this.getRequestResultFromDescriptor(handle.descriptor));
|
|
1574
|
+
};
|
|
1575
|
+
handle.start(this.persistence ? this.withPersistenceLifecycle(() => this.persistence.ready.then(() => mode === "network-only" ? void 0 : this.persistence.restoreRequest(handle.descriptor)).then(execute)) : execute());
|
|
2940
1576
|
}
|
|
2941
1577
|
markRecordReferences(record, markRecord) {
|
|
2942
1578
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2962,24 +1598,47 @@ var FateClient = class {
|
|
|
2962
1598
|
hasPendingRequests() {
|
|
2963
1599
|
return this.pendingNetworkRequests > 0;
|
|
2964
1600
|
}
|
|
2965
|
-
|
|
2966
|
-
|
|
1601
|
+
/** @internal */
|
|
1602
|
+
async withPersistenceLifecycle(request) {
|
|
1603
|
+
this.assertPersistenceActive();
|
|
1604
|
+
const signal = this.persistenceDisposal?.signal;
|
|
1605
|
+
let onDispose;
|
|
2967
1606
|
try {
|
|
2968
|
-
|
|
1607
|
+
const execution = request();
|
|
1608
|
+
const result = signal ? await Promise.race([execution, new Promise((_, reject) => {
|
|
1609
|
+
onDispose = () => reject(/* @__PURE__ */ new Error("fate: Persistence was disposed."));
|
|
1610
|
+
signal.addEventListener("abort", onDispose, { once: true });
|
|
1611
|
+
if (signal.aborted) onDispose();
|
|
1612
|
+
})]) : await execution;
|
|
1613
|
+
this.assertPersistenceActive();
|
|
1614
|
+
return result;
|
|
2969
1615
|
} finally {
|
|
2970
|
-
|
|
1616
|
+
if (onDispose) signal?.removeEventListener("abort", onDispose);
|
|
2971
1617
|
}
|
|
2972
1618
|
}
|
|
1619
|
+
async trackPendingRequest(request) {
|
|
1620
|
+
return this.withPersistenceLifecycle(async () => {
|
|
1621
|
+
if (this.persistence) await this.persistence.ready;
|
|
1622
|
+
this.assertPersistenceActive();
|
|
1623
|
+
this.pendingNetworkRequests += 1;
|
|
1624
|
+
try {
|
|
1625
|
+
return await this.withPersistenceLifecycle(request);
|
|
1626
|
+
} finally {
|
|
1627
|
+
this.pendingNetworkRequests -= 1;
|
|
1628
|
+
this.persistence?.changed();
|
|
1629
|
+
}
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
2973
1632
|
runPendingGarbageCollection() {
|
|
2974
1633
|
if (this.gcPending && !this.hasActiveOptimisticUpdates()) this.gc();
|
|
2975
1634
|
}
|
|
2976
|
-
async handleStoreAndNetworkRequest(request) {
|
|
1635
|
+
async handleStoreAndNetworkRequest(request, options) {
|
|
2977
1636
|
if (!this.hasRequestData(request)) {
|
|
2978
|
-
await this.executeRequest(request,
|
|
1637
|
+
await this.executeRequest(request, options);
|
|
2979
1638
|
return this.getRequestResultFromDescriptor(request);
|
|
2980
1639
|
}
|
|
2981
1640
|
const result = this.getRequestResultFromDescriptor(request);
|
|
2982
|
-
this.executeRequest(request,
|
|
1641
|
+
this.executeRequest(request, options).catch(() => {});
|
|
2983
1642
|
return result;
|
|
2984
1643
|
}
|
|
2985
1644
|
getRootType(name) {
|
|
@@ -3005,6 +1664,8 @@ var FateClient = class {
|
|
|
3005
1664
|
const fetchAll = options.fetchAll ?? false;
|
|
3006
1665
|
const groups = /* @__PURE__ */ new Map();
|
|
3007
1666
|
const promises = [];
|
|
1667
|
+
const fetchedItems = [];
|
|
1668
|
+
let fetchedAll = true;
|
|
3008
1669
|
for (const item of request.items) if (item.kind === "node" || item.kind === "nodes") {
|
|
3009
1670
|
const fields = item.plan.paths;
|
|
3010
1671
|
const fieldsSignature = [...fields].slice().sort().join(",");
|
|
@@ -3020,23 +1681,46 @@ var FateClient = class {
|
|
|
3020
1681
|
};
|
|
3021
1682
|
groups.set(groupKey, group);
|
|
3022
1683
|
}
|
|
1684
|
+
const fetchedIds = [];
|
|
3023
1685
|
for (const raw of item.ids) {
|
|
3024
1686
|
const entityId = toEntityId(item.type, raw);
|
|
3025
1687
|
const missing = this.store.missingForSelection(entityId, fields);
|
|
3026
|
-
if (fetchAll || missing.size > 0)
|
|
1688
|
+
if (fetchAll || missing.size > 0) {
|
|
1689
|
+
group.ids.push(raw);
|
|
1690
|
+
fetchedIds.push(raw);
|
|
1691
|
+
}
|
|
3027
1692
|
}
|
|
1693
|
+
if (fetchedIds.length) fetchedItems.push({
|
|
1694
|
+
...item,
|
|
1695
|
+
ids: fetchedIds
|
|
1696
|
+
});
|
|
1697
|
+
fetchedAll &&= fetchedIds.length === item.ids.length;
|
|
3028
1698
|
} else {
|
|
3029
1699
|
if (item.kind === "query") {
|
|
3030
1700
|
const hasResult = this.rootRequests.has(item.queryKey);
|
|
3031
1701
|
const entityId = this.rootRequests.get(item.queryKey);
|
|
3032
1702
|
const missing = hasResult && entityId ? this.store.missingForSelection(entityId, item.plan.paths) : item.plan.paths;
|
|
3033
|
-
if (fetchAll || !hasResult || entityId && missing.size > 0)
|
|
1703
|
+
if (fetchAll || !hasResult || entityId && missing.size > 0) {
|
|
1704
|
+
promises.push(this.fetchQueryAndNormalize(item));
|
|
1705
|
+
fetchedItems.push(item);
|
|
1706
|
+
} else fetchedAll = false;
|
|
3034
1707
|
}
|
|
3035
1708
|
if (item.kind !== "list") continue;
|
|
3036
|
-
if (!fetchAll && !getPaginationArgsInfo(item.argsPayload).hasCursorArg && this.hasRootListData(item))
|
|
1709
|
+
if (!fetchAll && !getPaginationArgsInfo(item.argsPayload).hasCursorArg && this.hasRootListData(item)) {
|
|
1710
|
+
fetchedAll = false;
|
|
1711
|
+
continue;
|
|
1712
|
+
}
|
|
3037
1713
|
promises.push(this.fetchListAndNormalize(item));
|
|
1714
|
+
fetchedItems.push(item);
|
|
3038
1715
|
}
|
|
3039
1716
|
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())]);
|
|
1717
|
+
if (this.persistence) {
|
|
1718
|
+
if (fetchedItems.length) this.persistence.fetched(fetchedAll ? request : {
|
|
1719
|
+
items: fetchedItems,
|
|
1720
|
+
key: `partial:${JSON.stringify([request.key, fetchedItems.map((item) => [item.name, "ids" in item ? item.ids : null])])}`
|
|
1721
|
+
}, options.persist);
|
|
1722
|
+
if (!fetchedItems.length || !fetchedAll) this.persistence.used(request, options.persist);
|
|
1723
|
+
}
|
|
3040
1724
|
}
|
|
3041
1725
|
hasRequestData(request) {
|
|
3042
1726
|
for (const item of request.items) {
|
|
@@ -3124,6 +1808,7 @@ var FateClient = class {
|
|
|
3124
1808
|
const resolvedArgs = resolvedArgsFromPlan(plan);
|
|
3125
1809
|
await this.trackPendingRequest(async () => {
|
|
3126
1810
|
const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
|
|
1811
|
+
this.assertPersistenceActive();
|
|
3127
1812
|
for (const record of records) this.writeEntity(type, record, select, plan, prefix);
|
|
3128
1813
|
});
|
|
3129
1814
|
}
|
|
@@ -3131,6 +1816,7 @@ var FateClient = class {
|
|
|
3131
1816
|
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
1817
|
await this.trackPendingRequest(async () => {
|
|
3133
1818
|
const record = await this.transport.fetchQuery(item.name, item.plan.paths, item.argsPayload);
|
|
1819
|
+
this.assertPersistenceActive();
|
|
3134
1820
|
if (!record || typeof record !== "object") {
|
|
3135
1821
|
this.rootRequests.set(item.queryKey, null);
|
|
3136
1822
|
return;
|
|
@@ -3143,6 +1829,7 @@ var FateClient = class {
|
|
|
3143
1829
|
if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${item.name}'.`);
|
|
3144
1830
|
await this.trackPendingRequest(async () => {
|
|
3145
1831
|
const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
|
|
1832
|
+
this.assertPersistenceActive();
|
|
3146
1833
|
this.store.update(() => {
|
|
3147
1834
|
const ids = [];
|
|
3148
1835
|
const cursors = [];
|
|
@@ -3185,7 +1872,8 @@ var FateClient = class {
|
|
|
3185
1872
|
const childType = relationDescriptor.type;
|
|
3186
1873
|
const childConfig = this.types.get(childType);
|
|
3187
1874
|
if (!childConfig) throw new Error(`fate: Unknown related type '${childType}' (field '${type}.${key}').`);
|
|
3188
|
-
|
|
1875
|
+
const childId = toEntityId(childType, childConfig.getId(value));
|
|
1876
|
+
result[key] = createNodeRef(childId);
|
|
3189
1877
|
this.writeEntity(childType, value, childPaths, plan, fieldPath);
|
|
3190
1878
|
}
|
|
3191
1879
|
} else if (relationDescriptor && typeof relationDescriptor === "object" && "listOf" in relationDescriptor) {
|
|
@@ -3528,7 +2216,7 @@ function createClient(options) {
|
|
|
3528
2216
|
* Builds a `Transport` backed by a tRPC client using the configured resolvers
|
|
3529
2217
|
* for by-id queries, lists, and mutations.
|
|
3530
2218
|
*/
|
|
3531
|
-
function createTRPCTransport({ byId, client, lists, mutations, queries }) {
|
|
2219
|
+
function createTRPCTransport({ byId, client, lists, mutateDurably, mutations, queries }) {
|
|
3532
2220
|
const transport = {
|
|
3533
2221
|
async fetchById(type, ids, select, args) {
|
|
3534
2222
|
const resolver = byId[type];
|
|
@@ -3556,7 +2244,8 @@ function createTRPCTransport({ byId, client, lists, mutations, queries }) {
|
|
|
3556
2244
|
args,
|
|
3557
2245
|
select: [...select]
|
|
3558
2246
|
});
|
|
3559
|
-
}
|
|
2247
|
+
},
|
|
2248
|
+
mutateDurably
|
|
3560
2249
|
};
|
|
3561
2250
|
transport.mutate = async (procedure, input, select) => {
|
|
3562
2251
|
const resolver = mutations?.[procedure];
|
|
@@ -3634,7 +2323,7 @@ const responseError = async (response) => {
|
|
|
3634
2323
|
return new FateRequestError(errorCodeFromStatus(response.status), message, { status: response.status });
|
|
3635
2324
|
};
|
|
3636
2325
|
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.");
|
|
2326
|
+
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
2327
|
return value;
|
|
3639
2328
|
};
|
|
3640
2329
|
const resultValue = (result) => {
|
|
@@ -3672,7 +2361,7 @@ function createHTTPTransport({ eventSource, fetch: fetchImpl = defaultFetch, hea
|
|
|
3672
2361
|
const response = await fetchImpl(endpoint, {
|
|
3673
2362
|
body: JSON.stringify({
|
|
3674
2363
|
operations: batch.map((entry) => entry.operation),
|
|
3675
|
-
version: 1
|
|
2364
|
+
version: batch.some((entry) => entry.operation.mutation) ? 2 : 1
|
|
3676
2365
|
}),
|
|
3677
2366
|
headers: await requestHeaders({ "content-type": "application/json" }, headers),
|
|
3678
2367
|
method: "POST"
|
|
@@ -3729,6 +2418,15 @@ function createHTTPTransport({ eventSource, fetch: fetchImpl = defaultFetch, hea
|
|
|
3729
2418
|
name,
|
|
3730
2419
|
select: [...select]
|
|
3731
2420
|
});
|
|
2421
|
+
},
|
|
2422
|
+
mutateDurably(name, input, select, identity) {
|
|
2423
|
+
return enqueue({
|
|
2424
|
+
input,
|
|
2425
|
+
kind: "mutation",
|
|
2426
|
+
mutation: identity,
|
|
2427
|
+
name,
|
|
2428
|
+
select: [...select]
|
|
2429
|
+
});
|
|
3732
2430
|
}
|
|
3733
2431
|
};
|
|
3734
2432
|
if (liveEnabled) {
|