@eryx-org/eryx 0.0.0 → 0.15.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.
Files changed (63) hide show
  1. package/README.md +281 -4
  2. package/eryx-sandbox.conditional-host-import-0.wasm +0 -0
  3. package/eryx-sandbox.conditional-host-import-1.wasm +0 -0
  4. package/eryx-sandbox.conditional-suspending-1-i32-to-i32.wasm +0 -0
  5. package/eryx-sandbox.core.wasm +0 -0
  6. package/eryx-sandbox.core10.wasm +0 -0
  7. package/eryx-sandbox.core11.wasm +0 -0
  8. package/eryx-sandbox.core12.wasm +0 -0
  9. package/eryx-sandbox.core13.wasm +0 -0
  10. package/eryx-sandbox.core2.wasm +0 -0
  11. package/eryx-sandbox.core3.wasm +0 -0
  12. package/eryx-sandbox.core4.wasm +0 -0
  13. package/eryx-sandbox.core5.wasm +0 -0
  14. package/eryx-sandbox.core6.wasm +0 -0
  15. package/eryx-sandbox.core7.wasm +0 -0
  16. package/eryx-sandbox.core8.wasm +0 -0
  17. package/eryx-sandbox.core9.wasm +0 -0
  18. package/eryx-sandbox.d.ts +185 -0
  19. package/eryx-sandbox.js +55329 -0
  20. package/index.d.ts +331 -0
  21. package/index.js +698 -0
  22. package/instance.js +174 -0
  23. package/interfaces/eryx-net-tcp.d.ts +39 -0
  24. package/interfaces/eryx-net-tls.d.ts +33 -0
  25. package/interfaces/wasi-cli-environment.d.ts +3 -0
  26. package/interfaces/wasi-cli-exit.d.ts +3 -0
  27. package/interfaces/wasi-cli-stderr.d.ts +3 -0
  28. package/interfaces/wasi-cli-stdin.d.ts +3 -0
  29. package/interfaces/wasi-cli-stdout.d.ts +3 -0
  30. package/interfaces/wasi-cli-terminal-input.d.ts +8 -0
  31. package/interfaces/wasi-cli-terminal-output.d.ts +8 -0
  32. package/interfaces/wasi-cli-terminal-stderr.d.ts +3 -0
  33. package/interfaces/wasi-cli-terminal-stdin.d.ts +3 -0
  34. package/interfaces/wasi-cli-terminal-stdout.d.ts +3 -0
  35. package/interfaces/wasi-clocks-monotonic-clock.d.ts +8 -0
  36. package/interfaces/wasi-clocks-wall-clock.d.ts +7 -0
  37. package/interfaces/wasi-filesystem-preopens.d.ts +3 -0
  38. package/interfaces/wasi-filesystem-types.d.ts +288 -0
  39. package/interfaces/wasi-io-error.d.ts +8 -0
  40. package/interfaces/wasi-io-poll.d.ts +10 -0
  41. package/interfaces/wasi-io-streams.d.ts +51 -0
  42. package/interfaces/wasi-random-random.d.ts +2 -0
  43. package/interfaces/wasi-sockets-instance-network.d.ts +3 -0
  44. package/interfaces/wasi-sockets-ip-name-lookup.d.ts +21 -0
  45. package/interfaces/wasi-sockets-network.d.ts +92 -0
  46. package/interfaces/wasi-sockets-tcp-create-socket.d.ts +8 -0
  47. package/interfaces/wasi-sockets-tcp.d.ts +127 -0
  48. package/interfaces/wasi-sockets-udp-create-socket.d.ts +8 -0
  49. package/interfaces/wasi-sockets-udp.d.ts +93 -0
  50. package/package.json +69 -3
  51. package/python-stdlib.tar.gz +0 -0
  52. package/shims/callbacks.d.ts +45 -0
  53. package/shims/callbacks.js +469 -0
  54. package/shims/ip.js +46 -0
  55. package/shims/net-fetch.js +264 -0
  56. package/shims/net-node.js +236 -0
  57. package/shims/net.d.ts +84 -0
  58. package/shims/net.js +260 -0
  59. package/shims/preview2-node.js +35 -0
  60. package/shims/raw-json.js +111 -0
  61. package/shims/sockets.js +53 -0
  62. package/stdlib-loader.js +174 -0
  63. package/worker.js +100 -0
package/index.d.ts ADDED
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Eryx - JavaScript API
3
+ *
4
+ * A Python sandbox powered by WebAssembly, for browser and Node.js.
5
+ */
6
+
7
+ export {
8
+ setTraceHandler,
9
+ setOutputHandler,
10
+ SuspendCallback,
11
+ } from "./shims/callbacks.js";
12
+
13
+ export { DEFAULT_BLOCKED_HOSTS, DEFAULT_BLOCKED_NETWORKS } from "./shims/net.js";
14
+ export type { NetConfig } from "./shims/net.js";
15
+
16
+ import type { NetConfig } from "./shims/net.js";
17
+ import type { Callbacks, ExecuteResult } from "@eryx-org/eryx-run/shared";
18
+
19
+ /**
20
+ * The errors a run throws, shared with eryx.run's client
21
+ * (`@eryx-org/eryx-run`), so one `instanceof` handles both. Code that raises
22
+ * rejects with a `PythonExceptionError` (its traceback as `message`, the
23
+ * output before it in `stdout` and `stderr`); a run past `timeoutMs` with a
24
+ * `TimeoutError`.
25
+ */
26
+ export {
27
+ EryxError,
28
+ ExecutionError,
29
+ PythonExceptionError,
30
+ ResourceLimitError,
31
+ TimeoutError,
32
+ } from "@eryx-org/eryx-run/shared";
33
+ export type { CallbackHandler, Callbacks, ExecuteResult } from "@eryx-org/eryx-run/shared";
34
+
35
+ /**
36
+ * Set the default network policy, or disable networking again with `null`
37
+ * (the initial default). Each {@link Sandbox} copies it when it is created,
38
+ * unless given its own `netConfig`; sandboxes that already exist keep their
39
+ * policy. It also applies to the default sandbox behind the module-level
40
+ * helpers. Throws a TypeError if a network isn't a valid CIDR or
41
+ * `maxConnections` isn't a non-negative integer.
42
+ */
43
+ export function setNetConfig(config: NetConfig | null): void;
44
+
45
+ /**
46
+ * A captured Python session state, from {@link Sandbox.snapshotState}.
47
+ */
48
+ export interface StateSnapshot {
49
+ /** The serialized state, accepted by `restoreState()`. */
50
+ data: Uint8Array;
51
+ /**
52
+ * Names of the globals left out because they could not be serialized (e.g.
53
+ * open sockets, generators). They raise `NameError` after a restore.
54
+ */
55
+ skippedGlobals: string[];
56
+ }
57
+
58
+
59
+ /** The callback that suspended execution. */
60
+ export interface SuspendedCallback {
61
+ /** Name of the callback that suspended. */
62
+ name: string;
63
+ /** Canonical arguments JSON it was invoked with. */
64
+ argsJson: string;
65
+ /** The reason passed to `SuspendCallback`. */
66
+ reason: string;
67
+ }
68
+
69
+ /** Options for {@link Sandbox.executeWithJournal}. */
70
+ export interface ExecuteWithJournalOptions {
71
+ /**
72
+ * The `journal` string from a previous run's outcome, whose results should be
73
+ * replayed. Pass it back unmodified (don't round-trip it through
74
+ * `JSON.parse`/`JSON.stringify`, which would lose number precision in results).
75
+ */
76
+ journal?: string;
77
+ }
78
+
79
+ /** The outcome of {@link Sandbox.executeWithJournal}. */
80
+ export interface ReplayOutcome {
81
+ /** The execution result, or `undefined` if execution failed. */
82
+ result?: ExecuteResult;
83
+ /**
84
+ * Why execution failed — a Python exception, or the `SuspendCallback` that
85
+ * halted it — or `undefined` on success. Check `suspended` first.
86
+ */
87
+ error?: Error;
88
+ /**
89
+ * Callbacks completed during this run, serialized as JSON in the same shape as
90
+ * the Rust `CallbackJournal`
91
+ * (`{"code", "entries": [{"index", "name", "args_hash", "args_json", "result": {"Ok": value} | {"Err": message}}]}`).
92
+ * Always present, even on error. Store it as-is.
93
+ */
94
+ journal: string;
95
+ /** How many callbacks were served from the supplied journal. */
96
+ replayedCallbacks: number;
97
+ /** Set if a callback threw `SuspendCallback`. */
98
+ suspended?: SuspendedCallback;
99
+ }
100
+
101
+ /**
102
+ * Options for a {@link Sandbox}.
103
+ *
104
+ * `traceHandler` and `outputHandler` override, for this sandbox only, the
105
+ * module-wide default set by the setter of the same name. An omitted one
106
+ * follows that default, including later changes to it.
107
+ */
108
+ export interface SandboxOptions {
109
+ /**
110
+ * Functions the code can `await`, by name, as for eryx.run's client: each
111
+ * a function of the code's keyword arguments, or `{ handler, description }`
112
+ * (the description is what `list_callbacks()` shows). What it returns must
113
+ * be JSON; if it throws, the call fails in Python. A handler can throw
114
+ * {@link SuspendCallback} to halt the run.
115
+ *
116
+ * @example
117
+ * new Sandbox({ callbacks: { get_user: async ({ id }) => ({ id, name: "Ada" }) } });
118
+ */
119
+ callbacks?: Callbacks;
120
+ /** Handler for trace events from the Python runtime. */
121
+ traceHandler?:
122
+ | ((lineno: number, eventJson: string, contextJson: string) => void)
123
+ | null;
124
+ /** Handler for streaming output: stream 0 = stdout, 1 = stderr. */
125
+ outputHandler?: ((stream: number, data: string) => void) | null;
126
+ /**
127
+ * This sandbox's network policy (`null` disables networking). Omitted, the
128
+ * sandbox copies the default set with {@link setNetConfig} when it is
129
+ * created; later {@link setNetConfig} calls don't change it.
130
+ */
131
+ netConfig?: NetConfig | null;
132
+ /**
133
+ * Run the sandbox in its own Worker (a `worker_threads` Worker in Node.js)
134
+ * and reject any call that takes longer than this many milliseconds with a
135
+ * {@link TimeoutError}. The Worker is then terminated and replaced, so the
136
+ * sandbox's Python state is lost. The deadline is wall-clock time and
137
+ * includes time spent in callbacks; instantiation doesn't count.
138
+ *
139
+ * Each such sandbox also holds a Worker with its own JavaScript heap and its
140
+ * own copy of the Python stdlib tree, about 70 MB on top of the instance's
141
+ * memory.
142
+ *
143
+ * Opt-in: there is no default timeout, unlike the Rust crate and the Python
144
+ * bindings (30 seconds). Without `timeoutMs`, nothing interrupts a call that
145
+ * never finishes, such as `while True: pass`. There is no memory limit
146
+ * either way: an instance's memory grows as its code allocates, up to the
147
+ * WebAssembly 4 GiB maximum or what the JavaScript engine allows.
148
+ */
149
+ timeoutMs?: number;
150
+ }
151
+
152
+
153
+ /**
154
+ * A Python sandbox powered by WebAssembly.
155
+ *
156
+ * Each Sandbox is its own WebAssembly instance, with its own Python
157
+ * interpreter, filesystem, network connections, callback handlers and
158
+ * journal, so code run in one can't see or change another's state. Python
159
+ * state (variables, imports, etc.) persists across execute() calls on the same
160
+ * Sandbox; connections are closed when each call finishes.
161
+ *
162
+ * The compiled code is shared, but every instance has its own linear memory
163
+ * (the pre-initialized interpreter image plus whatever the code allocates), so
164
+ * each live Sandbox costs tens of megabytes (about 20 MB idle on Node.js 24).
165
+ * One created with `timeoutMs` also runs in its own Worker, which adds about
166
+ * 70 MB. Call close() (or use `using`) when you're done with a sandbox: it
167
+ * stops the Worker at once and closes the sandbox's connections. A sandbox
168
+ * that is only dropped is freed whenever the garbage collector gets to it.
169
+ *
170
+ * @example
171
+ * const sandbox = new Sandbox();
172
+ *
173
+ * // Variables persist across calls
174
+ * await sandbox.execute('x = 42');
175
+ * const result = await sandbox.execute('print(x)');
176
+ * console.log(result.stdout); // "42"
177
+ *
178
+ * // Reset state
179
+ * await sandbox.clearState();
180
+ */
181
+ export class Sandbox {
182
+ /**
183
+ * Create a sandbox. Instantiation starts right away and finishes in the
184
+ * background; the first call waits for it, and rejects if it failed.
185
+ */
186
+ constructor(options?: SandboxOptions);
187
+
188
+ /**
189
+ * This sandbox's own file tree (a copy of the module's `_fileTree`). Changes
190
+ * to it are visible to the sandbox's next filesystem access. Undefined for a
191
+ * sandbox with `timeoutMs`, whose files live in its Worker.
192
+ *
193
+ * @internal For the demo app and advanced integrations.
194
+ */
195
+ readonly _fileTree: { dir: Record<string, unknown> } | undefined;
196
+
197
+ /**
198
+ * Execute Python code in the sandbox.
199
+ *
200
+ * The code runs in the sandboxed Python interpreter. Output to stdout
201
+ * and stderr is captured and returned. Variables and imports persist
202
+ * across calls on the same Sandbox instance.
203
+ *
204
+ * Calls on one Sandbox run one at a time, in call order; an execute() made
205
+ * while another call on it is running waits for it to finish. Don't await
206
+ * execute() on the same Sandbox from inside its callback handler: it would
207
+ * wait for the execution that invoked the handler, which never finishes.
208
+ *
209
+ * @param code - Python source code to execute
210
+ * @returns Captured stdout and stderr
211
+ * @throws If the Python code raises an unhandled exception
212
+ */
213
+ execute(code: string): Promise<ExecuteResult>;
214
+
215
+ /**
216
+ * Execute Python code, journaling callback results so a later run can replay
217
+ * them instead of re-invoking the callbacks.
218
+ *
219
+ * Callbacks matching an entry of `options.journal` (by name and canonical
220
+ * arguments, FIFO) return the recorded result without calling the handler;
221
+ * the first miss switches the rest of the run to live calls. A handler can
222
+ * throw {@link SuspendCallback} to halt execution.
223
+ *
224
+ * Never rejects for script failures: check `suspended`, then `error`. Like
225
+ * every call on this Sandbox, it waits for earlier calls to finish first.
226
+ */
227
+ executeWithJournal(
228
+ code: string,
229
+ options?: ExecuteWithJournalOptions,
230
+ ): Promise<ReplayOutcome>;
231
+
232
+ /**
233
+ * Set the name of the variable captured as the structured result.
234
+ *
235
+ * After each `execute()`, the variable with this name is read from the
236
+ * script's namespace, JSON-serialized, and returned as
237
+ * `ExecuteResult.result`. Defaults to `"result"`. Takes effect for calls made
238
+ * after it.
239
+ *
240
+ * @param name - The variable name to capture
241
+ */
242
+ setResultVariable(name: string): Promise<void>;
243
+
244
+ /**
245
+ * Replace this sandbox's network policy (`null` disables networking). It
246
+ * applies to connections opened after the call, and doesn't change the
247
+ * default set with the module-level {@link setNetConfig} or other
248
+ * sandboxes. Throws a TypeError if a network isn't a valid CIDR or
249
+ * `maxConnections` isn't a non-negative integer.
250
+ */
251
+ setNetConfig(config: NetConfig | null): void;
252
+
253
+ /**
254
+ * Stop using this sandbox. Its open connections are closed, and every later
255
+ * call rejects. For a `timeoutMs` sandbox the Worker is terminated at once,
256
+ * so a call in flight rejects too; without `timeoutMs` a call in flight runs
257
+ * to the end. Calling it again does nothing. `using` and `await using` call
258
+ * it at the end of their scope.
259
+ */
260
+ close(): void;
261
+ [Symbol.dispose](): void;
262
+ [Symbol.asyncDispose](): void;
263
+
264
+ /**
265
+ * Capture a snapshot of the current Python session state.
266
+ *
267
+ * Returns serialized state (via pickle) that can be restored later
268
+ * with restoreState(). This captures all user-defined variables that can
269
+ * be serialized, and names the others in `skippedGlobals`.
270
+ *
271
+ * @returns Serialized Python state
272
+ * @throws If serialization fails
273
+ */
274
+ snapshotState(): Promise<StateSnapshot>;
275
+
276
+ /**
277
+ * Restore Python session state from a previously captured snapshot.
278
+ *
279
+ * @param snapshot - A snapshot from snapshotState(), or its `data`
280
+ * @throws If deserialization fails
281
+ */
282
+ restoreState(snapshot: StateSnapshot | Uint8Array): Promise<void>;
283
+
284
+ /**
285
+ * Clear all persistent state from the session.
286
+ */
287
+ clearState(): Promise<void>;
288
+ }
289
+
290
+ /**
291
+ * Execute Python code in the default sandbox.
292
+ *
293
+ * This is a convenience function backed by one module-wide Sandbox, created on
294
+ * first use, so state from previous calls persists. It shares nothing with
295
+ * Sandbox instances you create yourself; use one of those for isolated
296
+ * execution.
297
+ *
298
+ * @param code - Python source code to execute
299
+ * @returns Captured stdout and stderr
300
+ * @throws If the Python code raises an unhandled exception
301
+ */
302
+ export function execute(code: string): Promise<ExecuteResult>;
303
+
304
+ /**
305
+ * Execute Python code in the default sandbox, journaling callback results.
306
+ * See {@link Sandbox.executeWithJournal}.
307
+ */
308
+ export function executeWithJournal(
309
+ code: string,
310
+ options?: ExecuteWithJournalOptions,
311
+ ): Promise<ReplayOutcome>;
312
+
313
+ /**
314
+ * Set the name of the variable captured as the structured result in the
315
+ * default sandbox (the one behind {@link execute}). See
316
+ * {@link Sandbox.setResultVariable}.
317
+ *
318
+ * @param name - The variable name to capture
319
+ */
320
+ export function setResultVariable(name: string): Promise<void>;
321
+
322
+ /**
323
+ * The template file tree each new Sandbox copies: `python-stdlib` and an empty
324
+ * `site-packages` directory. Files added here are visible to sandboxes created
325
+ * afterwards; change {@link Sandbox._fileTree} to reach one that exists.
326
+ *
327
+ * @internal This is primarily for use by the demo app and advanced integrations.
328
+ */
329
+ export const _fileTree: {
330
+ dir: Record<string, unknown>;
331
+ };