@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.
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Sync packs — Convex Components without the lock-in. A pack bundles
3
+ * schemas + collections + mutations + scheduled + permissions + readers/
4
+ * writers + CRDT field declarations into one npm-distributable module
5
+ * registered with a single {@link SyncEngine.registerPack} call.
6
+ *
7
+ * See `syncPacks.design.md` for the rationale and the worked examples.
8
+ *
9
+ * Pack design rules (enforced at register time):
10
+ *
11
+ * - A pack declares which tables it `ownsTables`. Two registered packs
12
+ * cannot claim the same table; the host app's directly-registered
13
+ * tables are NOT counted (host registrations always win).
14
+ * - Name construction (namespacing) is the pack's job — each published
15
+ * pack ships as a factory `create<Name>Pack(config)` that builds names
16
+ * from a `tablePrefix` and the host's user/auth context.
17
+ * - Packs compose via the subscription layer (read each other's
18
+ * collections), never via cross-pack `runMutation` calls.
19
+ */
20
+ import type { CollectionDefinition, JoinCollectionDefinition } from './collection';
21
+ import type { GraphCollectionDefinition } from './graph';
22
+ import type { MutationDefinition, TableWriter } from './mutation';
23
+ import type { PermissionsDefinition } from './permissions';
24
+ import type { ReactiveQueryDefinition, TableReader } from './reactive';
25
+ import type { ScheduleDefinition } from './schedule';
26
+ import type { SchemaDefinition } from './schema';
27
+ import type { SearchCollectionDefinition } from './search';
28
+ import type { CrdtMergeable } from '../crdt';
29
+ /**
30
+ * Same shape as the engine's per-table CRDT field map (see
31
+ * {@link SyncEngine.registerCrdt}). A pack declares CRDT field types
32
+ * here; the engine wires them on registration.
33
+ */
34
+ export type CrdtFieldsMap = Record<string, Record<string, CrdtMergeable<unknown>>>;
35
+ /**
36
+ * A pack — a self-contained bundle of every registration the engine
37
+ * already accepts. The engine's `registerPack(pack)` dispatches each
38
+ * field to the matching `engine.register*` method. There is no new
39
+ * persistence path; packs are pure composition.
40
+ */
41
+ export type SyncPack = {
42
+ /**
43
+ * Pack identifier. Used for devtools labelling and conflict
44
+ * diagnostics. Should match the npm package name (e.g.
45
+ * "@absolutejs/sync-pack-presence").
46
+ */
47
+ name: string;
48
+ /**
49
+ * Pack semver. Surfaced in {@link EngineInspection.packs} and in
50
+ * conflict diagnostics (e.g. "table 'comments' is owned by
51
+ * sync-pack-comments@2.1.0").
52
+ */
53
+ version: string;
54
+ /**
55
+ * Tables this pack OWNS. The engine rejects another pack that also
56
+ * claims one of these. Direct host registrations (e.g.
57
+ * `engine.registerSchema("foo", ...)`) are NOT tracked as ownership,
58
+ * so the host can still extend a pack's table or override its
59
+ * schema/permissions.
60
+ */
61
+ ownsTables: string[];
62
+ /**
63
+ * Tables this pack reads but does NOT own (e.g. a comments pack
64
+ * reads the host's `users` table for author info). Reported in
65
+ * {@link EngineInspection.packs}; not enforced unless
66
+ * {@link requireDependencies} is `true`.
67
+ */
68
+ readsTables?: string[];
69
+ /**
70
+ * When `true`, the engine throws {@link PackMissingDependencyError}
71
+ * at register time if any table in `readsTables` has no registered
72
+ * reader. Default `false` — host-app reads can be wired lazily.
73
+ */
74
+ requireDependencies?: boolean;
75
+ schemas?: SchemaDefinition;
76
+ permissions?: PermissionsDefinition<any>;
77
+ readers?: Record<string, TableReader<any>>;
78
+ writers?: Record<string, TableWriter<any, any, any>>;
79
+ crdt?: CrdtFieldsMap;
80
+ collections?: CollectionDefinition<any, any, any>[];
81
+ joinCollections?: JoinCollectionDefinition<any, any, any, any, any>[];
82
+ graphCollections?: GraphCollectionDefinition<any, any, any>[];
83
+ searchCollections?: SearchCollectionDefinition<any, any, any>[];
84
+ reactiveQueries?: ReactiveQueryDefinition<any, any, any>[];
85
+ mutations?: MutationDefinition<any, any, any>[];
86
+ schedules?: ScheduleDefinition[];
87
+ };
88
+ /**
89
+ * Pack metadata stored on the engine and surfaced via
90
+ * {@link EngineInspection.packs}.
91
+ */
92
+ export type RegisteredPack = {
93
+ name: string;
94
+ version: string;
95
+ ownsTables: string[];
96
+ readsTables: string[];
97
+ };
98
+ /**
99
+ * Thrown by {@link SyncEngine.registerPack} when a pack claims a table
100
+ * that another registered pack already owns. The message names both
101
+ * packs and the colliding table so the operator can pick a
102
+ * `tablePrefix`.
103
+ */
104
+ export declare class PackTableConflictError extends Error {
105
+ readonly table: string;
106
+ readonly existingPack: string;
107
+ readonly newPack: string;
108
+ constructor(table: string, existingPack: string, newPack: string);
109
+ }
110
+ /**
111
+ * Thrown by {@link SyncEngine.registerPack} when a pack has
112
+ * `requireDependencies: true` and at least one table in
113
+ * {@link SyncPack.readsTables} has no registered reader at register
114
+ * time. Pack authors opt into this when their pack cannot function
115
+ * without the host having wired the dependency up front.
116
+ */
117
+ export declare class PackMissingDependencyError extends Error {
118
+ readonly pack: string;
119
+ readonly missingTable: string;
120
+ constructor(pack: string, missingTable: string);
121
+ }
122
+ /**
123
+ * Identity helper. A pack is plain data — the helper exists for type
124
+ * inference, not for runtime behavior.
125
+ *
126
+ * @example
127
+ * export const createPresencePack = (config: PresencePackConfig) =>
128
+ * defineSyncPack({
129
+ * name: '@absolutejs/sync-pack-presence',
130
+ * version: '0.1.0',
131
+ * ownsTables: [resolveTableName('presence', config.tablePrefix)],
132
+ * schemas: { ... },
133
+ * collections: [ ... ],
134
+ * mutations: [ ... ],
135
+ * schedules: [ ... ],
136
+ * });
137
+ */
138
+ export declare const defineSyncPack: (pack: SyncPack) => SyncPack;
@@ -9,10 +9,9 @@
9
9
  * - Handler must be a string. It evaluates inside the isolate's JSC VM, with
10
10
  * no access to the host's modules, closures, or globals — only the
11
11
  * `args` / `ctx` clones and the `actions` Reference we pass in.
12
- * - First call per mutation pays an isolate spawn + compile (~3–25 ms
13
- * depending on backend). Every subsequent call is a single
14
- * `JSObjectCallAsFunction` (FFI) or one postMessage (Worker) no
15
- * per-call eval, no per-call `setGlobal`.
12
+ * - First call per mutation pays an isolated-jsc runner warmup + callable
13
+ * compile. Every subsequent call is served from the runner's keyed
14
+ * callable cache no per-call eval, no per-call `setGlobal`.
16
15
  * - Timeout terminates the isolate (the sandbox runner detects this and
17
16
  * lazily re-spawns on the next call). On the FFI backend timeouts throw
18
17
  * a TerminationException without killing the isolate; sync's runner
@@ -25,12 +24,11 @@
25
24
  * `WebSocket`) inside your handler — those live in the Bun-Worker
26
25
  * environment, not the bare JSC C API.
27
26
  *
28
- * **Per-call hot path (since 1.7.4 / isolated-jsc 0.6).** Each mutation is
29
- * compiled to a {@link Callable} once — a precompiled function expression
30
- * the sandbox owns by reference. Per call we invoke
31
- * `callable.call([args, ctx, dispatch])` where `dispatch` is a Reference
32
- * that bridges `actions.*` back to the host. No globals, no eval per call,
33
- * no shared-slot serialization machinery.
27
+ * **Per-call hot path (since isolated-jsc 0.8).** Each mutation owns a
28
+ * `createIsolatedRunner()` instance. The runner applies the `tenant-script`
29
+ * policy, keeps a keyed isolate pool, and caches the wrapped mutation as a
30
+ * precompiled callable. Per call we invoke `runner.call(name, source, args)`
31
+ * with a call id that routes `actions.*` back through a host Reference.
34
32
  *
35
33
  * The runner is built lazily per-mutation: nothing is spawned until the
36
34
  * mutation actually runs for the first time. No engine teardown hook is
@@ -66,10 +64,12 @@ export type HandlerMetricsRecord = {
66
64
  durationMs: number;
67
65
  /**
68
66
  * CPU time spent inside the JSC sandbox (ms). Comes from
69
- * `Script.runWithMetrics` — does NOT include host-side message-passing
67
+ * isolated-jsc runner metrics — does NOT include host-side message-passing
70
68
  * overhead on the Worker backend. Sub-millisecond runs round to 0.
71
69
  */
72
70
  cpuMs: number;
71
+ /** isolated-jsc backend that executed the call. */
72
+ backend?: 'ffi' | 'worker';
73
73
  /**
74
74
  * Heap size (bytes) measured immediately after the script returned.
75
75
  * Not the run's peak — a true peak needs continuous polling.
@@ -159,9 +159,9 @@ export type SandboxConfig = {
159
159
  timeout?: number;
160
160
  /**
161
161
  * isolated-jsc backend. Defaults to `'auto'` (FFI when libJSC is
162
- * reachable, Worker otherwise). Both backends now run the same
163
- * `Context.compileCallable`-based hot path; the choice trades cold
164
- * spawn (FFI wins ~6×) against Web API availability (Worker only).
162
+ * reachable, Worker otherwise). Both backends now run through the same
163
+ * `createIsolatedRunner().call()` hot path; the choice trades cold spawn
164
+ * (FFI wins ~6×) against Web API availability (Worker only).
165
165
  *
166
166
  * Pin to `'worker'` if your handler needs Web APIs (`URL`,
167
167
  * `TextEncoder`, `WebSocket`) — those live in the Bun-Worker
@@ -174,12 +174,11 @@ export type SandboxConfig = {
174
174
  };
175
175
  /**
176
176
  * Build a lazy runner for one mutation's sandboxed source. The first call
177
- * compiles the isolate + context + dispatch Reference + callable;
177
+ * compiles the runner + dispatch Reference + callable;
178
178
  * subsequent calls only generate a fresh callId, register the per-call
179
- * `actions` in the callMap, and invoke `callable.call([callId, args,
180
- * ctx])`. Per-call cost on FFI: one JSObjectCallAsFunction + three
181
- * cheap primitive packings. No per-call Reference allocation, no
182
- * setGlobal, no eval.
179
+ * `actions` in the callMap, and invoke `runner.call(...)`. Per-call cost on
180
+ * FFI: one JSObjectCallAsFunction + three cheap primitive packings. No
181
+ * per-call Reference allocation, no setGlobal, no eval.
183
182
  *
184
183
  * Concurrency-safe by construction: each call has its own callId →
185
184
  * its own actions slot in the callMap.
@@ -191,7 +190,8 @@ export declare const makeSandboxedHandler: (source: string, config?: SandboxConf
191
190
  /**
192
191
  * Engine-level extras the per-mutation config doesn't carry:
193
192
  * - `metricsHook` enables per-call telemetry via
194
- * `callable.callWithMetrics` (small cost; off without the hook).
193
+ * `runner.call(..., { withMetrics: true })` (small cost; off without
194
+ * the hook).
195
195
  * - `bridgeFetch` enables `actions.fetch(url, init)` inside the
196
196
  * sandbox with host-side allowlist + auth injection.
197
197
  */
@@ -1,5 +1,6 @@
1
1
  import type { MutationActions } from './mutation';
2
2
  import type { ReadHandle } from './reactive';
3
+ import type { RetryPolicy } from './retry';
3
4
  /**
4
5
  * Scheduled functions — server-triggered work whose effects flow through the
5
6
  * change feed, so what a schedule writes goes live to subscribers with no extra
@@ -30,6 +31,19 @@ export type ScheduleDefinition = {
30
31
  pattern: string;
31
32
  /** The work to run on each fire. Writes via `ctx.actions` go live. */
32
33
  run: (ctx: ScheduleContext) => Promise<void> | void;
34
+ /**
35
+ * Opt-in retry of the whole handler on classified-as-retryable errors —
36
+ * same shape and defaults as {@link MutationDefinition.retry}. When set
37
+ * and `run` throws a retryable error, the engine discards the buffered
38
+ * changes, awaits a backoff, and re-runs the handler with a fresh
39
+ * transaction. The handler MUST be idempotent under retry (external
40
+ * side effects fire more than once).
41
+ *
42
+ * For per-item retry (e.g. one of many emails failing), write that
43
+ * loop inside the handler — this outer retry covers transient
44
+ * infrastructure failures of the whole fire, not per-item logic.
45
+ */
46
+ retry?: RetryPolicy;
33
47
  };
34
48
  /**
35
49
  * Define a scheduled function. Identity at runtime (for type inference). Register
@@ -4,6 +4,7 @@ import type { MutationDefinition, TableWriter, TransactionRunner } from './mutat
4
4
  import type { ReactiveQueryDefinition, TableReader } from './reactive';
5
5
  import { type BridgeFetchConfig, type HandlerMetricsHook } from './sandbox';
6
6
  import type { PermissionsDefinition, TablePermissions } from './permissions';
7
+ import type { SyncPack } from './pack';
7
8
  import type { SearchCollectionDefinition } from './search';
8
9
  import type { ScheduleDefinition } from './schedule';
9
10
  import type { EngineActivity, EngineInspection } from './devtools';
@@ -213,6 +214,18 @@ export type SyncEngine = {
213
214
  * }
214
215
  */
215
216
  streamChanges: (options?: StreamChangesOptions) => AsyncIterable<LoggedChange>;
217
+ /**
218
+ * Register a {@link SyncPack} — a self-contained bundle of schemas,
219
+ * permissions, readers/writers, collections, mutations, and schedules.
220
+ * Dispatches each field to the matching `register*` method. Rejects
221
+ * with {@link PackTableConflictError} if the pack claims a table
222
+ * another registered pack already owns; with
223
+ * {@link PackMissingDependencyError} if `requireDependencies` is set
224
+ * and a `readsTables` entry has no registered reader.
225
+ *
226
+ * See `syncPacks.design.md` for the rationale.
227
+ */
228
+ registerPack: (pack: SyncPack) => void;
216
229
  };
217
230
  /**
218
231
  * A single committed change as it appears in the engine's change log and on
package/dist/index.js CHANGED
@@ -759,12 +759,7 @@ var wrap = (source) => `
759
759
  }
760
760
  `;
761
761
  var compile = async (source, config, bridgeFetch) => {
762
- const { createIsolate, Reference } = await loadIsolatedJsc();
763
- const isolate = await createIsolate({
764
- backend: config.backend ?? "auto",
765
- memoryLimit: config.memoryLimit ?? 32
766
- });
767
- const context = await isolate.createContext();
762
+ const { Reference, createIsolatedRunner, resolveIsolatePolicy } = await loadIsolatedJsc();
768
763
  const callMap = new Map;
769
764
  const dispatch = new Reference((callId, op, ...rest) => {
770
765
  const a = callMap.get(callId);
@@ -788,15 +783,25 @@ var compile = async (source, config, bridgeFetch) => {
788
783
  throw new Error(`unknown sandbox action op: ${String(op)}`);
789
784
  }
790
785
  });
791
- await context.setGlobal("__dispatch", dispatch);
792
- const callable = await context.compileCallable(wrap(source));
786
+ const timeoutMs = config.timeout ?? 5000;
787
+ const sourceToCall = wrap(source);
788
+ const policy = resolveIsolatePolicy("tenant-script", {
789
+ allowWorkerFallback: true,
790
+ backend: config.backend ?? "auto",
791
+ memoryLimit: config.memoryLimit ?? 32,
792
+ timeout: timeoutMs
793
+ });
794
+ const runner = createIsolatedRunner({
795
+ globals: { __dispatch: dispatch },
796
+ policy
797
+ });
798
+ await runner.precompile("sandboxedHandler", sourceToCall);
793
799
  return {
794
- callable,
795
800
  callMap,
796
- context,
797
- isolate,
798
801
  nextCallId: 1,
799
- timeoutMs: config.timeout ?? 5000
802
+ runner,
803
+ source: sourceToCall,
804
+ timeoutMs
800
805
  };
801
806
  };
802
807
  var runBridgeFetch = async (config, url, init) => {
@@ -852,10 +857,7 @@ var makeSandboxedHandler = (source, config = {}, engineExtras) => {
852
857
  const bridgeFetch = engineExtras?.bridgeFetch;
853
858
  const getCompiled = async () => {
854
859
  if (pending !== undefined) {
855
- const compiled = await pending;
856
- if (!compiled.isolate.isDisposed)
857
- return compiled;
858
- pending = undefined;
860
+ return pending;
859
861
  }
860
862
  pending = compile(source, config, bridgeFetch);
861
863
  return pending;
@@ -866,9 +868,13 @@ var makeSandboxedHandler = (source, config = {}, engineExtras) => {
866
868
  compiled.callMap.set(callId, actions);
867
869
  if (metricsHook === undefined) {
868
870
  try {
869
- return await compiled.callable.call([callId, args, ctx], {
870
- timeout: compiled.timeoutMs
871
- });
871
+ return await compiled.runner.call("sandboxedHandler", compiled.source, [callId, args, ctx], { run: { timeout: compiled.timeoutMs } });
872
+ } catch (error) {
873
+ if (isIsolateDisposalError(error)) {
874
+ pending = undefined;
875
+ await disposeCompiled(compiled);
876
+ }
877
+ throw error;
872
878
  } finally {
873
879
  compiled.callMap.delete(callId);
874
880
  }
@@ -876,8 +882,9 @@ var makeSandboxedHandler = (source, config = {}, engineExtras) => {
876
882
  const startedAt = performance.now();
877
883
  const id = makeRandomId();
878
884
  try {
879
- const { result, metrics } = await compiled.callable.callWithMetrics([callId, args, ctx], { timeout: compiled.timeoutMs });
885
+ const { result, metrics } = await compiled.runner.call("sandboxedHandler", compiled.source, [callId, args, ctx], { run: { timeout: compiled.timeoutMs }, withMetrics: true });
880
886
  fireMetrics(metricsHook.onMetrics, {
887
+ backend: metrics.backend,
881
888
  cpuMs: metrics.cpuMs,
882
889
  durationMs: performance.now() - startedAt,
883
890
  heapBytes: metrics.heapBytes,
@@ -888,6 +895,10 @@ var makeSandboxedHandler = (source, config = {}, engineExtras) => {
888
895
  });
889
896
  return result;
890
897
  } catch (error) {
898
+ if (isIsolateDisposalError(error)) {
899
+ pending = undefined;
900
+ await disposeCompiled(compiled);
901
+ }
891
902
  fireMetrics(metricsHook.onMetrics, {
892
903
  cpuMs: 0,
893
904
  durationMs: performance.now() - startedAt,
@@ -906,6 +917,12 @@ var makeSandboxedHandler = (source, config = {}, engineExtras) => {
906
917
  };
907
918
  };
908
919
  var makeRandomId = () => `hm_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;
920
+ var isIsolateDisposalError = (error) => error instanceof Error && (error.name === "TimeoutError" || error.name === "MemoryLimitError" || error.name === "IsolateDisposedError");
921
+ var disposeCompiled = async (compiled) => {
922
+ try {
923
+ await compiled.runner.dispose();
924
+ } catch {}
925
+ };
909
926
  var fireMetrics = (hook, record) => {
910
927
  let outcome;
911
928
  try {
@@ -918,6 +935,32 @@ var fireMetrics = (hook, record) => {
918
935
  }
919
936
  };
920
937
 
938
+ // src/engine/pack.ts
939
+ class PackTableConflictError extends Error {
940
+ table;
941
+ existingPack;
942
+ newPack;
943
+ constructor(table, existingPack, newPack) {
944
+ super(`Pack "${newPack}" claims table "${table}", but "${existingPack}" already owns it. Use a tablePrefix on one of them.`);
945
+ this.name = "PackTableConflictError";
946
+ this.table = table;
947
+ this.existingPack = existingPack;
948
+ this.newPack = newPack;
949
+ }
950
+ }
951
+
952
+ class PackMissingDependencyError extends Error {
953
+ pack;
954
+ missingTable;
955
+ constructor(pack, missingTable) {
956
+ super(`Pack "${pack}" requires a reader for table "${missingTable}" but none is registered. Call engine.registerReader("${missingTable}", ...) before engine.registerPack.`);
957
+ this.name = "PackMissingDependencyError";
958
+ this.pack = pack;
959
+ this.missingTable = missingTable;
960
+ }
961
+ }
962
+ var defineSyncPack = (pack) => pack;
963
+
921
964
  // src/engine/search.ts
922
965
  var SEARCH_SCORE_FIELD = "_score";
923
966
  var defineSearchCollection = (definition) => ({
@@ -1027,6 +1070,8 @@ var createSyncEngine = (options = {}) => {
1027
1070
  const writers = new Map;
1028
1071
  const readers = new Map;
1029
1072
  const schedules = new Map;
1073
+ const packTableOwners = new Map;
1074
+ const registeredPacks = [];
1030
1075
  const permissions = new Map;
1031
1076
  for (const [table, rules] of Object.entries(options.permissions ?? {})) {
1032
1077
  permissions.set(table, rules);
@@ -1758,7 +1803,7 @@ var createSyncEngine = (options = {}) => {
1758
1803
  }
1759
1804
  };
1760
1805
  };
1761
- return {
1806
+ const engine = {
1762
1807
  register: (collection) => {
1763
1808
  registry.set(collection.name, collection);
1764
1809
  for (const table of collection.tables ?? [collection.name]) {
@@ -2033,13 +2078,136 @@ var createSyncEngine = (options = {}) => {
2033
2078
  throw new Error(`Unknown schedule "${name}"`);
2034
2079
  }
2035
2080
  const runHandler = async (tx) => {
2036
- const { actions, buffered: buffered2 } = makeActions(tx, {}, false);
2081
+ const { actions, buffered } = makeActions(tx, {}, false);
2037
2082
  const db = makeReadHandle({}, new Set, new Set, [], false);
2038
2083
  await schedule.run({ actions, db });
2039
- return buffered2;
2084
+ return buffered;
2040
2085
  };
2041
- const buffered = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
2042
- await applyChangeBatch(buffered);
2086
+ const retry = schedule.retry;
2087
+ const maxAttempts = retry === undefined ? 1 : retry.maxAttempts ?? 5;
2088
+ const isRetryable = retry?.isRetryable ?? isSerializationFailure;
2089
+ const computeDelay = retry?.backoff ?? exponentialBackoff();
2090
+ const maxElapsedMs = retry?.maxElapsedMs ?? 30000;
2091
+ const startedAt = Date.now();
2092
+ let lastError;
2093
+ let attemptsMade = 0;
2094
+ for (let attempt = 1;attempt <= maxAttempts; attempt++) {
2095
+ attemptsMade = attempt;
2096
+ try {
2097
+ const buffered = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
2098
+ await applyChangeBatch(buffered);
2099
+ emitActivity({
2100
+ type: "schedule",
2101
+ at: Date.now(),
2102
+ name,
2103
+ status: "ok"
2104
+ });
2105
+ return;
2106
+ } catch (error) {
2107
+ lastError = error;
2108
+ const elapsedMs = Date.now() - startedAt;
2109
+ const canRetry = attempt < maxAttempts && isRetryable(error) && elapsedMs < maxElapsedMs;
2110
+ if (!canRetry)
2111
+ break;
2112
+ const rawDelay = computeDelay(attempt);
2113
+ const remaining = maxElapsedMs - elapsedMs;
2114
+ if (remaining <= 0)
2115
+ break;
2116
+ const delayMs = Math.max(0, Math.min(rawDelay, remaining));
2117
+ emitActivity({
2118
+ type: "scheduleRetry",
2119
+ at: Date.now(),
2120
+ name,
2121
+ attempt,
2122
+ delayMs,
2123
+ errorName: error instanceof Error ? error.name : "Error",
2124
+ errorMessage: error instanceof Error ? error.message : String(error)
2125
+ });
2126
+ if (delayMs > 0) {
2127
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
2128
+ }
2129
+ }
2130
+ }
2131
+ emitActivity({
2132
+ type: "schedule",
2133
+ at: Date.now(),
2134
+ name,
2135
+ status: "error"
2136
+ });
2137
+ if (attemptsMade > 1) {
2138
+ throw new RetriesExhaustedError(attemptsMade, Date.now() - startedAt, lastError);
2139
+ }
2140
+ throw lastError;
2141
+ },
2142
+ registerPack: (pack) => {
2143
+ for (const table of pack.ownsTables) {
2144
+ const existing = packTableOwners.get(table);
2145
+ if (existing !== undefined) {
2146
+ throw new PackTableConflictError(table, existing, pack.name);
2147
+ }
2148
+ }
2149
+ if (pack.requireDependencies === true) {
2150
+ for (const table of pack.readsTables ?? []) {
2151
+ if (!readers.has(table)) {
2152
+ throw new PackMissingDependencyError(pack.name, table);
2153
+ }
2154
+ }
2155
+ }
2156
+ if (pack.schemas !== undefined) {
2157
+ for (const [table, schema] of Object.entries(pack.schemas)) {
2158
+ engine.registerSchema(table, schema);
2159
+ }
2160
+ }
2161
+ if (pack.permissions !== undefined) {
2162
+ for (const [table, rules] of Object.entries(pack.permissions)) {
2163
+ engine.registerPermissions(table, rules);
2164
+ }
2165
+ }
2166
+ if (pack.readers !== undefined) {
2167
+ for (const [table, reader] of Object.entries(pack.readers)) {
2168
+ engine.registerReader(table, reader);
2169
+ }
2170
+ }
2171
+ if (pack.writers !== undefined) {
2172
+ for (const [table, writer] of Object.entries(pack.writers)) {
2173
+ engine.registerWriter(table, writer);
2174
+ }
2175
+ }
2176
+ if (pack.crdt !== undefined) {
2177
+ for (const [table, fields] of Object.entries(pack.crdt)) {
2178
+ engine.registerCrdt(table, fields);
2179
+ }
2180
+ }
2181
+ for (const collection of pack.collections ?? []) {
2182
+ engine.register(collection);
2183
+ }
2184
+ for (const collection of pack.joinCollections ?? []) {
2185
+ engine.registerJoin(collection);
2186
+ }
2187
+ for (const collection of pack.graphCollections ?? []) {
2188
+ engine.registerGraph(collection);
2189
+ }
2190
+ for (const collection of pack.searchCollections ?? []) {
2191
+ engine.registerSearch(collection);
2192
+ }
2193
+ for (const query of pack.reactiveQueries ?? []) {
2194
+ engine.registerReactive(query);
2195
+ }
2196
+ for (const mutation of pack.mutations ?? []) {
2197
+ engine.registerMutation(mutation);
2198
+ }
2199
+ for (const schedule of pack.schedules ?? []) {
2200
+ engine.registerSchedule(schedule);
2201
+ }
2202
+ for (const table of pack.ownsTables) {
2203
+ packTableOwners.set(table, pack.name);
2204
+ }
2205
+ registeredPacks.push({
2206
+ name: pack.name,
2207
+ version: pack.version,
2208
+ ownsTables: [...pack.ownsTables],
2209
+ readsTables: [...pack.readsTables ?? []]
2210
+ });
2043
2211
  },
2044
2212
  inspect: () => {
2045
2213
  const collections = [...registry.entries()].map(([name, def]) => {
@@ -2079,6 +2247,12 @@ var createSyncEngine = (options = {}) => {
2079
2247
  version: entry.version,
2080
2248
  table: entry.table,
2081
2249
  op: entry.change.op
2250
+ })),
2251
+ packs: registeredPacks.map((pack) => ({
2252
+ name: pack.name,
2253
+ version: pack.version,
2254
+ ownsTables: [...pack.ownsTables],
2255
+ readsTables: [...pack.readsTables]
2082
2256
  }))
2083
2257
  };
2084
2258
  },
@@ -2165,6 +2339,7 @@ var createSyncEngine = (options = {}) => {
2165
2339
  };
2166
2340
  }
2167
2341
  };
2342
+ return engine;
2168
2343
  };
2169
2344
 
2170
2345
  // src/engine/cdc.ts
@@ -2443,5 +2618,5 @@ export {
2443
2618
  createPresenceHub
2444
2619
  };
2445
2620
 
2446
- //# debugId=6D7E2B1F3EF1229664756E2164756E21
2621
+ //# debugId=483AEB81CDF95A5764756E2164756E21
2447
2622
  //# sourceMappingURL=index.js.map