@nkzw/fate 0.1.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +810 -129
- package/docs/api/classes/FateClient.md +832 -0
- package/docs/api/functions/clientRoot.md +27 -0
- package/docs/api/functions/createClient.md +21 -0
- package/docs/api/functions/createHTTPTransport.md +51 -0
- package/docs/api/functions/createTRPCTransport.md +46 -0
- package/docs/api/functions/getListEntries.md +15 -0
- package/docs/api/functions/getSelectionPlan.md +36 -0
- package/docs/api/functions/isRecord.md +15 -0
- package/docs/api/functions/isViewTag.md +17 -0
- package/docs/api/functions/liveConnectionTopic.md +19 -0
- package/docs/api/functions/liveEntityTopic.md +19 -0
- package/docs/api/functions/liveGlobalConnectionTopic.md +15 -0
- package/docs/api/functions/mutation.md +32 -0
- package/docs/api/functions/toEntityId.md +21 -0
- package/docs/api/functions/view.md +26 -0
- package/docs/api/index.md +81 -0
- package/docs/api/interfaces/FateThenable.md +31 -0
- package/docs/api/interfaces/Transport.md +199 -0
- package/docs/api/server/classes/FateRequestError.md +67 -0
- package/docs/api/server/drizzle/functions/createDrizzleFate.md +431 -0
- package/docs/api/server/drizzle/functions/createDrizzleSourceAdapter.md +21 -0
- package/docs/api/server/drizzle/functions/createDrizzleSourceRegistry.md +21 -0
- package/docs/api/server/drizzle/index.md +23 -0
- package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyConfig.md +15 -0
- package/docs/api/server/drizzle/type-aliases/DrizzleManyToManyInput.md +5 -0
- package/docs/api/server/drizzle/type-aliases/DrizzleQueryExtra.md +21 -0
- package/docs/api/server/drizzle/type-aliases/DrizzleSourceAdapter.md +337 -0
- package/docs/api/server/drizzle/type-aliases/DrizzleViewConfig.md +29 -0
- package/docs/api/server/functions/bindSourceProcedures.md +32 -0
- package/docs/api/server/functions/computed.md +43 -0
- package/docs/api/server/functions/count.md +21 -0
- package/docs/api/server/functions/createFateFetchHandler.md +21 -0
- package/docs/api/server/functions/createFateServer.md +41 -0
- package/docs/api/server/functions/createHonoFateHandler.md +21 -0
- package/docs/api/server/functions/createLiveEventBus.md +17 -0
- package/docs/api/server/functions/createNestedSourcePlan.md +103 -0
- package/docs/api/server/functions/createResolver.md +74 -0
- package/docs/api/server/functions/createSourcePlan.md +95 -0
- package/docs/api/server/functions/createSourceProcedures.md +46 -0
- package/docs/api/server/functions/dataView.md +32 -0
- package/docs/api/server/functions/field.md +15 -0
- package/docs/api/server/functions/getNestedSelection.md +19 -0
- package/docs/api/server/functions/getScopedArgs.md +21 -0
- package/docs/api/server/functions/hasNestedSelection.md +19 -0
- package/docs/api/server/functions/list.md +28 -0
- package/docs/api/server/functions/refetchSourceById.md +55 -0
- package/docs/api/server/functions/resolveSourceById.md +55 -0
- package/docs/api/server/functions/resolveSourceByIds.md +55 -0
- package/docs/api/server/functions/resolveSourceConnection.md +67 -0
- package/docs/api/server/functions/resolver.md +42 -0
- package/docs/api/server/functions/toPrismaSelect.md +25 -0
- package/docs/api/server/functions/withConnection.md +24 -0
- package/docs/api/server/index.md +109 -0
- package/docs/api/server/prisma/functions/createPrismaFate.md +435 -0
- package/docs/api/server/prisma/functions/createPrismaSourceAdapter.md +21 -0
- package/docs/api/server/prisma/functions/createPrismaSourceRegistry.md +21 -0
- package/docs/api/server/prisma/functions/prismaConnectionArgs.md +57 -0
- package/docs/api/server/prisma/index.md +23 -0
- package/docs/api/server/prisma/type-aliases/PrismaDelegate.md +59 -0
- package/docs/api/server/prisma/type-aliases/PrismaQueryExtra.md +5 -0
- package/docs/api/server/prisma/type-aliases/PrismaSourceAdapter.md +343 -0
- package/docs/api/server/prisma/type-aliases/PrismaViewConfig.md +31 -0
- package/docs/api/server/type-aliases/ComputedField.md +55 -0
- package/docs/api/server/type-aliases/ComputedSelection.md +5 -0
- package/docs/api/server/type-aliases/ConnectionItem.md +29 -0
- package/docs/api/server/type-aliases/ConnectionPagination.md +39 -0
- package/docs/api/server/type-aliases/ConnectionResult.md +29 -0
- package/docs/api/server/type-aliases/CountSelection.md +29 -0
- package/docs/api/server/type-aliases/CountWhere.md +5 -0
- package/docs/api/server/type-aliases/DataViewListOptions.md +13 -0
- package/docs/api/server/type-aliases/DataViewOrderBy.md +5 -0
- package/docs/api/server/type-aliases/DataViewOrderDirection.md +5 -0
- package/docs/api/server/type-aliases/DataViewResult.md +11 -0
- package/docs/api/server/type-aliases/Entity.md +21 -0
- package/docs/api/server/type-aliases/FateServer.md +75 -0
- package/docs/api/server/type-aliases/FateServerManifest.md +5 -0
- package/docs/api/server/type-aliases/FieldSelection.md +21 -0
- package/docs/api/server/type-aliases/LiveConnectionEventType.md +5 -0
- package/docs/api/server/type-aliases/LiveConnectionSourceEvent.md +5 -0
- package/docs/api/server/type-aliases/LiveEventBus.md +5 -0
- package/docs/api/server/type-aliases/LiveEventType.md +5 -0
- package/docs/api/server/type-aliases/LiveSourceEvent.md +5 -0
- package/docs/api/server/type-aliases/NativeFateAPI.md +47 -0
- package/docs/api/server/type-aliases/OrderDirection.md +5 -0
- package/docs/api/server/type-aliases/SourceConfig.md +55 -0
- package/docs/api/server/type-aliases/SourceDefinition.md +55 -0
- package/docs/api/server/type-aliases/SourceOrder.md +5 -0
- package/docs/api/server/type-aliases/SourceOrderField.md +21 -0
- package/docs/api/server/type-aliases/SourcePlan.md +29 -0
- package/docs/api/server/type-aliases/SourcePlanNode.md +29 -0
- package/docs/api/server/type-aliases/SourceRegistry.md +11 -0
- package/docs/api/server/type-aliases/SourceRelation.md +15 -0
- package/docs/api/server/type-aliases/SourceRelationConfig.md +23 -0
- package/docs/api/server/variables/byIdInput.md +5 -0
- package/docs/api/server/variables/connectionArgs.md +7 -0
- package/docs/api/type-aliases/ConnectionMetadata.md +7 -0
- package/docs/api/type-aliases/ConnectionRef.md +13 -0
- package/docs/api/type-aliases/Entity.md +15 -0
- package/docs/api/type-aliases/EntityId.md +7 -0
- package/docs/api/type-aliases/FateLiveConnectionEvent.md +5 -0
- package/docs/api/type-aliases/FateLiveEvent.md +5 -0
- package/docs/api/type-aliases/FateMutations.md +7 -0
- package/docs/api/type-aliases/FateOperation.md +5 -0
- package/docs/api/type-aliases/FateProtocolRequest.md +5 -0
- package/docs/api/type-aliases/FateProtocolResponse.md +5 -0
- package/docs/api/type-aliases/FateRecord.md +7 -0
- package/docs/api/type-aliases/FateRoots.md +7 -0
- package/docs/api/type-aliases/InferFateAPI.md +11 -0
- package/docs/api/type-aliases/List.md +5 -0
- package/docs/api/type-aliases/ListEntry.md +5 -0
- package/docs/api/type-aliases/ListItem.md +13 -0
- package/docs/api/type-aliases/Mask.md +15 -0
- package/docs/api/type-aliases/MutationDefinition.md +21 -0
- package/docs/api/type-aliases/MutationEntity.md +13 -0
- package/docs/api/type-aliases/MutationIdentifier.md +19 -0
- package/docs/api/type-aliases/MutationInput.md +13 -0
- package/docs/api/type-aliases/MutationResult.md +13 -0
- package/docs/api/type-aliases/NodesItem.md +13 -0
- package/docs/api/type-aliases/Pagination.md +39 -0
- package/docs/api/type-aliases/Request.md +7 -0
- package/docs/api/type-aliases/RequestMode.md +7 -0
- package/docs/api/type-aliases/RequestOptions.md +7 -0
- package/docs/api/type-aliases/RequestResult.md +18 -0
- package/docs/api/type-aliases/Selection.md +13 -0
- package/docs/api/type-aliases/Snapshot.md +7 -0
- package/docs/api/type-aliases/TypeConfig.md +41 -0
- package/docs/api/type-aliases/View.md +17 -0
- package/docs/api/type-aliases/ViewData.md +18 -0
- package/docs/api/type-aliases/ViewEntity.md +13 -0
- package/docs/api/type-aliases/ViewEntityName.md +13 -0
- package/docs/api/type-aliases/ViewRef.md +13 -0
- package/docs/api/type-aliases/ViewSelection.md +13 -0
- package/docs/api/type-aliases/ViewSnapshot.md +18 -0
- package/docs/api/type-aliases/ViewTag.md +7 -0
- package/docs/api/variables/ConnectionTag.md +7 -0
- package/docs/guide/actions.md +263 -0
- package/docs/guide/core-concepts.md +22 -0
- package/docs/guide/getting-started.md +57 -0
- package/docs/guide/list-views.md +86 -0
- package/docs/guide/live-views.md +245 -0
- package/docs/guide/requests.md +130 -0
- package/docs/guide/server-integration.md +630 -0
- package/docs/guide/views.md +278 -0
- package/docs/guide/void-integration.md +167 -0
- package/docs/index.md +4 -0
- package/lib/cli.mjs +17 -250
- package/lib/clientStub.d.mts +6 -0
- package/lib/clientStub.mjs +6 -0
- package/lib/executor-BqwHdN3n.d.mts +468 -0
- package/lib/index.d.mts +64 -2
- package/lib/index.mjs +1961 -555
- package/lib/list-4wRNSVgI.d.mts +10 -0
- package/lib/list-C_mi6GnE.mjs +16 -0
- package/lib/list.d.mts +2 -0
- package/lib/list.mjs +2 -0
- package/lib/liveTopics-DzNtJaBD.mjs +197 -0
- package/lib/{record-DnhZuvUe.mjs → record-AeZJC9fd.mjs} +1 -2
- package/lib/record-ENh92gyb.d.mts +315 -0
- package/lib/server/drizzle.d.mts +304 -0
- package/lib/server/drizzle.mjs +688 -0
- package/lib/server/prisma.d.mts +309 -0
- package/lib/server/prisma.mjs +345 -0
- package/lib/server.d.mts +82 -183
- package/lib/server.mjs +870 -416
- package/lib/sourceRouter-Bwpkr062.mjs +972 -0
- package/lib/{types-HECWvGpd.d.mts → types-Dz46PXr3.d.mts} +215 -63
- package/lib/vite.d.mts +17 -0
- package/lib/vite.mjs +593 -0
- package/package.json +44 -6
package/lib/server.mjs
CHANGED
|
@@ -1,458 +1,912 @@
|
|
|
1
|
-
import { t as isRecord
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
1
|
+
import { t as isRecord } from "./record-AeZJC9fd.mjs";
|
|
2
|
+
import { d as hashArgs, i as FateRequestError, n as liveEntityTopic, o as toProtocolError, r as liveGlobalConnectionTopic, t as liveConnectionTopic, u as filterConnectionArgs } from "./liveTopics-DzNtJaBD.mjs";
|
|
3
|
+
import { C as list, D as resolveConnection, E as connectionArgs, O as withConnection, S as isDataView, T as toPrismaSelect, _ as computed, a as refetchSourceById, b as dataView, c as resolveSourceConnection, f as createSourcePlan, h as hasNestedSelection, k as getScopedArgs, m as getNestedSelection, n as createSourceProcedures, o as resolveSourceById, r as byIdInput, s as resolveSourceByIds, t as bindSourceProcedures, u as createNestedSourcePlan, v as count, w as resolver, x as field, y as createResolver } from "./sourceRouter-Bwpkr062.mjs";
|
|
4
|
+
import { EventEmitter, on } from "node:events";
|
|
5
|
+
//#region src/server/live.ts
|
|
6
|
+
const eventName = (type, id) => `${type}:${String(id)}`;
|
|
7
|
+
const globalConnectionEventName = (procedure) => `connection:${procedure}:*`;
|
|
8
|
+
const normalizeConnectionArgs = (args) => {
|
|
9
|
+
const filtered = filterConnectionArgs(args);
|
|
10
|
+
if (!filtered) return;
|
|
11
|
+
const id = filtered.id;
|
|
12
|
+
return typeof id === "string" || typeof id === "number" ? {
|
|
13
|
+
...filtered,
|
|
14
|
+
id: String(id)
|
|
15
|
+
} : filtered;
|
|
16
16
|
};
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return isRecord(current) ? current : void 0;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Builds a Prisma `select` object from flattened selection paths and optional
|
|
33
|
-
* scoped args, always including the `id` field.
|
|
34
|
-
*/
|
|
35
|
-
function prismaSelect(paths, args$1) {
|
|
36
|
-
const allPaths = [...new Set([...paths, "id"])];
|
|
37
|
-
const select = {};
|
|
38
|
-
for (const path of allPaths) {
|
|
39
|
-
const segments = path.split(".");
|
|
40
|
-
let current = select;
|
|
41
|
-
let currentPath = "";
|
|
42
|
-
segments.forEach((segment, index) => {
|
|
43
|
-
currentPath = currentPath ? `${currentPath}.${segment}` : segment;
|
|
44
|
-
if (index === segments.length - 1) {
|
|
45
|
-
if (segment === "cursor") return;
|
|
46
|
-
current[segment] = true;
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
const existing = current[segment];
|
|
50
|
-
const relation = existing && typeof existing === "object" && existing !== null && "select" in existing ? existing : { select: {} };
|
|
51
|
-
const scopedArgs = getScopedArgs(args$1, currentPath);
|
|
52
|
-
if (scopedArgs) Object.assign(relation, toPrismaArgs(scopedArgs));
|
|
53
|
-
current[segment] = relation;
|
|
54
|
-
current = relation.select;
|
|
17
|
+
const connectionEventName = (procedure, args) => `connection:${procedure}:${hashArgs(normalizeConnectionArgs(args) ?? {})}`;
|
|
18
|
+
const mergeEvents = (emitter, names, signal) => ({ [Symbol.asyncIterator]() {
|
|
19
|
+
const queue = [];
|
|
20
|
+
let pending = null;
|
|
21
|
+
let done = false;
|
|
22
|
+
const cleanup = () => {
|
|
23
|
+
done = true;
|
|
24
|
+
for (const name of names) emitter.off(name, listener);
|
|
25
|
+
signal?.removeEventListener("abort", onAbort);
|
|
26
|
+
pending?.({
|
|
27
|
+
done: true,
|
|
28
|
+
value: void 0
|
|
55
29
|
});
|
|
56
|
-
|
|
57
|
-
return select;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
//#endregion
|
|
61
|
-
//#region src/server/connection.ts
|
|
62
|
-
const args = z.object({}).catchall(z.union([z.unknown(), z.lazy(() => args)]));
|
|
63
|
-
/**
|
|
64
|
-
* Zod schema for connection args (`after`, `before`, `first`, `last`, etc.).
|
|
65
|
-
*/
|
|
66
|
-
const connectionArgs = args.optional();
|
|
67
|
-
const connectionInput = z.strictObject({
|
|
68
|
-
args: connectionArgs,
|
|
69
|
-
select: z.array(z.string())
|
|
70
|
-
});
|
|
71
|
-
const paginationArgKeys = new Set([
|
|
72
|
-
"after",
|
|
73
|
-
"before",
|
|
74
|
-
"first",
|
|
75
|
-
"last"
|
|
76
|
-
]);
|
|
77
|
-
const paginationArgsSchema = z.strictObject({
|
|
78
|
-
after: z.string().optional(),
|
|
79
|
-
before: z.string().optional(),
|
|
80
|
-
first: z.number().int().positive().optional(),
|
|
81
|
-
last: z.number().int().positive().optional()
|
|
82
|
-
}).partial().refine(({ after, before }) => !(after && before), "Connection args can't include both 'after' and 'before'.").refine(({ first, last }) => !(first && last), "Connection args can't include both 'first' and 'last'.").refine(({ before, last }) => !last || before !== void 0, "Connection args using 'last' must also include 'before'.");
|
|
83
|
-
const extractPaginationArgs = (args) => args ? Object.fromEntries(Object.entries(args).filter(([key]) => paginationArgKeys.has(key))) : {};
|
|
84
|
-
/**
|
|
85
|
-
* Converts an array of nodes into a list view connection result the client
|
|
86
|
-
* can normalize.
|
|
87
|
-
*/
|
|
88
|
-
function arrayToConnection(nodes, { args, getCursor = (node) => String(node.id) } = {}) {
|
|
89
|
-
if (!nodes) return;
|
|
90
|
-
const paginationArgs = paginationArgsSchema.parse(extractPaginationArgs(args));
|
|
91
|
-
if (Object.keys(paginationArgs).length === 0) return {
|
|
92
|
-
items: nodes.map((node) => ({
|
|
93
|
-
cursor: getCursor(node),
|
|
94
|
-
node
|
|
95
|
-
})),
|
|
96
|
-
pagination: {
|
|
97
|
-
hasNext: false,
|
|
98
|
-
hasPrevious: false,
|
|
99
|
-
nextCursor: void 0,
|
|
100
|
-
previousCursor: void 0
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
const isBackward = paginationArgs.before !== void 0 || paginationArgs.last !== void 0;
|
|
104
|
-
const cursor = isBackward ? paginationArgs.before : paginationArgs.after;
|
|
105
|
-
const pageSize = paginationArgs.first ?? paginationArgs.last ?? nodes.length;
|
|
106
|
-
const cursorIndex = cursor === void 0 ? -1 : nodes.findIndex((node) => getCursor(node) === cursor);
|
|
107
|
-
const selectedNodes = cursorIndex < 0 ? nodes : isBackward ? nodes.slice(0, cursorIndex) : nodes.slice(cursorIndex + 1);
|
|
108
|
-
const hasNext = selectedNodes.length > pageSize;
|
|
109
|
-
const hasPrevious = nodes.length > selectedNodes.length;
|
|
110
|
-
const items = (isBackward ? selectedNodes.slice(Math.max(0, selectedNodes.length - pageSize)) : selectedNodes.slice(0, pageSize)).map((node) => ({
|
|
111
|
-
cursor: getCursor(node),
|
|
112
|
-
node
|
|
113
|
-
}));
|
|
114
|
-
const firstItem = items[0];
|
|
115
|
-
const lastItem = items.at(-1);
|
|
116
|
-
return {
|
|
117
|
-
items,
|
|
118
|
-
pagination: {
|
|
119
|
-
hasNext: isBackward ? hasPrevious : hasNext,
|
|
120
|
-
hasPrevious: isBackward ? hasNext : hasPrevious,
|
|
121
|
-
nextCursor: lastItem?.cursor,
|
|
122
|
-
previousCursor: (isBackward ? hasNext : hasPrevious) && firstItem ? firstItem.cursor : void 0
|
|
123
|
-
}
|
|
30
|
+
pending = null;
|
|
124
31
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
};
|
|
133
|
-
function toConnectionResult({ args, item, path, view }) {
|
|
134
|
-
if (!isRecord$1(item)) return item;
|
|
135
|
-
let result = null;
|
|
136
|
-
const base = () => result ? {
|
|
137
|
-
...item,
|
|
138
|
-
...result
|
|
139
|
-
} : item;
|
|
140
|
-
for (const [field, config] of Object.entries(view.fields)) {
|
|
141
|
-
if (!isDataViewField$1(config)) continue;
|
|
142
|
-
const current = base()[field];
|
|
143
|
-
const nextPath = path ? `${path}.${field}` : field;
|
|
144
|
-
if (config.kind === "list") {
|
|
145
|
-
if (!Array.isArray(current)) continue;
|
|
146
|
-
const connection = arrayToConnection(current.map((item$1) => toConnectionResult({
|
|
147
|
-
args,
|
|
148
|
-
item: item$1,
|
|
149
|
-
path: nextPath,
|
|
150
|
-
view: config
|
|
151
|
-
})), { args: getScopedArgs(args, nextPath) });
|
|
152
|
-
result = assignIfChanged(result, field, connection, current);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (Array.isArray(current)) {
|
|
156
|
-
const wrapped = current.map((entry) => toConnectionResult({
|
|
157
|
-
args,
|
|
158
|
-
item: entry,
|
|
159
|
-
path: nextPath,
|
|
160
|
-
view: config
|
|
161
|
-
}));
|
|
162
|
-
result = assignIfChanged(result, field, wrapped, current);
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (isRecord$1(current)) {
|
|
166
|
-
const wrapped = toConnectionResult({
|
|
167
|
-
args,
|
|
168
|
-
item: current,
|
|
169
|
-
path: nextPath,
|
|
170
|
-
view: config
|
|
32
|
+
const listener = (value) => {
|
|
33
|
+
if (pending) {
|
|
34
|
+
const resolve = pending;
|
|
35
|
+
pending = null;
|
|
36
|
+
resolve({
|
|
37
|
+
done: false,
|
|
38
|
+
value: [value]
|
|
171
39
|
});
|
|
172
|
-
|
|
40
|
+
return;
|
|
173
41
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
input,
|
|
195
|
-
skip: cursor ? 1 : void 0,
|
|
196
|
-
take: pageSize + 1
|
|
42
|
+
queue.push(value);
|
|
43
|
+
};
|
|
44
|
+
const onAbort = () => cleanup();
|
|
45
|
+
for (const name of names) emitter.on(name, listener);
|
|
46
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
47
|
+
return {
|
|
48
|
+
async next() {
|
|
49
|
+
if (queue.length > 0) return {
|
|
50
|
+
done: false,
|
|
51
|
+
value: [queue.shift()]
|
|
52
|
+
};
|
|
53
|
+
if (done || signal?.aborted) {
|
|
54
|
+
cleanup();
|
|
55
|
+
return {
|
|
56
|
+
done: true,
|
|
57
|
+
value: void 0
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return await new Promise((resolve) => {
|
|
61
|
+
pending = resolve;
|
|
197
62
|
});
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
ctx,
|
|
202
|
-
input,
|
|
203
|
-
items: limitedItems
|
|
204
|
-
}) : limitedItems).map((node) => ({
|
|
205
|
-
cursor: getCursor(node),
|
|
206
|
-
node
|
|
207
|
-
}));
|
|
208
|
-
const firstItem = items[0];
|
|
209
|
-
const lastItem = items.at(-1);
|
|
63
|
+
},
|
|
64
|
+
async return() {
|
|
65
|
+
cleanup();
|
|
210
66
|
return {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
hasNext: isBackward ? Boolean(cursor) : hasMore,
|
|
214
|
-
hasPrevious: isBackward ? hasMore : Boolean(cursor),
|
|
215
|
-
nextCursor: lastItem?.cursor,
|
|
216
|
-
previousCursor: (isBackward ? hasMore : Boolean(cursor)) ? firstItem?.cursor : void 0
|
|
217
|
-
}
|
|
67
|
+
done: true,
|
|
68
|
+
value: void 0
|
|
218
69
|
};
|
|
219
|
-
}
|
|
70
|
+
}
|
|
220
71
|
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
//#endregion
|
|
224
|
-
//#region src/server/dataView.ts
|
|
225
|
-
const dataViewFieldsKey = Symbol("__fate__DataViewFields");
|
|
72
|
+
} });
|
|
226
73
|
/**
|
|
227
|
-
*
|
|
74
|
+
* Creates a small in-memory event bus for Fate live view subscriptions.
|
|
228
75
|
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
76
|
+
* The bus signals that an entity changed and can optionally include changed
|
|
77
|
+
* field paths. The native live handler refetches the selected record, or only
|
|
78
|
+
* the changed selected fields when paths are provided, before sending it to
|
|
79
|
+
* clients. It does not persist events or replay events after reconnects; provide
|
|
80
|
+
* a durable custom bus for lossless `lastEventId` resume behavior.
|
|
234
81
|
*/
|
|
235
|
-
function
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
82
|
+
function createLiveEventBus() {
|
|
83
|
+
const emitter = new EventEmitter();
|
|
84
|
+
emitter.setMaxListeners(0);
|
|
85
|
+
const emit = (type, id, options = {}) => {
|
|
86
|
+
emitter.emit(eventName(type, id), {
|
|
87
|
+
changed: options.changed,
|
|
88
|
+
data: options.data,
|
|
89
|
+
eventId: options.eventId,
|
|
90
|
+
id,
|
|
91
|
+
type: options.type ?? "update"
|
|
92
|
+
});
|
|
242
93
|
};
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
94
|
+
const listen = (names, handler, signal) => {
|
|
95
|
+
const listener = (event) => handler(event);
|
|
96
|
+
const cleanup = () => {
|
|
97
|
+
for (const name of names) emitter.off(name, listener);
|
|
98
|
+
signal?.removeEventListener("abort", cleanup);
|
|
99
|
+
};
|
|
100
|
+
for (const name of names) emitter.on(name, listener);
|
|
101
|
+
signal?.addEventListener("abort", cleanup, { once: true });
|
|
102
|
+
return cleanup;
|
|
252
103
|
};
|
|
253
|
-
};
|
|
254
|
-
/**
|
|
255
|
-
* Declares a resolver field inside a data view, optionally providing a
|
|
256
|
-
* selection for any data dependencies.
|
|
257
|
-
*/
|
|
258
|
-
function resolver(config) {
|
|
259
104
|
return {
|
|
260
|
-
|
|
261
|
-
|
|
105
|
+
connection(procedure, args) {
|
|
106
|
+
const emitConnection = (type, options = {}) => {
|
|
107
|
+
const event = {
|
|
108
|
+
cursor: options.cursor,
|
|
109
|
+
eventId: options.eventId,
|
|
110
|
+
id: options.id,
|
|
111
|
+
node: options.node,
|
|
112
|
+
nodeType: options.nodeType,
|
|
113
|
+
targetCursor: options.targetCursor,
|
|
114
|
+
type
|
|
115
|
+
};
|
|
116
|
+
if (args) emitter.emit(connectionEventName(procedure, args), event);
|
|
117
|
+
else emitter.emit(globalConnectionEventName(procedure), event);
|
|
118
|
+
};
|
|
119
|
+
return {
|
|
120
|
+
appendEdge(nodeType, id, options) {
|
|
121
|
+
emitConnection("appendEdge", {
|
|
122
|
+
...options,
|
|
123
|
+
id,
|
|
124
|
+
nodeType
|
|
125
|
+
});
|
|
126
|
+
},
|
|
127
|
+
appendNode(nodeType, id, options) {
|
|
128
|
+
emitConnection("appendNode", {
|
|
129
|
+
...options,
|
|
130
|
+
id,
|
|
131
|
+
nodeType
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
deleteEdge(nodeType, id, options) {
|
|
135
|
+
emitConnection("deleteEdge", {
|
|
136
|
+
...options,
|
|
137
|
+
id,
|
|
138
|
+
nodeType
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
emit: emitConnection,
|
|
142
|
+
insertEdgeAfter(nodeType, id, targetCursor, options) {
|
|
143
|
+
emitConnection("insertEdgeAfter", {
|
|
144
|
+
...options,
|
|
145
|
+
id,
|
|
146
|
+
nodeType,
|
|
147
|
+
targetCursor
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
insertEdgeBefore(nodeType, id, targetCursor, options) {
|
|
151
|
+
emitConnection("insertEdgeBefore", {
|
|
152
|
+
...options,
|
|
153
|
+
id,
|
|
154
|
+
nodeType,
|
|
155
|
+
targetCursor
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
invalidate(options) {
|
|
159
|
+
emitConnection("invalidate", options);
|
|
160
|
+
},
|
|
161
|
+
prependEdge(nodeType, id, options) {
|
|
162
|
+
emitConnection("prependEdge", {
|
|
163
|
+
...options,
|
|
164
|
+
id,
|
|
165
|
+
nodeType
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
prependNode(nodeType, id, options) {
|
|
169
|
+
emitConnection("prependNode", {
|
|
170
|
+
...options,
|
|
171
|
+
id,
|
|
172
|
+
nodeType
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
delete(type, id, options) {
|
|
178
|
+
emit(type, id, {
|
|
179
|
+
...options,
|
|
180
|
+
type: "delete"
|
|
181
|
+
});
|
|
182
|
+
},
|
|
183
|
+
emit,
|
|
184
|
+
listen(type, id, handler, options) {
|
|
185
|
+
return listen([eventName(type, id)], handler, options?.signal);
|
|
186
|
+
},
|
|
187
|
+
listenConnection(target, handler, options) {
|
|
188
|
+
return listen([connectionEventName(target.procedure, target.args), globalConnectionEventName(target.procedure)], handler, options?.signal);
|
|
189
|
+
},
|
|
190
|
+
subscribe(type, id, options) {
|
|
191
|
+
return on(emitter, eventName(type, id), { signal: options?.signal });
|
|
192
|
+
},
|
|
193
|
+
subscribeConnection(target, options) {
|
|
194
|
+
return mergeEvents(emitter, [connectionEventName(target.procedure, target.args), globalConnectionEventName(target.procedure)], options?.signal);
|
|
195
|
+
},
|
|
196
|
+
update(type, id, options) {
|
|
197
|
+
emit(type, id, {
|
|
198
|
+
...options,
|
|
199
|
+
type: "update"
|
|
200
|
+
});
|
|
201
|
+
}
|
|
262
202
|
};
|
|
263
203
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/server/http.ts
|
|
206
|
+
const jsonHeaders = { "content-type": "application/json; charset=utf-8" };
|
|
207
|
+
const sseHeaders = {
|
|
208
|
+
"cache-control": "no-cache",
|
|
209
|
+
connection: "keep-alive",
|
|
210
|
+
"content-type": "text/event-stream; charset=utf-8"
|
|
211
|
+
};
|
|
212
|
+
const normalizeRootConfig = (root) => isDataView(root) ? { view: root } : root;
|
|
213
|
+
const rootProcedureName = (name, _root) => {
|
|
214
|
+
return name;
|
|
215
|
+
};
|
|
216
|
+
const getLiveBus = (live) => {
|
|
217
|
+
if (!live) return null;
|
|
218
|
+
if ("subscribe" in live) return live;
|
|
219
|
+
return live.bus;
|
|
220
|
+
};
|
|
221
|
+
const getWaitUntil = (adapterContext) => {
|
|
222
|
+
const context = adapterContext;
|
|
223
|
+
if (!context) return;
|
|
224
|
+
try {
|
|
225
|
+
const executionCtx = context.executionCtx;
|
|
226
|
+
const waitUntil = executionCtx?.waitUntil;
|
|
227
|
+
if (waitUntil) return waitUntil.bind(executionCtx);
|
|
228
|
+
} catch {}
|
|
229
|
+
try {
|
|
230
|
+
return context.waitUntil?.bind(context);
|
|
231
|
+
} catch {
|
|
232
|
+
return;
|
|
292
233
|
}
|
|
293
|
-
return filtered;
|
|
294
234
|
};
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
target[key] = value;
|
|
235
|
+
const parseJSON = async (request) => {
|
|
236
|
+
try {
|
|
237
|
+
return await request.json();
|
|
238
|
+
} catch {
|
|
239
|
+
throw new FateRequestError("BAD_REQUEST", "Request body must be valid JSON.");
|
|
303
240
|
}
|
|
304
241
|
};
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
if (isRecord
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
242
|
+
const validationError = (issues) => new FateRequestError("VALIDATION_ERROR", "Validation failed.", { issues });
|
|
243
|
+
const parseInput = async (schema, value) => {
|
|
244
|
+
if (!schema) return value;
|
|
245
|
+
const standard = schema["~standard"];
|
|
246
|
+
if (standard) try {
|
|
247
|
+
const result = await standard.validate(value);
|
|
248
|
+
if (isRecord(result) && "issues" in result) throw validationError(result.issues);
|
|
249
|
+
return isRecord(result) && "value" in result ? result.value : value;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error instanceof FateRequestError) throw error;
|
|
252
|
+
throw validationError(isRecord(error) ? error.issues : void 0);
|
|
253
|
+
}
|
|
254
|
+
if (schema.parse) try {
|
|
255
|
+
return schema.parse(value);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
throw validationError(isRecord(error) ? error.issues : void 0);
|
|
320
258
|
}
|
|
321
|
-
return
|
|
259
|
+
return value;
|
|
322
260
|
};
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (
|
|
337
|
-
node.resolvers.set(segment, field);
|
|
338
|
-
selectedPaths.add(nextPath);
|
|
339
|
-
}
|
|
340
|
-
return;
|
|
261
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
262
|
+
const isProtocolId = (value) => typeof value === "string" || typeof value === "number";
|
|
263
|
+
const operationKinds = new Set([
|
|
264
|
+
"byId",
|
|
265
|
+
"list",
|
|
266
|
+
"mutation",
|
|
267
|
+
"query"
|
|
268
|
+
]);
|
|
269
|
+
const assertProtocolRequest = (value) => {
|
|
270
|
+
if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.operations)) throw new FateRequestError("BAD_REQUEST", "Invalid Fate protocol request.");
|
|
271
|
+
for (const operation of value.operations) {
|
|
272
|
+
if (!isRecord(operation) || typeof operation.id !== "string" || typeof operation.kind !== "string" || !operationKinds.has(operation.kind) || !isStringArray(operation.select) || "args" in operation && operation.args !== void 0 && !isRecord(operation.args)) throw new FateRequestError("BAD_REQUEST", "Invalid Fate protocol operation.");
|
|
273
|
+
if (operation.kind === "byId" && (typeof operation.type !== "string" || !Array.isArray(operation.ids) || !operation.ids.every(isProtocolId))) throw new FateRequestError("BAD_REQUEST", "Invalid Fate byId operation.");
|
|
274
|
+
if ((operation.kind === "list" || operation.kind === "mutation" || operation.kind === "query") && typeof operation.name !== "string") throw new FateRequestError("BAD_REQUEST", "Invalid Fate named operation.");
|
|
341
275
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
|
|
276
|
+
return value;
|
|
277
|
+
};
|
|
278
|
+
const assertLiveControlRequest = (value) => {
|
|
279
|
+
if (!isRecord(value) || value.version !== 1 || typeof value.connectionId !== "string" || !Array.isArray(value.operations)) throw new FateRequestError("BAD_REQUEST", "Invalid Fate live request.");
|
|
280
|
+
for (const operation of value.operations) {
|
|
281
|
+
if (!isRecord(operation) || typeof operation.id !== "string" || typeof operation.kind !== "string") throw new FateRequestError("BAD_REQUEST", "Invalid Fate live operation.");
|
|
282
|
+
if (operation.kind === "subscribe") {
|
|
283
|
+
if (typeof operation.type !== "string" || !isProtocolId(operation.entityId) || "args" in operation && operation.args !== void 0 && !isRecord(operation.args) || "lastEventId" in operation && operation.lastEventId !== void 0 && typeof operation.lastEventId !== "string" || !isStringArray(operation.select)) throw new FateRequestError("BAD_REQUEST", "Invalid Fate live subscribe operation.");
|
|
284
|
+
continue;
|
|
351
285
|
}
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
|
|
286
|
+
if (operation.kind === "subscribeConnection") {
|
|
287
|
+
if (typeof operation.type !== "string" || typeof operation.procedure !== "string" || "args" in operation && operation.args !== void 0 && !isRecord(operation.args) || "selectionArgs" in operation && operation.selectionArgs !== void 0 && !isRecord(operation.selectionArgs) || "lastEventId" in operation && operation.lastEventId !== void 0 && typeof operation.lastEventId !== "string" || !isStringArray(operation.select)) throw new FateRequestError("BAD_REQUEST", "Invalid Fate live connection subscribe operation.");
|
|
288
|
+
continue;
|
|
355
289
|
}
|
|
356
|
-
|
|
357
|
-
|
|
290
|
+
if (operation.kind !== "unsubscribe") throw new FateRequestError("BAD_REQUEST", "Invalid Fate live operation.");
|
|
291
|
+
}
|
|
292
|
+
return value;
|
|
293
|
+
};
|
|
294
|
+
const sse = (message, eventId) => {
|
|
295
|
+
const lines = [];
|
|
296
|
+
if (eventId) lines.push(`id: ${eventId}`);
|
|
297
|
+
lines.push(`event: ${message.kind}`);
|
|
298
|
+
lines.push(`data: ${JSON.stringify(message)}`);
|
|
299
|
+
return `${lines.join("\n")}\n\n`;
|
|
300
|
+
};
|
|
301
|
+
const sseComment = (message) => `: ${message}\n\n`;
|
|
302
|
+
const livePayload = (event, data, select) => event.type === "delete" || data == null ? {
|
|
303
|
+
delete: true,
|
|
304
|
+
id: event.id
|
|
305
|
+
} : {
|
|
306
|
+
data,
|
|
307
|
+
...select ? { select: [...select] } : void 0
|
|
308
|
+
};
|
|
309
|
+
const pathsIntersect = (left, right) => left === right || left.startsWith(`${right}.`) || right.startsWith(`${left}.`);
|
|
310
|
+
const filterLiveSelection = (source, select, changed) => {
|
|
311
|
+
if (!changed) return null;
|
|
312
|
+
const changedPaths = changed.filter((path) => path.length > 0);
|
|
313
|
+
if (changedPaths.length === 0) return [];
|
|
314
|
+
const selected = select.filter((path) => changedPaths.some((changedPath) => pathsIntersect(path, changedPath)));
|
|
315
|
+
if (selected.length === 0) return [];
|
|
316
|
+
const result = new Set(selected);
|
|
317
|
+
result.add(source.id);
|
|
318
|
+
for (const path of select) {
|
|
319
|
+
if (!path.endsWith(".id")) continue;
|
|
320
|
+
const parentPath = path.slice(0, -3);
|
|
321
|
+
if (selected.some((selectedPath) => pathsIntersect(selectedPath, parentPath))) result.add(path);
|
|
358
322
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
323
|
+
return [...result];
|
|
324
|
+
};
|
|
325
|
+
const hasSelectedDataPath = (value, segments) => {
|
|
326
|
+
if (segments.length === 0) return true;
|
|
327
|
+
if (Array.isArray(value)) return value.every((entry) => hasSelectedDataPath(entry, segments));
|
|
328
|
+
if (!isRecord(value)) return false;
|
|
329
|
+
const [field, ...rest] = segments;
|
|
330
|
+
return field in value && hasSelectedDataPath(value[field], rest);
|
|
331
|
+
};
|
|
332
|
+
const canUseLiveEventData = (data, selectedPaths) => {
|
|
333
|
+
if (!isRecord(data)) return false;
|
|
334
|
+
for (const path of selectedPaths) if (!hasSelectedDataPath(data, path.split("."))) return false;
|
|
335
|
+
return true;
|
|
336
|
+
};
|
|
337
|
+
const resolveLiveSourceData = async ({ ctx, data, id, input, registry, source }) => {
|
|
338
|
+
if (data !== void 0) {
|
|
339
|
+
if (data == null) return data;
|
|
340
|
+
const plan = createSourcePlan({
|
|
341
|
+
...input,
|
|
342
|
+
ctx,
|
|
343
|
+
source
|
|
344
|
+
});
|
|
345
|
+
if (canUseLiveEventData(data, plan.selectedPaths)) return await plan.resolve(data);
|
|
362
346
|
}
|
|
347
|
+
return id == null ? null : await resolveSourceById({
|
|
348
|
+
ctx,
|
|
349
|
+
id: String(id),
|
|
350
|
+
input,
|
|
351
|
+
registry,
|
|
352
|
+
source
|
|
353
|
+
});
|
|
363
354
|
};
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
371
|
-
|
|
355
|
+
const liveConnectionPayload = (event, node) => {
|
|
356
|
+
switch (event.type) {
|
|
357
|
+
case "deleteEdge": return {
|
|
358
|
+
id: event.id,
|
|
359
|
+
nodeType: event.nodeType,
|
|
360
|
+
type: "deleteEdge"
|
|
361
|
+
};
|
|
362
|
+
case "invalidate": return { type: "invalidate" };
|
|
363
|
+
default: return {
|
|
364
|
+
edge: {
|
|
365
|
+
cursor: event.cursor,
|
|
366
|
+
node
|
|
367
|
+
},
|
|
368
|
+
nodeType: event.nodeType,
|
|
369
|
+
targetCursor: event.targetCursor,
|
|
370
|
+
type: event.type
|
|
371
|
+
};
|
|
372
372
|
}
|
|
373
|
-
for (const relation of node.relations.values()) collectResolvers(relation, select, args$1, context);
|
|
374
373
|
};
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
374
|
+
const sourceScore = (source) => Object.keys(source.view.fields).length + Object.keys(source.relations ?? {}).length;
|
|
375
|
+
function createFateServer({ context, lists, live, mutations, queries, roots, sources }) {
|
|
376
|
+
const rootLists = /* @__PURE__ */ new Map();
|
|
377
|
+
const rootQueries = /* @__PURE__ */ new Map();
|
|
378
|
+
const sourcesByType = /* @__PURE__ */ new Map();
|
|
379
|
+
const registerSourceByType = (source) => {
|
|
380
|
+
const existing = sourcesByType.get(source.view.typeName);
|
|
381
|
+
if (!existing || sourceScore(source) > sourceScore(existing)) sourcesByType.set(source.view.typeName, source);
|
|
382
|
+
};
|
|
383
|
+
const visit = (view) => {
|
|
384
|
+
registerSourceByType(sources.getSource(view));
|
|
385
|
+
for (const field of Object.values(view.fields)) if (isDataView(field)) visit(field);
|
|
381
386
|
};
|
|
382
|
-
const
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
387
|
+
for (const [name, root] of Object.entries(roots)) {
|
|
388
|
+
const config = normalizeRootConfig(root);
|
|
389
|
+
const procedure = rootProcedureName(name, root);
|
|
390
|
+
const source = sources.getSource(config.view);
|
|
391
|
+
visit(config.view);
|
|
392
|
+
if (config.view.kind === "list") rootLists.set(procedure, {
|
|
393
|
+
source,
|
|
394
|
+
view: config.view
|
|
395
|
+
});
|
|
396
|
+
else rootQueries.set(procedure, {
|
|
397
|
+
source,
|
|
398
|
+
view: config.view
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
for (const name of rootQueries.keys()) if (!queries?.[name]) throw new Error(`Native fate root query '${name}' requires a matching resolver in createFateServer({ queries }).`);
|
|
402
|
+
const liveBus = getLiveBus(live);
|
|
403
|
+
const getContext = async (request, adapterContext) => context ? await context({
|
|
404
|
+
adapterContext,
|
|
405
|
+
request
|
|
406
|
+
}) : void 0;
|
|
407
|
+
const executeOperation = async (operation, ctx) => {
|
|
408
|
+
try {
|
|
409
|
+
const input = {
|
|
410
|
+
args: operation.args,
|
|
411
|
+
select: operation.select
|
|
412
|
+
};
|
|
413
|
+
if (operation.kind === "byId") {
|
|
414
|
+
if (!operation.type || !operation.ids) throw new FateRequestError("BAD_REQUEST", "byId operations require type and ids.");
|
|
415
|
+
const source = sourcesByType.get(operation.type);
|
|
416
|
+
if (!source) throw new FateRequestError("NOT_FOUND", `No source registered for '${operation.type}'.`);
|
|
417
|
+
return {
|
|
418
|
+
data: await resolveSourceByIds({
|
|
419
|
+
ctx,
|
|
420
|
+
ids: operation.ids.map(String),
|
|
421
|
+
input,
|
|
422
|
+
registry: sources.registry,
|
|
423
|
+
source
|
|
424
|
+
}),
|
|
425
|
+
id: operation.id,
|
|
426
|
+
ok: true
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
if (!operation.name) throw new FateRequestError("BAD_REQUEST", `${operation.kind} operations require a name.`);
|
|
430
|
+
if (operation.kind === "list") {
|
|
431
|
+
const customList = lists?.[operation.name];
|
|
432
|
+
if (customList) return {
|
|
433
|
+
data: await customList.resolve({
|
|
434
|
+
ctx,
|
|
435
|
+
input: { args: operation.args },
|
|
436
|
+
select: operation.select
|
|
437
|
+
}),
|
|
438
|
+
id: operation.id,
|
|
439
|
+
ok: true
|
|
440
|
+
};
|
|
441
|
+
const root = rootLists.get(operation.name);
|
|
442
|
+
if (!root) throw new FateRequestError("NOT_FOUND", `No list registered for '${operation.name}'.`);
|
|
443
|
+
return {
|
|
444
|
+
data: await resolveConnection({
|
|
445
|
+
ctx,
|
|
446
|
+
input,
|
|
447
|
+
query: ({ ctx, cursor, direction, input, skip, take }) => resolveSourceConnection({
|
|
448
|
+
ctx,
|
|
449
|
+
cursor,
|
|
450
|
+
direction,
|
|
451
|
+
input,
|
|
452
|
+
registry: sources.registry,
|
|
453
|
+
skip,
|
|
454
|
+
source: root.source,
|
|
455
|
+
take
|
|
456
|
+
})
|
|
457
|
+
}),
|
|
458
|
+
id: operation.id,
|
|
459
|
+
ok: true
|
|
460
|
+
};
|
|
388
461
|
}
|
|
462
|
+
if (operation.kind === "query") {
|
|
463
|
+
const customQuery = queries?.[operation.name];
|
|
464
|
+
if (customQuery) return {
|
|
465
|
+
data: await customQuery.resolve({
|
|
466
|
+
ctx,
|
|
467
|
+
input: { args: operation.args },
|
|
468
|
+
select: operation.select
|
|
469
|
+
}),
|
|
470
|
+
id: operation.id,
|
|
471
|
+
ok: true
|
|
472
|
+
};
|
|
473
|
+
throw new FateRequestError("NOT_FOUND", `No query registered for '${operation.name}'.`);
|
|
474
|
+
}
|
|
475
|
+
const mutation = mutations?.[operation.name];
|
|
476
|
+
if (!mutation) throw new FateRequestError("NOT_FOUND", `No mutation registered for '${operation.name}'.`);
|
|
477
|
+
return {
|
|
478
|
+
data: await mutation.resolve({
|
|
479
|
+
ctx,
|
|
480
|
+
input: await parseInput(mutation.input, operation.input),
|
|
481
|
+
select: operation.select
|
|
482
|
+
}),
|
|
483
|
+
id: operation.id,
|
|
484
|
+
ok: true
|
|
485
|
+
};
|
|
486
|
+
} catch (error) {
|
|
487
|
+
return {
|
|
488
|
+
error: toProtocolError(error),
|
|
489
|
+
id: operation.id,
|
|
490
|
+
ok: false
|
|
491
|
+
};
|
|
389
492
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
493
|
+
};
|
|
494
|
+
const manifest = {
|
|
495
|
+
lists: Object.fromEntries([...[...rootLists.entries()].map(([name, entry]) => [name, { type: entry.view.typeName }]), ...Object.entries(lists ?? {}).map(([name, list]) => [name, { type: list.type ?? rootLists.get(name)?.view.typeName ?? "Unknown" }])].sort(([a], [b]) => a.localeCompare(b))),
|
|
496
|
+
live: liveBus ? Object.fromEntries([...sourcesByType.keys()].sort().map((type) => [type, true])) : {},
|
|
497
|
+
mutations: Object.fromEntries(Object.entries(mutations ?? {}).map(([name, mutation]) => [name, { type: mutation.type }]).sort(([a], [b]) => a.localeCompare(b))),
|
|
498
|
+
queries: Object.fromEntries([...[...rootQueries.entries()].map(([name, entry]) => [name, { type: entry.view.typeName }]), ...Object.entries(queries ?? {}).map(([name, query]) => [name, { type: query.type ?? rootQueries.get(name)?.view.typeName ?? "Unknown" }])].sort(([a], [b]) => a.localeCompare(b))),
|
|
499
|
+
types: Object.fromEntries([...sourcesByType.keys()].sort().map((type) => [type, true]))
|
|
500
|
+
};
|
|
501
|
+
const liveConnections = /* @__PURE__ */ new Map();
|
|
502
|
+
const encoder = new TextEncoder();
|
|
503
|
+
const cleanupLiveConnection = (connectionId) => {
|
|
504
|
+
const connection = liveConnections.get(connectionId);
|
|
505
|
+
if (!connection) return;
|
|
506
|
+
connection.closed = true;
|
|
507
|
+
liveConnections.delete(connectionId);
|
|
508
|
+
for (const subscription of connection.subscriptions.values()) subscription.abort();
|
|
509
|
+
connection.subscriptions.clear();
|
|
510
|
+
if (connection.drainInterval) {
|
|
511
|
+
clearInterval(connection.drainInterval);
|
|
512
|
+
connection.drainInterval = void 0;
|
|
513
|
+
}
|
|
514
|
+
if (connection.drainTimeout) {
|
|
515
|
+
clearTimeout(connection.drainTimeout);
|
|
516
|
+
connection.drainTimeout = void 0;
|
|
517
|
+
}
|
|
518
|
+
if (connection.heartbeat) {
|
|
519
|
+
clearInterval(connection.heartbeat);
|
|
520
|
+
connection.heartbeat = void 0;
|
|
521
|
+
}
|
|
522
|
+
try {
|
|
523
|
+
connection.controller?.close();
|
|
524
|
+
} catch {}
|
|
525
|
+
connection.abort();
|
|
526
|
+
};
|
|
527
|
+
const sendLiveMessage = (connection, message, eventId) => {
|
|
528
|
+
if (connection.closed || !connection.controller) return;
|
|
529
|
+
try {
|
|
530
|
+
connection.controller.enqueue(encoder.encode(sse(message, eventId)));
|
|
531
|
+
} catch {
|
|
532
|
+
connection.closed = true;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
const sendLiveComment = (connection, message) => {
|
|
536
|
+
if (connection.closed || !connection.controller) return;
|
|
537
|
+
try {
|
|
538
|
+
connection.controller.enqueue(encoder.encode(sseComment(message)));
|
|
539
|
+
} catch {
|
|
540
|
+
connection.closed = true;
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
const drainLiveConnection = async (connection) => {
|
|
544
|
+
if (connection.closed || connection.draining) return;
|
|
545
|
+
connection.draining = true;
|
|
546
|
+
let activeOperationId = null;
|
|
547
|
+
try {
|
|
548
|
+
while (!connection.closed && connection.queue.length > 0) {
|
|
549
|
+
const item = connection.queue.shift();
|
|
550
|
+
activeOperationId = item.operation.id;
|
|
551
|
+
if (!item.subscription.active || connection.subscriptions.get(item.operation.id) !== item.subscription) continue;
|
|
552
|
+
if (item.kind === "entity") {
|
|
553
|
+
const { event, operation, source } = item;
|
|
554
|
+
const select = filterLiveSelection(source, operation.select, event.changed);
|
|
555
|
+
if (select?.length === 0) continue;
|
|
556
|
+
const input = {
|
|
557
|
+
args: operation.args,
|
|
558
|
+
select: select ?? operation.select
|
|
559
|
+
};
|
|
560
|
+
sendLiveMessage(connection, {
|
|
561
|
+
event: livePayload(event, event.type === "delete" ? null : await resolveLiveSourceData({
|
|
562
|
+
ctx: connection.ctx,
|
|
563
|
+
data: event.data,
|
|
564
|
+
id: event.id,
|
|
565
|
+
input,
|
|
566
|
+
registry: sources.registry,
|
|
567
|
+
source
|
|
568
|
+
}), select ?? void 0),
|
|
569
|
+
id: operation.id,
|
|
570
|
+
kind: "next"
|
|
571
|
+
}, event.eventId);
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
const { event, operation } = item;
|
|
575
|
+
const nodeType = event.nodeType ?? operation.type;
|
|
576
|
+
const source = nodeType ? sourcesByType.get(nodeType) : null;
|
|
577
|
+
const node = event.type === "deleteEdge" || event.type === "invalidate" ? null : source ? await resolveLiveSourceData({
|
|
578
|
+
ctx: connection.ctx,
|
|
579
|
+
data: event.node,
|
|
580
|
+
id: event.id,
|
|
581
|
+
input: {
|
|
582
|
+
args: operation.selectionArgs,
|
|
583
|
+
select: operation.select
|
|
584
|
+
},
|
|
585
|
+
registry: sources.registry,
|
|
586
|
+
source
|
|
587
|
+
}) : null;
|
|
588
|
+
if (event.type !== "invalidate" && (!nodeType || event.type !== "deleteEdge" && node == null)) throw new FateRequestError("NOT_FOUND", `No source registered for live connection node type '${nodeType}'.`);
|
|
589
|
+
sendLiveMessage(connection, {
|
|
590
|
+
event: liveConnectionPayload(event, node),
|
|
591
|
+
id: operation.id,
|
|
592
|
+
kind: "connection"
|
|
593
|
+
}, event.eventId);
|
|
594
|
+
}
|
|
595
|
+
const now = Date.now();
|
|
596
|
+
if (!connection.closed && now - connection.lastHeartbeat >= 25e3) {
|
|
597
|
+
connection.lastHeartbeat = now;
|
|
598
|
+
sendLiveComment(connection, "heartbeat");
|
|
599
|
+
}
|
|
600
|
+
} catch (error) {
|
|
601
|
+
sendLiveMessage(connection, {
|
|
602
|
+
error: toProtocolError(error),
|
|
603
|
+
id: activeOperationId ?? "live",
|
|
604
|
+
kind: "error"
|
|
605
|
+
});
|
|
606
|
+
} finally {
|
|
607
|
+
connection.draining = false;
|
|
403
608
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
609
|
+
};
|
|
610
|
+
const scheduleLiveDrain = (connection) => {
|
|
611
|
+
if (connection.closed || connection.drainTimeout) return;
|
|
612
|
+
connection.drainTimeout = setTimeout(() => {
|
|
613
|
+
connection.drainTimeout = void 0;
|
|
614
|
+
drainLiveConnection(connection);
|
|
615
|
+
}, 0);
|
|
616
|
+
};
|
|
617
|
+
const subscribeLiveOperation = (connection, operation, waitUntil) => {
|
|
618
|
+
const source = sourcesByType.get(operation.type);
|
|
619
|
+
if (!source) throw new FateRequestError("NOT_FOUND", `No source registered for '${operation.type}'.`);
|
|
620
|
+
connection.subscriptions.get(operation.id)?.abort();
|
|
621
|
+
const subscriptionController = new AbortController();
|
|
622
|
+
const subscription = {
|
|
623
|
+
abort: () => {
|
|
624
|
+
subscription.active = false;
|
|
625
|
+
subscriptionController.abort();
|
|
626
|
+
},
|
|
627
|
+
active: true
|
|
628
|
+
};
|
|
629
|
+
connection.subscriptions.set(operation.id, subscription);
|
|
630
|
+
const handleEvent = async (event) => {
|
|
631
|
+
try {
|
|
632
|
+
const select = filterLiveSelection(source, operation.select, event.changed);
|
|
633
|
+
if (select?.length === 0) return;
|
|
634
|
+
const input = {
|
|
635
|
+
args: operation.args,
|
|
636
|
+
select: select ?? operation.select
|
|
637
|
+
};
|
|
638
|
+
sendLiveMessage(connection, {
|
|
639
|
+
event: livePayload(event, event.type === "delete" ? null : await resolveLiveSourceData({
|
|
640
|
+
ctx: connection.ctx,
|
|
641
|
+
data: event.data,
|
|
642
|
+
id: event.id,
|
|
643
|
+
input,
|
|
644
|
+
registry: sources.registry,
|
|
645
|
+
source
|
|
646
|
+
}), select ?? void 0),
|
|
647
|
+
id: operation.id,
|
|
648
|
+
kind: "next"
|
|
649
|
+
}, event.eventId);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
if (!subscriptionController.signal.aborted) sendLiveMessage(connection, {
|
|
652
|
+
error: toProtocolError(error),
|
|
653
|
+
id: operation.id,
|
|
654
|
+
kind: "error"
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
if (liveBus.listen) {
|
|
659
|
+
const unsubscribe = liveBus.listen(operation.type, operation.entityId, (event) => {
|
|
660
|
+
connection.queue.push({
|
|
661
|
+
event,
|
|
662
|
+
kind: "entity",
|
|
663
|
+
operation,
|
|
664
|
+
source,
|
|
665
|
+
subscription
|
|
666
|
+
});
|
|
667
|
+
scheduleLiveDrain(connection);
|
|
668
|
+
}, {
|
|
669
|
+
lastEventId: operation.lastEventId,
|
|
670
|
+
signal: subscriptionController.signal
|
|
409
671
|
});
|
|
410
|
-
|
|
672
|
+
const abort = () => {
|
|
673
|
+
subscription.active = false;
|
|
674
|
+
unsubscribe();
|
|
675
|
+
subscriptionController.abort();
|
|
676
|
+
};
|
|
677
|
+
subscription.abort = abort;
|
|
678
|
+
connection.subscriptions.set(operation.id, subscription);
|
|
679
|
+
return;
|
|
411
680
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
const select = prismaSelect([...allowedPaths], args$1);
|
|
429
|
-
collectResolvers(root, select, args$1, ctx);
|
|
430
|
-
const resolve = async (item) => toConnectionResult({
|
|
431
|
-
args: args$1,
|
|
432
|
-
item: filterToViewFields(await resolveNode({
|
|
433
|
-
item,
|
|
434
|
-
node: root,
|
|
435
|
-
options: {
|
|
436
|
-
args: args$1,
|
|
437
|
-
context: ctx
|
|
681
|
+
const task = (async () => {
|
|
682
|
+
try {
|
|
683
|
+
const iterable = liveBus.subscribe(operation.type, operation.entityId, {
|
|
684
|
+
lastEventId: operation.lastEventId,
|
|
685
|
+
signal: subscriptionController.signal
|
|
686
|
+
});
|
|
687
|
+
for await (const [event] of iterable) await handleEvent(event);
|
|
688
|
+
} catch (error) {
|
|
689
|
+
if (!subscriptionController.signal.aborted) sendLiveMessage(connection, {
|
|
690
|
+
error: toProtocolError(error),
|
|
691
|
+
id: operation.id,
|
|
692
|
+
kind: "error"
|
|
693
|
+
});
|
|
694
|
+
} finally {
|
|
695
|
+
if (connection.subscriptions.get(operation.id) === subscription) connection.subscriptions.delete(operation.id);
|
|
438
696
|
}
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
}
|
|
697
|
+
})();
|
|
698
|
+
waitUntil?.(task);
|
|
699
|
+
};
|
|
700
|
+
const subscribeLiveConnectionOperation = (connection, operation, waitUntil) => {
|
|
701
|
+
connection.subscriptions.get(operation.id)?.abort();
|
|
702
|
+
const subscriptionController = new AbortController();
|
|
703
|
+
const subscription = {
|
|
704
|
+
abort: () => {
|
|
705
|
+
subscription.active = false;
|
|
706
|
+
subscriptionController.abort();
|
|
707
|
+
},
|
|
708
|
+
active: true
|
|
709
|
+
};
|
|
710
|
+
connection.subscriptions.set(operation.id, subscription);
|
|
711
|
+
const handleEvent = async (event) => {
|
|
712
|
+
try {
|
|
713
|
+
const nodeType = event.nodeType ?? operation.type;
|
|
714
|
+
const source = nodeType ? sourcesByType.get(nodeType) : null;
|
|
715
|
+
const node = event.type === "deleteEdge" || event.type === "invalidate" ? null : source ? await resolveLiveSourceData({
|
|
716
|
+
ctx: connection.ctx,
|
|
717
|
+
data: event.node,
|
|
718
|
+
id: event.id,
|
|
719
|
+
input: {
|
|
720
|
+
args: operation.selectionArgs,
|
|
721
|
+
select: operation.select
|
|
722
|
+
},
|
|
723
|
+
registry: sources.registry,
|
|
724
|
+
source
|
|
725
|
+
}) : null;
|
|
726
|
+
if (event.type !== "invalidate" && (!nodeType || event.type !== "deleteEdge" && node == null)) throw new FateRequestError("NOT_FOUND", `No source registered for live connection node type '${nodeType}'.`);
|
|
727
|
+
sendLiveMessage(connection, {
|
|
728
|
+
event: liveConnectionPayload(event, node),
|
|
729
|
+
id: operation.id,
|
|
730
|
+
kind: "connection"
|
|
731
|
+
}, event.eventId);
|
|
732
|
+
} catch (error) {
|
|
733
|
+
if (!subscriptionController.signal.aborted) sendLiveMessage(connection, {
|
|
734
|
+
error: toProtocolError(error),
|
|
735
|
+
id: operation.id,
|
|
736
|
+
kind: "error"
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
if (liveBus.listenConnection) {
|
|
741
|
+
const unsubscribe = liveBus.listenConnection({
|
|
742
|
+
args: operation.args,
|
|
743
|
+
procedure: operation.procedure
|
|
744
|
+
}, (event) => {
|
|
745
|
+
connection.queue.push({
|
|
746
|
+
event,
|
|
747
|
+
kind: "connection",
|
|
748
|
+
operation,
|
|
749
|
+
subscription
|
|
750
|
+
});
|
|
751
|
+
scheduleLiveDrain(connection);
|
|
752
|
+
}, {
|
|
753
|
+
lastEventId: operation.lastEventId,
|
|
754
|
+
signal: subscriptionController.signal
|
|
755
|
+
});
|
|
756
|
+
const abort = () => {
|
|
757
|
+
subscription.active = false;
|
|
758
|
+
unsubscribe();
|
|
759
|
+
subscriptionController.abort();
|
|
760
|
+
};
|
|
761
|
+
subscription.abort = abort;
|
|
762
|
+
connection.subscriptions.set(operation.id, subscription);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const task = (async () => {
|
|
766
|
+
try {
|
|
767
|
+
const iterable = liveBus.subscribeConnection({
|
|
768
|
+
args: operation.args,
|
|
769
|
+
procedure: operation.procedure
|
|
770
|
+
}, {
|
|
771
|
+
lastEventId: operation.lastEventId,
|
|
772
|
+
signal: subscriptionController.signal
|
|
773
|
+
});
|
|
774
|
+
for await (const [event] of iterable) await handleEvent(event);
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (!subscriptionController.signal.aborted) sendLiveMessage(connection, {
|
|
777
|
+
error: toProtocolError(error),
|
|
778
|
+
id: operation.id,
|
|
779
|
+
kind: "error"
|
|
780
|
+
});
|
|
781
|
+
} finally {
|
|
782
|
+
if (connection.subscriptions.get(operation.id) === subscription) connection.subscriptions.delete(operation.id);
|
|
783
|
+
}
|
|
784
|
+
})();
|
|
785
|
+
waitUntil?.(task);
|
|
786
|
+
};
|
|
787
|
+
const controlLiveConnection = async (request, adapterContext) => {
|
|
788
|
+
const body = assertLiveControlRequest(await parseJSON(request));
|
|
789
|
+
const connection = liveConnections.get(body.connectionId);
|
|
790
|
+
if (!connection || connection.closed) throw new FateRequestError("NOT_FOUND", "Live connection not found.");
|
|
791
|
+
const results = [];
|
|
792
|
+
const waitUntil = getWaitUntil(adapterContext);
|
|
793
|
+
for (const operation of body.operations) try {
|
|
794
|
+
if (operation.kind === "subscribe") subscribeLiveOperation(connection, operation, waitUntil);
|
|
795
|
+
else if (operation.kind === "subscribeConnection") subscribeLiveConnectionOperation(connection, operation, waitUntil);
|
|
796
|
+
else {
|
|
797
|
+
connection.subscriptions.get(operation.id)?.abort();
|
|
798
|
+
connection.subscriptions.delete(operation.id);
|
|
799
|
+
}
|
|
800
|
+
results.push({
|
|
801
|
+
data: null,
|
|
802
|
+
id: operation.id,
|
|
803
|
+
ok: true
|
|
804
|
+
});
|
|
805
|
+
} catch (error) {
|
|
806
|
+
results.push({
|
|
807
|
+
error: toProtocolError(error),
|
|
808
|
+
id: operation.id,
|
|
809
|
+
ok: false
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
return {
|
|
813
|
+
results,
|
|
814
|
+
version: 1
|
|
815
|
+
};
|
|
816
|
+
};
|
|
442
817
|
return {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
818
|
+
async handleLiveRequest(request, adapterContext) {
|
|
819
|
+
try {
|
|
820
|
+
if (!liveBus) throw new FateRequestError("NOT_FOUND", "Live views are not enabled.");
|
|
821
|
+
if (request.method === "POST") return Response.json(await controlLiveConnection(request, adapterContext), { headers: jsonHeaders });
|
|
822
|
+
if (request.method !== "GET") throw new FateRequestError("BAD_REQUEST", "Invalid Fate live request.");
|
|
823
|
+
const connectionId = new URL(request.url).searchParams.get("connectionId");
|
|
824
|
+
if (!connectionId) throw new FateRequestError("BAD_REQUEST", "Invalid Fate live request.");
|
|
825
|
+
const ctx = await getContext(request, adapterContext);
|
|
826
|
+
const liveController = new AbortController();
|
|
827
|
+
const abortLive = () => liveController.abort();
|
|
828
|
+
if (request.signal.aborted) abortLive();
|
|
829
|
+
else request.signal.addEventListener("abort", abortLive, { once: true });
|
|
830
|
+
cleanupLiveConnection(connectionId);
|
|
831
|
+
const stream = new ReadableStream({
|
|
832
|
+
cancel() {
|
|
833
|
+
cleanupLiveConnection(connectionId);
|
|
834
|
+
},
|
|
835
|
+
pull() {
|
|
836
|
+
const connection = liveConnections.get(connectionId);
|
|
837
|
+
if (connection) drainLiveConnection(connection);
|
|
838
|
+
},
|
|
839
|
+
start(controller) {
|
|
840
|
+
const connection = {
|
|
841
|
+
abort: abortLive,
|
|
842
|
+
closed: false,
|
|
843
|
+
controller,
|
|
844
|
+
ctx,
|
|
845
|
+
lastHeartbeat: Date.now(),
|
|
846
|
+
queue: [],
|
|
847
|
+
subscriptions: /* @__PURE__ */ new Map()
|
|
848
|
+
};
|
|
849
|
+
connection.drainInterval = setInterval(() => void drainLiveConnection(connection), 100);
|
|
850
|
+
liveConnections.set(connectionId, connection);
|
|
851
|
+
controller.enqueue(encoder.encode(sseComment("connected")));
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
liveController.signal.addEventListener("abort", () => {
|
|
855
|
+
request.signal.removeEventListener("abort", abortLive);
|
|
856
|
+
cleanupLiveConnection(connectionId);
|
|
857
|
+
}, { once: true });
|
|
858
|
+
return new Response(stream, { headers: sseHeaders });
|
|
859
|
+
} catch (error) {
|
|
860
|
+
const protocolError = toProtocolError(error);
|
|
861
|
+
return Response.json({
|
|
862
|
+
results: [{
|
|
863
|
+
error: protocolError,
|
|
864
|
+
id: "live",
|
|
865
|
+
ok: false
|
|
866
|
+
}],
|
|
867
|
+
version: 1
|
|
868
|
+
}, {
|
|
869
|
+
headers: jsonHeaders,
|
|
870
|
+
status: error instanceof FateRequestError ? error.status : 500
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
},
|
|
874
|
+
async handleRequest(request, adapterContext) {
|
|
875
|
+
try {
|
|
876
|
+
const body = assertProtocolRequest(await parseJSON(request));
|
|
877
|
+
const ctx = await getContext(request, adapterContext);
|
|
878
|
+
const results = await Promise.all(body.operations.map((operation) => executeOperation(operation, ctx)));
|
|
879
|
+
return Response.json({
|
|
880
|
+
results,
|
|
881
|
+
version: 1
|
|
882
|
+
}, { headers: jsonHeaders });
|
|
883
|
+
} catch (error) {
|
|
884
|
+
const protocolError = toProtocolError(error);
|
|
885
|
+
return Response.json({
|
|
886
|
+
results: [{
|
|
887
|
+
error: protocolError,
|
|
888
|
+
id: "request",
|
|
889
|
+
ok: false
|
|
890
|
+
}],
|
|
891
|
+
version: 1
|
|
892
|
+
}, {
|
|
893
|
+
headers: jsonHeaders,
|
|
894
|
+
status: error instanceof FateRequestError ? error.status : 500
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
manifest
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function createFateFetchHandler(server) {
|
|
902
|
+
return (request, adapterContext) => {
|
|
903
|
+
return new URL(request.url).pathname.endsWith("/live") ? server.handleLiveRequest(request, adapterContext) : server.handleRequest(request, adapterContext);
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function createHonoFateHandler(server) {
|
|
907
|
+
return (context) => {
|
|
908
|
+
return new URL(context.req.raw.url).pathname.endsWith("/live") ? server.handleLiveRequest(context.req.raw, context) : server.handleRequest(context.req.raw, context);
|
|
446
909
|
};
|
|
447
910
|
}
|
|
448
|
-
|
|
449
|
-
//#endregion
|
|
450
|
-
//#region src/server/input.ts
|
|
451
|
-
const byIdInput = z.object({
|
|
452
|
-
args: connectionArgs,
|
|
453
|
-
ids: z.array(z.string().min(1)).nonempty(),
|
|
454
|
-
select: z.array(z.string())
|
|
455
|
-
});
|
|
456
|
-
|
|
457
911
|
//#endregion
|
|
458
|
-
export { byIdInput, connectionArgs, createResolver, dataView, list, resolver, withConnection };
|
|
912
|
+
export { FateRequestError, bindSourceProcedures, byIdInput, computed, connectionArgs, count, createFateFetchHandler, createFateServer, createHonoFateHandler, createLiveEventBus, createNestedSourcePlan, createResolver, createSourcePlan, createSourceProcedures, dataView, field, getNestedSelection, getScopedArgs, hasNestedSelection, isRecord, list, liveConnectionTopic, liveEntityTopic, liveGlobalConnectionTopic, refetchSourceById, resolveSourceById, resolveSourceByIds, resolveSourceConnection, resolver, toPrismaSelect, withConnection };
|