@lunora/do 1.0.0-alpha.13 → 1.0.0-alpha.15
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 +260 -55
- package/dist/index.d.ts +260 -55
- package/dist/index.mjs +10 -7
- package/dist/packem_shared/{ADMIN_FUNCTIONS-D_UiYJFk.mjs → ADMIN_FUNCTIONS-fMl1vyt1.mjs} +40 -1
- package/dist/packem_shared/{NotUniqueError-DZQtH02h.mjs → NotUniqueError-BXn2j5vu.mjs} +1 -1
- package/dist/packem_shared/{ROOT_DO_SIZE_WARN_BYTES-XEs4csIH.mjs → ROOT_DO_SIZE_WARN_BYTES-CEVhBec2.mjs} +1033 -89
- package/dist/packem_shared/{ReactiveCache-1hDydFyv.mjs → ReactiveCache-BYlSGY0N.mjs} +1 -1
- package/dist/packem_shared/{ctx-db-shapes-DVoeZpo-.mjs → ctx-db-shapes-BQapRFjB.mjs} +1 -1
- package/dist/packem_shared/diffExternalSource-Cx9HUPJj.mjs +44 -0
- package/dist/packem_shared/isSourceDue-BMTJv65e.mjs +40 -0
- package/dist/packem_shared/materializeExternalRows-uKBx9H0s.mjs +23 -0
- package/dist/packem_shared/{serveRelationFanout-C6lDaesn.mjs → serveRelationFanout-EZLD9-O3.mjs} +1 -1
- package/dist/packem_shared/{stableStringify-CyHKJXre.mjs → stableStringify-MydiuScU.mjs} +10 -0
- package/dist/packem_shared/subscription-delivery-Z6nXHvQN.mjs +340 -0
- package/dist/packem_shared/subscriptionListDeltas-CXwfoEE_.mjs +1 -0
- package/package.json +1 -1
- package/dist/packem_shared/subscriptionListDeltas-ce84gpwL.mjs +0 -111
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { stableStringify } from './stableStringify-MydiuScU.mjs';
|
|
2
|
+
|
|
3
|
+
const projectExternalSourceRow = (row, columns) => {
|
|
4
|
+
const projected = { _id: row._id };
|
|
5
|
+
if (columns) {
|
|
6
|
+
for (const key of columns) {
|
|
7
|
+
if (Object.hasOwn(row, key)) {
|
|
8
|
+
projected[key] = row[key];
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return projected;
|
|
12
|
+
}
|
|
13
|
+
for (const [key, value] of Object.entries(row)) {
|
|
14
|
+
if (key !== "_id" && key !== "_creationTime") {
|
|
15
|
+
projected[key] = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return projected;
|
|
19
|
+
};
|
|
20
|
+
const diffExternalSource = (pulled, baseline, options) => {
|
|
21
|
+
const { columns, table } = options;
|
|
22
|
+
const changes = [];
|
|
23
|
+
const nextBaseline = /* @__PURE__ */ new Map();
|
|
24
|
+
for (const source of pulled) {
|
|
25
|
+
const value = projectExternalSourceRow(source, columns);
|
|
26
|
+
const id = String(value._id);
|
|
27
|
+
const json = stableStringify(value);
|
|
28
|
+
nextBaseline.set(id, json);
|
|
29
|
+
const before = baseline.get(id);
|
|
30
|
+
if (before === void 0) {
|
|
31
|
+
changes.push({ doc: value, id, op: "insert", seq: 0, table, ts: 0 });
|
|
32
|
+
} else if (before !== json) {
|
|
33
|
+
changes.push({ doc: value, id, op: "update", seq: 0, table, ts: 0 });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const id of baseline.keys()) {
|
|
37
|
+
if (!nextBaseline.has(id)) {
|
|
38
|
+
changes.push({ id, op: "delete", seq: 0, table, ts: 0 });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { changes, nextBaseline };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export { diffExternalSource, projectExternalSourceRow };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { runExternalSourceTick } from './materializeExternalRows-uKBx9H0s.mjs';
|
|
2
|
+
|
|
3
|
+
const liftSourceId = (row, options = {}) => {
|
|
4
|
+
const { idColumn = "id", map } = options;
|
|
5
|
+
const idValue = row[idColumn];
|
|
6
|
+
if (idValue === void 0 || idValue === null) {
|
|
7
|
+
throw new Error(`external-source: row is missing id column "${idColumn}"`);
|
|
8
|
+
}
|
|
9
|
+
if (typeof idValue !== "string" && typeof idValue !== "number" && typeof idValue !== "bigint") {
|
|
10
|
+
throw new TypeError(`external-source: id column "${idColumn}" must be a string or number`);
|
|
11
|
+
}
|
|
12
|
+
const id = String(idValue);
|
|
13
|
+
if (map) {
|
|
14
|
+
return { ...map(row), _id: id };
|
|
15
|
+
}
|
|
16
|
+
const body = {};
|
|
17
|
+
for (const [key, value] of Object.entries(row)) {
|
|
18
|
+
if (key !== idColumn) {
|
|
19
|
+
body[key] = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { ...body, _id: id };
|
|
23
|
+
};
|
|
24
|
+
const isSourceDue = (refresh, lastPolledMs, nowMs) => {
|
|
25
|
+
if (refresh === "manual") {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (refresh === void 0 || lastPolledMs === void 0) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
return nowMs - lastPolledMs >= refresh.everyMs;
|
|
32
|
+
};
|
|
33
|
+
const pullExternalSourceTick = async (sql, writer, client, table, source, shardKey) => {
|
|
34
|
+
const parameters = source.tenantBy ? source.tenantBy(shardKey) : [];
|
|
35
|
+
const rows = await client.query(source.query, parameters);
|
|
36
|
+
const documents = rows.map((row) => liftSourceId(row, { idColumn: source.idColumn, map: source.map }));
|
|
37
|
+
return runExternalSourceTick(sql, writer, documents, { columns: source.columns, table });
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { isSourceDue, liftSourceId, pullExternalSourceTick };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { applyCdcChanges } from './CDC_LOG_TABLE-DSycmnDf.mjs';
|
|
2
|
+
import { s as selectShapeRows } from './ctx-db-shapes-BQapRFjB.mjs';
|
|
3
|
+
import { diffExternalSource, projectExternalSourceRow } from './diffExternalSource-Cx9HUPJj.mjs';
|
|
4
|
+
import { stableStringify } from './stableStringify-MydiuScU.mjs';
|
|
5
|
+
|
|
6
|
+
const materializeExternalRows = async (writer, pulled, baseline, options) => {
|
|
7
|
+
const { changes, nextBaseline } = diffExternalSource(pulled, baseline, options);
|
|
8
|
+
await applyCdcChanges(writer, changes);
|
|
9
|
+
return { applied: changes.length, nextBaseline };
|
|
10
|
+
};
|
|
11
|
+
const readExternalSourceBaseline = (sql, table, columns) => {
|
|
12
|
+
const baseline = /* @__PURE__ */ new Map();
|
|
13
|
+
for (const { doc, id } of selectShapeRows(sql, table, void 0)) {
|
|
14
|
+
baseline.set(id, stableStringify(projectExternalSourceRow({ ...doc, _id: id }, columns)));
|
|
15
|
+
}
|
|
16
|
+
return baseline;
|
|
17
|
+
};
|
|
18
|
+
const runExternalSourceTick = async (sql, writer, pulled, options) => {
|
|
19
|
+
const baseline = readExternalSourceBaseline(sql, options.table, options.columns);
|
|
20
|
+
return materializeExternalRows(writer, pulled, baseline, options);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { materializeExternalRows, readExternalSourceBaseline, runExternalSourceTick };
|
package/dist/packem_shared/{serveRelationFanout-C6lDaesn.mjs → serveRelationFanout-EZLD9-O3.mjs}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-
|
|
1
|
+
import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-fMl1vyt1.mjs';
|
|
2
2
|
|
|
3
3
|
const serveRelationFanout = async (schema, database, functionPath, args) => {
|
|
4
4
|
const table = typeof args["table"] === "string" ? args["table"] : "";
|
|
@@ -8,12 +8,22 @@ const stableStringify = (value) => {
|
|
|
8
8
|
if (value === void 0) {
|
|
9
9
|
return "null";
|
|
10
10
|
}
|
|
11
|
+
if (typeof value === "bigint") {
|
|
12
|
+
throw new TypeError("stableStringify: cannot use a bigint in a cache key (query/subscription/shape args) — pass it as a string");
|
|
13
|
+
}
|
|
11
14
|
if (value === null || typeof value !== "object") {
|
|
12
15
|
return JSON.stringify(value);
|
|
13
16
|
}
|
|
14
17
|
if (Array.isArray(value)) {
|
|
15
18
|
return `[${value.map((item) => stableStringify(item)).join(",")}]`;
|
|
16
19
|
}
|
|
20
|
+
const proto = Object.getPrototypeOf(value);
|
|
21
|
+
if (proto !== null && proto !== Object.prototype) {
|
|
22
|
+
const name = value.constructor?.name ?? "value";
|
|
23
|
+
throw new TypeError(
|
|
24
|
+
`stableStringify: cannot use a ${name} in a cache key (query/subscription/shape args) — only plain objects, arrays, and JSON primitives are supported`
|
|
25
|
+
);
|
|
26
|
+
}
|
|
17
27
|
const record = value;
|
|
18
28
|
const keys = Object.keys(record).toSorted(compareKeys);
|
|
19
29
|
const parts = [];
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
const TAG = "$lunora.wire$";
|
|
2
|
+
const MAX_DEPTH = 64;
|
|
3
|
+
const MAX_BIGINT_DIGITS = 1024;
|
|
4
|
+
const UNSAFE_KEY = "__proto__";
|
|
5
|
+
const TYPED_ARRAY_CTORS = {
|
|
6
|
+
BigInt64Array,
|
|
7
|
+
BigUint64Array,
|
|
8
|
+
Float32Array,
|
|
9
|
+
Float64Array,
|
|
10
|
+
Int8Array,
|
|
11
|
+
Int16Array,
|
|
12
|
+
Int32Array,
|
|
13
|
+
Uint8Array,
|
|
14
|
+
Uint8ClampedArray,
|
|
15
|
+
Uint16Array,
|
|
16
|
+
Uint32Array
|
|
17
|
+
};
|
|
18
|
+
const ERROR_CTORS = {
|
|
19
|
+
Error,
|
|
20
|
+
EvalError,
|
|
21
|
+
RangeError,
|
|
22
|
+
ReferenceError,
|
|
23
|
+
SyntaxError,
|
|
24
|
+
TypeError,
|
|
25
|
+
URIError
|
|
26
|
+
};
|
|
27
|
+
const toBase64 = (bytes) => {
|
|
28
|
+
let binary = "";
|
|
29
|
+
const chunk = 32768;
|
|
30
|
+
for (let index = 0; index < bytes.length; index += chunk) {
|
|
31
|
+
binary += String.fromCharCode(...bytes.subarray(index, index + chunk));
|
|
32
|
+
}
|
|
33
|
+
return btoa(binary);
|
|
34
|
+
};
|
|
35
|
+
const fromBase64 = (base64) => {
|
|
36
|
+
const binary = atob(base64);
|
|
37
|
+
const bytes = new Uint8Array(binary.length);
|
|
38
|
+
for (let index = 0; index < binary.length; index += 1) {
|
|
39
|
+
bytes[index] = binary.codePointAt(index) ?? 0;
|
|
40
|
+
}
|
|
41
|
+
return bytes;
|
|
42
|
+
};
|
|
43
|
+
const encodeWire = (value, depth = 0) => {
|
|
44
|
+
if (depth > MAX_DEPTH) {
|
|
45
|
+
throw new RangeError(`wire-codec: value nesting exceeds the ${MAX_DEPTH}-level limit`);
|
|
46
|
+
}
|
|
47
|
+
if (value === void 0) {
|
|
48
|
+
return [TAG, "undefined"];
|
|
49
|
+
}
|
|
50
|
+
if (value === null) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const kind = typeof value;
|
|
54
|
+
if (kind === "bigint") {
|
|
55
|
+
return [TAG, "bigint", value.toString()];
|
|
56
|
+
}
|
|
57
|
+
if (kind === "number") {
|
|
58
|
+
const numeric = value;
|
|
59
|
+
if (Number.isNaN(numeric)) {
|
|
60
|
+
return [TAG, "nan"];
|
|
61
|
+
}
|
|
62
|
+
if (numeric === Infinity) {
|
|
63
|
+
return [TAG, "inf"];
|
|
64
|
+
}
|
|
65
|
+
if (numeric === -Infinity) {
|
|
66
|
+
return [TAG, "-inf"];
|
|
67
|
+
}
|
|
68
|
+
return numeric;
|
|
69
|
+
}
|
|
70
|
+
if (kind !== "object") {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
if (value instanceof Date) {
|
|
74
|
+
return [TAG, "date", encodeWire(value.getTime(), depth + 1)];
|
|
75
|
+
}
|
|
76
|
+
if (value instanceof Error) {
|
|
77
|
+
const error = value;
|
|
78
|
+
const properties = {};
|
|
79
|
+
for (const key of Object.keys(error)) {
|
|
80
|
+
if (error[key] !== void 0) {
|
|
81
|
+
properties[key] = encodeWire(error[key], depth + 1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const encodedError = [TAG, "error", error.name, error.message, properties];
|
|
85
|
+
if (error.cause !== void 0) {
|
|
86
|
+
encodedError.push(encodeWire(error.cause, depth + 1));
|
|
87
|
+
}
|
|
88
|
+
return encodedError;
|
|
89
|
+
}
|
|
90
|
+
if (value instanceof URL) {
|
|
91
|
+
return [TAG, "url", value.href];
|
|
92
|
+
}
|
|
93
|
+
if (value instanceof Map) {
|
|
94
|
+
return [TAG, "map", [...value.entries()].map(([k, v]) => [encodeWire(k, depth + 1), encodeWire(v, depth + 1)])];
|
|
95
|
+
}
|
|
96
|
+
if (value instanceof Set) {
|
|
97
|
+
return [TAG, "set", [...value].map((item) => encodeWire(item, depth + 1))];
|
|
98
|
+
}
|
|
99
|
+
if (value instanceof ArrayBuffer) {
|
|
100
|
+
return [TAG, "bytes", toBase64(new Uint8Array(value)), "ArrayBuffer"];
|
|
101
|
+
}
|
|
102
|
+
if (ArrayBuffer.isView(value)) {
|
|
103
|
+
const view = value;
|
|
104
|
+
const ctorName = view.constructor.name;
|
|
105
|
+
const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
106
|
+
return ctorName === "Uint8Array" ? [TAG, "bytes", toBase64(bytes)] : [TAG, "bytes", toBase64(bytes), ctorName];
|
|
107
|
+
}
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
const encoded = value.map((item) => encodeWire(item, depth + 1));
|
|
110
|
+
return encoded.length > 0 && encoded[0] === TAG ? [TAG, "arr", encoded] : encoded;
|
|
111
|
+
}
|
|
112
|
+
const proto = Object.getPrototypeOf(value);
|
|
113
|
+
if (proto !== null && proto !== Object.prototype) {
|
|
114
|
+
const name = value.constructor?.name ?? "value";
|
|
115
|
+
throw new TypeError(
|
|
116
|
+
`wire-codec: cannot encode a ${name} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const source = value;
|
|
120
|
+
const result = {};
|
|
121
|
+
for (const key of Object.keys(source)) {
|
|
122
|
+
const field = source[key];
|
|
123
|
+
if (field !== void 0) {
|
|
124
|
+
result[key] = encodeWire(field, depth + 1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
};
|
|
129
|
+
const decodeWire = (value, depth = 0) => {
|
|
130
|
+
if (depth > MAX_DEPTH) {
|
|
131
|
+
throw new RangeError(`wire-codec: value nesting exceeds the ${MAX_DEPTH}-level limit`);
|
|
132
|
+
}
|
|
133
|
+
if (value === null || typeof value !== "object") {
|
|
134
|
+
return value;
|
|
135
|
+
}
|
|
136
|
+
if (Array.isArray(value)) {
|
|
137
|
+
if (value[0] === TAG) {
|
|
138
|
+
const tag = value[1];
|
|
139
|
+
switch (tag) {
|
|
140
|
+
case "-inf": {
|
|
141
|
+
return -Infinity;
|
|
142
|
+
}
|
|
143
|
+
case "arr": {
|
|
144
|
+
return value[2].map((item) => decodeWire(item, depth + 1));
|
|
145
|
+
}
|
|
146
|
+
case "bigint": {
|
|
147
|
+
const raw = value[2];
|
|
148
|
+
if (typeof raw !== "string" || raw.length > MAX_BIGINT_DIGITS || !/^-?\d+$/.test(raw)) {
|
|
149
|
+
throw new RangeError(`wire-codec: invalid or over-long bigint (max ${MAX_BIGINT_DIGITS} digits)`);
|
|
150
|
+
}
|
|
151
|
+
return BigInt(raw);
|
|
152
|
+
}
|
|
153
|
+
case "date": {
|
|
154
|
+
return new Date(decodeWire(value[2], depth + 1));
|
|
155
|
+
}
|
|
156
|
+
case "map": {
|
|
157
|
+
return new Map(value[2].map(([k, v]) => [decodeWire(k, depth + 1), decodeWire(v, depth + 1)]));
|
|
158
|
+
}
|
|
159
|
+
case "set": {
|
|
160
|
+
return new Set(value[2].map((item) => decodeWire(item, depth + 1)));
|
|
161
|
+
}
|
|
162
|
+
case "url": {
|
|
163
|
+
return new URL(value[2]);
|
|
164
|
+
}
|
|
165
|
+
case "error": {
|
|
166
|
+
const name = value[2];
|
|
167
|
+
const message = value[3];
|
|
168
|
+
const Ctor = (Object.hasOwn(ERROR_CTORS, name) ? ERROR_CTORS[name] : void 0) ?? Error;
|
|
169
|
+
const error = new Ctor(message);
|
|
170
|
+
if (error.name !== name) {
|
|
171
|
+
Object.defineProperty(error, "name", { configurable: true, value: name, writable: true });
|
|
172
|
+
}
|
|
173
|
+
Object.assign(error, decodeWire(value[4], depth + 1));
|
|
174
|
+
if (value.length > 5) {
|
|
175
|
+
Object.defineProperty(error, "cause", { configurable: true, value: decodeWire(value[5], depth + 1), writable: true });
|
|
176
|
+
}
|
|
177
|
+
return error;
|
|
178
|
+
}
|
|
179
|
+
case "bytes": {
|
|
180
|
+
const bytes = fromBase64(value[2]);
|
|
181
|
+
const ctorName = value[3] ?? "Uint8Array";
|
|
182
|
+
if (ctorName === "ArrayBuffer") {
|
|
183
|
+
return bytes.buffer.byteLength === bytes.byteLength ? bytes.buffer : bytes.slice().buffer;
|
|
184
|
+
}
|
|
185
|
+
const Ctor = Object.hasOwn(TYPED_ARRAY_CTORS, ctorName) ? TYPED_ARRAY_CTORS[ctorName] : void 0;
|
|
186
|
+
return Ctor ? new Ctor(bytes.slice().buffer) : bytes;
|
|
187
|
+
}
|
|
188
|
+
case "inf": {
|
|
189
|
+
return Infinity;
|
|
190
|
+
}
|
|
191
|
+
case "nan": {
|
|
192
|
+
return Number.NaN;
|
|
193
|
+
}
|
|
194
|
+
case "undefined": {
|
|
195
|
+
return void 0;
|
|
196
|
+
}
|
|
197
|
+
default: {
|
|
198
|
+
return value.map((item) => decodeWire(item, depth + 1));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return value.map((item) => decodeWire(item, depth + 1));
|
|
203
|
+
}
|
|
204
|
+
const source = value;
|
|
205
|
+
const result = {};
|
|
206
|
+
for (const key of Object.keys(source)) {
|
|
207
|
+
const decoded = decodeWire(source[key], depth + 1);
|
|
208
|
+
if (key === UNSAFE_KEY) {
|
|
209
|
+
Object.defineProperty(result, key, { configurable: true, enumerable: true, value: decoded, writable: true });
|
|
210
|
+
} else {
|
|
211
|
+
result[key] = decoded;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const ROW_ID_FIELD = "_id";
|
|
218
|
+
const DELTA_FALLBACK_TABLE = "__lunora__";
|
|
219
|
+
const readRowId = (row) => {
|
|
220
|
+
if (typeof row !== "object" || row === null || Array.isArray(row)) {
|
|
221
|
+
return void 0;
|
|
222
|
+
}
|
|
223
|
+
const id = row[ROW_ID_FIELD];
|
|
224
|
+
return typeof id === "string" ? id : void 0;
|
|
225
|
+
};
|
|
226
|
+
const indexRowsById = (rows) => {
|
|
227
|
+
const byId = /* @__PURE__ */ new Map();
|
|
228
|
+
const order = [];
|
|
229
|
+
for (const row of rows) {
|
|
230
|
+
const id = readRowId(row);
|
|
231
|
+
if (id === void 0 || byId.has(id)) {
|
|
232
|
+
return void 0;
|
|
233
|
+
}
|
|
234
|
+
byId.set(id, row);
|
|
235
|
+
order.push(id);
|
|
236
|
+
}
|
|
237
|
+
return { byId, order };
|
|
238
|
+
};
|
|
239
|
+
const survivorsKeepOrder = (previous, next) => {
|
|
240
|
+
const survivingPrevious = previous.order.filter((id) => next.byId.has(id));
|
|
241
|
+
const survivingNext = next.order.filter((id) => previous.byId.has(id));
|
|
242
|
+
if (survivingPrevious.length !== survivingNext.length) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
return survivingPrevious.every((id, index) => survivingNext[index] === id);
|
|
246
|
+
};
|
|
247
|
+
const collectDeleteDeltas = (previous, next, deltaTable, tableJson) => {
|
|
248
|
+
const out = [];
|
|
249
|
+
for (const id of previous.order) {
|
|
250
|
+
if (!next.byId.has(id)) {
|
|
251
|
+
out.push({
|
|
252
|
+
delta: { key: id, op: "delete", table: deltaTable },
|
|
253
|
+
frame: `{"key":${JSON.stringify(id)},"op":"delete","table":${tableJson}}`
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return out;
|
|
258
|
+
};
|
|
259
|
+
const collectUpsertDeltas = (previous, next, deltaTable, tableJson) => {
|
|
260
|
+
const out = [];
|
|
261
|
+
for (const id of next.order) {
|
|
262
|
+
const nextRow = next.byId.get(id);
|
|
263
|
+
const previousRow = previous.byId.get(id);
|
|
264
|
+
const nextFingerprint = JSON.stringify(encodeWire(nextRow));
|
|
265
|
+
const previousFingerprint = previousRow === void 0 ? void 0 : JSON.stringify(previousRow);
|
|
266
|
+
if (previousFingerprint === nextFingerprint) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const op = previousFingerprint === void 0 ? "insert" : "update";
|
|
270
|
+
out.push({
|
|
271
|
+
delta: { key: id, op, row: nextRow, table: deltaTable },
|
|
272
|
+
frame: `{"key":${JSON.stringify(id)},"op":"${op}","row":${nextFingerprint},"table":${tableJson}}`
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
};
|
|
277
|
+
const subscriptionListDeltas = (previousJson, nextResult, table, frames) => {
|
|
278
|
+
let parsed;
|
|
279
|
+
try {
|
|
280
|
+
parsed = JSON.parse(previousJson);
|
|
281
|
+
} catch {
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
if (!Array.isArray(parsed) || !Array.isArray(nextResult)) {
|
|
285
|
+
return void 0;
|
|
286
|
+
}
|
|
287
|
+
const previous = indexRowsById(parsed);
|
|
288
|
+
const next = indexRowsById(nextResult);
|
|
289
|
+
if (previous === void 0 || next === void 0) {
|
|
290
|
+
return void 0;
|
|
291
|
+
}
|
|
292
|
+
if (!survivorsKeepOrder(previous, next)) {
|
|
293
|
+
return void 0;
|
|
294
|
+
}
|
|
295
|
+
const deltaTable = table === "" ? DELTA_FALLBACK_TABLE : table;
|
|
296
|
+
const tableJson = JSON.stringify(deltaTable);
|
|
297
|
+
const framed = [...collectDeleteDeltas(previous, next, deltaTable, tableJson), ...collectUpsertDeltas(previous, next, deltaTable, tableJson)];
|
|
298
|
+
if (framed.length > next.order.length) {
|
|
299
|
+
return void 0;
|
|
300
|
+
}
|
|
301
|
+
if (frames !== void 0) {
|
|
302
|
+
for (const { frame } of framed) {
|
|
303
|
+
frames.push(frame);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return framed.map(({ delta }) => delta);
|
|
307
|
+
};
|
|
308
|
+
const trySendFrame = (ws, frame) => {
|
|
309
|
+
try {
|
|
310
|
+
ws.send(frame);
|
|
311
|
+
return true;
|
|
312
|
+
} catch {
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
const sendDeltaFrames = (ws, subId, deltaFrames, cursorSuffix) => {
|
|
317
|
+
const idJson = JSON.stringify(subId);
|
|
318
|
+
let delivered = true;
|
|
319
|
+
for (const deltaBody of deltaFrames) {
|
|
320
|
+
if (!trySendFrame(ws, `{"type":"delta","id":${idJson},"delta":${deltaBody}${cursorSuffix}}`)) {
|
|
321
|
+
delivered = false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return delivered;
|
|
325
|
+
};
|
|
326
|
+
const awaitWsDrain = async (ws) => {
|
|
327
|
+
let attempts = 0;
|
|
328
|
+
while (attempts < 100) {
|
|
329
|
+
attempts += 1;
|
|
330
|
+
const buffered = ws.bufferedAmount;
|
|
331
|
+
if (typeof buffered !== "number" || buffered < 1048576) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
await new Promise((resolve) => {
|
|
335
|
+
setTimeout(resolve, 20);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
export { awaitWsDrain as a, sendDeltaFrames as b, decodeWire as d, encodeWire as e, subscriptionListDeltas as s, trySendFrame as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { a as awaitWsDrain, b as sendDeltaFrames, s as subscriptionListDeltas, t as trySendFrame } from './subscription-delivery-Z6nXHvQN.mjs';
|
package/package.json
CHANGED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
const ROW_ID_FIELD = "_id";
|
|
2
|
-
const DELTA_FALLBACK_TABLE = "__lunora__";
|
|
3
|
-
const readRowId = (row) => {
|
|
4
|
-
if (typeof row !== "object" || row === null || Array.isArray(row)) {
|
|
5
|
-
return void 0;
|
|
6
|
-
}
|
|
7
|
-
const id = row[ROW_ID_FIELD];
|
|
8
|
-
return typeof id === "string" ? id : void 0;
|
|
9
|
-
};
|
|
10
|
-
const indexRowsById = (rows) => {
|
|
11
|
-
const byId = /* @__PURE__ */ new Map();
|
|
12
|
-
const order = [];
|
|
13
|
-
for (const row of rows) {
|
|
14
|
-
const id = readRowId(row);
|
|
15
|
-
if (id === void 0 || byId.has(id)) {
|
|
16
|
-
return void 0;
|
|
17
|
-
}
|
|
18
|
-
byId.set(id, row);
|
|
19
|
-
order.push(id);
|
|
20
|
-
}
|
|
21
|
-
return { byId, order };
|
|
22
|
-
};
|
|
23
|
-
const survivorsKeepOrder = (previous, next) => {
|
|
24
|
-
const survivingPrevious = previous.order.filter((id) => next.byId.has(id));
|
|
25
|
-
const survivingNext = next.order.filter((id) => previous.byId.has(id));
|
|
26
|
-
if (survivingPrevious.length !== survivingNext.length) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
return survivingPrevious.every((id, index) => survivingNext[index] === id);
|
|
30
|
-
};
|
|
31
|
-
const collectDeleteDeltas = (previous, next, deltaTable, tableJson) => {
|
|
32
|
-
const out = [];
|
|
33
|
-
for (const id of previous.order) {
|
|
34
|
-
if (!next.byId.has(id)) {
|
|
35
|
-
out.push({
|
|
36
|
-
delta: { key: id, op: "delete", table: deltaTable },
|
|
37
|
-
frame: `{"key":${JSON.stringify(id)},"op":"delete","table":${tableJson}}`
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return out;
|
|
42
|
-
};
|
|
43
|
-
const collectUpsertDeltas = (previous, next, deltaTable, tableJson) => {
|
|
44
|
-
const out = [];
|
|
45
|
-
for (const id of next.order) {
|
|
46
|
-
const nextRow = next.byId.get(id);
|
|
47
|
-
const previousRow = previous.byId.get(id);
|
|
48
|
-
const nextFingerprint = JSON.stringify(nextRow);
|
|
49
|
-
const previousFingerprint = previousRow === void 0 ? void 0 : JSON.stringify(previousRow);
|
|
50
|
-
if (previousFingerprint === nextFingerprint) {
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
const op = previousFingerprint === void 0 ? "insert" : "update";
|
|
54
|
-
out.push({
|
|
55
|
-
delta: { key: id, op, row: nextRow, table: deltaTable },
|
|
56
|
-
frame: `{"key":${JSON.stringify(id)},"op":"${op}","row":${nextFingerprint},"table":${tableJson}}`
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
return out;
|
|
60
|
-
};
|
|
61
|
-
const subscriptionListDeltas = (previousJson, nextResult, table, frames) => {
|
|
62
|
-
let parsed;
|
|
63
|
-
try {
|
|
64
|
-
parsed = JSON.parse(previousJson);
|
|
65
|
-
} catch {
|
|
66
|
-
return void 0;
|
|
67
|
-
}
|
|
68
|
-
if (!Array.isArray(parsed) || !Array.isArray(nextResult)) {
|
|
69
|
-
return void 0;
|
|
70
|
-
}
|
|
71
|
-
const previous = indexRowsById(parsed);
|
|
72
|
-
const next = indexRowsById(nextResult);
|
|
73
|
-
if (previous === void 0 || next === void 0) {
|
|
74
|
-
return void 0;
|
|
75
|
-
}
|
|
76
|
-
if (!survivorsKeepOrder(previous, next)) {
|
|
77
|
-
return void 0;
|
|
78
|
-
}
|
|
79
|
-
const deltaTable = table === "" ? DELTA_FALLBACK_TABLE : table;
|
|
80
|
-
const tableJson = JSON.stringify(deltaTable);
|
|
81
|
-
const framed = [...collectDeleteDeltas(previous, next, deltaTable, tableJson), ...collectUpsertDeltas(previous, next, deltaTable, tableJson)];
|
|
82
|
-
if (framed.length > next.order.length) {
|
|
83
|
-
return void 0;
|
|
84
|
-
}
|
|
85
|
-
if (frames !== void 0) {
|
|
86
|
-
for (const { frame } of framed) {
|
|
87
|
-
frames.push(frame);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return framed.map(({ delta }) => delta);
|
|
91
|
-
};
|
|
92
|
-
const trySendFrame = (ws, frame) => {
|
|
93
|
-
try {
|
|
94
|
-
ws.send(frame);
|
|
95
|
-
return true;
|
|
96
|
-
} catch {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
const sendDeltaFrames = (ws, subId, deltaFrames, cursorSuffix) => {
|
|
101
|
-
const idJson = JSON.stringify(subId);
|
|
102
|
-
let delivered = true;
|
|
103
|
-
for (const deltaBody of deltaFrames) {
|
|
104
|
-
if (!trySendFrame(ws, `{"type":"delta","id":${idJson},"delta":${deltaBody}${cursorSuffix}}`)) {
|
|
105
|
-
delivered = false;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return delivered;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
export { sendDeltaFrames, subscriptionListDeltas, trySendFrame };
|