@brass-build/client 0.4.1 → 0.5.0
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/AGENTS.md +124 -45
- package/CHANGELOG.md +80 -4
- package/README.md +666 -179
- package/dist/api-fetch.d.ts +8 -0
- package/dist/api-fetch.d.ts.map +1 -1
- package/dist/api-fetch.js +35 -8
- package/dist/api-fetch.js.map +1 -1
- package/dist/batched-get.d.ts +11 -0
- package/dist/batched-get.d.ts.map +1 -0
- package/dist/batched-get.js +92 -0
- package/dist/batched-get.js.map +1 -0
- package/dist/call-service.d.ts +4 -0
- package/dist/call-service.d.ts.map +1 -1
- package/dist/call-service.js +66 -3
- package/dist/call-service.js.map +1 -1
- package/dist/client.d.ts +48 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +918 -115
- package/dist/client.js.map +1 -1
- package/dist/document-checkpoints.d.ts +25 -2
- package/dist/document-checkpoints.d.ts.map +1 -1
- package/dist/document-checkpoints.js +258 -61
- package/dist/document-checkpoints.js.map +1 -1
- package/dist/document-commands.d.ts.map +1 -1
- package/dist/document-commands.js +5 -3
- package/dist/document-commands.js.map +1 -1
- package/dist/document-contents.d.ts +10 -0
- package/dist/document-contents.d.ts.map +1 -1
- package/dist/document-contents.js +23 -1
- package/dist/document-contents.js.map +1 -1
- package/dist/document-set-subscription.d.ts +56 -0
- package/dist/document-set-subscription.d.ts.map +1 -0
- package/dist/document-set-subscription.js +240 -0
- package/dist/document-set-subscription.js.map +1 -0
- package/dist/document-signals.d.ts +6 -0
- package/dist/document-signals.d.ts.map +1 -1
- package/dist/document-signals.js +31 -1
- package/dist/document-signals.js.map +1 -1
- package/dist/document-streams.d.ts +36 -2
- package/dist/document-streams.d.ts.map +1 -1
- package/dist/document-streams.js +69 -18
- package/dist/document-streams.js.map +1 -1
- package/dist/document-thumbnail-capture.d.ts +12 -2
- package/dist/document-thumbnail-capture.d.ts.map +1 -1
- package/dist/document-thumbnail-capture.js +103 -27
- package/dist/document-thumbnail-capture.js.map +1 -1
- package/dist/documents-namespace.d.ts +92 -10
- package/dist/documents-namespace.d.ts.map +1 -1
- package/dist/documents-namespace.js +285 -39
- package/dist/documents-namespace.js.map +1 -1
- package/dist/documents.d.ts +173 -8
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js +1778 -310
- package/dist/documents.js.map +1 -1
- package/dist/errors.d.ts +3 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fetch-retry.d.ts +1 -0
- package/dist/fetch-retry.d.ts.map +1 -1
- package/dist/fetch-retry.js +2 -0
- package/dist/fetch-retry.js.map +1 -1
- package/dist/file-choice.d.ts +2 -0
- package/dist/file-choice.d.ts.map +1 -0
- package/dist/file-choice.js +27 -0
- package/dist/file-choice.js.map +1 -0
- package/dist/guest-link.d.ts +18 -1
- package/dist/guest-link.d.ts.map +1 -1
- package/dist/guest-link.js +329 -1
- package/dist/guest-link.js.map +1 -1
- package/dist/handle-types.d.ts +67 -5
- package/dist/handle-types.d.ts.map +1 -1
- package/dist/handles.d.ts +15 -19
- package/dist/handles.d.ts.map +1 -1
- package/dist/handles.js +231 -76
- package/dist/handles.js.map +1 -1
- package/dist/hosted-ui.d.ts +61 -0
- package/dist/hosted-ui.d.ts.map +1 -1
- package/dist/hosted-ui.js +94 -42
- package/dist/hosted-ui.js.map +1 -1
- package/dist/import-contract.d.ts +74 -0
- package/dist/import-contract.d.ts.map +1 -0
- package/dist/import-contract.js +163 -0
- package/dist/import-contract.js.map +1 -0
- package/dist/import-guest.d.ts +9 -0
- package/dist/import-guest.d.ts.map +1 -0
- package/dist/import-guest.js +155 -0
- package/dist/import-guest.js.map +1 -0
- package/dist/import-report.d.ts +17 -0
- package/dist/import-report.d.ts.map +1 -0
- package/dist/import-report.js +7 -0
- package/dist/import-report.js.map +1 -0
- package/dist/importer-wait.d.ts +2 -0
- package/dist/importer-wait.d.ts.map +1 -0
- package/dist/importer-wait.js +5 -0
- package/dist/importer-wait.js.map +1 -0
- package/dist/importer.d.ts +18 -4
- package/dist/importer.d.ts.map +1 -1
- package/dist/importer.js +322 -105
- package/dist/importer.js.map +1 -1
- package/dist/in-development.d.ts +2 -0
- package/dist/in-development.d.ts.map +1 -0
- package/dist/in-development.js +10 -0
- package/dist/in-development.js.map +1 -0
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -8
- package/dist/index.js.map +1 -1
- package/dist/init-keys.d.ts +1 -1
- package/dist/init-keys.d.ts.map +1 -1
- package/dist/init-keys.js +6 -1
- package/dist/init-keys.js.map +1 -1
- package/dist/input-documents.d.ts +67 -0
- package/dist/input-documents.d.ts.map +1 -0
- package/dist/input-documents.js +693 -0
- package/dist/input-documents.js.map +1 -0
- package/dist/inputs-status.d.ts +80 -0
- package/dist/inputs-status.d.ts.map +1 -0
- package/dist/inputs-status.js +460 -0
- package/dist/inputs-status.js.map +1 -0
- package/dist/inputs.d.ts +2 -0
- package/dist/inputs.d.ts.map +1 -0
- package/dist/inputs.js +793 -0
- package/dist/inputs.js.map +1 -0
- package/dist/internal.d.ts +6 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +4 -1
- package/dist/internal.js.map +1 -1
- package/dist/join-content.d.ts +33 -0
- package/dist/join-content.d.ts.map +1 -0
- package/dist/join-content.js +114 -0
- package/dist/join-content.js.map +1 -0
- package/dist/join-stamp.d.ts +3 -0
- package/dist/join-stamp.d.ts.map +1 -0
- package/dist/join-stamp.js +12 -0
- package/dist/join-stamp.js.map +1 -0
- package/dist/manifest-drift.d.ts +9 -0
- package/dist/manifest-drift.d.ts.map +1 -0
- package/dist/manifest-drift.js +46 -0
- package/dist/manifest-drift.js.map +1 -0
- package/dist/open-document.d.ts +109 -2
- package/dist/open-document.d.ts.map +1 -1
- package/dist/open-document.js +1822 -811
- package/dist/open-document.js.map +1 -1
- package/dist/page-restore.d.ts +12 -0
- package/dist/page-restore.d.ts.map +1 -0
- package/dist/page-restore.js +23 -0
- package/dist/page-restore.js.map +1 -0
- package/dist/placement.d.ts +13 -0
- package/dist/placement.d.ts.map +1 -0
- package/dist/placement.js +34 -0
- package/dist/placement.js.map +1 -0
- package/dist/presence.d.ts +1 -0
- package/dist/presence.d.ts.map +1 -1
- package/dist/presence.js +16 -4
- package/dist/presence.js.map +1 -1
- package/dist/read-only-document.d.ts +87 -0
- package/dist/read-only-document.d.ts.map +1 -0
- package/dist/read-only-document.js +4 -0
- package/dist/read-only-document.js.map +1 -0
- package/dist/record-paging.d.ts +63 -6
- package/dist/record-paging.d.ts.map +1 -1
- package/dist/record-paging.js +122 -29
- package/dist/record-paging.js.map +1 -1
- package/dist/references.d.ts +40 -0
- package/dist/references.d.ts.map +1 -0
- package/dist/references.js +135 -0
- package/dist/references.js.map +1 -0
- package/dist/request-lane.d.ts +5 -0
- package/dist/request-lane.d.ts.map +1 -0
- package/dist/request-lane.js +48 -0
- package/dist/request-lane.js.map +1 -0
- package/dist/sealed-arrow.d.ts +170 -0
- package/dist/sealed-arrow.d.ts.map +1 -0
- package/dist/sealed-arrow.js +769 -0
- package/dist/sealed-arrow.js.map +1 -0
- package/dist/sealed-cache.d.ts +21 -0
- package/dist/sealed-cache.d.ts.map +1 -0
- package/dist/sealed-cache.js +247 -0
- package/dist/sealed-cache.js.map +1 -0
- package/dist/sealed-encode.d.ts +87 -0
- package/dist/sealed-encode.d.ts.map +1 -0
- package/dist/sealed-encode.js +876 -0
- package/dist/sealed-encode.js.map +1 -0
- package/dist/sealed-handles.d.ts +45 -0
- package/dist/sealed-handles.d.ts.map +1 -0
- package/dist/sealed-handles.js +408 -0
- package/dist/sealed-handles.js.map +1 -0
- package/dist/sealed-store.d.ts +242 -0
- package/dist/sealed-store.d.ts.map +1 -0
- package/dist/sealed-store.js +1155 -0
- package/dist/sealed-store.js.map +1 -0
- package/dist/serializer.d.ts +6 -15
- package/dist/serializer.d.ts.map +1 -1
- package/dist/session-toolbar.d.ts +20 -5
- package/dist/session-toolbar.d.ts.map +1 -1
- package/dist/session-toolbar.js +419 -67
- package/dist/session-toolbar.js.map +1 -1
- package/dist/session.d.ts +38 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +562 -79
- package/dist/session.js.map +1 -1
- package/dist/sha256.d.ts +3 -0
- package/dist/sha256.d.ts.map +1 -0
- package/dist/sha256.js +74 -0
- package/dist/sha256.js.map +1 -0
- package/dist/stable-handles.d.ts +19 -0
- package/dist/stable-handles.d.ts.map +1 -0
- package/dist/stable-handles.js +193 -0
- package/dist/stable-handles.js.map +1 -0
- package/dist/staged-seal.d.ts +2 -0
- package/dist/staged-seal.d.ts.map +1 -0
- package/dist/staged-seal.js +76 -0
- package/dist/staged-seal.js.map +1 -0
- package/dist/subscriber-delivery.d.ts +8 -0
- package/dist/subscriber-delivery.d.ts.map +1 -0
- package/dist/subscriber-delivery.js +31 -0
- package/dist/subscriber-delivery.js.map +1 -0
- package/dist/tail-op-import.d.ts +10 -0
- package/dist/tail-op-import.d.ts.map +1 -0
- package/dist/tail-op-import.js +33 -0
- package/dist/tail-op-import.js.map +1 -0
- package/dist/topic-lifecycle.d.ts +6 -0
- package/dist/topic-lifecycle.d.ts.map +1 -1
- package/dist/topic-lifecycle.js +32 -2
- package/dist/topic-lifecycle.js.map +1 -1
- package/dist/trial-replace-dialog.d.ts +10 -0
- package/dist/trial-replace-dialog.d.ts.map +1 -0
- package/dist/trial-replace-dialog.js +153 -0
- package/dist/trial-replace-dialog.js.map +1 -0
- package/dist/types.d.ts +8 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/undo.d.ts +60 -5
- package/dist/undo.d.ts.map +1 -1
- package/dist/undo.js +344 -75
- package/dist/undo.js.map +1 -1
- package/dist/version-bar.d.ts +1 -0
- package/dist/version-bar.d.ts.map +1 -1
- package/dist/version-bar.js +2 -1
- package/dist/version-bar.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/dist/ws-client.d.ts +21 -0
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +192 -8
- package/dist/ws-client.js.map +1 -1
- package/dist/ws-frame-limit.d.ts +1 -0
- package/dist/ws-frame-limit.d.ts.map +1 -1
- package/dist/ws-frame-limit.js.map +1 -1
- package/docs/app-manifest.md +121 -17
- package/docs/custom-chrome.md +24 -16
- package/docs/hosted-ui-advanced.md +38 -6
- package/docs/imported-documents.md +104 -51
- package/docs/presence.md +16 -4
- package/docs/schemas.md +8 -5
- package/docs/services.md +6 -6
- package/docs/streams.md +139 -150
- package/docs/text-fields.md +26 -19
- package/package.json +5 -2
- package/src/api-fetch.ts +53 -3
- package/src/auth-flow.ts +1 -1
- package/src/batched-get.ts +117 -0
- package/src/call-service.ts +78 -3
- package/src/client.ts +1022 -112
- package/src/document-checkpoints.ts +307 -65
- package/src/document-commands.ts +5 -5
- package/src/document-contents.ts +27 -1
- package/src/document-set-subscription.ts +274 -0
- package/src/document-signals.ts +47 -5
- package/src/document-streams.ts +85 -16
- package/src/document-thumbnail-capture.ts +118 -32
- package/src/documents-namespace.ts +539 -84
- package/src/documents.ts +2017 -348
- package/src/errors.ts +17 -6
- package/src/fetch-retry.ts +5 -0
- package/src/file-choice.ts +26 -0
- package/src/guest-link.ts +362 -1
- package/src/handle-types.ts +73 -47
- package/src/handles.ts +253 -83
- package/src/hosted-ui.ts +211 -50
- package/src/import-contract.ts +227 -0
- package/src/import-guest.ts +193 -0
- package/src/import-report.ts +24 -0
- package/src/importer-wait.ts +4 -0
- package/src/importer.ts +386 -131
- package/src/in-development.ts +10 -0
- package/src/index.ts +76 -16
- package/src/init-keys.ts +6 -1
- package/src/input-documents.ts +819 -0
- package/src/inputs-status.ts +598 -0
- package/src/inputs.ts +1090 -0
- package/src/internal.ts +7 -0
- package/src/join-content.ts +135 -0
- package/src/join-stamp.ts +12 -0
- package/src/manifest-drift.ts +54 -0
- package/src/open-document.ts +2346 -930
- package/src/page-restore.ts +38 -0
- package/src/placement.ts +54 -0
- package/src/presence.ts +21 -5
- package/src/read-only-document.ts +133 -0
- package/src/record-paging.ts +147 -25
- package/src/references.ts +207 -0
- package/src/request-lane.ts +59 -0
- package/src/sealed-arrow.ts +843 -0
- package/src/sealed-cache.ts +267 -0
- package/src/sealed-encode.ts +1015 -0
- package/src/sealed-handles.ts +486 -0
- package/src/sealed-store.ts +1330 -0
- package/src/serializer.ts +8 -20
- package/src/session-toolbar.ts +463 -72
- package/src/session.ts +740 -99
- package/src/sha256.ts +65 -0
- package/src/stable-handles.ts +235 -0
- package/src/staged-seal.ts +90 -0
- package/src/subscriber-delivery.ts +37 -0
- package/src/tail-op-import.ts +30 -0
- package/src/topic-lifecycle.ts +38 -2
- package/src/trial-replace-dialog.ts +163 -0
- package/src/types.ts +6 -6
- package/src/undo.ts +386 -73
- package/src/version-bar.ts +5 -1
- package/src/version.ts +1 -1
- package/src/ws-client.ts +208 -16
- package/src/ws-frame-limit.ts +1 -0
package/dist/handles.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
// Engine-neutral: Loro events are translated to a minimal `Change` shape at
|
|
6
6
|
// the handle boundary, so handle-only app code does not pin the SDK to Loro.
|
|
7
7
|
import { LoroMovableList, LoroMap, isContainer, } from './wasm-runtime.js';
|
|
8
|
-
import { PAGE_TAG, UNPAGED_MAX_RECORDS, PAGE_KEYS_FIELD, assertUsableRecordKey, classifyStoreKey, decodePageKeys, decodePageRecords, decodeRecordValue, encodePage, flattenPagedLookup, isReservedKey, overlayKeyFor, packWholeValue, parseSessionPageKey, repackRecord, parsePatchKey, patchKeyFor, applyRecordPatches, recordsPerPage, sampleBodyBytes, sessionPageKey, tombstoneKeyFor, unpackRecord, } from './record-paging.js';
|
|
8
|
+
import { PAGE_TAG, UNPAGED_MAX_RECORDS, PAGE_KEYS_FIELD, BODY_FIELD, TAG_FIELD, assertUsableRecordKey, classifyStoreKey, decodePageKeys, decodePageRecords, decodeRecordValue, encodePage, flattenPagedLookup, isPagedMarkerEntry, isReservedKey, overlayKeyFor, packWholeValue, parseSessionPageKey, repackRecord, parsePatchKey, patchKeyFor, applyRecordPatches, recordsPerPage, sampleBodyBytes, sessionPageKey, tombstoneKeyFor, unpackRecord, } from './record-paging.js';
|
|
9
9
|
import { MutateBatcher } from './mutate-batching.js';
|
|
10
|
+
import { pushBoundedSplice } from './subscriber-delivery.js';
|
|
10
11
|
// ---- Plain-JS-proxy machinery, scoped per-container --------------------
|
|
11
12
|
function isPlainObject(v) {
|
|
12
13
|
return (v !== null &&
|
|
@@ -153,19 +154,41 @@ function assignList(target, value) {
|
|
|
153
154
|
// same position, so the pair leaves the entry stored twice while `set`
|
|
154
155
|
// leaves it stored once. That is the same reasoning `assignListPosition`
|
|
155
156
|
// carries, and a whole-array assign is the write that reaches it most.
|
|
157
|
+
//
|
|
158
|
+
// The entries both ends share are left alone, and only the span between
|
|
159
|
+
// them is rewritten. Dropping the first of `[a, b]` is then a delete of `a`
|
|
160
|
+
// rather than `b` set into position 0 and the tail cut off, which matters
|
|
161
|
+
// when a peer undoes its own edit to `b` concurrently: the undo removes the
|
|
162
|
+
// `b` it wrote, and a set of `b` would put one back.
|
|
156
163
|
const oldLen = target.length;
|
|
157
|
-
|
|
164
|
+
let head = 0;
|
|
165
|
+
while (head < oldLen &&
|
|
166
|
+
head < value.length &&
|
|
167
|
+
samePlainValue(plainListItem(target, head), materializeContainers(value[head]))) {
|
|
168
|
+
head += 1;
|
|
169
|
+
}
|
|
170
|
+
let tail = 0;
|
|
171
|
+
while (tail < oldLen - head &&
|
|
172
|
+
tail < value.length - head &&
|
|
173
|
+
samePlainValue(plainListItem(target, oldLen - 1 - tail), materializeContainers(value[value.length - 1 - tail]))) {
|
|
174
|
+
tail += 1;
|
|
175
|
+
}
|
|
176
|
+
const oldMid = oldLen - head - tail;
|
|
177
|
+
const newMid = value.length - head - tail;
|
|
178
|
+
const shared = Math.min(oldMid, newMid);
|
|
179
|
+
for (let i = head; i < head + shared; i++) {
|
|
158
180
|
const item = materializeContainers(value[i]);
|
|
159
|
-
if (i >= oldLen) {
|
|
160
|
-
insertListAt(target, i, item);
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
181
|
if (samePlainValue(plainListItem(target, i), item))
|
|
164
182
|
continue;
|
|
165
183
|
assignListPosition(target, i, item);
|
|
166
184
|
}
|
|
167
|
-
if (
|
|
168
|
-
|
|
185
|
+
if (newMid > oldMid) {
|
|
186
|
+
for (let i = head + oldMid; i < head + newMid; i++) {
|
|
187
|
+
insertListAt(target, i, materializeContainers(value[i]));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else if (oldMid > newMid) {
|
|
191
|
+
target.delete(head + newMid, oldMid - newMid);
|
|
169
192
|
}
|
|
170
193
|
}
|
|
171
194
|
/** Whether a position already holds this value, so assigning it writes
|
|
@@ -212,12 +235,21 @@ function insertListTail(target, rawItem) {
|
|
|
212
235
|
// stored twice, and `set` leaves it stored once.
|
|
213
236
|
function assignListPosition(target, idx, rawItem) {
|
|
214
237
|
const item = materializeContainers(rawItem);
|
|
238
|
+
// A position already holding a container of the same kind is written in
|
|
239
|
+
// place, as `assignEntry` does for a map key. A fresh container at the
|
|
240
|
+
// position is a replace that a peer's concurrent replace of the same
|
|
241
|
+
// position does not merge with, so undoing either one restores its old
|
|
242
|
+
// container beside the other's instead of in its place, and the list
|
|
243
|
+
// comes out one element longer on every peer.
|
|
244
|
+
const existing = target.get(idx);
|
|
215
245
|
if (isPlainObject(item)) {
|
|
216
|
-
const child = target.setContainer(idx, new LoroMap());
|
|
246
|
+
const child = existing instanceof LoroMap ? existing : target.setContainer(idx, new LoroMap());
|
|
217
247
|
assignPlain(child, item);
|
|
218
248
|
}
|
|
219
249
|
else if (Array.isArray(item)) {
|
|
220
|
-
const child =
|
|
250
|
+
const child = existing instanceof LoroMovableList
|
|
251
|
+
? existing
|
|
252
|
+
: target.setContainer(idx, new LoroMovableList());
|
|
221
253
|
assignList(child, item);
|
|
222
254
|
}
|
|
223
255
|
else {
|
|
@@ -297,7 +329,7 @@ function writeThroughBody(value, commit) {
|
|
|
297
329
|
// `loroMap.get(key)` (O(1) per key, so `Object.keys()` is O(N)). Iterating
|
|
298
330
|
// `loroMap.keys()` in the descriptor trap makes `Object.keys()` O(N^2) at
|
|
299
331
|
// 50k entities (measured at >5 min of CPU before giving up).
|
|
300
|
-
|
|
332
|
+
function wrapLoroMapProxy(loroMap,
|
|
301
333
|
// Threaded into nested record proxies too, so a field edit counts. A
|
|
302
334
|
// mutation can hold thousands of them (`m[id].params = ...` in a loop),
|
|
303
335
|
// which reaches the same size a bulk write does while touching no
|
|
@@ -307,10 +339,14 @@ batcher, divert,
|
|
|
307
339
|
// reserved space the store's own bookkeeping occupies. True for a root map,
|
|
308
340
|
// which is what can page; false inside a record, whose field names are the
|
|
309
341
|
// app's own and constrained by nothing.
|
|
310
|
-
guardKeys = false
|
|
342
|
+
guardKeys = false,
|
|
343
|
+
// The root this map is, which decides whether it carries platform entries
|
|
344
|
+
// the draft hides and refuses (`isPagedMarkerEntry`).
|
|
345
|
+
rootKey = null) {
|
|
346
|
+
const hidden = (key) => isPagedMarkerEntry(rootKey, key);
|
|
311
347
|
const handler = {
|
|
312
348
|
get(_t, key) {
|
|
313
|
-
if (typeof key === 'symbol')
|
|
349
|
+
if (typeof key === 'symbol' || hidden(key))
|
|
314
350
|
return undefined;
|
|
315
351
|
if (divert?.holds(key)) {
|
|
316
352
|
const held = divert.peek(key);
|
|
@@ -354,7 +390,7 @@ guardKeys = false) {
|
|
|
354
390
|
if (typeof key === 'symbol')
|
|
355
391
|
return false;
|
|
356
392
|
if (guardKeys)
|
|
357
|
-
assertUsableRecordKey(key);
|
|
393
|
+
assertUsableRecordKey(key, rootKey);
|
|
358
394
|
// Before the write, so a commit it triggers seals the preceding
|
|
359
395
|
// batch rather than splitting this record out of it.
|
|
360
396
|
batcher?.record(value);
|
|
@@ -371,7 +407,7 @@ guardKeys = false) {
|
|
|
371
407
|
if (typeof key === 'symbol')
|
|
372
408
|
return false;
|
|
373
409
|
if (guardKeys)
|
|
374
|
-
assertUsableRecordKey(key);
|
|
410
|
+
assertUsableRecordKey(key, rootKey);
|
|
375
411
|
batcher?.recordDelete(key);
|
|
376
412
|
divert?.drop(key);
|
|
377
413
|
loroMap.delete(key);
|
|
@@ -379,19 +415,20 @@ guardKeys = false) {
|
|
|
379
415
|
return true;
|
|
380
416
|
},
|
|
381
417
|
has(_t, key) {
|
|
382
|
-
if (typeof key === 'symbol')
|
|
418
|
+
if (typeof key === 'symbol' || hidden(key))
|
|
383
419
|
return false;
|
|
384
420
|
if (divert?.holds(key))
|
|
385
421
|
return true;
|
|
386
422
|
return loroMap.get(key) !== undefined;
|
|
387
423
|
},
|
|
388
424
|
ownKeys() {
|
|
425
|
+
const stored = loroMap.keys().filter((key) => !hidden(key));
|
|
389
426
|
if (!divert)
|
|
390
|
-
return
|
|
391
|
-
return [...new Set([...
|
|
427
|
+
return stored;
|
|
428
|
+
return [...new Set([...stored, ...divert.keys()])];
|
|
392
429
|
},
|
|
393
430
|
getOwnPropertyDescriptor(_t, key) {
|
|
394
|
-
if (typeof key === 'symbol')
|
|
431
|
+
if (typeof key === 'symbol' || hidden(key))
|
|
395
432
|
return undefined;
|
|
396
433
|
if (divert?.holds(key)) {
|
|
397
434
|
return {
|
|
@@ -723,8 +760,6 @@ batcher) {
|
|
|
723
760
|
};
|
|
724
761
|
return new Proxy([], handler);
|
|
725
762
|
}
|
|
726
|
-
const TAG_FIELD = 'type';
|
|
727
|
-
const BODY_FIELD = 'params';
|
|
728
763
|
// The record keys one page packs, read off the live container without
|
|
729
764
|
// materialising its bodies. Two fields out of wasm rather than the whole
|
|
730
765
|
// container: a store's bodies are its bulk, and an index build asks every page
|
|
@@ -1244,7 +1279,9 @@ function buildPagedDeltaIndex(m) {
|
|
|
1244
1279
|
}
|
|
1245
1280
|
return { pages, shadowed };
|
|
1246
1281
|
}
|
|
1247
|
-
|
|
1282
|
+
// Null when every key the batch touched is one `hidden` withholds, so a
|
|
1283
|
+
// subscriber is not handed an empty change for an edit it cannot see.
|
|
1284
|
+
function unpagedMapDelta(m, depth, batch, hidden) {
|
|
1248
1285
|
const setKeys = new Set();
|
|
1249
1286
|
const deleted = new Set();
|
|
1250
1287
|
for (const ev of batch.events) {
|
|
@@ -1269,10 +1306,15 @@ function unpagedMapDelta(m, depth, batch) {
|
|
|
1269
1306
|
}
|
|
1270
1307
|
for (const key of deleted)
|
|
1271
1308
|
setKeys.delete(key);
|
|
1309
|
+
const touched = setKeys.size + deleted.size;
|
|
1272
1310
|
const set = new Map();
|
|
1273
1311
|
for (const key of setKeys)
|
|
1274
|
-
|
|
1275
|
-
|
|
1312
|
+
if (!hidden(key))
|
|
1313
|
+
set.set(key, plainEntry(m, key));
|
|
1314
|
+
const removed = [...deleted].filter((key) => !hidden(key));
|
|
1315
|
+
if (touched > 0 && set.size === 0 && removed.length === 0)
|
|
1316
|
+
return null;
|
|
1317
|
+
return { set, deleted: removed };
|
|
1276
1318
|
}
|
|
1277
1319
|
/**
|
|
1278
1320
|
* Read a changed page out to the records it holds, and name the ones it lost.
|
|
@@ -2367,23 +2409,23 @@ function trackEntryContainer(root, spec) {
|
|
|
2367
2409
|
// dedup, a different shape: it models no per-listener external resource, and
|
|
2368
2410
|
// the Loro subscription each record must re-create on rebind is exactly
|
|
2369
2411
|
// that.)
|
|
2370
|
-
/** Run
|
|
2371
|
-
|
|
2372
|
-
*
|
|
2373
|
-
*
|
|
2374
|
-
|
|
2375
|
-
function reportSubscriberThrow(err) {
|
|
2376
|
-
console.error('[Brass] subscriber threw:', err);
|
|
2377
|
-
}
|
|
2378
|
-
function fireQuietly(cb) {
|
|
2412
|
+
/** Run an app callback without letting a throw break the caller. One app
|
|
2413
|
+
* callback failing must not break a registration or stop the other
|
|
2414
|
+
* subscribers, so the throw goes to `report` (the owner's
|
|
2415
|
+
* `reportSubscriberThrow`) and the walk carries on. */
|
|
2416
|
+
function fireQuietly(cb, report) {
|
|
2379
2417
|
try {
|
|
2380
2418
|
cb();
|
|
2381
2419
|
}
|
|
2382
2420
|
catch (err) {
|
|
2383
|
-
|
|
2421
|
+
report(err);
|
|
2384
2422
|
}
|
|
2385
2423
|
}
|
|
2386
2424
|
class SubscriptionRegistry {
|
|
2425
|
+
_report;
|
|
2426
|
+
constructor(_report) {
|
|
2427
|
+
this._report = _report;
|
|
2428
|
+
}
|
|
2387
2429
|
_subs = new Set();
|
|
2388
2430
|
_settled = new Set();
|
|
2389
2431
|
// Whether a whole-container delivery is in progress. Always false while a
|
|
@@ -2413,7 +2455,7 @@ class SubscriptionRegistry {
|
|
|
2413
2455
|
onSettled(cb) {
|
|
2414
2456
|
this._settled.add(cb);
|
|
2415
2457
|
if (!this._delivering)
|
|
2416
|
-
fireQuietly(cb);
|
|
2458
|
+
fireQuietly(cb, this._report);
|
|
2417
2459
|
return () => {
|
|
2418
2460
|
this._settled.delete(cb);
|
|
2419
2461
|
};
|
|
@@ -2424,7 +2466,7 @@ class SubscriptionRegistry {
|
|
|
2424
2466
|
// before it is reached is never visited.
|
|
2425
2467
|
const listeners = Array.from(this._settled);
|
|
2426
2468
|
for (const cb of listeners)
|
|
2427
|
-
fireQuietly(cb);
|
|
2469
|
+
fireQuietly(cb, this._report);
|
|
2428
2470
|
}
|
|
2429
2471
|
// Fire the whole-container catch-up once, attach the per-container change
|
|
2430
2472
|
// subscription, track the record, and return an idempotent unsubscribe.
|
|
@@ -2492,7 +2534,7 @@ function sameStateVersion(a, b) {
|
|
|
2492
2534
|
* Every field is declared with an initialiser, so they all exist by the time
|
|
2493
2535
|
* this runs and a later assignment keeps the descriptor this sets.
|
|
2494
2536
|
*/
|
|
2495
|
-
function hideOwnFields(handle) {
|
|
2537
|
+
export function hideOwnFields(handle) {
|
|
2496
2538
|
for (const key of Object.keys(handle)) {
|
|
2497
2539
|
Object.defineProperty(handle, key, { enumerable: false });
|
|
2498
2540
|
}
|
|
@@ -2515,7 +2557,7 @@ class MapHandleImpl {
|
|
|
2515
2557
|
// Active subscriptions, tracked so a version checkout (which swaps the
|
|
2516
2558
|
// active source doc) can re-fire and re-attach each one against the new
|
|
2517
2559
|
// container.
|
|
2518
|
-
_registry = new SubscriptionRegistry();
|
|
2560
|
+
_registry = new SubscriptionRegistry((err) => this._owner.reportSubscriberThrow(err));
|
|
2519
2561
|
// Keys captured just before a rebind (`_rebindAnchor`), consumed by the
|
|
2520
2562
|
// matching `_resync` to report the keys the source swap removed. Null
|
|
2521
2563
|
// outside a rebind cycle, so a bare `_resync` reports no removals.
|
|
@@ -2524,13 +2566,28 @@ class MapHandleImpl {
|
|
|
2524
2566
|
// taken before the container exists watches up this chain for the write
|
|
2525
2567
|
// that creates it (`_nearestLiveContainer`).
|
|
2526
2568
|
_parent;
|
|
2527
|
-
|
|
2569
|
+
// The document root this handle is, or null for a composed handle. Decides
|
|
2570
|
+
// which keys are the platform's own entries rather than the app's
|
|
2571
|
+
// (`isPagedMarkerEntry`), which every read here hides.
|
|
2572
|
+
_rootKey;
|
|
2573
|
+
constructor(anchor, owner, paging = null, parent = null, rootKey = null) {
|
|
2528
2574
|
this._anchor = anchor;
|
|
2529
2575
|
this._owner = owner;
|
|
2530
2576
|
this._paging = paging;
|
|
2531
2577
|
this._parent = parent;
|
|
2578
|
+
this._rootKey = rootKey;
|
|
2532
2579
|
hideOwnFields(this);
|
|
2533
2580
|
}
|
|
2581
|
+
_hidden(key) {
|
|
2582
|
+
return isPagedMarkerEntry(this._rootKey, key);
|
|
2583
|
+
}
|
|
2584
|
+
// An unpaged map's JSON without the platform's own entries.
|
|
2585
|
+
_visible(json) {
|
|
2586
|
+
for (const key of Object.keys(json))
|
|
2587
|
+
if (this._hidden(key))
|
|
2588
|
+
delete json[key];
|
|
2589
|
+
return json;
|
|
2590
|
+
}
|
|
2534
2591
|
// The container at the top of this handle's composition chain: the root it
|
|
2535
2592
|
// is composed off, which the document holds for its whole life (`getMap` is
|
|
2536
2593
|
// get-or-create, and a root anchor always resolves). A composed handle's
|
|
@@ -2566,6 +2623,14 @@ class MapHandleImpl {
|
|
|
2566
2623
|
const v = this._anchor.plain?.();
|
|
2567
2624
|
return isPlainObject(v) ? v : undefined;
|
|
2568
2625
|
}
|
|
2626
|
+
/** Whether the plain body this handle is scoped to holds this entry itself.
|
|
2627
|
+
* The prototype chain is not part of a record, so a key an app never wrote
|
|
2628
|
+
* must not resolve through it. */
|
|
2629
|
+
_plainHas(entryKey) {
|
|
2630
|
+
const body = this._plainBody();
|
|
2631
|
+
return (body !== undefined &&
|
|
2632
|
+
Object.prototype.hasOwnProperty.call(body, entryKey));
|
|
2633
|
+
}
|
|
2569
2634
|
read() {
|
|
2570
2635
|
this._owner.assertReadable();
|
|
2571
2636
|
const m = this._anchor.resolve();
|
|
@@ -2575,7 +2640,7 @@ class MapHandleImpl {
|
|
|
2575
2640
|
// paged or not.
|
|
2576
2641
|
if (this._isPaged())
|
|
2577
2642
|
return this._readPaged(m);
|
|
2578
|
-
return m.toJSON();
|
|
2643
|
+
return this._visible(m.toJSON());
|
|
2579
2644
|
}
|
|
2580
2645
|
// The whole-store read, which decodes every page. That walk answers the same
|
|
2581
2646
|
// question a keyed read asks (which page holds which record), so the index
|
|
@@ -2592,9 +2657,18 @@ class MapHandleImpl {
|
|
|
2592
2657
|
}
|
|
2593
2658
|
get(entryKey) {
|
|
2594
2659
|
this._owner.assertReadable();
|
|
2660
|
+
if (this._hidden(entryKey))
|
|
2661
|
+
return undefined;
|
|
2595
2662
|
const m = this._anchor.resolve();
|
|
2596
|
-
if (!m)
|
|
2597
|
-
|
|
2663
|
+
if (!m) {
|
|
2664
|
+
// One read of the body, not one per check: `_plainBody` resolves the
|
|
2665
|
+
// anchor and reaches into wasm, and this runs on every keyed read.
|
|
2666
|
+
const body = this._plainBody();
|
|
2667
|
+
return body !== undefined &&
|
|
2668
|
+
Object.prototype.hasOwnProperty.call(body, entryKey)
|
|
2669
|
+
? body[entryKey]
|
|
2670
|
+
: undefined;
|
|
2671
|
+
}
|
|
2598
2672
|
if (this._isPaged()) {
|
|
2599
2673
|
const pager = this._sessionPager(m);
|
|
2600
2674
|
return recordFromPage(m, entryKey, this._pageMemo, pager.liveIndex, pager.patches(m));
|
|
@@ -2604,6 +2678,14 @@ class MapHandleImpl {
|
|
|
2604
2678
|
keys() {
|
|
2605
2679
|
return [...this.liveKeySet()];
|
|
2606
2680
|
}
|
|
2681
|
+
// Taken from one whole read: on a paged store that is the pass resolving
|
|
2682
|
+
// every record against the patches and markers that shadow it, which a walk
|
|
2683
|
+
// key by key would repeat per record.
|
|
2684
|
+
*entries() {
|
|
2685
|
+
const records = this.read();
|
|
2686
|
+
for (const key of Object.keys(records))
|
|
2687
|
+
yield [key, records[key]];
|
|
2688
|
+
}
|
|
2607
2689
|
/**
|
|
2608
2690
|
* The keys this map holds, as a set, without materialising a record.
|
|
2609
2691
|
*
|
|
@@ -2619,27 +2701,29 @@ class MapHandleImpl {
|
|
|
2619
2701
|
return new Set(Object.keys(this._plainBody() ?? {}));
|
|
2620
2702
|
if (this._isPaged())
|
|
2621
2703
|
return this._sessionPager(m).ensureIndex(m).liveKeys();
|
|
2622
|
-
return new Set(m.keys());
|
|
2704
|
+
return new Set(m.keys().filter((key) => !this._hidden(key)));
|
|
2623
2705
|
}
|
|
2624
2706
|
has(entryKey) {
|
|
2625
2707
|
this._owner.assertReadable();
|
|
2708
|
+
if (this._hidden(entryKey))
|
|
2709
|
+
return false;
|
|
2626
2710
|
const m = this._anchor.resolve();
|
|
2627
|
-
if (!m)
|
|
2628
|
-
|
|
2629
|
-
return body !== undefined && Object.prototype.hasOwnProperty.call(body, entryKey);
|
|
2630
|
-
}
|
|
2711
|
+
if (!m)
|
|
2712
|
+
return this._plainHas(entryKey);
|
|
2631
2713
|
if (this._isPaged())
|
|
2632
2714
|
return this.get(entryKey) !== undefined;
|
|
2633
2715
|
return entryExists(m, entryKey);
|
|
2634
2716
|
}
|
|
2635
|
-
mutate(fn) {
|
|
2717
|
+
mutate(fn, options) {
|
|
2636
2718
|
this._owner.assertWritable();
|
|
2637
2719
|
this._owner.ensureLocalUpdatesSubscribed();
|
|
2638
|
-
|
|
2639
|
-
|
|
2720
|
+
this._owner.runMutate(options, () => {
|
|
2721
|
+
// Commits as it goes past a size budget; see `mutate-batching.ts`.
|
|
2722
|
+
this._runMutate(fn, new MutateBatcher(() => {
|
|
2723
|
+
this._owner.commitMutate();
|
|
2724
|
+
}));
|
|
2640
2725
|
this._owner.commitMutate();
|
|
2641
|
-
})
|
|
2642
|
-
this._owner.commitMutate();
|
|
2726
|
+
});
|
|
2643
2727
|
}
|
|
2644
2728
|
// Run a mutate WITHOUT the commit pipeline (the caller owns
|
|
2645
2729
|
// assertWritable / ensureLocalUpdatesSubscribed / commit). The seam the
|
|
@@ -2654,7 +2738,7 @@ class MapHandleImpl {
|
|
|
2654
2738
|
return;
|
|
2655
2739
|
}
|
|
2656
2740
|
const divert = this._paging ? this._pagingDivert(m, batcher) : null;
|
|
2657
|
-
const proxy = wrapLoroMapProxy(m, batcher, divert?.hooks, this._paging !== null);
|
|
2741
|
+
const proxy = wrapLoroMapProxy(m, batcher, divert?.hooks, this._paging !== null, this._rootKey);
|
|
2658
2742
|
fn(proxy);
|
|
2659
2743
|
divert?.finish();
|
|
2660
2744
|
}
|
|
@@ -2977,7 +3061,7 @@ class MapHandleImpl {
|
|
|
2977
3061
|
}
|
|
2978
3062
|
// One walk of an unpaged map, whose size the paging threshold bounds, and
|
|
2979
3063
|
// no wasm handle minted per key the way a per-key read would.
|
|
2980
|
-
const keys = new Set(m.keys());
|
|
3064
|
+
const keys = new Set(m.keys().filter((key) => !this._hidden(key)));
|
|
2981
3065
|
return (key) => keys.has(key);
|
|
2982
3066
|
}
|
|
2983
3067
|
// Fire a subscriber once with the whole map as `change.set`, so a
|
|
@@ -3003,7 +3087,7 @@ class MapHandleImpl {
|
|
|
3003
3087
|
}, undefined, true);
|
|
3004
3088
|
}
|
|
3005
3089
|
catch (err) {
|
|
3006
|
-
reportSubscriberThrow(err);
|
|
3090
|
+
this._owner.reportSubscriberThrow(err);
|
|
3007
3091
|
}
|
|
3008
3092
|
}
|
|
3009
3093
|
// Attach the change subscription for `cb`, returning its detach.
|
|
@@ -3077,13 +3161,15 @@ class MapHandleImpl {
|
|
|
3077
3161
|
}
|
|
3078
3162
|
const change = (index
|
|
3079
3163
|
? pagedMapDelta(m, event, index, (key) => this.get(key))
|
|
3080
|
-
: unpagedMapDelta(m, depth, event));
|
|
3164
|
+
: unpagedMapDelta(m, depth, event, (key) => this._hidden(key)));
|
|
3165
|
+
if (change === null)
|
|
3166
|
+
return;
|
|
3081
3167
|
const origin = { source: this._owner.originOf(event) };
|
|
3082
3168
|
try {
|
|
3083
3169
|
cb(change, origin, false);
|
|
3084
3170
|
}
|
|
3085
3171
|
catch (err) {
|
|
3086
|
-
reportSubscriberThrow(err);
|
|
3172
|
+
this._owner.reportSubscriberThrow(err);
|
|
3087
3173
|
}
|
|
3088
3174
|
});
|
|
3089
3175
|
return () => sub();
|
|
@@ -3216,7 +3302,7 @@ class MapHandleImpl {
|
|
|
3216
3302
|
const direct = childAnchor(this._anchor, entryKey, ctor);
|
|
3217
3303
|
if (this._paging === null)
|
|
3218
3304
|
return direct;
|
|
3219
|
-
assertUsableRecordKey(entryKey);
|
|
3305
|
+
assertUsableRecordKey(entryKey, this._rootKey);
|
|
3220
3306
|
const overlay = childAnchor(this._anchor, overlayKeyFor(entryKey), ctor);
|
|
3221
3307
|
// Whether an edit written beside the pages is waiting to be folded over
|
|
3222
3308
|
// this record. Asked only once a container has been found, since a record
|
|
@@ -3313,6 +3399,20 @@ class MapHandleImpl {
|
|
|
3313
3399
|
* carry the position each edit lands at, so an app applies them in order and
|
|
3314
3400
|
* is done; nothing about the cursor reaches it.
|
|
3315
3401
|
*/
|
|
3402
|
+
/** Whether position `at`, read after the first `count` splices, is one of the
|
|
3403
|
+
* elements those splices inserted. */
|
|
3404
|
+
function insertedBySplices(splices, count, at) {
|
|
3405
|
+
let pos = at;
|
|
3406
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
3407
|
+
const { at: start, removed, inserted } = splices[i];
|
|
3408
|
+
if (pos < start)
|
|
3409
|
+
continue;
|
|
3410
|
+
if (pos < start + inserted.length)
|
|
3411
|
+
return true;
|
|
3412
|
+
pos += removed - inserted.length;
|
|
3413
|
+
}
|
|
3414
|
+
return false;
|
|
3415
|
+
}
|
|
3316
3416
|
function splicesFromListDiff(diff) {
|
|
3317
3417
|
const out = [];
|
|
3318
3418
|
let at = 0;
|
|
@@ -3327,7 +3427,7 @@ function splicesFromListDiff(diff) {
|
|
|
3327
3427
|
}
|
|
3328
3428
|
if (part.insert !== undefined) {
|
|
3329
3429
|
const inserted = part.insert.map(plainDiffItem);
|
|
3330
|
-
out
|
|
3430
|
+
pushBoundedSplice(out, { at, removed: 0, inserted });
|
|
3331
3431
|
at += inserted.length;
|
|
3332
3432
|
}
|
|
3333
3433
|
}
|
|
@@ -3358,7 +3458,7 @@ class ListHandleImpl {
|
|
|
3358
3458
|
_owner;
|
|
3359
3459
|
// Active subscriptions, tracked so a version checkout can re-fire and
|
|
3360
3460
|
// re-attach each one against the new container.
|
|
3361
|
-
_registry = new SubscriptionRegistry();
|
|
3461
|
+
_registry = new SubscriptionRegistry((err) => this._owner.reportSubscriberThrow(err));
|
|
3362
3462
|
// See `MapHandleImpl._parent`.
|
|
3363
3463
|
_parent;
|
|
3364
3464
|
constructor(anchor, owner, parent = null) {
|
|
@@ -3385,14 +3485,39 @@ class ListHandleImpl {
|
|
|
3385
3485
|
const l = this._anchor.resolve();
|
|
3386
3486
|
return l ? l.length : (this._plainBody()?.length ?? 0);
|
|
3387
3487
|
}
|
|
3388
|
-
|
|
3488
|
+
get(index) {
|
|
3489
|
+
this._owner.assertReadable();
|
|
3490
|
+
if (!Number.isInteger(index) || index < 0)
|
|
3491
|
+
return undefined;
|
|
3492
|
+
const l = this._anchor.resolve();
|
|
3493
|
+
if (!l)
|
|
3494
|
+
return this._plainBody()?.[index];
|
|
3495
|
+
if (index >= l.length)
|
|
3496
|
+
return undefined;
|
|
3497
|
+
const v = l.get(index);
|
|
3498
|
+
if (!isContainer(v))
|
|
3499
|
+
return v;
|
|
3500
|
+
const container = v;
|
|
3501
|
+
try {
|
|
3502
|
+
return container.toJSON();
|
|
3503
|
+
}
|
|
3504
|
+
finally {
|
|
3505
|
+
container.free();
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
*values() {
|
|
3509
|
+
yield* this.read();
|
|
3510
|
+
}
|
|
3511
|
+
mutate(fn, options) {
|
|
3389
3512
|
this._owner.assertWritable();
|
|
3390
3513
|
this._owner.ensureLocalUpdatesSubscribed();
|
|
3391
|
-
|
|
3392
|
-
|
|
3514
|
+
this._owner.runMutate(options, () => {
|
|
3515
|
+
// Commits as it goes past a size budget; see `mutate-batching.ts`.
|
|
3516
|
+
this._runMutate(fn, new MutateBatcher(() => {
|
|
3517
|
+
this._owner.commitMutate();
|
|
3518
|
+
}));
|
|
3393
3519
|
this._owner.commitMutate();
|
|
3394
|
-
})
|
|
3395
|
-
this._owner.commitMutate();
|
|
3520
|
+
});
|
|
3396
3521
|
}
|
|
3397
3522
|
// Run a list mutate without the commit pipeline; see
|
|
3398
3523
|
// `MapHandleImpl._runMutate`. The ordered-map primitive drives this to
|
|
@@ -3405,7 +3530,7 @@ class ListHandleImpl {
|
|
|
3405
3530
|
const proxy = wrapLoroListProxy(l, batcher);
|
|
3406
3531
|
fn(proxy);
|
|
3407
3532
|
}
|
|
3408
|
-
// Fire the whole list as
|
|
3533
|
+
// Fire the whole list as one change replacing everything the subscriber was
|
|
3409
3534
|
// last told. Used for registration and for a checkout / return resync, which
|
|
3410
3535
|
// describe no authored transition and so carry no origin.
|
|
3411
3536
|
//
|
|
@@ -3419,10 +3544,12 @@ class ListHandleImpl {
|
|
|
3419
3544
|
const held = this._held.get(cb) ?? 0;
|
|
3420
3545
|
this._held.set(cb, inserted.length);
|
|
3421
3546
|
try {
|
|
3422
|
-
|
|
3547
|
+
const splices = [];
|
|
3548
|
+
pushBoundedSplice(splices, { at: 0, removed: held, inserted });
|
|
3549
|
+
cb({ splices });
|
|
3423
3550
|
}
|
|
3424
3551
|
catch (err) {
|
|
3425
|
-
reportSubscriberThrow(err);
|
|
3552
|
+
this._owner.reportSubscriberThrow(err);
|
|
3426
3553
|
}
|
|
3427
3554
|
}
|
|
3428
3555
|
// Attach the change subscription for `cb`, returning its detach. A root
|
|
@@ -3446,11 +3573,30 @@ class ListHandleImpl {
|
|
|
3446
3573
|
// Subscribe `cb` to one container's own changes; see
|
|
3447
3574
|
// `MapHandleImpl._subscribeContainer`.
|
|
3448
3575
|
_subscribeContainer(l, cb) {
|
|
3576
|
+
const depth = containerDepth(l);
|
|
3449
3577
|
const sub = l.subscribe((event) => {
|
|
3450
|
-
const
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3578
|
+
const splices = [];
|
|
3579
|
+
// An element held as a child container changes inside that container,
|
|
3580
|
+
// which the list's own diff does not name. Each such element is
|
|
3581
|
+
// reported as a replace of its position, read after the list's own
|
|
3582
|
+
// splices so the index is the one the subscriber holds by then. An
|
|
3583
|
+
// element those splices inserted already arrived as it now stands, so
|
|
3584
|
+
// its own fields being written in the same commit adds no replace.
|
|
3585
|
+
const touched = new Set();
|
|
3586
|
+
for (const e of event.events) {
|
|
3587
|
+
if (e.path.length === depth && e.diff.type === 'list') {
|
|
3588
|
+
splices.push(...splicesFromListDiff(e.diff.diff));
|
|
3589
|
+
}
|
|
3590
|
+
else if (e.path.length > depth && typeof e.path[depth] === 'number') {
|
|
3591
|
+
touched.add(e.path[depth]);
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
const listSplices = splices.length;
|
|
3595
|
+
for (const at of [...touched].sort((x, y) => x - y)) {
|
|
3596
|
+
if (at >= l.length || insertedBySplices(splices, listSplices, at))
|
|
3597
|
+
continue;
|
|
3598
|
+
splices.push({ at, removed: 1, inserted: [plainListItem(l, at)] });
|
|
3599
|
+
}
|
|
3454
3600
|
let held = this._held.get(cb) ?? 0;
|
|
3455
3601
|
for (const splice of splices)
|
|
3456
3602
|
held += splice.inserted.length - splice.removed;
|
|
@@ -3459,7 +3605,7 @@ class ListHandleImpl {
|
|
|
3459
3605
|
cb({ splices }, { source: this._owner.originOf(event) });
|
|
3460
3606
|
}
|
|
3461
3607
|
catch (err) {
|
|
3462
|
-
reportSubscriberThrow(err);
|
|
3608
|
+
this._owner.reportSubscriberThrow(err);
|
|
3463
3609
|
}
|
|
3464
3610
|
});
|
|
3465
3611
|
return () => sub();
|
|
@@ -3591,6 +3737,10 @@ class OrderedMapHandleImpl {
|
|
|
3591
3737
|
keys() {
|
|
3592
3738
|
return sequenceOf(this._map.liveKeySet(), this._order.read());
|
|
3593
3739
|
}
|
|
3740
|
+
*entries() {
|
|
3741
|
+
for (const { key, value } of this.ordered())
|
|
3742
|
+
yield [key, value];
|
|
3743
|
+
}
|
|
3594
3744
|
ordered() {
|
|
3595
3745
|
const records = this._map.read();
|
|
3596
3746
|
const seen = new Set();
|
|
@@ -3723,9 +3873,14 @@ class OrderedMapHandleImpl {
|
|
|
3723
3873
|
});
|
|
3724
3874
|
return true;
|
|
3725
3875
|
}
|
|
3726
|
-
mutate(fn) {
|
|
3876
|
+
mutate(fn, options) {
|
|
3727
3877
|
this._owner.assertWritable();
|
|
3728
3878
|
this._owner.ensureLocalUpdatesSubscribed();
|
|
3879
|
+
this._owner.runMutate(options, () => {
|
|
3880
|
+
this._mutateOrdered(fn);
|
|
3881
|
+
});
|
|
3882
|
+
}
|
|
3883
|
+
_mutateOrdered(fn) {
|
|
3729
3884
|
// Net structural effect: keys added (`added` iterates in insertion
|
|
3730
3885
|
// order, and a delete-then-re-set re-appends, matching the order list's
|
|
3731
3886
|
// append semantics) and removed. A field edit routes through the inner
|
|
@@ -3931,8 +4086,8 @@ class OrderedMapHandleImpl {
|
|
|
3931
4086
|
// transparently as a flat record map. The Document supplies it for every root
|
|
3932
4087
|
// map; a non-paged map never consults it, so passing it to non-record roots
|
|
3933
4088
|
// is harmless. Omitted means the handle only serves the plain unpaged shape.
|
|
3934
|
-
export function createRootMapHandle(root, owner, paging) {
|
|
3935
|
-
return new MapHandleImpl(rootAnchor(root), owner, paging ?? null);
|
|
4089
|
+
export function createRootMapHandle(root, owner, paging, rootKey) {
|
|
4090
|
+
return new MapHandleImpl(rootAnchor(root), owner, paging ?? null, null, rootKey ?? null);
|
|
3936
4091
|
}
|
|
3937
4092
|
export function createRootListHandle(root, owner) {
|
|
3938
4093
|
return new ListHandleImpl(rootAnchor(root), owner);
|