@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.
package/dist/connect.d.ts CHANGED
@@ -6757,6 +6757,8 @@ interface VfsContentStore {
6757
6757
  write(data: Uint8Array | Blob, meta: {
6758
6758
  name: string;
6759
6759
  type: string;
6760
+ }, opts?: {
6761
+ onProgress?: (done: number, total: number) => void;
6760
6762
  }): Promise<{
6761
6763
  manifest: FileManifest;
6762
6764
  size: number;
@@ -6817,6 +6819,18 @@ declare class VfsNamespace {
6817
6819
  * than by TTL — a stale directory listing shows files that are not there.
6818
6820
  */
6819
6821
  private cache;
6822
+ /**
6823
+ * The write of each directory record still in flight, by id.
6824
+ *
6825
+ * Two writes of one record must land in the order they were made. The
6826
+ * personal space has no compare-and-swap: of two writes of one key that
6827
+ * are in flight together it keeps whichever request finishes last, and
6828
+ * that is often the one sent first. Then the earlier write, sealed before
6829
+ * the later edit was made, undoes it. That is how a file deleted forever
6830
+ * from the Trash, while the move that put it there was still being
6831
+ * written, came back. So each write waits for the one before it.
6832
+ */
6833
+ private readonly writing;
6820
6834
  private readonly historyLimit;
6821
6835
  private readonly maxEntries;
6822
6836
  private readonly maxPathChars;
@@ -6975,6 +6989,7 @@ declare class VfsNamespace {
6975
6989
  */
6976
6990
  writeFile(path: string, data: Uint8Array | string | Blob, opts?: {
6977
6991
  type?: string;
6992
+ onProgress?: (done: number, total: number) => void;
6978
6993
  }): Promise<VfsStat>;
6979
6994
  /**
6980
6995
  * Delete a file, or a directory when `recursive`.
@@ -7088,6 +7103,14 @@ declare class VfsNamespace {
7088
7103
  private readRecord;
7089
7104
  /** Apply an edit to a directory record and persist it. */
7090
7105
  private mutate;
7106
+ /**
7107
+ * Seal a directory record as it stands NOW and put it in the store.
7108
+ *
7109
+ * Read at the moment of sending, not when the edit was made, so a write
7110
+ * that waited behind another carries every edit made to the record by
7111
+ * then, its own included.
7112
+ */
7113
+ private writeRecord;
7091
7114
  private createDir;
7092
7115
  /**
7093
7116
  * Every directory record in a subtree, each once, opened - and nothing
@@ -8975,7 +8998,7 @@ declare function getId(row: {
8975
8998
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
8976
8999
  * `package.json` `version`.
8977
9000
  */
8978
- declare const VERSION = "0.15.0-alpha.0";
9001
+ declare const VERSION = "0.15.0-alpha.1";
8979
9002
 
8980
9003
  /**
8981
9004
  * The SDK's process-global logger.
@@ -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