@loro-extended/change 5.3.0 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -28
- package/dist/index.d.ts +291 -107
- package/dist/index.js +587 -36
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/change.test.ts +1 -1
- package/src/conversion.ts +40 -4
- package/src/diff-overlay.test.ts +95 -0
- package/src/diff-overlay.ts +10 -0
- package/src/discriminated-union-tojson.test.ts +2 -2
- package/src/fork-at.test.ts +1 -1
- package/src/functional-helpers.test.ts +50 -1
- package/src/functional-helpers.ts +152 -8
- package/src/index.ts +46 -18
- package/src/loro.ts +2 -1
- package/src/nested-container-materialization.test.ts +336 -0
- package/src/overlay-recursion.test.ts +8 -8
- package/src/replay-diff.test.ts +389 -0
- package/src/replay-diff.ts +229 -0
- package/src/shallow-fork.test.ts +302 -0
- package/src/shape.ts +7 -7
- package/src/typed-doc-ownkeys.test.ts +116 -0
- package/src/typed-doc.ts +33 -10
- package/src/typed-refs/base.ts +40 -4
- package/src/typed-refs/counter-ref-internals.ts +16 -2
- package/src/typed-refs/doc-ref-internals.ts +1 -0
- package/src/typed-refs/doc-ref-ownkeys.test.ts +78 -0
- package/src/typed-refs/index.ts +17 -0
- package/src/typed-refs/json-compatibility.test.ts +1 -1
- package/src/typed-refs/list-ref-base-internals.ts +2 -1
- package/src/typed-refs/list-ref-base.ts +79 -3
- package/src/typed-refs/record-ref-internals.ts +116 -2
- package/src/typed-refs/record-ref.test.ts +522 -1
- package/src/typed-refs/record-ref.ts +72 -3
- package/src/typed-refs/struct-ref-internals.ts +40 -3
- package/src/typed-refs/text-ref-internals.ts +70 -4
- package/src/typed-refs/tree-node-ref-internals.ts +14 -2
- package/src/typed-refs/tree-ref-internals.ts +2 -1
- package/src/typed-refs/utils.ts +65 -8
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
CounterDiff,
|
|
3
|
+
LoroCounter,
|
|
4
|
+
LoroDoc,
|
|
5
|
+
LoroEventBatch,
|
|
6
|
+
Subscription,
|
|
7
|
+
} from "loro-crdt"
|
|
2
8
|
import type { LoroCounterRef } from "../loro.js"
|
|
3
9
|
import type { CounterContainerShape } from "../shape.js"
|
|
4
10
|
import { BaseRefInternals } from "./base.js"
|
|
@@ -28,6 +34,14 @@ export class CounterRefInternals extends BaseRefInternals<CounterContainerShape>
|
|
|
28
34
|
getValue(): number {
|
|
29
35
|
const container = this.getContainer() as LoroCounter
|
|
30
36
|
const containerValue = container.value
|
|
37
|
+
const overlay = this.getOverlay()
|
|
38
|
+
if (overlay) {
|
|
39
|
+
const diff = overlay.get((container as any).id)
|
|
40
|
+
if (diff && diff.type === "counter") {
|
|
41
|
+
const counterDiff = diff as CounterDiff
|
|
42
|
+
return containerValue + counterDiff.increment
|
|
43
|
+
}
|
|
44
|
+
}
|
|
31
45
|
if (containerValue !== 0 || this.materialized) {
|
|
32
46
|
return containerValue
|
|
33
47
|
}
|
|
@@ -54,7 +68,7 @@ export class CounterRefInternals extends BaseRefInternals<CounterContainerShape>
|
|
|
54
68
|
get container(): LoroCounter {
|
|
55
69
|
return self.getContainer() as LoroCounter
|
|
56
70
|
},
|
|
57
|
-
subscribe(callback: (event:
|
|
71
|
+
subscribe(callback: (event: LoroEventBatch) => void): Subscription {
|
|
58
72
|
return (self.getContainer() as LoroCounter).subscribe(callback)
|
|
59
73
|
},
|
|
60
74
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { LoroDoc } from "loro-crdt"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
import { derivePlaceholder } from "../derive-placeholder.js"
|
|
4
|
+
import { Shape } from "../index.js"
|
|
5
|
+
import { DocRef } from "./doc-ref.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tests for DocRef class ownKeys behavior.
|
|
9
|
+
*
|
|
10
|
+
* Issue: DocRef extends TypedRef which has Symbol properties:
|
|
11
|
+
* - [INTERNAL_SYMBOL]: DocRefInternals<Shape>
|
|
12
|
+
* - [LORO_SYMBOL]: getter for loro() access
|
|
13
|
+
*
|
|
14
|
+
* When Reflect.ownKeys() is called on a DocRef instance, it returns these
|
|
15
|
+
* Symbol properties along with the schema keys.
|
|
16
|
+
*
|
|
17
|
+
* This is the underlying cause of the TypedDoc proxy issue - the proxy
|
|
18
|
+
* delegates ownKeys to the DocRef target.
|
|
19
|
+
*
|
|
20
|
+
* Note: DocRef is not a proxy, it's a class. The fix for this is in the
|
|
21
|
+
* TypedDoc proxy's ownKeys trap, which should filter out Symbols.
|
|
22
|
+
*/
|
|
23
|
+
describe("DocRef ownKeys", () => {
|
|
24
|
+
const schema = Shape.doc({
|
|
25
|
+
title: Shape.text(),
|
|
26
|
+
count: Shape.counter(),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
function createDocRef() {
|
|
30
|
+
const loroDoc = new LoroDoc()
|
|
31
|
+
const placeholder = derivePlaceholder(schema)
|
|
32
|
+
return new DocRef({
|
|
33
|
+
shape: schema,
|
|
34
|
+
placeholder,
|
|
35
|
+
doc: loroDoc,
|
|
36
|
+
autoCommit: true,
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("Reflect.ownKeys() on DocRef instance", () => {
|
|
41
|
+
it("returns Symbol properties from the class", () => {
|
|
42
|
+
const docRef = createDocRef()
|
|
43
|
+
|
|
44
|
+
const keys = Reflect.ownKeys(docRef)
|
|
45
|
+
|
|
46
|
+
// DocRef has Symbol properties from TypedRef base class
|
|
47
|
+
// This test documents the current behavior
|
|
48
|
+
const symbolKeys = keys.filter(k => typeof k === "symbol")
|
|
49
|
+
const stringKeys = keys.filter(k => typeof k === "string")
|
|
50
|
+
|
|
51
|
+
// Should have schema keys as strings
|
|
52
|
+
expect(stringKeys).toContain("title")
|
|
53
|
+
expect(stringKeys).toContain("count")
|
|
54
|
+
|
|
55
|
+
// Currently has Symbol keys (this is the root cause of the issue)
|
|
56
|
+
// The TypedDoc proxy should filter these out
|
|
57
|
+
expect(symbolKeys.length).toBeGreaterThan(0)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe("Object.keys() on DocRef instance", () => {
|
|
62
|
+
it("should return only enumerable string keys", () => {
|
|
63
|
+
const docRef = createDocRef()
|
|
64
|
+
|
|
65
|
+
const keys = Object.keys(docRef)
|
|
66
|
+
|
|
67
|
+
// Object.keys only returns enumerable string keys
|
|
68
|
+
// Symbol properties are not enumerable by default
|
|
69
|
+
for (const key of keys) {
|
|
70
|
+
expect(typeof key).toBe("string")
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Should include schema keys
|
|
74
|
+
expect(keys).toContain("title")
|
|
75
|
+
expect(keys).toContain("count")
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type { DiffOverlay } from "./base.js"
|
|
2
|
+
|
|
3
|
+
export type { CounterRef } from "./counter-ref.js"
|
|
4
|
+
|
|
5
|
+
export type { ListRef } from "./list-ref.js"
|
|
6
|
+
|
|
7
|
+
export type { MovableListRef } from "./movable-list-ref.js"
|
|
8
|
+
|
|
9
|
+
export type { RecordRef } from "./record-ref.js"
|
|
10
|
+
|
|
11
|
+
export type { StructRef } from "./struct-ref.js"
|
|
12
|
+
|
|
13
|
+
export type { TextRef } from "./text-ref.js"
|
|
14
|
+
|
|
15
|
+
export type { TreeNodeRef } from "./tree-node-ref.js"
|
|
16
|
+
|
|
17
|
+
export type { TreeRef } from "./tree-ref.js"
|
|
@@ -185,7 +185,7 @@ describe("JSON Compatibility", () => {
|
|
|
185
185
|
|
|
186
186
|
it("should be efficient (not access unrelated parts)", () => {
|
|
187
187
|
const loroDoc = new LoroDoc()
|
|
188
|
-
const doc = createTypedDoc(ChatSchema, loroDoc)
|
|
188
|
+
const doc = createTypedDoc(ChatSchema, { doc: loroDoc })
|
|
189
189
|
|
|
190
190
|
change(doc, (root: any) => {
|
|
191
191
|
root.messages.push({ id: "1", content: "A", timestamp: 1 })
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Container,
|
|
3
3
|
LoroDoc,
|
|
4
|
+
LoroEventBatch,
|
|
4
5
|
LoroList,
|
|
5
6
|
LoroMovableList,
|
|
6
7
|
Subscription,
|
|
@@ -256,7 +257,7 @@ export class ListRefBaseInternals<
|
|
|
256
257
|
get container(): LoroList | LoroMovableList {
|
|
257
258
|
return self.getContainer() as LoroList | LoroMovableList
|
|
258
259
|
},
|
|
259
|
-
subscribe(callback: (event:
|
|
260
|
+
subscribe(callback: (event: LoroEventBatch) => void): Subscription {
|
|
260
261
|
return (self.getContainer() as LoroList | LoroMovableList).subscribe(
|
|
261
262
|
callback,
|
|
262
263
|
)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Container,
|
|
3
|
+
Delta,
|
|
4
|
+
ListDiff,
|
|
3
5
|
LoroDoc,
|
|
6
|
+
LoroEventBatch,
|
|
4
7
|
LoroList,
|
|
5
8
|
LoroMovableList,
|
|
6
9
|
Subscription,
|
|
@@ -37,6 +40,39 @@ export class ListRefBaseInternals<
|
|
|
37
40
|
MutableItem = NestedShape["_mutable"],
|
|
38
41
|
> extends BaseRefInternals<any> {
|
|
39
42
|
private itemCache = new Map<number, any>()
|
|
43
|
+
private overlayListCache?: Item[]
|
|
44
|
+
|
|
45
|
+
getOverlayList(): Item[] | undefined {
|
|
46
|
+
const overlay = this.getOverlay()
|
|
47
|
+
if (!overlay) {
|
|
48
|
+
return undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const shape = this.getShape()
|
|
52
|
+
if (!isValueShape(shape.shape)) {
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!this.overlayListCache) {
|
|
57
|
+
const container = this.getContainer() as LoroList | LoroMovableList
|
|
58
|
+
const diff = overlay.get(container.id)
|
|
59
|
+
if (!diff || diff.type !== "list") {
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const afterValues: Item[] = []
|
|
64
|
+
for (let i = 0; i < container.length; i++) {
|
|
65
|
+
afterValues.push(container.get(i) as Item)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.overlayListCache = applyListDelta(
|
|
69
|
+
afterValues,
|
|
70
|
+
(diff as ListDiff).diff as Delta<Item[]>[],
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return this.overlayListCache
|
|
75
|
+
}
|
|
40
76
|
|
|
41
77
|
/** Get typed ref params for creating child refs at an index */
|
|
42
78
|
getChildTypedRefParams(
|
|
@@ -57,6 +93,7 @@ export class ListRefBaseInternals<
|
|
|
57
93
|
autoCommit: this.getAutoCommit(),
|
|
58
94
|
batchedMutation: this.getBatchedMutation(),
|
|
59
95
|
getDoc: () => this.getDoc(),
|
|
96
|
+
overlay: this.getOverlay(),
|
|
60
97
|
}
|
|
61
98
|
}
|
|
62
99
|
|
|
@@ -64,6 +101,7 @@ export class ListRefBaseInternals<
|
|
|
64
101
|
getPredicateItem(index: number): Item | undefined {
|
|
65
102
|
const shape = this.getShape()
|
|
66
103
|
const container = this.getContainer() as LoroList | LoroMovableList
|
|
104
|
+
const overlayList = this.getOverlayList()
|
|
67
105
|
|
|
68
106
|
// CRITICAL FIX: For predicates to work correctly with mutations,
|
|
69
107
|
// we need to check if there's a cached (mutated) version first
|
|
@@ -73,6 +111,10 @@ export class ListRefBaseInternals<
|
|
|
73
111
|
return cachedItem as Item
|
|
74
112
|
}
|
|
75
113
|
|
|
114
|
+
if (overlayList && isValueShape(shape.shape)) {
|
|
115
|
+
return overlayList[index]
|
|
116
|
+
}
|
|
117
|
+
|
|
76
118
|
const containerItem = container.get(index)
|
|
77
119
|
if (containerItem === undefined) {
|
|
78
120
|
return undefined as Item
|
|
@@ -113,9 +155,12 @@ export class ListRefBaseInternals<
|
|
|
113
155
|
getMutableItem(index: number): MutableItem | undefined {
|
|
114
156
|
const shape = this.getShape()
|
|
115
157
|
const container = this.getContainer() as LoroList | LoroMovableList
|
|
158
|
+
const overlayList = this.getOverlayList()
|
|
116
159
|
|
|
117
160
|
// Get the raw container item
|
|
118
|
-
const containerItem =
|
|
161
|
+
const containerItem = overlayList
|
|
162
|
+
? (overlayList[index] as Item | undefined)
|
|
163
|
+
: (container.get(index) as Item | undefined)
|
|
119
164
|
if (containerItem === undefined) {
|
|
120
165
|
return undefined as MutableItem
|
|
121
166
|
}
|
|
@@ -267,7 +312,7 @@ export class ListRefBaseInternals<
|
|
|
267
312
|
get container(): LoroList | LoroMovableList {
|
|
268
313
|
return self.getContainer() as LoroList | LoroMovableList
|
|
269
314
|
},
|
|
270
|
-
subscribe(callback: (event:
|
|
315
|
+
subscribe(callback: (event: LoroEventBatch) => void): Subscription {
|
|
271
316
|
return (self.getContainer() as LoroList | LoroMovableList).subscribe(
|
|
272
317
|
callback,
|
|
273
318
|
)
|
|
@@ -461,6 +506,10 @@ export abstract class ListRefBase<
|
|
|
461
506
|
}
|
|
462
507
|
|
|
463
508
|
toArray(): Item[] {
|
|
509
|
+
const overlayList = this[INTERNAL_SYMBOL].getOverlayList()
|
|
510
|
+
if (overlayList) {
|
|
511
|
+
return [...overlayList]
|
|
512
|
+
}
|
|
464
513
|
const result: Item[] = []
|
|
465
514
|
for (let i = 0; i < this.length; i++) {
|
|
466
515
|
result.push(this[INTERNAL_SYMBOL].getPredicateItem(i) as Item)
|
|
@@ -470,10 +519,11 @@ export abstract class ListRefBase<
|
|
|
470
519
|
|
|
471
520
|
toJSON(): Item[] {
|
|
472
521
|
const shape = this[INTERNAL_SYMBOL].getShape()
|
|
522
|
+
const overlayList = this[INTERNAL_SYMBOL].getOverlayList()
|
|
473
523
|
const container = this[INTERNAL_SYMBOL].getContainer() as
|
|
474
524
|
| LoroList
|
|
475
525
|
| LoroMovableList
|
|
476
|
-
const nativeJson = container.toJSON() as any[]
|
|
526
|
+
const nativeJson = overlayList ?? (container.toJSON() as any[])
|
|
477
527
|
|
|
478
528
|
// If the nested shape is a container shape (map, record, etc.) or an object value shape,
|
|
479
529
|
// we need to overlay placeholders for each item
|
|
@@ -510,9 +560,35 @@ export abstract class ListRefBase<
|
|
|
510
560
|
}
|
|
511
561
|
|
|
512
562
|
get length(): number {
|
|
563
|
+
const overlayList = this[INTERNAL_SYMBOL].getOverlayList()
|
|
564
|
+
if (overlayList) {
|
|
565
|
+
return overlayList.length
|
|
566
|
+
}
|
|
513
567
|
const container = this[INTERNAL_SYMBOL].getContainer() as
|
|
514
568
|
| LoroList
|
|
515
569
|
| LoroMovableList
|
|
516
570
|
return container.length
|
|
517
571
|
}
|
|
518
572
|
}
|
|
573
|
+
|
|
574
|
+
function applyListDelta<T>(input: T[], delta: Delta<T[]>[]): T[] {
|
|
575
|
+
const result: T[] = []
|
|
576
|
+
let index = 0
|
|
577
|
+
|
|
578
|
+
for (const op of delta) {
|
|
579
|
+
if (op.retain !== undefined) {
|
|
580
|
+
result.push(...input.slice(index, index + op.retain))
|
|
581
|
+
index += op.retain
|
|
582
|
+
} else if (op.delete !== undefined) {
|
|
583
|
+
index += op.delete
|
|
584
|
+
} else if (op.insert !== undefined) {
|
|
585
|
+
result.push(...op.insert)
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (index < input.length) {
|
|
590
|
+
result.push(...input.slice(index))
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return result
|
|
594
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Container,
|
|
3
3
|
LoroDoc,
|
|
4
|
+
LoroEventBatch,
|
|
4
5
|
LoroMap,
|
|
6
|
+
MapDiff,
|
|
5
7
|
Subscription,
|
|
6
8
|
Value,
|
|
7
9
|
} from "loro-crdt"
|
|
@@ -93,6 +95,17 @@ export class RecordRefInternals<
|
|
|
93
95
|
const container = this.getContainer() as LoroMap
|
|
94
96
|
|
|
95
97
|
if (isValueShape(shape)) {
|
|
98
|
+
const overlay = this.getOverlay()
|
|
99
|
+
if (overlay) {
|
|
100
|
+
const containerId = (container as any).id
|
|
101
|
+
const diff = overlay.get(containerId)
|
|
102
|
+
if (diff && diff.type === "map") {
|
|
103
|
+
const mapDiff = diff as MapDiff
|
|
104
|
+
if (key in mapDiff.updated) {
|
|
105
|
+
return mapDiff.updated[key] as Value
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
96
109
|
// When NOT in batchedMutation mode (direct access outside of change()), ALWAYS read fresh
|
|
97
110
|
// from container (NEVER cache). This ensures we always get the latest value
|
|
98
111
|
// from the CRDT, even when modified by a different ref instance (e.g., drafts from change())
|
|
@@ -166,9 +179,10 @@ export class RecordRefInternals<
|
|
|
166
179
|
} else {
|
|
167
180
|
// For container shapes, try to assign the plain value
|
|
168
181
|
// Use getOrCreateRef to ensure the container is created
|
|
182
|
+
// assignPlainValueToTypedRef handles batching and commits internally
|
|
169
183
|
const ref = this.getOrCreateRef(key)
|
|
170
184
|
if (assignPlainValueToTypedRef(ref as TypedRef<any>, value)) {
|
|
171
|
-
|
|
185
|
+
// Don't call commitIfAuto here - assignPlainValueToTypedRef handles it
|
|
172
186
|
return
|
|
173
187
|
}
|
|
174
188
|
throw new Error(
|
|
@@ -185,6 +199,106 @@ export class RecordRefInternals<
|
|
|
185
199
|
this.commitIfAuto()
|
|
186
200
|
}
|
|
187
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Replace entire contents with new values.
|
|
204
|
+
* Keys not in `values` are removed.
|
|
205
|
+
*/
|
|
206
|
+
replace(values: Record<string, any>): void {
|
|
207
|
+
const container = this.getContainer() as LoroMap
|
|
208
|
+
const currentKeys = new Set(container.keys())
|
|
209
|
+
const newKeys = new Set(Object.keys(values))
|
|
210
|
+
|
|
211
|
+
// Suppress auto-commit during batch operations
|
|
212
|
+
const wasSuppressed = this.isSuppressAutoCommit()
|
|
213
|
+
if (!wasSuppressed) {
|
|
214
|
+
this.setSuppressAutoCommit(true)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
// Delete keys that are not in the new values
|
|
219
|
+
for (const key of currentKeys) {
|
|
220
|
+
if (!newKeys.has(key)) {
|
|
221
|
+
container.delete(key)
|
|
222
|
+
this.refCache.delete(key)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Set new/updated values
|
|
227
|
+
for (const key of newKeys) {
|
|
228
|
+
this.set(key, values[key])
|
|
229
|
+
}
|
|
230
|
+
} finally {
|
|
231
|
+
// Restore auto-commit state
|
|
232
|
+
if (!wasSuppressed) {
|
|
233
|
+
this.setSuppressAutoCommit(false)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Commit once after all operations
|
|
238
|
+
this.commitIfAuto()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Merge values into record.
|
|
243
|
+
* Existing keys not in `values` are kept.
|
|
244
|
+
*/
|
|
245
|
+
merge(values: Record<string, any>): void {
|
|
246
|
+
// Suppress auto-commit during batch operations
|
|
247
|
+
const wasSuppressed = this.isSuppressAutoCommit()
|
|
248
|
+
if (!wasSuppressed) {
|
|
249
|
+
this.setSuppressAutoCommit(true)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
// Set new/updated values (no deletions)
|
|
254
|
+
for (const key of Object.keys(values)) {
|
|
255
|
+
this.set(key, values[key])
|
|
256
|
+
}
|
|
257
|
+
} finally {
|
|
258
|
+
// Restore auto-commit state
|
|
259
|
+
if (!wasSuppressed) {
|
|
260
|
+
this.setSuppressAutoCommit(false)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Commit once after all operations
|
|
265
|
+
this.commitIfAuto()
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Remove all entries from the record.
|
|
270
|
+
*/
|
|
271
|
+
clear(): void {
|
|
272
|
+
const container = this.getContainer() as LoroMap
|
|
273
|
+
const keys = container.keys()
|
|
274
|
+
|
|
275
|
+
if (keys.length === 0) {
|
|
276
|
+
return // No-op on empty record
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Suppress auto-commit during batch operations
|
|
280
|
+
const wasSuppressed = this.isSuppressAutoCommit()
|
|
281
|
+
if (!wasSuppressed) {
|
|
282
|
+
this.setSuppressAutoCommit(true)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
// Delete all keys
|
|
287
|
+
for (const key of keys) {
|
|
288
|
+
container.delete(key)
|
|
289
|
+
this.refCache.delete(key)
|
|
290
|
+
}
|
|
291
|
+
} finally {
|
|
292
|
+
// Restore auto-commit state
|
|
293
|
+
if (!wasSuppressed) {
|
|
294
|
+
this.setSuppressAutoCommit(false)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Commit once after all operations
|
|
299
|
+
this.commitIfAuto()
|
|
300
|
+
}
|
|
301
|
+
|
|
188
302
|
/** Absorb mutated plain values back into Loro containers */
|
|
189
303
|
absorbPlainValues(): void {
|
|
190
304
|
absorbCachedPlainValues(this.refCache, () => this.getContainer() as LoroMap)
|
|
@@ -200,7 +314,7 @@ export class RecordRefInternals<
|
|
|
200
314
|
get container(): LoroMap {
|
|
201
315
|
return self.getContainer() as LoroMap
|
|
202
316
|
},
|
|
203
|
-
subscribe(callback: (event:
|
|
317
|
+
subscribe(callback: (event: LoroEventBatch) => void): Subscription {
|
|
204
318
|
return (self.getContainer() as LoroMap).subscribe(callback)
|
|
205
319
|
},
|
|
206
320
|
setContainer(key: string, container: Container): Container {
|