@zenbujs/core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +11 -0
- package/dist/advice-config-CjgkEf2E.mjs +135 -0
- package/dist/advice-config-Cy133IQP.mjs +2 -0
- package/dist/advice-runtime.d.mts +35 -0
- package/dist/advice-runtime.mjs +131 -0
- package/dist/advice.d.mts +36 -0
- package/dist/advice.mjs +2 -0
- package/dist/base-window-BUt8pwbw.mjs +94 -0
- package/dist/base-window-DEIAk618.mjs +2 -0
- package/dist/build-config-pbv0w4oN.mjs +17 -0
- package/dist/build-electron-B4Gd0Gi4.mjs +516 -0
- package/dist/build-source-_q1n1zTV.mjs +162 -0
- package/dist/chunk-Dm34NbLt.mjs +6 -0
- package/dist/cli/bin.d.mts +1 -0
- package/dist/cli/bin.mjs +88 -0
- package/dist/cli/build.d.mts +53 -0
- package/dist/cli/build.mjs +48 -0
- package/dist/cli-BLbQQIVB.mjs +8054 -0
- package/dist/config-CdVrW85P.mjs +59 -0
- package/dist/config-LK73dJmO.mjs +2 -0
- package/dist/db-ByKPbnP6.mjs +2 -0
- package/dist/db-DhuAJrye.mjs +531 -0
- package/dist/db.d.mts +16 -0
- package/dist/db.mjs +16 -0
- package/dist/dev-BuqklM0k.mjs +85 -0
- package/dist/env-bootstrap-BtVME-CU.d.mts +16 -0
- package/dist/env-bootstrap-rj7I-59x.mjs +53 -0
- package/dist/env-bootstrap.d.mts +2 -0
- package/dist/env-bootstrap.mjs +2 -0
- package/dist/http-IBcLzbYu.mjs +2 -0
- package/dist/index-Bhlbyrn7.d.mts +63 -0
- package/dist/index-CPZ5d6Hl.d.mts +442 -0
- package/dist/index-FtE8MXJ_.d.mts +1 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +5 -0
- package/dist/launcher.mjs +173 -0
- package/dist/link-6roQ7Cn6.mjs +580 -0
- package/dist/loaders/zenbu.d.mts +22 -0
- package/dist/loaders/zenbu.mjs +267 -0
- package/dist/log-CyKv8hQg.mjs +20 -0
- package/dist/mirror-sync-CodOnwkD.mjs +332 -0
- package/dist/monorepo-CmGPHsVm.mjs +119 -0
- package/dist/node-D4M19_mV.mjs +5 -0
- package/dist/node-loader.d.mts +17 -0
- package/dist/node-loader.mjs +33 -0
- package/dist/pause-DvAUNmKn.mjs +52 -0
- package/dist/publish-source-BVgB62Zj.mjs +131 -0
- package/dist/react.d.mts +76 -0
- package/dist/react.mjs +291 -0
- package/dist/registry-Dh_e7HU1.d.mts +61 -0
- package/dist/registry.d.mts +2 -0
- package/dist/registry.mjs +1 -0
- package/dist/reloader-BCkLjDhS.mjs +2 -0
- package/dist/reloader-lLAJ3lqg.mjs +164 -0
- package/dist/renderer-host-Bg8QdeeH.mjs +1508 -0
- package/dist/renderer-host-DpvBPTHJ.mjs +2 -0
- package/dist/rpc-BwwQK6hD.mjs +71 -0
- package/dist/rpc-CqitnyR4.mjs +2 -0
- package/dist/rpc.d.mts +2 -0
- package/dist/rpc.mjs +2 -0
- package/dist/runtime-CjqDr8Yf.d.mts +109 -0
- package/dist/runtime-DUFKDIe4.mjs +409 -0
- package/dist/runtime.d.mts +2 -0
- package/dist/runtime.mjs +2 -0
- package/dist/schema-CIg4GzHQ.mjs +100 -0
- package/dist/schema-DMoSkwUx.d.mts +62 -0
- package/dist/schema-dGK6qkfR.mjs +28 -0
- package/dist/schema.d.mts +2 -0
- package/dist/schema.mjs +2 -0
- package/dist/server-BXwZEQ-n.mjs +66 -0
- package/dist/server-DjrZUbbu.mjs +2 -0
- package/dist/services/default.d.mts +11 -0
- package/dist/services/default.mjs +22 -0
- package/dist/services/index.d.mts +276 -0
- package/dist/services/index.mjs +7 -0
- package/dist/setup-gate-BeD6WS6d.mjs +110 -0
- package/dist/setup-gate-BqOzm7zp.d.mts +4 -0
- package/dist/setup-gate.d.mts +2 -0
- package/dist/setup-gate.mjs +2 -0
- package/dist/src-pELM4_iH.mjs +376 -0
- package/dist/trace-DCB7qFzT.mjs +10 -0
- package/dist/transform-DJH3vN4b.mjs +84041 -0
- package/dist/transport-BMSzG2-F.mjs +1045 -0
- package/dist/view-registry-BualWgAf.mjs +2 -0
- package/dist/vite-plugins-Bh3SCOw-.mjs +331 -0
- package/dist/vite.d.mts +68 -0
- package/dist/vite.mjs +2 -0
- package/dist/window-CM2a9Kyc.mjs +2 -0
- package/dist/window-CmmpCVX6.mjs +156 -0
- package/dist/write-9dRFczGJ.mjs +1248 -0
- package/migrations/0000_migration.ts +34 -0
- package/migrations/meta/0000_snapshot.json +18 -0
- package/migrations/meta/_journal.json +10 -0
- package/package.json +124 -0
|
@@ -0,0 +1,1045 @@
|
|
|
1
|
+
import { t as traceKyju } from "./trace-DCB7qFzT.mjs";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Ref from "effect/Ref";
|
|
4
|
+
import * as Context from "effect/Context";
|
|
5
|
+
import * as Stream from "effect/Stream";
|
|
6
|
+
import { nanoid } from "nanoid";
|
|
7
|
+
import * as Fiber from "effect/Fiber";
|
|
8
|
+
import * as SubscriptionRef from "effect/SubscriptionRef";
|
|
9
|
+
//#region ../kyju/src/v2/shared.ts
|
|
10
|
+
const SendContext = Context.GenericTag("SendContext");
|
|
11
|
+
const ClientStateRef = Context.GenericTag("ClientStateRef");
|
|
12
|
+
const MaxPageSizeContext = Context.GenericTag("MaxPageSizeContext");
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region ../kyju/src/v2/replica/helpers.ts
|
|
15
|
+
const makeRequestAwaiter = () => {
|
|
16
|
+
const pending = /* @__PURE__ */ new Map();
|
|
17
|
+
return {
|
|
18
|
+
resolve: (ack) => {
|
|
19
|
+
const entry = pending.get(ack.requestId);
|
|
20
|
+
if (entry) {
|
|
21
|
+
pending.delete(ack.requestId);
|
|
22
|
+
entry.resolve(ack);
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
await: (requestId) => new Promise((resolve) => {
|
|
26
|
+
pending.set(requestId, {
|
|
27
|
+
requestId,
|
|
28
|
+
resolve
|
|
29
|
+
});
|
|
30
|
+
})
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
const setAtPath = ({ root, path, value }) => {
|
|
34
|
+
if (path.length === 0) return value;
|
|
35
|
+
const [head, ...rest] = path;
|
|
36
|
+
const isIndex = /^\d+$/.test(head);
|
|
37
|
+
if (Array.isArray(root)) {
|
|
38
|
+
/**
|
|
39
|
+
* so react observes a new reference
|
|
40
|
+
*/
|
|
41
|
+
const arr = [...root];
|
|
42
|
+
const index = Number(head);
|
|
43
|
+
if (rest.length === 0) {
|
|
44
|
+
arr[index] = value;
|
|
45
|
+
return arr;
|
|
46
|
+
}
|
|
47
|
+
arr[index] = setAtPath({
|
|
48
|
+
root: arr[index] ?? (/^\d+$/.test(rest[0] ?? "") ? [] : {}),
|
|
49
|
+
path: rest,
|
|
50
|
+
value
|
|
51
|
+
});
|
|
52
|
+
return arr;
|
|
53
|
+
}
|
|
54
|
+
if (typeof root !== "object" || root === null) root = isIndex ? [] : {};
|
|
55
|
+
if (Array.isArray(root)) {
|
|
56
|
+
const arr = [...root];
|
|
57
|
+
const index = Number(head);
|
|
58
|
+
if (rest.length === 0) {
|
|
59
|
+
arr[index] = value;
|
|
60
|
+
return arr;
|
|
61
|
+
}
|
|
62
|
+
arr[index] = setAtPath({
|
|
63
|
+
root: arr[index] ?? (/^\d+$/.test(rest[0] ?? "") ? [] : {}),
|
|
64
|
+
path: rest,
|
|
65
|
+
value
|
|
66
|
+
});
|
|
67
|
+
return arr;
|
|
68
|
+
}
|
|
69
|
+
const obj = { ...root };
|
|
70
|
+
if (rest.length === 0) {
|
|
71
|
+
obj[head] = value;
|
|
72
|
+
return obj;
|
|
73
|
+
}
|
|
74
|
+
obj[head] = setAtPath({
|
|
75
|
+
root: obj[head] ?? (/^\d+$/.test(rest[0] ?? "") ? [] : {}),
|
|
76
|
+
path: rest,
|
|
77
|
+
value
|
|
78
|
+
});
|
|
79
|
+
return obj;
|
|
80
|
+
};
|
|
81
|
+
const requireConnected = ({ ref }) => Effect.gen(function* () {
|
|
82
|
+
const state = yield* Ref.get(ref);
|
|
83
|
+
if (state.kind === "disconnected") return yield* Effect.die("Cannot process event: not connected");
|
|
84
|
+
return state;
|
|
85
|
+
});
|
|
86
|
+
const applyState = ({ ref, fn }) => Ref.update(ref, (current) => {
|
|
87
|
+
if (current.kind === "disconnected") return current;
|
|
88
|
+
return fn(current);
|
|
89
|
+
});
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region ../kyju/src/v2/replica/replica.ts
|
|
92
|
+
const concatToCollection = (col, items) => {
|
|
93
|
+
if (items.length === 0) return col;
|
|
94
|
+
return {
|
|
95
|
+
...col,
|
|
96
|
+
items: [...col.items, ...items],
|
|
97
|
+
totalCount: col.totalCount + items.length
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
const applyRootSet = (ref, op) => applyState({
|
|
101
|
+
ref,
|
|
102
|
+
fn: (state) => ({
|
|
103
|
+
...state,
|
|
104
|
+
root: setAtPath({
|
|
105
|
+
root: state.root,
|
|
106
|
+
path: op.path,
|
|
107
|
+
value: op.value
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
});
|
|
111
|
+
const applyCollectionCreate = (ref, op) => {
|
|
112
|
+
const collection = {
|
|
113
|
+
id: op.collectionId,
|
|
114
|
+
totalCount: op.data?.length ?? 0,
|
|
115
|
+
items: op.data ?? []
|
|
116
|
+
};
|
|
117
|
+
return applyState({
|
|
118
|
+
ref,
|
|
119
|
+
fn: (state) => ({
|
|
120
|
+
...state,
|
|
121
|
+
collections: [...state.collections, collection]
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
const applyCollectionDelete = (ref, op) => applyState({
|
|
126
|
+
ref,
|
|
127
|
+
fn: (state) => ({
|
|
128
|
+
...state,
|
|
129
|
+
collections: state.collections.filter((col) => col.id !== op.collectionId)
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
const applyBlobCreate = (ref, op) => {
|
|
133
|
+
const blob = {
|
|
134
|
+
id: op.blobId,
|
|
135
|
+
data: op.hot ? {
|
|
136
|
+
kind: "hot",
|
|
137
|
+
value: op.data
|
|
138
|
+
} : { kind: "cold" },
|
|
139
|
+
fileSize: 0
|
|
140
|
+
};
|
|
141
|
+
return applyState({
|
|
142
|
+
ref,
|
|
143
|
+
fn: (state) => ({
|
|
144
|
+
...state,
|
|
145
|
+
blobs: [...state.blobs, blob]
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
const applyBlobSet = (ref, op) => applyState({
|
|
150
|
+
ref,
|
|
151
|
+
fn: (state) => ({
|
|
152
|
+
...state,
|
|
153
|
+
blobs: state.blobs.map((blob) => {
|
|
154
|
+
if (blob.id !== op.blobId) return blob;
|
|
155
|
+
if (blob.data.kind === "hot") return {
|
|
156
|
+
...blob,
|
|
157
|
+
data: {
|
|
158
|
+
kind: "hot",
|
|
159
|
+
value: op.data
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
return blob;
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
});
|
|
166
|
+
const applyBlobDelete = (ref, op) => applyState({
|
|
167
|
+
ref,
|
|
168
|
+
fn: (state) => ({
|
|
169
|
+
...state,
|
|
170
|
+
blobs: state.blobs.filter((blob) => blob.id !== op.blobId)
|
|
171
|
+
})
|
|
172
|
+
});
|
|
173
|
+
const applyWrite = ({ stateRef, op }) => {
|
|
174
|
+
switch (op.type) {
|
|
175
|
+
case "root.set": return applyRootSet(stateRef, op);
|
|
176
|
+
case "collection.create": return applyCollectionCreate(stateRef, op);
|
|
177
|
+
case "collection.concat": return applyState({
|
|
178
|
+
ref: stateRef,
|
|
179
|
+
fn: (state) => ({
|
|
180
|
+
...state,
|
|
181
|
+
collections: state.collections.map((col) => col.id === op.collectionId ? concatToCollection(col, op.data) : col)
|
|
182
|
+
})
|
|
183
|
+
});
|
|
184
|
+
case "collection.delete": return applyCollectionDelete(stateRef, op);
|
|
185
|
+
case "blob.create": return applyBlobCreate(stateRef, op);
|
|
186
|
+
case "blob.set": return applyBlobSet(stateRef, op);
|
|
187
|
+
case "blob.delete": return applyBlobDelete(stateRef, op);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const applyReplicatedWrite = ({ stateRef, op }) => {
|
|
191
|
+
switch (op.type) {
|
|
192
|
+
case "root.set": return applyRootSet(stateRef, op);
|
|
193
|
+
case "collection.create": return applyCollectionCreate(stateRef, op);
|
|
194
|
+
case "collection.concat": return applyState({
|
|
195
|
+
ref: stateRef,
|
|
196
|
+
fn: (state) => ({
|
|
197
|
+
...state,
|
|
198
|
+
collections: state.collections.map((col) => {
|
|
199
|
+
if (col.id !== op.collectionId) return col;
|
|
200
|
+
return concatToCollection(col, op.data);
|
|
201
|
+
})
|
|
202
|
+
})
|
|
203
|
+
});
|
|
204
|
+
case "collection.delete": return applyCollectionDelete(stateRef, op);
|
|
205
|
+
case "blob.create": return applyBlobCreate(stateRef, op);
|
|
206
|
+
case "blob.set": return applyBlobSet(stateRef, op);
|
|
207
|
+
case "blob.delete": return applyBlobDelete(stateRef, op);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const createReplicaEffect = (replicaId, collectionCallbacks) => Effect.gen(function* () {
|
|
211
|
+
const awaiter = makeRequestAwaiter();
|
|
212
|
+
const stateRef = yield* ClientStateRef;
|
|
213
|
+
const sendToServer = (event, requestId) => Effect.gen(function* () {
|
|
214
|
+
const { send } = yield* SendContext;
|
|
215
|
+
const promise = awaiter.await(requestId);
|
|
216
|
+
send(event);
|
|
217
|
+
return yield* Effect.promise(() => promise);
|
|
218
|
+
});
|
|
219
|
+
const mergeSubscribeAck = (ack, collectionId) => applyState({
|
|
220
|
+
ref: stateRef,
|
|
221
|
+
fn: (state) => {
|
|
222
|
+
const entry = {
|
|
223
|
+
id: collectionId,
|
|
224
|
+
totalCount: ack.totalCount,
|
|
225
|
+
items: ack.items
|
|
226
|
+
};
|
|
227
|
+
const exists = state.collections.some((col) => col.id === collectionId);
|
|
228
|
+
return {
|
|
229
|
+
...state,
|
|
230
|
+
collections: exists ? state.collections.map((col) => col.id === collectionId ? entry : col) : [...state.collections, entry]
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
const notifyConcatCallbacks = (op) => Effect.gen(function* () {
|
|
235
|
+
if (op.type !== "collection.concat") return;
|
|
236
|
+
const cbs = collectionCallbacks.get(op.collectionId);
|
|
237
|
+
if (!cbs || cbs.size === 0) return;
|
|
238
|
+
const currentState = yield* Ref.get(stateRef);
|
|
239
|
+
if (currentState.kind !== "connected") return;
|
|
240
|
+
const col = currentState.collections.find((c) => c.id === op.collectionId);
|
|
241
|
+
if (!col) return;
|
|
242
|
+
for (const cb of cbs) cb({
|
|
243
|
+
collectionId: op.collectionId,
|
|
244
|
+
newItems: op.data,
|
|
245
|
+
collection: col
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
const postMessage = (event) => Effect.gen(function* () {
|
|
249
|
+
switch (event.kind) {
|
|
250
|
+
case "connect": {
|
|
251
|
+
const requestId = nanoid();
|
|
252
|
+
const ack = yield* sendToServer({
|
|
253
|
+
kind: "connect",
|
|
254
|
+
message: {
|
|
255
|
+
type: "connect",
|
|
256
|
+
version: event.version,
|
|
257
|
+
requestId,
|
|
258
|
+
replicaId
|
|
259
|
+
}
|
|
260
|
+
}, requestId);
|
|
261
|
+
if (ack.error) return yield* Effect.fail(ack.error);
|
|
262
|
+
yield* Ref.set(stateRef, {
|
|
263
|
+
kind: "connected",
|
|
264
|
+
sessionId: ack.sessionId,
|
|
265
|
+
root: ack.root,
|
|
266
|
+
collections: [],
|
|
267
|
+
blobs: []
|
|
268
|
+
});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
case "disconnect": {
|
|
272
|
+
const state = yield* requireConnected({ ref: stateRef });
|
|
273
|
+
const requestId = nanoid();
|
|
274
|
+
const ack = yield* sendToServer({
|
|
275
|
+
kind: "disconnect",
|
|
276
|
+
replicaId,
|
|
277
|
+
message: {
|
|
278
|
+
type: "disconnect",
|
|
279
|
+
sessionId: state.sessionId,
|
|
280
|
+
requestId
|
|
281
|
+
}
|
|
282
|
+
}, requestId);
|
|
283
|
+
if (ack.error) return yield* Effect.fail(ack.error);
|
|
284
|
+
yield* Ref.set(stateRef, { kind: "disconnected" });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
case "subscribe-collection": {
|
|
288
|
+
const state = yield* requireConnected({ ref: stateRef });
|
|
289
|
+
const requestId = nanoid();
|
|
290
|
+
const ack = yield* sendToServer({
|
|
291
|
+
kind: "subscribe-collection",
|
|
292
|
+
collectionId: event.collectionId,
|
|
293
|
+
sessionId: state.sessionId,
|
|
294
|
+
requestId,
|
|
295
|
+
replicaId
|
|
296
|
+
}, requestId);
|
|
297
|
+
if (ack.error) return yield* Effect.fail(ack.error);
|
|
298
|
+
yield* mergeSubscribeAck(ack, event.collectionId);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
case "unsubscribe-collection": {
|
|
302
|
+
const state = yield* requireConnected({ ref: stateRef });
|
|
303
|
+
const requestId = nanoid();
|
|
304
|
+
yield* sendToServer({
|
|
305
|
+
kind: "unsubscribe-collection",
|
|
306
|
+
collectionId: event.collectionId,
|
|
307
|
+
sessionId: state.sessionId,
|
|
308
|
+
requestId,
|
|
309
|
+
replicaId
|
|
310
|
+
}, requestId);
|
|
311
|
+
yield* applyState({
|
|
312
|
+
ref: stateRef,
|
|
313
|
+
fn: (s) => ({
|
|
314
|
+
...s,
|
|
315
|
+
collections: s.collections.filter((col) => col.id !== event.collectionId)
|
|
316
|
+
})
|
|
317
|
+
});
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
case "write": {
|
|
321
|
+
const state = yield* requireConnected({ ref: stateRef });
|
|
322
|
+
yield* traceKyju("kyju:replica.applyWrite", applyWrite({
|
|
323
|
+
stateRef,
|
|
324
|
+
op: event.op
|
|
325
|
+
}), { op: event.op.type });
|
|
326
|
+
yield* notifyConcatCallbacks(event.op);
|
|
327
|
+
const writeRequestId = nanoid();
|
|
328
|
+
const writeAck = yield* traceKyju("kyju:replica.sendToServer", sendToServer({
|
|
329
|
+
kind: "write",
|
|
330
|
+
op: event.op,
|
|
331
|
+
sessionId: state.sessionId,
|
|
332
|
+
requestId: writeRequestId,
|
|
333
|
+
replicaId
|
|
334
|
+
}, writeRequestId), { op: event.op.type });
|
|
335
|
+
if (writeAck.error) return yield* Effect.fail(writeAck.error);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
case "replicated-write":
|
|
339
|
+
yield* requireConnected({ ref: stateRef });
|
|
340
|
+
yield* applyReplicatedWrite({
|
|
341
|
+
stateRef,
|
|
342
|
+
op: event.op
|
|
343
|
+
});
|
|
344
|
+
yield* notifyConcatCallbacks(event.op);
|
|
345
|
+
return;
|
|
346
|
+
case "read": {
|
|
347
|
+
const readState = yield* requireConnected({ ref: stateRef });
|
|
348
|
+
const readRequestId = nanoid();
|
|
349
|
+
const readOp = event.op;
|
|
350
|
+
switch (readOp.type) {
|
|
351
|
+
case "collection.fetch-range":
|
|
352
|
+
yield* sendToServer({
|
|
353
|
+
kind: "read",
|
|
354
|
+
op: readOp,
|
|
355
|
+
sessionId: readState.sessionId,
|
|
356
|
+
requestId: readRequestId,
|
|
357
|
+
replicaId
|
|
358
|
+
}, readRequestId);
|
|
359
|
+
return;
|
|
360
|
+
case "blob.read": {
|
|
361
|
+
const ack = yield* sendToServer({
|
|
362
|
+
kind: "read",
|
|
363
|
+
op: readOp,
|
|
364
|
+
sessionId: readState.sessionId,
|
|
365
|
+
requestId: readRequestId,
|
|
366
|
+
replicaId
|
|
367
|
+
}, readRequestId);
|
|
368
|
+
if (ack.error) return yield* Effect.fail(ack.error);
|
|
369
|
+
yield* applyState({
|
|
370
|
+
ref: stateRef,
|
|
371
|
+
fn: (state) => {
|
|
372
|
+
const entry = {
|
|
373
|
+
id: readOp.blobId,
|
|
374
|
+
data: {
|
|
375
|
+
kind: "hot",
|
|
376
|
+
value: ack.data
|
|
377
|
+
},
|
|
378
|
+
fileSize: 0
|
|
379
|
+
};
|
|
380
|
+
const exists = state.blobs.some((b) => b.id === readOp.blobId);
|
|
381
|
+
return {
|
|
382
|
+
...state,
|
|
383
|
+
blobs: exists ? state.blobs.map((b) => b.id === readOp.blobId ? entry : b) : [...state.blobs, entry]
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
case "db-update": {
|
|
392
|
+
const msg = event.message;
|
|
393
|
+
if (msg.type === "ack") {
|
|
394
|
+
awaiter.resolve(msg);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
yield* requireConnected({ ref: stateRef });
|
|
398
|
+
switch (msg.type) {
|
|
399
|
+
case "blob.metadataUpdate":
|
|
400
|
+
yield* applyState({
|
|
401
|
+
ref: stateRef,
|
|
402
|
+
fn: (state) => ({
|
|
403
|
+
...state,
|
|
404
|
+
blobs: state.blobs.map((blob) => blob.id === msg.blobId ? {
|
|
405
|
+
...blob,
|
|
406
|
+
fileSize: msg.fileSize
|
|
407
|
+
} : blob)
|
|
408
|
+
})
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
case "reconnect": {
|
|
412
|
+
const reconState = yield* requireConnected({ ref: stateRef });
|
|
413
|
+
const disconnectId = nanoid();
|
|
414
|
+
const disconnectAck = yield* sendToServer({
|
|
415
|
+
kind: "disconnect",
|
|
416
|
+
replicaId,
|
|
417
|
+
message: {
|
|
418
|
+
type: "disconnect",
|
|
419
|
+
sessionId: reconState.sessionId,
|
|
420
|
+
requestId: disconnectId
|
|
421
|
+
}
|
|
422
|
+
}, disconnectId);
|
|
423
|
+
if (disconnectAck.error) return yield* Effect.fail(disconnectAck.error);
|
|
424
|
+
yield* Ref.set(stateRef, { kind: "disconnected" });
|
|
425
|
+
const connectId = nanoid();
|
|
426
|
+
const connectAck = yield* sendToServer({
|
|
427
|
+
kind: "connect",
|
|
428
|
+
message: {
|
|
429
|
+
type: "connect",
|
|
430
|
+
version: 0,
|
|
431
|
+
requestId: connectId,
|
|
432
|
+
replicaId
|
|
433
|
+
}
|
|
434
|
+
}, connectId);
|
|
435
|
+
if (connectAck.error) return yield* Effect.fail(connectAck.error);
|
|
436
|
+
yield* Ref.set(stateRef, {
|
|
437
|
+
kind: "connected",
|
|
438
|
+
sessionId: connectAck.sessionId,
|
|
439
|
+
root: connectAck.root,
|
|
440
|
+
collections: [],
|
|
441
|
+
blobs: []
|
|
442
|
+
});
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
return { postMessage };
|
|
450
|
+
});
|
|
451
|
+
const createReplica = (args) => {
|
|
452
|
+
const replicaId = args.replicaId ?? nanoid();
|
|
453
|
+
const stateRef = Effect.runSync(SubscriptionRef.make({ kind: "disconnected" }));
|
|
454
|
+
const collectionCallbacks = /* @__PURE__ */ new Map();
|
|
455
|
+
const provide = (effect) => effect.pipe(Effect.provideService(SendContext, { send: args.send }), Effect.provideService(ClientStateRef, stateRef), Effect.provideService(MaxPageSizeContext, { maxPageSizeBytes: args.maxPageSizeBytes }));
|
|
456
|
+
const { postMessage: rawPostMessageEffect } = Effect.runSync(provide(createReplicaEffect(replicaId, collectionCallbacks)));
|
|
457
|
+
const postMessageEffect = (event) => provide(rawPostMessageEffect(event));
|
|
458
|
+
const postMessage = (event) => Effect.runPromise(postMessageEffect(event).pipe(Effect.catchAll(() => Effect.void)));
|
|
459
|
+
const onCollectionConcat = (collectionId, cb) => {
|
|
460
|
+
let cbs = collectionCallbacks.get(collectionId);
|
|
461
|
+
if (!cbs) {
|
|
462
|
+
cbs = /* @__PURE__ */ new Set();
|
|
463
|
+
collectionCallbacks.set(collectionId, cbs);
|
|
464
|
+
}
|
|
465
|
+
cbs.add(cb);
|
|
466
|
+
};
|
|
467
|
+
const offCollectionConcat = (collectionId, cb) => {
|
|
468
|
+
const cbs = collectionCallbacks.get(collectionId);
|
|
469
|
+
if (cbs) {
|
|
470
|
+
cbs.delete(cb);
|
|
471
|
+
if (cbs.size === 0) collectionCallbacks.delete(collectionId);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
const getState = () => Effect.runSync(Ref.get(stateRef));
|
|
475
|
+
const _forceState = (state) => {
|
|
476
|
+
Effect.runSync(Ref.set(stateRef, state));
|
|
477
|
+
};
|
|
478
|
+
const subscribe = (cb) => {
|
|
479
|
+
const fiber = Effect.runFork(Stream.runForEach(stateRef.changes, (state) => Effect.sync(() => cb(state))));
|
|
480
|
+
return () => {
|
|
481
|
+
Effect.runFork(Fiber.interrupt(fiber));
|
|
482
|
+
};
|
|
483
|
+
};
|
|
484
|
+
return {
|
|
485
|
+
postMessage,
|
|
486
|
+
postMessageEffect,
|
|
487
|
+
getState,
|
|
488
|
+
_forceState,
|
|
489
|
+
subscribe,
|
|
490
|
+
replicaId,
|
|
491
|
+
onCollectionConcat,
|
|
492
|
+
offCollectionConcat
|
|
493
|
+
};
|
|
494
|
+
};
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region ../kyju/src/v2/client/proxy.ts
|
|
497
|
+
/**
|
|
498
|
+
*
|
|
499
|
+
* we are recording the set of operations performed on a proxy object,
|
|
500
|
+
* so that we can later play them over and convert them into events
|
|
501
|
+
* to the kyju db
|
|
502
|
+
*
|
|
503
|
+
*/
|
|
504
|
+
const createRecordingProxy = (target) => {
|
|
505
|
+
const operations = [];
|
|
506
|
+
const dirtyArrayPaths = /* @__PURE__ */ new Map();
|
|
507
|
+
const markArrayDirty = (arrayPath, arr) => {
|
|
508
|
+
const key = arrayPath.join("\0");
|
|
509
|
+
if (!dirtyArrayPaths.has(key)) dirtyArrayPaths.set(key, {
|
|
510
|
+
path: arrayPath,
|
|
511
|
+
arr
|
|
512
|
+
});
|
|
513
|
+
};
|
|
514
|
+
const makeProxy = (obj, currentPath) => {
|
|
515
|
+
return new Proxy(obj, {
|
|
516
|
+
get(t, prop, receiver) {
|
|
517
|
+
if (typeof prop === "symbol") return Reflect.get(t, prop, receiver);
|
|
518
|
+
const value = t[prop];
|
|
519
|
+
if (value !== null && typeof value === "object") return makeProxy(value, [...currentPath, prop]);
|
|
520
|
+
return value;
|
|
521
|
+
},
|
|
522
|
+
set(t, prop, value) {
|
|
523
|
+
if (typeof prop === "symbol") {
|
|
524
|
+
t[prop] = value;
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
t[prop] = value;
|
|
528
|
+
if (Array.isArray(t)) markArrayDirty(currentPath, t);
|
|
529
|
+
else operations.push({
|
|
530
|
+
path: [...currentPath, prop],
|
|
531
|
+
value
|
|
532
|
+
});
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
};
|
|
537
|
+
const snapshot = JSON.parse(JSON.stringify(target));
|
|
538
|
+
return {
|
|
539
|
+
proxy: makeProxy(snapshot, []),
|
|
540
|
+
getOperations: () => {
|
|
541
|
+
const result = [...operations];
|
|
542
|
+
for (const { path, arr } of dirtyArrayPaths.values()) result.push({
|
|
543
|
+
path,
|
|
544
|
+
value: arr
|
|
545
|
+
});
|
|
546
|
+
return result;
|
|
547
|
+
},
|
|
548
|
+
snapshot
|
|
549
|
+
};
|
|
550
|
+
};
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region ../kyju/src/v2/client/client.ts
|
|
553
|
+
function createClientCore(send, replica) {
|
|
554
|
+
const subscriberCounts = /* @__PURE__ */ new Map();
|
|
555
|
+
const subscribePromises = /* @__PURE__ */ new Map();
|
|
556
|
+
const getConnectedState = () => {
|
|
557
|
+
const state = replica.getState();
|
|
558
|
+
if (state.kind === "disconnected") throw new Error("Cannot read state: not connected");
|
|
559
|
+
return state;
|
|
560
|
+
};
|
|
561
|
+
const getRoot = () => getConnectedState().root;
|
|
562
|
+
const getAtPath = (obj, path) => {
|
|
563
|
+
let current = obj;
|
|
564
|
+
for (const key of path) {
|
|
565
|
+
if (current == null) return void 0;
|
|
566
|
+
current = current[key];
|
|
567
|
+
}
|
|
568
|
+
return current;
|
|
569
|
+
};
|
|
570
|
+
const isCollectionValue = (val) => val != null && typeof val === "object" && "collectionId" in val;
|
|
571
|
+
const isBlobValue = (val) => val != null && typeof val === "object" && "blobId" in val;
|
|
572
|
+
const subscribeToPath = (path, cb) => {
|
|
573
|
+
let prev;
|
|
574
|
+
let initialized = false;
|
|
575
|
+
return replica.subscribe((state) => {
|
|
576
|
+
if (state.kind === "disconnected") return;
|
|
577
|
+
const value = getAtPath(state.root, path);
|
|
578
|
+
if (!initialized || !Object.is(prev, value)) {
|
|
579
|
+
prev = value;
|
|
580
|
+
initialized = true;
|
|
581
|
+
cb(value);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
};
|
|
585
|
+
const makeCollectionNode = (fieldKey) => {
|
|
586
|
+
const getCollectionId = () => {
|
|
587
|
+
return getAtPath(getRoot(), [fieldKey])?.collectionId;
|
|
588
|
+
};
|
|
589
|
+
return {
|
|
590
|
+
concat: (items) => {
|
|
591
|
+
return send({
|
|
592
|
+
kind: "write",
|
|
593
|
+
op: {
|
|
594
|
+
type: "collection.concat",
|
|
595
|
+
collectionId: getCollectionId(),
|
|
596
|
+
data: items
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
},
|
|
600
|
+
read: (range) => send({
|
|
601
|
+
kind: "read",
|
|
602
|
+
op: {
|
|
603
|
+
type: "collection.fetch-range",
|
|
604
|
+
collectionId: getCollectionId(),
|
|
605
|
+
range: range ?? {
|
|
606
|
+
start: 0,
|
|
607
|
+
end: Number.MAX_SAFE_INTEGER
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}),
|
|
611
|
+
create: (data) => Effect.gen(function* () {
|
|
612
|
+
const collectionId = nanoid();
|
|
613
|
+
yield* send({
|
|
614
|
+
kind: "write",
|
|
615
|
+
op: {
|
|
616
|
+
type: "collection.create",
|
|
617
|
+
collectionId,
|
|
618
|
+
data
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
yield* send({
|
|
622
|
+
kind: "write",
|
|
623
|
+
op: {
|
|
624
|
+
type: "root.set",
|
|
625
|
+
path: [fieldKey],
|
|
626
|
+
value: {
|
|
627
|
+
collectionId,
|
|
628
|
+
debugName: fieldKey
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
}),
|
|
633
|
+
delete: () => send({
|
|
634
|
+
kind: "write",
|
|
635
|
+
op: {
|
|
636
|
+
type: "collection.delete",
|
|
637
|
+
collectionId: getCollectionId()
|
|
638
|
+
}
|
|
639
|
+
}),
|
|
640
|
+
subscribe: (cb) => subscribeToPath([fieldKey], cb),
|
|
641
|
+
subscribeData: (cb) => makeCollectionNodeAtPath([fieldKey]).subscribeData(cb)
|
|
642
|
+
};
|
|
643
|
+
};
|
|
644
|
+
const makeCollectionNodeAtPath = (path) => {
|
|
645
|
+
const getCollectionId = () => {
|
|
646
|
+
return getAtPath(getRoot(), path)?.collectionId;
|
|
647
|
+
};
|
|
648
|
+
return {
|
|
649
|
+
concat: (items) => send({
|
|
650
|
+
kind: "write",
|
|
651
|
+
op: {
|
|
652
|
+
type: "collection.concat",
|
|
653
|
+
collectionId: getCollectionId(),
|
|
654
|
+
data: items
|
|
655
|
+
}
|
|
656
|
+
}),
|
|
657
|
+
read: (range) => send({
|
|
658
|
+
kind: "read",
|
|
659
|
+
op: {
|
|
660
|
+
type: "collection.fetch-range",
|
|
661
|
+
collectionId: getCollectionId(),
|
|
662
|
+
range: range ?? {
|
|
663
|
+
start: 0,
|
|
664
|
+
end: Number.MAX_SAFE_INTEGER
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}),
|
|
668
|
+
create: (data) => Effect.gen(function* () {
|
|
669
|
+
const collectionId = nanoid();
|
|
670
|
+
yield* send({
|
|
671
|
+
kind: "write",
|
|
672
|
+
op: {
|
|
673
|
+
type: "collection.create",
|
|
674
|
+
collectionId,
|
|
675
|
+
data
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
yield* send({
|
|
679
|
+
kind: "write",
|
|
680
|
+
op: {
|
|
681
|
+
type: "root.set",
|
|
682
|
+
path,
|
|
683
|
+
value: {
|
|
684
|
+
collectionId,
|
|
685
|
+
debugName: path[path.length - 1] ?? ""
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
}),
|
|
690
|
+
delete: () => send({
|
|
691
|
+
kind: "write",
|
|
692
|
+
op: {
|
|
693
|
+
type: "collection.delete",
|
|
694
|
+
collectionId: getCollectionId()
|
|
695
|
+
}
|
|
696
|
+
}),
|
|
697
|
+
subscribe: (cb) => subscribeToPath(path, cb),
|
|
698
|
+
subscribeData: (cb) => {
|
|
699
|
+
const collectionId = getCollectionId();
|
|
700
|
+
const count = subscriberCounts.get(collectionId) ?? 0;
|
|
701
|
+
subscriberCounts.set(collectionId, count + 1);
|
|
702
|
+
const wrappedCb = (data) => {
|
|
703
|
+
cb({
|
|
704
|
+
collection: data.collection,
|
|
705
|
+
newItems: data.newItems
|
|
706
|
+
});
|
|
707
|
+
};
|
|
708
|
+
replica.onCollectionConcat(collectionId, wrappedCb);
|
|
709
|
+
const deliverInitial = () => {
|
|
710
|
+
const state = replica.getState();
|
|
711
|
+
if (state.kind === "connected") {
|
|
712
|
+
const col = state.collections.find((c) => c.id === collectionId);
|
|
713
|
+
if (col) cb({
|
|
714
|
+
collection: col,
|
|
715
|
+
newItems: col.items
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
if (count === 0) {
|
|
720
|
+
const p = Effect.runPromise(send({
|
|
721
|
+
kind: "subscribe-collection",
|
|
722
|
+
collectionId
|
|
723
|
+
}).pipe(Effect.catchAll(() => Effect.void))).then(deliverInitial);
|
|
724
|
+
subscribePromises.set(collectionId, p);
|
|
725
|
+
} else {
|
|
726
|
+
const existing = subscribePromises.get(collectionId);
|
|
727
|
+
if (existing) existing.then(deliverInitial);
|
|
728
|
+
else deliverInitial();
|
|
729
|
+
}
|
|
730
|
+
let unsubscribed = false;
|
|
731
|
+
return () => {
|
|
732
|
+
if (unsubscribed) return;
|
|
733
|
+
unsubscribed = true;
|
|
734
|
+
replica.offCollectionConcat(collectionId, wrappedCb);
|
|
735
|
+
const current = subscriberCounts.get(collectionId) ?? 1;
|
|
736
|
+
if (current <= 1) {
|
|
737
|
+
subscriberCounts.delete(collectionId);
|
|
738
|
+
subscribePromises.delete(collectionId);
|
|
739
|
+
Effect.runPromise(send({
|
|
740
|
+
kind: "unsubscribe-collection",
|
|
741
|
+
collectionId
|
|
742
|
+
}).pipe(Effect.catchAll(() => Effect.void)));
|
|
743
|
+
} else subscriberCounts.set(collectionId, current - 1);
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
};
|
|
748
|
+
const makeBlobNode = (fieldKey) => {
|
|
749
|
+
const getBlobId = () => {
|
|
750
|
+
return getAtPath(getRoot(), [fieldKey])?.blobId;
|
|
751
|
+
};
|
|
752
|
+
return {
|
|
753
|
+
read: () => send({
|
|
754
|
+
kind: "read",
|
|
755
|
+
op: {
|
|
756
|
+
type: "blob.read",
|
|
757
|
+
blobId: getBlobId()
|
|
758
|
+
}
|
|
759
|
+
}),
|
|
760
|
+
set: (data) => send({
|
|
761
|
+
kind: "write",
|
|
762
|
+
op: {
|
|
763
|
+
type: "blob.set",
|
|
764
|
+
blobId: getBlobId(),
|
|
765
|
+
data
|
|
766
|
+
}
|
|
767
|
+
}),
|
|
768
|
+
create: (data, hot) => Effect.gen(function* () {
|
|
769
|
+
const blobId = nanoid();
|
|
770
|
+
yield* send({
|
|
771
|
+
kind: "write",
|
|
772
|
+
op: {
|
|
773
|
+
type: "blob.create",
|
|
774
|
+
blobId,
|
|
775
|
+
data,
|
|
776
|
+
hot
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
yield* send({
|
|
780
|
+
kind: "write",
|
|
781
|
+
op: {
|
|
782
|
+
type: "root.set",
|
|
783
|
+
path: [fieldKey],
|
|
784
|
+
value: {
|
|
785
|
+
blobId,
|
|
786
|
+
debugName: fieldKey
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}),
|
|
791
|
+
delete: () => send({
|
|
792
|
+
kind: "write",
|
|
793
|
+
op: {
|
|
794
|
+
type: "blob.delete",
|
|
795
|
+
blobId: getBlobId()
|
|
796
|
+
}
|
|
797
|
+
})
|
|
798
|
+
};
|
|
799
|
+
};
|
|
800
|
+
const ARRAY_READ_METHODS = new Set([
|
|
801
|
+
"map",
|
|
802
|
+
"filter",
|
|
803
|
+
"slice",
|
|
804
|
+
"flatMap",
|
|
805
|
+
"reduce",
|
|
806
|
+
"reduceRight",
|
|
807
|
+
"forEach",
|
|
808
|
+
"some",
|
|
809
|
+
"every",
|
|
810
|
+
"indexOf",
|
|
811
|
+
"includes",
|
|
812
|
+
"join",
|
|
813
|
+
"findIndex",
|
|
814
|
+
"keys",
|
|
815
|
+
"values",
|
|
816
|
+
"entries",
|
|
817
|
+
"flat",
|
|
818
|
+
"toString"
|
|
819
|
+
]);
|
|
820
|
+
const makeFieldNode = (path) => new Proxy({}, { get(_, prop) {
|
|
821
|
+
if (typeof prop === "symbol") {
|
|
822
|
+
const val = getAtPath(getRoot(), path);
|
|
823
|
+
if (Array.isArray(val) && prop === Symbol.iterator) return val[Symbol.iterator].bind(val);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
const val = getAtPath(getRoot(), path);
|
|
827
|
+
if (isCollectionValue(val)) {
|
|
828
|
+
const node = makeCollectionNodeAtPath(path);
|
|
829
|
+
if (prop in node || prop === "concat" || prop === "subscribeData" || prop === "read" || prop === "create" || prop === "delete" || prop === "subscribe") return node[prop];
|
|
830
|
+
}
|
|
831
|
+
if (Array.isArray(val)) switch (prop) {
|
|
832
|
+
case "read": return () => val;
|
|
833
|
+
case "set": return (value) => send({
|
|
834
|
+
kind: "write",
|
|
835
|
+
op: {
|
|
836
|
+
type: "root.set",
|
|
837
|
+
path,
|
|
838
|
+
value
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
case "subscribe": return (cb) => subscribeToPath(path, cb);
|
|
842
|
+
case "length": return val.length;
|
|
843
|
+
case "find": return (predicate) => {
|
|
844
|
+
const index = val.findIndex(predicate);
|
|
845
|
+
if (index < 0) return void 0;
|
|
846
|
+
return makeFieldNode([...path, String(index)]);
|
|
847
|
+
};
|
|
848
|
+
case "at": return (i) => {
|
|
849
|
+
const resolved = i < 0 ? val.length + i : i;
|
|
850
|
+
if (resolved < 0 || resolved >= val.length) return void 0;
|
|
851
|
+
return makeFieldNode([...path, String(resolved)]);
|
|
852
|
+
};
|
|
853
|
+
default:
|
|
854
|
+
if (ARRAY_READ_METHODS.has(prop)) return Array.prototype[prop].bind(val);
|
|
855
|
+
if (/^\d+$/.test(prop)) return makeFieldNode([...path, prop]);
|
|
856
|
+
return makeFieldNode([...path, prop]);
|
|
857
|
+
}
|
|
858
|
+
switch (prop) {
|
|
859
|
+
case "read": return () => val;
|
|
860
|
+
case "set": return (value) => send({
|
|
861
|
+
kind: "write",
|
|
862
|
+
op: {
|
|
863
|
+
type: "root.set",
|
|
864
|
+
path,
|
|
865
|
+
value
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
case "subscribe": return (cb) => subscribeToPath(path, cb);
|
|
869
|
+
default: return makeFieldNode([...path, prop]);
|
|
870
|
+
}
|
|
871
|
+
} });
|
|
872
|
+
const readRootFn = () => getRoot();
|
|
873
|
+
const updateFn = (fn) => traceKyju("kyju:client.update", Effect.gen(function* () {
|
|
874
|
+
const { proxy, getOperations } = createRecordingProxy(getRoot());
|
|
875
|
+
const result = fn(proxy);
|
|
876
|
+
if (result !== void 0) yield* send({
|
|
877
|
+
kind: "write",
|
|
878
|
+
op: {
|
|
879
|
+
type: "root.set",
|
|
880
|
+
path: [],
|
|
881
|
+
value: result
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
else {
|
|
885
|
+
const ops = getOperations();
|
|
886
|
+
for (const op of ops) yield* send({
|
|
887
|
+
kind: "write",
|
|
888
|
+
op: {
|
|
889
|
+
type: "root.set",
|
|
890
|
+
path: op.path,
|
|
891
|
+
value: op.value
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
}));
|
|
896
|
+
const createBlobFn = (data, hot) => Effect.gen(function* () {
|
|
897
|
+
const blobId = nanoid();
|
|
898
|
+
yield* send({
|
|
899
|
+
kind: "write",
|
|
900
|
+
op: {
|
|
901
|
+
type: "blob.create",
|
|
902
|
+
blobId,
|
|
903
|
+
data,
|
|
904
|
+
hot
|
|
905
|
+
}
|
|
906
|
+
});
|
|
907
|
+
return blobId;
|
|
908
|
+
});
|
|
909
|
+
const deleteBlobFn = (blobId) => send({
|
|
910
|
+
kind: "write",
|
|
911
|
+
op: {
|
|
912
|
+
type: "blob.delete",
|
|
913
|
+
blobId
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
const getBlobDataFn = (blobId) => Effect.gen(function* () {
|
|
917
|
+
const state = replica.getState();
|
|
918
|
+
if (state.kind !== "connected") return null;
|
|
919
|
+
const blob = state.blobs.find((b) => b.id === blobId);
|
|
920
|
+
if (blob && blob.data.kind === "hot") return blob.data.value;
|
|
921
|
+
yield* send({
|
|
922
|
+
kind: "read",
|
|
923
|
+
op: {
|
|
924
|
+
type: "blob.read",
|
|
925
|
+
blobId
|
|
926
|
+
}
|
|
927
|
+
}).pipe(Effect.catchAll(() => Effect.succeed(void 0)));
|
|
928
|
+
const newState = replica.getState();
|
|
929
|
+
if (newState.kind !== "connected") return null;
|
|
930
|
+
const warmed = newState.blobs.find((b) => b.id === blobId);
|
|
931
|
+
if (!warmed || warmed.data.kind !== "hot") return null;
|
|
932
|
+
return warmed.data.value;
|
|
933
|
+
});
|
|
934
|
+
return new Proxy({}, { get(_, prop) {
|
|
935
|
+
if (typeof prop === "symbol") return void 0;
|
|
936
|
+
if (prop === "readRoot") return readRootFn;
|
|
937
|
+
if (prop === "update") return updateFn;
|
|
938
|
+
if (prop === "createBlob") return createBlobFn;
|
|
939
|
+
if (prop === "deleteBlob") return deleteBlobFn;
|
|
940
|
+
if (prop === "getBlobData") return getBlobDataFn;
|
|
941
|
+
const rootVal = getAtPath(getRoot(), [prop]);
|
|
942
|
+
if (isCollectionValue(rootVal)) return makeCollectionNode(prop);
|
|
943
|
+
if (isBlobValue(rootVal)) return makeBlobNode(prop);
|
|
944
|
+
return makeFieldNode([prop]);
|
|
945
|
+
} });
|
|
946
|
+
}
|
|
947
|
+
const createEffectClient = (replica) => createClientCore(replica.postMessageEffect, replica);
|
|
948
|
+
function wrapEffectProxy(target) {
|
|
949
|
+
return new Proxy(target, { get(t, prop) {
|
|
950
|
+
if (typeof prop === "symbol") return Reflect.get(t, prop);
|
|
951
|
+
const val = t[prop];
|
|
952
|
+
if (typeof val === "function") return (...args) => {
|
|
953
|
+
const result = val(...args);
|
|
954
|
+
if (Effect.isEffect(result)) return Effect.runPromise(result.pipe(Effect.catchAll(() => Effect.void)));
|
|
955
|
+
if (result != null && typeof result === "object" && !Array.isArray(result)) return wrapEffectProxy(result);
|
|
956
|
+
return result;
|
|
957
|
+
};
|
|
958
|
+
if (val != null && typeof val === "object" && !Array.isArray(val)) return wrapEffectProxy(val);
|
|
959
|
+
return val;
|
|
960
|
+
} });
|
|
961
|
+
}
|
|
962
|
+
const createClient = (replica) => {
|
|
963
|
+
return wrapEffectProxy(createEffectClient("postMessageEffect" in replica ? replica : {
|
|
964
|
+
...replica,
|
|
965
|
+
postMessageEffect: (event) => Effect.promise(() => replica.postMessage(event))
|
|
966
|
+
}));
|
|
967
|
+
};
|
|
968
|
+
//#endregion
|
|
969
|
+
//#region ../kyju/src/v2/transport.ts
|
|
970
|
+
const UINT8_TAG = "__$u8";
|
|
971
|
+
function dbStringify(value) {
|
|
972
|
+
return JSON.stringify(value, (_key, val) => {
|
|
973
|
+
if (val instanceof Uint8Array) {
|
|
974
|
+
let binary = "";
|
|
975
|
+
for (let i = 0; i < val.length; i++) binary += String.fromCharCode(val[i]);
|
|
976
|
+
return { [UINT8_TAG]: btoa(binary) };
|
|
977
|
+
}
|
|
978
|
+
return val;
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
function dbParse(text) {
|
|
982
|
+
return JSON.parse(text, (_key, val) => {
|
|
983
|
+
if (val !== null && typeof val === "object") {
|
|
984
|
+
if (UINT8_TAG in val) {
|
|
985
|
+
const binary = atob(val[UINT8_TAG]);
|
|
986
|
+
const bytes = new Uint8Array(binary.length);
|
|
987
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
988
|
+
return bytes;
|
|
989
|
+
}
|
|
990
|
+
if (val.type === "Buffer" && Array.isArray(val.data)) return new Uint8Array(val.data);
|
|
991
|
+
}
|
|
992
|
+
return val;
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
function extractReplicaId(event) {
|
|
996
|
+
if (event.kind === "connect") return event.message.replicaId;
|
|
997
|
+
if ("replicaId" in event) return event.replicaId;
|
|
998
|
+
}
|
|
999
|
+
function createRouter() {
|
|
1000
|
+
const routes = /* @__PURE__ */ new Map();
|
|
1001
|
+
return {
|
|
1002
|
+
send(event) {
|
|
1003
|
+
routes.get(event.replicaId)?.(event);
|
|
1004
|
+
},
|
|
1005
|
+
connection(opts) {
|
|
1006
|
+
let replicaId;
|
|
1007
|
+
return {
|
|
1008
|
+
receive(event) {
|
|
1009
|
+
const rid = extractReplicaId(event);
|
|
1010
|
+
if (rid && !replicaId) {
|
|
1011
|
+
replicaId = rid;
|
|
1012
|
+
routes.set(rid, opts.send);
|
|
1013
|
+
}
|
|
1014
|
+
return opts.postMessage(event);
|
|
1015
|
+
},
|
|
1016
|
+
close() {
|
|
1017
|
+
if (replicaId) routes.delete(replicaId);
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
async function connectReplica(opts) {
|
|
1024
|
+
const replica = createReplica({
|
|
1025
|
+
send: opts.send,
|
|
1026
|
+
maxPageSizeBytes: opts.maxPageSizeBytes ?? 1024 * 1024
|
|
1027
|
+
});
|
|
1028
|
+
const unsub = opts.subscribe((event) => {
|
|
1029
|
+
if (event.replicaId === replica.replicaId) replica.postMessage(event);
|
|
1030
|
+
});
|
|
1031
|
+
await replica.postMessage({
|
|
1032
|
+
kind: "connect",
|
|
1033
|
+
version: 0
|
|
1034
|
+
});
|
|
1035
|
+
return {
|
|
1036
|
+
client: createClient(replica),
|
|
1037
|
+
replica,
|
|
1038
|
+
disconnect: async () => {
|
|
1039
|
+
await replica.postMessage({ kind: "disconnect" }).catch(() => {});
|
|
1040
|
+
unsub();
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
//#endregion
|
|
1045
|
+
export { createClient as a, dbStringify as i, createRouter as n, createEffectClient as o, dbParse as r, createReplica as s, connectReplica as t };
|