@ai-ecoverse/slicc-kernel 1.4.0 → 1.5.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.
@@ -59,34 +59,62 @@ function settle(transaction, value) {
59
59
  }
60
60
  var IndexedDbMeta = class {
61
61
  db;
62
+ cache = /* @__PURE__ */ new Map();
63
+ channel;
64
+ generation = 0;
62
65
  constructor(db) {
63
66
  this.db = db;
64
67
  db.onversionchange = () => db.close();
68
+ this.channel = new BroadcastChannel(`slicc-kernel-meta:${db.name}`);
69
+ this.channel.onmessage = ({ data }) => this.drop(data);
70
+ this.channel.unref?.();
71
+ }
72
+ drop({ paths = [], trees = [] }) {
73
+ this.generation++;
74
+ for (const path of paths) this.cache.delete(path);
75
+ for (const key of [...this.cache.keys()]) {
76
+ if (trees.some((tree) => under(key, tree))) this.cache.delete(key);
77
+ }
78
+ }
79
+ changed(invalidation) {
80
+ this.drop(invalidation);
81
+ this.channel.postMessage(invalidation);
65
82
  }
66
83
  transaction(mode) {
67
84
  const transaction = this.db.transaction(META_STORE, mode);
68
85
  return [transaction, transaction.objectStore(META_STORE)];
69
86
  }
70
87
  async get(paths) {
71
- const [transaction, store] = this.transaction("readonly");
72
- const found = paths.map(() => void 0);
73
- paths.forEach((path, i) => {
74
- const request2 = store.get(path);
75
- request2.onsuccess = () => {
76
- found[i] = request2.result;
77
- };
78
- });
79
- return settle(transaction, () => found);
88
+ const missing3 = paths.filter((path) => !this.cache.has(path));
89
+ if (missing3.length > 0) {
90
+ const generation = this.generation;
91
+ const [transaction, store] = this.transaction("readonly");
92
+ const found = /* @__PURE__ */ new Map();
93
+ for (const path of missing3) {
94
+ const request2 = store.get(path);
95
+ request2.onsuccess = () => void found.set(path, request2.result ?? null);
96
+ }
97
+ await settle(transaction, () => void 0);
98
+ if (generation === this.generation)
99
+ for (const [path, entry] of found) this.cache.set(path, entry);
100
+ return paths.map(
101
+ (path) => (found.has(path) ? found.get(path) : this.cache.get(path)) ?? void 0
102
+ );
103
+ }
104
+ return paths.map((path) => this.cache.get(path) ?? void 0);
80
105
  }
81
106
  async update(path, change) {
82
107
  const [transaction, store] = this.transaction("readwrite");
83
108
  const request2 = store.get(path);
109
+ let next;
84
110
  request2.onsuccess = () => {
85
- const next = change(request2.result);
111
+ next = change(request2.result);
86
112
  if (next) store.put(next);
87
113
  else store.delete(path);
88
114
  };
89
- return settle(transaction, () => void 0);
115
+ await settle(transaction, () => void 0);
116
+ this.changed({ paths: [path] });
117
+ this.cache.set(path, next ?? null);
90
118
  }
91
119
  async move(from, to, root) {
92
120
  const [transaction, store] = this.transaction("readwrite");
@@ -102,7 +130,8 @@ var IndexedDbMeta = class {
102
130
  }
103
131
  store.put(moved(root(source.result), to));
104
132
  };
105
- return settle(transaction, () => void 0);
133
+ await settle(transaction, () => void 0);
134
+ this.changed({ trees: [from, to] });
106
135
  }
107
136
  async remove(paths) {
108
137
  const [transaction, store] = this.transaction("readwrite");
@@ -110,7 +139,8 @@ var IndexedDbMeta = class {
110
139
  store.delete(path);
111
140
  store.delete(descendants(path));
112
141
  }
113
- return settle(transaction, () => void 0);
142
+ await settle(transaction, () => void 0);
143
+ this.changed({ trees: paths });
114
144
  }
115
145
  async links(dir) {
116
146
  const [transaction, store] = this.transaction("readonly");
@@ -183,6 +213,7 @@ var S_IFLNK = 40960;
183
213
  var PERM_MASK = 4095;
184
214
  var READ_ATTEMPTS = 3;
185
215
  var MAX_LINKS = 40;
216
+ var CHECK_MS = 1e3;
186
217
  var EXECUTABLE = /\/node_modules\/(?:@[^/]+\/)?[^/]+\/bin\//;
187
218
  var ERRNO_BY_DOM_ERROR = {
188
219
  NotFoundError: "ENOENT",
@@ -222,37 +253,100 @@ function isHandle(child) {
222
253
  var OpfsFs = class {
223
254
  root;
224
255
  meta;
225
- constructor(root, meta = new MemoryMeta()) {
256
+ dirs = /* @__PURE__ */ new Map();
257
+ checked = /* @__PURE__ */ new WeakMap();
258
+ channel;
259
+ constructor(root, meta = new MemoryMeta(), channel) {
226
260
  this.root = root;
227
261
  this.meta = meta;
262
+ this.channel = channel === void 0 ? void 0 : new BroadcastChannel(channel);
263
+ if (this.channel) {
264
+ this.channel.onmessage = ({ data }) => {
265
+ for (const path of data) this.forget(path);
266
+ };
267
+ this.channel.unref?.();
268
+ }
269
+ }
270
+ changed(...paths) {
271
+ for (const path of paths) this.forget(path);
272
+ this.channel?.postMessage(paths);
228
273
  }
229
274
  resolvePath(base, path) {
230
275
  return resolvePath(base, path);
231
276
  }
277
+ async walk(parts, from, start) {
278
+ let dir = start;
279
+ for (let at = from; at < parts.length; at++) {
280
+ dir = await dir.getDirectoryHandle(parts[at]);
281
+ this.dirs.set(`/${parts.slice(0, at + 1).join("/")}`, dir);
282
+ this.checked.set(dir, performance.now());
283
+ }
284
+ return dir;
285
+ }
232
286
  async dir(path) {
233
- let dir = this.root;
287
+ const parts = split(path);
288
+ let from = parts.length;
289
+ let cached;
290
+ for (; from > 0 && !cached; from--)
291
+ cached = this.dirs.get(`/${parts.slice(0, from).join("/")}`);
292
+ if (cached) from++;
293
+ const at = `/${parts.slice(0, from).join("/")}`;
294
+ if (cached && !await this.current(at, cached)) {
295
+ this.forget(at);
296
+ cached = void 0;
297
+ from = 0;
298
+ }
234
299
  try {
235
- for (const part of split(path)) dir = await dir.getDirectoryHandle(part);
300
+ return await this.walk(parts, from, cached ?? this.root);
236
301
  } catch (err) {
237
302
  throw translate(err, path);
238
303
  }
239
- return dir;
304
+ }
305
+ async current(path, dir) {
306
+ if (performance.now() - (this.checked.get(dir) ?? -CHECK_MS) < CHECK_MS) return true;
307
+ const where = await this.root.resolve(dir).catch(() => null);
308
+ if (where === null || `/${where.join("/")}` !== path) return false;
309
+ this.checked.set(dir, performance.now());
310
+ return true;
311
+ }
312
+ forget(path) {
313
+ for (const key of [...this.dirs.keys()]) if (within(key, path)) this.dirs.delete(key);
314
+ }
315
+ async inDir(path, op) {
316
+ const dir = await this.dir(path);
317
+ try {
318
+ return await op(dir);
319
+ } catch (err) {
320
+ if (err?.name !== "NotFoundError" || await this.current(path, dir))
321
+ throw err;
322
+ this.forget(path);
323
+ return op(await this.dir(path));
324
+ }
325
+ }
326
+ async child(parent, name2, kind) {
327
+ return kind === "file" ? parent.getFileHandle(name2) : parent.getDirectoryHandle(name2);
240
328
  }
241
329
  async parent(path) {
242
330
  const parts = split(path);
243
331
  const name2 = parts.pop();
244
332
  if (name2 === void 0) throw fsError("EBUSY", path);
245
- return [await this.dir(`/${parts.join("/")}`), name2];
333
+ return [`/${parts.join("/")}`, name2];
246
334
  }
247
335
  async handle(path) {
248
336
  if (split(path).length === 0) return this.root;
249
337
  const [dir, name2] = await this.parent(path);
250
- try {
251
- return await dir.getFileHandle(name2);
252
- } catch (err) {
253
- if (err?.name !== "TypeMismatchError") throw translate(err, path);
254
- }
255
- return dir.getDirectoryHandle(name2);
338
+ const known = this.dirs.has(path);
339
+ return this.inDir(dir, async (parent) => {
340
+ const [first, second] = known ? ["directory", "file"] : ["file", "directory"];
341
+ try {
342
+ return await this.child(parent, name2, first);
343
+ } catch (err) {
344
+ if (err?.name !== "TypeMismatchError") throw err;
345
+ }
346
+ return this.child(parent, name2, second);
347
+ }).catch((err) => {
348
+ throw translate(err, path);
349
+ });
256
350
  }
257
351
  real(path) {
258
352
  return this.handle(path).then(
@@ -354,24 +448,25 @@ var OpfsFs = class {
354
448
  }
355
449
  async writeTarget(path) {
356
450
  try {
357
- return (await this.locate(path, true)).path;
451
+ return [(await this.locate(path, true)).path, true];
358
452
  } catch (err) {
359
453
  if (!missing(err)) throw err;
360
454
  }
361
455
  const [entry] = await this.meta.get([path]);
362
456
  const wanted = entry?.link === void 0 ? path : resolvePath(parentOf2(path), entry.link);
363
- return resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted));
457
+ return [resolvePath((await this.locate(parentOf2(wanted), true)).path, baseOf(wanted)), false];
364
458
  }
365
459
  async writeFile(path, content) {
366
- const target = await this.writeTarget(path);
460
+ const [target, existed] = await this.writeTarget(path);
367
461
  const [dir, name2] = await this.parent(target);
368
462
  let file;
369
463
  try {
370
- file = await dir.getFileHandle(name2, { create: true });
464
+ file = await this.inDir(dir, (parent) => parent.getFileHandle(name2, { create: true }));
371
465
  } catch (err) {
372
466
  const code = err?.name === "TypeMismatchError" ? "EISDIR" : null;
373
467
  throw code ? fsError(code, path) : translate(err, path);
374
468
  }
469
+ if (!existed && content.length === 0) return;
375
470
  const writable = await file.createWritable();
376
471
  await writable.write(content);
377
472
  await writable.close();
@@ -434,16 +529,21 @@ var OpfsFs = class {
434
529
  if (found.handle) {
435
530
  const [dir, name2] = await this.parent(found.path);
436
531
  try {
437
- await dir.removeEntry(name2, { recursive: options.recursive === true });
532
+ await this.inDir(
533
+ dir,
534
+ (parent) => parent.removeEntry(name2, { recursive: options.recursive === true })
535
+ );
438
536
  } catch (err) {
439
537
  throw translate(err, path);
440
538
  }
539
+ this.changed(found.path);
441
540
  }
442
541
  await this.meta.remove([found.path]);
443
542
  }
444
543
  async rename(from, to) {
445
544
  if (from === to) return;
446
545
  const source = await this.locate(from, false);
546
+ this.forget(source.path);
447
547
  const parent = await this.locate(parentOf2(to), true);
448
548
  const destination = resolvePath(parent.path, baseOf(to));
449
549
  const sourceStat = await this.describe(source);
@@ -476,6 +576,7 @@ var OpfsFs = class {
476
576
  const name2 = baseOf(destination);
477
577
  const swap = targetStat?.isDirectory === true;
478
578
  const staging = swap ? `.${name2}.${crypto.randomUUID()}` : name2;
579
+ this.forget(destination);
479
580
  const timed = await this.explicitTimes(source.path);
480
581
  const moved2 = await this.place(source.handle, dir, staging);
481
582
  if (swap) await this.rm(destination, { recursive: true });
@@ -489,6 +590,7 @@ var OpfsFs = class {
489
590
  copied ||= !placed;
490
591
  }
491
592
  if (copied) await this.restamp(destination, timed);
593
+ this.changed(source.path, destination);
492
594
  }
493
595
  async modified(path) {
494
596
  const handle = await this.handle(path);
@@ -5798,6 +5900,9 @@ function act(session, req) {
5798
5900
  else if (req.action === "signal") session.signal(signalNumber(req.signal));
5799
5901
  else session.close();
5800
5902
  }
5903
+ function dirsChannel(name2) {
5904
+ return name2 === false ? void 0 : `slicc-kernel-dirs:${name2}`;
5905
+ }
5801
5906
  function serveKernel(port, deps) {
5802
5907
  let launcher;
5803
5908
  let remote;
@@ -5824,7 +5929,7 @@ function serveKernel(port, deps) {
5824
5929
  const name2 = req.metadata ?? META_DB;
5825
5930
  const meta = name2 === false ? void 0 : await deps.metadata?.(name2);
5826
5931
  remote = req.transport ? new RemoteTransport(port, req.transport) : void 0;
5827
- const fs = new OpfsFs(root, meta);
5932
+ const fs = new OpfsFs(root, meta, dirsChannel(name2));
5828
5933
  await fs.reconcile();
5829
5934
  const started = new Launcher({
5830
5935
  fs,