meridian-sdk 1.2.1 → 1.3.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 (66) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/agents.d.ts +143 -8
  3. package/dist/agents.d.ts.map +1 -1
  4. package/dist/agents.js +141 -14
  5. package/dist/agents.js.map +1 -1
  6. package/dist/client.d.ts +7 -2
  7. package/dist/client.d.ts.map +1 -1
  8. package/dist/client.js +4 -4
  9. package/dist/client.js.map +1 -1
  10. package/dist/conflict/types.d.ts +54 -0
  11. package/dist/conflict/types.d.ts.map +1 -0
  12. package/dist/conflict/types.js +9 -0
  13. package/dist/conflict/types.js.map +1 -0
  14. package/dist/crdt/rga.d.ts +20 -4
  15. package/dist/crdt/rga.d.ts.map +1 -1
  16. package/dist/crdt/rga.js +42 -6
  17. package/dist/crdt/rga.js.map +1 -1
  18. package/dist/crdt/tree.d.ts +45 -0
  19. package/dist/crdt/tree.d.ts.map +1 -1
  20. package/dist/crdt/tree.js +135 -10
  21. package/dist/crdt/tree.js.map +1 -1
  22. package/dist/index.d.ts +10 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +9 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/schema.d.ts +63 -3
  27. package/dist/schema.d.ts.map +1 -1
  28. package/dist/schema.js +18 -1
  29. package/dist/schema.js.map +1 -1
  30. package/dist/sync/delta.d.ts +24 -0
  31. package/dist/sync/delta.d.ts.map +1 -1
  32. package/dist/sync/delta.js +1 -1
  33. package/dist/sync/delta.js.map +1 -1
  34. package/dist/undo/UndoManager.d.ts +74 -0
  35. package/dist/undo/UndoManager.d.ts.map +1 -0
  36. package/dist/undo/UndoManager.js +318 -0
  37. package/dist/undo/UndoManager.js.map +1 -0
  38. package/dist/undo/types.d.ts +63 -0
  39. package/dist/undo/types.d.ts.map +1 -0
  40. package/dist/undo/types.js +9 -0
  41. package/dist/undo/types.js.map +1 -0
  42. package/dist/utils/fractional.d.ts +78 -0
  43. package/dist/utils/fractional.d.ts.map +1 -0
  44. package/dist/utils/fractional.js +159 -0
  45. package/dist/utils/fractional.js.map +1 -0
  46. package/dist/validation/index.d.ts +107 -0
  47. package/dist/validation/index.d.ts.map +1 -0
  48. package/dist/validation/index.js +123 -0
  49. package/dist/validation/index.js.map +1 -0
  50. package/package.json +1 -1
  51. package/src/agents.ts +224 -15
  52. package/src/client.ts +5 -4
  53. package/src/conflict/types.ts +60 -0
  54. package/src/crdt/rga.ts +46 -7
  55. package/src/crdt/tree.ts +164 -0
  56. package/src/index.ts +28 -1
  57. package/src/schema.ts +24 -1
  58. package/src/sync/delta.ts +15 -2
  59. package/src/undo/UndoManager.ts +369 -0
  60. package/src/undo/types.ts +74 -0
  61. package/src/utils/fractional.ts +166 -0
  62. package/src/validation/index.ts +137 -0
  63. package/test/conflict.test.ts +242 -0
  64. package/test/fractional.test.ts +127 -0
  65. package/test/undo.test.ts +272 -0
  66. package/test/validation.test.ts +137 -0
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Undo/redo entry types for CRDT-aware per-client history.
3
+ *
4
+ * Each entry stores enough information to produce the inverse operation.
5
+ * RGA Delete is intentionally not recorded — tombstones are final and
6
+ * the content is not recoverable (same semantics as ProseMirror/Yjs).
7
+ */
8
+ export interface RgaInsertEntry {
9
+ readonly kind: "rga_insert";
10
+ readonly crdtId: string;
11
+ /** HLC string "wall_ms:logical:node_id" of the inserted node. Undo = deleteById(nodeId). */
12
+ readonly nodeId: string;
13
+ /** Original visible position — used for redo re-insert. */
14
+ readonly pos: number;
15
+ /** The character that was inserted. */
16
+ readonly content: string;
17
+ }
18
+ export interface TreeAddEntry {
19
+ readonly kind: "tree_add";
20
+ readonly crdtId: string;
21
+ /** Node ID returned by addNode(). Undo = deleteNode(nodeId). */
22
+ readonly nodeId: string;
23
+ /** Stored for redo: re-add with same parent/position/value. */
24
+ readonly parentId: string | null;
25
+ readonly position: string;
26
+ readonly value: string;
27
+ }
28
+ export interface TreeDeleteEntry {
29
+ readonly kind: "tree_delete";
30
+ readonly crdtId: string;
31
+ readonly nodeId: string;
32
+ /** State captured before the delete — used to reconstruct addNode for undo. */
33
+ readonly parentId: string | null;
34
+ readonly position: string;
35
+ readonly value: string;
36
+ readonly updatedAt: string;
37
+ }
38
+ export interface TreeMoveEntry {
39
+ readonly kind: "tree_move";
40
+ readonly crdtId: string;
41
+ readonly nodeId: string;
42
+ readonly oldParentId: string | null;
43
+ readonly oldPosition: string;
44
+ /** Stored for redo. */
45
+ readonly newParentId: string | null;
46
+ readonly newPosition: string;
47
+ }
48
+ export interface TreeUpdateEntry {
49
+ readonly kind: "tree_update";
50
+ readonly crdtId: string;
51
+ readonly nodeId: string;
52
+ /** Captured before the update — used for undo. */
53
+ readonly previousValue: string;
54
+ readonly previousUpdatedAt: string;
55
+ /** Stored for redo. */
56
+ readonly newValue: string;
57
+ }
58
+ export type UndoEntry = RgaInsertEntry | TreeAddEntry | TreeDeleteEntry | TreeMoveEntry | TreeUpdateEntry;
59
+ /** A batch groups one or more entries into a single logical undo step. */
60
+ export interface UndoBatch {
61
+ readonly entries: readonly UndoEntry[];
62
+ }
63
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/undo/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,4FAA4F;IAC5F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,uBAAuB;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,uBAAuB;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,SAAS,GACjB,cAAc,GACd,YAAY,GACZ,eAAe,GACf,aAAa,GACb,eAAe,CAAC;AAEpB,0EAA0E;AAC1E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;CACxC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Undo/redo entry types for CRDT-aware per-client history.
3
+ *
4
+ * Each entry stores enough information to produce the inverse operation.
5
+ * RGA Delete is intentionally not recorded — tombstones are final and
6
+ * the content is not recoverable (same semantics as ProseMirror/Yjs).
7
+ */
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/undo/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Fractional indexing helpers for TreeCRDT sibling ordering.
3
+ *
4
+ * Positions are lexicographically ordered strings over the alphabet [a-z0-9].
5
+ * They can be compared with plain string comparison: "a0" < "b0" < "z9".
6
+ *
7
+ * Use these helpers to compute positions when inserting nodes between, before,
8
+ * or after existing siblings — without having to manage the alphabet manually.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { fi } from "meridian-sdk";
13
+ *
14
+ * const first = fi.start(); // "a0"
15
+ * const last = fi.end(); // "z9"
16
+ * const mid = fi.between("a0","z9"); // "m4" (midpoint)
17
+ * const prev = fi.before("a0"); // "V9" (below a0)
18
+ * const next = fi.after("z9"); // "{0" (above z9, still valid)
19
+ * ```
20
+ */
21
+ /**
22
+ * Returns a position string strictly between `a` and `b`.
23
+ *
24
+ * Both `a` and `b` must be valid fractional index strings, and `a < b`
25
+ * lexicographically. Uses BigInt arithmetic to find the exact midpoint,
26
+ * appending an extra character when the gap is too small.
27
+ *
28
+ * @param a - Lower bound (exclusive).
29
+ * @param b - Upper bound (exclusive).
30
+ */
31
+ export declare function between(a: string, b: string): string;
32
+ /**
33
+ * Returns a position string that sorts before `a`.
34
+ *
35
+ * @param a - The reference position.
36
+ */
37
+ export declare function before(a: string): string;
38
+ /**
39
+ * Returns a position string that sorts after `b`.
40
+ *
41
+ * @param b - The reference position.
42
+ */
43
+ export declare function after(b: string): string;
44
+ /**
45
+ * Returns the canonical start position — the lowest recommended initial value.
46
+ */
47
+ export declare function start(): string;
48
+ /**
49
+ * Returns the canonical end position — the highest recommended initial value.
50
+ */
51
+ export declare function end(): string;
52
+ /**
53
+ * Computes an array of `n` evenly-spaced positions between `a` and `b`
54
+ * (exclusive). Useful for bulk-inserting nodes in order.
55
+ *
56
+ * @param a - Lower bound (exclusive).
57
+ * @param b - Upper bound (exclusive).
58
+ * @param n - Number of positions to generate.
59
+ */
60
+ export declare function spread(a: string, b: string, n: number): string[];
61
+ /**
62
+ * Namespace object for convenient import.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * import { fi } from "meridian-sdk";
67
+ * const pos = fi.between("a0", "z9");
68
+ * ```
69
+ */
70
+ export declare const fi: {
71
+ between: typeof between;
72
+ before: typeof before;
73
+ after: typeof after;
74
+ start: typeof start;
75
+ end: typeof end;
76
+ spread: typeof spread;
77
+ };
78
+ //# sourceMappingURL=fractional.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fractional.d.ts","sourceRoot":"","sources":["../../src/utils/fractional.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA+CH;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBpD;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvC;AAED;;GAEG;AACH,wBAAgB,KAAK,IAAI,MAAM,CAE9B;AAED;;GAEG;AACH,wBAAgB,GAAG,IAAI,MAAM,CAE5B;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAoBhE;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,EAAE;;;;;;;CAAiD,CAAC"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Fractional indexing helpers for TreeCRDT sibling ordering.
3
+ *
4
+ * Positions are lexicographically ordered strings over the alphabet [a-z0-9].
5
+ * They can be compared with plain string comparison: "a0" < "b0" < "z9".
6
+ *
7
+ * Use these helpers to compute positions when inserting nodes between, before,
8
+ * or after existing siblings — without having to manage the alphabet manually.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * import { fi } from "meridian-sdk";
13
+ *
14
+ * const first = fi.start(); // "a0"
15
+ * const last = fi.end(); // "z9"
16
+ * const mid = fi.between("a0","z9"); // "m4" (midpoint)
17
+ * const prev = fi.before("a0"); // "V9" (below a0)
18
+ * const next = fi.after("z9"); // "{0" (above z9, still valid)
19
+ * ```
20
+ */
21
+ /** Characters used in fractional index strings, in ascending order. */
22
+ const CHARS = "0123456789abcdefghijklmnopqrstuvwxyz";
23
+ const BASE = BigInt(CHARS.length); // 36
24
+ /** Encode a position string to a BigInt for arithmetic. */
25
+ function toNum(s) {
26
+ let n = 0n;
27
+ for (const c of s) {
28
+ const idx = CHARS.indexOf(c);
29
+ if (idx === -1)
30
+ throw new Error(`Invalid fractional index character: "${c}"`);
31
+ n = n * BASE + BigInt(idx);
32
+ }
33
+ return n;
34
+ }
35
+ /** Decode a BigInt back to a fixed-length position string. */
36
+ function toStr(n, len) {
37
+ let s = "";
38
+ for (let i = 0; i < len; i++) {
39
+ s = (CHARS[Number(n % BASE)] ?? "0") + s;
40
+ n = n / BASE;
41
+ }
42
+ return s;
43
+ }
44
+ /** Increment a position string by 1 in base-36. */
45
+ function increment(s) {
46
+ const n = toNum(s) + 1n;
47
+ // If the result requires more digits, extend by one char
48
+ const maxForLen = BASE ** BigInt(s.length) - 1n;
49
+ if (n > maxForLen)
50
+ return toStr(n, s.length + 1);
51
+ return toStr(n, s.length);
52
+ }
53
+ /** Decrement a position string by 1 in base-36, trimming trailing zeros. */
54
+ function decrement(s) {
55
+ const n = toNum(s) - 1n;
56
+ let result = toStr(n < 0n ? 0n : n, s.length);
57
+ // Trim trailing zeros beyond length 1
58
+ while (result.length > 1 && result.endsWith("0"))
59
+ result = result.slice(0, -1);
60
+ // If underflow, prepend a zero digit
61
+ if (n < 0n)
62
+ return "0" + result;
63
+ return result;
64
+ }
65
+ /**
66
+ * Returns a position string strictly between `a` and `b`.
67
+ *
68
+ * Both `a` and `b` must be valid fractional index strings, and `a < b`
69
+ * lexicographically. Uses BigInt arithmetic to find the exact midpoint,
70
+ * appending an extra character when the gap is too small.
71
+ *
72
+ * @param a - Lower bound (exclusive).
73
+ * @param b - Upper bound (exclusive).
74
+ */
75
+ export function between(a, b) {
76
+ if (a >= b)
77
+ throw new Error(`between: a must be < b (got "${a}" >= "${b}")`);
78
+ // Pad both to the same length + 1 extra digit for precision
79
+ const len = Math.max(a.length, b.length) + 1;
80
+ const aPadded = a.padEnd(len, "0");
81
+ const bPadded = b.padEnd(len, "0");
82
+ const an = toNum(aPadded);
83
+ const bn = toNum(bPadded);
84
+ const mid = (an + bn) / 2n;
85
+ let result = toStr(mid, len);
86
+ // Trim trailing zeros for cleaner output (keep min length 1)
87
+ while (result.length > 1 && result.endsWith("0"))
88
+ result = result.slice(0, -1);
89
+ return result;
90
+ }
91
+ /**
92
+ * Returns a position string that sorts before `a`.
93
+ *
94
+ * @param a - The reference position.
95
+ */
96
+ export function before(a) {
97
+ return decrement(a);
98
+ }
99
+ /**
100
+ * Returns a position string that sorts after `b`.
101
+ *
102
+ * @param b - The reference position.
103
+ */
104
+ export function after(b) {
105
+ return increment(b);
106
+ }
107
+ /**
108
+ * Returns the canonical start position — the lowest recommended initial value.
109
+ */
110
+ export function start() {
111
+ return "a0";
112
+ }
113
+ /**
114
+ * Returns the canonical end position — the highest recommended initial value.
115
+ */
116
+ export function end() {
117
+ return "z9";
118
+ }
119
+ /**
120
+ * Computes an array of `n` evenly-spaced positions between `a` and `b`
121
+ * (exclusive). Useful for bulk-inserting nodes in order.
122
+ *
123
+ * @param a - Lower bound (exclusive).
124
+ * @param b - Upper bound (exclusive).
125
+ * @param n - Number of positions to generate.
126
+ */
127
+ export function spread(a, b, n) {
128
+ if (n <= 0)
129
+ return [];
130
+ if (n === 1)
131
+ return [between(a, b)];
132
+ const positions = [];
133
+ let lo = a;
134
+ for (let i = 0; i < n; i++) {
135
+ // Divide the remaining range into (n - i) equal slots
136
+ const remaining = n - i;
137
+ // Approximate: compute a position 1/(remaining+1) through [lo, b]
138
+ // We do this iteratively by bisecting
139
+ let hi = b;
140
+ for (let step = 0; step < Math.ceil(Math.log2(remaining + 1)); step++) {
141
+ hi = between(lo, b);
142
+ }
143
+ const pos = between(lo, b);
144
+ positions.push(pos);
145
+ lo = pos;
146
+ }
147
+ return positions;
148
+ }
149
+ /**
150
+ * Namespace object for convenient import.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * import { fi } from "meridian-sdk";
155
+ * const pos = fi.between("a0", "z9");
156
+ * ```
157
+ */
158
+ export const fi = { between, before, after, start, end, spread };
159
+ //# sourceMappingURL=fractional.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fractional.js","sourceRoot":"","sources":["../../src/utils/fractional.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Runtime validation for CRDT string values.
3
+ *
4
+ * Values stored in TreeCRDT and RGA are always strings on the wire. This
5
+ * layer lets you attach a validator to a handle so that ops are checked
6
+ * before being sent. Invalid values throw a `CrdtValidationError`.
7
+ *
8
+ * The validator interface is intentionally minimal — plug in Zod, Valibot,
9
+ * ArkType, or a plain function without any SDK dependency on those libraries.
10
+ *
11
+ * @example Using Zod:
12
+ * ```ts
13
+ * import { z } from "zod";
14
+ * import { zodValidator } from "meridian-sdk";
15
+ *
16
+ * const schema = z.object({ title: z.string(), done: z.boolean() });
17
+ * const tree = client.tree("tasks", { validator: zodValidator(schema) });
18
+ *
19
+ * // Validated — value is parsed JSON then checked against schema
20
+ * tree.addNode(null, "a0", JSON.stringify({ title: "Task 1", done: false }));
21
+ *
22
+ * // Throws CrdtValidationError
23
+ * tree.addNode(null, "a0", JSON.stringify({ title: 42 }));
24
+ * ```
25
+ *
26
+ * @example Using a plain function:
27
+ * ```ts
28
+ * import { fnValidator } from "meridian-sdk";
29
+ *
30
+ * const tree = client.tree("tasks", {
31
+ * validator: fnValidator((v) => typeof v === "string" && v.length > 0),
32
+ * });
33
+ * ```
34
+ */
35
+ /** Thrown when a CRDT value fails validation before being sent. */
36
+ export declare class CrdtValidationError extends Error {
37
+ /** The raw string value that failed. */
38
+ readonly value: string;
39
+ /** Human-readable reason from the validator. */
40
+ readonly reason: string;
41
+ readonly name = "CrdtValidationError";
42
+ constructor(
43
+ /** The raw string value that failed. */
44
+ value: string,
45
+ /** Human-readable reason from the validator. */
46
+ reason: string);
47
+ }
48
+ /**
49
+ * A validator that can be attached to a CRDT handle.
50
+ *
51
+ * `validate` receives the raw string value as stored on the wire. It should
52
+ * throw or return `{ ok: false, error: string }` for invalid values, and
53
+ * return `{ ok: true }` or `undefined` for valid ones.
54
+ */
55
+ export interface CrdtValidator {
56
+ /**
57
+ * Validate `value`. Throw, or return `{ ok: false, error }` to reject.
58
+ * Return `{ ok: true }` or `undefined` to accept.
59
+ */
60
+ validate(value: string): {
61
+ ok: false;
62
+ error: string;
63
+ } | {
64
+ ok: true;
65
+ } | undefined | void;
66
+ }
67
+ /**
68
+ * Run a validator against a value. Throws `CrdtValidationError` on failure.
69
+ * No-ops if `validator` is undefined.
70
+ */
71
+ export declare function runValidator(validator: CrdtValidator | undefined, value: string): void;
72
+ /**
73
+ * Creates a `CrdtValidator` from any Zod schema (or any object with a
74
+ * `safeParse` method returning `{ success, error }`).
75
+ *
76
+ * The string value is JSON-parsed before being passed to the schema.
77
+ * If JSON parsing fails, the raw string is passed as-is.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * import { z } from "zod";
82
+ * const validator = zodValidator(z.object({ title: z.string() }));
83
+ * ```
84
+ */
85
+ export declare function zodValidator(schema: {
86
+ safeParse(value: unknown): {
87
+ success: true;
88
+ } | {
89
+ success: false;
90
+ error: {
91
+ message: string;
92
+ };
93
+ };
94
+ }): CrdtValidator;
95
+ /**
96
+ * Creates a `CrdtValidator` from a plain predicate function.
97
+ *
98
+ * The function receives the raw string value. Return `true` to accept,
99
+ * `false` or a string (used as the error message) to reject.
100
+ *
101
+ * @example
102
+ * ```ts
103
+ * const validator = fnValidator((v) => v.length > 0 || "Value must not be empty");
104
+ * ```
105
+ */
106
+ export declare function fnValidator(fn: (value: string) => boolean | string): CrdtValidator;
107
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validation/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,mEAAmE;AACnE,qBAAa,mBAAoB,SAAQ,KAAK;IAG1C,wCAAwC;aACxB,KAAK,EAAE,MAAM;IAC7B,gDAAgD;aAChC,MAAM,EAAE,MAAM;IALhC,SAAkB,IAAI,yBAAyB;;IAE7C,wCAAwC;IACxB,KAAK,EAAE,MAAM;IAC7B,gDAAgD;IAChC,MAAM,EAAE,MAAM;CAIjC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG,SAAS,GAAG,IAAI,CAAC;CACzF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,aAAa,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAYtF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG;QAAE,OAAO,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CAC/F,GAAG,aAAa,CAgBhB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,GACtC,aAAa,CASf"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Runtime validation for CRDT string values.
3
+ *
4
+ * Values stored in TreeCRDT and RGA are always strings on the wire. This
5
+ * layer lets you attach a validator to a handle so that ops are checked
6
+ * before being sent. Invalid values throw a `CrdtValidationError`.
7
+ *
8
+ * The validator interface is intentionally minimal — plug in Zod, Valibot,
9
+ * ArkType, or a plain function without any SDK dependency on those libraries.
10
+ *
11
+ * @example Using Zod:
12
+ * ```ts
13
+ * import { z } from "zod";
14
+ * import { zodValidator } from "meridian-sdk";
15
+ *
16
+ * const schema = z.object({ title: z.string(), done: z.boolean() });
17
+ * const tree = client.tree("tasks", { validator: zodValidator(schema) });
18
+ *
19
+ * // Validated — value is parsed JSON then checked against schema
20
+ * tree.addNode(null, "a0", JSON.stringify({ title: "Task 1", done: false }));
21
+ *
22
+ * // Throws CrdtValidationError
23
+ * tree.addNode(null, "a0", JSON.stringify({ title: 42 }));
24
+ * ```
25
+ *
26
+ * @example Using a plain function:
27
+ * ```ts
28
+ * import { fnValidator } from "meridian-sdk";
29
+ *
30
+ * const tree = client.tree("tasks", {
31
+ * validator: fnValidator((v) => typeof v === "string" && v.length > 0),
32
+ * });
33
+ * ```
34
+ */
35
+ /** Thrown when a CRDT value fails validation before being sent. */
36
+ export class CrdtValidationError extends Error {
37
+ value;
38
+ reason;
39
+ name = "CrdtValidationError";
40
+ constructor(
41
+ /** The raw string value that failed. */
42
+ value,
43
+ /** Human-readable reason from the validator. */
44
+ reason) {
45
+ super(`CrdtValidationError: ${reason} (value: ${JSON.stringify(value)})`);
46
+ this.value = value;
47
+ this.reason = reason;
48
+ }
49
+ }
50
+ /**
51
+ * Run a validator against a value. Throws `CrdtValidationError` on failure.
52
+ * No-ops if `validator` is undefined.
53
+ */
54
+ export function runValidator(validator, value) {
55
+ if (validator === undefined)
56
+ return;
57
+ let result;
58
+ try {
59
+ result = validator.validate(value);
60
+ }
61
+ catch (err) {
62
+ const reason = err instanceof Error ? err.message : String(err);
63
+ throw new CrdtValidationError(value, reason);
64
+ }
65
+ if (result !== undefined && result !== null && !result.ok) {
66
+ throw new CrdtValidationError(value, result.error);
67
+ }
68
+ }
69
+ /**
70
+ * Creates a `CrdtValidator` from any Zod schema (or any object with a
71
+ * `safeParse` method returning `{ success, error }`).
72
+ *
73
+ * The string value is JSON-parsed before being passed to the schema.
74
+ * If JSON parsing fails, the raw string is passed as-is.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * import { z } from "zod";
79
+ * const validator = zodValidator(z.object({ title: z.string() }));
80
+ * ```
81
+ */
82
+ export function zodValidator(schema) {
83
+ return {
84
+ validate(raw) {
85
+ let parsed;
86
+ try {
87
+ parsed = JSON.parse(raw);
88
+ }
89
+ catch {
90
+ parsed = raw;
91
+ }
92
+ const result = schema.safeParse(parsed);
93
+ if (!result.success) {
94
+ return { ok: false, error: result.error.message };
95
+ }
96
+ return { ok: true };
97
+ },
98
+ };
99
+ }
100
+ /**
101
+ * Creates a `CrdtValidator` from a plain predicate function.
102
+ *
103
+ * The function receives the raw string value. Return `true` to accept,
104
+ * `false` or a string (used as the error message) to reject.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * const validator = fnValidator((v) => v.length > 0 || "Value must not be empty");
109
+ * ```
110
+ */
111
+ export function fnValidator(fn) {
112
+ return {
113
+ validate(raw) {
114
+ const result = fn(raw);
115
+ if (result === true)
116
+ return { ok: true };
117
+ if (result === false)
118
+ return { ok: false, error: "Validation failed" };
119
+ return { ok: false, error: result };
120
+ },
121
+ };
122
+ }
123
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validation/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,mEAAmE;AACnE,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAI1B;IAEA;IALA,IAAI,GAAG,qBAAqB,CAAC;IAC/C;IACE,wCAAwC;IACxB,KAAa;IAC7B,gDAAgD;IAChC,MAAc;QAE9B,KAAK,CAAC,wBAAwB,MAAM,YAAY,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAJ1D,UAAK,GAAL,KAAK,CAAQ;QAEb,WAAM,GAAN,MAAM,CAAQ;IAGhC,CAAC;CACF;AAiBD;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,SAAoC,EAAE,KAAa;IAC9E,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO;IACpC,IAAI,MAA6C,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,IAAI,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QAC1D,MAAM,IAAI,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;IACrD,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAAC,MAE5B;IACC,OAAO;QACL,QAAQ,CAAC,GAAW;YAClB,IAAI,MAAe,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC3B,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,GAAG,GAAG,CAAC;YACf,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACpD,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CACzB,EAAuC;IAEvC,OAAO;QACL,QAAQ,CAAC,GAAW;YAClB,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;YACvB,IAAI,MAAM,KAAK,IAAI;gBAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;YACzC,IAAI,MAAM,KAAK,KAAK;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;YACvE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QACtC,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meridian-sdk",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "TypeScript SDK for Meridian CRDT server",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",