@absolutejs/sync 1.8.0 → 1.8.1

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.
@@ -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
  */
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 {
@@ -2443,5 +2460,5 @@ export {
2443
2460
  createPresenceHub
2444
2461
  };
2445
2462
 
2446
- //# debugId=6D7E2B1F3EF1229664756E2164756E21
2463
+ //# debugId=DDC1D4CC66583DD464756E2164756E21
2447
2464
  //# sourceMappingURL=index.js.map