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.
- package/CHANGELOG.md +14 -0
- package/README.md +48 -0
- package/dist/agents.d.ts +60 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +200 -0
- package/dist/agents.js.map +1 -0
- package/dist/client.d.ts +30 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +43 -1
- package/dist/client.js.map +1 -1
- package/dist/crdt/awareness.d.ts +3 -0
- package/dist/crdt/awareness.d.ts.map +1 -1
- package/dist/crdt/awareness.js +8 -4
- package/dist/crdt/awareness.js.map +1 -1
- package/dist/crdt/crdtmap.d.ts +3 -0
- package/dist/crdt/crdtmap.d.ts.map +1 -1
- package/dist/crdt/crdtmap.js +8 -0
- package/dist/crdt/crdtmap.js.map +1 -1
- package/dist/crdt/gcounter.d.ts +3 -0
- package/dist/crdt/gcounter.d.ts.map +1 -1
- package/dist/crdt/gcounter.js +8 -0
- package/dist/crdt/gcounter.js.map +1 -1
- package/dist/crdt/lwwregister.d.ts +3 -1
- package/dist/crdt/lwwregister.d.ts.map +1 -1
- package/dist/crdt/lwwregister.js +8 -1
- package/dist/crdt/lwwregister.js.map +1 -1
- package/dist/crdt/orset.d.ts +3 -1
- package/dist/crdt/orset.d.ts.map +1 -1
- package/dist/crdt/orset.js +8 -1
- package/dist/crdt/orset.js.map +1 -1
- package/dist/crdt/pncounter.d.ts +3 -0
- package/dist/crdt/pncounter.d.ts.map +1 -1
- package/dist/crdt/pncounter.js +8 -0
- package/dist/crdt/pncounter.js.map +1 -1
- package/dist/crdt/presence.d.ts +3 -1
- package/dist/crdt/presence.d.ts.map +1 -1
- package/dist/crdt/presence.js +8 -1
- package/dist/crdt/presence.js.map +1 -1
- package/dist/crdt/rga.d.ts +54 -0
- package/dist/crdt/rga.d.ts.map +1 -0
- package/dist/crdt/rga.js +122 -0
- package/dist/crdt/rga.js.map +1 -0
- package/dist/errors.d.ts +6 -6
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/layer.d.ts +34 -0
- package/dist/layer.d.ts.map +1 -0
- package/dist/layer.js +31 -0
- package/dist/layer.js.map +1 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +2 -1
- package/dist/schema.js.map +1 -1
- package/dist/sync/delta.d.ts +6 -0
- package/dist/sync/delta.d.ts.map +1 -1
- package/dist/sync/delta.js +4 -0
- package/dist/sync/delta.js.map +1 -1
- package/dist/transport/http.d.ts +12 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +4 -0
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/websocket.d.ts +22 -7
- package/dist/transport/websocket.d.ts.map +1 -1
- package/dist/transport/websocket.js +173 -98
- package/dist/transport/websocket.js.map +1 -1
- package/package.json +2 -2
- package/src/agents.ts +277 -0
- package/src/client.ts +49 -1
- package/src/crdt/awareness.ts +9 -9
- package/src/crdt/crdtmap.ts +9 -0
- package/src/crdt/gcounter.ts +9 -0
- package/src/crdt/lwwregister.ts +9 -1
- package/src/crdt/orset.ts +9 -1
- package/src/crdt/pncounter.ts +9 -0
- package/src/crdt/presence.ts +9 -1
- package/src/crdt/rga.ts +136 -0
- package/src/index.ts +17 -1
- package/src/layer.ts +44 -0
- package/src/schema.ts +2 -1
- package/src/sync/delta.ts +11 -1
- package/src/transport/http.ts +18 -1
- package/src/transport/websocket.ts +220 -96
- package/test/agents.test.ts +104 -0
- package/test/crdt.test.ts +0 -28
- package/test/integration.test.ts +0 -7
- package/test/offline-queue.test.ts +0 -8
- 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();
|
package/test/integration.test.ts
CHANGED
|
@@ -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);
|