swarm-mail 0.1.0 → 0.1.3

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 (75) hide show
  1. package/README.md +28 -0
  2. package/dist/adapter.d.ts +36 -0
  3. package/dist/adapter.d.ts.map +1 -0
  4. package/dist/index.d.ts +22 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +16710 -0
  7. package/{src/pglite.ts → dist/pglite.d.ts} +7 -93
  8. package/dist/pglite.d.ts.map +1 -0
  9. package/dist/streams/agent-mail.d.ts +139 -0
  10. package/dist/streams/agent-mail.d.ts.map +1 -0
  11. package/dist/streams/debug.d.ts +173 -0
  12. package/dist/streams/debug.d.ts.map +1 -0
  13. package/dist/streams/effect/ask.d.ts +124 -0
  14. package/dist/streams/effect/ask.d.ts.map +1 -0
  15. package/dist/streams/effect/cursor.d.ts +87 -0
  16. package/dist/streams/effect/cursor.d.ts.map +1 -0
  17. package/dist/streams/effect/deferred.d.ts +108 -0
  18. package/dist/streams/effect/deferred.d.ts.map +1 -0
  19. package/{src/streams/effect/index.ts → dist/streams/effect/index.d.ts} +1 -0
  20. package/dist/streams/effect/index.d.ts.map +1 -0
  21. package/{src/streams/effect/layers.ts → dist/streams/effect/layers.d.ts} +8 -33
  22. package/dist/streams/effect/layers.d.ts.map +1 -0
  23. package/dist/streams/effect/lock.d.ts +137 -0
  24. package/dist/streams/effect/lock.d.ts.map +1 -0
  25. package/dist/streams/effect/mailbox.d.ts +98 -0
  26. package/dist/streams/effect/mailbox.d.ts.map +1 -0
  27. package/dist/streams/events.d.ts +487 -0
  28. package/dist/streams/events.d.ts.map +1 -0
  29. package/dist/streams/index.d.ts +106 -0
  30. package/dist/streams/index.d.ts.map +1 -0
  31. package/dist/streams/migrations.d.ts +102 -0
  32. package/dist/streams/migrations.d.ts.map +1 -0
  33. package/dist/streams/projections.d.ts +173 -0
  34. package/dist/streams/projections.d.ts.map +1 -0
  35. package/dist/streams/store.d.ts +171 -0
  36. package/dist/streams/store.d.ts.map +1 -0
  37. package/dist/streams/swarm-mail.d.ts +153 -0
  38. package/dist/streams/swarm-mail.d.ts.map +1 -0
  39. package/dist/types/adapter.d.ts +267 -0
  40. package/dist/types/adapter.d.ts.map +1 -0
  41. package/dist/types/database.d.ts +117 -0
  42. package/dist/types/database.d.ts.map +1 -0
  43. package/{src/types/index.ts → dist/types/index.d.ts} +2 -15
  44. package/dist/types/index.d.ts.map +1 -0
  45. package/package.json +21 -5
  46. package/src/adapter.ts +0 -306
  47. package/src/index.ts +0 -57
  48. package/src/streams/agent-mail.test.ts +0 -777
  49. package/src/streams/agent-mail.ts +0 -535
  50. package/src/streams/debug.test.ts +0 -500
  51. package/src/streams/debug.ts +0 -727
  52. package/src/streams/effect/ask.integration.test.ts +0 -314
  53. package/src/streams/effect/ask.ts +0 -202
  54. package/src/streams/effect/cursor.integration.test.ts +0 -418
  55. package/src/streams/effect/cursor.ts +0 -288
  56. package/src/streams/effect/deferred.test.ts +0 -357
  57. package/src/streams/effect/deferred.ts +0 -445
  58. package/src/streams/effect/lock.test.ts +0 -385
  59. package/src/streams/effect/lock.ts +0 -399
  60. package/src/streams/effect/mailbox.test.ts +0 -260
  61. package/src/streams/effect/mailbox.ts +0 -318
  62. package/src/streams/events.test.ts +0 -924
  63. package/src/streams/events.ts +0 -329
  64. package/src/streams/index.test.ts +0 -229
  65. package/src/streams/index.ts +0 -578
  66. package/src/streams/migrations.test.ts +0 -359
  67. package/src/streams/migrations.ts +0 -362
  68. package/src/streams/projections.test.ts +0 -611
  69. package/src/streams/projections.ts +0 -564
  70. package/src/streams/store.integration.test.ts +0 -658
  71. package/src/streams/store.ts +0 -1129
  72. package/src/streams/swarm-mail.ts +0 -552
  73. package/src/types/adapter.ts +0 -392
  74. package/src/types/database.ts +0 -127
  75. package/tsconfig.json +0 -22
@@ -1,314 +0,0 @@
1
- /**
2
- * Ask Pattern Integration Tests
3
- *
4
- * Tests request/response communication between agents using
5
- * DurableMailbox + DurableDeferred pattern.
6
- */
7
-
8
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
9
- import { Effect } from "effect";
10
- import { ask, respond, askWithMailbox } from "./ask";
11
- import { DurableMailbox } from "./mailbox";
12
- import { DurableDeferred } from "./deferred";
13
- import { DurableAskLive } from "./layers";
14
- import { resetDatabase, closeDatabase } from "../index";
15
- import { randomUUID } from "node:crypto";
16
-
17
- // ============================================================================
18
- // Test Fixtures
19
- // ============================================================================
20
-
21
- interface TestRequest {
22
- action: string;
23
- userId: number;
24
- }
25
-
26
- interface TestResponse {
27
- status: "success" | "error";
28
- data?: unknown;
29
- message?: string;
30
- }
31
-
32
- // ============================================================================
33
- // Setup/Teardown
34
- // ============================================================================
35
-
36
- let testDbPath: string;
37
-
38
- beforeEach(async () => {
39
- testDbPath = `/tmp/ask-test-${randomUUID()}`;
40
- await resetDatabase(testDbPath);
41
- });
42
-
43
- afterEach(async () => {
44
- await closeDatabase(testDbPath);
45
- });
46
-
47
- // ============================================================================
48
- // Tests
49
- // ============================================================================
50
-
51
- describe("Ask Pattern", () => {
52
- it("should send request and receive response via ask()", async () => {
53
- const program = Effect.gen(function* () {
54
- const mailboxService = yield* DurableMailbox;
55
-
56
- // Create mailboxes for both agents
57
- const agentA = yield* mailboxService.create({
58
- agent: "agent-a",
59
- projectKey: "test-proj",
60
- projectPath: testDbPath,
61
- });
62
-
63
- const agentB = yield* mailboxService.create({
64
- agent: "agent-b",
65
- projectKey: "test-proj",
66
- projectPath: testDbPath,
67
- });
68
-
69
- // Agent B: Listen for request and respond
70
- const responder = Effect.promise(async () => {
71
- for await (const envelope of agentB.receive<TestRequest>()) {
72
- expect(envelope.payload.action).toBe("getUserData");
73
- expect(envelope.payload.userId).toBe(123);
74
- expect(envelope.replyTo).toBeDefined();
75
-
76
- // Send response
77
- await Effect.runPromise(
78
- respond<TestResponse>(
79
- envelope,
80
- {
81
- status: "success",
82
- data: { username: "testuser", id: 123 },
83
- },
84
- testDbPath,
85
- ).pipe(Effect.provide(DurableAskLive)),
86
- );
87
-
88
- await Effect.runPromise(envelope.commit());
89
- break; // Exit after first message
90
- }
91
- });
92
-
93
- // Start responder in background
94
- Effect.runFork(responder.pipe(Effect.provide(DurableAskLive)));
95
-
96
- // Agent A: Send request via ask()
97
- const response = yield* ask<TestRequest, TestResponse>({
98
- mailbox: agentA,
99
- to: "agent-b",
100
- payload: { action: "getUserData", userId: 123 },
101
- ttlSeconds: 5,
102
- });
103
-
104
- expect(response.status).toBe("success");
105
- expect(response.data).toEqual({ username: "testuser", id: 123 });
106
-
107
- return response;
108
- }).pipe(Effect.provide(DurableAskLive));
109
-
110
- const result = await Effect.runPromise(program);
111
- expect(result.status).toBe("success");
112
- });
113
-
114
- it("should timeout when no response received", async () => {
115
- const program = Effect.gen(function* () {
116
- const mailboxService = yield* DurableMailbox;
117
-
118
- const agentA = yield* mailboxService.create({
119
- agent: "agent-a",
120
- projectKey: "test-proj",
121
- projectPath: testDbPath,
122
- });
123
-
124
- // No one listening, should timeout after 1 second
125
- const response = yield* ask<TestRequest, TestResponse>({
126
- mailbox: agentA,
127
- to: "agent-b-nonexistent",
128
- payload: { action: "getUserData", userId: 123 },
129
- ttlSeconds: 1,
130
- });
131
-
132
- return response;
133
- }).pipe(Effect.provide(DurableAskLive));
134
-
135
- // Expect timeout error
136
- const result = await Effect.runPromise(Effect.either(program));
137
- expect(result._tag).toBe("Left");
138
- if (result._tag === "Left") {
139
- expect(result.left).toHaveProperty("_tag", "TimeoutError");
140
- }
141
- });
142
-
143
- it("should support askWithMailbox for one-off requests", async () => {
144
- const program = Effect.gen(function* () {
145
- const mailboxService = yield* DurableMailbox;
146
-
147
- // Agent B: Listen for request
148
- const agentB = yield* mailboxService.create({
149
- agent: "agent-b",
150
- projectKey: "test-proj",
151
- projectPath: testDbPath,
152
- });
153
-
154
- const responder = Effect.promise(async () => {
155
- for await (const envelope of agentB.receive<TestRequest>()) {
156
- await Effect.runPromise(
157
- respond<TestResponse>(
158
- envelope,
159
- { status: "success", message: "One-off response" },
160
- testDbPath,
161
- ).pipe(Effect.provide(DurableAskLive)),
162
- );
163
- await Effect.runPromise(envelope.commit());
164
- break;
165
- }
166
- });
167
-
168
- Effect.runFork(responder.pipe(Effect.provide(DurableAskLive)));
169
-
170
- // Use askWithMailbox (creates mailbox automatically)
171
- const response = yield* askWithMailbox<TestRequest, TestResponse>({
172
- agent: "agent-a",
173
- projectKey: "test-proj",
174
- to: "agent-b",
175
- payload: { action: "ping", userId: 0 },
176
- ttlSeconds: 5,
177
- projectPath: testDbPath,
178
- });
179
-
180
- expect(response.status).toBe("success");
181
- expect(response.message).toBe("One-off response");
182
-
183
- return response;
184
- }).pipe(Effect.provide(DurableAskLive));
185
-
186
- const result = await Effect.runPromise(program);
187
- expect(result.status).toBe("success");
188
- });
189
-
190
- it("should handle multiple concurrent asks", async () => {
191
- const program = Effect.gen(function* () {
192
- const mailboxService = yield* DurableMailbox;
193
-
194
- const agentA = yield* mailboxService.create({
195
- agent: "agent-a",
196
- projectKey: "test-proj",
197
- projectPath: testDbPath,
198
- });
199
-
200
- const agentB = yield* mailboxService.create({
201
- agent: "agent-b",
202
- projectKey: "test-proj",
203
- projectPath: testDbPath,
204
- });
205
-
206
- // Agent B: Respond to all requests (limit to 3)
207
- const responder = Effect.promise(async () => {
208
- let count = 0;
209
- for await (const envelope of agentB.receive<TestRequest>()) {
210
- await Effect.runPromise(
211
- respond<TestResponse>(
212
- envelope,
213
- {
214
- status: "success",
215
- data: { userId: envelope.payload.userId },
216
- },
217
- testDbPath,
218
- ).pipe(Effect.provide(DurableAskLive)),
219
- );
220
- await Effect.runPromise(envelope.commit());
221
- count++;
222
- if (count >= 3) break; // Exit after 3 responses
223
- }
224
- });
225
-
226
- Effect.runFork(responder.pipe(Effect.provide(DurableAskLive)));
227
-
228
- // Send 3 concurrent requests
229
- const requests = [
230
- ask<TestRequest, TestResponse>({
231
- mailbox: agentA,
232
- to: "agent-b",
233
- payload: { action: "getData", userId: 1 },
234
- ttlSeconds: 5,
235
- }),
236
- ask<TestRequest, TestResponse>({
237
- mailbox: agentA,
238
- to: "agent-b",
239
- payload: { action: "getData", userId: 2 },
240
- ttlSeconds: 5,
241
- }),
242
- ask<TestRequest, TestResponse>({
243
- mailbox: agentA,
244
- to: "agent-b",
245
- payload: { action: "getData", userId: 3 },
246
- ttlSeconds: 5,
247
- }),
248
- ];
249
-
250
- const responses = yield* Effect.all(requests, { concurrency: 3 });
251
-
252
- expect(responses).toHaveLength(3);
253
- // Sort by userId to avoid flaky ordering - concurrent responses may arrive in any order
254
- const sortedData = responses
255
- .map((r) => r.data)
256
- .sort((a: any, b: any) => a.userId - b.userId);
257
- expect(sortedData).toEqual([{ userId: 1 }, { userId: 2 }, { userId: 3 }]);
258
-
259
- return responses;
260
- }).pipe(Effect.provide(DurableAskLive));
261
-
262
- const results = await Effect.runPromise(program);
263
- expect(results).toHaveLength(3);
264
- });
265
-
266
- it("should support thread IDs for conversation tracking", async () => {
267
- const program = Effect.gen(function* () {
268
- const mailboxService = yield* DurableMailbox;
269
-
270
- const agentA = yield* mailboxService.create({
271
- agent: "agent-a",
272
- projectKey: "test-proj",
273
- projectPath: testDbPath,
274
- });
275
-
276
- const agentB = yield* mailboxService.create({
277
- agent: "agent-b",
278
- projectKey: "test-proj",
279
- projectPath: testDbPath,
280
- });
281
-
282
- const responder = Effect.promise(async () => {
283
- for await (const envelope of agentB.receive<TestRequest>()) {
284
- expect(envelope.threadId).toBe("conversation-123");
285
- await Effect.runPromise(
286
- respond<TestResponse>(
287
- envelope,
288
- { status: "success" },
289
- testDbPath,
290
- ).pipe(Effect.provide(DurableAskLive)),
291
- );
292
- await Effect.runPromise(envelope.commit());
293
- break;
294
- }
295
- });
296
-
297
- Effect.runFork(responder.pipe(Effect.provide(DurableAskLive)));
298
-
299
- const response = yield* ask<TestRequest, TestResponse>({
300
- mailbox: agentA,
301
- to: "agent-b",
302
- payload: { action: "test", userId: 0 },
303
- threadId: "conversation-123",
304
- ttlSeconds: 5,
305
- });
306
-
307
- expect(response.status).toBe("success");
308
-
309
- return response;
310
- }).pipe(Effect.provide(DurableAskLive));
311
-
312
- await Effect.runPromise(program);
313
- });
314
- });
@@ -1,202 +0,0 @@
1
- /**
2
- * Ask Pattern - Request/Response over Durable Streams
3
- *
4
- * Combines DurableMailbox (message passing) + DurableDeferred (distributed promise)
5
- * for synchronous-style request/response communication between agents.
6
- *
7
- * Based on Kyle Matthews' pattern from Agent Mail.
8
- *
9
- * @example
10
- * ```typescript
11
- * // Agent A asks Agent B for data
12
- * const result = yield* ask<Request, Response>({
13
- * mailbox: myMailbox,
14
- * to: "agent-b",
15
- * payload: { query: "getUserData", userId: 123 },
16
- * ttlSeconds: 30,
17
- * });
18
- *
19
- * // Agent B receives request and responds
20
- * for await (const envelope of mailbox.receive()) {
21
- * const response = processRequest(envelope.payload);
22
- * if (envelope.replyTo) {
23
- * yield* DurableDeferred.resolve(envelope.replyTo, response);
24
- * }
25
- * yield* envelope.commit();
26
- * }
27
- * ```
28
- */
29
-
30
- import { Effect } from "effect";
31
- import { DurableCursor } from "./cursor";
32
- import { DurableMailbox, type Mailbox } from "./mailbox";
33
- import { DurableDeferred, type DeferredConfig } from "./deferred";
34
- import type { TimeoutError, NotFoundError } from "./deferred";
35
-
36
- // ============================================================================
37
- // Types
38
- // ============================================================================
39
-
40
- /**
41
- * Configuration for ask() request
42
- */
43
- export interface AskConfig<Req> {
44
- /** Mailbox to send message from */
45
- readonly mailbox: Mailbox;
46
- /** Recipient agent(s) */
47
- readonly to: string | string[];
48
- /** Request payload */
49
- readonly payload: Req;
50
- /** Time-to-live in seconds before timeout (default: 60) */
51
- readonly ttlSeconds?: number;
52
- /** Optional thread ID for conversation tracking */
53
- readonly threadId?: string;
54
- /** Optional importance level */
55
- readonly importance?: "low" | "normal" | "high" | "urgent";
56
- /** Optional project path for database isolation */
57
- readonly projectPath?: string;
58
- }
59
-
60
- // ============================================================================
61
- // Ask Implementation
62
- // ============================================================================
63
-
64
- /**
65
- * Request/response pattern combining mailbox send + deferred await
66
- *
67
- * Creates a deferred promise, sends message with replyTo URL, then blocks
68
- * until the recipient resolves the deferred (or timeout).
69
- *
70
- * @template Req - Request payload type
71
- * @template Res - Response type
72
- * @param config - Ask configuration
73
- * @returns Effect that resolves with response or fails with timeout/not-found
74
- *
75
- * @example
76
- * ```typescript
77
- * const program = Effect.gen(function* () {
78
- * const mailbox = yield* DurableMailbox;
79
- * const myMailbox = yield* mailbox.create({ agent: "worker-1", projectKey: "proj" });
80
- *
81
- * const response = yield* ask<Request, Response>({
82
- * mailbox: myMailbox,
83
- * to: "worker-2",
84
- * payload: { task: "getData" },
85
- * ttlSeconds: 30,
86
- * });
87
- *
88
- * console.log("Got response:", response);
89
- * });
90
- * ```
91
- */
92
- export function ask<Req, Res>(
93
- config: AskConfig<Req>,
94
- ): Effect.Effect<Res, TimeoutError | NotFoundError, DurableDeferred> {
95
- return Effect.gen(function* () {
96
- const deferred = yield* DurableDeferred;
97
-
98
- // Create deferred for response
99
- const deferredConfig: DeferredConfig = {
100
- ttlSeconds: config.ttlSeconds ?? 60,
101
- projectPath: config.projectPath,
102
- };
103
- const responseHandle = yield* deferred.create<Res>(deferredConfig);
104
-
105
- // Send message with replyTo URL
106
- yield* config.mailbox.send(config.to, {
107
- payload: config.payload,
108
- replyTo: responseHandle.url,
109
- threadId: config.threadId,
110
- importance: config.importance,
111
- });
112
-
113
- // Block until response or timeout
114
- const response = yield* responseHandle.value;
115
-
116
- return response;
117
- });
118
- }
119
-
120
- // ============================================================================
121
- // Convenience Variants
122
- // ============================================================================
123
-
124
- /**
125
- * Ask pattern with automatic mailbox creation
126
- *
127
- * Simpler variant when you don't need to reuse the mailbox.
128
- *
129
- * @example
130
- * ```typescript
131
- * const response = yield* askWithMailbox({
132
- * agent: "worker-1",
133
- * projectKey: "proj",
134
- * to: "worker-2",
135
- * payload: { task: "getData" },
136
- * });
137
- * ```
138
- */
139
- export function askWithMailbox<Req, Res>(config: {
140
- readonly agent: string;
141
- readonly projectKey: string;
142
- readonly to: string | string[];
143
- readonly payload: Req;
144
- readonly ttlSeconds?: number;
145
- readonly threadId?: string;
146
- readonly importance?: "low" | "normal" | "high" | "urgent";
147
- readonly projectPath?: string;
148
- }): Effect.Effect<
149
- Res,
150
- TimeoutError | NotFoundError,
151
- DurableDeferred | DurableMailbox | DurableCursor
152
- > {
153
- return Effect.gen(function* () {
154
- const mailboxService = yield* DurableMailbox;
155
-
156
- const mailbox = yield* mailboxService.create({
157
- agent: config.agent,
158
- projectKey: config.projectKey,
159
- projectPath: config.projectPath,
160
- });
161
-
162
- return yield* ask<Req, Res>({
163
- mailbox,
164
- to: config.to,
165
- payload: config.payload,
166
- ttlSeconds: config.ttlSeconds,
167
- threadId: config.threadId,
168
- importance: config.importance,
169
- projectPath: config.projectPath,
170
- });
171
- });
172
- }
173
-
174
- /**
175
- * Respond to a message envelope by resolving its replyTo deferred
176
- *
177
- * Helper for the receiver side of ask() pattern.
178
- *
179
- * @example
180
- * ```typescript
181
- * for await (const envelope of mailbox.receive()) {
182
- * const response = processRequest(envelope.payload);
183
- * yield* respond(envelope, response);
184
- * yield* envelope.commit();
185
- * }
186
- * ```
187
- */
188
- export function respond<T>(
189
- envelope: { readonly replyTo?: string },
190
- value: T,
191
- projectPath?: string,
192
- ): Effect.Effect<void, NotFoundError, DurableDeferred> {
193
- return Effect.gen(function* () {
194
- if (!envelope.replyTo) {
195
- // No replyTo - this wasn't an ask() request, just return
196
- return;
197
- }
198
-
199
- const deferred = yield* DurableDeferred;
200
- yield* deferred.resolve(envelope.replyTo, value, projectPath);
201
- });
202
- }