@sandboxxjs/core 0.2.0 → 0.3.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.
package/dist/index.d.ts CHANGED
@@ -8,6 +8,8 @@ interface StateConfig {
8
8
  initializeLog?: StateLog;
9
9
  /** Enable state recording */
10
10
  enableRecord?: boolean;
11
+ /** Store type (default: resourcex, test: memory) */
12
+ store?: "resourcex" | "memory";
11
13
  }
12
14
  interface SandboxConfig {
13
15
  /** Isolator type */
@@ -44,9 +46,11 @@ interface PythonConfig {
44
46
  useVenv?: boolean;
45
47
  }
46
48
  /**
47
- * Base Sandbox interface - 4 core APIs
49
+ * Base Sandbox interface - 4 core APIs + ID
48
50
  */
49
51
  interface Sandbox {
52
+ /** Unique sandbox ID */
53
+ readonly id: string;
50
54
  /** Execute shell command */
51
55
  shell(command: string): Promise<ShellResult>;
52
56
  /** Upload file to sandbox */
@@ -125,6 +129,7 @@ declare abstract class Isolator {
125
129
  abstract destroy(): Promise<void>;
126
130
  }
127
131
  declare class BaseSandbox implements Sandbox {
132
+ readonly id: string;
128
133
  protected isolator: Isolator;
129
134
  protected config: SandboxConfig;
130
135
  constructor(config: SandboxConfig);
package/dist/index.js CHANGED
@@ -474,6 +474,36 @@ var require_cross_spawn = __commonJS((exports, module) => {
474
474
  module.exports._enoent = enoent;
475
475
  });
476
476
 
477
+ // ../../node_modules/.bun/nanoid@5.1.6/node_modules/nanoid/index.js
478
+ import { webcrypto as crypto } from "node:crypto";
479
+
480
+ // ../../node_modules/.bun/nanoid@5.1.6/node_modules/nanoid/url-alphabet/index.js
481
+ var urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
482
+
483
+ // ../../node_modules/.bun/nanoid@5.1.6/node_modules/nanoid/index.js
484
+ var POOL_SIZE_MULTIPLIER = 128;
485
+ var pool;
486
+ var poolOffset;
487
+ function fillPool(bytes) {
488
+ if (!pool || pool.length < bytes) {
489
+ pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER);
490
+ crypto.getRandomValues(pool);
491
+ poolOffset = 0;
492
+ } else if (poolOffset + bytes > pool.length) {
493
+ crypto.getRandomValues(pool);
494
+ poolOffset = 0;
495
+ }
496
+ poolOffset += bytes;
497
+ }
498
+ function nanoid(size = 21) {
499
+ fillPool(size |= 0);
500
+ let id = "";
501
+ for (let i = poolOffset - size;i < poolOffset; i++) {
502
+ id += urlAlphabet[pool[i] & 63];
503
+ }
504
+ return id;
505
+ }
506
+
477
507
  // ../../node_modules/.bun/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js
478
508
  function isPlainObject(value) {
479
509
  if (typeof value !== "object" || value === null) {
@@ -7397,9 +7427,11 @@ class CloudflareContainerIsolator extends Isolator {
7397
7427
 
7398
7428
  // src/Sandbox.ts
7399
7429
  class BaseSandbox {
7430
+ id;
7400
7431
  isolator;
7401
7432
  config;
7402
7433
  constructor(config) {
7434
+ this.id = `sandbox-${nanoid()}`;
7403
7435
  this.config = config;
7404
7436
  this.isolator = this.createIsolator(config.isolator);
7405
7437
  }
@@ -7432,9 +7464,15 @@ class BaseSandbox {
7432
7464
  }
7433
7465
  }
7434
7466
  // ../state/dist/index.js
7467
+ import { createRequire as createRequire3 } from "node:module";
7435
7468
  import { readFile as readFile2, writeFile as writeFile2, readdir, mkdir as mkdir2, rm as rm2, access, stat as fsStat } from "node:fs/promises";
7436
7469
  import { resolve, dirname as dirname2 } from "node:path";
7470
+ import { homedir } from "node:os";
7471
+ import { join as join2 } from "node:path";
7472
+ import { readFile as readFile22, writeFile as writeFile22, readdir as readdir2, access as access2, unlink, mkdir as mkdir22, stat, rm as rm22 } from "node:fs/promises";
7473
+ import { join as join22 } from "node:path";
7437
7474
  import { createHash } from "crypto";
7475
+ var __require2 = /* @__PURE__ */ createRequire3(import.meta.url);
7438
7476
 
7439
7477
  class StateError extends Error {
7440
7478
  constructor(message) {
@@ -7724,57 +7762,6 @@ async function replayStateLog(log, target) {
7724
7762
  }
7725
7763
  }
7726
7764
  }
7727
- function createRecordingProxy(target, namespace, log) {
7728
- return new Proxy(target, {
7729
- get(obj, prop) {
7730
- const value = obj[prop];
7731
- if (typeof value !== "function") {
7732
- return value;
7733
- }
7734
- const method = prop;
7735
- const op = findOp(namespace, method);
7736
- if (!op) {
7737
- return value.bind(obj);
7738
- }
7739
- return (...args) => {
7740
- const result = value.apply(obj, args);
7741
- const record = () => {
7742
- const entryArgs = argsToEntry(op, args);
7743
- log.recordEntry(op, entryArgs);
7744
- };
7745
- if (result instanceof Promise) {
7746
- return result.then((res) => {
7747
- record();
7748
- return res;
7749
- });
7750
- } else {
7751
- record();
7752
- return result;
7753
- }
7754
- };
7755
- }
7756
- });
7757
- }
7758
- function createState(options) {
7759
- const { sandbox, env, enableRecord } = options;
7760
- const baseFS = new StateFS(sandbox);
7761
- const baseEnv = new StateEnv(env);
7762
- const baseStorage = new StateStorage;
7763
- if (!enableRecord) {
7764
- return {
7765
- fs: baseFS,
7766
- env: baseEnv,
7767
- storage: baseStorage
7768
- };
7769
- }
7770
- const stateLog = buildStateLog();
7771
- return {
7772
- fs: createRecordingProxy(baseFS, "fs", stateLog),
7773
- env: createRecordingProxy(baseEnv, "env", stateLog),
7774
- storage: createRecordingProxy(baseStorage, "storage", stateLog),
7775
- stateLog
7776
- };
7777
- }
7778
7765
 
7779
7766
  class ResourceXError extends Error {
7780
7767
  constructor(message, options) {
@@ -7782,6 +7769,16 @@ class ResourceXError extends Error {
7782
7769
  this.name = "ResourceXError";
7783
7770
  }
7784
7771
  }
7772
+
7773
+ class ParseError extends ResourceXError {
7774
+ url;
7775
+ constructor(message, url) {
7776
+ super(message);
7777
+ this.url = url;
7778
+ this.name = "ParseError";
7779
+ }
7780
+ }
7781
+
7785
7782
  class TransportError extends ResourceXError {
7786
7783
  transport;
7787
7784
  constructor(message, transport, options) {
@@ -7799,6 +7796,35 @@ class SemanticError extends ResourceXError {
7799
7796
  this.name = "SemanticError";
7800
7797
  }
7801
7798
  }
7799
+ function parseARP(url) {
7800
+ if (!url.startsWith("arp:")) {
7801
+ throw new ParseError(`Invalid ARP URL: must start with "arp:"`, url);
7802
+ }
7803
+ const content = url.substring(4);
7804
+ const separatorIndex = content.indexOf("://");
7805
+ if (separatorIndex === -1) {
7806
+ throw new ParseError(`Invalid ARP URL: missing "://"`, url);
7807
+ }
7808
+ const typePart = content.substring(0, separatorIndex);
7809
+ const location = content.substring(separatorIndex + 3);
7810
+ const colonIndex = typePart.indexOf(":");
7811
+ if (colonIndex === -1) {
7812
+ throw new ParseError(`Invalid ARP URL: must have exactly 2 types (semantic:transport)`, url);
7813
+ }
7814
+ const semantic = typePart.substring(0, colonIndex);
7815
+ const transport = typePart.substring(colonIndex + 1);
7816
+ if (!semantic) {
7817
+ throw new ParseError(`Invalid ARP URL: semantic type cannot be empty`, url);
7818
+ }
7819
+ if (!transport) {
7820
+ throw new ParseError(`Invalid ARP URL: transport type cannot be empty`, url);
7821
+ }
7822
+ if (!location) {
7823
+ throw new ParseError(`Invalid ARP URL: location cannot be empty`, url);
7824
+ }
7825
+ return { semantic, transport, location };
7826
+ }
7827
+
7802
7828
  class HttpTransportHandler {
7803
7829
  name;
7804
7830
  protocol;
@@ -7930,11 +7956,99 @@ class FileTransportHandler {
7930
7956
  }
7931
7957
  }
7932
7958
  var fileHandler = new FileTransportHandler;
7959
+ function deepracticeHandler(config = {}) {
7960
+ const parentDir = config.parentDir || homedir();
7961
+ const baseDir = join2(parentDir, ".deepractice");
7962
+ function resolvePath(location) {
7963
+ return join2(baseDir, location);
7964
+ }
7965
+ return {
7966
+ name: "deepractice",
7967
+ capabilities: {
7968
+ canRead: true,
7969
+ canWrite: true,
7970
+ canList: true,
7971
+ canDelete: true,
7972
+ canStat: true
7973
+ },
7974
+ async read(location) {
7975
+ const fullPath = resolvePath(location);
7976
+ try {
7977
+ return await readFile22(fullPath);
7978
+ } catch (error) {
7979
+ throw new TransportError(`Failed to read from deepractice: ${error.message}`, "deepractice", { cause: error });
7980
+ }
7981
+ },
7982
+ async write(location, content) {
7983
+ const fullPath = resolvePath(location);
7984
+ try {
7985
+ await mkdir22(join2(fullPath, ".."), { recursive: true });
7986
+ await writeFile22(fullPath, content);
7987
+ } catch (error) {
7988
+ throw new TransportError(`Failed to write to deepractice: ${error.message}`, "deepractice", { cause: error });
7989
+ }
7990
+ },
7991
+ async list(location) {
7992
+ const fullPath = resolvePath(location);
7993
+ try {
7994
+ return await readdir2(fullPath);
7995
+ } catch (error) {
7996
+ throw new TransportError(`Failed to list deepractice directory: ${error.message}`, "deepractice", { cause: error });
7997
+ }
7998
+ },
7999
+ async exists(location) {
8000
+ const fullPath = resolvePath(location);
8001
+ try {
8002
+ await access2(fullPath);
8003
+ return true;
8004
+ } catch {
8005
+ return false;
8006
+ }
8007
+ },
8008
+ async stat(location) {
8009
+ const fullPath = resolvePath(location);
8010
+ try {
8011
+ const stats = await stat(fullPath);
8012
+ return {
8013
+ size: stats.size,
8014
+ isDirectory: stats.isDirectory(),
8015
+ modifiedAt: stats.mtime
8016
+ };
8017
+ } catch (error) {
8018
+ throw new TransportError(`Failed to stat deepractice resource: ${error.message}`, "deepractice", { cause: error });
8019
+ }
8020
+ },
8021
+ async delete(location) {
8022
+ const fullPath = resolvePath(location);
8023
+ try {
8024
+ const stats = await stat(fullPath);
8025
+ if (stats.isDirectory()) {
8026
+ await rm22(fullPath, { recursive: true, force: true });
8027
+ } else {
8028
+ await unlink(fullPath);
8029
+ }
8030
+ } catch (error) {
8031
+ throw new TransportError(`Failed to delete from deepractice: ${error.message}`, "deepractice", { cause: error });
8032
+ }
8033
+ }
8034
+ };
8035
+ }
7933
8036
  var handlers = new Map([
7934
8037
  ["https", httpsHandler],
7935
8038
  ["http", httpHandler],
7936
8039
  ["file", fileHandler]
7937
8040
  ]);
8041
+ function getTransportHandler(name) {
8042
+ const handler = handlers.get(name);
8043
+ if (!handler) {
8044
+ throw new TransportError(`Unsupported transport type: ${name}`, name);
8045
+ }
8046
+ return handler;
8047
+ }
8048
+ function registerTransportHandler(handler) {
8049
+ handlers.set(handler.name, handler);
8050
+ }
8051
+
7938
8052
  class TextSemanticHandler {
7939
8053
  name = "text";
7940
8054
  async resolve(transport, location, context) {
@@ -8046,20 +8160,218 @@ var handlers2 = new Map([
8046
8160
  ["text", textHandler],
8047
8161
  ["binary", binaryHandler]
8048
8162
  ]);
8163
+ function getSemanticHandler(name) {
8164
+ const handler = handlers2.get(name);
8165
+ if (!handler) {
8166
+ throw new SemanticError(`Unsupported semantic type: ${name}`, name);
8167
+ }
8168
+ return handler;
8169
+ }
8170
+ function registerSemanticHandler(handler) {
8171
+ handlers2.set(handler.name, handler);
8172
+ }
8173
+ function createContext(url, semantic, transport, location) {
8174
+ return {
8175
+ url,
8176
+ semantic,
8177
+ transport,
8178
+ location,
8179
+ timestamp: new Date
8180
+ };
8181
+ }
8182
+ async function resolve2(url) {
8183
+ const parsed = parseARP(url);
8184
+ const transport = getTransportHandler(parsed.transport);
8185
+ const semantic = getSemanticHandler(parsed.semantic);
8186
+ const context = createContext(url, parsed.semantic, parsed.transport, parsed.location);
8187
+ return semantic.resolve(transport, parsed.location, context);
8188
+ }
8189
+ async function deposit(url, data) {
8190
+ const parsed = parseARP(url);
8191
+ const transport = getTransportHandler(parsed.transport);
8192
+ const semantic = getSemanticHandler(parsed.semantic);
8193
+ if (!semantic.deposit) {
8194
+ throw new SemanticError(`Semantic "${semantic.name}" does not support deposit operation`, parsed.semantic);
8195
+ }
8196
+ const context = createContext(url, parsed.semantic, parsed.transport, parsed.location);
8197
+ await semantic.deposit(transport, parsed.location, data, context);
8198
+ }
8199
+ async function resourceExists(url) {
8200
+ const parsed = parseARP(url);
8201
+ const transport = getTransportHandler(parsed.transport);
8202
+ const semantic = getSemanticHandler(parsed.semantic);
8203
+ const context = createContext(url, parsed.semantic, parsed.transport, parsed.location);
8204
+ if (semantic.exists) {
8205
+ return semantic.exists(transport, parsed.location, context);
8206
+ }
8207
+ if (transport.exists) {
8208
+ return transport.exists(parsed.location);
8209
+ }
8210
+ try {
8211
+ await transport.read(parsed.location);
8212
+ return true;
8213
+ } catch {
8214
+ return false;
8215
+ }
8216
+ }
8217
+ async function resourceDelete(url) {
8218
+ const parsed = parseARP(url);
8219
+ const transport = getTransportHandler(parsed.transport);
8220
+ const semantic = getSemanticHandler(parsed.semantic);
8221
+ const context = createContext(url, parsed.semantic, parsed.transport, parsed.location);
8222
+ if (semantic.delete) {
8223
+ return semantic.delete(transport, parsed.location, context);
8224
+ }
8225
+ if (!transport.delete) {
8226
+ throw new SemanticError(`Neither semantic "${semantic.name}" nor transport "${transport.name}" supports delete operation`, parsed.semantic);
8227
+ }
8228
+ await transport.delete(parsed.location);
8229
+ }
8230
+ function createResourceRegistry() {
8231
+ const registry = new Map;
8232
+ return {
8233
+ register(definition) {
8234
+ if (!/^[a-z][a-z0-9-]*$/.test(definition.name)) {
8235
+ throw new ParseError(`Invalid resource name: "${definition.name}". Must start with lowercase letter and contain only lowercase letters, numbers, and hyphens.`, definition.name);
8236
+ }
8237
+ getSemanticHandler(definition.semantic);
8238
+ getTransportHandler(definition.transport);
8239
+ registry.set(definition.name, definition);
8240
+ },
8241
+ get(name) {
8242
+ return registry.get(name);
8243
+ },
8244
+ has(name) {
8245
+ return registry.has(name);
8246
+ },
8247
+ clear() {
8248
+ registry.clear();
8249
+ }
8250
+ };
8251
+ }
8252
+
8253
+ class ResourceX {
8254
+ timeout;
8255
+ alias;
8256
+ resourceRegistry;
8257
+ constructor(config = {}) {
8258
+ this.timeout = config.timeout;
8259
+ this.alias = config.alias || "@";
8260
+ this.resourceRegistry = createResourceRegistry();
8261
+ if (config.transports) {
8262
+ for (const handler of config.transports) {
8263
+ registerTransportHandler(handler);
8264
+ }
8265
+ }
8266
+ if (config.semantics) {
8267
+ for (const handler of config.semantics) {
8268
+ registerSemanticHandler(handler);
8269
+ }
8270
+ }
8271
+ if (config.resources) {
8272
+ for (const resource of config.resources) {
8273
+ this.resourceRegistry.register(resource);
8274
+ }
8275
+ }
8276
+ }
8277
+ parseURL(url) {
8278
+ let content;
8279
+ if (url.startsWith("arp:")) {
8280
+ content = url.substring(4);
8281
+ } else if (url.startsWith(this.alias)) {
8282
+ content = url.substring(this.alias.length);
8283
+ } else {
8284
+ throw new ParseError(`Invalid URL prefix: must start with "arp:" or "${this.alias}"`, url);
8285
+ }
8286
+ const separatorIndex = content.indexOf("://");
8287
+ if (separatorIndex === -1) {
8288
+ throw new ParseError(`Invalid URL format: missing "://"`, url);
8289
+ }
8290
+ const beforeSeparator = content.substring(0, separatorIndex);
8291
+ const location = content.substring(separatorIndex + 3);
8292
+ const colonCount = (beforeSeparator.match(/:/g) || []).length;
8293
+ if (colonCount === 1) {
8294
+ const parts = beforeSeparator.split(":");
8295
+ if (parts.length !== 2) {
8296
+ throw new ParseError(`Invalid ARP URL format`, url);
8297
+ }
8298
+ const [semantic, transport] = parts;
8299
+ if (!semantic || !transport || !location) {
8300
+ throw new ParseError(`Invalid ARP URL: semantic, transport, and location are required`, url);
8301
+ }
8302
+ const arpUrl = `arp:${semantic}:${transport}://${location}`;
8303
+ return {
8304
+ arpUrl,
8305
+ parsed: { semantic, transport, location }
8306
+ };
8307
+ }
8308
+ if (colonCount === 0) {
8309
+ const name = beforeSeparator;
8310
+ if (!name || !location) {
8311
+ throw new ParseError(`Invalid Resource URL: name and location are required`, url);
8312
+ }
8313
+ const definition = this.resourceRegistry.get(name);
8314
+ if (!definition) {
8315
+ throw new ParseError(`Unknown resource: "${name}"`, url);
8316
+ }
8317
+ const fullLocation = definition.basePath ? join22(definition.basePath, location) : location;
8318
+ const arpUrl = `arp:${definition.semantic}:${definition.transport}://${fullLocation}`;
8319
+ return {
8320
+ arpUrl,
8321
+ parsed: {
8322
+ semantic: definition.semantic,
8323
+ transport: definition.transport,
8324
+ location: fullLocation
8325
+ }
8326
+ };
8327
+ }
8328
+ throw new ParseError(`Invalid URL format: unexpected colon count in "${beforeSeparator}"`, url);
8329
+ }
8330
+ parse(url) {
8331
+ return this.parseURL(url).parsed;
8332
+ }
8333
+ async resolve(url) {
8334
+ const { arpUrl } = this.parseURL(url);
8335
+ return resolve2(arpUrl);
8336
+ }
8337
+ async deposit(url, data) {
8338
+ const { arpUrl } = this.parseURL(url);
8339
+ return deposit(arpUrl, data);
8340
+ }
8341
+ async exists(url) {
8342
+ const { arpUrl } = this.parseURL(url);
8343
+ return resourceExists(arpUrl);
8344
+ }
8345
+ async delete(url) {
8346
+ const { arpUrl } = this.parseURL(url);
8347
+ return resourceDelete(arpUrl);
8348
+ }
8349
+ }
8350
+ function createResourceX(config) {
8351
+ return new ResourceX(config);
8352
+ }
8353
+
8049
8354
  class MemoryStateStore {
8050
8355
  logs = new Map;
8051
8356
  blobs = new Map;
8357
+ entries = new Map;
8052
8358
  async saveLog(key, data) {
8053
8359
  this.logs.set(key, data);
8054
8360
  }
8055
8361
  async loadLog(key) {
8362
+ const entries = this.entries.get(key);
8363
+ if (entries && entries.length > 0) {
8364
+ return JSON.stringify(entries);
8365
+ }
8056
8366
  return this.logs.get(key) ?? null;
8057
8367
  }
8058
8368
  async deleteLog(key) {
8059
8369
  this.logs.delete(key);
8370
+ this.entries.delete(key);
8060
8371
  }
8061
8372
  async listLogs() {
8062
- return [...this.logs.keys()];
8373
+ const keys = new Set([...this.logs.keys(), ...this.entries.keys()]);
8374
+ return [...keys];
8063
8375
  }
8064
8376
  async saveBlob(ref, data) {
8065
8377
  this.blobs.set(ref, data);
@@ -8070,6 +8382,172 @@ class MemoryStateStore {
8070
8382
  async deleteBlob(ref) {
8071
8383
  this.blobs.delete(ref);
8072
8384
  }
8385
+ async appendEntry(sandboxId, entry) {
8386
+ const entries = this.entries.get(sandboxId) ?? [];
8387
+ entries.push(entry);
8388
+ this.entries.set(sandboxId, entries);
8389
+ }
8390
+ }
8391
+
8392
+ class ResourceXStateStore {
8393
+ rx;
8394
+ basePath;
8395
+ constructor() {
8396
+ this.rx = createResourceX({
8397
+ transports: [deepracticeHandler()]
8398
+ });
8399
+ const os = __require2("os");
8400
+ const path7 = __require2("path");
8401
+ this.basePath = path7.join(os.homedir(), ".deepractice/sandbox");
8402
+ }
8403
+ logUrl(key) {
8404
+ return `@text:deepractice://sandbox/state-logs/${key}.json`;
8405
+ }
8406
+ logPath(sandboxId) {
8407
+ const path7 = __require2("path");
8408
+ return path7.join(this.basePath, "state-logs", `${sandboxId}.jsonl`);
8409
+ }
8410
+ blobUrl(ref) {
8411
+ return `@binary:deepractice://sandbox/blobs/${ref}`;
8412
+ }
8413
+ async saveLog(key, data) {
8414
+ await this.rx.deposit(this.logUrl(key), data);
8415
+ }
8416
+ async loadLog(key) {
8417
+ const fs2 = __require2("fs/promises");
8418
+ const jsonlPath = this.logPath(key);
8419
+ try {
8420
+ const content = await fs2.readFile(jsonlPath, "utf-8");
8421
+ const entries = content.trim().split(`
8422
+ `).filter((line) => line).map((line) => JSON.parse(line));
8423
+ return JSON.stringify(entries);
8424
+ } catch {
8425
+ try {
8426
+ const exists = await this.rx.exists(this.logUrl(key));
8427
+ if (!exists)
8428
+ return null;
8429
+ const resource = await this.rx.resolve(this.logUrl(key));
8430
+ return resource.content;
8431
+ } catch {
8432
+ return null;
8433
+ }
8434
+ }
8435
+ }
8436
+ async deleteLog(key) {
8437
+ const fs2 = __require2("fs/promises");
8438
+ try {
8439
+ await fs2.unlink(this.logPath(key));
8440
+ } catch {}
8441
+ try {
8442
+ const exists = await this.rx.exists(this.logUrl(key));
8443
+ if (exists) {
8444
+ await this.rx.delete(this.logUrl(key));
8445
+ }
8446
+ } catch {}
8447
+ }
8448
+ async listLogs() {
8449
+ return [];
8450
+ }
8451
+ async appendEntry(sandboxId, entry) {
8452
+ const fs2 = __require2("fs/promises");
8453
+ const path7 = __require2("path");
8454
+ const filePath = this.logPath(sandboxId);
8455
+ await fs2.mkdir(path7.dirname(filePath), { recursive: true });
8456
+ const line = JSON.stringify(entry) + `
8457
+ `;
8458
+ await fs2.appendFile(filePath, line, "utf-8");
8459
+ }
8460
+ async saveBlob(ref, data) {
8461
+ await this.rx.deposit(this.blobUrl(ref), data);
8462
+ }
8463
+ async loadBlob(ref) {
8464
+ try {
8465
+ const exists = await this.rx.exists(this.blobUrl(ref));
8466
+ if (!exists)
8467
+ return null;
8468
+ const resource = await this.rx.resolve(this.blobUrl(ref));
8469
+ return resource.content;
8470
+ } catch {
8471
+ return null;
8472
+ }
8473
+ }
8474
+ async deleteBlob(ref) {
8475
+ try {
8476
+ const exists = await this.rx.exists(this.blobUrl(ref));
8477
+ if (exists) {
8478
+ await this.rx.delete(this.blobUrl(ref));
8479
+ }
8480
+ } catch {}
8481
+ }
8482
+ }
8483
+ function createStateStore(options) {
8484
+ if (options.type === "memory") {
8485
+ return new MemoryStateStore;
8486
+ }
8487
+ if (options.type === "resourcex") {
8488
+ return new ResourceXStateStore;
8489
+ }
8490
+ throw new Error(`StateStore type "${options.type}" not implemented`);
8491
+ }
8492
+ function createRecordingProxy(target, namespace, log, onRecord) {
8493
+ return new Proxy(target, {
8494
+ get(obj, prop) {
8495
+ const value = obj[prop];
8496
+ if (typeof value !== "function") {
8497
+ return value;
8498
+ }
8499
+ const method = prop;
8500
+ const op = findOp(namespace, method);
8501
+ if (!op) {
8502
+ return value.bind(obj);
8503
+ }
8504
+ return (...args) => {
8505
+ const result = value.apply(obj, args);
8506
+ const record = () => {
8507
+ const entryArgs = argsToEntry(op, args);
8508
+ log.recordEntry(op, entryArgs);
8509
+ if (onRecord) {
8510
+ onRecord({ op, args: entryArgs });
8511
+ }
8512
+ };
8513
+ if (result instanceof Promise) {
8514
+ return result.then((res) => {
8515
+ record();
8516
+ return res;
8517
+ });
8518
+ } else {
8519
+ record();
8520
+ return result;
8521
+ }
8522
+ };
8523
+ }
8524
+ });
8525
+ }
8526
+ function createState(options) {
8527
+ const { sandbox, env, enableRecord, store = "resourcex", sandboxId } = options;
8528
+ const baseFS = new StateFS(sandbox);
8529
+ const baseEnv = new StateEnv(env);
8530
+ const baseStorage = new StateStorage;
8531
+ if (!enableRecord) {
8532
+ return {
8533
+ fs: baseFS,
8534
+ env: baseEnv,
8535
+ storage: baseStorage
8536
+ };
8537
+ }
8538
+ const stateLog = buildStateLog();
8539
+ const stateStore = createStateStore({ type: store });
8540
+ const onRecord = (entry) => {
8541
+ stateStore.appendEntry(sandboxId, entry).catch((err) => {
8542
+ console.error(`[SandboX] Failed to persist entry:`, err);
8543
+ });
8544
+ };
8545
+ return {
8546
+ fs: createRecordingProxy(baseFS, "fs", stateLog, onRecord),
8547
+ env: createRecordingProxy(baseEnv, "env", stateLog, onRecord),
8548
+ storage: createRecordingProxy(baseStorage, "storage", stateLog, onRecord),
8549
+ stateLog
8550
+ };
8073
8551
  }
8074
8552
  function generateRef(data) {
8075
8553
  const hash = createHash("sha256").update(data).digest("hex");
@@ -8117,7 +8595,9 @@ function withState(Base) {
8117
8595
  const state = createState({
8118
8596
  sandbox: this,
8119
8597
  env: stateConfig?.env,
8120
- enableRecord: stateConfig?.enableRecord
8598
+ enableRecord: stateConfig?.enableRecord,
8599
+ store: stateConfig?.store,
8600
+ sandboxId: this.id
8121
8601
  });
8122
8602
  this.fs = state.fs;
8123
8603
  this.env = state.env;
@@ -8207,4 +8687,4 @@ export {
8207
8687
  BaseSandbox
8208
8688
  };
8209
8689
 
8210
- //# debugId=7693E135F99AC4E664756E2164756E21
8690
+ //# debugId=09AEF7B255D0477C64756E2164756E21