@muhkoo/connect 0.15.0-alpha.3 → 0.15.0-alpha.4

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
@@ -4100,14 +4100,36 @@ interface OfflineStore {
4100
4100
  * Replays are idempotent by construction (kv/db are LWW-by-key, messages dedupe
4101
4101
  * by `clientId`, file PUTs are content-addressed), so re-running a partially
4102
4102
  * drained queue is safe.
4103
+ *
4104
+ * Every tab of an origin shares the queue. Given a `tryLock`, a drain runs
4105
+ * only while holding `muhkoo:offline-drain:<commitment>`, so two tabs coming
4106
+ * back online replay it once between them, not once each.
4103
4107
  */
4104
4108
 
4105
4109
  type Replayer = (entry: QueueEntry) => Promise<void>;
4110
+ /**
4111
+ * Run `fn` holding lock `name` if no one else holds it: true once it has
4112
+ * run, false - without running it - when another holder has it. Web Locks'
4113
+ * `ifAvailable` in a browser tab; see `core/crossTab.ts`.
4114
+ */
4115
+ type TryLock = (name: string, fn: () => Promise<void>) => Promise<boolean>;
4116
+ interface OutboundQueueOptions {
4117
+ /**
4118
+ * The lock that keeps two tabs from draining at once. Every tab of an
4119
+ * origin shares one IndexedDB queue, and each replayed all of it when it
4120
+ * came back online: two tabs, every queued write twice. Absent (Node,
4121
+ * tests), every drain runs, as before.
4122
+ */
4123
+ tryLock?: TryLock;
4124
+ /** Whose drain lock to take: the signed-in user's. With no one signed in, the drain runs unlocked. */
4125
+ commitment?: () => string | null;
4126
+ }
4106
4127
  declare class OutboundQueue {
4107
4128
  private readonly store;
4129
+ private readonly opts;
4108
4130
  private readonly replayers;
4109
4131
  private draining;
4110
- constructor(store: OfflineStore);
4132
+ constructor(store: OfflineStore, opts?: OutboundQueueOptions);
4111
4133
  /** A domain registers how to replay its mutations. */
4112
4134
  register(domain: QueueEntry["domain"], replayer: Replayer): void;
4113
4135
  /** Record a mutation for later replay; resolves with its assigned seq. */
@@ -4120,6 +4142,13 @@ declare class OutboundQueue {
4120
4142
  * remaining entries for the next attempt.
4121
4143
  */
4122
4144
  drain(): Promise<void>;
4145
+ /**
4146
+ * One drain, holding the device-wide drain lock where there is one. A
4147
+ * tab that finds another tab holding it leaves the queue to that one,
4148
+ * which is replaying it already and removes each entry as it lands - so
4149
+ * a drain after it finds only what it left.
4150
+ */
4151
+ private drainOnce;
4123
4152
  private runDrain;
4124
4153
  }
4125
4154
 
@@ -4249,6 +4278,12 @@ interface OfflineManagerDeps {
4249
4278
  session: SessionState;
4250
4279
  /** Whether offline support is active (browser) vs a no-op (Node/Workers). */
4251
4280
  enabled: boolean;
4281
+ /**
4282
+ * The drain lock the tabs of this origin share - Web Locks in a browser
4283
+ * tab. Absent, every tab drains the shared queue itself, as before. See
4284
+ * `OutboundQueue`.
4285
+ */
4286
+ locks?: TryLock;
4252
4287
  }
4253
4288
  declare class OfflineManager {
4254
4289
  /** True in browsers with IndexedDB; false when running over a NoopStore. */
@@ -6757,6 +6792,114 @@ declare class VfsLimitError extends Error {
6757
6792
  constructor(limit: VfsLimitError["limit"], max: number, path: string, at: string, doing?: string);
6758
6793
  }
6759
6794
 
6795
+ /**
6796
+ * How `client.vfs` orders writes of one directory record between the tabs of
6797
+ * an app on one device. Internal: nothing re-exports this module, and it is
6798
+ * not public API.
6799
+ *
6800
+ * The personal space keeps whichever write of a key arrives last, so two tabs
6801
+ * that each edit a copy of a folder and write it lose one tab's edit. A
6802
+ * coordinator gives every namespace on the device one lock per record, held
6803
+ * from the decision to write until the write has landed or failed, and a
6804
+ * count of each record's writes, which a writer stores before it lets go of
6805
+ * the lock and the next holder reads to tell whether its copy is current.
6806
+ * It also tells the others which record was written, as a hint: that can
6807
+ * reach them after the next holder has decided, or never. And it gives
6808
+ * each file or folder being moved or deleted a lock, which a move or delete
6809
+ * of it elsewhere takes only if it is free, and refuses otherwise (see
6810
+ * `holdMoving`). `Client` builds one
6811
+ * from Web Locks and a BroadcastChannel in a browser tab (see
6812
+ * `core/crossTab.ts`); without one, writes are ordered within one namespace
6813
+ * only, as they always were.
6814
+ */
6815
+ interface VfsCoordinator {
6816
+ /**
6817
+ * Run `fn` holding the device-wide lock for record `id`, and settle as it
6818
+ * does. Where the runtime refuses the lock to this document, as it then
6819
+ * does to every tab of the app, `fn` runs without it.
6820
+ */
6821
+ lock<T>(id: string, fn: () => Promise<T>): Promise<T>;
6822
+ /** Tell every other namespace that this one has just written record `id`. Called while holding its lock. */
6823
+ announce(id: string): void;
6824
+ /**
6825
+ * Call `handler` with the id of each record another namespace announces -
6826
+ * or with null when announcements may have been missed, because this one
6827
+ * has only just started hearing them, or now hears them for someone else,
6828
+ * so that every record held must be taken as written elsewhere. Returns a
6829
+ * function that stops it.
6830
+ */
6831
+ onAnnounce(handler: (id: string | null) => void): () => void;
6832
+ /**
6833
+ * Make sure announcements for whoever is signed in now are being heard.
6834
+ * Cheap; called before every read of a record from the store, so that
6835
+ * nothing is read before this namespace can hear of a later write of it.
6836
+ */
6837
+ listen(): void;
6838
+ /**
6839
+ * How often each record in `ids` has been written by any namespace on
6840
+ * the device, for whoever is signed in: one counter each, in order, read
6841
+ * at one moment from storage every tab of the app shares (IndexedDB, in
6842
+ * a browser). Null where it cannot be told: the storage cannot be read,
6843
+ * or does not answer within a few seconds, or a write of the record has
6844
+ * begun and not been counted (see `begin`). Never rejects.
6845
+ *
6846
+ * A copy of a record fetched after its counter was read holds every
6847
+ * write the counter counts. Read again once its holder has taken a lock
6848
+ * that another tab held while it wrote the record - the record's own, or
6849
+ * a move lock - a counter that has not moved says no other tab has
6850
+ * written it since: unlike an announcement, a count is stored before
6851
+ * the writer lets go of its locks. See `VfsNamespace.sendLocked`.
6852
+ */
6853
+ counters(ids: readonly string[]): Promise<Array<string | null>>;
6854
+ /**
6855
+ * Called holding record `id`'s lock, before deciding whether a copy of it
6856
+ * is current: its counter as the last tab to write it left it - one whose
6857
+ * write began and was never counted, which no copy holds, included. And
6858
+ * from then, stored with it, a mark that a write of it has begun, which
6859
+ * `counters` reports as null until `bump`: a tab that goes, or cannot
6860
+ * count its write, in between leaves every tab reading the record again.
6861
+ * Null when the storage could not be read and marked within a few
6862
+ * seconds. Never rejects.
6863
+ */
6864
+ begin(id: string): Promise<string | null>;
6865
+ /**
6866
+ * Called holding record `id`'s lock, after `begin`, once the write has
6867
+ * landed or failed, or nothing was written after all: count it, and
6868
+ * resolve to the record's new counter once that is stored - so before
6869
+ * the lock is let go of. Null when it could not be stored within a few
6870
+ * seconds. Never rejects.
6871
+ */
6872
+ bump(id: string): Promise<string | null>;
6873
+ /**
6874
+ * Hold the lock that says entry `id` - a file or a folder, by its id in
6875
+ * the tree - is being moved or deleted on this device, if no one holds
6876
+ * it. Never waits: resolves to null when another holder has it, in this
6877
+ * tab or another, and otherwise to the function that lets go of it. A
6878
+ * tab that dies lets go of everything it held. With no one signed in it
6879
+ * holds nothing, and resolves to a function that does nothing; so too
6880
+ * where the runtime refuses the lock to this document, as it then does
6881
+ * to every tab of the app.
6882
+ *
6883
+ * `shared` holds a folder's lock as a place on the way to where a move
6884
+ * is putting something. Any number of shared holders hold it at once,
6885
+ * in any tab; a holder that is not shared cannot have it while they do,
6886
+ * nor they while it does.
6887
+ *
6888
+ * A move marks its entry before it puts it in its second place, and a
6889
+ * mark cannot tell a move still writing from one that was interrupted.
6890
+ * Another tab took the first for the second: it finished the move too,
6891
+ * naming the entry in two places, or let go of what the move was about
6892
+ * to put somewhere else. So a move holds this from before it marks until
6893
+ * it has taken the entry from where it was, a delete holds it for all it
6894
+ * is deleting, and either refuses when it cannot have it. A move also
6895
+ * holds the folder it is moving into, and every folder above that one,
6896
+ * shared, for as long: a move into a folder that another tab was
6897
+ * deleting put what it moved in a record the delete then purged. See
6898
+ * `VfsNamespace.rename` and `VfsNamespace.delete`.
6899
+ */
6900
+ holdMoving(id: string, shared?: boolean): Promise<(() => void) | null>;
6901
+ }
6902
+
6760
6903
  /**
6761
6904
  * `client.vfs` — a real filesystem over Muhkoo primitives.
6762
6905
  *
@@ -6769,21 +6912,25 @@ declare class VfsLimitError extends Error {
6769
6912
  *
6770
6913
  * Every mutation is a read-modify-write of exactly ONE directory record, so a
6771
6914
  * write cannot leave the tree half-updated: either the parent names the new
6772
- * child or it does not. Two tabs writing different directories never conflict;
6773
- * two tabs (or devices) writing the SAME directory are last-writer-wins. A VFS
6774
- * write sends no HLC and the personal space has no compare-and-swap: it applies
6775
- * puts of one key in the order they arrive, so whichever arrives last is kept,
6776
- * and the other tab's edit is lost. Orphaned records (a directory whose parent
6777
- * entry lost the race) are unreachable rather than corrupting — they cost
6778
- * storage until a future sweep, which is the trade this shape makes in
6915
+ * child or it does not. A VFS write sends no HLC and the personal space has
6916
+ * no compare-and-swap: of two writes of one key it keeps whichever arrives
6917
+ * last.
6918
+ *
6919
+ * Within ONE namespace, writes of a record are sent one at a time, in the
6920
+ * order they were made - edits made while one is in flight go out together
6921
+ * in the next (see `queue`) - and a read that has to fetch a record waits for
6922
+ * its write in flight (see `writing`). Every edit of a record is made to one
6923
+ * in-memory copy of it, and that copy is what is written (see `mutate`).
6924
+ *
6925
+ * Between the tabs of an app on one device, a coordinator (`VfsCoordinator`;
6926
+ * `Client` wires one in a browser tab) gives each record one lock, and a tab
6927
+ * reads a record another tab has written again before its next write of it
6928
+ * (see `sendLocked`). Between devices - or a browser and the CLI - two writes
6929
+ * of the same directory are still last-writer-wins, and the other edit is
6930
+ * lost: that takes a compare-and-swap at the server. Orphaned records (a
6931
+ * directory whose parent entry lost the race) are unreachable rather than
6932
+ * corrupting - they cost storage until a sweep, the trade this shape makes in
6779
6933
  * exchange for single-record writes.
6780
- *
6781
- * Within ONE tab, writes of a record are queued and sent one at a time, in the
6782
- * order they were made, and a read that has to fetch a record waits for its
6783
- * write in flight (see `writing`). Every edit of a record is made to one
6784
- * in-memory copy of it, and that copy is what is written (see `mutate`). All
6785
- * of this is per namespace instance only; nothing orders writes across tabs
6786
- * or devices - that would take a compare-and-swap at the server.
6787
6934
  */
6788
6935
 
6789
6936
  /**
@@ -6849,6 +6996,13 @@ interface VfsNamespaceDeps {
6849
6996
  entries?: number;
6850
6997
  pathChars?: number;
6851
6998
  };
6999
+ /**
7000
+ * Orders writes of a record with the other tabs of this app on the
7001
+ * device. `Client` passes one in a browser tab; without it - Node, a
7002
+ * test, a runtime with no Web Locks - writes are ordered within this
7003
+ * namespace only. See `VfsCoordinator`.
7004
+ */
7005
+ coordinator?: VfsCoordinator;
6852
7006
  }
6853
7007
  declare class VfsNamespace {
6854
7008
  private readonly deps;
@@ -6892,6 +7046,81 @@ declare class VfsNamespace {
6892
7046
  * `openDirById`.
6893
7047
  */
6894
7048
  private readonly writing;
7049
+ /**
7050
+ * Every entry this namespace is moving right now, by id.
7051
+ *
7052
+ * A move between two directories marks the entry before it puts it in
7053
+ * the second (see `rename`), and a mark is all a later operation can
7054
+ * see. It cannot tell a move still writing from one that was
7055
+ * interrupted, and was taken for the second: a delete that began after
7056
+ * a move had marked a folder looked for the folder's other half, found
7057
+ * none yet, and purged it while the move was putting it in its new
7058
+ * place; a second move of a file finished the "interrupted" move and
7059
+ * left it named in two places. So a move holds its entry here until it
7060
+ * has taken it from where it was, and `rename` and `delete` refuse an
7061
+ * entry that is here - or, for a delete, a folder holding one. A delete
7062
+ * holds what it deletes the same way: see `deleting`.
7063
+ *
7064
+ * This set is one namespace's. With a coordinator, a move also holds
7065
+ * the entry's move lock for every tab on the device, for as long as the
7066
+ * entry is here: see `VfsCoordinator.holdMoving`.
7067
+ */
7068
+ private readonly inMotion;
7069
+ /**
7070
+ * What each delete under way in this namespace is deleting, by entry id,
7071
+ * one set per delete: its target from the moment it has found the target
7072
+ * not in `inMotion`, and everything it read below the target once it has
7073
+ * read it. Each delete lets go of its own set when it is done.
7074
+ *
7075
+ * A delete waits after it has read what it will let go of: on more
7076
+ * reads, and on a read of the whole tree for the other half of a move.
7077
+ * A move of any of it that started meanwhile could put it in its new
7078
+ * place, fail to take it from the old, and end, all before the delete's
7079
+ * detach. Ended, it was no longer in `inMotion`; and an entry that was
7080
+ * marked already showed no change. So the delete let go of what the
7081
+ * move had just named somewhere else. Now `rename` refuses an entry that
7082
+ * is here, or one on a way from the root that passes through a folder
7083
+ * that is: everything below a delete's target, read yet or not.
7084
+ */
7085
+ private readonly deleting;
7086
+ /**
7087
+ * The write of each record that has not gone out yet, by id: the one
7088
+ * the next edit of that record joins. See `queue`.
7089
+ */
7090
+ private readonly waiting;
7091
+ /**
7092
+ * Announcements heard from other tabs, counted. `heardAt` is the count
7093
+ * at the last one for each record, `missedAt` the count when some may
7094
+ * have been missed. A copy read after both holds every write announced:
7095
+ * see `current`.
7096
+ */
7097
+ private hearing;
7098
+ private missedAt;
7099
+ private readonly heardAt;
7100
+ /**
7101
+ * Each record a write of which this namespace marked as begun and could
7102
+ * not count, and has said so, until one is counted. And whether it has
7103
+ * said that a write it could not mark or count may be lost, which it
7104
+ * says only once. See `notCounted`.
7105
+ */
7106
+ private readonly uncounted;
7107
+ private lossWarned;
7108
+ /**
7109
+ * The copy of each record this namespace is writing under the record's
7110
+ * lock, by id, from when it has decided what to write until it has
7111
+ * counted the write, or failed to. Its counter reads as unknown
7112
+ * meanwhile, as this write has begun and is not counted yet, but the
7113
+ * copy holds every write of the record another tab has counted, and no
7114
+ * other tab can make one until this one lets go of the lock. See
7115
+ * `dropStale`.
7116
+ */
7117
+ private readonly locked;
7118
+ /**
7119
+ * Each directory a `createDir` here has named whose write has not landed
7120
+ * yet, by entry id: a promise that settles - and never rejects - once
7121
+ * that write has landed or failed. See `mkdir`.
7122
+ */
7123
+ private readonly naming;
6895
7124
  private readonly historyLimit;
6896
7125
  private readonly maxEntries;
6897
7126
  private readonly maxPathChars;
@@ -7113,11 +7342,12 @@ declare class VfsNamespace {
7113
7342
  * Delete metadata records no longer reachable from the root.
7114
7343
  *
7115
7344
  * These accumulate because every write touches exactly one record: if two
7116
- * tabs or devices edit the same directory, the personal space keeps
7117
- * whichever put arrives last (a VFS write sends no HLC, and the space
7118
- * applies puts of one key in arrival order), which can leave a child whose
7119
- * parent entry lost the race. The per-record write queue (`writing`) only
7120
- * orders writes within one tab. Unreachable is harmless —
7345
+ * devices edit the same directory - or two tabs, where there is no
7346
+ * coordinator - the personal space keeps whichever put arrives last (a
7347
+ * VFS write sends no HLC, and the space applies puts of one key in arrival
7348
+ * order), which can leave a child whose parent entry lost the race. The
7349
+ * per-record queue and the coordinator order writes within one device
7350
+ * only. Unreachable is harmless —
7121
7351
  * it is invisible and undecryptable-by-nobody — but it costs storage, so
7122
7352
  * this reclaims it.
7123
7353
  *
@@ -7157,6 +7387,15 @@ declare class VfsNamespace {
7157
7387
  * the error names whichever asked first.
7158
7388
  */
7159
7389
  private openDirById;
7390
+ /**
7391
+ * Fetch record `id` from the store and open it. Whatever another tab
7392
+ * announced before the fetch began is in what it returns (see
7393
+ * `current`), and so is every write its counter counts: read here, or
7394
+ * given as `counter` (see `OpenDir.counter`).
7395
+ */
7396
+ private fetchRecord;
7397
+ /** Record `id`'s write counter now: see `OpenDir.counter`. */
7398
+ private counterOf;
7160
7399
  /**
7161
7400
  * Turn a decrypted record into an OpenDir, or throw.
7162
7401
  *
@@ -7179,8 +7418,18 @@ declare class VfsNamespace {
7179
7418
  *
7180
7419
  * So `edit` must take what it writes from the record it is given, not
7181
7420
  * from anything read off `dir` before an await - and re-check whatever it
7182
- * relies on from then, throwing if another write changed it. Returning
7183
- * false writes nothing after all.
7421
+ * relies on from then, throwing if another write changed it, before it
7422
+ * changes anything. Returning false writes nothing after all.
7423
+ *
7424
+ * The write that carries the edit may carry others, made while an
7425
+ * earlier write of the record was in flight (see `queue`): it lands, or
7426
+ * fails, for all of them at once.
7427
+ *
7428
+ * With a coordinator, the edit may be made a second time, to the record
7429
+ * as another tab left it, and refused then (see `replay`): an edit must
7430
+ * check everything before it changes anything, and whatever it sets
7431
+ * outside `node` and `record` must be safe to set again, since each run
7432
+ * overwrites the last.
7184
7433
  */
7185
7434
  private mutate;
7186
7435
  /**
@@ -7206,6 +7455,112 @@ declare class VfsNamespace {
7206
7455
  private reopen;
7207
7456
  /** Make `dir` the cached copy of its record - unless the cache has been cleared since it was read. */
7208
7457
  private adopt;
7458
+ /** After an edit of `copy`: its time, and any entry it cannot read that the edit replaced. */
7459
+ private touch;
7460
+ /**
7461
+ * The write that will carry the edit just made to `copy`.
7462
+ *
7463
+ * Writes of a record go out one at a time, each once the one before it
7464
+ * has landed or failed (see `writing`), and each seals the record as it
7465
+ * stands when it goes out. So a write still waiting carries every edit
7466
+ * made while it waits, and an edit made then joins it instead of
7467
+ * queueing a write of its own: a burst of edits of one directory while a
7468
+ * write of it is in flight costs two writes, not one per edit. Each is a
7469
+ * billed write; a 20-file upload into one folder cost twenty.
7470
+ */
7471
+ private queue;
7472
+ /**
7473
+ * Send `batch`: from here on, an edit of its record goes in the next
7474
+ * write. With a coordinator, holding the lock every tab of the device
7475
+ * takes to write this record - see `sendLocked`.
7476
+ */
7477
+ private send;
7478
+ /** Stop `batch` taking more edits, as it is sealed. */
7479
+ private close;
7480
+ /**
7481
+ * Send `batch` while no other tab can write its record.
7482
+ *
7483
+ * If another tab wrote the record since this copy was read, the copy is
7484
+ * out of date, and writing it would undo that tab's write. So it is read
7485
+ * again, here, under the lock, and every edit the batch carries is made
7486
+ * again to what was read (see `replay`). A copy no other tab has written
7487
+ * over is sent as it is: one tab on its own costs no extra request.
7488
+ *
7489
+ * Which it is, the record's write counter says (see
7490
+ * `VfsCoordinator.counters`). Read once the lock is granted, it counts
7491
+ * every write of the record another tab made, because each counts its
7492
+ * write, and waits for the count to be stored, before it lets go of the
7493
+ * lock. If it has moved since the copy was read, or cannot be told, the
7494
+ * copy is read again. Whether this tab has heard of another tab's write
7495
+ * does not decide it: an announcement can reach this tab after the
7496
+ * lock does, or never. One it has heard of can only add a read.
7497
+ *
7498
+ * The write is counted once it has landed or failed - one that failed
7499
+ * may have landed even so - and the other tabs are then told of it,
7500
+ * all before the lock is let go of.
7501
+ */
7502
+ private sendLocked;
7503
+ /**
7504
+ * Say that a write of `copy`'s record could not be counted.
7505
+ *
7506
+ * `marked`: `VfsCoordinator.begin` stored its mark, which stays until a
7507
+ * count can be stored, so every tab reads the record again before it
7508
+ * writes it. Said once for each record, until a write of it is counted.
7509
+ *
7510
+ * Not marked, a tab whose IndexedDB works may take its copy for current
7511
+ * and write it over this write. Said once for this namespace, whatever
7512
+ * the record: where IndexedDB cannot be used at all, no write of any
7513
+ * folder is counted, and a warning for each said nothing new. And only
7514
+ * where another tab can count its writes can one be lost, which the
7515
+ * warning says: where no tab can, every tab reads each folder again.
7516
+ */
7517
+ private notCounted;
7518
+ /**
7519
+ * Make every edit in `batch` again, in order, to `fresh` - the record as
7520
+ * another tab left it - and make that the copy. An edit that no longer
7521
+ * applies throws, as it would have the first time, and is left out; its
7522
+ * caller gets what it threw. Every edit checks before it changes
7523
+ * anything (see `mutate`), so one that throws has changed nothing. True
7524
+ * when what is left changes the record, and so needs writing.
7525
+ */
7526
+ private replay;
7527
+ /**
7528
+ * Another tab wrote record `id` - or, null, this namespace may have
7529
+ * missed some announcements. What the cache holds is read again when
7530
+ * next asked for, as a change frame from the personal space would have
7531
+ * it, and a copy read before then is read again before the next write
7532
+ * of its record (see `current`). A hint, not a guarantee: an
7533
+ * announcement can reach this tab after the next lock does, or never.
7534
+ * What decides whether a copy may be written over, or relied on under a
7535
+ * move's or a delete's locks, is the record's counter: see `sendLocked`
7536
+ * and `dropStale`.
7537
+ */
7538
+ private heard;
7539
+ /**
7540
+ * Does `copy` hold every write of its record another tab has announced
7541
+ * that this one has heard of? See `heard`. Only ever a reason to read it
7542
+ * again, never to take it for current: see `sendLocked`.
7543
+ */
7544
+ private current;
7545
+ /**
7546
+ * Drop from the cache every copy of a record in `ids` that may not hold
7547
+ * every write of it another tab has made: its counter has moved since
7548
+ * the copy was read, or cannot be told, or the copy is still being read.
7549
+ * Returns every id whose copy here is not known to be current - those,
7550
+ * and those not cached - so that the next read of each fetches it again,
7551
+ * once any write of it in flight here has landed or failed. Not a copy
7552
+ * this tab is writing under its lock (see `locked`): its counter reads
7553
+ * as unknown only because that write is not counted yet, and dropped,
7554
+ * the next read would wait for the write.
7555
+ *
7556
+ * The counters are read in one go, after the caller has taken the locks
7557
+ * it relies on: another tab counts each write of a record before it lets
7558
+ * go of the locks it held while writing it, the record's own and any
7559
+ * move lock, so what it wrote before letting go of one of them is
7560
+ * counted by then. Its announcements may not have reached this tab yet,
7561
+ * or ever. See `sendLocked`.
7562
+ */
7563
+ private dropStale;
7209
7564
  /**
7210
7565
  * Seal a directory record as it stands NOW and put it in the store.
7211
7566
  *
@@ -7230,8 +7585,44 @@ declare class VfsNamespace {
7230
7585
  *
7231
7586
  * `moving` says whether anything in it is marked as half of an
7232
7587
  * interrupted move, which is what makes a delete look for the other half.
7588
+ *
7589
+ * `ids` is every entry id in it, and `shape` what each record named when
7590
+ * it was read: see `currentSubtree`.
7233
7591
  */
7234
7592
  private subtree;
7593
+ /**
7594
+ * The records a recursive delete read, as they are now - or null when
7595
+ * one has changed since in a way that purging it would get wrong.
7596
+ *
7597
+ * The delete decided what to let go of, and whether to look for the
7598
+ * other half of a move, from what it read; then it waited, on reads and
7599
+ * on the write of its parent. A record that is no longer named by the
7600
+ * record it was read in has been moved out, and is not this delete's to
7601
+ * purge; an entry marked since it was read is being moved, and is about
7602
+ * to be named somewhere the delete never looked. Purging either purged
7603
+ * the folder a move had just put somewhere else. Anything else - a file
7604
+ * saved into one of them, or moved out - is purged as it now stands,
7605
+ * which is why the copies to purge are what this returns.
7606
+ *
7607
+ * Synchronous, for an edit. A record whose copy is not to hand counts as
7608
+ * changed.
7609
+ *
7610
+ * `held`, with a coordinator, is every entry whose move lock the delete
7611
+ * holds. An entry named since the records were read - saved or moved in
7612
+ * - is not among them, so nothing stops another tab moving it out while
7613
+ * the delete writes the parent, and purging it then let go of what that
7614
+ * move had put somewhere else. So with `held`, that counts as changed
7615
+ * too, and the delete is tried again from the start.
7616
+ */
7617
+ private currentSubtree;
7618
+ /**
7619
+ * Hold the move lock of every entry in `ids` - see
7620
+ * `VfsCoordinator.holdMoving` - or of none: null when another holder has
7621
+ * any of them, and then nothing is held. Otherwise the function that
7622
+ * lets go of them all. Asked for all at once: none of them waits.
7623
+ * `shared` holds them as the folders a move goes into.
7624
+ */
7625
+ private holdAll;
7235
7626
  /**
7236
7627
  * Delete these records and let go of every file in them. The second half
7237
7628
  * of a recursive delete, run only once the subtree is detached. Cannot
@@ -9102,7 +9493,7 @@ declare function getId(row: {
9102
9493
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
9103
9494
  * `package.json` `version`.
9104
9495
  */
9105
- declare const VERSION = "0.15.0-alpha.3";
9496
+ declare const VERSION = "0.15.0-alpha.4";
9106
9497
 
9107
9498
  /**
9108
9499
  * The SDK's process-global logger.