meridian-sdk 1.0.1 → 1.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +48 -0
  3. package/dist/agents.d.ts +60 -0
  4. package/dist/agents.d.ts.map +1 -0
  5. package/dist/agents.js +200 -0
  6. package/dist/agents.js.map +1 -0
  7. package/dist/client.d.ts +30 -1
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +43 -1
  10. package/dist/client.js.map +1 -1
  11. package/dist/crdt/awareness.d.ts +3 -0
  12. package/dist/crdt/awareness.d.ts.map +1 -1
  13. package/dist/crdt/awareness.js +8 -4
  14. package/dist/crdt/awareness.js.map +1 -1
  15. package/dist/crdt/crdtmap.d.ts +3 -0
  16. package/dist/crdt/crdtmap.d.ts.map +1 -1
  17. package/dist/crdt/crdtmap.js +8 -0
  18. package/dist/crdt/crdtmap.js.map +1 -1
  19. package/dist/crdt/gcounter.d.ts +3 -0
  20. package/dist/crdt/gcounter.d.ts.map +1 -1
  21. package/dist/crdt/gcounter.js +8 -0
  22. package/dist/crdt/gcounter.js.map +1 -1
  23. package/dist/crdt/lwwregister.d.ts +3 -1
  24. package/dist/crdt/lwwregister.d.ts.map +1 -1
  25. package/dist/crdt/lwwregister.js +8 -1
  26. package/dist/crdt/lwwregister.js.map +1 -1
  27. package/dist/crdt/orset.d.ts +3 -1
  28. package/dist/crdt/orset.d.ts.map +1 -1
  29. package/dist/crdt/orset.js +8 -1
  30. package/dist/crdt/orset.js.map +1 -1
  31. package/dist/crdt/pncounter.d.ts +3 -0
  32. package/dist/crdt/pncounter.d.ts.map +1 -1
  33. package/dist/crdt/pncounter.js +8 -0
  34. package/dist/crdt/pncounter.js.map +1 -1
  35. package/dist/crdt/presence.d.ts +3 -1
  36. package/dist/crdt/presence.d.ts.map +1 -1
  37. package/dist/crdt/presence.js +8 -1
  38. package/dist/crdt/presence.js.map +1 -1
  39. package/dist/crdt/rga.d.ts +54 -0
  40. package/dist/crdt/rga.d.ts.map +1 -0
  41. package/dist/crdt/rga.js +122 -0
  42. package/dist/crdt/rga.js.map +1 -0
  43. package/dist/errors.d.ts +6 -6
  44. package/dist/index.d.ts +6 -2
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +5 -0
  47. package/dist/index.js.map +1 -1
  48. package/dist/layer.d.ts +34 -0
  49. package/dist/layer.d.ts.map +1 -0
  50. package/dist/layer.js +31 -0
  51. package/dist/layer.js.map +1 -0
  52. package/dist/schema.d.ts +2 -0
  53. package/dist/schema.d.ts.map +1 -1
  54. package/dist/schema.js +2 -1
  55. package/dist/schema.js.map +1 -1
  56. package/dist/sync/delta.d.ts +6 -0
  57. package/dist/sync/delta.d.ts.map +1 -1
  58. package/dist/sync/delta.js +4 -0
  59. package/dist/sync/delta.js.map +1 -1
  60. package/dist/transport/http.d.ts +12 -1
  61. package/dist/transport/http.d.ts.map +1 -1
  62. package/dist/transport/http.js +4 -0
  63. package/dist/transport/http.js.map +1 -1
  64. package/dist/transport/websocket.d.ts +22 -7
  65. package/dist/transport/websocket.d.ts.map +1 -1
  66. package/dist/transport/websocket.js +173 -98
  67. package/dist/transport/websocket.js.map +1 -1
  68. package/package.json +2 -2
  69. package/src/agents.ts +277 -0
  70. package/src/client.ts +49 -1
  71. package/src/crdt/awareness.ts +9 -9
  72. package/src/crdt/crdtmap.ts +9 -0
  73. package/src/crdt/gcounter.ts +9 -0
  74. package/src/crdt/lwwregister.ts +9 -1
  75. package/src/crdt/orset.ts +9 -1
  76. package/src/crdt/pncounter.ts +9 -0
  77. package/src/crdt/presence.ts +9 -1
  78. package/src/crdt/rga.ts +136 -0
  79. package/src/index.ts +17 -1
  80. package/src/layer.ts +44 -0
  81. package/src/schema.ts +2 -1
  82. package/src/sync/delta.ts +11 -1
  83. package/src/transport/http.ts +18 -1
  84. package/src/transport/websocket.ts +220 -96
  85. package/test/agents.test.ts +104 -0
  86. package/test/crdt.test.ts +0 -28
  87. package/test/integration.test.ts +0 -7
  88. package/test/offline-queue.test.ts +0 -8
  89. package/test/sync.test.ts +0 -12
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Unit tests for agents.ts — no server required.
3
+ */
4
+
5
+ import { describe, it, expect } from "bun:test";
6
+ import { getMeridianTools, executeMeridianTool } from "../src/agents.js";
7
+ import type { ToolUseBlock } from "../src/agents.js";
8
+
9
+ const CONFIG = { baseUrl: "http://localhost:3000", token: "tok", namespace: "test-ns" };
10
+
11
+ describe("getMeridianTools", () => {
12
+ it("generates 4 tools per CRDT ID", () => {
13
+ const tools = getMeridianTools(CONFIG, ["counter"]);
14
+ expect(tools).toHaveLength(4);
15
+ });
16
+
17
+ it("generates correct tool names", () => {
18
+ const tools = getMeridianTools(CONFIG, ["my-counter"]);
19
+ const names = tools.map((t) => t.name);
20
+ expect(names).toContain("meridian_read_my_counter");
21
+ expect(names).toContain("meridian_increment_my_counter");
22
+ expect(names).toContain("meridian_set_my_counter");
23
+ expect(names).toContain("meridian_add_my_counter");
24
+ });
25
+
26
+ it("handles multiple CRDT IDs", () => {
27
+ const tools = getMeridianTools(CONFIG, ["counter", "tasks", "notes"]);
28
+ expect(tools).toHaveLength(12);
29
+ });
30
+
31
+ it("returns empty array for empty crdtIds", () => {
32
+ const tools = getMeridianTools(CONFIG, []);
33
+ expect(tools).toHaveLength(0);
34
+ });
35
+
36
+ it("sanitizes special characters in CRDT IDs", () => {
37
+ const tools = getMeridianTools(CONFIG, ["my:crdt/id"]);
38
+ const names = tools.map((t) => t.name);
39
+ // colons and slashes should be replaced with underscores
40
+ expect(names.every((n) => /^meridian_[a-z_]+$/.test(n))).toBe(true);
41
+ });
42
+
43
+ it("read tool has no required fields", () => {
44
+ const tools = getMeridianTools(CONFIG, ["counter"]);
45
+ const readTool = tools.find((t) => t.name === "meridian_read_counter");
46
+ expect(readTool).toBeDefined();
47
+ expect(readTool!.input_schema.required).toBeUndefined();
48
+ });
49
+
50
+ it("increment tool requires amount and client_id", () => {
51
+ const tools = getMeridianTools(CONFIG, ["counter"]);
52
+ const incrTool = tools.find((t) => t.name === "meridian_increment_counter");
53
+ expect(incrTool!.input_schema.required).toContain("amount");
54
+ expect(incrTool!.input_schema.required).toContain("client_id");
55
+ });
56
+
57
+ it("set tool requires value and client_id", () => {
58
+ const tools = getMeridianTools(CONFIG, ["notes"]);
59
+ const setTool = tools.find((t) => t.name === "meridian_set_notes");
60
+ expect(setTool!.input_schema.required).toContain("value");
61
+ expect(setTool!.input_schema.required).toContain("client_id");
62
+ });
63
+
64
+ it("add tool requires element and client_id", () => {
65
+ const tools = getMeridianTools(CONFIG, ["tags"]);
66
+ const addTool = tools.find((t) => t.name === "meridian_add_tags");
67
+ expect(addTool!.input_schema.required).toContain("element");
68
+ expect(addTool!.input_schema.required).toContain("client_id");
69
+ });
70
+
71
+ it("all tools have descriptions mentioning the CRDT id", () => {
72
+ const tools = getMeridianTools(CONFIG, ["visits"]);
73
+ for (const tool of tools) {
74
+ expect(tool.description).toContain("visits");
75
+ }
76
+ });
77
+
78
+ it("read tool description mentions the namespace", () => {
79
+ const tools = getMeridianTools(CONFIG, ["visits"]);
80
+ const readTool = tools.find((t) => t.name === "meridian_read_visits");
81
+ expect(readTool!.description).toContain("test-ns");
82
+ });
83
+
84
+ it("all tools have input_schema type object", () => {
85
+ const tools = getMeridianTools(CONFIG, ["x"]);
86
+ for (const tool of tools) {
87
+ expect(tool.input_schema.type).toBe("object");
88
+ }
89
+ });
90
+ });
91
+
92
+ describe("executeMeridianTool", () => {
93
+ it("returns error JSON for unknown tool name", async () => {
94
+ const toolUse: ToolUseBlock = {
95
+ type: "tool_use",
96
+ id: "tu_1",
97
+ name: "meridian_unknown_op",
98
+ input: {},
99
+ };
100
+ const result = await executeMeridianTool(CONFIG, toolUse);
101
+ const parsed = JSON.parse(result);
102
+ expect(parsed.error).toBeDefined();
103
+ });
104
+ });
package/test/crdt.test.ts CHANGED
@@ -13,10 +13,6 @@ import { PresenceHandle } from "../src/crdt/presence.js";
13
13
  import { CRDTMapHandle } from "../src/crdt/crdtmap.js";
14
14
  import type { WsTransport } from "../src/transport/websocket.js";
15
15
 
16
- // ---------------------------------------------------------------------------
17
- // Stub transport — captures sends, never actually connects
18
- // ---------------------------------------------------------------------------
19
-
20
16
  function stubTransport(): WsTransport & { sent: unknown[] } {
21
17
  const sent: unknown[] = [];
22
18
  return {
@@ -32,10 +28,6 @@ function stubTransport(): WsTransport & { sent: unknown[] } {
32
28
 
33
29
  const BASE_OPTS = { ns: "test", clientId: 1 };
34
30
 
35
- // ---------------------------------------------------------------------------
36
- // GCounter
37
- // ---------------------------------------------------------------------------
38
-
39
31
  describe("GCounterHandle", () => {
40
32
  it("starts at 0", () => {
41
33
  const t = stubTransport();
@@ -96,10 +88,6 @@ describe("GCounterHandle", () => {
96
88
  });
97
89
  });
98
90
 
99
- // ---------------------------------------------------------------------------
100
- // PNCounter
101
- // ---------------------------------------------------------------------------
102
-
103
91
  describe("PNCounterHandle", () => {
104
92
  it("starts at 0", () => {
105
93
  const t = stubTransport();
@@ -130,10 +118,6 @@ describe("PNCounterHandle", () => {
130
118
  });
131
119
  });
132
120
 
133
- // ---------------------------------------------------------------------------
134
- // ORSet
135
- // ---------------------------------------------------------------------------
136
-
137
121
  describe("ORSetHandle", () => {
138
122
  it("empty by default", () => {
139
123
  const t = stubTransport();
@@ -179,10 +163,6 @@ describe("ORSetHandle", () => {
179
163
  });
180
164
  });
181
165
 
182
- // ---------------------------------------------------------------------------
183
- // LWW Register
184
- // ---------------------------------------------------------------------------
185
-
186
166
  describe("LwwRegisterHandle", () => {
187
167
  it("starts null", () => {
188
168
  const t = stubTransport();
@@ -228,10 +208,6 @@ describe("LwwRegisterHandle", () => {
228
208
  });
229
209
  });
230
210
 
231
- // ---------------------------------------------------------------------------
232
- // Presence
233
- // ---------------------------------------------------------------------------
234
-
235
211
  describe("PresenceHandle", () => {
236
212
  it("empty by default", () => {
237
213
  const t = stubTransport();
@@ -292,10 +268,6 @@ describe("PresenceHandle", () => {
292
268
  });
293
269
  });
294
270
 
295
- // ---------------------------------------------------------------------------
296
- // TTL — ttl_ms forwarded in Op message
297
- // ---------------------------------------------------------------------------
298
-
299
271
  describe("ttl_ms forwarding", () => {
300
272
  it("GCounter.increment includes ttl_ms when provided", () => {
301
273
  const t = stubTransport();
@@ -17,12 +17,9 @@ import { MeridianClient } from "../src/client.js";
17
17
  import { HttpClient } from "../src/transport/http.js";
18
18
  import { uuidToBytes } from "../src/codec.js";
19
19
 
20
- // ---------------------------------------------------------------------------
21
20
  // Config — matches the dev signing key [0x42; 32]
22
21
  // The token below is for namespace="integration", client_id=1, TTL=24h
23
22
  // Regenerate with: cargo run --bin gen_token -- integration 1
24
- // ---------------------------------------------------------------------------
25
-
26
23
  const SERVER_URL = "http://127.0.0.1:3737";
27
24
  const NAMESPACE = "integration";
28
25
 
@@ -53,10 +50,6 @@ async function isServerUp(): Promise<boolean> {
53
50
  }
54
51
  }
55
52
 
56
- // ---------------------------------------------------------------------------
57
- // Test suite
58
- // ---------------------------------------------------------------------------
59
-
60
53
  describe("Meridian integration", () => {
61
54
  let http: HttpClient;
62
55
 
@@ -8,10 +8,6 @@ import { describe, it, expect, beforeEach, afterEach } from "bun:test";
8
8
  import { WsTransport } from "../src/transport/websocket.js";
9
9
  import { OFFLINE_QUEUE_MAX } from "../src/constants.js";
10
10
 
11
- // ---------------------------------------------------------------------------
12
- // Fake WebSocket
13
- // ---------------------------------------------------------------------------
14
-
15
11
  type WsEventName = "open" | "message" | "close" | "error";
16
12
 
17
13
  class FakeWebSocket {
@@ -69,10 +65,6 @@ function makeTransport(onStateChange?: (s: string) => void): WsTransport {
69
65
  });
70
66
  }
71
67
 
72
- // ---------------------------------------------------------------------------
73
- // Tests
74
- // ---------------------------------------------------------------------------
75
-
76
68
  describe("WsTransport offline queue", () => {
77
69
  it("buffers ops sent while DISCONNECTED", () => {
78
70
  const t = makeTransport();
package/test/sync.test.ts CHANGED
@@ -9,10 +9,6 @@ import { encode, decode, encodeClientMsg, decodeServerMsg } from "../src/codec.j
9
9
  import { parseToken, checkTokenExpiry } from "../src/auth/token.js";
10
10
  import { CodecError, TokenParseError, TokenExpiredError } from "../src/errors.js";
11
11
 
12
- // ---------------------------------------------------------------------------
13
- // VectorClockTracker
14
- // ---------------------------------------------------------------------------
15
-
16
12
  describe("VectorClockTracker", () => {
17
13
  function freshStorage() {
18
14
  const store = new Map<string, Record<string, number>>();
@@ -66,10 +62,6 @@ describe("VectorClockTracker", () => {
66
62
  });
67
63
  });
68
64
 
69
- // ---------------------------------------------------------------------------
70
- // Codec
71
- // ---------------------------------------------------------------------------
72
-
73
65
  describe("codec", () => {
74
66
  it("encode/decode roundtrip for plain objects", () => {
75
67
  const obj = { foo: "bar", n: 42 };
@@ -117,10 +109,6 @@ describe("codec", () => {
117
109
  });
118
110
  });
119
111
 
120
- // ---------------------------------------------------------------------------
121
- // Token parsing
122
- // ---------------------------------------------------------------------------
123
-
124
112
  describe("parseToken", () => {
125
113
  function makeToken(claims: object): string {
126
114
  const payload = encode(claims);