json-patch-to-crdt 0.2.0 → 0.4.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 +100 -4
- package/dist/{compact-BE9UsxEo.mjs → compact-BcwxBNx_.mjs} +1664 -418
- package/dist/{compact-DrmgKiVW.js → compact-CXfvMNCT.js} +1753 -459
- package/dist/{depth-Cd3nyHWy.d.mts → depth-CpJSyZE5.d.mts} +94 -15
- package/dist/{depth-tcJ8L1dj.d.ts → depth-D88VeWb-.d.ts} +94 -15
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -2
- package/dist/index.mjs +2 -2
- package/dist/internals.d.mts +31 -4
- package/dist/internals.d.ts +31 -4
- package/dist/internals.js +9 -1
- package/dist/internals.mjs +2 -2
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -31,10 +31,7 @@ Node.js `>=18`.
|
|
|
31
31
|
```ts
|
|
32
32
|
import { applyPatch, createState, toJson, type JsonPatchOp } from "json-patch-to-crdt";
|
|
33
33
|
|
|
34
|
-
const state = createState(
|
|
35
|
-
{ todos: ["write docs"], done: false },
|
|
36
|
-
{ actor: "client-A" },
|
|
37
|
-
);
|
|
34
|
+
const state = createState({ todos: ["write docs"], done: false }, { actor: "client-A" });
|
|
38
35
|
|
|
39
36
|
const patch: JsonPatchOp[] = [
|
|
40
37
|
{ op: "add", path: "/todos/-", value: "ship package" },
|
|
@@ -88,8 +85,31 @@ console.log(delta);
|
|
|
88
85
|
// { op: "add", path: "/profile/active", value: true },
|
|
89
86
|
// { op: "add", path: "/tags/1", value: "b" }
|
|
90
87
|
// ]
|
|
88
|
+
|
|
89
|
+
const reordered = diffJsonPatch(
|
|
90
|
+
{ tags: ["a", "b", "c"] },
|
|
91
|
+
{ tags: ["b", "a", "c"] },
|
|
92
|
+
{ arrayStrategy: "lcs", emitMoves: true },
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
console.log(reordered);
|
|
96
|
+
// [{ op: "move", from: "/tags/0", path: "/tags/1" }]
|
|
91
97
|
```
|
|
92
98
|
|
|
99
|
+
For array-heavy snapshots, `diffJsonPatch` and `crdtToJsonPatch` support:
|
|
100
|
+
|
|
101
|
+
- `arrayStrategy: "lcs"`: deterministic index-level array edits using the classic LCS matrix. This is the default.
|
|
102
|
+
- `arrayStrategy: "lcs-linear"`: deterministic index-level array edits using a lower-memory linear-space LCS traversal.
|
|
103
|
+
- `arrayStrategy: "atomic"`: replace the whole array with a single patch operation.
|
|
104
|
+
|
|
105
|
+
`lcsMaxCells` only applies to `arrayStrategy: "lcs"`. If the classic LCS matrix for the trimmed unmatched window would exceed the configured cap, the diff falls back to an atomic array `replace`.
|
|
106
|
+
|
|
107
|
+
`lcsLinearMaxCells` is the matching opt-in guardrail for `arrayStrategy: "lcs-linear"`. It uses the same trimmed unmatched-window estimate, but caps worst-case runtime instead of matrix allocation. When the cap is exceeded, `lcs-linear` also falls back to an atomic array `replace`. If you do not set `lcsLinearMaxCells`, `lcs-linear` keeps its previous behavior and will continue to run without an automatic fallback.
|
|
108
|
+
|
|
109
|
+
`diffJsonPatch` keeps the existing `add`/`remove`/`replace` output by default. Set
|
|
110
|
+
`emitMoves` and/or `emitCopies` to opt into deterministic RFC 6902 `move`/`copy`
|
|
111
|
+
rewrites.
|
|
112
|
+
|
|
93
113
|
## Serialize / Restore State
|
|
94
114
|
|
|
95
115
|
```ts
|
|
@@ -111,6 +131,15 @@ console.log(toJson(next));
|
|
|
111
131
|
// { counter: 2 }
|
|
112
132
|
```
|
|
113
133
|
|
|
134
|
+
Persisted snapshot compatibility:
|
|
135
|
+
|
|
136
|
+
- `serializeState(...)` emits a versioned envelope.
|
|
137
|
+
- `deserializeState(...)` accepts both the current versioned format and legacy unversioned snapshots.
|
|
138
|
+
- Future envelope versions are rejected until an explicit migration path is added.
|
|
139
|
+
|
|
140
|
+
The same compatibility contract applies to the lower-level `serializeDoc(...)` and
|
|
141
|
+
`deserializeDoc(...)` helpers exported from `json-patch-to-crdt/internals`.
|
|
142
|
+
|
|
114
143
|
## Error Handling
|
|
115
144
|
|
|
116
145
|
`applyPatch` throws `PatchError` when a patch cannot be applied.
|
|
@@ -129,6 +158,67 @@ try {
|
|
|
129
158
|
|
|
130
159
|
If you prefer non-throwing results, use `tryApplyPatch(...)` / `tryMergeState(...)`.
|
|
131
160
|
|
|
161
|
+
## Version Vector Helpers
|
|
162
|
+
|
|
163
|
+
`observedVersionVector(...)` lets you inspect the highest observed counter per
|
|
164
|
+
actor from either a `Doc` or a `CrdtState`. Use `mergeVersionVectors(...)` to
|
|
165
|
+
union peer observations, and `intersectVersionVectors(...)` when you need a
|
|
166
|
+
causally-stable checkpoint that every replica has already seen.
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import {
|
|
170
|
+
compactStateTombstones,
|
|
171
|
+
intersectVersionVectors,
|
|
172
|
+
mergeVersionVectors,
|
|
173
|
+
observedVersionVector,
|
|
174
|
+
versionVectorCovers,
|
|
175
|
+
} from "json-patch-to-crdt";
|
|
176
|
+
|
|
177
|
+
const seenByReplicaA = observedVersionVector(replicaA);
|
|
178
|
+
const seenByReplicaB = observedVersionVector(replicaB);
|
|
179
|
+
|
|
180
|
+
const mergedCheckpoint = mergeVersionVectors(seenByReplicaA, seenByReplicaB);
|
|
181
|
+
const stableCheckpoint = intersectVersionVectors(seenByReplicaA, seenByReplicaB);
|
|
182
|
+
|
|
183
|
+
if (versionVectorCovers(mergedCheckpoint, stableCheckpoint)) {
|
|
184
|
+
const compacted = compactStateTombstones(state, { stable: stableCheckpoint });
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Use `mergeVersionVectors(...)` for sync-style "what has this cluster observed?"
|
|
189
|
+
bookkeeping. Use `intersectVersionVectors(...)` for tombstone compaction
|
|
190
|
+
checkpoints, because compaction is only safe once every live peer covers the
|
|
191
|
+
same delete dots. If you call `intersectVersionVectors(...)` with a single
|
|
192
|
+
vector, it returns that vector unchanged.
|
|
193
|
+
|
|
194
|
+
## Runtime JSON Validation
|
|
195
|
+
|
|
196
|
+
`createState`, `applyPatch`, `diffJsonPatch`, and `crdtToJsonPatch` accept a
|
|
197
|
+
`jsonValidation` option for callers that may pass runtime values through `any`.
|
|
198
|
+
|
|
199
|
+
- `"none"` keeps the current behavior with no extra runtime validation.
|
|
200
|
+
- `"strict"` rejects values that are not valid JSON, including non-finite
|
|
201
|
+
numbers, `undefined`, and non-plain objects such as `Date`, `Map`, `Set`,
|
|
202
|
+
`RegExp`, typed arrays, and class instances.
|
|
203
|
+
- `"normalize"` coerces invalid values into JSON-safe output. Non-finite
|
|
204
|
+
numbers become `null`. Non-plain objects also become `null` at the root or
|
|
205
|
+
inside arrays, and are omitted from object properties.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { createState, toJson } from "json-patch-to-crdt";
|
|
209
|
+
|
|
210
|
+
const unsafeInput = {
|
|
211
|
+
keep: true,
|
|
212
|
+
nested: { when: new Date("2020-01-01") },
|
|
213
|
+
arr: [new Uint8Array([1, 2, 3])],
|
|
214
|
+
} as any;
|
|
215
|
+
|
|
216
|
+
const state = createState(unsafeInput, { actor: "A", jsonValidation: "normalize" });
|
|
217
|
+
|
|
218
|
+
console.log(toJson(state));
|
|
219
|
+
// { keep: true, nested: {}, arr: [null] }
|
|
220
|
+
```
|
|
221
|
+
|
|
132
222
|
## API Overview
|
|
133
223
|
|
|
134
224
|
Main exports most apps need:
|
|
@@ -139,6 +229,11 @@ Main exports most apps need:
|
|
|
139
229
|
- `tryApplyPatch(state, patch, options?)`
|
|
140
230
|
- `mergeState(local, remote, { actor })`
|
|
141
231
|
- `tryMergeState(local, remote, options?)`
|
|
232
|
+
- `observedVersionVector(stateOrDoc)`
|
|
233
|
+
- `mergeVersionVectors(...vectors)`
|
|
234
|
+
- `intersectVersionVectors(...vectors)`
|
|
235
|
+
- `versionVectorCovers(observed, required)`
|
|
236
|
+
- `compactStateTombstones(state, { stable })`
|
|
142
237
|
- `toJson(stateOrDoc)`
|
|
143
238
|
- `diffJsonPatch(baseJson, nextJson, options?)`
|
|
144
239
|
- `serializeState(state)` / `deserializeState(payload)`
|
|
@@ -155,6 +250,7 @@ import { crdtToJsonPatch, applyPatchAsActor } from "json-patch-to-crdt/internals
|
|
|
155
250
|
- Arrays use a CRDT sequence internally; concurrent inserts are preserved.
|
|
156
251
|
- Patches are interpreted relative to a snapshot (RFC-style sequential execution by default).
|
|
157
252
|
- Merge assumes replicas come from the same origin state (use `forkState`).
|
|
253
|
+
- Persisted CRDT snapshots currently use envelope version `1`; legacy unversioned snapshots remain readable.
|
|
158
254
|
|
|
159
255
|
## License
|
|
160
256
|
|