@absolutejs/sync 1.8.0 → 1.9.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/engine/devtools.d.ts +27 -0
- package/dist/engine/index.d.ts +2 -0
- package/dist/engine/index.js +203 -26
- package/dist/engine/index.js.map +7 -6
- package/dist/engine/pack.d.ts +138 -0
- package/dist/engine/sandbox.d.ts +20 -20
- package/dist/engine/schedule.d.ts +14 -0
- package/dist/engine/syncEngine.d.ts +13 -0
- package/dist/index.js +201 -26
- package/dist/index.js.map +6 -5
- package/package.json +4 -4
|
@@ -35,6 +35,18 @@ export type EngineInspection = {
|
|
|
35
35
|
table: string;
|
|
36
36
|
op: RowOp;
|
|
37
37
|
}[];
|
|
38
|
+
/**
|
|
39
|
+
* Registered sync packs (see {@link SyncEngine.registerPack}). Each
|
|
40
|
+
* entry reports the pack's name, version, the tables it owns, and the
|
|
41
|
+
* tables it reads but does not own. Surfaced for devtools and for
|
|
42
|
+
* conflict diagnostics.
|
|
43
|
+
*/
|
|
44
|
+
packs: {
|
|
45
|
+
name: string;
|
|
46
|
+
version: string;
|
|
47
|
+
ownsTables: string[];
|
|
48
|
+
readsTables: string[];
|
|
49
|
+
}[];
|
|
38
50
|
};
|
|
39
51
|
/**
|
|
40
52
|
* A live engine event (see {@link SyncEngine.onActivity}): a committed change or
|
|
@@ -63,4 +75,19 @@ export type EngineActivity = {
|
|
|
63
75
|
delayMs: number;
|
|
64
76
|
errorName: string;
|
|
65
77
|
errorMessage: string;
|
|
78
|
+
} | {
|
|
79
|
+
type: 'schedule';
|
|
80
|
+
at: number;
|
|
81
|
+
name: string;
|
|
82
|
+
status: 'ok' | 'error';
|
|
83
|
+
} | {
|
|
84
|
+
/** Emitted between attempts of a retried schedule. Mirrors
|
|
85
|
+
* {@link mutationRetry}. */
|
|
86
|
+
type: 'scheduleRetry';
|
|
87
|
+
at: number;
|
|
88
|
+
name: string;
|
|
89
|
+
attempt: number;
|
|
90
|
+
delayMs: number;
|
|
91
|
+
errorName: string;
|
|
92
|
+
errorMessage: string;
|
|
66
93
|
};
|
package/dist/engine/index.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export { createVectorIndex } from './vectorIndex';
|
|
|
40
40
|
export type { VectorIndexOptions, VectorMetric } from './vectorIndex';
|
|
41
41
|
export { defineSchedule } from './schedule';
|
|
42
42
|
export type { ScheduleContext, ScheduleDefinition } from './schedule';
|
|
43
|
+
export { defineSyncPack, PackMissingDependencyError, PackTableConflictError } from './pack';
|
|
44
|
+
export type { CrdtFieldsMap, RegisteredPack, SyncPack } from './pack';
|
|
43
45
|
export { defineMutation } from './mutation';
|
|
44
46
|
export type { MutationActions, MutationDefinition, MutationHandler, TableWriter, TransactionRunner } from './mutation';
|
|
45
47
|
export type { BridgeFetchConfig, BridgeFetchEndpoint, BridgeFetchResponse, HandlerMetricsHook, HandlerMetricsRecord, SandboxConfig } from './sandbox';
|
package/dist/engine/index.js
CHANGED
|
@@ -1056,6 +1056,31 @@ var createVectorIndex = (options) => {
|
|
|
1056
1056
|
};
|
|
1057
1057
|
// src/engine/schedule.ts
|
|
1058
1058
|
var defineSchedule = (definition) => definition;
|
|
1059
|
+
// src/engine/pack.ts
|
|
1060
|
+
class PackTableConflictError extends Error {
|
|
1061
|
+
table;
|
|
1062
|
+
existingPack;
|
|
1063
|
+
newPack;
|
|
1064
|
+
constructor(table, existingPack, newPack) {
|
|
1065
|
+
super(`Pack "${newPack}" claims table "${table}", but "${existingPack}" already owns it. Use a tablePrefix on one of them.`);
|
|
1066
|
+
this.name = "PackTableConflictError";
|
|
1067
|
+
this.table = table;
|
|
1068
|
+
this.existingPack = existingPack;
|
|
1069
|
+
this.newPack = newPack;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
class PackMissingDependencyError extends Error {
|
|
1074
|
+
pack;
|
|
1075
|
+
missingTable;
|
|
1076
|
+
constructor(pack, missingTable) {
|
|
1077
|
+
super(`Pack "${pack}" requires a reader for table "${missingTable}" but none is registered. Call engine.registerReader("${missingTable}", ...) before engine.registerPack.`);
|
|
1078
|
+
this.name = "PackMissingDependencyError";
|
|
1079
|
+
this.pack = pack;
|
|
1080
|
+
this.missingTable = missingTable;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
var defineSyncPack = (pack) => pack;
|
|
1059
1084
|
// src/engine/mutation.ts
|
|
1060
1085
|
var defineMutation = (definition) => definition;
|
|
1061
1086
|
// src/engine/retry.ts
|
|
@@ -1127,12 +1152,7 @@ var wrap = (source) => `
|
|
|
1127
1152
|
}
|
|
1128
1153
|
`;
|
|
1129
1154
|
var compile = async (source, config, bridgeFetch) => {
|
|
1130
|
-
const {
|
|
1131
|
-
const isolate = await createIsolate({
|
|
1132
|
-
backend: config.backend ?? "auto",
|
|
1133
|
-
memoryLimit: config.memoryLimit ?? 32
|
|
1134
|
-
});
|
|
1135
|
-
const context = await isolate.createContext();
|
|
1155
|
+
const { Reference, createIsolatedRunner, resolveIsolatePolicy } = await loadIsolatedJsc();
|
|
1136
1156
|
const callMap = new Map;
|
|
1137
1157
|
const dispatch = new Reference((callId, op, ...rest) => {
|
|
1138
1158
|
const a = callMap.get(callId);
|
|
@@ -1156,15 +1176,25 @@ var compile = async (source, config, bridgeFetch) => {
|
|
|
1156
1176
|
throw new Error(`unknown sandbox action op: ${String(op)}`);
|
|
1157
1177
|
}
|
|
1158
1178
|
});
|
|
1159
|
-
|
|
1160
|
-
const
|
|
1179
|
+
const timeoutMs = config.timeout ?? 5000;
|
|
1180
|
+
const sourceToCall = wrap(source);
|
|
1181
|
+
const policy = resolveIsolatePolicy("tenant-script", {
|
|
1182
|
+
allowWorkerFallback: true,
|
|
1183
|
+
backend: config.backend ?? "auto",
|
|
1184
|
+
memoryLimit: config.memoryLimit ?? 32,
|
|
1185
|
+
timeout: timeoutMs
|
|
1186
|
+
});
|
|
1187
|
+
const runner = createIsolatedRunner({
|
|
1188
|
+
globals: { __dispatch: dispatch },
|
|
1189
|
+
policy
|
|
1190
|
+
});
|
|
1191
|
+
await runner.precompile("sandboxedHandler", sourceToCall);
|
|
1161
1192
|
return {
|
|
1162
|
-
callable,
|
|
1163
1193
|
callMap,
|
|
1164
|
-
context,
|
|
1165
|
-
isolate,
|
|
1166
1194
|
nextCallId: 1,
|
|
1167
|
-
|
|
1195
|
+
runner,
|
|
1196
|
+
source: sourceToCall,
|
|
1197
|
+
timeoutMs
|
|
1168
1198
|
};
|
|
1169
1199
|
};
|
|
1170
1200
|
var runBridgeFetch = async (config, url, init) => {
|
|
@@ -1220,10 +1250,7 @@ var makeSandboxedHandler = (source, config = {}, engineExtras) => {
|
|
|
1220
1250
|
const bridgeFetch = engineExtras?.bridgeFetch;
|
|
1221
1251
|
const getCompiled = async () => {
|
|
1222
1252
|
if (pending !== undefined) {
|
|
1223
|
-
|
|
1224
|
-
if (!compiled.isolate.isDisposed)
|
|
1225
|
-
return compiled;
|
|
1226
|
-
pending = undefined;
|
|
1253
|
+
return pending;
|
|
1227
1254
|
}
|
|
1228
1255
|
pending = compile(source, config, bridgeFetch);
|
|
1229
1256
|
return pending;
|
|
@@ -1234,9 +1261,13 @@ var makeSandboxedHandler = (source, config = {}, engineExtras) => {
|
|
|
1234
1261
|
compiled.callMap.set(callId, actions);
|
|
1235
1262
|
if (metricsHook === undefined) {
|
|
1236
1263
|
try {
|
|
1237
|
-
return await compiled.
|
|
1238
|
-
|
|
1239
|
-
|
|
1264
|
+
return await compiled.runner.call("sandboxedHandler", compiled.source, [callId, args, ctx], { run: { timeout: compiled.timeoutMs } });
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
if (isIsolateDisposalError(error)) {
|
|
1267
|
+
pending = undefined;
|
|
1268
|
+
await disposeCompiled(compiled);
|
|
1269
|
+
}
|
|
1270
|
+
throw error;
|
|
1240
1271
|
} finally {
|
|
1241
1272
|
compiled.callMap.delete(callId);
|
|
1242
1273
|
}
|
|
@@ -1244,8 +1275,9 @@ var makeSandboxedHandler = (source, config = {}, engineExtras) => {
|
|
|
1244
1275
|
const startedAt = performance.now();
|
|
1245
1276
|
const id = makeRandomId();
|
|
1246
1277
|
try {
|
|
1247
|
-
const { result, metrics } = await compiled.
|
|
1278
|
+
const { result, metrics } = await compiled.runner.call("sandboxedHandler", compiled.source, [callId, args, ctx], { run: { timeout: compiled.timeoutMs }, withMetrics: true });
|
|
1248
1279
|
fireMetrics(metricsHook.onMetrics, {
|
|
1280
|
+
backend: metrics.backend,
|
|
1249
1281
|
cpuMs: metrics.cpuMs,
|
|
1250
1282
|
durationMs: performance.now() - startedAt,
|
|
1251
1283
|
heapBytes: metrics.heapBytes,
|
|
@@ -1256,6 +1288,10 @@ var makeSandboxedHandler = (source, config = {}, engineExtras) => {
|
|
|
1256
1288
|
});
|
|
1257
1289
|
return result;
|
|
1258
1290
|
} catch (error) {
|
|
1291
|
+
if (isIsolateDisposalError(error)) {
|
|
1292
|
+
pending = undefined;
|
|
1293
|
+
await disposeCompiled(compiled);
|
|
1294
|
+
}
|
|
1259
1295
|
fireMetrics(metricsHook.onMetrics, {
|
|
1260
1296
|
cpuMs: 0,
|
|
1261
1297
|
durationMs: performance.now() - startedAt,
|
|
@@ -1274,6 +1310,12 @@ var makeSandboxedHandler = (source, config = {}, engineExtras) => {
|
|
|
1274
1310
|
};
|
|
1275
1311
|
};
|
|
1276
1312
|
var makeRandomId = () => `hm_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
|
|
1313
|
+
var isIsolateDisposalError = (error) => error instanceof Error && (error.name === "TimeoutError" || error.name === "MemoryLimitError" || error.name === "IsolateDisposedError");
|
|
1314
|
+
var disposeCompiled = async (compiled) => {
|
|
1315
|
+
try {
|
|
1316
|
+
await compiled.runner.dispose();
|
|
1317
|
+
} catch {}
|
|
1318
|
+
};
|
|
1277
1319
|
var fireMetrics = (hook, record) => {
|
|
1278
1320
|
let outcome;
|
|
1279
1321
|
try {
|
|
@@ -1388,6 +1430,8 @@ var createSyncEngine = (options = {}) => {
|
|
|
1388
1430
|
const writers = new Map;
|
|
1389
1431
|
const readers = new Map;
|
|
1390
1432
|
const schedules = new Map;
|
|
1433
|
+
const packTableOwners = new Map;
|
|
1434
|
+
const registeredPacks = [];
|
|
1391
1435
|
const permissions = new Map;
|
|
1392
1436
|
for (const [table, rules] of Object.entries(options.permissions ?? {})) {
|
|
1393
1437
|
permissions.set(table, rules);
|
|
@@ -2119,7 +2163,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
2119
2163
|
}
|
|
2120
2164
|
};
|
|
2121
2165
|
};
|
|
2122
|
-
|
|
2166
|
+
const engine = {
|
|
2123
2167
|
register: (collection) => {
|
|
2124
2168
|
registry.set(collection.name, collection);
|
|
2125
2169
|
for (const table of collection.tables ?? [collection.name]) {
|
|
@@ -2394,13 +2438,136 @@ var createSyncEngine = (options = {}) => {
|
|
|
2394
2438
|
throw new Error(`Unknown schedule "${name}"`);
|
|
2395
2439
|
}
|
|
2396
2440
|
const runHandler = async (tx) => {
|
|
2397
|
-
const { actions, buffered
|
|
2441
|
+
const { actions, buffered } = makeActions(tx, {}, false);
|
|
2398
2442
|
const db = makeReadHandle({}, new Set, new Set, [], false);
|
|
2399
2443
|
await schedule.run({ actions, db });
|
|
2400
|
-
return
|
|
2444
|
+
return buffered;
|
|
2401
2445
|
};
|
|
2402
|
-
const
|
|
2403
|
-
|
|
2446
|
+
const retry = schedule.retry;
|
|
2447
|
+
const maxAttempts = retry === undefined ? 1 : retry.maxAttempts ?? 5;
|
|
2448
|
+
const isRetryable = retry?.isRetryable ?? isSerializationFailure;
|
|
2449
|
+
const computeDelay = retry?.backoff ?? exponentialBackoff();
|
|
2450
|
+
const maxElapsedMs = retry?.maxElapsedMs ?? 30000;
|
|
2451
|
+
const startedAt = Date.now();
|
|
2452
|
+
let lastError;
|
|
2453
|
+
let attemptsMade = 0;
|
|
2454
|
+
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
2455
|
+
attemptsMade = attempt;
|
|
2456
|
+
try {
|
|
2457
|
+
const buffered = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
|
|
2458
|
+
await applyChangeBatch(buffered);
|
|
2459
|
+
emitActivity({
|
|
2460
|
+
type: "schedule",
|
|
2461
|
+
at: Date.now(),
|
|
2462
|
+
name,
|
|
2463
|
+
status: "ok"
|
|
2464
|
+
});
|
|
2465
|
+
return;
|
|
2466
|
+
} catch (error) {
|
|
2467
|
+
lastError = error;
|
|
2468
|
+
const elapsedMs = Date.now() - startedAt;
|
|
2469
|
+
const canRetry = attempt < maxAttempts && isRetryable(error) && elapsedMs < maxElapsedMs;
|
|
2470
|
+
if (!canRetry)
|
|
2471
|
+
break;
|
|
2472
|
+
const rawDelay = computeDelay(attempt);
|
|
2473
|
+
const remaining = maxElapsedMs - elapsedMs;
|
|
2474
|
+
if (remaining <= 0)
|
|
2475
|
+
break;
|
|
2476
|
+
const delayMs = Math.max(0, Math.min(rawDelay, remaining));
|
|
2477
|
+
emitActivity({
|
|
2478
|
+
type: "scheduleRetry",
|
|
2479
|
+
at: Date.now(),
|
|
2480
|
+
name,
|
|
2481
|
+
attempt,
|
|
2482
|
+
delayMs,
|
|
2483
|
+
errorName: error instanceof Error ? error.name : "Error",
|
|
2484
|
+
errorMessage: error instanceof Error ? error.message : String(error)
|
|
2485
|
+
});
|
|
2486
|
+
if (delayMs > 0) {
|
|
2487
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
emitActivity({
|
|
2492
|
+
type: "schedule",
|
|
2493
|
+
at: Date.now(),
|
|
2494
|
+
name,
|
|
2495
|
+
status: "error"
|
|
2496
|
+
});
|
|
2497
|
+
if (attemptsMade > 1) {
|
|
2498
|
+
throw new RetriesExhaustedError(attemptsMade, Date.now() - startedAt, lastError);
|
|
2499
|
+
}
|
|
2500
|
+
throw lastError;
|
|
2501
|
+
},
|
|
2502
|
+
registerPack: (pack) => {
|
|
2503
|
+
for (const table of pack.ownsTables) {
|
|
2504
|
+
const existing = packTableOwners.get(table);
|
|
2505
|
+
if (existing !== undefined) {
|
|
2506
|
+
throw new PackTableConflictError(table, existing, pack.name);
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
if (pack.requireDependencies === true) {
|
|
2510
|
+
for (const table of pack.readsTables ?? []) {
|
|
2511
|
+
if (!readers.has(table)) {
|
|
2512
|
+
throw new PackMissingDependencyError(pack.name, table);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
if (pack.schemas !== undefined) {
|
|
2517
|
+
for (const [table, schema] of Object.entries(pack.schemas)) {
|
|
2518
|
+
engine.registerSchema(table, schema);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
if (pack.permissions !== undefined) {
|
|
2522
|
+
for (const [table, rules] of Object.entries(pack.permissions)) {
|
|
2523
|
+
engine.registerPermissions(table, rules);
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
if (pack.readers !== undefined) {
|
|
2527
|
+
for (const [table, reader] of Object.entries(pack.readers)) {
|
|
2528
|
+
engine.registerReader(table, reader);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
if (pack.writers !== undefined) {
|
|
2532
|
+
for (const [table, writer] of Object.entries(pack.writers)) {
|
|
2533
|
+
engine.registerWriter(table, writer);
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
if (pack.crdt !== undefined) {
|
|
2537
|
+
for (const [table, fields] of Object.entries(pack.crdt)) {
|
|
2538
|
+
engine.registerCrdt(table, fields);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
for (const collection of pack.collections ?? []) {
|
|
2542
|
+
engine.register(collection);
|
|
2543
|
+
}
|
|
2544
|
+
for (const collection of pack.joinCollections ?? []) {
|
|
2545
|
+
engine.registerJoin(collection);
|
|
2546
|
+
}
|
|
2547
|
+
for (const collection of pack.graphCollections ?? []) {
|
|
2548
|
+
engine.registerGraph(collection);
|
|
2549
|
+
}
|
|
2550
|
+
for (const collection of pack.searchCollections ?? []) {
|
|
2551
|
+
engine.registerSearch(collection);
|
|
2552
|
+
}
|
|
2553
|
+
for (const query2 of pack.reactiveQueries ?? []) {
|
|
2554
|
+
engine.registerReactive(query2);
|
|
2555
|
+
}
|
|
2556
|
+
for (const mutation of pack.mutations ?? []) {
|
|
2557
|
+
engine.registerMutation(mutation);
|
|
2558
|
+
}
|
|
2559
|
+
for (const schedule of pack.schedules ?? []) {
|
|
2560
|
+
engine.registerSchedule(schedule);
|
|
2561
|
+
}
|
|
2562
|
+
for (const table of pack.ownsTables) {
|
|
2563
|
+
packTableOwners.set(table, pack.name);
|
|
2564
|
+
}
|
|
2565
|
+
registeredPacks.push({
|
|
2566
|
+
name: pack.name,
|
|
2567
|
+
version: pack.version,
|
|
2568
|
+
ownsTables: [...pack.ownsTables],
|
|
2569
|
+
readsTables: [...pack.readsTables ?? []]
|
|
2570
|
+
});
|
|
2404
2571
|
},
|
|
2405
2572
|
inspect: () => {
|
|
2406
2573
|
const collections = [...registry.entries()].map(([name, def]) => {
|
|
@@ -2440,6 +2607,12 @@ var createSyncEngine = (options = {}) => {
|
|
|
2440
2607
|
version: entry.version,
|
|
2441
2608
|
table: entry.table,
|
|
2442
2609
|
op: entry.change.op
|
|
2610
|
+
})),
|
|
2611
|
+
packs: registeredPacks.map((pack) => ({
|
|
2612
|
+
name: pack.name,
|
|
2613
|
+
version: pack.version,
|
|
2614
|
+
ownsTables: [...pack.ownsTables],
|
|
2615
|
+
readsTables: [...pack.readsTables]
|
|
2443
2616
|
}))
|
|
2444
2617
|
};
|
|
2445
2618
|
},
|
|
@@ -2526,6 +2699,7 @@ var createSyncEngine = (options = {}) => {
|
|
|
2526
2699
|
};
|
|
2527
2700
|
}
|
|
2528
2701
|
};
|
|
2702
|
+
return engine;
|
|
2529
2703
|
};
|
|
2530
2704
|
// src/engine/cdc.ts
|
|
2531
2705
|
import { Elysia } from "elysia";
|
|
@@ -2875,6 +3049,7 @@ export {
|
|
|
2875
3049
|
filterOp,
|
|
2876
3050
|
field,
|
|
2877
3051
|
exponentialBackoff,
|
|
3052
|
+
defineSyncPack,
|
|
2878
3053
|
defineSearchCollection,
|
|
2879
3054
|
defineSchema,
|
|
2880
3055
|
defineSchedule,
|
|
@@ -2900,9 +3075,11 @@ export {
|
|
|
2900
3075
|
SchemaError,
|
|
2901
3076
|
SEARCH_SCORE_FIELD,
|
|
2902
3077
|
RetriesExhaustedError,
|
|
3078
|
+
PackTableConflictError,
|
|
3079
|
+
PackMissingDependencyError,
|
|
2903
3080
|
MissedChangesError,
|
|
2904
3081
|
CdcConsumerSlowError
|
|
2905
3082
|
};
|
|
2906
3083
|
|
|
2907
|
-
//# debugId=
|
|
3084
|
+
//# debugId=3D4E309EDB79002E64756E2164756E21
|
|
2908
3085
|
//# sourceMappingURL=index.js.map
|