@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
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
/** Internal marker added to objects that represent a view payload. */
|
|
3
|
+
const ViewKind = Symbol("__fate__view");
|
|
4
|
+
/** Symbol used to attach the set of view tags that were spread into a ref or masked record. */
|
|
5
|
+
const ViewsTag = Symbol("__fate__views");
|
|
6
|
+
/** Symbol used to brand a value as a node reference inside the cache. */
|
|
7
|
+
const NodeRefTag = Symbol("__fate__node-ref");
|
|
8
|
+
/** Symbol attached to connection results so pagination helpers can find their metadata. */
|
|
9
|
+
const ConnectionTag = Symbol("__fate__connection");
|
|
10
|
+
/** Symbol used to mark a selection as deferred. */
|
|
11
|
+
const DeferTag = Symbol("__fate__defer");
|
|
12
|
+
/** Symbol attached to deferred values returned from masked views. */
|
|
13
|
+
const DeferredTag = Symbol("__fate__deferred");
|
|
14
|
+
const viewTag = "__fate-view__";
|
|
15
|
+
/** Generates a stable view tag for a view definition. */
|
|
16
|
+
function getViewTag(id) {
|
|
17
|
+
return `${viewTag}${id}`;
|
|
18
|
+
}
|
|
19
|
+
/** Determines whether a property key is a fate view tag. */
|
|
20
|
+
function isViewTag(key) {
|
|
21
|
+
return key.startsWith(viewTag);
|
|
22
|
+
}
|
|
23
|
+
/** Indicates whether a request item represents an explicit node ID. */
|
|
24
|
+
function isNodeItem(item) {
|
|
25
|
+
return "id" in item;
|
|
26
|
+
}
|
|
27
|
+
/** Indicates whether a request item represents explicit node IDs. */
|
|
28
|
+
function isNodesItem(item) {
|
|
29
|
+
return "ids" in item;
|
|
30
|
+
}
|
|
31
|
+
/** Indicates whether a request item represents a root query. */
|
|
32
|
+
function isQueryItem(item) {
|
|
33
|
+
return "view" in item && !("id" in item) && !("ids" in item);
|
|
34
|
+
}
|
|
35
|
+
/** Brand used on root definitions to mark their identity in the d.ts output. */
|
|
36
|
+
const RootKind = "__fate__root";
|
|
37
|
+
/** Brand used on mutation definitions to mark their identity in the d.ts output. */
|
|
38
|
+
const MutationKind = "__fate__mutation";
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/node-ref.ts
|
|
41
|
+
function createNodeRef(id) {
|
|
42
|
+
const ref = {};
|
|
43
|
+
Object.defineProperty(ref, NodeRefTag, {
|
|
44
|
+
configurable: false,
|
|
45
|
+
enumerable: false,
|
|
46
|
+
value: id,
|
|
47
|
+
writable: false
|
|
48
|
+
});
|
|
49
|
+
return Object.freeze(ref);
|
|
50
|
+
}
|
|
51
|
+
function isNodeRef(value) {
|
|
52
|
+
return !value || typeof value !== "object" ? false : NodeRefTag in value;
|
|
53
|
+
}
|
|
54
|
+
function getNodeRefId(ref) {
|
|
55
|
+
return ref[NodeRefTag];
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/hydration.ts
|
|
59
|
+
const maxDepth = 64;
|
|
60
|
+
const defaultHydrationLimits = {
|
|
61
|
+
maxCollectionLength: 1e5,
|
|
62
|
+
maxNodes: 25e4,
|
|
63
|
+
maxStringLength: 1e6
|
|
64
|
+
};
|
|
65
|
+
const resolveHydrationLimits = (limits) => {
|
|
66
|
+
const result = {
|
|
67
|
+
...defaultHydrationLimits,
|
|
68
|
+
...limits
|
|
69
|
+
};
|
|
70
|
+
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.`);
|
|
71
|
+
return result;
|
|
72
|
+
};
|
|
73
|
+
const error = (path, message) => /* @__PURE__ */ new Error(`fate: Cannot serialize hydration value at '${path}': ${message}`);
|
|
74
|
+
const isPlainObject = (value) => {
|
|
75
|
+
const prototype = Object.getPrototypeOf(value);
|
|
76
|
+
return prototype === Object.prototype || prototype === null;
|
|
77
|
+
};
|
|
78
|
+
const isDenseArray = (value) => {
|
|
79
|
+
for (let index = 0; index < value.length; index += 1) if (!Object.hasOwn(value, index)) return false;
|
|
80
|
+
return true;
|
|
81
|
+
};
|
|
82
|
+
const isDate = (value) => Object.prototype.toString.call(value) === "[object Date]" && typeof value.getTime === "function" && typeof value.toISOString === "function";
|
|
83
|
+
const encodeHydrationValue = (value, limits) => {
|
|
84
|
+
const resolvedLimits = resolveHydrationLimits(limits);
|
|
85
|
+
const seen = /* @__PURE__ */ new Set();
|
|
86
|
+
let nodes = 0;
|
|
87
|
+
const encode = (entry, path, depth) => {
|
|
88
|
+
nodes += 1;
|
|
89
|
+
if (nodes > resolvedLimits.maxNodes) throw error(path, `Maximum node count of ${String(resolvedLimits.maxNodes)} exceeded.`);
|
|
90
|
+
if (depth > maxDepth) throw error(path, `Maximum depth of ${String(maxDepth)} exceeded.`);
|
|
91
|
+
if (entry === null) return ["null"];
|
|
92
|
+
const type = typeof entry;
|
|
93
|
+
if (typeof entry === "string" || typeof entry === "boolean") {
|
|
94
|
+
if (typeof entry === "string" && entry.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
95
|
+
return [typeof entry, entry];
|
|
96
|
+
}
|
|
97
|
+
if (type === "number") {
|
|
98
|
+
if (Number.isNaN(entry)) return ["number", "NaN"];
|
|
99
|
+
if (entry === Infinity) return ["number", "Infinity"];
|
|
100
|
+
if (entry === -Infinity) return ["number", "-Infinity"];
|
|
101
|
+
if (Object.is(entry, -0)) return ["number", "-0"];
|
|
102
|
+
return ["number", entry];
|
|
103
|
+
}
|
|
104
|
+
if (type === "undefined") return ["undefined"];
|
|
105
|
+
if (type === "bigint") {
|
|
106
|
+
const bigint = String(entry);
|
|
107
|
+
if (bigint.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
108
|
+
return ["bigint", bigint];
|
|
109
|
+
}
|
|
110
|
+
if (type === "function" || type === "symbol") throw error(path, `Received unsupported '${type}' value.`);
|
|
111
|
+
if (!entry || typeof entry !== "object") throw error(path, `Received unsupported '${type}' value.`);
|
|
112
|
+
if (isNodeRef(entry)) {
|
|
113
|
+
const id = getNodeRefId(entry);
|
|
114
|
+
if (id.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
115
|
+
return ["ref", id];
|
|
116
|
+
}
|
|
117
|
+
if (isDate(entry)) {
|
|
118
|
+
if (Number.isNaN(entry.getTime())) throw error(path, `Received an invalid Date.`);
|
|
119
|
+
return ["date", entry.toISOString()];
|
|
120
|
+
}
|
|
121
|
+
if (seen.has(entry)) throw error(path, `Circular references are not supported.`);
|
|
122
|
+
seen.add(entry);
|
|
123
|
+
try {
|
|
124
|
+
if (Array.isArray(entry)) {
|
|
125
|
+
if (!isDenseArray(entry)) throw error(path, `Sparse arrays are not supported.`);
|
|
126
|
+
if (entry.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
|
|
127
|
+
return ["array", entry.map((item, index) => encode(item, `${path}[${String(index)}]`, depth + 1))];
|
|
128
|
+
}
|
|
129
|
+
if (!isPlainObject(entry)) throw error(path, `Received unsupported '${entry.constructor?.name ?? "object"}' value.`);
|
|
130
|
+
if (Object.getOwnPropertySymbols(entry).length > 0) throw error(path, `Received an unsupported symbol-keyed property.`);
|
|
131
|
+
const entries = Object.entries(entry);
|
|
132
|
+
if (entries.length > resolvedLimits.maxCollectionLength) throw error(path, `Maximum collection length of ${String(resolvedLimits.maxCollectionLength)} exceeded.`);
|
|
133
|
+
return ["object", entries.map(([key, item]) => {
|
|
134
|
+
if (key.length > resolvedLimits.maxStringLength) throw error(path, `Maximum string length of ${String(resolvedLimits.maxStringLength)} exceeded.`);
|
|
135
|
+
return [key, encode(item, `${path}.${key}`, depth + 1)];
|
|
136
|
+
})];
|
|
137
|
+
} finally {
|
|
138
|
+
seen.delete(entry);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
return encode(value, "data", 0);
|
|
142
|
+
};
|
|
143
|
+
const invalidPayload = (path) => /* @__PURE__ */ new Error(`fate: Invalid hydration payload at '${path}'.`);
|
|
144
|
+
const tagLengths = {
|
|
145
|
+
array: 2,
|
|
146
|
+
bigint: 2,
|
|
147
|
+
boolean: 2,
|
|
148
|
+
date: 2,
|
|
149
|
+
null: 1,
|
|
150
|
+
number: 2,
|
|
151
|
+
object: 2,
|
|
152
|
+
ref: 2,
|
|
153
|
+
string: 2,
|
|
154
|
+
undefined: 1
|
|
155
|
+
};
|
|
156
|
+
const decodeHydrationValue = (value, limits) => {
|
|
157
|
+
const resolvedLimits = resolveHydrationLimits(limits);
|
|
158
|
+
let nodes = 0;
|
|
159
|
+
const decode = (entry, path, depth) => {
|
|
160
|
+
nodes += 1;
|
|
161
|
+
if (nodes > resolvedLimits.maxNodes || depth > maxDepth || !Array.isArray(entry) || typeof entry[0] !== "string") throw invalidPayload(path);
|
|
162
|
+
const [tag, payload] = entry;
|
|
163
|
+
if (entry.length !== tagLengths[tag]) throw invalidPayload(path);
|
|
164
|
+
switch (tag) {
|
|
165
|
+
case "null": return null;
|
|
166
|
+
case "undefined": return;
|
|
167
|
+
case "string":
|
|
168
|
+
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
169
|
+
return payload;
|
|
170
|
+
case "boolean":
|
|
171
|
+
if (typeof payload !== "boolean") throw invalidPayload(path);
|
|
172
|
+
return payload;
|
|
173
|
+
case "number":
|
|
174
|
+
if (typeof payload === "number") {
|
|
175
|
+
if (!Number.isFinite(payload) || Object.is(payload, -0)) throw invalidPayload(path);
|
|
176
|
+
return payload;
|
|
177
|
+
}
|
|
178
|
+
if (payload === "NaN") return NaN;
|
|
179
|
+
if (payload === "Infinity") return Infinity;
|
|
180
|
+
if (payload === "-Infinity") return -Infinity;
|
|
181
|
+
if (payload === "-0") return -0;
|
|
182
|
+
throw invalidPayload(path);
|
|
183
|
+
case "bigint":
|
|
184
|
+
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
185
|
+
try {
|
|
186
|
+
const bigint = BigInt(payload);
|
|
187
|
+
if (String(bigint) !== payload) throw invalidPayload(path);
|
|
188
|
+
return bigint;
|
|
189
|
+
} catch {
|
|
190
|
+
throw invalidPayload(path);
|
|
191
|
+
}
|
|
192
|
+
case "date": {
|
|
193
|
+
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
194
|
+
const date = new Date(payload);
|
|
195
|
+
if (Number.isNaN(date.getTime()) || date.toISOString() !== payload) throw invalidPayload(path);
|
|
196
|
+
return date;
|
|
197
|
+
}
|
|
198
|
+
case "ref":
|
|
199
|
+
if (typeof payload !== "string" || payload.length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
200
|
+
return createNodeRef(payload);
|
|
201
|
+
case "array":
|
|
202
|
+
if (!Array.isArray(payload) || !isDenseArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
|
|
203
|
+
return payload.map((item, index) => decode(item, `${path}[${String(index)}]`, depth + 1));
|
|
204
|
+
case "object": {
|
|
205
|
+
if (!Array.isArray(payload) || payload.length > resolvedLimits.maxCollectionLength) throw invalidPayload(path);
|
|
206
|
+
const result = Object.create(null);
|
|
207
|
+
for (const item of payload) {
|
|
208
|
+
if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== "string" || item[0].length > resolvedLimits.maxStringLength) throw invalidPayload(path);
|
|
209
|
+
if (item[0] in result) throw invalidPayload(path);
|
|
210
|
+
result[item[0]] = decode(item[1], `${path}.${item[0]}`, depth + 1);
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
default: throw invalidPayload(path);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
return decode(value, "data", 0);
|
|
218
|
+
};
|
|
219
|
+
const isRecord = (value) => {
|
|
220
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || isNodeRef(value)) return false;
|
|
221
|
+
const prototype = Object.getPrototypeOf(value);
|
|
222
|
+
return prototype === Object.prototype || prototype === null;
|
|
223
|
+
};
|
|
224
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
225
|
+
const hasDuplicates = (values) => new Set(values).size !== values.length;
|
|
226
|
+
const isUniqueStringArray = (value) => isStringArray(value) && !hasDuplicates(value);
|
|
227
|
+
const hasOnlyKeys = (value, keys) => Object.keys(value).every((key) => keys.has(key));
|
|
228
|
+
const listKeys = /* @__PURE__ */ new Set([
|
|
229
|
+
"backwardPageLimit",
|
|
230
|
+
"cursors",
|
|
231
|
+
"forwardPageLimit",
|
|
232
|
+
"ids",
|
|
233
|
+
"liveAfterIds",
|
|
234
|
+
"liveBeforeIds",
|
|
235
|
+
"pagination",
|
|
236
|
+
"pendingAfterIds",
|
|
237
|
+
"pendingBeforeIds"
|
|
238
|
+
]);
|
|
239
|
+
const paginationKeys = /* @__PURE__ */ new Set([
|
|
240
|
+
"hasNext",
|
|
241
|
+
"hasPrevious",
|
|
242
|
+
"nextCursor",
|
|
243
|
+
"previousCursor"
|
|
244
|
+
]);
|
|
245
|
+
const clientStateKeys = /* @__PURE__ */ new Set([
|
|
246
|
+
"rootLists",
|
|
247
|
+
"rootRequests",
|
|
248
|
+
"store"
|
|
249
|
+
]);
|
|
250
|
+
const storeStateKeys = /* @__PURE__ */ new Set([
|
|
251
|
+
"coverage",
|
|
252
|
+
"lists",
|
|
253
|
+
"records"
|
|
254
|
+
]);
|
|
255
|
+
const isEntry = (value) => Array.isArray(value) && value.length === 2 && typeof value[0] === "string";
|
|
256
|
+
const isOptionalLimit = (value) => value === void 0 || typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
257
|
+
const isPagination = (value) => value === void 0 || isRecord(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");
|
|
258
|
+
const isList = (value) => {
|
|
259
|
+
if (!isRecord(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;
|
|
260
|
+
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));
|
|
261
|
+
};
|
|
262
|
+
const isFieldPath = (value) => value.length > 0 && value.split(".").every((segment) => segment.length > 0);
|
|
263
|
+
const isFieldPathArray = (value) => isUniqueStringArray(value) && value.every(isFieldPath);
|
|
264
|
+
const hasDuplicateEntries = (entries) => hasDuplicates(entries.map((entry) => entry[0]));
|
|
265
|
+
const decodeClientHydrationState = (value, limits) => {
|
|
266
|
+
const decoded = decodeHydrationValue(value, limits);
|
|
267
|
+
if (!isRecord(decoded) || !hasOnlyKeys(decoded, clientStateKeys) || !Array.isArray(decoded.rootLists) || !Array.isArray(decoded.rootRequests) || !isRecord(decoded.store) || !hasOnlyKeys(decoded.store, storeStateKeys) || !Array.isArray(decoded.store.coverage) || !Array.isArray(decoded.store.lists) || !Array.isArray(decoded.store.records)) throw invalidPayload("data");
|
|
268
|
+
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(entry[1]))) throw invalidPayload("data");
|
|
269
|
+
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");
|
|
270
|
+
return decoded;
|
|
271
|
+
};
|
|
272
|
+
//#endregion
|
|
273
|
+
export { isNodesItem as _, createNodeRef as a, ConnectionTag as c, MutationKind as d, RootKind as f, isNodeItem as g, getViewTag as h, resolveHydrationLimits as i, DeferTag as l, ViewsTag as m, decodeHydrationValue as n, getNodeRefId as o, ViewKind as p, encodeHydrationValue as r, isNodeRef as s, decodeClientHydrationState as t, DeferredTag as u, isQueryItem as v, isViewTag as y };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { r as MutationIdentity } from "./transport-DqjhjLfC.mjs";
|
|
2
|
+
//#region src/server/idempotency-types.d.ts
|
|
3
|
+
/** Server enforcement for a durable mutation. The resolver must use the supplied context. */
|
|
4
|
+
interface MutationIdempotency<Context> {
|
|
5
|
+
execute<Result>(options: {
|
|
6
|
+
ctx: Context;
|
|
7
|
+
identity: MutationIdentity;
|
|
8
|
+
input: unknown;
|
|
9
|
+
name: string;
|
|
10
|
+
resolve(ctx: Context): Promise<Result>;
|
|
11
|
+
select: Array<string>;
|
|
12
|
+
}): Promise<Result>;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { MutationIdempotency as t };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { h as encodeHydrationValue } from "./transport-DqjhjLfC.mjs";
|
|
2
|
+
import { t as MutationIdempotency } from "./idempotency-types-CqA-BFpn.mjs";
|
|
3
|
+
//#region src/idempotency.d.ts
|
|
4
|
+
type MutationReceipt = Readonly<{
|
|
5
|
+
fingerprint: string;
|
|
6
|
+
result: ReturnType<typeof encodeHydrationValue>;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* All methods and the resolver run in ONE application database transaction.
|
|
10
|
+
* The adapter must serialize concurrent calls for the same scope and ID,
|
|
11
|
+
* including calls from different processes. Receipts must not expire.
|
|
12
|
+
*/
|
|
13
|
+
interface IdempotencyStore<Context> {
|
|
14
|
+
transaction<T>(context: Context, scope: string, id: string, run: (transaction: {
|
|
15
|
+
context: Context;
|
|
16
|
+
read(): Promise<MutationReceipt | undefined>;
|
|
17
|
+
write(receipt: MutationReceipt): Promise<void>;
|
|
18
|
+
}) => Promise<T>): Promise<T>;
|
|
19
|
+
}
|
|
20
|
+
/** Enforces durable deduplication using the application's own transaction adapter. */
|
|
21
|
+
declare function createMutationIdempotency<Context>(options: {
|
|
22
|
+
scope(context: Context): string | Promise<string>;
|
|
23
|
+
store: IdempotencyStore<Context>;
|
|
24
|
+
}): MutationIdempotency<Context>;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { IdempotencyStore, type MutationIdempotency, MutationReceipt, createMutationIdempotency };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { n as decodeHydrationValue, r as encodeHydrationValue } from "./hydration-DPZRJgnu.mjs";
|
|
2
|
+
import { t as FateRequestError } from "./protocol-DS5sf6sd.mjs";
|
|
3
|
+
import { t as sortObjectKeys } from "./sortObjectKeys-yhuitEQZ.mjs";
|
|
4
|
+
//#region src/idempotency.ts
|
|
5
|
+
/** Enforces durable deduplication using the application's own transaction adapter. */
|
|
6
|
+
function createMutationIdempotency(options) {
|
|
7
|
+
return { async execute({ ctx, identity, input, name, resolve, select }) {
|
|
8
|
+
const scope = await options.scope(ctx);
|
|
9
|
+
if (!scope || scope !== identity.scope) throw new FateRequestError("FORBIDDEN", "Mutation identity does not match the authenticated persistence scope.");
|
|
10
|
+
if (!identity.id || identity.id.length > 256 || identity.replayOnly !== void 0 && identity.replayOnly !== true) throw new FateRequestError("BAD_REQUEST", "Invalid mutation identity.");
|
|
11
|
+
const fingerprint = JSON.stringify(encodeHydrationValue(sortObjectKeys({
|
|
12
|
+
input,
|
|
13
|
+
name,
|
|
14
|
+
select: [...select].sort()
|
|
15
|
+
})));
|
|
16
|
+
return options.store.transaction(ctx, scope, identity.id, async (transaction) => {
|
|
17
|
+
const receipt = await transaction.read();
|
|
18
|
+
if (receipt) {
|
|
19
|
+
if (receipt.fingerprint !== fingerprint) throw new FateRequestError("BAD_REQUEST", "Mutation identity was reused with different input.");
|
|
20
|
+
return decodeHydrationValue(receipt.result);
|
|
21
|
+
}
|
|
22
|
+
if (identity.replayOnly) throw new FateRequestError("NOT_FOUND", "Mutation receipt was not found.");
|
|
23
|
+
const result = await resolve(transaction.context);
|
|
24
|
+
const encoded = encodeHydrationValue(result);
|
|
25
|
+
await transaction.write({
|
|
26
|
+
fingerprint,
|
|
27
|
+
result: encoded
|
|
28
|
+
});
|
|
29
|
+
return result;
|
|
30
|
+
});
|
|
31
|
+
} };
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { createMutationIdempotency };
|
package/lib/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { a as GraphQLTransportOptions, i as GraphQLMutationOutput, n as GraphQLMutationInput, o as createGraphQLTransport, r as GraphQLMutationMap, s as graphqlMutation, t as GraphQLMutationDefinition } from "./graphqlTransport-
|
|
3
|
-
import { C as liveGlobalConnectionTopic, S as liveEntityTopic, a as FateProtocolRequest, i as FateOperation, n as FateLiveConnectionEvent, o as FateProtocolResponse, r as FateLiveEvent, t as isRecord, u as InferFateAPI, x as liveConnectionTopic } from "./record-
|
|
4
|
-
import { n as getListEntries, t as ListEntry } from "./list-
|
|
1
|
+
import { $ as ViewSnapshot, A as FateThenable, B as Pagination, C as Deferred, D as Entity, E as DeferredTag, F as MutationIdentifier, G as TypeConfig, H as RequestResult, I as MutationInput, J as ViewData, K as TypeName, L as MutationResult, M as Mask, N as MutationDefinition, O as EntityId, P as MutationEntity, Q as ViewSelection, R as MutationShape, S as DeferTag, T as DeferredSelection, U as RootDefinition, V as Request$1, W as Selection, X as ViewEntityName, Y as ViewEntity, Z as ViewRef, _ as getSelectionPlan, a as PersistenceSession, at as mutation, b as ConnectionRef, c as FateClient, d as createClient, et as ViewTag, f as FateDehydratedState, g as List, i as Persistence, it as FateMutations, j as ListItem, k as FateRoots, l as RequestMode, m as HydrationLimits, n as createTRPCTransport, nt as __FateSelectionBrand, o as PersistenceSnapshot, p as HydrateOptions, q as View, r as MutationIdentity, rt as isViewTag, s as DeferredSnapshot, t as Transport, tt as __FateEntityBrand, u as RequestOptions, v as AnyRecord, w as DeferredMetadata, x as ConnectionTag, y as ConnectionMetadata, z as NodesItem } from "./transport-DqjhjLfC.mjs";
|
|
2
|
+
import { a as GraphQLTransportOptions, i as GraphQLMutationOutput, n as GraphQLMutationInput, o as createGraphQLTransport, r as GraphQLMutationMap, s as graphqlMutation, t as GraphQLMutationDefinition } from "./graphqlTransport-DGLYyRxL.mjs";
|
|
3
|
+
import { C as liveGlobalConnectionTopic, S as liveEntityTopic, a as FateProtocolRequest, i as FateOperation, n as FateLiveConnectionEvent, o as FateProtocolResponse, r as FateLiveEvent, t as isRecord, u as InferFateAPI, x as liveConnectionTopic } from "./record-DITM4zb_.mjs";
|
|
4
|
+
import { n as getListEntries, t as ListEntry } from "./list-BRT1VtiT.mjs";
|
|
5
5
|
//#region src/defer.d.ts
|
|
6
6
|
declare function defer<S>(selection: S): DeferredSelection<S>;
|
|
7
7
|
declare const isDeferred: (value: unknown) => value is Deferred<unknown>;
|
|
@@ -84,4 +84,4 @@ type SelectionValidation<T extends Entity, S extends Selection<T>> = Exclude<key
|
|
|
84
84
|
*/
|
|
85
85
|
declare function view<T extends Entity>(): <S extends Selection<T>>(select: S & SelectionValidation<T, S>) => View<T, S>;
|
|
86
86
|
//#endregion
|
|
87
|
-
export { type ConnectionMetadata, type ConnectionRef, ConnectionTag, DeferTag, type Deferred, type DeferredMetadata, type DeferredSelection, type DeferredSnapshot, DeferredTag, type Entity, type EntityId, FateClient, type FateDehydratedState, type FateLiveConnectionEvent, type FateLiveEvent, type FateMutations, type FateOperation, type FateProtocolRequest, type FateProtocolResponse, type AnyRecord as FateRecord, type FateRoots, type FateThenable, type GraphQLMutationDefinition, type GraphQLMutationInput, type GraphQLMutationMap, type GraphQLMutationOutput, type GraphQLTransportOptions, type HydrateOptions, type HydrationLimits, type InferFateAPI, type List, type ListEntry, type ListItem, type Mask, type MutationDefinition, type MutationEntity, type MutationIdentifier, type MutationInput, type MutationResult, type NodesItem, type Pagination, type Request$1 as Request, type RequestMode, type RequestOptions, type RequestResult, type Selection, type Transport, type TypeConfig, type View, type ViewData, type ViewEntity, type ViewEntityName, type ViewRef, type ViewSelection, type ViewSnapshot, type ViewTag, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, getDeferredMetadata, getListEntries, getSelectionPlan, graphqlMutation, isDeferred, isRecord, isViewTag, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, mutation, toEntityId, view };
|
|
87
|
+
export { type ConnectionMetadata, type ConnectionRef, ConnectionTag, DeferTag, type Deferred, type DeferredMetadata, type DeferredSelection, type DeferredSnapshot, DeferredTag, type Entity, type EntityId, FateClient, type FateDehydratedState, type FateLiveConnectionEvent, type FateLiveEvent, type FateMutations, type FateOperation, type FateProtocolRequest, type FateProtocolResponse, type AnyRecord as FateRecord, type FateRoots, type FateThenable, type GraphQLMutationDefinition, type GraphQLMutationInput, type GraphQLMutationMap, type GraphQLMutationOutput, type GraphQLTransportOptions, type HydrateOptions, type HydrationLimits, type InferFateAPI, type List, type ListEntry, type ListItem, type Mask, type MutationDefinition, type MutationEntity, type MutationIdentifier, type MutationIdentity, type MutationInput, type MutationResult, type NodesItem, type Pagination, type Persistence, type PersistenceSession, type PersistenceSnapshot, type Request$1 as Request, type RequestMode, type RequestOptions, type RequestResult, type Selection, type Transport, type TypeConfig, type View, type ViewData, type ViewEntity, type ViewEntityName, type ViewRef, type ViewSelection, type ViewSnapshot, type ViewTag, clientRoot, createClient, createGraphQLTransport, createHTTPTransport, createTRPCTransport, defer, getDeferredMetadata, getListEntries, getSelectionPlan, graphqlMutation, isDeferred, isRecord, isViewTag, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, mutation, toEntityId, view };
|