@lunora/do 1.0.0-alpha.30 → 1.0.0-alpha.32
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/dist/index.d.mts +105 -6
- package/dist/index.d.ts +105 -6
- package/dist/index.mjs +9 -8
- package/dist/packem_shared/{MAX_SQL_ROWS-D57CJaT9.mjs → MAX_SQL_ROWS-iFAA8FbD.mjs} +32 -2
- package/dist/packem_shared/{ROOT_DO_SIZE_WARN_BYTES-BA8QOChj.mjs → ROOT_DO_SIZE_WARN_BYTES-BWz51mpB.mjs} +152 -28
- package/dist/packem_shared/{ReactiveCache-BYlSGY0N.mjs → ReactiveCache-DnSvbjil.mjs} +4 -3
- package/dist/packem_shared/{diffExternalSource-Cx9HUPJj.mjs → diffExternalSource-CovHfdyo.mjs} +1 -1
- package/dist/packem_shared/isSoftDeleted-i5kKh6Up.mjs +183 -0
- package/dist/packem_shared/{materializeExternalRows-CTqZisSC.mjs → materializeExternalRows-FyA5Rwn4.mjs} +22 -3
- package/dist/packem_shared/{stableStringify-MydiuScU.mjs → stableStringify-mC40mZts.mjs} +2 -2
- package/dist/packem_shared/stableWireKey-DKuXO7T5.mjs +6 -0
- package/dist/packem_shared/subscriptionListDeltas-CT76bYny.mjs +126 -0
- package/dist/packem_shared/{subscription-delivery-CWigSEr3.mjs → wire-codec-CzQc1pvf.mjs} +1 -124
- package/package.json +3 -3
- package/dist/packem_shared/isSourceDue-CYkt7Ru8.mjs +0 -41
- package/dist/packem_shared/subscriptionListDeltas-DRLvFlM1.mjs +0 -1
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { e as encodeWire } from './wire-codec-CzQc1pvf.mjs';
|
|
2
|
+
|
|
3
|
+
const ROW_ID_FIELD = "_id";
|
|
4
|
+
const DELTA_FALLBACK_TABLE = "__lunora__";
|
|
5
|
+
const readRowId = (row) => {
|
|
6
|
+
if (typeof row !== "object" || row === null || Array.isArray(row)) {
|
|
7
|
+
return void 0;
|
|
8
|
+
}
|
|
9
|
+
const id = row[ROW_ID_FIELD];
|
|
10
|
+
return typeof id === "string" ? id : void 0;
|
|
11
|
+
};
|
|
12
|
+
const indexRowsById = (rows) => {
|
|
13
|
+
const byId = /* @__PURE__ */ new Map();
|
|
14
|
+
const order = [];
|
|
15
|
+
for (const row of rows) {
|
|
16
|
+
const id = readRowId(row);
|
|
17
|
+
if (id === void 0 || byId.has(id)) {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
byId.set(id, row);
|
|
21
|
+
order.push(id);
|
|
22
|
+
}
|
|
23
|
+
return { byId, order };
|
|
24
|
+
};
|
|
25
|
+
const survivorsKeepOrder = (previous, next) => {
|
|
26
|
+
const survivingPrevious = previous.order.filter((id) => next.byId.has(id));
|
|
27
|
+
const survivingNext = next.order.filter((id) => previous.byId.has(id));
|
|
28
|
+
if (survivingPrevious.length !== survivingNext.length) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return survivingPrevious.every((id, index) => survivingNext[index] === id);
|
|
32
|
+
};
|
|
33
|
+
const collectDeleteDeltas = (previous, next, deltaTable, tableJson) => {
|
|
34
|
+
const out = [];
|
|
35
|
+
for (const id of previous.order) {
|
|
36
|
+
if (!next.byId.has(id)) {
|
|
37
|
+
out.push({
|
|
38
|
+
delta: { key: id, op: "delete", table: deltaTable },
|
|
39
|
+
frame: `{"key":${JSON.stringify(id)},"op":"delete","table":${tableJson}}`
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
};
|
|
45
|
+
const collectUpsertDeltas = (previous, next, deltaTable, tableJson) => {
|
|
46
|
+
const out = [];
|
|
47
|
+
for (const id of next.order) {
|
|
48
|
+
const nextRow = next.byId.get(id);
|
|
49
|
+
const previousRow = previous.byId.get(id);
|
|
50
|
+
const nextFingerprint = JSON.stringify(encodeWire(nextRow));
|
|
51
|
+
const previousFingerprint = previousRow === void 0 ? void 0 : JSON.stringify(previousRow);
|
|
52
|
+
if (previousFingerprint === nextFingerprint) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const op = previousFingerprint === void 0 ? "insert" : "update";
|
|
56
|
+
out.push({
|
|
57
|
+
delta: { key: id, op, row: nextRow, table: deltaTable },
|
|
58
|
+
frame: `{"key":${JSON.stringify(id)},"op":"${op}","row":${nextFingerprint},"table":${tableJson}}`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
};
|
|
63
|
+
const subscriptionListDeltas = (previousJson, nextResult, table, frames) => {
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(previousJson);
|
|
67
|
+
} catch {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
if (!Array.isArray(parsed) || !Array.isArray(nextResult)) {
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
const previous = indexRowsById(parsed);
|
|
74
|
+
const next = indexRowsById(nextResult);
|
|
75
|
+
if (previous === void 0 || next === void 0) {
|
|
76
|
+
return void 0;
|
|
77
|
+
}
|
|
78
|
+
if (!survivorsKeepOrder(previous, next)) {
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
const deltaTable = table === "" ? DELTA_FALLBACK_TABLE : table;
|
|
82
|
+
const tableJson = JSON.stringify(deltaTable);
|
|
83
|
+
const framed = [...collectDeleteDeltas(previous, next, deltaTable, tableJson), ...collectUpsertDeltas(previous, next, deltaTable, tableJson)];
|
|
84
|
+
if (framed.length > next.order.length) {
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
if (frames !== void 0) {
|
|
88
|
+
for (const { frame } of framed) {
|
|
89
|
+
frames.push(frame);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return framed.map(({ delta }) => delta);
|
|
93
|
+
};
|
|
94
|
+
const trySendFrame = (ws, frame) => {
|
|
95
|
+
try {
|
|
96
|
+
ws.send(frame);
|
|
97
|
+
return true;
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const sendDeltaFrames = (ws, subId, deltaFrames, cursorSuffix) => {
|
|
103
|
+
const idJson = JSON.stringify(subId);
|
|
104
|
+
let delivered = true;
|
|
105
|
+
for (const deltaBody of deltaFrames) {
|
|
106
|
+
if (!trySendFrame(ws, `{"type":"delta","id":${idJson},"delta":${deltaBody}${cursorSuffix}}`)) {
|
|
107
|
+
delivered = false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return delivered;
|
|
111
|
+
};
|
|
112
|
+
const awaitWsDrain = async (ws) => {
|
|
113
|
+
let attempts = 0;
|
|
114
|
+
while (attempts < 100) {
|
|
115
|
+
attempts += 1;
|
|
116
|
+
const buffered = ws.bufferedAmount;
|
|
117
|
+
if (typeof buffered !== "number" || buffered < 1048576) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
await new Promise((resolve) => {
|
|
121
|
+
setTimeout(resolve, 20);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export { awaitWsDrain, sendDeltaFrames, subscriptionListDeltas, trySendFrame };
|
|
@@ -222,127 +222,4 @@ const decodeWire = (value, depth = 0) => {
|
|
|
222
222
|
return result;
|
|
223
223
|
};
|
|
224
224
|
|
|
225
|
-
|
|
226
|
-
const DELTA_FALLBACK_TABLE = "__lunora__";
|
|
227
|
-
const readRowId = (row) => {
|
|
228
|
-
if (typeof row !== "object" || row === null || Array.isArray(row)) {
|
|
229
|
-
return void 0;
|
|
230
|
-
}
|
|
231
|
-
const id = row[ROW_ID_FIELD];
|
|
232
|
-
return typeof id === "string" ? id : void 0;
|
|
233
|
-
};
|
|
234
|
-
const indexRowsById = (rows) => {
|
|
235
|
-
const byId = /* @__PURE__ */ new Map();
|
|
236
|
-
const order = [];
|
|
237
|
-
for (const row of rows) {
|
|
238
|
-
const id = readRowId(row);
|
|
239
|
-
if (id === void 0 || byId.has(id)) {
|
|
240
|
-
return void 0;
|
|
241
|
-
}
|
|
242
|
-
byId.set(id, row);
|
|
243
|
-
order.push(id);
|
|
244
|
-
}
|
|
245
|
-
return { byId, order };
|
|
246
|
-
};
|
|
247
|
-
const survivorsKeepOrder = (previous, next) => {
|
|
248
|
-
const survivingPrevious = previous.order.filter((id) => next.byId.has(id));
|
|
249
|
-
const survivingNext = next.order.filter((id) => previous.byId.has(id));
|
|
250
|
-
if (survivingPrevious.length !== survivingNext.length) {
|
|
251
|
-
return false;
|
|
252
|
-
}
|
|
253
|
-
return survivingPrevious.every((id, index) => survivingNext[index] === id);
|
|
254
|
-
};
|
|
255
|
-
const collectDeleteDeltas = (previous, next, deltaTable, tableJson) => {
|
|
256
|
-
const out = [];
|
|
257
|
-
for (const id of previous.order) {
|
|
258
|
-
if (!next.byId.has(id)) {
|
|
259
|
-
out.push({
|
|
260
|
-
delta: { key: id, op: "delete", table: deltaTable },
|
|
261
|
-
frame: `{"key":${JSON.stringify(id)},"op":"delete","table":${tableJson}}`
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return out;
|
|
266
|
-
};
|
|
267
|
-
const collectUpsertDeltas = (previous, next, deltaTable, tableJson) => {
|
|
268
|
-
const out = [];
|
|
269
|
-
for (const id of next.order) {
|
|
270
|
-
const nextRow = next.byId.get(id);
|
|
271
|
-
const previousRow = previous.byId.get(id);
|
|
272
|
-
const nextFingerprint = JSON.stringify(encodeWire(nextRow));
|
|
273
|
-
const previousFingerprint = previousRow === void 0 ? void 0 : JSON.stringify(previousRow);
|
|
274
|
-
if (previousFingerprint === nextFingerprint) {
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
const op = previousFingerprint === void 0 ? "insert" : "update";
|
|
278
|
-
out.push({
|
|
279
|
-
delta: { key: id, op, row: nextRow, table: deltaTable },
|
|
280
|
-
frame: `{"key":${JSON.stringify(id)},"op":"${op}","row":${nextFingerprint},"table":${tableJson}}`
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
return out;
|
|
284
|
-
};
|
|
285
|
-
const subscriptionListDeltas = (previousJson, nextResult, table, frames) => {
|
|
286
|
-
let parsed;
|
|
287
|
-
try {
|
|
288
|
-
parsed = JSON.parse(previousJson);
|
|
289
|
-
} catch {
|
|
290
|
-
return void 0;
|
|
291
|
-
}
|
|
292
|
-
if (!Array.isArray(parsed) || !Array.isArray(nextResult)) {
|
|
293
|
-
return void 0;
|
|
294
|
-
}
|
|
295
|
-
const previous = indexRowsById(parsed);
|
|
296
|
-
const next = indexRowsById(nextResult);
|
|
297
|
-
if (previous === void 0 || next === void 0) {
|
|
298
|
-
return void 0;
|
|
299
|
-
}
|
|
300
|
-
if (!survivorsKeepOrder(previous, next)) {
|
|
301
|
-
return void 0;
|
|
302
|
-
}
|
|
303
|
-
const deltaTable = table === "" ? DELTA_FALLBACK_TABLE : table;
|
|
304
|
-
const tableJson = JSON.stringify(deltaTable);
|
|
305
|
-
const framed = [...collectDeleteDeltas(previous, next, deltaTable, tableJson), ...collectUpsertDeltas(previous, next, deltaTable, tableJson)];
|
|
306
|
-
if (framed.length > next.order.length) {
|
|
307
|
-
return void 0;
|
|
308
|
-
}
|
|
309
|
-
if (frames !== void 0) {
|
|
310
|
-
for (const { frame } of framed) {
|
|
311
|
-
frames.push(frame);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
return framed.map(({ delta }) => delta);
|
|
315
|
-
};
|
|
316
|
-
const trySendFrame = (ws, frame) => {
|
|
317
|
-
try {
|
|
318
|
-
ws.send(frame);
|
|
319
|
-
return true;
|
|
320
|
-
} catch {
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
const sendDeltaFrames = (ws, subId, deltaFrames, cursorSuffix) => {
|
|
325
|
-
const idJson = JSON.stringify(subId);
|
|
326
|
-
let delivered = true;
|
|
327
|
-
for (const deltaBody of deltaFrames) {
|
|
328
|
-
if (!trySendFrame(ws, `{"type":"delta","id":${idJson},"delta":${deltaBody}${cursorSuffix}}`)) {
|
|
329
|
-
delivered = false;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return delivered;
|
|
333
|
-
};
|
|
334
|
-
const awaitWsDrain = async (ws) => {
|
|
335
|
-
let attempts = 0;
|
|
336
|
-
while (attempts < 100) {
|
|
337
|
-
attempts += 1;
|
|
338
|
-
const buffered = ws.bufferedAmount;
|
|
339
|
-
if (typeof buffered !== "number" || buffered < 1048576) {
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
await new Promise((resolve) => {
|
|
343
|
-
setTimeout(resolve, 20);
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
export { awaitWsDrain as a, sendDeltaFrames as b, decodeWire as d, encodeWire as e, subscriptionListDeltas as s, trySendFrame as t };
|
|
225
|
+
export { decodeWire as d, encodeWire as e };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/do",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.32",
|
|
4
4
|
"description": "Lunora Durable Objects: ShardDO (SQLite, OCC, hibernated WebSocket subscriptions) and SessionDO",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
50
|
-
"@lunora/fingerprint": "1.0.0-alpha.
|
|
49
|
+
"@lunora/errors": "1.0.0-alpha.5",
|
|
50
|
+
"@lunora/fingerprint": "1.0.0-alpha.2",
|
|
51
51
|
"@visulima/redact": "3.0.0",
|
|
52
52
|
"drizzle-orm": "^0.45.2"
|
|
53
53
|
},
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { LunoraError } from '@lunora/errors';
|
|
2
|
-
import { runExternalSourceTick } from './materializeExternalRows-CTqZisSC.mjs';
|
|
3
|
-
|
|
4
|
-
const liftSourceId = (row, options = {}) => {
|
|
5
|
-
const { idColumn = "id", map } = options;
|
|
6
|
-
const idValue = row[idColumn];
|
|
7
|
-
if (idValue === void 0 || idValue === null) {
|
|
8
|
-
throw new LunoraError("INTERNAL", `external-source: row is missing id column "${idColumn}"`);
|
|
9
|
-
}
|
|
10
|
-
if (typeof idValue !== "string" && typeof idValue !== "number" && typeof idValue !== "bigint") {
|
|
11
|
-
throw new TypeError(`external-source: id column "${idColumn}" must be a string or number`);
|
|
12
|
-
}
|
|
13
|
-
const id = String(idValue);
|
|
14
|
-
if (map) {
|
|
15
|
-
return { ...map(row), _id: id };
|
|
16
|
-
}
|
|
17
|
-
const body = {};
|
|
18
|
-
for (const [key, value] of Object.entries(row)) {
|
|
19
|
-
if (key !== idColumn) {
|
|
20
|
-
body[key] = value;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return { ...body, _id: id };
|
|
24
|
-
};
|
|
25
|
-
const isSourceDue = (refresh, lastPolledMs, nowMs) => {
|
|
26
|
-
if (refresh === "manual") {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
if (refresh === void 0 || lastPolledMs === void 0) {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
return nowMs - lastPolledMs >= refresh.everyMs;
|
|
33
|
-
};
|
|
34
|
-
const pullExternalSourceTick = async (sql, writer, client, table, source, shardKey) => {
|
|
35
|
-
const parameters = source.tenantBy ? source.tenantBy(shardKey) : [];
|
|
36
|
-
const rows = await client.query(source.query, parameters);
|
|
37
|
-
const documents = rows.map((row) => liftSourceId(row, { idColumn: source.idColumn, map: source.map }));
|
|
38
|
-
return runExternalSourceTick(sql, writer, documents, { columns: source.columns, table });
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export { isSourceDue, liftSourceId, pullExternalSourceTick };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { a as awaitWsDrain, b as sendDeltaFrames, s as subscriptionListDeltas, t as trySendFrame } from './subscription-delivery-CWigSEr3.mjs';
|