@ai-ecoverse/slicc-kernel 1.14.0 → 1.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.
@@ -177,6 +177,37 @@ function openMeta(name2, factory) {
177
177
  });
178
178
  }
179
179
 
180
+ // src/mount/media.ts
181
+ var MEDIA_STORE = "handles";
182
+ function memoryMedia() {
183
+ const handles = /* @__PURE__ */ new Map();
184
+ return {
185
+ get: async (id) => handles.get(id),
186
+ put: async (id, handle) => void handles.set(id, handle)
187
+ };
188
+ }
189
+ function settle2(request2, value) {
190
+ return new Promise((resolve, reject) => {
191
+ request2.onsuccess = () => resolve(value());
192
+ request2.onerror = () => reject(request2.error);
193
+ });
194
+ }
195
+ function openMedia(name2, factory) {
196
+ const request2 = factory.open(name2, 1);
197
+ request2.onupgradeneeded = () => request2.result.createObjectStore(MEDIA_STORE);
198
+ return settle2(request2, () => {
199
+ const db = request2.result;
200
+ const store = (mode) => db.transaction(MEDIA_STORE, mode).objectStore(MEDIA_STORE);
201
+ return {
202
+ get: (id) => {
203
+ const got = store("readonly").get(id);
204
+ return settle2(got, () => got.result);
205
+ },
206
+ put: (id, handle) => settle2(store("readwrite").put(handle, id), () => void 0)
207
+ };
208
+ });
209
+ }
210
+
180
211
  // src/pin.ts
181
212
  function pinScript(url) {
182
213
  try {
@@ -643,7 +674,8 @@ var ERRNO_BY_DOM_ERROR = {
643
674
  TypeMismatchError: "ENOTDIR",
644
675
  InvalidModificationError: "ENOTEMPTY",
645
676
  NoModificationAllowedError: "EBUSY",
646
- TypeError: "EINVAL"
677
+ TypeError: "EINVAL",
678
+ NotAllowedError: "EACCES"
647
679
  };
648
680
  function translate(err, path) {
649
681
  if (typeof err?.code === "string") return err;
@@ -3661,7 +3693,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
3661
3693
  Atomics.notify(header2, SAB_I_PUB);
3662
3694
  if (offset + chunk2 >= entry.payload.byteLength) drop(id);
3663
3695
  }
3664
- function settle2(id, result) {
3696
+ function settle3(id, result) {
3665
3697
  if (disposed) return;
3666
3698
  const { status, payload } = encodeSabResult(result);
3667
3699
  drop(id);
@@ -3680,7 +3712,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
3680
3712
  const entry = pending.get(data.id);
3681
3713
  const offset = data.offset;
3682
3714
  if (!entry || typeof offset !== "number" || !Number.isInteger(offset) || offset < 0 || offset > entry.payload.byteLength) {
3683
- settle2(data.id, { ok: false, errno: "EIO", message: "sync-sab: bad continuation" });
3715
+ settle3(data.id, { ok: false, errno: "EIO", message: "sync-sab: bad continuation" });
3684
3716
  return;
3685
3717
  }
3686
3718
  publish(data.id, entry, offset);
@@ -3694,7 +3726,7 @@ function attachSyncSabResponder(port, sab, token, opts) {
3694
3726
  try {
3695
3727
  dispatched = dispatch2(req);
3696
3728
  } catch (err) {
3697
- settle2(id, {
3729
+ settle3(id, {
3698
3730
  ok: false,
3699
3731
  errno: "EIO",
3700
3732
  message: err instanceof Error ? err.message : String(err)
@@ -3702,8 +3734,8 @@ function attachSyncSabResponder(port, sab, token, opts) {
3702
3734
  return;
3703
3735
  }
3704
3736
  void dispatched.then(
3705
- (result) => settle2(id, result),
3706
- (err) => settle2(id, {
3737
+ (result) => settle3(id, result),
3738
+ (err) => settle3(id, {
3707
3739
  ok: false,
3708
3740
  errno: "EIO",
3709
3741
  message: err instanceof Error ? err.message : String(err)
@@ -5797,8 +5829,8 @@ function spawnWasmProcess(opts) {
5797
5829
  return dispatchSyncFs(req);
5798
5830
  };
5799
5831
  const responder = attachSyncSabResponder(worker, sab, token, { dispatch: dispatch2 });
5800
- let settle2;
5801
- const exited = new Promise((resolve) => settle2 = resolve);
5832
+ let settle3;
5833
+ const exited = new Promise((resolve) => settle3 = resolve);
5802
5834
  let done4 = false;
5803
5835
  let endedBy;
5804
5836
  const finish = (code, sig) => {
@@ -5813,8 +5845,8 @@ function spawnWasmProcess(opts) {
5813
5845
  opts.held?.delete(holds);
5814
5846
  worker.terminate();
5815
5847
  void process.exit().then(
5816
- () => settle2(code),
5817
- () => settle2(code)
5848
+ () => settle3(code),
5849
+ () => settle3(code)
5818
5850
  );
5819
5851
  };
5820
5852
  const handle = (data) => {
@@ -6127,6 +6159,148 @@ function serveFilesystem(port, handlers, capabilities = {}) {
6127
6159
  return { invalidate: (paths) => reply({ invalidate: paths }) };
6128
6160
  }
6129
6161
 
6162
+ // src/mount/fsa.ts
6163
+ var FSA_CAPABILITIES = { listingStats: true };
6164
+ var codeOf = (err) => err?.code;
6165
+ var denied = (err) => codeOf(err) === "EACCES" || err?.name === "NotAllowedError";
6166
+ function attrOf(st) {
6167
+ return {
6168
+ kind: st.isDirectory ? "directory" : st.isSymbolicLink ? "symlink" : "file",
6169
+ size: st.size,
6170
+ mtime: st.mtime.getTime(),
6171
+ mode: st.mode,
6172
+ ino: st.ino
6173
+ };
6174
+ }
6175
+ function kernelFsHandlers(fs) {
6176
+ const handles = /* @__PURE__ */ new Map();
6177
+ let nextFh = 0;
6178
+ const handle = (fh) => {
6179
+ const found = handles.get(fh);
6180
+ if (!found) throw fsError2("EBADF", `handle ${fh}`);
6181
+ return found;
6182
+ };
6183
+ async function existing(path) {
6184
+ try {
6185
+ return await fs.stat(path);
6186
+ } catch (err) {
6187
+ if (codeOf(err) === "ENOENT") return void 0;
6188
+ throw err;
6189
+ }
6190
+ }
6191
+ return {
6192
+ getattr: async (path) => attrOf(await fs.lstat(path)),
6193
+ readdir: async (path) => (await fs.readdirStat(path)).flatMap(([name2, st]) => {
6194
+ if (!st) return [];
6195
+ const attr = attrOf(st);
6196
+ return [{ name: name2, kind: attr.kind, attr }];
6197
+ }),
6198
+ async open(path, flags) {
6199
+ const st = await existing(path);
6200
+ if (st && flags.exclusive) throw fsError2("EEXIST", path);
6201
+ if (st?.isDirectory && flags.write) throw fsError2("EISDIR", path);
6202
+ if (!st && !flags.create) throw fsError2("ENOENT", path);
6203
+ const data = st && !flags.truncate ? await fs.readFileBuffer(path) : new Uint8Array(0);
6204
+ handles.set(++nextFh, { path, data, length: data.length, dirty: !st || flags.truncate });
6205
+ return nextFh;
6206
+ },
6207
+ async read(fh, offset, size) {
6208
+ const h = handle(fh);
6209
+ return h.data.slice(offset, Math.min(h.length, offset + size));
6210
+ },
6211
+ async write(fh, offset, bytes) {
6212
+ const h = handle(fh);
6213
+ const end = offset + bytes.length;
6214
+ if (end > h.data.length) {
6215
+ const grown = new Uint8Array(Math.max(end, h.data.length * 2));
6216
+ grown.set(h.data.subarray(0, h.length));
6217
+ h.data = grown;
6218
+ }
6219
+ h.data.set(bytes, offset);
6220
+ h.length = Math.max(h.length, end);
6221
+ h.dirty = true;
6222
+ },
6223
+ async release(fh) {
6224
+ const h = handle(fh);
6225
+ handles.delete(fh);
6226
+ if (h.dirty) await fs.writeFile(h.path, h.data.slice(0, h.length));
6227
+ },
6228
+ mkdir: (path) => fs.mkdir(path),
6229
+ async rmdir(path) {
6230
+ if (!(await fs.lstat(path)).isDirectory) throw fsError2("ENOTDIR", path);
6231
+ if ((await fs.readdir(path)).length > 0) throw fsError2("ENOTEMPTY", path);
6232
+ await fs.rm(path);
6233
+ },
6234
+ async unlink(path) {
6235
+ if ((await fs.lstat(path)).isDirectory) throw fsError2("EISDIR", path);
6236
+ await fs.rm(path);
6237
+ },
6238
+ rename: (from, to) => fs.rename(from, to),
6239
+ symlink: (target, path) => fs.symlink(target, path),
6240
+ readlink: (path) => fs.readlink(path),
6241
+ async setattr(path, change) {
6242
+ if (change.mode !== void 0) await fs.chmod(path, change.mode);
6243
+ if (change.mtime !== void 0) {
6244
+ await fs.utimes(path, new Date(change.mtime), new Date(change.mtime));
6245
+ }
6246
+ }
6247
+ };
6248
+ }
6249
+ var EMPTY_ROOT = { kind: "directory", size: 0, mtime: 0, mode: 16877 };
6250
+ function granted(handle) {
6251
+ if (!handle.queryPermission) return Promise.resolve(true);
6252
+ return handle.queryPermission({ mode: "readwrite" }).then(
6253
+ (state) => state === "granted",
6254
+ () => false
6255
+ );
6256
+ }
6257
+ function removableMedium(onLost, open = (handle) => new OpfsFs(handle)) {
6258
+ let current;
6259
+ const absent = () => fsError2("ENOMEDIUM", "No medium found");
6260
+ function wrap(pick) {
6261
+ return async (...args) => {
6262
+ const medium = current;
6263
+ if (!medium) throw absent();
6264
+ try {
6265
+ return await pick(medium.handlers)(...args);
6266
+ } catch (err) {
6267
+ if (!denied(err) || await granted(medium.handle)) throw err;
6268
+ if (current === medium) {
6269
+ current = void 0;
6270
+ onLost(medium.handle);
6271
+ }
6272
+ throw absent();
6273
+ }
6274
+ };
6275
+ }
6276
+ const getattr = wrap((h) => h.getattr);
6277
+ const readdir = wrap((h) => h.readdir);
6278
+ return {
6279
+ handlers: {
6280
+ getattr: (path) => current || path !== "/" ? getattr(path) : Promise.resolve(EMPTY_ROOT),
6281
+ readdir: (path) => current || path !== "/" ? readdir(path) : Promise.resolve([]),
6282
+ open: wrap((h) => h.open),
6283
+ read: wrap((h) => h.read),
6284
+ write: wrap((h) => h.write),
6285
+ release: wrap((h) => h.release),
6286
+ mkdir: wrap((h) => h.mkdir),
6287
+ rmdir: wrap((h) => h.rmdir),
6288
+ unlink: wrap((h) => h.unlink),
6289
+ rename: wrap((h) => h.rename),
6290
+ symlink: wrap((h) => h.symlink),
6291
+ readlink: wrap((h) => h.readlink),
6292
+ setattr: wrap((h) => h.setattr)
6293
+ },
6294
+ present: () => current !== void 0,
6295
+ insert(handle) {
6296
+ current = { handle, handlers: kernelFsHandlers(open(handle)) };
6297
+ },
6298
+ eject() {
6299
+ current = void 0;
6300
+ }
6301
+ };
6302
+ }
6303
+
6130
6304
  // src/mount/connection.ts
6131
6305
  var DRIVER_TIMEOUT_MS = 3e4;
6132
6306
  function unref(timer) {
@@ -6270,14 +6444,16 @@ var MountTable = class {
6270
6444
  this.deps = deps;
6271
6445
  }
6272
6446
  list() {
6273
- return [...this.mounts.values()].map(({ type, source, target, options, state, error }) => ({
6274
- type,
6275
- source,
6276
- target,
6277
- options: { ...options },
6278
- state,
6279
- ...error ? { error } : {}
6280
- }));
6447
+ return [...this.mounts.values()].map(
6448
+ ({ type, source, target, options, state, error, present }) => ({
6449
+ type,
6450
+ source,
6451
+ target,
6452
+ options: { ...options },
6453
+ state: state === "ok" && present?.() === false ? "nomedium" : state,
6454
+ ...error ? { error } : {}
6455
+ })
6456
+ );
6281
6457
  }
6282
6458
  locate(path) {
6283
6459
  let best;
@@ -6348,7 +6524,8 @@ var MountTable = class {
6348
6524
  maxFile: options.maxfile !== void 0 ? parseSize(options.maxfile) : caps.maxFile ?? 0,
6349
6525
  attrs: /* @__PURE__ */ new Map(),
6350
6526
  lists: /* @__PURE__ */ new Map(),
6351
- dispose: () => opened2.dispose()
6527
+ dispose: () => opened2.dispose(),
6528
+ ...opened2.present ? { present: opened2.present } : {}
6352
6529
  };
6353
6530
  mounted = mount;
6354
6531
  this.mounts.set(target, mount);
@@ -7078,6 +7255,10 @@ var Launcher = class {
7078
7255
  pnpmHome;
7079
7256
  mounts;
7080
7257
  createDriverWorker;
7258
+ media;
7259
+ onMountPending;
7260
+ removable = /* @__PURE__ */ new Map();
7261
+ inserted = /* @__PURE__ */ new Map();
7081
7262
  openFiles = /* @__PURE__ */ new Set();
7082
7263
  held = /* @__PURE__ */ new Set();
7083
7264
  nextPid = 1e3;
@@ -7092,6 +7273,8 @@ var Launcher = class {
7092
7273
  this.base = this.watchers.wrap(this.mounts.wrap(options.fs));
7093
7274
  this.createWorker = options.createWorker;
7094
7275
  this.createDriverWorker = options.createDriverWorker;
7276
+ this.media = options.media ?? memoryMedia();
7277
+ this.onMountPending = options.onMountPending;
7095
7278
  this.pnpmHome = options.env?.PNPM_HOME ?? PNPM_HOME;
7096
7279
  this.modulesDir = options.modules ?? "/node_modules";
7097
7280
  this.env = { ...networkEnv(), ...options.env };
@@ -7355,7 +7538,8 @@ var Launcher = class {
7355
7538
  if (sig !== 0) handle.signal(sig);
7356
7539
  return true;
7357
7540
  }
7358
- async driver(type, _spec) {
7541
+ async driver(type, spec) {
7542
+ if (type === "fsa") return this.fsaDriver(spec);
7359
7543
  if (type === "tmpfs") {
7360
7544
  const { port1, port2 } = new MessageChannel();
7361
7545
  serveFilesystem(port2, tmpfs(), { symlinks: true, chmod: true, attrTtl: 0 });
@@ -7396,8 +7580,47 @@ var Launcher = class {
7396
7580
  })
7397
7581
  };
7398
7582
  }
7583
+ async fsaDriver(spec) {
7584
+ const { target, source } = spec;
7585
+ const ask = (handle) => this.onMountPending?.({ target, source, ...handle ? { handle } : {} });
7586
+ const { port1, port2 } = new MessageChannel();
7587
+ const medium = removableMedium((handle) => {
7588
+ served.invalidate(true);
7589
+ ask(handle);
7590
+ });
7591
+ const served = serveFilesystem(port2, medium.handlers, FSA_CAPABILITIES);
7592
+ const id = source.slice("fsa:".length);
7593
+ this.removable.set(target, { id, medium, served });
7594
+ const stored = this.inserted.get(id) ?? await this.media.get(id);
7595
+ if (stored && await granted(stored)) medium.insert(stored);
7596
+ else ask(stored);
7597
+ return {
7598
+ port: port1,
7599
+ present: () => medium.present(),
7600
+ dispose: () => {
7601
+ this.removable.delete(target);
7602
+ port1.close();
7603
+ port2.close();
7604
+ }
7605
+ };
7606
+ }
7399
7607
  mount(spec) {
7400
- return this.mounts.mount(spec, this.fs);
7608
+ if (spec.type !== "fsa" || spec.source.startsWith("fsa:"))
7609
+ return this.mounts.mount(spec, this.fs);
7610
+ return this.mounts.mount({ ...spec, source: `fsa:${crypto.randomUUID()}` }, this.fs);
7611
+ }
7612
+ async insert(target, handle, source) {
7613
+ const at = normalizePath(target);
7614
+ const slot = this.removable.get(at);
7615
+ if (!slot) throw fsError("EINVAL", `${at} is not a removable mount`);
7616
+ if (source !== void 0 && source !== `fsa:${slot.id}`) {
7617
+ throw fsError("EINVAL", `${at} is no longer the drive ${source}`);
7618
+ }
7619
+ if (!await granted(handle)) throw fsError("EACCES", `${at}: no permission for this folder`);
7620
+ this.inserted.set(slot.id, handle);
7621
+ await this.media.put(slot.id, handle);
7622
+ slot.medium.insert(handle);
7623
+ slot.served.invalidate(true);
7401
7624
  }
7402
7625
  umount(target) {
7403
7626
  this.mounts.umount(target);
@@ -7505,6 +7728,10 @@ function act2(session, req) {
7505
7728
  function dirsChannel(name2) {
7506
7729
  return name2 === false ? void 0 : `slicc-kernel-dirs:${name2}`;
7507
7730
  }
7731
+ async function stores(deps, name2) {
7732
+ if (name2 === false) return {};
7733
+ return { meta: await deps.metadata?.(name2), media: await deps.media?.(`${name2}:media`) };
7734
+ }
7508
7735
  function serveKernel(port, deps) {
7509
7736
  let launcher;
7510
7737
  let remote;
@@ -7556,7 +7783,7 @@ function serveKernel(port, deps) {
7556
7783
  async function init(req) {
7557
7784
  launcher = (req.root ? Promise.resolve(req.root) : deps.storage()).then(async (root) => {
7558
7785
  const name2 = req.metadata ?? META_DB;
7559
- const meta = name2 === false ? void 0 : await deps.metadata?.(name2);
7786
+ const { meta, media } = await stores(deps, name2);
7560
7787
  remote = req.transport ? new RemoteTransport(port, req.transport) : void 0;
7561
7788
  const fs = new OpfsFs(root, meta, dirsChannel(name2));
7562
7789
  await fs.reconcile();
@@ -7567,6 +7794,8 @@ function serveKernel(port, deps) {
7567
7794
  ...req.modules ? { modules: req.modules } : {},
7568
7795
  ...req.env ? { env: req.env } : {},
7569
7796
  ...remote ? { transport: remote } : {},
7797
+ ...media ? { media } : {},
7798
+ onMountPending: (medium) => port.postMessage({ medium }),
7570
7799
  caStore: caStore(deps.metadata ? name2 : false)
7571
7800
  });
7572
7801
  await started.prepare();
@@ -7579,13 +7808,15 @@ function serveKernel(port, deps) {
7579
7808
  const l = await ready2();
7580
7809
  if (req.op === "mount") return l.mount(req.spec);
7581
7810
  if (req.op === "umount") return l.umount(req.target);
7811
+ if (req.op === "insert") return l.insert(req.target, req.handle, req.source);
7582
7812
  return l.mounts.list();
7583
7813
  }
7584
7814
  async function handle(req) {
7585
7815
  if (req.op === "init") return init(req);
7586
7816
  if (req.op === "open-terminal") return terminal(req);
7587
7817
  if (req.op === "connect") return connect();
7588
- if (req.op === "mount" || req.op === "umount" || req.op === "mounts") return mountOp(req);
7818
+ if (req.op === "mount" || req.op === "umount" || req.op === "mounts" || req.op === "insert")
7819
+ return mountOp(req);
7589
7820
  if (req.op === "terminal") {
7590
7821
  const session = terminals.get(req.terminal);
7591
7822
  if (!session) throw new Error(`no terminal ${req.terminal}`);
@@ -7618,6 +7849,7 @@ serveKernel(globalThis, {
7618
7849
  storage: () => navigator.storage.getDirectory(),
7619
7850
  createWorker: () => new Worker(processWorker, { type: "module" }),
7620
7851
  createDriverWorker: () => new Worker(driverWorker, { type: "module" }),
7621
- metadata: (name2) => openMeta(name2, indexedDB)
7852
+ metadata: (name2) => openMeta(name2, indexedDB),
7853
+ media: (name2) => openMedia(name2, indexedDB)
7622
7854
  });
7623
7855
  //# sourceMappingURL=kernel-worker.js.map