meridian-sdk 1.2.0 → 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 (67) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +1 -0
  3. package/dist/agents.d.ts +143 -8
  4. package/dist/agents.d.ts.map +1 -1
  5. package/dist/agents.js +141 -14
  6. package/dist/agents.js.map +1 -1
  7. package/dist/client.d.ts +35 -2
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +45 -3
  10. package/dist/client.js.map +1 -1
  11. package/dist/conflict/types.d.ts +54 -0
  12. package/dist/conflict/types.d.ts.map +1 -0
  13. package/dist/conflict/types.js +9 -0
  14. package/dist/conflict/types.js.map +1 -0
  15. package/dist/crdt/rga.d.ts +20 -4
  16. package/dist/crdt/rga.d.ts.map +1 -1
  17. package/dist/crdt/rga.js +42 -6
  18. package/dist/crdt/rga.js.map +1 -1
  19. package/dist/crdt/tree.d.ts +128 -0
  20. package/dist/crdt/tree.d.ts.map +1 -0
  21. package/dist/crdt/tree.js +304 -0
  22. package/dist/crdt/tree.js.map +1 -0
  23. package/dist/index.d.ts +13 -4
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +10 -3
  26. package/dist/index.js.map +1 -1
  27. package/dist/schema.d.ts +63 -3
  28. package/dist/schema.d.ts.map +1 -1
  29. package/dist/schema.js +18 -1
  30. package/dist/schema.js.map +1 -1
  31. package/dist/sync/delta.d.ts +35 -0
  32. package/dist/sync/delta.d.ts.map +1 -1
  33. package/dist/sync/delta.js +4 -0
  34. package/dist/sync/delta.js.map +1 -1
  35. package/dist/undo/UndoManager.d.ts +74 -0
  36. package/dist/undo/UndoManager.d.ts.map +1 -0
  37. package/dist/undo/UndoManager.js +318 -0
  38. package/dist/undo/UndoManager.js.map +1 -0
  39. package/dist/undo/types.d.ts +63 -0
  40. package/dist/undo/types.d.ts.map +1 -0
  41. package/dist/undo/types.js +9 -0
  42. package/dist/undo/types.js.map +1 -0
  43. package/dist/utils/fractional.d.ts +78 -0
  44. package/dist/utils/fractional.d.ts.map +1 -0
  45. package/dist/utils/fractional.js +159 -0
  46. package/dist/utils/fractional.js.map +1 -0
  47. package/dist/validation/index.d.ts +107 -0
  48. package/dist/validation/index.d.ts.map +1 -0
  49. package/dist/validation/index.js +123 -0
  50. package/dist/validation/index.js.map +1 -0
  51. package/package.json +1 -1
  52. package/src/agents.ts +224 -15
  53. package/src/client.ts +52 -3
  54. package/src/conflict/types.ts +60 -0
  55. package/src/crdt/rga.ts +46 -7
  56. package/src/crdt/tree.ts +367 -0
  57. package/src/index.ts +31 -1
  58. package/src/schema.ts +24 -1
  59. package/src/sync/delta.ts +30 -1
  60. package/src/undo/UndoManager.ts +369 -0
  61. package/src/undo/types.ts +74 -0
  62. package/src/utils/fractional.ts +166 -0
  63. package/src/validation/index.ts +137 -0
  64. package/test/conflict.test.ts +242 -0
  65. package/test/fractional.test.ts +127 -0
  66. package/test/undo.test.ts +272 -0
  67. package/test/validation.test.ts +137 -0
@@ -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.0",
3
+ "version": "1.3.0",
4
4
  "description": "TypeScript SDK for Meridian CRDT server",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",