@nkzw/fate 1.0.3 → 1.1.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 +354 -2
- package/docs/api/classes/FateClient.md +110 -40
- package/docs/api/functions/clientRoot.md +1 -1
- package/docs/api/functions/createClient.md +8 -4
- package/docs/api/functions/createGraphQLTransport.md +21 -0
- package/docs/api/functions/createHTTPTransport.md +1 -1
- package/docs/api/functions/createTRPCTransport.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 +39 -0
- 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 +10 -0
- package/docs/api/interfaces/FateThenable.md +3 -3
- package/docs/api/interfaces/Transport.md +7 -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/Entity.md +2 -2
- package/docs/api/type-aliases/EntityId.md +1 -1
- package/docs/api/type-aliases/FateDehydratedState.md +11 -0
- 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 +1 -1
- 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 +19 -0
- package/docs/api/type-aliases/GraphQLMutationInput.md +11 -0
- package/docs/api/type-aliases/GraphQLMutationMap.md +11 -0
- package/docs/api/type-aliases/GraphQLMutationOutput.md +11 -0
- package/docs/api/type-aliases/GraphQLTransportOptions.md +119 -0
- package/docs/api/type-aliases/HydrateOptions.md +7 -0
- package/docs/api/type-aliases/HydrationLimits.md +7 -0
- 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/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/Request.md +1 -1
- package/docs/api/type-aliases/RequestMode.md +1 -1
- package/docs/api/type-aliases/RequestOptions.md +1 -1
- package/docs/api/type-aliases/RequestResult.md +1 -1
- package/docs/api/type-aliases/Selection.md +1 -1
- package/docs/api/type-aliases/Snapshot.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/guide/getting-started.md +1 -1
- package/docs/guide/graphql-integration.md +298 -0
- package/docs/guide/requests.md +52 -0
- package/docs/guide/server-integration.md +2 -1
- package/lib/{executor-BqwHdN3n.d.mts → executor-sLcAqDMs.d.mts} +1 -1
- package/lib/graphqlTransport-DbRHbYtb.d.mts +74 -0
- package/lib/graphqlTransport-IFgmY-Qq.mjs +499 -0
- package/lib/graphqlTransport.d.mts +2 -0
- package/lib/graphqlTransport.mjs +2 -0
- package/lib/index.d.mts +5 -4
- package/lib/index.mjs +501 -89
- package/lib/{list-4wRNSVgI.d.mts → list-CMQOQGdn.d.mts} +1 -1
- package/lib/list.d.mts +1 -1
- package/lib/list.mjs +1 -1
- package/lib/{liveTopics-DzNtJaBD.mjs → liveTopics-JIDh3t3P.mjs} +1 -1
- package/lib/{record-CirbfZWX.d.mts → record-BtdKzEGI.d.mts} +2 -2
- package/lib/server/drizzle.d.mts +6 -8
- package/lib/server/drizzle.mjs +2 -2
- package/lib/server/prisma.d.mts +6 -8
- package/lib/server/prisma.mjs +2 -2
- package/lib/server.d.mts +3 -3
- package/lib/server.mjs +3 -3
- package/lib/{sourceRouter-C22uFcZg.mjs → sourceRouter-N9Vp0Cj6.mjs} +1 -1
- package/lib/{types-Dz46PXr3.d.mts → transport-BTgtSwOM.d.mts} +211 -143
- package/lib/vite.d.mts +1 -1
- package/lib/vite.mjs +150 -3
- package/package.json +11 -3
- /package/lib/{list-C_mi6GnE.mjs → list-_6fXYLtJ.mjs} +0 -0
- /package/lib/{record-AeZJC9fd.mjs → record-B07VwXd-.mjs} +0 -0
package/lib/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { t as isRecord } from "./record-
|
|
2
|
-
import { a as errorCodeFromStatus, c as cloneArgs, d as hashArgs, f as paginationArgKeys, i as FateRequestError, l as combineArgsPayload, m as scopeArgsPayload, n as liveEntityTopic, p as resolvedArgsFromPlan, r as liveGlobalConnectionTopic, s as applyArgsPayloadToPlan, t as liveConnectionTopic, u as filterConnectionArgs } from "./liveTopics-
|
|
3
|
-
import { t as getListEntries } from "./list-
|
|
1
|
+
import { t as isRecord } from "./record-B07VwXd-.mjs";
|
|
2
|
+
import { a as errorCodeFromStatus, c as cloneArgs, d as hashArgs, f as paginationArgKeys, i as FateRequestError, l as combineArgsPayload, m as scopeArgsPayload, n as liveEntityTopic, p as resolvedArgsFromPlan, r as liveGlobalConnectionTopic, s as applyArgsPayloadToPlan, t as liveConnectionTopic, u as filterConnectionArgs } from "./liveTopics-JIDh3t3P.mjs";
|
|
3
|
+
import { t as getListEntries } from "./list-_6fXYLtJ.mjs";
|
|
4
|
+
import { n as graphqlMutation, t as createGraphQLTransport } from "./graphqlTransport-IFgmY-Qq.mjs";
|
|
4
5
|
//#region src/cache.ts
|
|
5
6
|
var ViewDataCache = class {
|
|
6
7
|
constructor() {
|
|
@@ -8,6 +9,11 @@ var ViewDataCache = class {
|
|
|
8
9
|
this.rootDependencies = /* @__PURE__ */ new Map();
|
|
9
10
|
this.dependencyIndex = /* @__PURE__ */ new Map();
|
|
10
11
|
}
|
|
12
|
+
clear() {
|
|
13
|
+
this.cache.clear();
|
|
14
|
+
this.rootDependencies.clear();
|
|
15
|
+
this.dependencyIndex.clear();
|
|
16
|
+
}
|
|
11
17
|
get(entityId, view, ref) {
|
|
12
18
|
return this.cache.get(entityId)?.get(view)?.get(ref) ?? null;
|
|
13
19
|
}
|
|
@@ -63,6 +69,274 @@ var ViewDataCache = class {
|
|
|
63
69
|
}
|
|
64
70
|
};
|
|
65
71
|
//#endregion
|
|
72
|
+
//#region src/types.ts
|
|
73
|
+
/** Internal marker added to objects that represent a view payload. */
|
|
74
|
+
const ViewKind = Symbol("__fate__view");
|
|
75
|
+
/** Symbol used to attach the set of view tags that were spread into a ref or masked record. */
|
|
76
|
+
const ViewsTag = Symbol("__fate__views");
|
|
77
|
+
/** Symbol used to brand a value as a node reference inside the cache. */
|
|
78
|
+
const NodeRefTag = Symbol("__fate__node-ref");
|
|
79
|
+
/** Symbol attached to connection results so pagination helpers can find their metadata. */
|
|
80
|
+
const ConnectionTag = Symbol("__fate__connection");
|
|
81
|
+
const viewTag = "__fate-view__";
|
|
82
|
+
/** Generates a stable view tag for a view definition. */
|
|
83
|
+
function getViewTag(id) {
|
|
84
|
+
return `${viewTag}${id}`;
|
|
85
|
+
}
|
|
86
|
+
/** Determines whether a property key is a fate view tag. */
|
|
87
|
+
function isViewTag(key) {
|
|
88
|
+
return key.startsWith(viewTag);
|
|
89
|
+
}
|
|
90
|
+
/** Indicates whether a request item represents an explicit node ID. */
|
|
91
|
+
function isNodeItem(item) {
|
|
92
|
+
return "id" in item;
|
|
93
|
+
}
|
|
94
|
+
/** Indicates whether a request item represents explicit node IDs. */
|
|
95
|
+
function isNodesItem(item) {
|
|
96
|
+
return "ids" in item;
|
|
97
|
+
}
|
|
98
|
+
/** Indicates whether a request item represents a root query. */
|
|
99
|
+
function isQueryItem(item) {
|
|
100
|
+
return "view" in item && !("id" in item) && !("ids" in item);
|
|
101
|
+
}
|
|
102
|
+
/** Brand used on root definitions to mark their identity in the d.ts output. */
|
|
103
|
+
const RootKind = "__fate__root";
|
|
104
|
+
/** Brand used on mutation definitions to mark their identity in the d.ts output. */
|
|
105
|
+
const MutationKind = "__fate__mutation";
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/node-ref.ts
|
|
108
|
+
function createNodeRef(id) {
|
|
109
|
+
const ref = {};
|
|
110
|
+
Object.defineProperty(ref, NodeRefTag, {
|
|
111
|
+
configurable: false,
|
|
112
|
+
enumerable: false,
|
|
113
|
+
value: id,
|
|
114
|
+
writable: false
|
|
115
|
+
});
|
|
116
|
+
return Object.freeze(ref);
|
|
117
|
+
}
|
|
118
|
+
function isNodeRef(value) {
|
|
119
|
+
return !value || typeof value !== "object" ? false : NodeRefTag in value;
|
|
120
|
+
}
|
|
121
|
+
function getNodeRefId(ref) {
|
|
122
|
+
return ref[NodeRefTag];
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/hydration.ts
|
|
126
|
+
const maxDepth = 64;
|
|
127
|
+
const defaultHydrationLimits = {
|
|
128
|
+
maxCollectionLength: 1e5,
|
|
129
|
+
maxNodes: 25e4,
|
|
130
|
+
maxStringLength: 1e6
|
|
131
|
+
};
|
|
132
|
+
const resolveHydrationLimits = (limits) => {
|
|
133
|
+
const result = {
|
|
134
|
+
...defaultHydrationLimits,
|
|
135
|
+
...limits
|
|
136
|
+
};
|
|
137
|
+
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.`);
|
|
138
|
+
return result;
|
|
139
|
+
};
|
|
140
|
+
const error = (path, message) => /* @__PURE__ */ new Error(`fate: Cannot serialize hydration value at '${path}': ${message}`);
|
|
141
|
+
const isPlainObject = (value) => {
|
|
142
|
+
const prototype = Object.getPrototypeOf(value);
|
|
143
|
+
return prototype === Object.prototype || prototype === null;
|
|
144
|
+
};
|
|
145
|
+
const isDenseArray = (value) => {
|
|
146
|
+
for (let index = 0; index < value.length; index += 1) if (!Object.hasOwn(value, index)) return false;
|
|
147
|
+
return true;
|
|
148
|
+
};
|
|
149
|
+
const isDate$1 = (value) => Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function" && typeof value.toISOString === "function";
|
|
150
|
+
const encodeHydrationValue = (value, limits) => {
|
|
151
|
+
const resolvedLimits = resolveHydrationLimits(limits);
|
|
152
|
+
const seen = /* @__PURE__ */ new Set();
|
|
153
|
+
let nodes = 0;
|
|
154
|
+
const encode = (entry, path, depth) => {
|
|
155
|
+
nodes += 1;
|
|
156
|
+
if (nodes > resolvedLimits.maxNodes) throw error(path, `Maximum node count of ${String(resolvedLimits.maxNodes)} exceeded.`);
|
|
157
|
+
if (depth > maxDepth) throw error(path, `Maximum depth of ${String(maxDepth)} exceeded.`);
|
|
158
|
+
if (entry === null) return ["null"];
|
|
159
|
+
const type = typeof entry;
|
|
160
|
+
if (typeof entry === "string" || typeof entry === "boolean") {
|
|
161
|
+
if (typeof entry === "string" && entry.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
162
|
+
return [typeof entry, entry];
|
|
163
|
+
}
|
|
164
|
+
if (type === "number") {
|
|
165
|
+
if (Number.isNaN(entry)) return ["number", "NaN"];
|
|
166
|
+
if (entry === Infinity) return ["number", "Infinity"];
|
|
167
|
+
if (entry === -Infinity) return ["number", "-Infinity"];
|
|
168
|
+
if (Object.is(entry, -0)) return ["number", "-0"];
|
|
169
|
+
return ["number", entry];
|
|
170
|
+
}
|
|
171
|
+
if (type === "undefined") return ["undefined"];
|
|
172
|
+
if (type === "bigint") {
|
|
173
|
+
const bigint = String(entry);
|
|
174
|
+
if (bigint.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
175
|
+
return ["bigint", bigint];
|
|
176
|
+
}
|
|
177
|
+
if (type === "function" || type === "symbol") throw error(path, `Received unsupported '${type}' value.`);
|
|
178
|
+
if (!entry || typeof entry !== "object") throw error(path, `Received unsupported '${type}' value.`);
|
|
179
|
+
if (isNodeRef(entry)) {
|
|
180
|
+
const id = getNodeRefId(entry);
|
|
181
|
+
if (id.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
182
|
+
return ["ref", id];
|
|
183
|
+
}
|
|
184
|
+
if (isDate$1(entry)) {
|
|
185
|
+
if (Number.isNaN(entry.getTime())) throw error(path, `Received an invalid Date.`);
|
|
186
|
+
return ["date", entry.toISOString()];
|
|
187
|
+
}
|
|
188
|
+
if (seen.has(entry)) throw error(path, `Circular references are not supported.`);
|
|
189
|
+
seen.add(entry);
|
|
190
|
+
try {
|
|
191
|
+
if (Array.isArray(entry)) {
|
|
192
|
+
if (!isDenseArray(entry)) throw error(path, `Sparse arrays are not supported.`);
|
|
193
|
+
if (entry.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
|
|
194
|
+
return ["array", entry.map((item, index) => encode(item, `${path}[${String(index)}]`, depth + 1))];
|
|
195
|
+
}
|
|
196
|
+
if (!isPlainObject(entry)) throw error(path, `Received unsupported '${entry.constructor?.name ?? "object"}' value.`);
|
|
197
|
+
if (Object.getOwnPropertySymbols(entry).length > 0) throw error(path, `Received an unsupported symbol-keyed property.`);
|
|
198
|
+
const entries = Object.entries(entry);
|
|
199
|
+
if (entries.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
|
|
200
|
+
return ["object", entries.map(([key, item]) => {
|
|
201
|
+
if (key.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
202
|
+
return [key, encode(item, `${path}.${key}`, depth + 1)];
|
|
203
|
+
})];
|
|
204
|
+
} finally {
|
|
205
|
+
seen.delete(entry);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
return encode(value, "data", 0);
|
|
209
|
+
};
|
|
210
|
+
const invalidPayload = (path) => /* @__PURE__ */ new Error(`fate: Invalid hydration payload at '${path}'.`);
|
|
211
|
+
const tagLengths = {
|
|
212
|
+
array: 2,
|
|
213
|
+
bigint: 2,
|
|
214
|
+
boolean: 2,
|
|
215
|
+
date: 2,
|
|
216
|
+
null: 1,
|
|
217
|
+
number: 2,
|
|
218
|
+
object: 2,
|
|
219
|
+
ref: 2,
|
|
220
|
+
string: 2,
|
|
221
|
+
undefined: 1
|
|
222
|
+
};
|
|
223
|
+
const decodeHydrationValue = (value, limits) => {
|
|
224
|
+
const resolvedLimits = resolveHydrationLimits(limits);
|
|
225
|
+
let nodes = 0;
|
|
226
|
+
const decode = (entry, path, depth) => {
|
|
227
|
+
nodes += 1;
|
|
228
|
+
if (nodes > resolvedLimits.maxNodes || depth > maxDepth || !Array.isArray(entry) || typeof entry[0] !== "string") throw invalidPayload(path);
|
|
229
|
+
const [tag, payload] = entry;
|
|
230
|
+
if (entry.length !== tagLengths[tag]) throw invalidPayload(path);
|
|
231
|
+
switch (tag) {
|
|
232
|
+
case "null": return null;
|
|
233
|
+
case "undefined": return;
|
|
234
|
+
case "string":
|
|
235
|
+
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
236
|
+
return payload;
|
|
237
|
+
case "boolean":
|
|
238
|
+
if (typeof payload !== "boolean") throw invalidPayload(path);
|
|
239
|
+
return payload;
|
|
240
|
+
case "number":
|
|
241
|
+
if (typeof payload === "number") {
|
|
242
|
+
if (!Number.isFinite(payload) || Object.is(payload, -0)) throw invalidPayload(path);
|
|
243
|
+
return payload;
|
|
244
|
+
}
|
|
245
|
+
if (payload === "NaN") return NaN;
|
|
246
|
+
if (payload === "Infinity") return Infinity;
|
|
247
|
+
if (payload === "-Infinity") return -Infinity;
|
|
248
|
+
if (payload === "-0") return -0;
|
|
249
|
+
throw invalidPayload(path);
|
|
250
|
+
case "bigint":
|
|
251
|
+
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
252
|
+
try {
|
|
253
|
+
const bigint = BigInt(payload);
|
|
254
|
+
if (String(bigint) !== payload) throw invalidPayload(path);
|
|
255
|
+
return bigint;
|
|
256
|
+
} catch {
|
|
257
|
+
throw invalidPayload(path);
|
|
258
|
+
}
|
|
259
|
+
case "date": {
|
|
260
|
+
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
261
|
+
const date = new Date(payload);
|
|
262
|
+
if (Number.isNaN(date.getTime()) || date.toISOString() !== payload) throw invalidPayload(path);
|
|
263
|
+
return date;
|
|
264
|
+
}
|
|
265
|
+
case "ref":
|
|
266
|
+
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
267
|
+
return createNodeRef(payload);
|
|
268
|
+
case "array":
|
|
269
|
+
if (!Array.isArray(payload) || !isDenseArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
|
|
270
|
+
return payload.map((item, index) => decode(item, `${path}[${String(index)}]`, depth + 1));
|
|
271
|
+
case "object": {
|
|
272
|
+
if (!Array.isArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
|
|
273
|
+
const result = Object.create(null);
|
|
274
|
+
for (const item of payload) {
|
|
275
|
+
if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== "string" || item[0].length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
276
|
+
if (item[0] in result) throw invalidPayload(path);
|
|
277
|
+
result[item[0]] = decode(item[1], `${path}.${item[0]}`, depth + 1);
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
default: throw invalidPayload(path);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
return decode(value, "data", 0);
|
|
285
|
+
};
|
|
286
|
+
const isRecord$1 = (value) => {
|
|
287
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
|
|
288
|
+
const prototype = Object.getPrototypeOf(value);
|
|
289
|
+
return prototype === Object.prototype || prototype === null;
|
|
290
|
+
};
|
|
291
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
292
|
+
const hasDuplicates = (values) => new Set(values).size !== values.length;
|
|
293
|
+
const isUniqueStringArray = (value) => isStringArray(value) && !hasDuplicates(value);
|
|
294
|
+
const hasOnlyKeys = (value, keys) => Object.keys(value).every((key) => keys.has(key));
|
|
295
|
+
const listKeys = new Set([
|
|
296
|
+
"backwardPageLimit",
|
|
297
|
+
"cursors",
|
|
298
|
+
"forwardPageLimit",
|
|
299
|
+
"ids",
|
|
300
|
+
"liveAfterIds",
|
|
301
|
+
"liveBeforeIds",
|
|
302
|
+
"pagination",
|
|
303
|
+
"pendingAfterIds",
|
|
304
|
+
"pendingBeforeIds"
|
|
305
|
+
]);
|
|
306
|
+
const paginationKeys = new Set([
|
|
307
|
+
"hasNext",
|
|
308
|
+
"hasPrevious",
|
|
309
|
+
"nextCursor",
|
|
310
|
+
"previousCursor"
|
|
311
|
+
]);
|
|
312
|
+
const clientStateKeys = new Set([
|
|
313
|
+
"rootLists",
|
|
314
|
+
"rootRequests",
|
|
315
|
+
"store"
|
|
316
|
+
]);
|
|
317
|
+
const storeStateKeys = new Set([
|
|
318
|
+
"coverage",
|
|
319
|
+
"lists",
|
|
320
|
+
"records"
|
|
321
|
+
]);
|
|
322
|
+
const isEntry = (value) => Array.isArray(value) && value.length === 2 && typeof value[0] === "string";
|
|
323
|
+
const isOptionalLimit = (value) => value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
324
|
+
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");
|
|
325
|
+
const isList = (value) => {
|
|
326
|
+
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;
|
|
327
|
+
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));
|
|
328
|
+
};
|
|
329
|
+
const isFieldPath = (value) => value.length > 0 && value.split(".").every((segment) => segment.length > 0);
|
|
330
|
+
const isFieldPathArray = (value) => isUniqueStringArray(value) && value.every(isFieldPath);
|
|
331
|
+
const hasDuplicateEntries = (entries) => hasDuplicates(entries.map((entry) => entry[0]));
|
|
332
|
+
const decodeClientHydrationState = (value, limits) => {
|
|
333
|
+
const decoded = decodeHydrationValue(value, limits);
|
|
334
|
+
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");
|
|
335
|
+
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");
|
|
336
|
+
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");
|
|
337
|
+
return decoded;
|
|
338
|
+
};
|
|
339
|
+
//#endregion
|
|
66
340
|
//#region src/mask.ts
|
|
67
341
|
function emptyMask() {
|
|
68
342
|
return {
|
|
@@ -111,6 +385,18 @@ function fromPaths(paths) {
|
|
|
111
385
|
for (const path of paths) addPath(mask, path);
|
|
112
386
|
return mask;
|
|
113
387
|
}
|
|
388
|
+
function toPaths(mask) {
|
|
389
|
+
const paths = [];
|
|
390
|
+
const visit = (current, prefix) => {
|
|
391
|
+
if (current.all) {
|
|
392
|
+
if (prefix) paths.push(prefix);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
for (const [key, child] of current.children) visit(child, prefix ? `${prefix}.${key}` : key);
|
|
396
|
+
};
|
|
397
|
+
visit(mask, null);
|
|
398
|
+
return paths;
|
|
399
|
+
}
|
|
114
400
|
function isCovered(mask, path) {
|
|
115
401
|
if (mask.all) return true;
|
|
116
402
|
const parts = path.split(".");
|
|
@@ -138,7 +424,7 @@ function intersects(a, b) {
|
|
|
138
424
|
return false;
|
|
139
425
|
}
|
|
140
426
|
//#endregion
|
|
141
|
-
//#region ../../node_modules/.pnpm/@trpc+server@11.17.0_typescript@
|
|
427
|
+
//#region ../../node_modules/.pnpm/@trpc+server@11.17.0_typescript@6.0.3/node_modules/@trpc/server/dist/getErrorShape-BPSzUA7W.mjs
|
|
142
428
|
var __create = Object.create;
|
|
143
429
|
var __defProp = Object.defineProperty;
|
|
144
430
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -264,41 +550,6 @@ __toESM(__commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.72.2/node_
|
|
|
264
550
|
module.exports = _objectSpread2, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
|
265
551
|
} })(), 1);
|
|
266
552
|
//#endregion
|
|
267
|
-
//#region src/types.ts
|
|
268
|
-
/** Internal marker added to objects that represent a view payload. */
|
|
269
|
-
const ViewKind = Symbol("__fate__view");
|
|
270
|
-
/** Symbol used to attach the set of view tags that were spread into a ref or masked record. */
|
|
271
|
-
const ViewsTag = Symbol("__fate__views");
|
|
272
|
-
/** Symbol used to brand a value as a node reference inside the cache. */
|
|
273
|
-
const NodeRefTag = Symbol("__fate__node-ref");
|
|
274
|
-
/** Symbol attached to connection results so pagination helpers can find their metadata. */
|
|
275
|
-
const ConnectionTag = Symbol("__fate__connection");
|
|
276
|
-
const viewTag = "__fate-view__";
|
|
277
|
-
/** Generates a stable view tag for a view definition. */
|
|
278
|
-
function getViewTag(id) {
|
|
279
|
-
return `${viewTag}${id}`;
|
|
280
|
-
}
|
|
281
|
-
/** Determines whether a property key is a fate view tag. */
|
|
282
|
-
function isViewTag(key) {
|
|
283
|
-
return key.startsWith(viewTag);
|
|
284
|
-
}
|
|
285
|
-
/** Indicates whether a request item represents an explicit node ID. */
|
|
286
|
-
function isNodeItem(item) {
|
|
287
|
-
return "id" in item;
|
|
288
|
-
}
|
|
289
|
-
/** Indicates whether a request item represents explicit node IDs. */
|
|
290
|
-
function isNodesItem(item) {
|
|
291
|
-
return "ids" in item;
|
|
292
|
-
}
|
|
293
|
-
/** Indicates whether a request item represents a root query. */
|
|
294
|
-
function isQueryItem(item) {
|
|
295
|
-
return "view" in item && !("id" in item) && !("ids" in item);
|
|
296
|
-
}
|
|
297
|
-
/** Brand used on root definitions to mark their identity in the d.ts output. */
|
|
298
|
-
const RootKind = "__fate__root";
|
|
299
|
-
/** Brand used on mutation definitions to mark their identity in the d.ts output. */
|
|
300
|
-
const MutationKind = "__fate__mutation";
|
|
301
|
-
//#endregion
|
|
302
553
|
//#region src/view.ts
|
|
303
554
|
/**
|
|
304
555
|
* Collects all view payloads that apply to the given ref.
|
|
@@ -593,7 +844,7 @@ function wrapMutation(client, identifier) {
|
|
|
593
844
|
client.clearOptimisticUpdate(optimisticToken);
|
|
594
845
|
}
|
|
595
846
|
};
|
|
596
|
-
const mutationPromise = performMutation
|
|
847
|
+
const mutationPromise = client.trackPendingRequest(performMutation);
|
|
597
848
|
if (optimisticEntityId) client.registerPendingOptimisticMutation(optimisticEntityId, mutationPromise);
|
|
598
849
|
return mutationPromise;
|
|
599
850
|
};
|
|
@@ -621,24 +872,6 @@ const categorizeHTTPErrorStatus = (statusCode) => {
|
|
|
621
872
|
}
|
|
622
873
|
};
|
|
623
874
|
//#endregion
|
|
624
|
-
//#region src/node-ref.ts
|
|
625
|
-
function createNodeRef(id) {
|
|
626
|
-
const ref = {};
|
|
627
|
-
Object.defineProperty(ref, NodeRefTag, {
|
|
628
|
-
configurable: false,
|
|
629
|
-
enumerable: false,
|
|
630
|
-
value: id,
|
|
631
|
-
writable: false
|
|
632
|
-
});
|
|
633
|
-
return Object.freeze(ref);
|
|
634
|
-
}
|
|
635
|
-
function isNodeRef(value) {
|
|
636
|
-
return !value || typeof value !== "object" ? false : NodeRefTag in value;
|
|
637
|
-
}
|
|
638
|
-
function getNodeRefId(ref) {
|
|
639
|
-
return ref[NodeRefTag];
|
|
640
|
-
}
|
|
641
|
-
//#endregion
|
|
642
875
|
//#region src/operation-lifetime.ts
|
|
643
876
|
var OperationLifetime = class {
|
|
644
877
|
constructor(releaseBufferSize) {
|
|
@@ -726,6 +959,45 @@ const cloneValue = (value) => {
|
|
|
726
959
|
}
|
|
727
960
|
return value;
|
|
728
961
|
};
|
|
962
|
+
const isPlainRecord = (value) => {
|
|
963
|
+
if (value == null || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
|
|
964
|
+
const prototype = Object.getPrototypeOf(value);
|
|
965
|
+
return prototype === Object.prototype || prototype === null;
|
|
966
|
+
};
|
|
967
|
+
const isDate = (value) => value != null && typeof value === "object" && Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function";
|
|
968
|
+
const mergePreservingExisting = (incoming, existing) => {
|
|
969
|
+
if (!isPlainRecord(incoming) || !isPlainRecord(existing)) return existing;
|
|
970
|
+
const result = Object.create(null);
|
|
971
|
+
for (const [key, value] of Object.entries(incoming)) Object.defineProperty(result, key, {
|
|
972
|
+
configurable: true,
|
|
973
|
+
enumerable: true,
|
|
974
|
+
value,
|
|
975
|
+
writable: true
|
|
976
|
+
});
|
|
977
|
+
for (const [key, value] of Object.entries(existing)) Object.defineProperty(result, key, {
|
|
978
|
+
configurable: true,
|
|
979
|
+
enumerable: true,
|
|
980
|
+
value: Object.hasOwn(result, key) ? mergePreservingExisting(result[key], value) : value,
|
|
981
|
+
writable: true
|
|
982
|
+
});
|
|
983
|
+
return result;
|
|
984
|
+
};
|
|
985
|
+
const areHydrationValuesEqual = (left, right) => {
|
|
986
|
+
if (Object.is(left, right)) return true;
|
|
987
|
+
if (isNodeRef(left) || isNodeRef(right)) return isNodeRef(left) && isNodeRef(right) && getNodeRefId(left) === getNodeRefId(right);
|
|
988
|
+
if (isDate(left) || isDate(right)) return isDate(left) && isDate(right) && left.getTime() === right.getTime();
|
|
989
|
+
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]));
|
|
990
|
+
if (!isPlainRecord(left) || !isPlainRecord(right)) return false;
|
|
991
|
+
const leftKeys = Object.keys(left);
|
|
992
|
+
const rightKeys = Object.keys(right);
|
|
993
|
+
return leftKeys.length === rightKeys.length && leftKeys.every((key) => Object.hasOwn(right, key) && areHydrationValuesEqual(left[key], right[key]));
|
|
994
|
+
};
|
|
995
|
+
const areMasksEqual = (left, right) => {
|
|
996
|
+
if (!left || !right) return left === right;
|
|
997
|
+
const leftPaths = toPaths(left).sort();
|
|
998
|
+
const rightPaths = toPaths(right).sort();
|
|
999
|
+
return leftPaths.length === rightPaths.length && leftPaths.every((path, index) => path === rightPaths[index]);
|
|
1000
|
+
};
|
|
729
1001
|
const emptyFunction = () => {};
|
|
730
1002
|
var Store = class {
|
|
731
1003
|
constructor() {
|
|
@@ -739,6 +1011,58 @@ var Store = class {
|
|
|
739
1011
|
this.subscriptions = /* @__PURE__ */ new Map();
|
|
740
1012
|
this.listSubscriptions = /* @__PURE__ */ new Map();
|
|
741
1013
|
}
|
|
1014
|
+
dehydrate() {
|
|
1015
|
+
return {
|
|
1016
|
+
coverage: [...this.coverage].map(([id, mask]) => [id, toPaths(mask)]),
|
|
1017
|
+
lists: [...this.lists],
|
|
1018
|
+
records: [...this.records]
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
hydrate(state, mode, options = {}) {
|
|
1022
|
+
const previousCoverage = this.coverage;
|
|
1023
|
+
const previousLists = this.lists;
|
|
1024
|
+
const previousRecords = this.records;
|
|
1025
|
+
const coverage = mode === "replace" ? /* @__PURE__ */ new Map() : new Map([...previousCoverage].map(([id, mask]) => [id, cloneMask(mask)]));
|
|
1026
|
+
const lists = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousLists);
|
|
1027
|
+
const records = mode === "replace" ? /* @__PURE__ */ new Map() : new Map(previousRecords);
|
|
1028
|
+
for (const [id, incoming] of state.records) {
|
|
1029
|
+
const previous = records.get(id);
|
|
1030
|
+
const next = previous && mode === "preserve-existing" ? mergePreservingExisting(incoming, previous) : incoming;
|
|
1031
|
+
records.set(id, previous && areHydrationValuesEqual(previous, next) ? previous : next);
|
|
1032
|
+
}
|
|
1033
|
+
for (const [id, paths] of state.coverage) {
|
|
1034
|
+
const incoming = fromPaths(paths);
|
|
1035
|
+
const previous = coverage.get(id);
|
|
1036
|
+
if (previous && mode === "preserve-existing") union(previous, incoming);
|
|
1037
|
+
else coverage.set(id, incoming);
|
|
1038
|
+
}
|
|
1039
|
+
for (const [key, list] of state.lists) {
|
|
1040
|
+
const previous = lists.get(key);
|
|
1041
|
+
if (mode === "replace" || !previous) lists.set(key, previous && areHydrationValuesEqual(previous, list) ? previous : list);
|
|
1042
|
+
}
|
|
1043
|
+
this.coverage = coverage;
|
|
1044
|
+
this.lists = lists;
|
|
1045
|
+
this.records = records;
|
|
1046
|
+
this.rebuildIndexes();
|
|
1047
|
+
const changedRecordIds = /* @__PURE__ */ new Set();
|
|
1048
|
+
for (const id of new Set([...previousRecords.keys(), ...records.keys()])) if (!areHydrationValuesEqual(previousRecords.get(id), records.get(id)) || !areMasksEqual(previousCoverage.get(id), coverage.get(id))) changedRecordIds.add(id);
|
|
1049
|
+
const changedListKeys = /* @__PURE__ */ new Set();
|
|
1050
|
+
for (const key of new Set([...previousLists.keys(), ...lists.keys()])) if (!areHydrationValuesEqual(previousLists.get(key), lists.get(key))) changedListKeys.add(key);
|
|
1051
|
+
const notify = () => {
|
|
1052
|
+
for (const id of changedRecordIds) this.notify(id);
|
|
1053
|
+
for (const key of changedListKeys) this.notifyListSubscribers(key);
|
|
1054
|
+
};
|
|
1055
|
+
if (!options.deferNotifications) notify();
|
|
1056
|
+
return notify;
|
|
1057
|
+
}
|
|
1058
|
+
rebuildIndexes() {
|
|
1059
|
+
this.listKeysByOwnerField.clear();
|
|
1060
|
+
this.listKeysByReferencedEntity.clear();
|
|
1061
|
+
this.recordReferenceFields.clear();
|
|
1062
|
+
this.recordReferencesByTarget.clear();
|
|
1063
|
+
for (const [id, record] of this.records) this.addRecordReferenceIndexes(id, record);
|
|
1064
|
+
for (const [key, list] of this.lists) this.addListIndexes(key, list);
|
|
1065
|
+
}
|
|
742
1066
|
read(id) {
|
|
743
1067
|
return this.records.get(id);
|
|
744
1068
|
}
|
|
@@ -1337,6 +1661,14 @@ const getId = (record) => {
|
|
|
1337
1661
|
};
|
|
1338
1662
|
const emptySet = /* @__PURE__ */ new Set();
|
|
1339
1663
|
const emptyDispose = () => void 0;
|
|
1664
|
+
const compareStrings = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
1665
|
+
const getDefaultHydrationScope = (roots, types) => JSON.stringify({
|
|
1666
|
+
roots: Object.entries(roots ?? {}).map(([name, root]) => [name, root.type]).sort(([left], [right]) => compareStrings(left, right)),
|
|
1667
|
+
types: types.map(({ fields, type }) => ({
|
|
1668
|
+
fields: Object.entries(fields ?? {}).map(([field, descriptor]) => [field, descriptor]).sort(([left], [right]) => compareStrings(left, right)),
|
|
1669
|
+
type
|
|
1670
|
+
})).sort((left, right) => compareStrings(left.type, right.type))
|
|
1671
|
+
});
|
|
1340
1672
|
const dropCanonicalListIds = (listIds, ids) => {
|
|
1341
1673
|
if (!listIds || listIds.length === 0) return;
|
|
1342
1674
|
const canonicalIds = new Set(ids);
|
|
@@ -1477,6 +1809,7 @@ var FateClient = class {
|
|
|
1477
1809
|
this.stalledRequests = /* @__PURE__ */ new Set();
|
|
1478
1810
|
this.gcPending = false;
|
|
1479
1811
|
this.gcScheduled = false;
|
|
1812
|
+
this.pendingNetworkRequests = 0;
|
|
1480
1813
|
this.store = new Store();
|
|
1481
1814
|
this.viewDataCache = new ViewDataCache();
|
|
1482
1815
|
this.liveConnectionInvalidationToken = 0;
|
|
@@ -1485,6 +1818,10 @@ var FateClient = class {
|
|
|
1485
1818
|
this.mutations = Object.create(null);
|
|
1486
1819
|
this.onLiveError = options.onLiveError;
|
|
1487
1820
|
this.operationLifetime = new OperationLifetime(options.gcReleaseBufferSize ?? 10);
|
|
1821
|
+
this.hydrationLimits = resolveHydrationLimits(options.hydrationLimits);
|
|
1822
|
+
const hydrationScope = options.hydrationScope ?? getDefaultHydrationScope(options.roots, options.types);
|
|
1823
|
+
if (typeof hydrationScope !== "string" || hydrationScope.length === 0) throw new Error(`fate: Hydration scope must be a non-empty string.`);
|
|
1824
|
+
this.hydrationScope = hydrationScope;
|
|
1488
1825
|
this.roots = options.roots;
|
|
1489
1826
|
this.transport = options.transport;
|
|
1490
1827
|
this.types = new Map(options.types.map((entity) => [entity.type, {
|
|
@@ -1529,6 +1866,56 @@ var FateClient = class {
|
|
|
1529
1866
|
}
|
|
1530
1867
|
}
|
|
1531
1868
|
}
|
|
1869
|
+
/**
|
|
1870
|
+
* Exports the durable normalized cache as an opaque JSON-safe value for SSR
|
|
1871
|
+
* transfer or persistence. Runtime requests, subscriptions, retainers, and
|
|
1872
|
+
* optimistic state are intentionally excluded.
|
|
1873
|
+
*/
|
|
1874
|
+
dehydrate() {
|
|
1875
|
+
if (this.hasActiveOptimisticUpdates()) throw new Error(`fate: Cannot dehydrate while optimistic updates are active.`);
|
|
1876
|
+
if (this.hasPendingRequests()) throw new Error(`fate: Cannot dehydrate while requests are pending.`);
|
|
1877
|
+
return {
|
|
1878
|
+
data: encodeHydrationValue({
|
|
1879
|
+
rootLists: [...this.rootLists].map(([type, keys]) => [type, [...keys]]),
|
|
1880
|
+
rootRequests: [...this.rootRequests],
|
|
1881
|
+
store: this.store.dehydrate()
|
|
1882
|
+
}, this.hydrationLimits),
|
|
1883
|
+
scope: this.hydrationScope,
|
|
1884
|
+
version: 1
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* Restores a durable normalized cache snapshot without restoring runtime
|
|
1889
|
+
* request handles, retainers, subscriptions, or optimistic update state.
|
|
1890
|
+
* Hydrate before rendering SSR content. Replaying the same snapshot is safe.
|
|
1891
|
+
*/
|
|
1892
|
+
hydrate(state, options = {}) {
|
|
1893
|
+
if (!state || typeof state !== "object" || state.version !== 1) throw new Error(`fate: Unsupported hydration state version.`);
|
|
1894
|
+
if (state.scope !== this.hydrationScope) throw new Error(`fate: Hydration state scope does not match this client.`);
|
|
1895
|
+
if (this.hasActiveOptimisticUpdates()) throw new Error(`fate: Cannot hydrate while optimistic updates are active.`);
|
|
1896
|
+
if (this.hasPendingRequests()) throw new Error(`fate: Cannot hydrate while requests are pending.`);
|
|
1897
|
+
const mode = options.merge ?? "preserve-existing";
|
|
1898
|
+
if (mode !== "preserve-existing" && mode !== "replace") throw new Error(`fate: Unsupported hydration merge mode.`);
|
|
1899
|
+
const decoded = decodeClientHydrationState(state.data, this.hydrationLimits);
|
|
1900
|
+
const notify = this.store.hydrate(decoded.store, mode, { deferNotifications: true });
|
|
1901
|
+
if (mode === "replace") {
|
|
1902
|
+
this.rootLists.clear();
|
|
1903
|
+
this.rootRequests.clear();
|
|
1904
|
+
this.stableRefs.clear();
|
|
1905
|
+
}
|
|
1906
|
+
for (const [type, incomingKeys] of decoded.rootLists) {
|
|
1907
|
+
let keys = this.rootLists.get(type);
|
|
1908
|
+
if (!keys) {
|
|
1909
|
+
keys = /* @__PURE__ */ new Set();
|
|
1910
|
+
this.rootLists.set(type, keys);
|
|
1911
|
+
}
|
|
1912
|
+
for (const key of incomingKeys) keys.add(key);
|
|
1913
|
+
}
|
|
1914
|
+
for (const [key, entityId] of decoded.rootRequests) if (mode === "replace" || !this.rootRequests.has(key)) this.rootRequests.set(key, entityId);
|
|
1915
|
+
this.stalledRequests.clear();
|
|
1916
|
+
this.viewDataCache.clear();
|
|
1917
|
+
notify();
|
|
1918
|
+
}
|
|
1532
1919
|
registerRootList(type, key) {
|
|
1533
1920
|
let lists = this.rootLists.get(type);
|
|
1534
1921
|
if (!lists) {
|
|
@@ -1597,7 +1984,7 @@ var FateClient = class {
|
|
|
1597
1984
|
...baseRecord,
|
|
1598
1985
|
args: argsPayload
|
|
1599
1986
|
} : argsPayload ? { args: argsPayload } : input;
|
|
1600
|
-
return await this.transport.mutate(key, requestInput, select);
|
|
1987
|
+
return await this.trackPendingRequest(() => this.transport.mutate(key, requestInput, select));
|
|
1601
1988
|
}
|
|
1602
1989
|
write(type, data, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
|
|
1603
1990
|
return this.writeEntity(type, data, select, snapshots, plan, pathPrefix, blockedMask, insert, listSnapshots);
|
|
@@ -1690,13 +2077,18 @@ var FateClient = class {
|
|
|
1690
2077
|
if (this.stalledRequests.has(key)) return resolveSnapshot();
|
|
1691
2078
|
const pendingPromise = this.pending.get(key) || null;
|
|
1692
2079
|
if (pendingPromise) return pendingPromise;
|
|
1693
|
-
const promise = this.
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
2080
|
+
const promise = this.trackPendingRequest(async () => {
|
|
2081
|
+
await this.fetchByIdAndNormalize(type, [id], missing, plan);
|
|
2082
|
+
try {
|
|
2083
|
+
if (this.store.missingForSelection(entityId, selectedPaths).size > 0) {
|
|
2084
|
+
this.stalledRequests.add(key);
|
|
2085
|
+
return resolveSnapshot();
|
|
2086
|
+
}
|
|
2087
|
+
this.stalledRequests.delete(key);
|
|
2088
|
+
return this.readView(view, ref);
|
|
2089
|
+
} finally {
|
|
2090
|
+
this.pending.delete(key);
|
|
1697
2091
|
}
|
|
1698
|
-
this.stalledRequests.delete(key);
|
|
1699
|
-
return this.readView(view, ref);
|
|
1700
2092
|
});
|
|
1701
2093
|
this.pending.set(key, promise);
|
|
1702
2094
|
return promise;
|
|
@@ -2134,6 +2526,9 @@ var FateClient = class {
|
|
|
2134
2526
|
return filtered;
|
|
2135
2527
|
}
|
|
2136
2528
|
async loadConnection(view, connection, args, options = {}) {
|
|
2529
|
+
return await this.trackPendingRequest(() => this.loadConnectionInternal(view, connection, args, options));
|
|
2530
|
+
}
|
|
2531
|
+
async loadConnectionInternal(view, connection, args, options = {}) {
|
|
2137
2532
|
const direction = options.direction ?? "forward";
|
|
2138
2533
|
if (connection.root) {
|
|
2139
2534
|
if (!this.transport.fetchList) throw new Error(`fate: transport does not support list fetching. Please add support for 'fetchList' in your transport.`);
|
|
@@ -2379,6 +2774,17 @@ var FateClient = class {
|
|
|
2379
2774
|
hasActiveOptimisticUpdates() {
|
|
2380
2775
|
return this.optimisticMasks.size > 0 || this.optimisticByEntity.size > 0 || this.pendingOptimisticMutations.size > 0;
|
|
2381
2776
|
}
|
|
2777
|
+
hasPendingRequests() {
|
|
2778
|
+
return this.pendingNetworkRequests > 0;
|
|
2779
|
+
}
|
|
2780
|
+
async trackPendingRequest(request) {
|
|
2781
|
+
this.pendingNetworkRequests += 1;
|
|
2782
|
+
try {
|
|
2783
|
+
return await request();
|
|
2784
|
+
} finally {
|
|
2785
|
+
this.pendingNetworkRequests -= 1;
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2382
2788
|
runPendingGarbageCollection() {
|
|
2383
2789
|
if (this.gcPending && !this.hasActiveOptimisticUpdates()) this.gc();
|
|
2384
2790
|
}
|
|
@@ -2531,35 +2937,41 @@ var FateClient = class {
|
|
|
2531
2937
|
}
|
|
2532
2938
|
async fetchByIdAndNormalize(type, ids, select, plan, prefix = null) {
|
|
2533
2939
|
const resolvedArgs = resolvedArgsFromPlan(plan);
|
|
2534
|
-
|
|
2535
|
-
|
|
2940
|
+
await this.trackPendingRequest(async () => {
|
|
2941
|
+
const records = await this.transport.fetchById(type, ids, select, resolvedArgs);
|
|
2942
|
+
for (const record of records) this.writeEntity(type, record, select, void 0, plan, prefix);
|
|
2943
|
+
});
|
|
2536
2944
|
}
|
|
2537
2945
|
async fetchQueryAndNormalize(item) {
|
|
2538
2946
|
if (!this.transport.fetchQuery) throw new Error(`fate: transport does not support queries. Please add support for 'fetchQuery' in your transport for '${item.name}'.`);
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2947
|
+
await this.trackPendingRequest(async () => {
|
|
2948
|
+
const record = await this.transport.fetchQuery(item.name, item.plan.paths, item.argsPayload);
|
|
2949
|
+
if (!record || typeof record !== "object") {
|
|
2950
|
+
this.rootRequests.set(item.queryKey, null);
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
const entityId = this.writeEntity(item.type, record, item.plan.paths, void 0, item.plan);
|
|
2954
|
+
this.rootRequests.set(item.queryKey, entityId);
|
|
2955
|
+
});
|
|
2546
2956
|
}
|
|
2547
2957
|
async fetchListAndNormalize(item) {
|
|
2548
2958
|
if (!this.transport.fetchList) throw new Error(`fate: 'transport.fetchList' is not configured but request includes a list for key '${item.name}'.`);
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2959
|
+
await this.trackPendingRequest(async () => {
|
|
2960
|
+
const { items, pagination } = await this.transport.fetchList(item.name, item.plan.paths, item.argsPayload);
|
|
2961
|
+
const ids = [];
|
|
2962
|
+
const cursors = [];
|
|
2963
|
+
for (const entry of items) {
|
|
2964
|
+
const id = this.writeEntity(item.type, entry.node, item.plan.paths, void 0, item.plan);
|
|
2965
|
+
ids.push(id);
|
|
2966
|
+
cursors.push(entry.cursor);
|
|
2967
|
+
}
|
|
2968
|
+
if (!filterConnectionArgs(item.argsPayload)) this.registerRootList(item.type, item.listKey);
|
|
2969
|
+
const previous = this.store.getListState(item.listKey);
|
|
2970
|
+
this.store.setList(item.listKey, this.mergeListState(previous, ids, cursors, pagination, {
|
|
2971
|
+
...getPaginationMergeInfo(item.argsPayload),
|
|
2972
|
+
replace: true
|
|
2973
|
+
}));
|
|
2974
|
+
});
|
|
2563
2975
|
}
|
|
2564
2976
|
writeEntity(type, record, select, snapshots, plan, pathPrefix = null, blockedMask, insert, listSnapshots) {
|
|
2565
2977
|
const config = this.types.get(type);
|
|
@@ -3396,4 +3808,4 @@ function clientRoot(type) {
|
|
|
3396
3808
|
});
|
|
3397
3809
|
}
|
|
3398
3810
|
//#endregion
|
|
3399
|
-
export { ConnectionTag, FateClient, clientRoot, createClient, createHTTPTransport, createTRPCTransport, getListEntries, getSelectionPlan, isRecord, isViewTag, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, mutation, toEntityId, view };
|
|
3811
|
+
export { ConnectionTag, FateClient, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, getListEntries, getSelectionPlan, graphqlMutation, isRecord, isViewTag, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, mutation, toEntityId, view };
|