@loro-extended/change 0.2.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/LICENSE +21 -0
- package/README.md +565 -0
- package/dist/index.d.ts +339 -0
- package/dist/index.js +1491 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
- package/src/change.test.ts +2006 -0
- package/src/change.ts +105 -0
- package/src/conversion.test.ts +728 -0
- package/src/conversion.ts +220 -0
- package/src/draft-nodes/base.ts +34 -0
- package/src/draft-nodes/counter.ts +21 -0
- package/src/draft-nodes/doc.ts +81 -0
- package/src/draft-nodes/list-base.ts +326 -0
- package/src/draft-nodes/list.ts +18 -0
- package/src/draft-nodes/map.ts +156 -0
- package/src/draft-nodes/movable-list.ts +26 -0
- package/src/draft-nodes/record.ts +215 -0
- package/src/draft-nodes/text.ts +48 -0
- package/src/draft-nodes/tree.ts +31 -0
- package/src/draft-nodes/utils.ts +55 -0
- package/src/index.ts +33 -0
- package/src/json-patch.test.ts +697 -0
- package/src/json-patch.ts +391 -0
- package/src/overlay.ts +90 -0
- package/src/record.test.ts +188 -0
- package/src/schema.fixtures.ts +138 -0
- package/src/shape.ts +348 -0
- package/src/types.ts +15 -0
- package/src/utils/type-guards.ts +210 -0
- package/src/validation.ts +261 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Container,
|
|
3
|
+
LoroCounter,
|
|
4
|
+
LoroList,
|
|
5
|
+
LoroMap,
|
|
6
|
+
LoroMovableList,
|
|
7
|
+
LoroText,
|
|
8
|
+
LoroTree,
|
|
9
|
+
type Value,
|
|
10
|
+
} from "loro-crdt"
|
|
11
|
+
import type {
|
|
12
|
+
ContainerOrValueShape,
|
|
13
|
+
ContainerShape,
|
|
14
|
+
MapContainerShape,
|
|
15
|
+
ValueShape,
|
|
16
|
+
} from "../shape.js"
|
|
17
|
+
import { isContainerShape, isValueShape } from "../utils/type-guards.js"
|
|
18
|
+
import { DraftNode, type DraftNodeParams } from "./base.js"
|
|
19
|
+
import { createContainerDraftNode } from "./utils.js"
|
|
20
|
+
|
|
21
|
+
const containerConstructor = {
|
|
22
|
+
counter: LoroCounter,
|
|
23
|
+
list: LoroList,
|
|
24
|
+
map: LoroMap,
|
|
25
|
+
movableList: LoroMovableList,
|
|
26
|
+
record: LoroMap,
|
|
27
|
+
text: LoroText,
|
|
28
|
+
tree: LoroTree,
|
|
29
|
+
} as const
|
|
30
|
+
|
|
31
|
+
// Map draft node
|
|
32
|
+
export class MapDraftNode<
|
|
33
|
+
NestedShapes extends Record<string, ContainerOrValueShape>,
|
|
34
|
+
> extends DraftNode<any> {
|
|
35
|
+
private propertyCache = new Map<string, DraftNode<ContainerShape> | Value>()
|
|
36
|
+
|
|
37
|
+
constructor(params: DraftNodeParams<MapContainerShape<NestedShapes>>) {
|
|
38
|
+
super(params)
|
|
39
|
+
this.createLazyProperties()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected get shape(): MapContainerShape<NestedShapes> {
|
|
43
|
+
return super.shape as MapContainerShape<NestedShapes>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protected get container(): LoroMap {
|
|
47
|
+
return super.container as LoroMap
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
absorbPlainValues() {
|
|
51
|
+
for (const [key, node] of this.propertyCache.entries()) {
|
|
52
|
+
if (node instanceof DraftNode) {
|
|
53
|
+
// Contains a DraftNode, not a plain Value: keep recursing
|
|
54
|
+
node.absorbPlainValues()
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Plain value!
|
|
59
|
+
this.container.set(key, node)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getDraftNodeParams<S extends ContainerShape>(
|
|
64
|
+
key: string,
|
|
65
|
+
shape: S,
|
|
66
|
+
): DraftNodeParams<ContainerShape> {
|
|
67
|
+
const emptyState = (this.emptyState as any)?.[key]
|
|
68
|
+
|
|
69
|
+
const LoroContainer = containerConstructor[shape._type]
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
shape,
|
|
73
|
+
emptyState,
|
|
74
|
+
getContainer: () =>
|
|
75
|
+
this.container.getOrCreateContainer(key, new (LoroContainer as any)()),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getOrCreateNode<Shape extends ContainerShape | ValueShape>(
|
|
80
|
+
key: string,
|
|
81
|
+
shape: Shape,
|
|
82
|
+
): Shape extends ContainerShape ? DraftNode<Shape> : Value {
|
|
83
|
+
let node = this.propertyCache.get(key)
|
|
84
|
+
if (!node) {
|
|
85
|
+
if (isContainerShape(shape)) {
|
|
86
|
+
node = createContainerDraftNode(this.getDraftNodeParams(key, shape))
|
|
87
|
+
} else {
|
|
88
|
+
// For value shapes, first try to get the value from the container
|
|
89
|
+
const containerValue = this.container.get(key)
|
|
90
|
+
if (containerValue !== undefined) {
|
|
91
|
+
node = containerValue as Value
|
|
92
|
+
} else {
|
|
93
|
+
// Only fall back to empty state if the container doesn't have the value
|
|
94
|
+
const emptyState = (this.emptyState as any)?.[key]
|
|
95
|
+
if (!emptyState) {
|
|
96
|
+
throw new Error("empty state required")
|
|
97
|
+
}
|
|
98
|
+
node = emptyState as Value
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!node) throw new Error("no container made")
|
|
102
|
+
this.propertyCache.set(key, node)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return node as Shape extends ContainerShape ? DraftNode<Shape> : Value
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private createLazyProperties(): void {
|
|
109
|
+
for (const key in this.shape.shapes) {
|
|
110
|
+
const shape = this.shape.shapes[key]
|
|
111
|
+
Object.defineProperty(this, key, {
|
|
112
|
+
get: () => this.getOrCreateNode(key, shape),
|
|
113
|
+
set: isValueShape(shape)
|
|
114
|
+
? value => {
|
|
115
|
+
// console.log("set value", value)
|
|
116
|
+
this.container.set(key, value)
|
|
117
|
+
}
|
|
118
|
+
: undefined,
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// TOOD(duane): return correct type here
|
|
124
|
+
get(key: string): any {
|
|
125
|
+
return this.container.get(key)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
set(key: string, value: Value): void {
|
|
129
|
+
this.container.set(key, value)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
setContainer<C extends Container>(key: string, container: C): C {
|
|
133
|
+
return this.container.setContainer(key, container)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
delete(key: string): void {
|
|
137
|
+
this.container.delete(key)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
has(key: string): boolean {
|
|
141
|
+
// LoroMap doesn't have a has method, so we check if get returns undefined
|
|
142
|
+
return this.container.get(key) !== undefined
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
keys(): string[] {
|
|
146
|
+
return this.container.keys()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
values(): any[] {
|
|
150
|
+
return this.container.values()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
get size(): number {
|
|
154
|
+
return this.container.size
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Container, LoroMovableList } from "loro-crdt"
|
|
2
|
+
import type { ContainerOrValueShape } from "../shape.js"
|
|
3
|
+
import { ListDraftNodeBase } from "./list-base.js"
|
|
4
|
+
|
|
5
|
+
// Movable list draft node
|
|
6
|
+
export class MovableListDraftNode<
|
|
7
|
+
NestedShape extends ContainerOrValueShape,
|
|
8
|
+
Item = NestedShape["_plain"],
|
|
9
|
+
> extends ListDraftNodeBase<NestedShape> {
|
|
10
|
+
protected get container(): LoroMovableList {
|
|
11
|
+
return super.container as LoroMovableList
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
protected absorbValueAtIndex(index: number, value: any): void {
|
|
15
|
+
// LoroMovableList has set method
|
|
16
|
+
this.container.set(index, value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
move(from: number, to: number): void {
|
|
20
|
+
this.container.move(from, to)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
set(index: number, item: Exclude<Item, Container>) {
|
|
24
|
+
return this.container.set(index, item)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Container,
|
|
3
|
+
LoroCounter,
|
|
4
|
+
LoroList,
|
|
5
|
+
LoroMap,
|
|
6
|
+
LoroMovableList,
|
|
7
|
+
LoroText,
|
|
8
|
+
LoroTree,
|
|
9
|
+
type Value,
|
|
10
|
+
} from "loro-crdt"
|
|
11
|
+
import type {
|
|
12
|
+
ContainerOrValueShape,
|
|
13
|
+
ContainerShape,
|
|
14
|
+
RecordContainerShape,
|
|
15
|
+
} from "../shape.js"
|
|
16
|
+
import type { InferDraftType } from "../types.js"
|
|
17
|
+
import { isContainerShape, isValueShape } from "../utils/type-guards.js"
|
|
18
|
+
import { DraftNode, type DraftNodeParams } from "./base.js"
|
|
19
|
+
import { createContainerDraftNode } from "./utils.js"
|
|
20
|
+
|
|
21
|
+
const containerConstructor = {
|
|
22
|
+
counter: LoroCounter,
|
|
23
|
+
list: LoroList,
|
|
24
|
+
map: LoroMap,
|
|
25
|
+
movableList: LoroMovableList,
|
|
26
|
+
record: LoroMap,
|
|
27
|
+
text: LoroText,
|
|
28
|
+
tree: LoroTree,
|
|
29
|
+
} as const
|
|
30
|
+
|
|
31
|
+
// Record draft node
|
|
32
|
+
export class RecordDraftNode<
|
|
33
|
+
NestedShape extends ContainerOrValueShape,
|
|
34
|
+
> extends DraftNode<any> {
|
|
35
|
+
private nodeCache = new Map<string, DraftNode<ContainerShape> | Value>()
|
|
36
|
+
|
|
37
|
+
constructor(params: DraftNodeParams<RecordContainerShape<NestedShape>>) {
|
|
38
|
+
super(params)
|
|
39
|
+
// We don't need to create lazy properties because keys are dynamic
|
|
40
|
+
// But we could use a Proxy if we wanted property access syntax like record.key
|
|
41
|
+
// However, for now let's stick to get/set methods or maybe Proxy for better DX?
|
|
42
|
+
// The requirement says "records with uniform specific key type and value".
|
|
43
|
+
// Usually records are accessed via keys.
|
|
44
|
+
// If we want `draft.record.key`, we need a Proxy.
|
|
45
|
+
// biome-ignore lint/correctness/noConstructorReturn: Proxy return is intentional
|
|
46
|
+
return new Proxy(this, {
|
|
47
|
+
get: (target, prop) => {
|
|
48
|
+
if (typeof prop === "string" && !(prop in target)) {
|
|
49
|
+
return target.get(prop)
|
|
50
|
+
}
|
|
51
|
+
return Reflect.get(target, prop)
|
|
52
|
+
},
|
|
53
|
+
set: (target, prop, value) => {
|
|
54
|
+
if (typeof prop === "string" && !(prop in target)) {
|
|
55
|
+
target.set(prop, value)
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
return Reflect.set(target, prop, value)
|
|
59
|
+
},
|
|
60
|
+
deleteProperty: (target, prop) => {
|
|
61
|
+
if (typeof prop === "string" && !(prop in target)) {
|
|
62
|
+
target.delete(prop)
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
return Reflect.deleteProperty(target, prop)
|
|
66
|
+
},
|
|
67
|
+
ownKeys: target => {
|
|
68
|
+
return target.keys()
|
|
69
|
+
},
|
|
70
|
+
getOwnPropertyDescriptor: (target, prop) => {
|
|
71
|
+
if (typeof prop === "string" && target.has(prop)) {
|
|
72
|
+
return {
|
|
73
|
+
configurable: true,
|
|
74
|
+
enumerable: true,
|
|
75
|
+
value: target.get(prop),
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return Reflect.getOwnPropertyDescriptor(target, prop)
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
protected get shape(): RecordContainerShape<NestedShape> {
|
|
84
|
+
return super.shape as RecordContainerShape<NestedShape>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
protected get container(): LoroMap {
|
|
88
|
+
return super.container as LoroMap
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
absorbPlainValues() {
|
|
92
|
+
for (const [key, node] of this.nodeCache.entries()) {
|
|
93
|
+
if (node instanceof DraftNode) {
|
|
94
|
+
// Contains a DraftNode, not a plain Value: keep recursing
|
|
95
|
+
node.absorbPlainValues()
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Plain value!
|
|
100
|
+
this.container.set(key, node)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
getDraftNodeParams<S extends ContainerShape>(
|
|
105
|
+
key: string,
|
|
106
|
+
shape: S,
|
|
107
|
+
): DraftNodeParams<ContainerShape> {
|
|
108
|
+
const emptyState = (this.emptyState as any)?.[key]
|
|
109
|
+
|
|
110
|
+
const LoroContainer = containerConstructor[shape._type]
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
shape,
|
|
114
|
+
emptyState,
|
|
115
|
+
getContainer: () =>
|
|
116
|
+
this.container.getOrCreateContainer(key, new (LoroContainer as any)()),
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getOrCreateNode(key: string): InferDraftType<NestedShape> {
|
|
121
|
+
let node = this.nodeCache.get(key)
|
|
122
|
+
if (!node) {
|
|
123
|
+
const shape = this.shape.shape
|
|
124
|
+
if (isContainerShape(shape)) {
|
|
125
|
+
node = createContainerDraftNode(
|
|
126
|
+
this.getDraftNodeParams(key, shape as ContainerShape),
|
|
127
|
+
)
|
|
128
|
+
} else {
|
|
129
|
+
// For value shapes, first try to get the value from the container
|
|
130
|
+
const containerValue = this.container.get(key)
|
|
131
|
+
if (containerValue !== undefined) {
|
|
132
|
+
node = containerValue as Value
|
|
133
|
+
} else {
|
|
134
|
+
// Only fall back to empty state if the container doesn't have the value
|
|
135
|
+
const emptyState = (this.emptyState as any)?.[key]
|
|
136
|
+
// For records, empty state might not have the key, which is fine?
|
|
137
|
+
// But if we are accessing it, maybe we expect it to exist or be created?
|
|
138
|
+
// If it's a value type, we can't really "create" it without a value.
|
|
139
|
+
// So if it's undefined in container and empty state, we return undefined?
|
|
140
|
+
// But the return type expects Value.
|
|
141
|
+
// Let's check MapDraftNode.
|
|
142
|
+
// MapDraftNode throws "empty state required" if not found.
|
|
143
|
+
// But for Record, keys are dynamic.
|
|
144
|
+
if (emptyState === undefined) {
|
|
145
|
+
// If it's a value type and not in container or empty state,
|
|
146
|
+
// we should probably return undefined if the type allows it,
|
|
147
|
+
// or maybe the default value for that type?
|
|
148
|
+
// But we don't have a default value generator for shapes.
|
|
149
|
+
// Actually Shape.plain.* factories have _plain and _draft which are defaults.
|
|
150
|
+
node = (shape as any)._plain
|
|
151
|
+
} else {
|
|
152
|
+
node = emptyState as Value
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (node !== undefined) {
|
|
157
|
+
this.nodeCache.set(key, node)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return node as any
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
get(key: string): InferDraftType<NestedShape> {
|
|
165
|
+
return this.getOrCreateNode(key)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
set(key: string, value: any): void {
|
|
169
|
+
if (isValueShape(this.shape.shape)) {
|
|
170
|
+
this.container.set(key, value)
|
|
171
|
+
// Update cache if needed?
|
|
172
|
+
// MapDraftNode updates container directly for values.
|
|
173
|
+
// But we also cache values in nodeCache for consistency?
|
|
174
|
+
// MapDraftNode doesn't cache values in propertyCache if they are set via setter?
|
|
175
|
+
// Actually MapDraftNode setter:
|
|
176
|
+
// set: isValueShape(shape) ? value => this.container.set(key, value) : undefined
|
|
177
|
+
// It doesn't update propertyCache.
|
|
178
|
+
// But getOrCreateNode checks propertyCache first.
|
|
179
|
+
// So if we set it, we should probably update propertyCache or clear it for that key.
|
|
180
|
+
this.nodeCache.set(key, value)
|
|
181
|
+
} else {
|
|
182
|
+
// For containers, we can't set them directly usually.
|
|
183
|
+
// But if the user passes a plain object that matches the shape, maybe we should convert it?
|
|
184
|
+
// But typically we modify the draft node.
|
|
185
|
+
throw new Error(
|
|
186
|
+
"Cannot set container directly, modify the draft node instead",
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
setContainer<C extends Container>(key: string, container: C): C {
|
|
192
|
+
return this.container.setContainer(key, container)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
delete(key: string): void {
|
|
196
|
+
this.container.delete(key)
|
|
197
|
+
this.nodeCache.delete(key)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
has(key: string): boolean {
|
|
201
|
+
return this.container.get(key) !== undefined
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
keys(): string[] {
|
|
205
|
+
return this.container.keys()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
values(): any[] {
|
|
209
|
+
return this.container.values()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
get size(): number {
|
|
213
|
+
return this.container.size
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { TextContainerShape } from "../shape.js"
|
|
2
|
+
import { DraftNode } from "./base.js"
|
|
3
|
+
|
|
4
|
+
// Text draft node
|
|
5
|
+
export class TextDraftNode extends DraftNode<TextContainerShape> {
|
|
6
|
+
absorbPlainValues() {
|
|
7
|
+
// no plain values contained within
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Text methods
|
|
11
|
+
insert(index: number, content: string): void {
|
|
12
|
+
// TODO(duane): condition can be removed when https://github.com/loro-dev/loro/issues/872 is addressed
|
|
13
|
+
if (content.length === 0) return
|
|
14
|
+
this.container.insert(index, content)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
delete(index: number, len: number): void {
|
|
18
|
+
this.container.delete(index, len)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
toString(): string {
|
|
22
|
+
return this.container.toString()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
update(text: string): void {
|
|
26
|
+
this.container.update(text)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
mark(range: { start: number; end: number }, key: string, value: any): void {
|
|
30
|
+
this.container.mark(range, key, value)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
unmark(range: { start: number; end: number }, key: string): void {
|
|
34
|
+
this.container.unmark(range, key)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
toDelta(): any[] {
|
|
38
|
+
return this.container.toDelta()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
applyDelta(delta: any[]): void {
|
|
42
|
+
this.container.applyDelta(delta)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get length(): number {
|
|
46
|
+
return this.container.length
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { TreeContainerShape } from "../shape.js"
|
|
2
|
+
import { DraftNode } from "./base.js"
|
|
3
|
+
|
|
4
|
+
// Tree draft node
|
|
5
|
+
export class TreeDraftNode<T extends TreeContainerShape> extends DraftNode<T> {
|
|
6
|
+
absorbPlainValues() {
|
|
7
|
+
// TODO(duane): implement for trees
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
createNode(parent?: any, index?: number): any {
|
|
11
|
+
return this.container.createNode(parent, index)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
move(target: any, parent?: any, index?: number): void {
|
|
15
|
+
this.container.move(target, parent, index)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
delete(target: any): void {
|
|
19
|
+
this.container.delete(target)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
has(target: any): boolean {
|
|
23
|
+
return this.container.has(target)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getNodeByID(id: any): any {
|
|
27
|
+
return this.container.getNodeByID
|
|
28
|
+
? this.container.getNodeByID(id)
|
|
29
|
+
: undefined
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ContainerShape,
|
|
3
|
+
CounterContainerShape,
|
|
4
|
+
ListContainerShape,
|
|
5
|
+
MapContainerShape,
|
|
6
|
+
MovableListContainerShape,
|
|
7
|
+
RecordContainerShape,
|
|
8
|
+
TextContainerShape,
|
|
9
|
+
TreeContainerShape,
|
|
10
|
+
} from "../shape.js"
|
|
11
|
+
import type { DraftNode, DraftNodeParams } from "./base.js"
|
|
12
|
+
import { CounterDraftNode } from "./counter.js"
|
|
13
|
+
import { ListDraftNode } from "./list.js"
|
|
14
|
+
import { MapDraftNode } from "./map.js"
|
|
15
|
+
import { MovableListDraftNode } from "./movable-list.js"
|
|
16
|
+
import { RecordDraftNode } from "./record.js"
|
|
17
|
+
import { TextDraftNode } from "./text.js"
|
|
18
|
+
import { TreeDraftNode } from "./tree.js"
|
|
19
|
+
|
|
20
|
+
// Generic catch-all overload
|
|
21
|
+
export function createContainerDraftNode<T extends ContainerShape>(
|
|
22
|
+
params: DraftNodeParams<T>,
|
|
23
|
+
): DraftNode<T>
|
|
24
|
+
|
|
25
|
+
// Implementation
|
|
26
|
+
export function createContainerDraftNode(
|
|
27
|
+
params: DraftNodeParams<ContainerShape>,
|
|
28
|
+
): DraftNode<ContainerShape> {
|
|
29
|
+
switch (params.shape._type) {
|
|
30
|
+
case "counter":
|
|
31
|
+
return new CounterDraftNode(
|
|
32
|
+
params as DraftNodeParams<CounterContainerShape>,
|
|
33
|
+
)
|
|
34
|
+
case "list":
|
|
35
|
+
return new ListDraftNode(params as DraftNodeParams<ListContainerShape>)
|
|
36
|
+
case "map":
|
|
37
|
+
return new MapDraftNode(params as DraftNodeParams<MapContainerShape>)
|
|
38
|
+
case "movableList":
|
|
39
|
+
return new MovableListDraftNode(
|
|
40
|
+
params as DraftNodeParams<MovableListContainerShape>,
|
|
41
|
+
)
|
|
42
|
+
case "record":
|
|
43
|
+
return new RecordDraftNode(
|
|
44
|
+
params as DraftNodeParams<RecordContainerShape>,
|
|
45
|
+
)
|
|
46
|
+
case "text":
|
|
47
|
+
return new TextDraftNode(params as DraftNodeParams<TextContainerShape>)
|
|
48
|
+
case "tree":
|
|
49
|
+
return new TreeDraftNode(params as DraftNodeParams<TreeContainerShape>)
|
|
50
|
+
default:
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Unknown container type: ${(params.shape as ContainerShape)._type}`,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Main API exports
|
|
2
|
+
export { createTypedDoc, TypedDoc } from "./change.js"
|
|
3
|
+
export { mergeValue, overlayEmptyState } from "./overlay.js"
|
|
4
|
+
export type {
|
|
5
|
+
ArrayValueShape,
|
|
6
|
+
ContainerOrValueShape,
|
|
7
|
+
ContainerShape,
|
|
8
|
+
ContainerType as RootContainerType,
|
|
9
|
+
// Container shapes
|
|
10
|
+
CounterContainerShape,
|
|
11
|
+
// Schema node types
|
|
12
|
+
DocShape,
|
|
13
|
+
ListContainerShape,
|
|
14
|
+
MapContainerShape,
|
|
15
|
+
MovableListContainerShape,
|
|
16
|
+
RecordContainerShape,
|
|
17
|
+
RecordValueShape,
|
|
18
|
+
TextContainerShape,
|
|
19
|
+
TreeContainerShape,
|
|
20
|
+
// Value shapes
|
|
21
|
+
ValueShape,
|
|
22
|
+
// ...
|
|
23
|
+
} from "./shape.js"
|
|
24
|
+
// Schema and type exports
|
|
25
|
+
export { Shape } from "./shape.js"
|
|
26
|
+
export type {
|
|
27
|
+
Draft,
|
|
28
|
+
InferDraftType,
|
|
29
|
+
// Type inference
|
|
30
|
+
InferPlainType,
|
|
31
|
+
} from "./types.js"
|
|
32
|
+
// Utility exports
|
|
33
|
+
export { validateEmptyState } from "./validation.js"
|