@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/src/change.ts ADDED
@@ -0,0 +1,105 @@
1
+ /** biome-ignore-all lint/suspicious/noExplicitAny: fix later */
2
+
3
+ import { LoroDoc } from "loro-crdt"
4
+ import { DraftDoc } from "./draft-nodes/doc.js"
5
+ import {
6
+ type JsonPatch,
7
+ JsonPatchApplicator,
8
+ type JsonPatchOperation,
9
+ normalizePath,
10
+ } from "./json-patch.js"
11
+ import { overlayEmptyState } from "./overlay.js"
12
+ import type { DocShape } from "./shape.js"
13
+ import type { Draft, InferPlainType } from "./types.js"
14
+ import { validateEmptyState } from "./validation.js"
15
+
16
+ // Core TypedDoc abstraction around LoroDoc
17
+ export class TypedDoc<Shape extends DocShape> {
18
+ constructor(
19
+ private shape: Shape,
20
+ private emptyState: InferPlainType<Shape>,
21
+ private doc: LoroDoc = new LoroDoc(),
22
+ ) {
23
+ validateEmptyState(emptyState, shape)
24
+ }
25
+
26
+ get value(): InferPlainType<Shape> {
27
+ const crdtValue = this.doc.toJSON()
28
+ return overlayEmptyState(
29
+ this.shape,
30
+ crdtValue,
31
+ this.emptyState,
32
+ ) as InferPlainType<Shape>
33
+ }
34
+
35
+ change(fn: (draft: Draft<Shape>) => void): InferPlainType<Shape> {
36
+ // Reuse existing DocumentDraft system with empty state integration
37
+ const draft = new DraftDoc({
38
+ shape: this.shape,
39
+ emptyState: this.emptyState,
40
+ doc: this.doc,
41
+ })
42
+ fn(draft as unknown as Draft<Shape>)
43
+ draft.absorbPlainValues()
44
+ this.doc.commit()
45
+ return this.value
46
+ }
47
+
48
+ /**
49
+ * Apply JSON Patch operations to the document
50
+ *
51
+ * @param patch - Array of JSON Patch operations (RFC 6902)
52
+ * @param pathPrefix - Optional path prefix for scoped operations
53
+ * @returns Updated document value
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const result = typedDoc.applyPatch([
58
+ * { op: 'add', path: '/users/0/name', value: 'Alice' },
59
+ * { op: 'replace', path: '/settings/theme', value: 'dark' }
60
+ * ])
61
+ * ```
62
+ */
63
+ applyPatch(
64
+ patch: JsonPatch,
65
+ pathPrefix?: (string | number)[],
66
+ ): InferPlainType<Shape> {
67
+ return this.change(draft => {
68
+ const applicator = new JsonPatchApplicator(draft)
69
+
70
+ // Apply path prefix if provided
71
+ const prefixedPatch = pathPrefix
72
+ ? patch.map((op: JsonPatchOperation) => ({
73
+ ...op,
74
+ path: [...pathPrefix, ...normalizePath(op.path)],
75
+ }))
76
+ : patch
77
+
78
+ applicator.applyPatch(prefixedPatch)
79
+ })
80
+ }
81
+
82
+ // Expose underlying doc for advanced use cases
83
+ get loroDoc(): LoroDoc {
84
+ return this.doc
85
+ }
86
+
87
+ // Expose shape for internal use
88
+ get docShape(): Shape {
89
+ return this.shape
90
+ }
91
+
92
+ // Get raw CRDT value without overlay
93
+ get rawValue(): any {
94
+ return this.doc.toJSON()
95
+ }
96
+ }
97
+
98
+ // Factory function for TypedLoroDoc
99
+ export function createTypedDoc<Shape extends DocShape>(
100
+ shape: Shape,
101
+ emptyState: InferPlainType<Shape>,
102
+ existingDoc?: LoroDoc,
103
+ ): TypedDoc<Shape> {
104
+ return new TypedDoc<Shape>(shape, emptyState, existingDoc || new LoroDoc())
105
+ }