1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/**
* Sync Lock Module
*
* Coordinates concurrent sync calls using the Web Locks API.
*
* Behavior:
* - Same-method coalescing: if a sync of the same method is in progress,
* subsequent callers share its result promise
* - Different-method serialization: different methods (e.g. syncState vs
* syncNoteTransport) wait for each other via the Web Lock, or via an
* in-process per-dbId promise chain when Web Locks are unavailable
* - Web Locks also serialize across tabs (Chrome 69+, Safari 15.4+)
* - `WebClient._withStoreLock` in `index.js` takes the same lock, through
* `runUnderLock` and `coalesceRun`, for every call that writes the chain MMR.
* It coalesces in a registry of its own client, so a sync joins only a
* pending run of the same sync on that client, never another client's, and
* only while no other call has been queued on that client since the run
*/
/**
* Check if the Web Locks API is available.
*/
export
// Coalesce map keyed by `${dbId}:${methodId}` -> in-flight promise, used by
// `withSyncLock`. A WebClient passes `coalesceRun` a map of its own instead.
const inFlight = ;
// The `epoch()` each run registered with one was started in; see `coalesceRun`.
const startedIn = ;
// Per-dbId promise tail used to serialize cross-method calls when Web Locks
// are unavailable. Each new task chains onto the current tail so different
// methods on the same dbId run sequentially within the tab.
const fallbackTails = ;
/**
* Build the coalesce-map key for an in-flight sync of `(dbId, methodId)`.
*
* @param {string} dbId
* @param {string} methodId
* @returns {string}
*/
/**
* Run `fn` while holding the per-db Web Lock. When Web Locks are unavailable,
* serializes `fn` against any other in-flight call on the same `dbId` via an
* in-process promise chain — the wasm-bindgen `WebClient` uses a synchronous
* `RefCell` for interior mutability in the browser, so overlapping
* cross-method borrows would throw "recursive use of an object detected
* which would lead to unsafe aliasing in rust".
*
* Every call runs its own `fn`, queued behind the current holder; see
* `withSyncLock` for the coalescing variant.
*
* @param {string} dbId
* @param {() => Promise<T>} fn
* @returns {Promise<T>}
* @template T
*/
export
/**
* Share one in-flight run of (dbId, methodId) among concurrent callers.
*
* The first caller's `start()` begins the run, and it stays registered in
* `registry` until it settles; a caller arriving meanwhile with the same
* registry gets the same promise and starts nothing. With `epoch`, a caller
* joins only while `epoch()` still returns what it returned once the run had
* started; otherwise it starts a run of its own, which replaces that one in
* the registry. `start` takes the lock itself, if it needs one.
*
* @param {string} dbId - Database ID
* @param {string} methodId - Method identifier (see MethodName constants)
* @param {() => Promise<T>} start - Begins the run
* @param {Map<string, Promise<T>>} [registry] - The runs callers can join.
* Defaults to the module-wide one `withSyncLock` uses.
* @param {() => number} [epoch] - Moves once a joiner would no longer settle
* after every call made before it. Omitted, a run is joinable until it
* settles.
* @returns {Promise<T>}
* @template T
*/
export
/**
* Run `fn` under the sync lock for (dbId, methodId).
*
* Concurrent calls with the same (dbId, methodId) share the same promise
* (coalescing). Concurrent calls on the same dbId with different methodIds
* serialize via the Web Lock.
*
* It is the store lock a `WebClient` takes on the store named `dbId`, and it
* is not reentrant: `fn` must not wait, directly or through any lock or
* queue, for anything that waits for a call on a client of that store.
*
* - Any call on such a client, not only its store-locked calls (`syncState`,
* `syncChain`, `syncNoteTransport`, `importNoteFile` or `notes.import`,
* `fetchPrivateNotes` or `notes.fetchPrivate`, and
* `syncBeforeAccountCreation`, `newWallet`, `newFaucet`, `newAccount` and
* `newAccountWithSecretKey`, which request and wait for the lock only on a
* store still at genesis, the creations holding it only for the sync they
* run first, so no application callback such as an external keystore's
* `insertKey` runs under it). A store-locked call takes its place on its
* client's call chain first and requests the lock inside it, so one already
* queued on that client, an auto-sync for example, holds the chain while it
* waits for the lock `fn` holds, and every later call on that client queues
* behind it.
* - Any lock whose holder is waiting on such a call, such as
* `@miden-sdk/react`'s `runExclusive`, which `MidenProvider` holds across
* its init sync, or an application mutex around `_withInnerWebClient`.
*
* Every client on the store then stops.
*
* @param {string} dbId - Database ID
* @param {string} methodId - Method identifier (see MethodName constants)
* @param {() => Promise<T>} fn - Work to run under the lock
* @returns {Promise<T>}
*/
export