@muhkoo/connect 0.15.0-alpha.0 → 0.15.0-alpha.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.
@@ -6675,7 +6675,7 @@ async function parseOrThrow(response, op) {
6675
6675
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
6676
6676
  * `package.json` `version`.
6677
6677
  */
6678
- const VERSION = "0.15.0-alpha.0";
6678
+ const VERSION = "0.15.0-alpha.1";
6679
6679
 
6680
6680
  /**
6681
6681
  * `HttpClient` — the one place the unified {@link Client} talks to the
@@ -15458,6 +15458,18 @@ class VfsNamespace {
15458
15458
  * than by TTL — a stale directory listing shows files that are not there.
15459
15459
  */
15460
15460
  cache = new Map();
15461
+ /**
15462
+ * The write of each directory record still in flight, by id.
15463
+ *
15464
+ * Two writes of one record must land in the order they were made. The
15465
+ * personal space has no compare-and-swap: of two writes of one key that
15466
+ * are in flight together it keeps whichever request finishes last, and
15467
+ * that is often the one sent first. Then the earlier write, sealed before
15468
+ * the later edit was made, undoes it. That is how a file deleted forever
15469
+ * from the Trash, while the move that put it there was still being
15470
+ * written, came back. So each write waits for the one before it.
15471
+ */
15472
+ writing = new Map();
15461
15473
  historyLimit;
15462
15474
  maxEntries;
15463
15475
  maxPathChars;
@@ -15921,10 +15933,7 @@ class VfsNamespace {
15921
15933
  const parentPath = dirname(target);
15922
15934
  await this.mkdir(parentPath, { recursive: true });
15923
15935
  const bytes = typeof data === "string" ? new TextEncoder().encode(data) : data;
15924
- const { manifest, size } = await this.deps.content.write(bytes, {
15925
- name,
15926
- type: opts.type ?? contentTypeFor(name),
15927
- });
15936
+ const { manifest, size } = await this.deps.content.write(bytes, { name, type: opts.type ?? contentTypeFor(name) }, { onProgress: opts.onProgress });
15928
15937
  const parent = await this.openDir(parentPath);
15929
15938
  const prior = entryIn(parent.node, name);
15930
15939
  if (prior?.kind === "dir")
@@ -16482,24 +16491,14 @@ class VfsNamespace {
16482
16491
  if (Object.hasOwn(dir.node.entries, name))
16483
16492
  delete dir.hidden[name];
16484
16493
  }
16485
- const stored = Object.keys(dir.hidden).length
16486
- ? { ...dir.node, entries: Object.assign(Object.create(null), dir.hidden, dir.node.entries) }
16487
- : dir.node;
16494
+ // Not sent until any earlier write of this record has landed: see
16495
+ // `writing`. Whether that one worked or not, this one still goes.
16496
+ const before = this.writing.get(dir.id);
16497
+ const send = () => this.writeRecord(dir);
16498
+ const write = before ? before.then(send, send) : send();
16499
+ this.writing.set(dir.id, write);
16488
16500
  try {
16489
- let sealed;
16490
- try {
16491
- sealed = await seal(dir.key, stored);
16492
- }
16493
- catch (err) {
16494
- // JSON.stringify's own failures: a value too deeply nested to
16495
- // write. Reading drops those from a stored record, so this is
16496
- // what the write itself was given.
16497
- if (!(err instanceof RangeError || err instanceof TypeError))
16498
- throw err;
16499
- const where = dir.path ?? `the directory stored as record ${printable(dir.id, 40)}`;
16500
- throw new VfsConflictError(`cannot write to ${where}: it would hold a value nested too deeply to store`);
16501
- }
16502
- await this.deps.store.put(dirKey(dir.id), sealed);
16501
+ await write;
16503
16502
  }
16504
16503
  catch (err) {
16505
16504
  // The edit above was made in place, on the very object the cache
@@ -16510,8 +16509,38 @@ class VfsNamespace {
16510
16509
  this.cache.delete(dir.id);
16511
16510
  throw err;
16512
16511
  }
16512
+ finally {
16513
+ if (this.writing.get(dir.id) === write)
16514
+ this.writing.delete(dir.id);
16515
+ }
16513
16516
  this.cache.set(dir.id, Promise.resolve(dir));
16514
16517
  }
16518
+ /**
16519
+ * Seal a directory record as it stands NOW and put it in the store.
16520
+ *
16521
+ * Read at the moment of sending, not when the edit was made, so a write
16522
+ * that waited behind another carries every edit made to the record by
16523
+ * then, its own included.
16524
+ */
16525
+ async writeRecord(dir) {
16526
+ const stored = Object.keys(dir.hidden).length
16527
+ ? { ...dir.node, entries: Object.assign(Object.create(null), dir.hidden, dir.node.entries) }
16528
+ : dir.node;
16529
+ let sealed;
16530
+ try {
16531
+ sealed = await seal(dir.key, stored);
16532
+ }
16533
+ catch (err) {
16534
+ // JSON.stringify's own failures: a value too deeply nested to
16535
+ // write. Reading drops those from a stored record, so this is
16536
+ // what the write itself was given.
16537
+ if (!(err instanceof RangeError || err instanceof TypeError))
16538
+ throw err;
16539
+ const where = dir.path ?? `the directory stored as record ${printable(dir.id, 40)}`;
16540
+ throw new VfsConflictError(`cannot write to ${where}: it would hold a value nested too deeply to store`);
16541
+ }
16542
+ await this.deps.store.put(dirKey(dir.id), sealed);
16543
+ }
16515
16544
  async createDir(parentPath, name) {
16516
16545
  assertValidName(name);
16517
16546
  const parent = await this.openDir(parentPath);
@@ -19488,9 +19517,9 @@ class Client {
19488
19517
  // GC. `storage.writeFile` would additionally index the manifest
19489
19518
  // in the space — a second, diverging source of truth for a file
19490
19519
  // whose handle the VFS already holds.
19491
- write: async (data, meta) => {
19520
+ write: async (data, meta, opts) => {
19492
19521
  const space = await resolveContentSpace();
19493
- const { manifest, stat } = await space.putFile(data, { name: meta.name, type: meta.type });
19522
+ const { manifest, stat } = await space.putFile(data, { name: meta.name, type: meta.type }, { onProgress: opts?.onProgress });
19494
19523
  return { manifest, size: stat.size };
19495
19524
  },
19496
19525
  // Reads go through the manifest alone — no space needed. That is