@absolutejs/sync 1.5.0 → 1.6.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/dist/adapters/mysql/index.d.ts +7 -0
- package/dist/adapters/mysql/index.js +12 -3
- package/dist/adapters/mysql/index.js.map +4 -4
- package/dist/adapters/postgres/index.d.ts +7 -0
- package/dist/adapters/postgres/index.js +6 -3
- package/dist/adapters/postgres/index.js.map +3 -3
- package/dist/adapters/sqlite/index.js +5 -2
- package/dist/adapters/sqlite/index.js.map +3 -3
- package/dist/engine/cdc.d.ts +74 -0
- package/dist/engine/index.d.ts +4 -2
- package/dist/engine/index.js +196 -3
- package/dist/engine/index.js.map +6 -5
- package/dist/engine/pollingSource.d.ts +7 -0
- package/dist/engine/syncEngine.d.ts +74 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1689 -3
- package/dist/index.js.map +10 -3
- package/package.json +1 -1
package/dist/engine/index.js
CHANGED
|
@@ -569,6 +569,7 @@ var createPollingChangeSource = (options) => {
|
|
|
569
569
|
const onError = options.onError ?? ((error) => {
|
|
570
570
|
console.warn("[sync] polling change source error:", error);
|
|
571
571
|
});
|
|
572
|
+
const onSkip = options.onSkip;
|
|
572
573
|
let cursor = options.startSeq ?? 0;
|
|
573
574
|
let running = false;
|
|
574
575
|
let timer;
|
|
@@ -580,7 +581,9 @@ var createPollingChangeSource = (options) => {
|
|
|
580
581
|
const rows = await options.poll(cursor);
|
|
581
582
|
for (const row of rows) {
|
|
582
583
|
const parsed = parse(row);
|
|
583
|
-
if (parsed
|
|
584
|
+
if (parsed === undefined) {
|
|
585
|
+
onSkip?.(row, "parse-failed");
|
|
586
|
+
} else {
|
|
584
587
|
await emit(parsed.table, parsed.change);
|
|
585
588
|
}
|
|
586
589
|
if (typeof row.seq === "number" && row.seq > cursor) {
|
|
@@ -1161,6 +1164,28 @@ class SchemaError extends Error {
|
|
|
1161
1164
|
this.name = "SchemaError";
|
|
1162
1165
|
}
|
|
1163
1166
|
}
|
|
1167
|
+
|
|
1168
|
+
class MissedChangesError extends Error {
|
|
1169
|
+
requestedSince;
|
|
1170
|
+
availableSince;
|
|
1171
|
+
constructor(requestedSince, availableSince) {
|
|
1172
|
+
super(`Change log no longer covers version ${requestedSince}; oldest available is ${availableSince}. ` + `Re-bootstrap and resume from ${availableSince}.`);
|
|
1173
|
+
this.name = "MissedChangesError";
|
|
1174
|
+
this.requestedSince = requestedSince;
|
|
1175
|
+
this.availableSince = availableSince;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
class CdcConsumerSlowError extends Error {
|
|
1180
|
+
maxBuffer;
|
|
1181
|
+
lastDeliveredVersion;
|
|
1182
|
+
constructor(maxBuffer, lastDeliveredVersion) {
|
|
1183
|
+
super(`CDC stream buffer overflowed (max ${maxBuffer}); consumer fell behind. ` + `Last delivered version: ${lastDeliveredVersion}. Resubscribe with since=${lastDeliveredVersion}.`);
|
|
1184
|
+
this.name = "CdcConsumerSlowError";
|
|
1185
|
+
this.maxBuffer = maxBuffer;
|
|
1186
|
+
this.lastDeliveredVersion = lastDeliveredVersion;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1164
1189
|
var defaultKey = (row) => row.id;
|
|
1165
1190
|
var shallowEqual4 = (a, b) => {
|
|
1166
1191
|
if (a === b) {
|
|
@@ -1306,6 +1331,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
1306
1331
|
listener(event);
|
|
1307
1332
|
}
|
|
1308
1333
|
};
|
|
1334
|
+
const streamSubscribers = new Set;
|
|
1309
1335
|
const runInTransaction = options.transaction;
|
|
1310
1336
|
const instanceId = globalThis.crypto?.randomUUID?.() ?? `i${Math.random()}`;
|
|
1311
1337
|
let clusterBus;
|
|
@@ -1677,6 +1703,9 @@ var createSyncEngine = (options = {}) => {
|
|
|
1677
1703
|
if (changeLog.length > changeLogSize) {
|
|
1678
1704
|
changeLog.shift();
|
|
1679
1705
|
}
|
|
1706
|
+
for (const subscriber of streamSubscribers) {
|
|
1707
|
+
subscriber(entry);
|
|
1708
|
+
}
|
|
1680
1709
|
};
|
|
1681
1710
|
const applyChange = async (table, change, shouldBroadcast = true) => {
|
|
1682
1711
|
version += 1;
|
|
@@ -2276,9 +2305,170 @@ var createSyncEngine = (options = {}) => {
|
|
|
2276
2305
|
return () => {
|
|
2277
2306
|
activityListeners.delete(listener);
|
|
2278
2307
|
};
|
|
2308
|
+
},
|
|
2309
|
+
streamChanges: ({
|
|
2310
|
+
since = 0,
|
|
2311
|
+
signal,
|
|
2312
|
+
maxBuffer = 1e4
|
|
2313
|
+
} = {}) => {
|
|
2314
|
+
const oldest = changeLog[0];
|
|
2315
|
+
if (since > 0 && oldest !== undefined && oldest.version > since + 1) {
|
|
2316
|
+
const err = new MissedChangesError(since, oldest.version);
|
|
2317
|
+
return {
|
|
2318
|
+
[Symbol.asyncIterator]() {
|
|
2319
|
+
return {
|
|
2320
|
+
next: () => Promise.reject(err)
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
const buffer = [];
|
|
2326
|
+
let waiter = null;
|
|
2327
|
+
let overflow = false;
|
|
2328
|
+
const wake = () => {
|
|
2329
|
+
if (waiter !== null) {
|
|
2330
|
+
const resume = waiter;
|
|
2331
|
+
waiter = null;
|
|
2332
|
+
resume();
|
|
2333
|
+
}
|
|
2334
|
+
};
|
|
2335
|
+
const subscriber = (entry) => {
|
|
2336
|
+
if (buffer.length >= maxBuffer) {
|
|
2337
|
+
overflow = true;
|
|
2338
|
+
wake();
|
|
2339
|
+
return;
|
|
2340
|
+
}
|
|
2341
|
+
buffer.push(entry);
|
|
2342
|
+
wake();
|
|
2343
|
+
};
|
|
2344
|
+
streamSubscribers.add(subscriber);
|
|
2345
|
+
const onAbort = () => wake();
|
|
2346
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2347
|
+
let lastDelivered = since;
|
|
2348
|
+
return {
|
|
2349
|
+
async* [Symbol.asyncIterator]() {
|
|
2350
|
+
try {
|
|
2351
|
+
const history = [...changeLog];
|
|
2352
|
+
const headVersion = history.length > 0 ? history[history.length - 1].version : since;
|
|
2353
|
+
for (const entry of history) {
|
|
2354
|
+
if (signal?.aborted)
|
|
2355
|
+
return;
|
|
2356
|
+
if (entry.version > since) {
|
|
2357
|
+
lastDelivered = entry.version;
|
|
2358
|
+
yield entry;
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
while (!signal?.aborted) {
|
|
2362
|
+
while (buffer.length > 0) {
|
|
2363
|
+
const entry = buffer.shift();
|
|
2364
|
+
if (entry.version > headVersion) {
|
|
2365
|
+
lastDelivered = entry.version;
|
|
2366
|
+
yield entry;
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
if (overflow) {
|
|
2370
|
+
throw new CdcConsumerSlowError(maxBuffer, lastDelivered);
|
|
2371
|
+
}
|
|
2372
|
+
if (signal?.aborted)
|
|
2373
|
+
return;
|
|
2374
|
+
await new Promise((resolve) => {
|
|
2375
|
+
waiter = resolve;
|
|
2376
|
+
});
|
|
2377
|
+
}
|
|
2378
|
+
} finally {
|
|
2379
|
+
streamSubscribers.delete(subscriber);
|
|
2380
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
};
|
|
2279
2384
|
}
|
|
2280
2385
|
};
|
|
2281
2386
|
};
|
|
2387
|
+
// src/engine/cdc.ts
|
|
2388
|
+
import { Elysia } from "elysia";
|
|
2389
|
+
var parseSince = (query2, lastEventId) => {
|
|
2390
|
+
const raw = query2.since ?? lastEventId ?? "0";
|
|
2391
|
+
const parsed = Number(raw);
|
|
2392
|
+
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : 0;
|
|
2393
|
+
};
|
|
2394
|
+
var encodeEvent = (event, id, data) => {
|
|
2395
|
+
const parts = [];
|
|
2396
|
+
if (id !== null)
|
|
2397
|
+
parts.push(`id: ${id}`);
|
|
2398
|
+
parts.push(`event: ${event}`);
|
|
2399
|
+
parts.push(`data: ${JSON.stringify(data)}`);
|
|
2400
|
+
return `${parts.join(`
|
|
2401
|
+
`)}
|
|
2402
|
+
|
|
2403
|
+
`;
|
|
2404
|
+
};
|
|
2405
|
+
var syncCdc = ({
|
|
2406
|
+
engine,
|
|
2407
|
+
path = "/sync/cdc",
|
|
2408
|
+
heartbeatMs = 25000,
|
|
2409
|
+
maxBuffer = 1e4
|
|
2410
|
+
}) => new Elysia({ name: "@absolutejs/sync/cdc" }).get(path, (context) => {
|
|
2411
|
+
const lastEventId = context.request.headers.get("last-event-id");
|
|
2412
|
+
const since = parseSince(context.query, lastEventId);
|
|
2413
|
+
const encoder = new TextEncoder;
|
|
2414
|
+
const stream = new ReadableStream({
|
|
2415
|
+
async start(controller) {
|
|
2416
|
+
const write = (chunk) => {
|
|
2417
|
+
try {
|
|
2418
|
+
controller.enqueue(encoder.encode(chunk));
|
|
2419
|
+
} catch {}
|
|
2420
|
+
};
|
|
2421
|
+
write(encodeEvent("open", null, {
|
|
2422
|
+
since,
|
|
2423
|
+
at: Date.now()
|
|
2424
|
+
}));
|
|
2425
|
+
const heartbeat = setInterval(() => write(`: ping
|
|
2426
|
+
|
|
2427
|
+
`), heartbeatMs);
|
|
2428
|
+
try {
|
|
2429
|
+
for await (const entry of engine.streamChanges({
|
|
2430
|
+
since,
|
|
2431
|
+
signal: context.request.signal,
|
|
2432
|
+
maxBuffer
|
|
2433
|
+
})) {
|
|
2434
|
+
write(encodeEvent("change", entry.version, entry));
|
|
2435
|
+
}
|
|
2436
|
+
} catch (error) {
|
|
2437
|
+
if (error instanceof MissedChangesError) {
|
|
2438
|
+
write(encodeEvent("error", null, {
|
|
2439
|
+
name: "MissedChangesError",
|
|
2440
|
+
message: error.message,
|
|
2441
|
+
requestedSince: error.requestedSince,
|
|
2442
|
+
availableSince: error.availableSince
|
|
2443
|
+
}));
|
|
2444
|
+
} else if (error instanceof CdcConsumerSlowError) {
|
|
2445
|
+
write(encodeEvent("error", null, {
|
|
2446
|
+
name: "CdcConsumerSlowError",
|
|
2447
|
+
message: error.message,
|
|
2448
|
+
lastDeliveredVersion: error.lastDeliveredVersion
|
|
2449
|
+
}));
|
|
2450
|
+
} else {
|
|
2451
|
+
write(encodeEvent("error", null, {
|
|
2452
|
+
name: error instanceof Error ? error.name : "Error",
|
|
2453
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2454
|
+
}));
|
|
2455
|
+
}
|
|
2456
|
+
} finally {
|
|
2457
|
+
clearInterval(heartbeat);
|
|
2458
|
+
try {
|
|
2459
|
+
controller.close();
|
|
2460
|
+
} catch {}
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
});
|
|
2464
|
+
return new Response(stream, {
|
|
2465
|
+
headers: {
|
|
2466
|
+
"cache-control": "no-cache, no-transform",
|
|
2467
|
+
connection: "keep-alive",
|
|
2468
|
+
"content-type": "text/event-stream"
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
});
|
|
2282
2472
|
// src/engine/schema.ts
|
|
2283
2473
|
var defineSchema = (schemas) => schemas;
|
|
2284
2474
|
var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
@@ -2527,6 +2717,7 @@ var createSyncConnection = ({
|
|
|
2527
2717
|
return { handle, close };
|
|
2528
2718
|
};
|
|
2529
2719
|
export {
|
|
2720
|
+
syncCdc,
|
|
2530
2721
|
query,
|
|
2531
2722
|
parseOutboxRow,
|
|
2532
2723
|
orderByOp,
|
|
@@ -2565,8 +2756,10 @@ export {
|
|
|
2565
2756
|
UnauthorizedError,
|
|
2566
2757
|
SchemaError,
|
|
2567
2758
|
SEARCH_SCORE_FIELD,
|
|
2568
|
-
RetriesExhaustedError
|
|
2759
|
+
RetriesExhaustedError,
|
|
2760
|
+
MissedChangesError,
|
|
2761
|
+
CdcConsumerSlowError
|
|
2569
2762
|
};
|
|
2570
2763
|
|
|
2571
|
-
//# debugId=
|
|
2764
|
+
//# debugId=3FBF66DA7F03006864756E2164756E21
|
|
2572
2765
|
//# sourceMappingURL=index.js.map
|