@loro-extended/change 0.9.0 → 1.0.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/README.md +179 -69
- package/dist/index.d.ts +369 -172
- package/dist/index.js +691 -382
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/change.test.ts +180 -175
- package/src/conversion.test.ts +91 -91
- package/src/conversion.ts +12 -12
- package/src/derive-placeholder.test.ts +14 -14
- package/src/derive-placeholder.ts +3 -3
- package/src/discriminated-union-assignability.test.ts +7 -7
- package/src/discriminated-union-tojson.test.ts +13 -24
- package/src/discriminated-union.test.ts +9 -8
- package/src/equality.test.ts +10 -2
- package/src/functional-helpers.test.ts +149 -0
- package/src/functional-helpers.ts +61 -0
- package/src/grand-unified-api.test.ts +423 -0
- package/src/index.ts +8 -6
- package/src/json-patch.test.ts +64 -56
- package/src/overlay-recursion.test.ts +326 -0
- package/src/overlay.ts +54 -17
- package/src/readonly.test.ts +27 -26
- package/src/shape.ts +103 -21
- package/src/typed-doc.ts +227 -58
- package/src/typed-refs/base.ts +33 -1
- package/src/typed-refs/counter.test.ts +44 -13
- package/src/typed-refs/counter.ts +42 -5
- package/src/typed-refs/doc.ts +29 -30
- package/src/typed-refs/json-compatibility.test.ts +37 -32
- package/src/typed-refs/list-base.ts +49 -21
- package/src/typed-refs/list.test.ts +4 -3
- package/src/typed-refs/movable-list.test.ts +3 -2
- package/src/typed-refs/movable-list.ts +6 -3
- package/src/typed-refs/proxy-handlers.ts +14 -1
- package/src/typed-refs/record.test.ts +116 -51
- package/src/typed-refs/record.ts +86 -81
- package/src/typed-refs/{map.ts → struct.ts} +66 -78
- package/src/typed-refs/text.ts +48 -7
- package/src/typed-refs/tree.ts +3 -3
- package/src/typed-refs/utils.ts +120 -13
- package/src/types.test.ts +34 -39
- package/src/types.ts +5 -40
- package/src/utils/type-guards.ts +11 -6
- package/src/validation.ts +10 -10
package/src/typed-refs/record.ts
CHANGED
|
@@ -1,40 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Container,
|
|
3
|
-
LoroCounter,
|
|
4
|
-
LoroList,
|
|
5
|
-
LoroMap,
|
|
6
|
-
LoroMovableList,
|
|
7
|
-
LoroText,
|
|
8
|
-
LoroTree,
|
|
9
|
-
type Value,
|
|
10
|
-
} from "loro-crdt"
|
|
1
|
+
import type { Container, LoroMap, Value } from "loro-crdt"
|
|
11
2
|
import { deriveShapePlaceholder } from "../derive-placeholder.js"
|
|
3
|
+
import { mergeValue } from "../overlay.js"
|
|
12
4
|
import type {
|
|
13
5
|
ContainerOrValueShape,
|
|
14
6
|
ContainerShape,
|
|
15
7
|
RecordContainerShape,
|
|
16
8
|
} from "../shape.js"
|
|
17
|
-
import type { Infer,
|
|
9
|
+
import type { Infer, InferMutableType } from "../types.js"
|
|
18
10
|
import { isContainerShape, isValueShape } from "../utils/type-guards.js"
|
|
19
11
|
import { TypedRef, type TypedRefParams } from "./base.js"
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
text: LoroText,
|
|
29
|
-
tree: LoroTree,
|
|
30
|
-
} as const
|
|
12
|
+
import {
|
|
13
|
+
absorbCachedPlainValues,
|
|
14
|
+
assignPlainValueToTypedRef,
|
|
15
|
+
containerConstructor,
|
|
16
|
+
createContainerTypedRef,
|
|
17
|
+
serializeRefToJSON,
|
|
18
|
+
unwrapReadonlyPrimitive,
|
|
19
|
+
} from "./utils.js"
|
|
31
20
|
|
|
32
21
|
// Record typed ref
|
|
33
22
|
export class RecordRef<
|
|
34
23
|
NestedShape extends ContainerOrValueShape,
|
|
35
24
|
> extends TypedRef<any> {
|
|
36
25
|
[key: string]: Infer<NestedShape> | any
|
|
37
|
-
private
|
|
26
|
+
private refCache = new Map<string, TypedRef<ContainerShape> | Value>()
|
|
38
27
|
|
|
39
28
|
protected get shape(): RecordContainerShape<NestedShape> {
|
|
40
29
|
return super.shape as RecordContainerShape<NestedShape>
|
|
@@ -45,16 +34,7 @@ export class RecordRef<
|
|
|
45
34
|
}
|
|
46
35
|
|
|
47
36
|
absorbPlainValues() {
|
|
48
|
-
|
|
49
|
-
if (node instanceof TypedRef) {
|
|
50
|
-
// Contains a TypedRef, not a plain Value: keep recursing
|
|
51
|
-
node.absorbPlainValues()
|
|
52
|
-
continue
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Plain value!
|
|
56
|
-
this.container.set(key, node)
|
|
57
|
-
}
|
|
37
|
+
absorbCachedPlainValues(this.refCache, () => this.container)
|
|
58
38
|
}
|
|
59
39
|
|
|
60
40
|
getTypedRefParams<S extends ContainerShape>(
|
|
@@ -79,85 +59,94 @@ export class RecordRef<
|
|
|
79
59
|
getContainer: () =>
|
|
80
60
|
this.container.getOrCreateContainer(key, new (LoroContainer as any)()),
|
|
81
61
|
readonly: this.readonly,
|
|
62
|
+
autoCommit: this._params.autoCommit,
|
|
63
|
+
getDoc: this._params.getDoc,
|
|
82
64
|
}
|
|
83
65
|
}
|
|
84
66
|
|
|
85
|
-
|
|
86
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Gets an existing ref for a key, or returns undefined if the key doesn't exist.
|
|
69
|
+
* Used for reading operations where we want optional chaining to work.
|
|
70
|
+
*/
|
|
71
|
+
getRef(key: string): any {
|
|
72
|
+
// For container shapes, check if the key exists first
|
|
87
73
|
// This allows optional chaining (?.) to work correctly for non-existent keys
|
|
88
|
-
|
|
89
|
-
if (this.readonly && isContainerShape(this.shape.shape)) {
|
|
74
|
+
if (isContainerShape(this.shape.shape)) {
|
|
90
75
|
const existing = this.container.get(key)
|
|
91
76
|
if (existing === undefined) {
|
|
92
77
|
return undefined
|
|
93
78
|
}
|
|
94
79
|
}
|
|
95
80
|
|
|
96
|
-
|
|
97
|
-
|
|
81
|
+
return this.getOrCreateRef(key)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Gets or creates a ref for a key.
|
|
86
|
+
* Always creates the container if it doesn't exist.
|
|
87
|
+
* This is the method used for write operations.
|
|
88
|
+
*/
|
|
89
|
+
getOrCreateRef(key: string): any {
|
|
90
|
+
let ref = this.refCache.get(key)
|
|
91
|
+
if (!ref) {
|
|
98
92
|
const shape = this.shape.shape
|
|
99
93
|
if (isContainerShape(shape)) {
|
|
100
|
-
|
|
94
|
+
ref = createContainerTypedRef(
|
|
101
95
|
this.getTypedRefParams(key, shape as ContainerShape),
|
|
102
96
|
)
|
|
103
|
-
// Cache container
|
|
104
|
-
this.
|
|
97
|
+
// Cache container refs
|
|
98
|
+
this.refCache.set(key, ref)
|
|
105
99
|
} else {
|
|
106
100
|
// For value shapes, first try to get the value from the container
|
|
107
101
|
const containerValue = this.container.get(key)
|
|
108
102
|
if (containerValue !== undefined) {
|
|
109
|
-
|
|
103
|
+
ref = containerValue as Value
|
|
110
104
|
} else {
|
|
111
105
|
// Only fall back to placeholder if the container doesn't have the value
|
|
112
106
|
const placeholder = (this.placeholder as any)?.[key]
|
|
113
107
|
if (placeholder === undefined) {
|
|
114
108
|
// If it's a value type and not in container or placeholder,
|
|
115
109
|
// fallback to the default value from the shape
|
|
116
|
-
|
|
110
|
+
ref = (shape as any)._plain
|
|
117
111
|
} else {
|
|
118
|
-
|
|
112
|
+
ref = placeholder as Value
|
|
119
113
|
}
|
|
120
114
|
}
|
|
121
115
|
// Only cache primitive values if NOT readonly
|
|
122
|
-
if (
|
|
123
|
-
this.
|
|
116
|
+
if (ref !== undefined && !this.readonly) {
|
|
117
|
+
this.refCache.set(key, ref)
|
|
124
118
|
}
|
|
125
119
|
}
|
|
126
120
|
}
|
|
127
121
|
|
|
128
122
|
if (this.readonly && isContainerShape(this.shape.shape)) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (shape._type === "text") {
|
|
134
|
-
return (node as any).toString()
|
|
135
|
-
}
|
|
123
|
+
return unwrapReadonlyPrimitive(
|
|
124
|
+
ref as TypedRef<any>,
|
|
125
|
+
this.shape.shape as ContainerShape,
|
|
126
|
+
)
|
|
136
127
|
}
|
|
137
128
|
|
|
138
|
-
return
|
|
129
|
+
return ref as any
|
|
139
130
|
}
|
|
140
131
|
|
|
141
|
-
get(key: string):
|
|
142
|
-
return this.
|
|
132
|
+
get(key: string): InferMutableType<NestedShape> {
|
|
133
|
+
return this.getRef(key)
|
|
143
134
|
}
|
|
144
135
|
|
|
145
136
|
set(key: string, value: any): void {
|
|
146
|
-
|
|
137
|
+
this.assertMutable()
|
|
147
138
|
if (isValueShape(this.shape.shape)) {
|
|
148
139
|
this.container.set(key, value)
|
|
149
|
-
this.
|
|
140
|
+
this.refCache.set(key, value)
|
|
141
|
+
this.commitIfAuto()
|
|
150
142
|
} else {
|
|
151
|
-
// For
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return
|
|
158
|
-
}
|
|
143
|
+
// For container shapes, try to assign the plain value
|
|
144
|
+
// Use getOrCreateRef to ensure the container is created
|
|
145
|
+
const ref = this.getOrCreateRef(key)
|
|
146
|
+
if (assignPlainValueToTypedRef(ref, value)) {
|
|
147
|
+
this.commitIfAuto()
|
|
148
|
+
return
|
|
159
149
|
}
|
|
160
|
-
|
|
161
150
|
throw new Error(
|
|
162
151
|
"Cannot set container directly, modify the typed ref instead",
|
|
163
152
|
)
|
|
@@ -165,14 +154,17 @@ export class RecordRef<
|
|
|
165
154
|
}
|
|
166
155
|
|
|
167
156
|
setContainer<C extends Container>(key: string, container: C): C {
|
|
168
|
-
|
|
169
|
-
|
|
157
|
+
this.assertMutable()
|
|
158
|
+
const result = this.container.setContainer(key, container)
|
|
159
|
+
this.commitIfAuto()
|
|
160
|
+
return result
|
|
170
161
|
}
|
|
171
162
|
|
|
172
163
|
delete(key: string): void {
|
|
173
|
-
|
|
164
|
+
this.assertMutable()
|
|
174
165
|
this.container.delete(key)
|
|
175
|
-
this.
|
|
166
|
+
this.refCache.delete(key)
|
|
167
|
+
this.commitIfAuto()
|
|
176
168
|
}
|
|
177
169
|
|
|
178
170
|
has(key: string): boolean {
|
|
@@ -191,16 +183,29 @@ export class RecordRef<
|
|
|
191
183
|
return this.container.size
|
|
192
184
|
}
|
|
193
185
|
|
|
194
|
-
toJSON(): Record<string,
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
186
|
+
toJSON(): Record<string, Infer<NestedShape>> {
|
|
187
|
+
// Fast path: readonly mode
|
|
188
|
+
if (this.readonly) {
|
|
189
|
+
const nativeJson = this.container.toJSON() as Record<string, any>
|
|
190
|
+
// For records, we need to overlay placeholders for each entry's nested shape
|
|
191
|
+
const result: Record<string, Infer<NestedShape>> = {}
|
|
192
|
+
for (const key of Object.keys(nativeJson)) {
|
|
193
|
+
// For records, the placeholder is always {}, so we need to derive
|
|
194
|
+
// the placeholder for the nested shape on the fly
|
|
195
|
+
const nestedPlaceholderValue = deriveShapePlaceholder(this.shape.shape)
|
|
196
|
+
|
|
197
|
+
result[key] = mergeValue(
|
|
198
|
+
this.shape.shape,
|
|
199
|
+
nativeJson[key],
|
|
200
|
+
nestedPlaceholderValue as Value,
|
|
201
|
+
) as Infer<NestedShape>
|
|
202
202
|
}
|
|
203
|
+
return result
|
|
203
204
|
}
|
|
204
|
-
|
|
205
|
+
|
|
206
|
+
return serializeRefToJSON(this, this.keys()) as Record<
|
|
207
|
+
string,
|
|
208
|
+
Infer<NestedShape>
|
|
209
|
+
>
|
|
205
210
|
}
|
|
206
211
|
}
|
|
@@ -1,46 +1,39 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
LoroCounter,
|
|
4
|
-
LoroList,
|
|
5
|
-
LoroMap,
|
|
6
|
-
LoroMovableList,
|
|
7
|
-
LoroText,
|
|
8
|
-
LoroTree,
|
|
9
|
-
type Value,
|
|
10
|
-
} from "loro-crdt"
|
|
1
|
+
import type { Container, LoroMap, Value } from "loro-crdt"
|
|
2
|
+
import { mergeValue } from "../overlay.js"
|
|
11
3
|
import type {
|
|
12
4
|
ContainerOrValueShape,
|
|
13
5
|
ContainerShape,
|
|
14
|
-
|
|
6
|
+
StructContainerShape,
|
|
15
7
|
ValueShape,
|
|
16
8
|
} from "../shape.js"
|
|
9
|
+
import type { Infer } from "../types.js"
|
|
17
10
|
import { isContainerShape, isValueShape } from "../utils/type-guards.js"
|
|
18
11
|
import { TypedRef, type TypedRefParams } from "./base.js"
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export class
|
|
12
|
+
import {
|
|
13
|
+
absorbCachedPlainValues,
|
|
14
|
+
assignPlainValueToTypedRef,
|
|
15
|
+
containerConstructor,
|
|
16
|
+
createContainerTypedRef,
|
|
17
|
+
serializeRefToJSON,
|
|
18
|
+
unwrapReadonlyPrimitive,
|
|
19
|
+
} from "./utils.js"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Typed ref for struct containers (objects with fixed keys).
|
|
23
|
+
* Uses LoroMap as the underlying container.
|
|
24
|
+
*/
|
|
25
|
+
export class StructRef<
|
|
33
26
|
NestedShapes extends Record<string, ContainerOrValueShape>,
|
|
34
27
|
> extends TypedRef<any> {
|
|
35
28
|
private propertyCache = new Map<string, TypedRef<ContainerShape> | Value>()
|
|
36
29
|
|
|
37
|
-
constructor(params: TypedRefParams<
|
|
30
|
+
constructor(params: TypedRefParams<StructContainerShape<NestedShapes>>) {
|
|
38
31
|
super(params)
|
|
39
32
|
this.createLazyProperties()
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
protected get shape():
|
|
43
|
-
return super.shape as
|
|
35
|
+
protected get shape(): StructContainerShape<NestedShapes> {
|
|
36
|
+
return super.shape as StructContainerShape<NestedShapes>
|
|
44
37
|
}
|
|
45
38
|
|
|
46
39
|
protected get container(): LoroMap {
|
|
@@ -48,16 +41,7 @@ export class MapRef<
|
|
|
48
41
|
}
|
|
49
42
|
|
|
50
43
|
absorbPlainValues() {
|
|
51
|
-
|
|
52
|
-
if (node instanceof TypedRef) {
|
|
53
|
-
// Contains a TypedRef, not a plain Value: keep recursing
|
|
54
|
-
node.absorbPlainValues()
|
|
55
|
-
continue
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Plain value!
|
|
59
|
-
this.container.set(key, node)
|
|
60
|
-
}
|
|
44
|
+
absorbCachedPlainValues(this.propertyCache, () => this.container)
|
|
61
45
|
}
|
|
62
46
|
|
|
63
47
|
getTypedRefParams<S extends ContainerShape>(
|
|
@@ -74,40 +58,42 @@ export class MapRef<
|
|
|
74
58
|
getContainer: () =>
|
|
75
59
|
this.container.getOrCreateContainer(key, new (LoroContainer as any)()),
|
|
76
60
|
readonly: this.readonly,
|
|
61
|
+
autoCommit: this._params.autoCommit,
|
|
62
|
+
getDoc: this._params.getDoc,
|
|
77
63
|
}
|
|
78
64
|
}
|
|
79
65
|
|
|
80
|
-
|
|
66
|
+
getOrCreateRef<Shape extends ContainerShape | ValueShape>(
|
|
81
67
|
key: string,
|
|
82
68
|
shape: Shape,
|
|
83
69
|
): any {
|
|
84
|
-
let
|
|
85
|
-
if (!
|
|
70
|
+
let ref = this.propertyCache.get(key)
|
|
71
|
+
if (!ref) {
|
|
86
72
|
if (isContainerShape(shape)) {
|
|
87
|
-
|
|
88
|
-
// We cache container
|
|
89
|
-
this.propertyCache.set(key,
|
|
73
|
+
ref = createContainerTypedRef(this.getTypedRefParams(key, shape))
|
|
74
|
+
// We cache container refs even in readonly mode because they are just handles
|
|
75
|
+
this.propertyCache.set(key, ref)
|
|
90
76
|
} else {
|
|
91
77
|
// For value shapes, first try to get the value from the container
|
|
92
78
|
const containerValue = this.container.get(key)
|
|
93
79
|
if (containerValue !== undefined) {
|
|
94
|
-
|
|
80
|
+
ref = containerValue as Value
|
|
95
81
|
} else {
|
|
96
82
|
// Only fall back to placeholder if the container doesn't have the value
|
|
97
83
|
const placeholder = (this.placeholder as any)?.[key]
|
|
98
84
|
if (placeholder === undefined) {
|
|
99
85
|
throw new Error("placeholder required")
|
|
100
86
|
}
|
|
101
|
-
|
|
87
|
+
ref = placeholder as Value
|
|
102
88
|
}
|
|
103
89
|
|
|
104
90
|
// In readonly mode, we DO NOT cache primitive values.
|
|
105
91
|
// This ensures we always get the latest value from the CRDT on next access.
|
|
106
92
|
if (!this.readonly) {
|
|
107
|
-
this.propertyCache.set(key,
|
|
93
|
+
this.propertyCache.set(key, ref)
|
|
108
94
|
}
|
|
109
95
|
}
|
|
110
|
-
if (
|
|
96
|
+
if (ref === undefined) throw new Error("no container made")
|
|
111
97
|
}
|
|
112
98
|
|
|
113
99
|
if (this.readonly && isContainerShape(shape)) {
|
|
@@ -118,34 +104,27 @@ export class MapRef<
|
|
|
118
104
|
return (this.placeholder as any)?.[key]
|
|
119
105
|
}
|
|
120
106
|
|
|
121
|
-
|
|
122
|
-
return (node as any).value
|
|
123
|
-
}
|
|
124
|
-
if (shape._type === "text") {
|
|
125
|
-
return (node as any).toString()
|
|
126
|
-
}
|
|
107
|
+
return unwrapReadonlyPrimitive(ref as TypedRef<any>, shape)
|
|
127
108
|
}
|
|
128
109
|
|
|
129
|
-
return
|
|
110
|
+
return ref as Shape extends ContainerShape ? TypedRef<Shape> : Value
|
|
130
111
|
}
|
|
131
112
|
|
|
132
113
|
private createLazyProperties(): void {
|
|
133
114
|
for (const key in this.shape.shapes) {
|
|
134
115
|
const shape = this.shape.shapes[key]
|
|
135
116
|
Object.defineProperty(this, key, {
|
|
136
|
-
get: () => this.
|
|
117
|
+
get: () => this.getOrCreateRef(key, shape),
|
|
137
118
|
set: value => {
|
|
138
|
-
|
|
119
|
+
this.assertMutable()
|
|
139
120
|
if (isValueShape(shape)) {
|
|
140
121
|
this.container.set(key, value)
|
|
141
122
|
this.propertyCache.set(key, value)
|
|
142
123
|
} else {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return
|
|
148
|
-
}
|
|
124
|
+
// For container shapes, try to assign the plain value
|
|
125
|
+
const ref = this.getOrCreateRef(key, shape)
|
|
126
|
+
if (assignPlainValueToTypedRef(ref as TypedRef<any>, value)) {
|
|
127
|
+
return
|
|
149
128
|
}
|
|
150
129
|
throw new Error(
|
|
151
130
|
"Cannot set container directly, modify the typed ref instead",
|
|
@@ -157,37 +136,46 @@ export class MapRef<
|
|
|
157
136
|
}
|
|
158
137
|
}
|
|
159
138
|
|
|
160
|
-
toJSON():
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
139
|
+
toJSON(): Infer<StructContainerShape<NestedShapes>> {
|
|
140
|
+
// Fast path: readonly mode
|
|
141
|
+
if (this.readonly) {
|
|
142
|
+
const nativeJson = this.container.toJSON() as Value
|
|
143
|
+
// Overlay placeholders for missing properties
|
|
144
|
+
return mergeValue(
|
|
145
|
+
this.shape,
|
|
146
|
+
nativeJson,
|
|
147
|
+
this.placeholder as Value,
|
|
148
|
+
) as Infer<StructContainerShape<NestedShapes>>
|
|
169
149
|
}
|
|
170
|
-
|
|
150
|
+
|
|
151
|
+
return serializeRefToJSON(
|
|
152
|
+
this as any,
|
|
153
|
+
Object.keys(this.shape.shapes),
|
|
154
|
+
) as Infer<StructContainerShape<NestedShapes>>
|
|
171
155
|
}
|
|
172
156
|
|
|
173
|
-
//
|
|
157
|
+
// TODO(duane): return correct type here
|
|
174
158
|
get(key: string): any {
|
|
175
159
|
return this.container.get(key)
|
|
176
160
|
}
|
|
177
161
|
|
|
178
162
|
set(key: string, value: Value): void {
|
|
179
|
-
|
|
163
|
+
this.assertMutable()
|
|
180
164
|
this.container.set(key, value)
|
|
165
|
+
this.commitIfAuto()
|
|
181
166
|
}
|
|
182
167
|
|
|
183
168
|
setContainer<C extends Container>(key: string, container: C): C {
|
|
184
|
-
|
|
185
|
-
|
|
169
|
+
this.assertMutable()
|
|
170
|
+
const result = this.container.setContainer(key, container)
|
|
171
|
+
this.commitIfAuto()
|
|
172
|
+
return result
|
|
186
173
|
}
|
|
187
174
|
|
|
188
175
|
delete(key: string): void {
|
|
189
|
-
|
|
176
|
+
this.assertMutable()
|
|
190
177
|
this.container.delete(key)
|
|
178
|
+
this.commitIfAuto()
|
|
191
179
|
}
|
|
192
180
|
|
|
193
181
|
has(key: string): boolean {
|
package/src/typed-refs/text.ts
CHANGED
|
@@ -1,44 +1,83 @@
|
|
|
1
|
+
import type { LoroText } from "loro-crdt"
|
|
1
2
|
import type { TextContainerShape } from "../shape.js"
|
|
2
3
|
import { TypedRef } from "./base.js"
|
|
3
4
|
|
|
4
5
|
// Text typed ref
|
|
5
6
|
export class TextRef extends TypedRef<TextContainerShape> {
|
|
7
|
+
// Track if we've materialized the container (made any changes)
|
|
8
|
+
private _materialized = false
|
|
9
|
+
|
|
10
|
+
protected get container(): LoroText {
|
|
11
|
+
return super.container as LoroText
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
absorbPlainValues() {
|
|
7
15
|
// no plain values contained within
|
|
8
16
|
}
|
|
9
17
|
|
|
10
18
|
// Text methods
|
|
11
19
|
insert(index: number, content: string): void {
|
|
12
|
-
|
|
20
|
+
this.assertMutable()
|
|
21
|
+
this._materialized = true
|
|
13
22
|
this.container.insert(index, content)
|
|
23
|
+
this.commitIfAuto()
|
|
14
24
|
}
|
|
15
25
|
|
|
16
26
|
delete(index: number, len: number): void {
|
|
17
|
-
|
|
27
|
+
this.assertMutable()
|
|
28
|
+
this._materialized = true
|
|
18
29
|
this.container.delete(index, len)
|
|
30
|
+
this.commitIfAuto()
|
|
19
31
|
}
|
|
20
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Returns the text content.
|
|
35
|
+
* If the text hasn't been materialized (no operations performed),
|
|
36
|
+
* returns the placeholder value if available.
|
|
37
|
+
*/
|
|
21
38
|
toString(): string {
|
|
22
|
-
|
|
39
|
+
const containerValue = this.container.toString()
|
|
40
|
+
if (containerValue !== "" || this._materialized) {
|
|
41
|
+
return containerValue
|
|
42
|
+
}
|
|
43
|
+
// Return placeholder if available and container is at default state
|
|
44
|
+
if (this.placeholder !== undefined) {
|
|
45
|
+
return this.placeholder as string
|
|
46
|
+
}
|
|
47
|
+
return containerValue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
valueOf(): string {
|
|
51
|
+
return this.toString()
|
|
23
52
|
}
|
|
24
53
|
|
|
25
54
|
toJSON(): string {
|
|
26
55
|
return this.toString()
|
|
27
56
|
}
|
|
28
57
|
|
|
58
|
+
[Symbol.toPrimitive](_hint: string): string {
|
|
59
|
+
return this.toString()
|
|
60
|
+
}
|
|
61
|
+
|
|
29
62
|
update(text: string): void {
|
|
30
|
-
|
|
63
|
+
this.assertMutable()
|
|
64
|
+
this._materialized = true
|
|
31
65
|
this.container.update(text)
|
|
66
|
+
this.commitIfAuto()
|
|
32
67
|
}
|
|
33
68
|
|
|
34
69
|
mark(range: { start: number; end: number }, key: string, value: any): void {
|
|
35
|
-
|
|
70
|
+
this.assertMutable()
|
|
71
|
+
this._materialized = true
|
|
36
72
|
this.container.mark(range, key, value)
|
|
73
|
+
this.commitIfAuto()
|
|
37
74
|
}
|
|
38
75
|
|
|
39
76
|
unmark(range: { start: number; end: number }, key: string): void {
|
|
40
|
-
|
|
77
|
+
this.assertMutable()
|
|
78
|
+
this._materialized = true
|
|
41
79
|
this.container.unmark(range, key)
|
|
80
|
+
this.commitIfAuto()
|
|
42
81
|
}
|
|
43
82
|
|
|
44
83
|
toDelta(): any[] {
|
|
@@ -46,8 +85,10 @@ export class TextRef extends TypedRef<TextContainerShape> {
|
|
|
46
85
|
}
|
|
47
86
|
|
|
48
87
|
applyDelta(delta: any[]): void {
|
|
49
|
-
|
|
88
|
+
this.assertMutable()
|
|
89
|
+
this._materialized = true
|
|
50
90
|
this.container.applyDelta(delta)
|
|
91
|
+
this.commitIfAuto()
|
|
51
92
|
}
|
|
52
93
|
|
|
53
94
|
get length(): number {
|
package/src/typed-refs/tree.ts
CHANGED
|
@@ -8,17 +8,17 @@ export class TreeRef<T extends TreeContainerShape> extends TypedRef<T> {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
createNode(parent?: any, index?: number): any {
|
|
11
|
-
|
|
11
|
+
this.assertMutable()
|
|
12
12
|
return this.container.createNode(parent, index)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
move(target: any, parent?: any, index?: number): void {
|
|
16
|
-
|
|
16
|
+
this.assertMutable()
|
|
17
17
|
this.container.move(target, parent, index)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
delete(target: any): void {
|
|
21
|
-
|
|
21
|
+
this.assertMutable()
|
|
22
22
|
this.container.delete(target)
|
|
23
23
|
}
|
|
24
24
|
|