json-patch-to-crdt 0.0.0 → 0.1.1

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.
@@ -161,33 +161,83 @@ type CrdtState = {
161
161
  doc: Doc;
162
162
  clock: Clock;
163
163
  };
164
+ /** Options for `forkState`. */
165
+ interface ForkStateOptions {
166
+ /**
167
+ * Allow reusing the origin actor ID when forking.
168
+ * Defaults to `false` to prevent duplicate-dot collisions across replicas.
169
+ */
170
+ allowActorReuse?: boolean;
171
+ }
164
172
  /** Result from applying a patch for a specific actor using a shared version vector. */
165
173
  type ApplyPatchAsActorResult = {
166
174
  /** Updated CRDT state for the actor that produced this patch. */state: CrdtState; /** Updated version vector after applying the patch. */
167
175
  vv: VersionVector;
168
176
  };
169
- /** Result of applying a patch: success or a 409 conflict with a message. */
177
+ /** Options for internals-only `applyPatchAsActor`. */
178
+ type ApplyPatchAsActorOptions = {
179
+ base?: Doc;
180
+ testAgainst?: "head" | "base";
181
+ semantics?: PatchSemantics;
182
+ };
183
+ /** Typed failure reason used across patch/merge helpers. */
184
+ type PatchErrorReason = "INVALID_PATCH" | "INVALID_POINTER" | "MISSING_PARENT" | "MISSING_TARGET" | "INVALID_TARGET" | "OUT_OF_BOUNDS" | "TEST_FAILED" | "INVALID_MOVE" | "LINEAGE_MISMATCH";
185
+ /** Structured conflict payload used by non-throwing APIs. */
186
+ type ApplyError = {
187
+ ok: false; /** HTTP-friendly status code for conflict-style failures. */
188
+ code: 409; /** Machine-readable reason for branching logic. */
189
+ reason: PatchErrorReason; /** Human-readable description of the failure. */
190
+ message: string; /** Optional pointer/path context when available. */
191
+ path?: string; /** Optional patch operation index when available. */
192
+ opIndex?: number;
193
+ };
194
+ /** Result of applying a patch: success or structured conflict details. */
170
195
  type ApplyResult = {
171
196
  ok: true;
172
- } | {
173
- ok: false;
174
- code: 409;
175
- message: string;
176
- };
197
+ } | ApplyError;
177
198
  /** How JSON Patch operations are interpreted during application. */
178
199
  type PatchSemantics = "base" | "sequential";
200
+ /** Options for compile/validation helpers. */
201
+ type CompilePatchOptions = {
202
+ semantics?: PatchSemantics;
203
+ };
179
204
  /**
180
- * Options for `applyPatch` / `applyPatchInPlace`.
181
- * - `semantics: "base"` maps array indices against a fixed snapshot (current default).
182
- * - `semantics: "sequential"` applies operations one-by-one against the evolving head.
183
- * - `atomic` applies only to `applyPatchInPlace` (ignored by immutable `applyPatch`).
205
+ * Options for immutable patch application (`applyPatch` / `tryApplyPatch`).
206
+ * - `semantics: "sequential"` applies operations one-by-one against the evolving head (default).
207
+ * - `semantics: "base"` maps array indices against a fixed snapshot.
208
+ * - `base` should be a previously observed state snapshot from the same document lineage.
184
209
  */
185
210
  type ApplyPatchOptions = {
186
- base?: Doc;
211
+ base?: CrdtState;
187
212
  testAgainst?: "head" | "base";
188
213
  semantics?: PatchSemantics;
214
+ };
215
+ /** Options for in-place patch application (`applyPatchInPlace` / `tryApplyPatchInPlace`). */
216
+ type ApplyPatchInPlaceOptions = ApplyPatchOptions & {
189
217
  atomic?: boolean;
190
218
  };
219
+ /** Non-throwing result for immutable patch application. */
220
+ type TryApplyPatchResult = {
221
+ ok: true;
222
+ state: CrdtState;
223
+ } | {
224
+ ok: false;
225
+ error: ApplyError;
226
+ };
227
+ /** Non-throwing result for in-place patch application. */
228
+ type TryApplyPatchInPlaceResult = {
229
+ ok: true;
230
+ } | {
231
+ ok: false;
232
+ error: ApplyError;
233
+ };
234
+ /** Non-throwing result for patch validation preflight. */
235
+ type ValidatePatchResult = {
236
+ ok: true;
237
+ } | {
238
+ ok: false;
239
+ error: ApplyError;
240
+ };
191
241
  /** Options for `mergeState`. */
192
242
  type MergeStateOptions = {
193
243
  /**
@@ -209,6 +259,32 @@ type MergeDocOptions = {
209
259
  */
210
260
  requireSharedOrigin?: boolean;
211
261
  };
262
+ /** Non-throwing result for `mergeDoc`. */
263
+ type TryMergeDocResult = {
264
+ ok: true;
265
+ doc: Doc;
266
+ } | {
267
+ ok: false;
268
+ error: ApplyError;
269
+ };
270
+ /** Non-throwing result for `mergeState`. */
271
+ type TryMergeStateResult = {
272
+ ok: true;
273
+ state: CrdtState;
274
+ } | {
275
+ ok: false;
276
+ error: ApplyError;
277
+ };
278
+ /** Options-object overload shape for low-level JSON Patch -> CRDT conversion. */
279
+ type JsonPatchToCrdtOptions = {
280
+ base: Doc;
281
+ head: Doc;
282
+ patch: JsonPatchOp[];
283
+ newDot: () => Dot;
284
+ evalTestAgainst?: "head" | "base";
285
+ bumpCounterAbove?: (ctr: number) => void;
286
+ semantics?: PatchSemantics;
287
+ };
212
288
  /** Options for `crdtToJsonPatch` and `diffJsonPatch`. */
213
289
  type DiffOptions = {
214
290
  arrayStrategy?: "atomic" | "lcs";
@@ -220,10 +296,14 @@ type DiffOptions = {
220
296
  declare const ROOT_KEY = "@@crdt/root";
221
297
  //#endregion
222
298
  //#region src/state.d.ts
223
- /** Error thrown when a JSON Patch cannot be applied. Includes a numeric `.code` (409 for conflicts). */
299
+ /** Error thrown when a JSON Patch cannot be applied. Includes structured conflict metadata. */
224
300
  declare class PatchError extends Error {
225
- readonly code: number;
226
- constructor(message: string, code?: number);
301
+ readonly code: 409;
302
+ readonly reason: PatchErrorReason;
303
+ readonly path?: string;
304
+ readonly opIndex?: number;
305
+ constructor(error: ApplyError);
306
+ constructor(message: string, code?: 409, reason?: PatchErrorReason);
227
307
  }
228
308
  /**
229
309
  * Create a new CRDT state from an initial JSON value.
@@ -235,6 +315,12 @@ declare function createState(initial: JsonValue, options: {
235
315
  actor: ActorId;
236
316
  start?: number;
237
317
  }): CrdtState;
318
+ /**
319
+ * Fork a replica from a shared origin state while assigning a new local actor ID.
320
+ * The forked state has an independent document clone and clock.
321
+ * By default this rejects actor reuse to prevent duplicate-dot collisions across peers.
322
+ */
323
+ declare function forkState(origin: CrdtState, actor: ActorId, options?: ForkStateOptions): CrdtState;
238
324
  /**
239
325
  * Materialize a CRDT document or state back to a plain JSON value.
240
326
  * @param target - A `Doc` or `CrdtState` to materialize.
@@ -246,7 +332,7 @@ declare function toJson(target: Doc | CrdtState): JsonValue;
246
332
  * Throws `PatchError` on conflict (e.g. out-of-bounds index, failed test op).
247
333
  * @param state - The current CRDT state.
248
334
  * @param patch - Array of RFC 6902 JSON Patch operations.
249
- * @param options - Optional base document and test evaluation mode.
335
+ * @param options - Optional base state snapshot and patch semantics.
250
336
  * @returns A new `CrdtState` with the patch applied.
251
337
  */
252
338
  declare function applyPatch(state: CrdtState, patch: JsonPatchOp[], options?: ApplyPatchOptions): CrdtState;
@@ -255,90 +341,32 @@ declare function applyPatch(state: CrdtState, patch: JsonPatchOp[], options?: Ap
255
341
  * Throws `PatchError` on conflict.
256
342
  * @param state - The CRDT state to mutate.
257
343
  * @param patch - Array of RFC 6902 JSON Patch operations.
258
- * @param options - Optional base document and test evaluation mode.
344
+ * @param options - Optional base state snapshot, patch semantics, and atomicity.
259
345
  */
260
- declare function applyPatchInPlace(state: CrdtState, patch: JsonPatchOp[], options?: ApplyPatchOptions): void;
261
- /**
262
- * Apply a JSON Patch as a specific actor while maintaining an external version vector.
263
- * Returns the updated state and a new version vector snapshot.
264
- */
265
- declare function applyPatchAsActor(doc: Doc, vv: VersionVector, actor: ActorId, patch: JsonPatchOp[], options?: ApplyPatchOptions): ApplyPatchAsActorResult;
266
- //#endregion
267
- //#region src/clock.d.ts
268
- /**
269
- * Create a new clock for the given actor. Each call to `clock.next()` yields a fresh `Dot`.
270
- * @param actor - Unique identifier for this peer.
271
- * @param start - Initial counter value (defaults to 0).
272
- */
273
- declare function createClock(actor: ActorId, start?: number): Clock;
274
- /** Create an independent copy of a clock at the same counter position. */
275
- declare function cloneClock(clock: Clock): Clock;
276
- /**
277
- * Generate the next per-actor dot from a mutable version vector.
278
- * Useful when a server needs to mint dots for many actors.
279
- */
280
- declare function nextDotForActor(vv: VersionVector, actor: ActorId): Dot;
281
- /** Record an observed dot in a version vector. */
282
- declare function observeDot(vv: VersionVector, dot: Dot): void;
283
- //#endregion
284
- //#region src/doc.d.ts
346
+ declare function applyPatchInPlace(state: CrdtState, patch: JsonPatchOp[], options?: ApplyPatchInPlaceOptions): void;
347
+ /** Non-throwing immutable patch application variant. */
348
+ declare function tryApplyPatch(state: CrdtState, patch: JsonPatchOp[], options?: ApplyPatchOptions): TryApplyPatchResult;
349
+ /** Non-throwing in-place patch application variant. */
350
+ declare function tryApplyPatchInPlace(state: CrdtState, patch: JsonPatchOp[], options?: ApplyPatchInPlaceOptions): TryApplyPatchInPlaceResult;
285
351
  /**
286
- * Create a CRDT document from a JSON value, using fresh dots for each node.
287
- * @param value - The JSON value to convert.
288
- * @param nextDot - A function that generates a unique `Dot` on each call.
289
- * @returns A new CRDT `Doc`.
352
+ * Validate whether a patch is applicable against a JSON base value under the chosen options.
353
+ * Does not mutate caller-provided values.
290
354
  */
291
- declare function docFromJson(value: JsonValue, nextDot: () => Dot): Doc;
355
+ declare function validateJsonPatch(base: JsonValue, patch: JsonPatchOp[], options?: ApplyPatchOptions): ValidatePatchResult;
292
356
  /**
293
- * Legacy: create a doc using a single dot with counter offsets for array children.
294
- * Prefer `docFromJson(value, nextDot)` to ensure unique dots per node.
295
- */
296
- declare function docFromJsonWithDot(value: JsonValue, dot: Dot): Doc;
297
- /** Deep-clone a CRDT document. The clone is fully independent of the original. */
298
- declare function cloneDoc(doc: Doc): Doc;
299
- /**
300
- * Apply compiled intent operations to a CRDT document.
301
- * Array indices are resolved against the base document.
302
- * @param base - The base document snapshot used for index mapping and test evaluation.
303
- * @param head - The target document to mutate.
304
- * @param intents - Compiled intent operations from `compileJsonPatchToIntent`.
305
- * @param newDot - A function that generates a unique `Dot` per mutation.
306
- * @param evalTestAgainst - Whether `test` ops are evaluated against `"head"` or `"base"`.
307
- * @param bumpCounterAbove - Optional hook that can fast-forward the underlying counter before inserts.
308
- * @returns `{ ok: true }` on success, or `{ ok: false, code: 409, message }` on conflict.
309
- */
310
- declare function applyIntentsToCrdt(base: Doc, head: Doc, intents: IntentOp[], newDot: () => Dot, evalTestAgainst?: "head" | "base", bumpCounterAbove?: (ctr: number) => void): ApplyResult;
311
- /**
312
- * Convenience wrapper: compile a JSON Patch and apply it to a CRDT document.
313
- * @param base - The base document for index resolution.
314
- * @param head - The target document to mutate.
315
- * @param patch - Array of RFC 6902 JSON Patch operations.
316
- * @param newDot - A function that generates a unique `Dot` per mutation.
317
- * @param evalTestAgainst - Whether `test` ops evaluate against `"head"` or `"base"`.
318
- * @param bumpCounterAbove - Optional hook that can fast-forward the underlying counter before inserts.
319
- * @returns `{ ok: true }` on success, or `{ ok: false, code: 409, message }` on conflict.
320
- */
321
- declare function jsonPatchToCrdt(base: Doc, head: Doc, patch: JsonPatchOp[], newDot: () => Dot, evalTestAgainst?: "head" | "base", bumpCounterAbove?: (ctr: number) => void): ApplyResult;
322
- /**
323
- * Safe wrapper around `jsonPatchToCrdt` that converts compile-time errors into `409` results.
324
- * This function never throws for malformed/invalid patch paths.
325
- */
326
- declare function jsonPatchToCrdtSafe(base: Doc, head: Doc, patch: JsonPatchOp[], newDot: () => Dot, evalTestAgainst?: "head" | "base", bumpCounterAbove?: (ctr: number) => void): ApplyResult;
327
- /**
328
- * Generate a JSON Patch delta between two CRDT documents.
329
- * @param base - The base document snapshot.
330
- * @param head - The current document state.
331
- * @param options - Diff options (e.g. `{ arrayStrategy: "lcs" }`).
332
- * @returns An array of JSON Patch operations that transform base into head.
333
- */
334
- declare function crdtToJsonPatch(base: Doc, head: Doc, options?: DiffOptions): JsonPatchOp[];
335
- /**
336
- * Emit a single root `replace` patch representing the full document state.
337
- * Use `crdtToJsonPatch(base, head)` for delta patches instead.
357
+ * Apply a JSON Patch as a specific actor while maintaining an external version vector.
358
+ * Returns the updated state and a new version vector snapshot.
338
359
  */
339
- declare function crdtToFullReplace(doc: Doc): JsonPatchOp[];
360
+ declare function applyPatchAsActor(doc: Doc, vv: VersionVector, actor: ActorId, patch: JsonPatchOp[], options?: ApplyPatchAsActorOptions): ApplyPatchAsActorResult;
340
361
  //#endregion
341
362
  //#region src/patch.d.ts
363
+ /** Structured compile error used to map patch validation failures to typed reasons. */
364
+ declare class PatchCompileError extends Error {
365
+ readonly reason: PatchErrorReason;
366
+ readonly path?: string;
367
+ readonly opIndex?: number;
368
+ constructor(reason: PatchErrorReason, message: string, path?: string, opIndex?: number);
369
+ }
342
370
  /**
343
371
  * Parse an RFC 6901 JSON Pointer into a path array, unescaping `~1` and `~0`.
344
372
  * @param ptr - A JSON Pointer string (e.g. `"/a/b"` or `""`).
@@ -360,7 +388,7 @@ declare function getAtJson(base: JsonValue, path: string[]): JsonValue;
360
388
  * @param patch - Array of JSON Patch operations.
361
389
  * @returns An array of `IntentOp` ready for `applyIntentsToCrdt`.
362
390
  */
363
- declare function compileJsonPatchToIntent(baseJson: JsonValue, patch: JsonPatchOp[]): IntentOp[];
391
+ declare function compileJsonPatchToIntent(baseJson: JsonValue, patch: JsonPatchOp[], options?: CompilePatchOptions): IntentOp[];
364
392
  /**
365
393
  * Compute a JSON Patch delta between two JSON values.
366
394
  * By default arrays use a deterministic LCS strategy.
@@ -384,11 +412,14 @@ declare function serializeState(state: CrdtState): SerializedState;
384
412
  /** Reconstruct a full CRDT state from its serialized form, restoring the clock. */
385
413
  declare function deserializeState(data: SerializedState): CrdtState;
386
414
  //#endregion
387
- //#region src/materialize.d.ts
388
- /** Recursively convert a CRDT node graph into a plain JSON value. */
389
- declare function materialize(node: Node): JsonValue;
390
- //#endregion
391
415
  //#region src/merge.d.ts
416
+ /** Error thrown by throwing merge helpers (`mergeDoc` / `mergeState`). */
417
+ declare class MergeError extends Error {
418
+ readonly code: 409;
419
+ readonly reason: "LINEAGE_MISMATCH";
420
+ readonly path?: string;
421
+ constructor(error: ApplyError);
422
+ }
392
423
  /**
393
424
  * Merge two CRDT documents from different peers into one.
394
425
  * By default this requires shared array lineage for non-empty sequences.
@@ -403,6 +434,8 @@ declare function materialize(node: Node): JsonValue;
403
434
  * - **Kind mismatch**: the node with the higher "representative dot" wins and replaces the other entirely.
404
435
  */
405
436
  declare function mergeDoc(a: Doc, b: Doc, options?: MergeDocOptions): Doc;
437
+ /** Non-throwing `mergeDoc` variant with structured conflict details. */
438
+ declare function tryMergeDoc(a: Doc, b: Doc, options?: MergeDocOptions): TryMergeDocResult;
406
439
  /**
407
440
  * Merge two CRDT states.
408
441
  *
@@ -415,5 +448,7 @@ declare function mergeDoc(a: Doc, b: Doc, options?: MergeDocOptions): Doc;
415
448
  * that actor across both input clocks and the merged document dots.
416
449
  */
417
450
  declare function mergeState(a: CrdtState, b: CrdtState, options?: MergeStateOptions): CrdtState;
451
+ /** Non-throwing `mergeState` variant with structured conflict details. */
452
+ declare function tryMergeState(a: CrdtState, b: CrdtState, options?: MergeStateOptions): TryMergeStateResult;
418
453
  //#endregion
419
- export { PatchSemantics as $, createState as A, Dot as B, createClock as C, applyPatch as D, PatchError as E, ApplyResult as F, JsonPrimitive as G, IntentOp as H, Clock as I, MergeDocOptions as J, JsonValue as K, CrdtState as L, ActorId as M, ApplyPatchAsActorResult as N, applyPatchAsActor as O, ApplyPatchOptions as P, ObjNode as Q, DiffOptions as R, cloneClock as S, observeDot as T, JsonPatch as U, ElemId as V, JsonPatchOp as W, Node as X, MergeStateOptions as Y, ObjEntry as Z, crdtToJsonPatch as _, deserializeState as a, SerializedState as at, jsonPatchToCrdt as b, compileJsonPatchToIntent as c, jsonEquals as d, ROOT_KEY as et, parseJsonPointer as f, crdtToFullReplace as g, cloneDoc as h, deserializeDoc as i, SerializedNode as it, toJson as j, applyPatchInPlace as k, diffJsonPatch as l, applyIntentsToCrdt as m, mergeState as n, RgaSeq as nt, serializeDoc as o, VersionVector as ot, stringifyJsonPointer as p, LwwReg as q, materialize as r, SerializedDoc as rt, serializeState as s, mergeDoc as t, RgaElem as tt, getAtJson as u, docFromJson as v, nextDotForActor as w, jsonPatchToCrdtSafe as x, docFromJsonWithDot as y, Doc as z };
454
+ export { ObjNode as $, ApplyPatchAsActorResult as A, ElemId as B, toJson as C, ActorId as D, validateJsonPatch as E, CompilePatchOptions as F, JsonPatchToCrdtOptions as G, IntentOp as H, CrdtState as I, LwwReg as J, JsonPrimitive as K, DiffOptions as L, ApplyPatchOptions as M, ApplyResult as N, ApplyError as O, Clock as P, ObjEntry as Q, Doc as R, forkState as S, tryApplyPatchInPlace as T, JsonPatch as U, ForkStateOptions as V, JsonPatchOp as W, MergeStateOptions as X, MergeDocOptions as Y, Node as Z, PatchError as _, tryMergeState as a, SerializedClock as at, applyPatchInPlace as b, serializeDoc as c, SerializedRgaElem as ct, compileJsonPatchToIntent as d, TryApplyPatchResult as dt, PatchErrorReason as et, diffJsonPatch as f, TryMergeDocResult as ft, stringifyJsonPointer as g, parseJsonPointer as h, VersionVector as ht, tryMergeDoc as i, RgaSeq as it, ApplyPatchInPlaceOptions as j, ApplyPatchAsActorOptions as k, serializeState as l, SerializedState as lt, jsonEquals as m, ValidatePatchResult as mt, mergeDoc as n, ROOT_KEY as nt, deserializeDoc as o, SerializedDoc as ot, getAtJson as p, TryMergeStateResult as pt, JsonValue as q, mergeState as r, RgaElem as rt, deserializeState as s, SerializedNode as st, MergeError as t, PatchSemantics as tt, PatchCompileError as u, TryApplyPatchInPlaceResult as ut, applyPatch as v, tryApplyPatch as w, createState as x, applyPatchAsActor as y, Dot as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-patch-to-crdt",
3
- "version": "0.0.0",
3
+ "version": "0.1.1",
4
4
  "description": "Convert JSON Patch (RFC 6902) to and from a CRDT-friendly representation.",
5
5
  "keywords": [
6
6
  "crdt",
@@ -9,6 +9,9 @@
9
9
  "rfc6902"
10
10
  ],
11
11
  "license": "MIT",
12
+ "repository": {
13
+ "url": "https://github.com/samlaycock/json-patch-to-crdt"
14
+ },
12
15
  "files": [
13
16
  "dist"
14
17
  ],
@@ -51,8 +54,5 @@
51
54
  },
52
55
  "peerDependencies": {
53
56
  "typescript": "^5"
54
- },
55
- "engines": {
56
- "node": ">=18"
57
57
  }
58
58
  }