@muhkoo/connect 0.15.0-alpha.2 → 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. */
@@ -4335,8 +4370,12 @@ declare class KvCache {
4335
4370
  * state but a fresh local write wins.
4336
4371
  */
4337
4372
  mergeRemote(commitment: string, fullKey: string, envelope: unknown | null, op: "set" | "delete", hlc?: string): Promise<void>;
4338
- /** Non-tombstoned full keys for a commitment (used for offline `list`). */
4339
- liveKeys(commitment: string): Promise<string[]>;
4373
+ /**
4374
+ * Non-tombstoned full keys for a commitment that start with `prefix` (all
4375
+ * of them by default), for offline `list`. The store scans the key range
4376
+ * `${commitment}|${prefix}`, so other collections are never read.
4377
+ */
4378
+ liveKeys(commitment: string, prefix?: string): Promise<string[]>;
4340
4379
  /** Queue a kv mutation for replay on reconnect. */
4341
4380
  enqueue(method: "set" | "delete", args: unknown, hlc: string, clientId: string): Promise<void>;
4342
4381
  }
@@ -4348,6 +4387,7 @@ declare class KvCache {
4348
4387
  * await client.kv.set('todos', id, { title, completed })
4349
4388
  * const todo = await client.kv.get('todos', id)
4350
4389
  * const ids = await client.kv.list('todos')
4390
+ * const keys = await client.kv.keys('todos/') // full keys, any prefix
4351
4391
  * await client.kv.delete('todos', id)
4352
4392
  * const off = client.kv.on('change', e => …) // realtime, cross-device
4353
4393
  *
@@ -4399,8 +4439,23 @@ declare class KvNamespace {
4399
4439
  get<T = unknown>(collection: string, id: string): Promise<T | null>;
4400
4440
  /** Delete `collection`/`id`. Returns whether the key existed. */
4401
4441
  delete(collection: string, id: string): Promise<boolean>;
4402
- /** List the ids present in `collection`. */
4442
+ /**
4443
+ * List the ids present in `collection`. The server filters by
4444
+ * `${collection}/` and pages; values are not fetched.
4445
+ */
4403
4446
  list(collection: string): Promise<string[]>;
4447
+ /**
4448
+ * Every key in the signed-in user's personal space that starts with
4449
+ * `prefix` (all of them when it is ""), as full keys: `todos/t1`, not
4450
+ * `t1`. Values are not fetched. The personal space is shared by every
4451
+ * app the user signs into, so expect keys you did not write: the VFS's
4452
+ * `vfs/…`, version control's `vcs/…`, the SDK's own `chat-keys` and
4453
+ * `space-keys/…`.
4454
+ *
4455
+ * The local `startsWith` filter stays on purpose: a server older than
4456
+ * the key index ignores `prefix` and returns every key.
4457
+ */
4458
+ keys(prefix?: string): Promise<string[]>;
4404
4459
  /** Reserved — server-side query is deferred (see the overhaul plan). */
4405
4460
  query(_collection: string, _filter?: unknown): Promise<never>;
4406
4461
  /**
@@ -6664,8 +6719,14 @@ interface VfsStore {
6664
6719
  get(key: string): Promise<unknown | null>;
6665
6720
  put(key: string, value: unknown): Promise<void>;
6666
6721
  delete(key: string): Promise<void>;
6667
- /** Every key in the store — the personal space is shared, so this is NOT VFS-only. */
6668
- list(): Promise<string[]>;
6722
+ /**
6723
+ * Keys in the store that start with `prefix`, or every key without one.
6724
+ * The personal space is shared, so this is NOT VFS-only. An implementation
6725
+ * may ignore `prefix` and return more (the parameter is optional so that
6726
+ * a store written for the old no-argument `list()` still fits), so callers
6727
+ * filter what they get back.
6728
+ */
6729
+ list(prefix?: string): Promise<string[]>;
6669
6730
  }
6670
6731
  /** Raised when the master seed is not held, so the root key cannot be derived. */
6671
6732
  declare class VfsLockedError extends Error {
@@ -6731,6 +6792,114 @@ declare class VfsLimitError extends Error {
6731
6792
  constructor(limit: VfsLimitError["limit"], max: number, path: string, at: string, doing?: string);
6732
6793
  }
6733
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
+
6734
6903
  /**
6735
6904
  * `client.vfs` — a real filesystem over Muhkoo primitives.
6736
6905
  *
@@ -6743,20 +6912,25 @@ declare class VfsLimitError extends Error {
6743
6912
  *
6744
6913
  * Every mutation is a read-modify-write of exactly ONE directory record, so a
6745
6914
  * write cannot leave the tree half-updated: either the parent names the new
6746
- * child or it does not. Two tabs writing different directories never conflict;
6747
- * two tabs (or devices) writing the SAME directory are last-writer-wins. A VFS
6748
- * write sends no HLC and the personal space has no compare-and-swap: it applies
6749
- * puts of one key in the order they arrive, so whichever arrives last is kept,
6750
- * and the other tab's edit is lost. Orphaned records (a directory whose parent
6751
- * entry lost the race) are unreachable rather than corrupting — they cost
6752
- * 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
6753
6933
  * exchange for single-record writes.
6754
- *
6755
- * Within ONE tab, writes of a record are queued and sent one at a time, in the
6756
- * order they were made, and a read that has to fetch a record waits for its
6757
- * write in flight (see `writing`). That ordering is per namespace instance
6758
- * only; nothing orders writes across tabs or devices - that would take a
6759
- * compare-and-swap at the server.
6760
6934
  */
6761
6935
 
6762
6936
  /**
@@ -6822,6 +6996,13 @@ interface VfsNamespaceDeps {
6822
6996
  entries?: number;
6823
6997
  pathChars?: number;
6824
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;
6825
7006
  }
6826
7007
  declare class VfsNamespace {
6827
7008
  private readonly deps;
@@ -6832,8 +7013,25 @@ declare class VfsNamespace {
6832
7013
  * The file tree re-lists constantly while typing, and every miss is a
6833
7014
  * network round trip plus a decrypt. Invalidated precisely on write rather
6834
7015
  * than by TTL — a stale directory listing shows files that are not there.
7016
+ *
7017
+ * What a cached read resolves to is also THE copy of that record: the one
7018
+ * every edit of it is made to, and the one written. See `mutate`.
6835
7019
  */
6836
7020
  private cache;
7021
+ /**
7022
+ * Bumped by `clearCache`, which is for a change of identity.
7023
+ *
7024
+ * A read or write begun before it can finish after it, and what it read
7025
+ * or wrote belongs to whoever was signed in before. So every record
7026
+ * carries the generation it was read under, and none goes into the cache,
7027
+ * or is edited as the cache's copy, under another.
7028
+ */
7029
+ private generation;
7030
+ /**
7031
+ * The record each promise in `cache` resolved to, and each write in
7032
+ * `writing` is sending: what `editable` finds without waiting.
7033
+ */
7034
+ private readonly copyOf;
6837
7035
  /**
6838
7036
  * The write of each directory record still in flight, by id.
6839
7037
  *
@@ -6848,6 +7046,81 @@ declare class VfsNamespace {
6848
7046
  * `openDirById`.
6849
7047
  */
6850
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;
6851
7124
  private readonly historyLimit;
6852
7125
  private readonly maxEntries;
6853
7126
  private readonly maxPathChars;
@@ -7069,11 +7342,12 @@ declare class VfsNamespace {
7069
7342
  * Delete metadata records no longer reachable from the root.
7070
7343
  *
7071
7344
  * These accumulate because every write touches exactly one record: if two
7072
- * tabs or devices edit the same directory, the personal space keeps
7073
- * whichever put arrives last (a VFS write sends no HLC, and the space
7074
- * applies puts of one key in arrival order), which can leave a child whose
7075
- * parent entry lost the race. The per-record write queue (`writing`) only
7076
- * 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 —
7077
7351
  * it is invisible and undecryptable-by-nobody — but it costs storage, so
7078
7352
  * this reclaims it.
7079
7353
  *
@@ -7113,6 +7387,15 @@ declare class VfsNamespace {
7113
7387
  * the error names whichever asked first.
7114
7388
  */
7115
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;
7116
7399
  /**
7117
7400
  * Turn a decrypted record into an OpenDir, or throw.
7118
7401
  *
@@ -7121,8 +7404,163 @@ declare class VfsNamespace {
7121
7404
  * aside in `hidden` instead: out of every read, but kept for the write.
7122
7405
  */
7123
7406
  private readRecord;
7124
- /** Apply an edit to a directory record and persist it. */
7407
+ /**
7408
+ * Apply an edit to a directory record and persist it.
7409
+ *
7410
+ * `dir` says which record - its id, key and path - and nothing more. The
7411
+ * edit is made to THE copy of that record (see `editable`), which `dir`
7412
+ * may no longer be, and that copy is what is written. Editing whichever
7413
+ * copy the caller held let two copies of one record exist at once: an
7414
+ * overwrite held its directory across storing the old version's
7415
+ * history, a write of the directory failed meanwhile and dropped it from
7416
+ * the cache, and a save that then read a second copy and stored it,
7417
+ * successfully, was undone when the overwrite stored the first.
7418
+ *
7419
+ * So `edit` must take what it writes from the record it is given, not
7420
+ * from anything read off `dir` before an await - and re-check whatever it
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.
7433
+ */
7125
7434
  private mutate;
7435
+ /**
7436
+ * The copy of `dir`'s record that edits are made to, if there is one
7437
+ * without waiting: the one the cache holds, once read.
7438
+ *
7439
+ * Failing that, the one a write in flight is sending. The record was
7440
+ * dropped from the cache - a change was reported, our own write's echo
7441
+ * among them - and a read that came in since is waiting for that write,
7442
+ * to answer with this very copy if it lands (see `openDirById`); if it
7443
+ * fails, the write an edit now queues behind it carries the copy instead.
7444
+ *
7445
+ * Undefined when the record is not cached and nothing is writing it, or
7446
+ * it is still being read: `reopen` waits for that. A record read before
7447
+ * `clearCache` is edited as it is, as it always was. It is not the copy of
7448
+ * anything the cache holds now, and must not be handed the next identity's.
7449
+ */
7450
+ private editable;
7451
+ /**
7452
+ * Read `dir`'s record - the cached read, or a fetch made once any write of
7453
+ * it in flight has settled - and then find the copy edits are made to.
7454
+ */
7455
+ private reopen;
7456
+ /** Make `dir` the cached copy of its record - unless the cache has been cleared since it was read. */
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;
7126
7564
  /**
7127
7565
  * Seal a directory record as it stands NOW and put it in the store.
7128
7566
  *
@@ -7131,6 +7569,7 @@ declare class VfsNamespace {
7131
7569
  * then, its own included.
7132
7570
  */
7133
7571
  private writeRecord;
7572
+ /** For `mkdir`: name a new, empty directory `path` in the one at `parentPath`. */
7134
7573
  private createDir;
7135
7574
  /**
7136
7575
  * Every directory record in a subtree, each once, opened - and nothing
@@ -7146,8 +7585,44 @@ declare class VfsNamespace {
7146
7585
  *
7147
7586
  * `moving` says whether anything in it is marked as half of an
7148
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`.
7149
7591
  */
7150
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;
7151
7626
  /**
7152
7627
  * Delete these records and let go of every file in them. The second half
7153
7628
  * of a recursive delete, run only once the subtree is detached. Cannot
@@ -9018,7 +9493,7 @@ declare function getId(row: {
9018
9493
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
9019
9494
  * `package.json` `version`.
9020
9495
  */
9021
- declare const VERSION = "0.15.0-alpha.2";
9496
+ declare const VERSION = "0.15.0-alpha.4";
9022
9497
 
9023
9498
  /**
9024
9499
  * The SDK's process-global logger.