@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.
Files changed (56) hide show
  1. package/README.md +173 -149
  2. package/dist/index.d.ts +962 -335
  3. package/dist/index.js +1040 -598
  4. package/dist/index.js.map +1 -1
  5. package/package.json +4 -4
  6. package/src/change.test.ts +51 -52
  7. package/src/functional-helpers.test.ts +316 -4
  8. package/src/functional-helpers.ts +96 -6
  9. package/src/grand-unified-api.test.ts +35 -29
  10. package/src/index.ts +25 -1
  11. package/src/json-patch.test.ts +46 -27
  12. package/src/loro.test.ts +449 -0
  13. package/src/loro.ts +273 -0
  14. package/src/overlay-recursion.test.ts +1 -1
  15. package/src/path-evaluator.ts +1 -1
  16. package/src/path-selector.test.ts +94 -1
  17. package/src/shape.ts +47 -15
  18. package/src/typed-doc.ts +99 -98
  19. package/src/typed-refs/base.ts +126 -35
  20. package/src/typed-refs/counter-ref-internals.ts +62 -0
  21. package/src/typed-refs/{counter.test.ts → counter-ref.test.ts} +5 -4
  22. package/src/typed-refs/counter-ref.ts +45 -0
  23. package/src/typed-refs/{doc.ts → doc-ref-internals.ts} +33 -38
  24. package/src/typed-refs/doc-ref.ts +47 -0
  25. package/src/typed-refs/encapsulation.test.ts +226 -0
  26. package/src/typed-refs/list-ref-base-internals.ts +280 -0
  27. package/src/typed-refs/{list-base.ts → list-ref-base.ts} +255 -160
  28. package/src/typed-refs/list-ref-internals.ts +21 -0
  29. package/src/typed-refs/{list.ts → list-ref.ts} +10 -11
  30. package/src/typed-refs/movable-list-ref-internals.ts +38 -0
  31. package/src/typed-refs/movable-list-ref.ts +31 -0
  32. package/src/typed-refs/proxy-handlers.ts +13 -4
  33. package/src/typed-refs/{record.ts → record-ref-internals.ts} +78 -79
  34. package/src/typed-refs/{record.test.ts → record-ref.test.ts} +21 -16
  35. package/src/typed-refs/record-ref.ts +80 -0
  36. package/src/typed-refs/struct-ref-internals.ts +195 -0
  37. package/src/typed-refs/{struct-value-updates.test.ts → struct-ref.test.ts} +5 -3
  38. package/src/typed-refs/struct-ref.ts +257 -0
  39. package/src/typed-refs/text-ref-internals.ts +100 -0
  40. package/src/typed-refs/text-ref.ts +72 -0
  41. package/src/typed-refs/tree-node-ref-internals.ts +111 -0
  42. package/src/typed-refs/{tree-node.ts → tree-node-ref.ts} +58 -94
  43. package/src/typed-refs/tree-ref-internals.ts +110 -0
  44. package/src/typed-refs/tree-ref.ts +194 -0
  45. package/src/typed-refs/utils.ts +21 -23
  46. package/src/typed-refs/counter.ts +0 -62
  47. package/src/typed-refs/movable-list.ts +0 -32
  48. package/src/typed-refs/struct.ts +0 -201
  49. package/src/typed-refs/text.ts +0 -91
  50. package/src/typed-refs/tree.ts +0 -268
  51. /package/src/typed-refs/{list-value-updates.test.ts → list-ref-value-updates.test.ts} +0 -0
  52. /package/src/typed-refs/{list.test.ts → list-ref.test.ts} +0 -0
  53. /package/src/typed-refs/{movable-list.test.ts → movable-list-ref.test.ts} +0 -0
  54. /package/src/typed-refs/{record-value-updates.test.ts → record-ref-value-updates.test.ts} +0 -0
  55. /package/src/typed-refs/{tree-node-value-updates.test.ts → tree-node-ref.test.ts} +0 -0
  56. /package/src/typed-refs/{tree.test.ts → tree-node.test.ts} +0 -0
@@ -1,6 +1,29 @@
1
- import type { LoroDoc } from "loro-crdt"
2
- import type { DocShape } from "./shape.js"
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.$.change(fn)
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 doc - The TypedDoc to unwrap
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
- return doc.$.loroDoc
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.$.change() for batched mutations
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.$.change(draft => {
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.$.loroDoc.version()
132
+ const versionBefore = loro(doc).doc.version()
132
133
 
133
- doc.$.change(draft => {
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.$.loroDoc.version()
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.$.change(draft => {
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.$.change(draft => {
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.$.change(draft => {
182
- draft.count.increment(5)
183
- }).count.increment(3)
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.$.change(draft => {
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.$.change(draft => {
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.$.change(draft => {
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.$.change(draft => {
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.set("author", "John Doe")
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.$.change(draft => {
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("$ namespace", () => {
390
- it("should provide access to meta-operations via $", () => {
392
+ describe("loro() namespace", () => {
393
+ it("should provide access to CRDT internals via loro()", () => {
391
394
  const doc = createTypedDoc(schema)
392
395
 
393
- // $ should exist
394
- expect(doc.$).toBeDefined()
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
- // $ should have batch, toJSON, loroDoc, etc.
397
- expect(typeof doc.$.change).toBe("function")
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 $ in Object.keys()", () => {
408
+ it("should not enumerate internal properties in Object.keys()", () => {
403
409
  const doc = createTypedDoc(schema)
404
410
 
405
- // $ should not appear in Object.keys()
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
- // $ should be accessible via 'in'
420
- expect("$" in doc).toBe(true)
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,
@@ -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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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.set("theme", "dark")
43
- draft.config.set("debug", false)
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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.set("language", "fr")
67
- draft.settings.set("volume", 75)
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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
- const result1 = typedDoc.$.applyPatch(patch1).toJSON()
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
- const result2 = typedDoc.$.applyPatch(patch2).toJSON()
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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.set("version", "2.0.0")
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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.$.applyPatch(patch)
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
- const result = typedDoc.$.applyPatch(patch, ["users", "alice"]).toJSON()
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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.$.applyPatch(patch)
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.$.applyPatch(patch)
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
- const result = typedDoc.$.applyPatch(patch).toJSON()
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.$.applyPatch(patch1)
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
- const result = typedDoc.$.applyPatch(patch2).toJSON()
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")