@loro-extended/change 4.0.0 → 5.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 +173 -149
- package/dist/index.d.ts +962 -335
- package/dist/index.js +1040 -598
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/change.test.ts +51 -52
- package/src/functional-helpers.test.ts +316 -4
- package/src/functional-helpers.ts +96 -6
- package/src/grand-unified-api.test.ts +35 -29
- package/src/index.ts +25 -1
- package/src/json-patch.test.ts +46 -27
- package/src/loro.test.ts +449 -0
- package/src/loro.ts +273 -0
- package/src/overlay-recursion.test.ts +1 -1
- package/src/path-evaluator.ts +1 -1
- package/src/path-selector.test.ts +94 -1
- package/src/shape.ts +47 -15
- package/src/typed-doc.ts +99 -98
- package/src/typed-refs/base.ts +126 -35
- package/src/typed-refs/counter-ref-internals.ts +62 -0
- package/src/typed-refs/{counter.test.ts → counter-ref.test.ts} +5 -4
- package/src/typed-refs/counter-ref.ts +45 -0
- package/src/typed-refs/{doc.ts → doc-ref-internals.ts} +33 -38
- package/src/typed-refs/doc-ref.ts +47 -0
- package/src/typed-refs/encapsulation.test.ts +226 -0
- package/src/typed-refs/list-ref-base-internals.ts +280 -0
- package/src/typed-refs/{list-base.ts → list-ref-base.ts} +255 -160
- package/src/typed-refs/list-ref-internals.ts +21 -0
- package/src/typed-refs/{list.ts → list-ref.ts} +10 -11
- package/src/typed-refs/movable-list-ref-internals.ts +38 -0
- package/src/typed-refs/movable-list-ref.ts +31 -0
- package/src/typed-refs/proxy-handlers.ts +13 -4
- package/src/typed-refs/{record.ts → record-ref-internals.ts} +78 -79
- package/src/typed-refs/{record.test.ts → record-ref.test.ts} +21 -16
- package/src/typed-refs/record-ref.ts +80 -0
- package/src/typed-refs/struct-ref-internals.ts +195 -0
- package/src/typed-refs/{struct-value-updates.test.ts → struct-ref.test.ts} +5 -3
- package/src/typed-refs/struct-ref.ts +257 -0
- package/src/typed-refs/text-ref-internals.ts +100 -0
- package/src/typed-refs/text-ref.ts +72 -0
- package/src/typed-refs/tree-node-ref-internals.ts +111 -0
- package/src/typed-refs/{tree-node.ts → tree-node-ref.ts} +58 -94
- package/src/typed-refs/tree-ref-internals.ts +110 -0
- package/src/typed-refs/tree-ref.ts +194 -0
- package/src/typed-refs/utils.ts +21 -23
- package/src/typed-refs/counter.ts +0 -62
- package/src/typed-refs/movable-list.ts +0 -32
- package/src/typed-refs/struct.ts +0 -201
- package/src/typed-refs/text.ts +0 -91
- package/src/typed-refs/tree.ts +0 -268
- /package/src/typed-refs/{list-value-updates.test.ts → list-ref-value-updates.test.ts} +0 -0
- /package/src/typed-refs/{list.test.ts → list-ref.test.ts} +0 -0
- /package/src/typed-refs/{movable-list.test.ts → movable-list-ref.test.ts} +0 -0
- /package/src/typed-refs/{record-value-updates.test.ts → record-ref-value-updates.test.ts} +0 -0
- /package/src/typed-refs/{tree-node-value-updates.test.ts → tree-node-ref.test.ts} +0 -0
- /package/src/typed-refs/{tree.test.ts → tree-node.test.ts} +0 -0
|
@@ -1,6 +1,29 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
LoroCounter,
|
|
3
|
+
LoroDoc,
|
|
4
|
+
LoroList,
|
|
5
|
+
LoroMap,
|
|
6
|
+
LoroMovableList,
|
|
7
|
+
LoroText,
|
|
8
|
+
LoroTree,
|
|
9
|
+
} from "loro-crdt"
|
|
10
|
+
import { loro } from "./loro.js"
|
|
11
|
+
import type {
|
|
12
|
+
ContainerOrValueShape,
|
|
13
|
+
ContainerShape,
|
|
14
|
+
CounterContainerShape,
|
|
15
|
+
DocShape,
|
|
16
|
+
ListContainerShape,
|
|
17
|
+
MovableListContainerShape,
|
|
18
|
+
RecordContainerShape,
|
|
19
|
+
ShapeToContainer,
|
|
20
|
+
StructContainerShape,
|
|
21
|
+
TextContainerShape,
|
|
22
|
+
} from "./shape.js"
|
|
3
23
|
import type { TypedDoc } from "./typed-doc.js"
|
|
24
|
+
import type { TypedRef } from "./typed-refs/base.js"
|
|
25
|
+
import type { StructRef } from "./typed-refs/struct-ref.js"
|
|
26
|
+
import type { TreeRef } from "./typed-refs/tree-ref.js"
|
|
4
27
|
import type { Mutable } from "./types.js"
|
|
5
28
|
|
|
6
29
|
/**
|
|
@@ -36,26 +59,93 @@ export function change<Shape extends DocShape>(
|
|
|
36
59
|
doc: TypedDoc<Shape>,
|
|
37
60
|
fn: (draft: Mutable<Shape>) => void,
|
|
38
61
|
): TypedDoc<Shape> {
|
|
39
|
-
return doc
|
|
62
|
+
return doc.change(fn)
|
|
40
63
|
}
|
|
41
64
|
|
|
42
65
|
/**
|
|
43
66
|
* Access the underlying LoroDoc for advanced operations.
|
|
67
|
+
* Works on both TypedDoc and any typed ref (TextRef, CounterRef, ListRef, etc.).
|
|
44
68
|
*
|
|
45
|
-
* @param
|
|
46
|
-
* @returns The underlying LoroDoc instance
|
|
69
|
+
* @param docOrRef - The TypedDoc or typed ref to unwrap
|
|
70
|
+
* @returns The underlying LoroDoc instance (or undefined for refs created outside a doc context)
|
|
47
71
|
*
|
|
48
72
|
* @example
|
|
49
73
|
* ```typescript
|
|
50
74
|
* import { getLoroDoc } from "@loro-extended/change"
|
|
51
75
|
*
|
|
76
|
+
* // From TypedDoc
|
|
52
77
|
* const loroDoc = getLoroDoc(doc)
|
|
53
78
|
* const version = loroDoc.version()
|
|
54
79
|
* loroDoc.subscribe(() => console.log("changed"))
|
|
80
|
+
*
|
|
81
|
+
* // From any ref (TextRef, CounterRef, ListRef, etc.)
|
|
82
|
+
* const titleRef = doc.title
|
|
83
|
+
* const loroDoc = getLoroDoc(titleRef)
|
|
84
|
+
* loroDoc?.subscribe(() => console.log("changed"))
|
|
55
85
|
* ```
|
|
56
86
|
*/
|
|
57
87
|
export function getLoroDoc<Shape extends DocShape>(
|
|
58
88
|
doc: TypedDoc<Shape>,
|
|
89
|
+
): LoroDoc
|
|
90
|
+
export function getLoroDoc<Shape extends ContainerShape>(
|
|
91
|
+
ref: TypedRef<Shape>,
|
|
92
|
+
): LoroDoc
|
|
93
|
+
export function getLoroDoc<DataShape extends StructContainerShape>(
|
|
94
|
+
ref: TreeRef<DataShape>,
|
|
95
|
+
): LoroDoc
|
|
96
|
+
export function getLoroDoc(
|
|
97
|
+
docOrRef: TypedDoc<any> | TypedRef<any> | TreeRef<any>,
|
|
59
98
|
): LoroDoc {
|
|
60
|
-
|
|
99
|
+
// Use loro() to access the underlying LoroDoc
|
|
100
|
+
return loro(docOrRef as any).doc
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Access the underlying Loro container from a typed ref.
|
|
105
|
+
* Returns the correctly-typed container based on the ref type.
|
|
106
|
+
*
|
|
107
|
+
* @param ref - The typed ref to unwrap
|
|
108
|
+
* @returns The underlying Loro container (LoroText, LoroCounter, LoroList, etc.)
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* import { getLoroContainer } from "@loro-extended/change"
|
|
113
|
+
*
|
|
114
|
+
* const titleRef = doc.title
|
|
115
|
+
* const loroText = getLoroContainer(titleRef) // LoroText
|
|
116
|
+
*
|
|
117
|
+
* const countRef = doc.count
|
|
118
|
+
* const loroCounter = getLoroContainer(countRef) // LoroCounter
|
|
119
|
+
*
|
|
120
|
+
* const itemsRef = doc.items
|
|
121
|
+
* const loroList = getLoroContainer(itemsRef) // LoroList
|
|
122
|
+
*
|
|
123
|
+
* // Subscribe to container-level changes
|
|
124
|
+
* loroText.subscribe((event) => console.log("Text changed:", event))
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
export function getLoroContainer(ref: TypedRef<TextContainerShape>): LoroText
|
|
128
|
+
export function getLoroContainer(
|
|
129
|
+
ref: TypedRef<CounterContainerShape>,
|
|
130
|
+
): LoroCounter
|
|
131
|
+
export function getLoroContainer(ref: TypedRef<ListContainerShape>): LoroList
|
|
132
|
+
export function getLoroContainer(
|
|
133
|
+
ref: TypedRef<MovableListContainerShape>,
|
|
134
|
+
): LoroMovableList
|
|
135
|
+
export function getLoroContainer(ref: TypedRef<RecordContainerShape>): LoroMap
|
|
136
|
+
export function getLoroContainer(ref: TypedRef<StructContainerShape>): LoroMap
|
|
137
|
+
export function getLoroContainer<
|
|
138
|
+
NestedShapes extends Record<string, ContainerOrValueShape>,
|
|
139
|
+
>(ref: StructRef<NestedShapes>): LoroMap
|
|
140
|
+
export function getLoroContainer<DataShape extends StructContainerShape>(
|
|
141
|
+
ref: TreeRef<DataShape>,
|
|
142
|
+
): LoroTree
|
|
143
|
+
export function getLoroContainer<Shape extends ContainerShape>(
|
|
144
|
+
ref: TypedRef<Shape>,
|
|
145
|
+
): ShapeToContainer<Shape>
|
|
146
|
+
export function getLoroContainer(
|
|
147
|
+
ref: TypedRef<any> | TreeRef<any> | StructRef<any>,
|
|
148
|
+
): unknown {
|
|
149
|
+
// Use loro() to access the underlying container
|
|
150
|
+
return loro(ref as any).container
|
|
61
151
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
+
import { loro } from "./loro.js"
|
|
2
3
|
import { Shape } from "./shape.js"
|
|
3
4
|
import { createTypedDoc } from "./typed-doc.js"
|
|
4
5
|
|
|
@@ -7,7 +8,7 @@ import { createTypedDoc } from "./typed-doc.js"
|
|
|
7
8
|
*
|
|
8
9
|
* This API provides direct schema access on the doc object:
|
|
9
10
|
* - doc.count.increment(5) instead of doc.count.increment(5)
|
|
10
|
-
* - doc
|
|
11
|
+
* - doc.change() for batched mutations
|
|
11
12
|
* - doc.toJSON() for serialization
|
|
12
13
|
*/
|
|
13
14
|
describe("Grand Unified API v3", () => {
|
|
@@ -113,7 +114,7 @@ describe("Grand Unified API v3", () => {
|
|
|
113
114
|
|
|
114
115
|
it("should support 'in' operator after change()", () => {
|
|
115
116
|
const doc = createTypedDoc(schema)
|
|
116
|
-
doc
|
|
117
|
+
doc.change(draft => {
|
|
117
118
|
draft.users.set("alice", { name: "Alice" })
|
|
118
119
|
draft.users.set("bob", { name: "Bob" })
|
|
119
120
|
})
|
|
@@ -128,15 +129,15 @@ describe("Grand Unified API v3", () => {
|
|
|
128
129
|
const doc = createTypedDoc(schema)
|
|
129
130
|
|
|
130
131
|
// Track commits by checking version changes
|
|
131
|
-
const versionBefore = doc
|
|
132
|
+
const versionBefore = loro(doc).doc.version()
|
|
132
133
|
|
|
133
|
-
doc
|
|
134
|
+
doc.change(draft => {
|
|
134
135
|
draft.count.increment(1)
|
|
135
136
|
draft.count.increment(2)
|
|
136
137
|
draft.count.increment(3)
|
|
137
138
|
})
|
|
138
139
|
|
|
139
|
-
const versionAfter = doc
|
|
140
|
+
const versionAfter = loro(doc).doc.version()
|
|
140
141
|
|
|
141
142
|
// Version should have changed
|
|
142
143
|
expect(versionAfter).not.toEqual(versionBefore)
|
|
@@ -146,7 +147,7 @@ describe("Grand Unified API v3", () => {
|
|
|
146
147
|
it("should batch multiple different operations", () => {
|
|
147
148
|
const doc = createTypedDoc(schema)
|
|
148
149
|
|
|
149
|
-
doc
|
|
150
|
+
doc.change(draft => {
|
|
150
151
|
draft.title.insert(0, "Hello World")
|
|
151
152
|
draft.count.increment(42)
|
|
152
153
|
draft.users.set("alice", { name: "Alice" })
|
|
@@ -163,7 +164,7 @@ describe("Grand Unified API v3", () => {
|
|
|
163
164
|
it("should return doc for chaining from change()", () => {
|
|
164
165
|
const doc = createTypedDoc(schema)
|
|
165
166
|
|
|
166
|
-
const result = doc
|
|
167
|
+
const result = doc.change(draft => {
|
|
167
168
|
draft.title.insert(0, "Test")
|
|
168
169
|
draft.count.increment(5)
|
|
169
170
|
})
|
|
@@ -178,9 +179,11 @@ describe("Grand Unified API v3", () => {
|
|
|
178
179
|
const doc = createTypedDoc(schema)
|
|
179
180
|
|
|
180
181
|
// Chain mutations after change
|
|
181
|
-
doc
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
doc
|
|
183
|
+
.change(draft => {
|
|
184
|
+
draft.count.increment(5)
|
|
185
|
+
})
|
|
186
|
+
.count.increment(3)
|
|
184
187
|
|
|
185
188
|
expect(doc.toJSON().count).toBe(8)
|
|
186
189
|
})
|
|
@@ -192,19 +195,19 @@ describe("Grand Unified API v3", () => {
|
|
|
192
195
|
|
|
193
196
|
// Both should have .has()
|
|
194
197
|
expect(typeof doc.users.has).toBe("function")
|
|
195
|
-
doc
|
|
198
|
+
doc.change(draft => {
|
|
196
199
|
expect(typeof draft.users.has).toBe("function")
|
|
197
200
|
})
|
|
198
201
|
|
|
199
202
|
// Both should have .keys()
|
|
200
203
|
expect(typeof doc.users.keys).toBe("function")
|
|
201
|
-
doc
|
|
204
|
+
doc.change(draft => {
|
|
202
205
|
expect(typeof draft.users.keys).toBe("function")
|
|
203
206
|
})
|
|
204
207
|
|
|
205
208
|
// Both should have .set()
|
|
206
209
|
expect(typeof doc.users.set).toBe("function")
|
|
207
|
-
doc
|
|
210
|
+
doc.change(draft => {
|
|
208
211
|
expect(typeof draft.users.set).toBe("function")
|
|
209
212
|
})
|
|
210
213
|
})
|
|
@@ -213,7 +216,7 @@ describe("Grand Unified API v3", () => {
|
|
|
213
216
|
const doc = createTypedDoc(schema)
|
|
214
217
|
|
|
215
218
|
// Set up some data
|
|
216
|
-
doc
|
|
219
|
+
doc.change(draft => {
|
|
217
220
|
draft.title.insert(0, "Test Title")
|
|
218
221
|
draft.count.increment(42)
|
|
219
222
|
draft.users.set("alice", { name: "Alice" })
|
|
@@ -245,7 +248,7 @@ describe("Grand Unified API v3", () => {
|
|
|
245
248
|
// Direct mutations on nested containers
|
|
246
249
|
doc.article.title.insert(0, "My Article")
|
|
247
250
|
doc.article.metadata.views.increment(100)
|
|
248
|
-
doc.article.metadata.
|
|
251
|
+
doc.article.metadata.author = "John Doe"
|
|
249
252
|
|
|
250
253
|
const result = doc.toJSON()
|
|
251
254
|
expect(result.article.title).toBe("My Article")
|
|
@@ -266,7 +269,7 @@ describe("Grand Unified API v3", () => {
|
|
|
266
269
|
const doc = createTypedDoc(listMapSchema)
|
|
267
270
|
|
|
268
271
|
// Push via batch first to create the structure
|
|
269
|
-
doc
|
|
272
|
+
doc.change(draft => {
|
|
270
273
|
draft.articles.push({ title: "Article 1", views: 0 })
|
|
271
274
|
draft.articles.push({ title: "Article 2", views: 0 })
|
|
272
275
|
})
|
|
@@ -386,25 +389,28 @@ describe("Grand Unified API v3", () => {
|
|
|
386
389
|
})
|
|
387
390
|
})
|
|
388
391
|
|
|
389
|
-
describe("
|
|
390
|
-
it("should provide access to
|
|
392
|
+
describe("loro() namespace", () => {
|
|
393
|
+
it("should provide access to CRDT internals via loro()", () => {
|
|
391
394
|
const doc = createTypedDoc(schema)
|
|
392
395
|
|
|
393
|
-
//
|
|
394
|
-
expect(doc
|
|
396
|
+
// loro(doc) should provide access to CRDT internals
|
|
397
|
+
expect(loro(doc).doc).toBeDefined()
|
|
398
|
+
expect(loro(doc).subscribe).toBeDefined()
|
|
399
|
+
expect(loro(doc).applyPatch).toBeDefined()
|
|
400
|
+
expect(loro(doc).rawValue).toBeDefined()
|
|
401
|
+
expect(loro(doc).docShape).toBeDefined()
|
|
395
402
|
|
|
396
|
-
//
|
|
397
|
-
expect(typeof doc
|
|
403
|
+
// doc should have change() and toJSON() directly
|
|
404
|
+
expect(typeof doc.change).toBe("function")
|
|
398
405
|
expect(typeof doc.toJSON).toBe("function")
|
|
399
|
-
expect(doc.$.loroDoc).toBeDefined()
|
|
400
406
|
})
|
|
401
407
|
|
|
402
|
-
it("should not enumerate
|
|
408
|
+
it("should not enumerate internal properties in Object.keys()", () => {
|
|
403
409
|
const doc = createTypedDoc(schema)
|
|
404
410
|
|
|
405
|
-
//
|
|
411
|
+
// Internal properties should not appear in Object.keys()
|
|
406
412
|
const keys = Object.keys(doc)
|
|
407
|
-
expect(keys).not.toContain("
|
|
413
|
+
expect(keys).not.toContain("change")
|
|
408
414
|
|
|
409
415
|
// But schema keys should appear
|
|
410
416
|
expect(keys).toContain("title")
|
|
@@ -413,11 +419,11 @@ describe("Grand Unified API v3", () => {
|
|
|
413
419
|
expect(keys).toContain("items")
|
|
414
420
|
})
|
|
415
421
|
|
|
416
|
-
it("should support 'in' operator for
|
|
422
|
+
it("should support 'in' operator for change", () => {
|
|
417
423
|
const doc = createTypedDoc(schema)
|
|
418
424
|
|
|
419
|
-
//
|
|
420
|
-
expect("
|
|
425
|
+
// change should be accessible via 'in'
|
|
426
|
+
expect("change" in doc).toBe(true)
|
|
421
427
|
})
|
|
422
428
|
})
|
|
423
429
|
})
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,19 @@ export {
|
|
|
5
5
|
deriveShapePlaceholder,
|
|
6
6
|
} from "./derive-placeholder.js"
|
|
7
7
|
// Functional helpers (recommended API)
|
|
8
|
-
export { change, getLoroDoc } from "./functional-helpers.js"
|
|
8
|
+
export { change, getLoroContainer, getLoroDoc } from "./functional-helpers.js"
|
|
9
|
+
// The loro() escape hatch for CRDT internals
|
|
10
|
+
export {
|
|
11
|
+
LORO_SYMBOL,
|
|
12
|
+
type LoroCounterRef,
|
|
13
|
+
type LoroListRef,
|
|
14
|
+
type LoroMapRef,
|
|
15
|
+
type LoroRefBase,
|
|
16
|
+
type LoroTextRef,
|
|
17
|
+
type LoroTreeRef,
|
|
18
|
+
type LoroTypedDocRef,
|
|
19
|
+
loro,
|
|
20
|
+
} from "./loro.js"
|
|
9
21
|
export { mergeValue, overlayPlaceholder } from "./overlay.js"
|
|
10
22
|
// Path selector DSL exports
|
|
11
23
|
export { createPathBuilder } from "./path-builder.js"
|
|
@@ -44,6 +56,9 @@ export type {
|
|
|
44
56
|
StructValueShape,
|
|
45
57
|
TextContainerShape,
|
|
46
58
|
TreeContainerShape,
|
|
59
|
+
// Tree-related types
|
|
60
|
+
TreeNodeJSON,
|
|
61
|
+
TreeRefInterface,
|
|
47
62
|
UnionValueShape,
|
|
48
63
|
// Value shapes
|
|
49
64
|
ValueShape,
|
|
@@ -56,6 +71,15 @@ export type {
|
|
|
56
71
|
export { Shape } from "./shape.js"
|
|
57
72
|
export type { TypedDoc } from "./typed-doc.js"
|
|
58
73
|
export { createTypedDoc } from "./typed-doc.js"
|
|
74
|
+
// Typed ref types - for specifying types with the loro() function
|
|
75
|
+
export type { CounterRef } from "./typed-refs/counter-ref.js"
|
|
76
|
+
export type { ListRef } from "./typed-refs/list-ref.js"
|
|
77
|
+
export type { MovableListRef } from "./typed-refs/movable-list-ref.js"
|
|
78
|
+
export type { RecordRef } from "./typed-refs/record-ref.js"
|
|
79
|
+
export type { StructRef } from "./typed-refs/struct-ref.js"
|
|
80
|
+
export type { TextRef } from "./typed-refs/text-ref.js"
|
|
81
|
+
export type { TreeNodeRef } from "./typed-refs/tree-node-ref.js"
|
|
82
|
+
export type { TreeRef } from "./typed-refs/tree-ref.js"
|
|
59
83
|
export type {
|
|
60
84
|
// Type inference - Infer<T> is the recommended unified helper
|
|
61
85
|
Infer,
|
package/src/json-patch.test.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
2
|
import { change } from "./functional-helpers.js"
|
|
3
3
|
import type { JsonPatch } from "./json-patch.js"
|
|
4
|
+
import { loro } from "./loro.js"
|
|
4
5
|
import { Shape } from "./shape.js"
|
|
5
6
|
import { createTypedDoc } from "./typed-doc.js"
|
|
6
7
|
|
|
@@ -21,7 +22,8 @@ describe("JSON Patch Integration", () => {
|
|
|
21
22
|
{ op: "add", path: "/metadata/count", value: 42 },
|
|
22
23
|
]
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
loro(typedDoc).applyPatch(patch)
|
|
26
|
+
const result = typedDoc.toJSON()
|
|
25
27
|
|
|
26
28
|
expect(result.metadata.title).toBe("Hello World")
|
|
27
29
|
expect(result.metadata.count).toBe(42)
|
|
@@ -39,13 +41,14 @@ describe("JSON Patch Integration", () => {
|
|
|
39
41
|
|
|
40
42
|
// First set some values
|
|
41
43
|
change(typedDoc, draft => {
|
|
42
|
-
draft.config.
|
|
43
|
-
draft.config.
|
|
44
|
+
draft.config.theme = "dark"
|
|
45
|
+
draft.config.debug = false
|
|
44
46
|
})
|
|
45
47
|
|
|
46
48
|
const patch: JsonPatch = [{ op: "remove", path: "/config/debug" }]
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
loro(typedDoc).applyPatch(patch)
|
|
51
|
+
const result = typedDoc.toJSON()
|
|
49
52
|
|
|
50
53
|
expect(result.config.theme).toBe("dark")
|
|
51
54
|
expect(result.config.debug).toBe(true) // Should fall back to empty state
|
|
@@ -63,8 +66,8 @@ describe("JSON Patch Integration", () => {
|
|
|
63
66
|
|
|
64
67
|
// Set initial values
|
|
65
68
|
change(typedDoc, draft => {
|
|
66
|
-
draft.settings.
|
|
67
|
-
draft.settings.
|
|
69
|
+
draft.settings.language = "fr"
|
|
70
|
+
draft.settings.volume = 75
|
|
68
71
|
})
|
|
69
72
|
|
|
70
73
|
const patch: JsonPatch = [
|
|
@@ -72,7 +75,8 @@ describe("JSON Patch Integration", () => {
|
|
|
72
75
|
{ op: "replace", path: "/settings/volume", value: 100 },
|
|
73
76
|
]
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
loro(typedDoc).applyPatch(patch)
|
|
79
|
+
const result = typedDoc.toJSON()
|
|
76
80
|
|
|
77
81
|
expect(result.settings.language).toBe("es")
|
|
78
82
|
expect(result.settings.volume).toBe(100)
|
|
@@ -93,7 +97,8 @@ describe("JSON Patch Integration", () => {
|
|
|
93
97
|
{ op: "add", path: "/items/1", value: "middle" }, // Insert in middle
|
|
94
98
|
]
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
loro(typedDoc).applyPatch(patch)
|
|
101
|
+
const result = typedDoc.toJSON()
|
|
97
102
|
|
|
98
103
|
expect(result.items).toEqual(["first", "middle", "second"])
|
|
99
104
|
})
|
|
@@ -116,7 +121,8 @@ describe("JSON Patch Integration", () => {
|
|
|
116
121
|
{ op: "remove", path: "/tasks/1" }, // Remove "task2"
|
|
117
122
|
]
|
|
118
123
|
|
|
119
|
-
|
|
124
|
+
loro(typedDoc).applyPatch(patch)
|
|
125
|
+
const result = typedDoc.toJSON()
|
|
120
126
|
|
|
121
127
|
expect(result.tasks).toEqual(["task1", "task3"])
|
|
122
128
|
})
|
|
@@ -139,7 +145,8 @@ describe("JSON Patch Integration", () => {
|
|
|
139
145
|
{ op: "replace", path: "/numbers/1", value: 20 },
|
|
140
146
|
]
|
|
141
147
|
|
|
142
|
-
|
|
148
|
+
loro(typedDoc).applyPatch(patch)
|
|
149
|
+
const result = typedDoc.toJSON()
|
|
143
150
|
|
|
144
151
|
expect(result.numbers).toEqual([1, 20, 3])
|
|
145
152
|
})
|
|
@@ -215,7 +222,8 @@ describe("JSON Patch Integration", () => {
|
|
|
215
222
|
},
|
|
216
223
|
]
|
|
217
224
|
|
|
218
|
-
|
|
225
|
+
loro(typedDoc).applyPatch(patch)
|
|
226
|
+
const result = typedDoc.toJSON()
|
|
219
227
|
|
|
220
228
|
expect(result.user.profile.name).toBe("Alice")
|
|
221
229
|
expect(result.user.profile.settings.theme).toBe("dark")
|
|
@@ -253,7 +261,8 @@ describe("JSON Patch Integration", () => {
|
|
|
253
261
|
},
|
|
254
262
|
]
|
|
255
263
|
|
|
256
|
-
|
|
264
|
+
loro(typedDoc).applyPatch(patch)
|
|
265
|
+
const result = typedDoc.toJSON()
|
|
257
266
|
|
|
258
267
|
expect(result.todos).toHaveLength(2)
|
|
259
268
|
expect(result.todos[0]).toEqual({
|
|
@@ -288,7 +297,8 @@ describe("JSON Patch Integration", () => {
|
|
|
288
297
|
{ op: "move", from: "/items/0", path: "/items/2" }, // Move "first" to end
|
|
289
298
|
]
|
|
290
299
|
|
|
291
|
-
|
|
300
|
+
loro(typedDoc).applyPatch(patch)
|
|
301
|
+
const result = typedDoc.toJSON()
|
|
292
302
|
|
|
293
303
|
expect(result.items).toEqual(["second", "third", "first"])
|
|
294
304
|
})
|
|
@@ -312,7 +322,8 @@ describe("JSON Patch Integration", () => {
|
|
|
312
322
|
{ op: "move", from: "/items/0", path: "/items/3" },
|
|
313
323
|
]
|
|
314
324
|
|
|
315
|
-
|
|
325
|
+
loro(typedDoc).applyPatch(patch1)
|
|
326
|
+
const result1 = typedDoc.toJSON()
|
|
316
327
|
expect(result1.items).toEqual(["B", "C", "D", "A"])
|
|
317
328
|
|
|
318
329
|
// Reset for next test
|
|
@@ -329,7 +340,8 @@ describe("JSON Patch Integration", () => {
|
|
|
329
340
|
{ op: "move", from: "/items/1", path: "/items/3" },
|
|
330
341
|
]
|
|
331
342
|
|
|
332
|
-
|
|
343
|
+
loro(typedDoc).applyPatch(patch2)
|
|
344
|
+
const result2 = typedDoc.toJSON()
|
|
333
345
|
expect(result2.items).toEqual(["A", "C", "D", "B"])
|
|
334
346
|
})
|
|
335
347
|
|
|
@@ -352,7 +364,8 @@ describe("JSON Patch Integration", () => {
|
|
|
352
364
|
{ op: "copy", from: "/source/1", path: "/target/1" },
|
|
353
365
|
]
|
|
354
366
|
|
|
355
|
-
|
|
367
|
+
loro(typedDoc).applyPatch(patch)
|
|
368
|
+
const result = typedDoc.toJSON()
|
|
356
369
|
|
|
357
370
|
expect(result.source).toEqual(["item1", "item2"])
|
|
358
371
|
expect(result.target).toEqual(["item1", "item2"])
|
|
@@ -370,7 +383,7 @@ describe("JSON Patch Integration", () => {
|
|
|
370
383
|
const typedDoc = createTypedDoc(schema)
|
|
371
384
|
|
|
372
385
|
change(typedDoc, draft => {
|
|
373
|
-
draft.config.
|
|
386
|
+
draft.config.version = "2.0.0"
|
|
374
387
|
})
|
|
375
388
|
|
|
376
389
|
const patch: JsonPatch = [
|
|
@@ -378,7 +391,8 @@ describe("JSON Patch Integration", () => {
|
|
|
378
391
|
{ op: "replace", path: "/config/version", value: "2.1.0" },
|
|
379
392
|
]
|
|
380
393
|
|
|
381
|
-
|
|
394
|
+
loro(typedDoc).applyPatch(patch)
|
|
395
|
+
const result = typedDoc.toJSON()
|
|
382
396
|
|
|
383
397
|
expect(result.config.version).toBe("2.1.0")
|
|
384
398
|
})
|
|
@@ -397,7 +411,7 @@ describe("JSON Patch Integration", () => {
|
|
|
397
411
|
]
|
|
398
412
|
|
|
399
413
|
expect(() => {
|
|
400
|
-
typedDoc
|
|
414
|
+
loro(typedDoc).applyPatch(patch)
|
|
401
415
|
}).toThrow("JSON Patch test failed at path: /config/version")
|
|
402
416
|
})
|
|
403
417
|
})
|
|
@@ -425,7 +439,8 @@ describe("JSON Patch Integration", () => {
|
|
|
425
439
|
{ op: "add", path: "/email", value: "alice@example.com" },
|
|
426
440
|
]
|
|
427
441
|
|
|
428
|
-
|
|
442
|
+
loro(typedDoc).applyPatch(patch, ["users", "alice"])
|
|
443
|
+
const result = typedDoc.toJSON()
|
|
429
444
|
|
|
430
445
|
expect(result.users.alice.name).toBe("Alice Smith")
|
|
431
446
|
expect(result.users.alice.email).toBe("alice@example.com")
|
|
@@ -448,7 +463,8 @@ describe("JSON Patch Integration", () => {
|
|
|
448
463
|
{ op: "add", path: "/data/items/1", value: "second" },
|
|
449
464
|
]
|
|
450
465
|
|
|
451
|
-
|
|
466
|
+
loro(typedDoc).applyPatch(patch)
|
|
467
|
+
const result = typedDoc.toJSON()
|
|
452
468
|
|
|
453
469
|
expect(result.data.items).toEqual(["first", "second"])
|
|
454
470
|
})
|
|
@@ -467,7 +483,8 @@ describe("JSON Patch Integration", () => {
|
|
|
467
483
|
{ op: "add", path: ["data", "items", 1], value: "second" },
|
|
468
484
|
]
|
|
469
485
|
|
|
470
|
-
|
|
486
|
+
loro(typedDoc).applyPatch(patch)
|
|
487
|
+
const result = typedDoc.toJSON()
|
|
471
488
|
|
|
472
489
|
expect(result.data.items).toEqual(["first", "second"])
|
|
473
490
|
})
|
|
@@ -488,7 +505,7 @@ describe("JSON Patch Integration", () => {
|
|
|
488
505
|
]
|
|
489
506
|
|
|
490
507
|
expect(() => {
|
|
491
|
-
typedDoc
|
|
508
|
+
loro(typedDoc).applyPatch(patch)
|
|
492
509
|
}).toThrow("Cannot navigate to path segment: nonexistent")
|
|
493
510
|
})
|
|
494
511
|
|
|
@@ -504,7 +521,7 @@ describe("JSON Patch Integration", () => {
|
|
|
504
521
|
]
|
|
505
522
|
|
|
506
523
|
expect(() => {
|
|
507
|
-
typedDoc
|
|
524
|
+
loro(typedDoc).applyPatch(patch)
|
|
508
525
|
}).toThrow("Index out of bound")
|
|
509
526
|
})
|
|
510
527
|
})
|
|
@@ -533,7 +550,8 @@ describe("JSON Patch Integration", () => {
|
|
|
533
550
|
{ op: "add", path: "/data/items/1", value: "item2" },
|
|
534
551
|
]
|
|
535
552
|
|
|
536
|
-
|
|
553
|
+
loro(typedDoc).applyPatch(patch)
|
|
554
|
+
const result = typedDoc.toJSON()
|
|
537
555
|
|
|
538
556
|
expect(result.counter).toBe(5)
|
|
539
557
|
expect(result.text).toBe("Hello")
|
|
@@ -555,14 +573,15 @@ describe("JSON Patch Integration", () => {
|
|
|
555
573
|
{ op: "replace", path: "/settings/theme", value: "dark" },
|
|
556
574
|
]
|
|
557
575
|
|
|
558
|
-
typedDoc
|
|
576
|
+
loro(typedDoc).applyPatch(patch1)
|
|
559
577
|
|
|
560
578
|
// Second patch
|
|
561
579
|
const patch2: JsonPatch = [
|
|
562
580
|
{ op: "replace", path: "/settings/language", value: "fr" },
|
|
563
581
|
]
|
|
564
582
|
|
|
565
|
-
|
|
583
|
+
loro(typedDoc).applyPatch(patch2)
|
|
584
|
+
const result = typedDoc.toJSON()
|
|
566
585
|
|
|
567
586
|
expect(result.settings.theme).toBe("dark") // Should persist from first patch
|
|
568
587
|
expect(result.settings.language).toBe("fr")
|