@absolutejs/sync 1.11.0 → 1.12.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.
@@ -171,6 +171,52 @@ export type SandboxConfig = {
171
171
  * reachable (e.g. CI with a known image).
172
172
  */
173
173
  backend?: 'auto' | 'ffi' | 'worker';
174
+ /**
175
+ * **Escape hatch.** Map of host functions the sandboxed handler may
176
+ * call as `unsafeHost.fnName(...args)`. The name is deliberately
177
+ * loud: anyone reading the source must see immediately that a
178
+ * sandboxed mutation is reaching through to non-deterministic host
179
+ * code (third-party API, queue push, email send, anything that
180
+ * touches the outside world).
181
+ *
182
+ * Without this option, the sandbox is hermetic — only `args`,
183
+ * `ctx`, and `actions` are reachable. Declare an entry here, name
184
+ * it visibly (e.g. `chargeStripe`, `sendSlackPing`), and the engine
185
+ * exposes it on the sandbox-side `unsafeHost` Proxy. The fn runs on
186
+ * the host; its return value is structured-cloned back across the
187
+ * isolate boundary; thrown errors propagate into the sandbox as
188
+ * normal JS errors the handler can catch.
189
+ *
190
+ * The deterministic-mutation guarantees stop at the call site —
191
+ * retries WILL re-fire these host fns (they're outside the
192
+ * transaction). Treat them as side effects and either make them
193
+ * idempotent or pair them with explicit compensation in the
194
+ * handler. Convex's actions are the same model; this is the same
195
+ * trade.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * defineMutation({
200
+ * name: 'payments:checkout',
201
+ * sandboxedHandler: `async (args, ctx, actions, unsafeHost) => {
202
+ * const order = await actions.insert('orders', { ...args, status: 'pending' });
203
+ * const receipt = await unsafeHost.chargeStripe({
204
+ * amount: order.amount,
205
+ * token: args.token,
206
+ * });
207
+ * await actions.update('orders', { id: order.id, status: 'paid', receipt });
208
+ * return order;
209
+ * }`,
210
+ * sandbox: {
211
+ * unsafeHost: {
212
+ * chargeStripe: ({ amount, token }) =>
213
+ * stripe.charges.create({ amount, source: token }),
214
+ * },
215
+ * },
216
+ * });
217
+ * ```
218
+ */
219
+ unsafeHost?: Record<string, (...args: any[]) => unknown | Promise<unknown>>;
174
220
  };
175
221
  /**
176
222
  * Build a lazy runner for one mutation's sandboxed source. The first call
package/dist/index.js CHANGED
@@ -179,7 +179,8 @@ var sync = ({
179
179
  headers: {
180
180
  "cache-control": "no-cache, no-transform",
181
181
  connection: "keep-alive",
182
- "content-type": "text/event-stream"
182
+ "content-type": "text/event-stream",
183
+ "x-accel-buffering": "no"
183
184
  }
184
185
  });
185
186
  });
@@ -740,7 +741,7 @@ var wrap = (source) => `
740
741
  const userFn = (${source});
741
742
  if (typeof userFn !== 'function') {
742
743
  throw new Error(
743
- 'sandboxedHandler must evaluate to (args, ctx, actions) => result; got ' +
744
+ 'sandboxedHandler must evaluate to (args, ctx, actions, unsafeHost) => result; got ' +
744
745
  typeof userFn
745
746
  );
746
747
  }
@@ -752,12 +753,24 @@ var wrap = (source) => `
752
753
  now: () => __dispatch(__callId, 'now'),
753
754
  fetch: (url, init) => __dispatch(__callId, 'fetch', url, init)
754
755
  };
755
- return userFn(args, ctx, actions);
756
+ // Escape hatch \u2014 host fns the mutation explicitly opted in to.
757
+ // The Proxy means every property access is a host call; the
758
+ // engine throws if the property name isn't declared in the
759
+ // mutation's sandbox.unsafeHost map.
760
+ const unsafeHost = new Proxy({}, {
761
+ get: (_target, fnName) => {
762
+ if (typeof fnName !== 'string') return undefined;
763
+ return (...callArgs) =>
764
+ __dispatch(__callId, 'unsafeHost', fnName, callArgs);
765
+ }
766
+ });
767
+ return userFn(args, ctx, actions, unsafeHost);
756
768
  }
757
769
  `;
758
770
  var compile = async (source, config, bridgeFetch) => {
759
771
  const { Reference, createIsolatedRunner, resolveIsolatePolicy } = await loadIsolatedJsc();
760
772
  const callMap = new Map;
773
+ const unsafeHost = config.unsafeHost;
761
774
  const dispatch = new Reference((callId, op, ...rest) => {
762
775
  const a = callMap.get(callId);
763
776
  if (a === undefined) {
@@ -776,6 +789,14 @@ var compile = async (source, config, bridgeFetch) => {
776
789
  return a.now();
777
790
  case "fetch":
778
791
  return runBridgeFetch(bridgeFetch, rest[0], rest[1]);
792
+ case "unsafeHost": {
793
+ const fnName = rest[0];
794
+ const callArgs = rest[1] ?? [];
795
+ if (unsafeHost === undefined || typeof unsafeHost[fnName] !== "function") {
796
+ throw new Error(`sandboxedHandler called unsafeHost.${fnName}() but it was not declared in the mutation's sandbox.unsafeHost config. Declare it (and only the host fns you intend to expose) to opt in to the escape hatch.`);
797
+ }
798
+ return unsafeHost[fnName](...callArgs);
799
+ }
779
800
  default:
780
801
  throw new Error(`unknown sandbox action op: ${String(op)}`);
781
802
  }
@@ -2478,7 +2499,8 @@ var syncCdc = ({
2478
2499
  headers: {
2479
2500
  "cache-control": "no-cache, no-transform",
2480
2501
  connection: "keep-alive",
2481
- "content-type": "text/event-stream"
2502
+ "content-type": "text/event-stream",
2503
+ "x-accel-buffering": "no"
2482
2504
  }
2483
2505
  });
2484
2506
  });
@@ -2593,7 +2615,8 @@ data: ${JSON.stringify(event)}
2593
2615
  headers: {
2594
2616
  "cache-control": "no-cache, no-transform",
2595
2617
  connection: "keep-alive",
2596
- "content-type": "text/event-stream"
2618
+ "content-type": "text/event-stream",
2619
+ "x-accel-buffering": "no"
2597
2620
  }
2598
2621
  });
2599
2622
  });
@@ -2675,5 +2698,5 @@ export {
2675
2698
  createPresenceHub
2676
2699
  };
2677
2700
 
2678
- //# debugId=FFB4969DBBAB620C64756E2164756E21
2701
+ //# debugId=D6679B754081BCC764756E2164756E21
2679
2702
  //# sourceMappingURL=index.js.map