@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.
@@ -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
  };
@@ -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';
@@ -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 { createIsolate, Reference } = await loadIsolatedJsc();
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
- await context.setGlobal("__dispatch", dispatch);
1160
- const callable = await context.compileCallable(wrap(source));
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
- timeoutMs: config.timeout ?? 5000
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
- const compiled = await pending;
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.callable.call([callId, args, ctx], {
1238
- timeout: compiled.timeoutMs
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.callable.callWithMetrics([callId, args, ctx], { timeout: compiled.timeoutMs });
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
- return {
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: buffered2 } = makeActions(tx, {}, false);
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 buffered2;
2444
+ return buffered;
2401
2445
  };
2402
- const buffered = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
2403
- await applyChangeBatch(buffered);
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=783626175939945A64756E2164756E21
3084
+ //# debugId=3D4E309EDB79002E64756E2164756E21
2908
3085
  //# sourceMappingURL=index.js.map