swarm-mail 0.1.0 → 0.1.2
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/README.md +28 -0
- package/dist/adapter.d.ts +36 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23131 -0
- package/{src/pglite.ts → dist/pglite.d.ts} +7 -93
- package/dist/pglite.d.ts.map +1 -0
- package/dist/streams/agent-mail.d.ts +139 -0
- package/dist/streams/agent-mail.d.ts.map +1 -0
- package/dist/streams/debug.d.ts +173 -0
- package/dist/streams/debug.d.ts.map +1 -0
- package/dist/streams/effect/ask.d.ts +124 -0
- package/dist/streams/effect/ask.d.ts.map +1 -0
- package/dist/streams/effect/cursor.d.ts +87 -0
- package/dist/streams/effect/cursor.d.ts.map +1 -0
- package/dist/streams/effect/deferred.d.ts +108 -0
- package/dist/streams/effect/deferred.d.ts.map +1 -0
- package/{src/streams/effect/index.ts → dist/streams/effect/index.d.ts} +1 -0
- package/dist/streams/effect/index.d.ts.map +1 -0
- package/{src/streams/effect/layers.ts → dist/streams/effect/layers.d.ts} +8 -33
- package/dist/streams/effect/layers.d.ts.map +1 -0
- package/dist/streams/effect/lock.d.ts +137 -0
- package/dist/streams/effect/lock.d.ts.map +1 -0
- package/dist/streams/effect/mailbox.d.ts +98 -0
- package/dist/streams/effect/mailbox.d.ts.map +1 -0
- package/dist/streams/events.d.ts +487 -0
- package/dist/streams/events.d.ts.map +1 -0
- package/dist/streams/index.d.ts +106 -0
- package/dist/streams/index.d.ts.map +1 -0
- package/dist/streams/migrations.d.ts +102 -0
- package/dist/streams/migrations.d.ts.map +1 -0
- package/dist/streams/projections.d.ts +173 -0
- package/dist/streams/projections.d.ts.map +1 -0
- package/dist/streams/store.d.ts +171 -0
- package/dist/streams/store.d.ts.map +1 -0
- package/dist/streams/swarm-mail.d.ts +153 -0
- package/dist/streams/swarm-mail.d.ts.map +1 -0
- package/dist/types/adapter.d.ts +267 -0
- package/dist/types/adapter.d.ts.map +1 -0
- package/dist/types/database.d.ts +117 -0
- package/dist/types/database.d.ts.map +1 -0
- package/{src/types/index.ts → dist/types/index.d.ts} +2 -15
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +20 -4
- package/src/adapter.ts +0 -306
- package/src/index.ts +0 -57
- package/src/streams/agent-mail.test.ts +0 -777
- package/src/streams/agent-mail.ts +0 -535
- package/src/streams/debug.test.ts +0 -500
- package/src/streams/debug.ts +0 -727
- package/src/streams/effect/ask.integration.test.ts +0 -314
- package/src/streams/effect/ask.ts +0 -202
- package/src/streams/effect/cursor.integration.test.ts +0 -418
- package/src/streams/effect/cursor.ts +0 -288
- package/src/streams/effect/deferred.test.ts +0 -357
- package/src/streams/effect/deferred.ts +0 -445
- package/src/streams/effect/lock.test.ts +0 -385
- package/src/streams/effect/lock.ts +0 -399
- package/src/streams/effect/mailbox.test.ts +0 -260
- package/src/streams/effect/mailbox.ts +0 -318
- package/src/streams/events.test.ts +0 -924
- package/src/streams/events.ts +0 -329
- package/src/streams/index.test.ts +0 -229
- package/src/streams/index.ts +0 -578
- package/src/streams/migrations.test.ts +0 -359
- package/src/streams/migrations.ts +0 -362
- package/src/streams/projections.test.ts +0 -611
- package/src/streams/projections.ts +0 -564
- package/src/streams/store.integration.test.ts +0 -658
- package/src/streams/store.ts +0 -1129
- package/src/streams/swarm-mail.ts +0 -552
- package/src/types/adapter.ts +0 -392
- package/src/types/database.ts +0 -127
- package/tsconfig.json +0 -22
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for DurableDeferred service
|
|
3
|
-
*
|
|
4
|
-
* Verifies:
|
|
5
|
-
* - Create deferred with unique URL
|
|
6
|
-
* - Resolve deferred from another context
|
|
7
|
-
* - Reject deferred with error
|
|
8
|
-
* - Timeout when not resolved in time
|
|
9
|
-
* - Concurrent access patterns
|
|
10
|
-
* - Cleanup of expired entries
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
14
|
-
import { mkdir, rm } from "node:fs/promises";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
import { tmpdir } from "node:os";
|
|
17
|
-
import { Effect } from "effect";
|
|
18
|
-
import { closeDatabase } from "../index";
|
|
19
|
-
import {
|
|
20
|
-
TimeoutError,
|
|
21
|
-
NotFoundError,
|
|
22
|
-
createDeferred,
|
|
23
|
-
resolveDeferred,
|
|
24
|
-
rejectDeferred,
|
|
25
|
-
cleanupDeferreds,
|
|
26
|
-
DurableDeferredLive,
|
|
27
|
-
} from "./deferred";
|
|
28
|
-
|
|
29
|
-
let TEST_PROJECT_PATH: string;
|
|
30
|
-
|
|
31
|
-
describe("DurableDeferred", () => {
|
|
32
|
-
beforeEach(async () => {
|
|
33
|
-
TEST_PROJECT_PATH = join(
|
|
34
|
-
tmpdir(),
|
|
35
|
-
`deferred-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
36
|
-
);
|
|
37
|
-
await mkdir(TEST_PROJECT_PATH, { recursive: true });
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
afterEach(async () => {
|
|
41
|
-
try {
|
|
42
|
-
await closeDatabase(TEST_PROJECT_PATH);
|
|
43
|
-
// Small delay to let PGLite fully release file handles
|
|
44
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
45
|
-
await rm(join(TEST_PROJECT_PATH, ".opencode"), {
|
|
46
|
-
recursive: true,
|
|
47
|
-
force: true,
|
|
48
|
-
});
|
|
49
|
-
} catch {
|
|
50
|
-
// Ignore cleanup errors
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
describe("create", () => {
|
|
55
|
-
it("creates a deferred with unique URL", async () => {
|
|
56
|
-
const program = Effect.gen(function* (_) {
|
|
57
|
-
const handle = yield* _(
|
|
58
|
-
createDeferred<string>({
|
|
59
|
-
ttlSeconds: 60,
|
|
60
|
-
projectPath: TEST_PROJECT_PATH,
|
|
61
|
-
}),
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
expect(handle.url).toMatch(/^deferred:/);
|
|
65
|
-
expect(handle.value).toBeDefined();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
await Effect.runPromise(
|
|
69
|
-
program.pipe(Effect.provide(DurableDeferredLive)),
|
|
70
|
-
);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("creates multiple deferreds with different URLs", async () => {
|
|
74
|
-
const program = Effect.gen(function* (_) {
|
|
75
|
-
const handle1 = yield* _(
|
|
76
|
-
createDeferred<string>({
|
|
77
|
-
ttlSeconds: 60,
|
|
78
|
-
projectPath: TEST_PROJECT_PATH,
|
|
79
|
-
}),
|
|
80
|
-
);
|
|
81
|
-
const handle2 = yield* _(
|
|
82
|
-
createDeferred<string>({
|
|
83
|
-
ttlSeconds: 60,
|
|
84
|
-
projectPath: TEST_PROJECT_PATH,
|
|
85
|
-
}),
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
expect(handle1.url).not.toBe(handle2.url);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
await Effect.runPromise(
|
|
92
|
-
program.pipe(Effect.provide(DurableDeferredLive)),
|
|
93
|
-
);
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
describe("resolve", () => {
|
|
98
|
-
it("resolves a deferred and returns value", async () => {
|
|
99
|
-
const program = Effect.gen(function* (_) {
|
|
100
|
-
const handle = yield* _(
|
|
101
|
-
createDeferred<{ message: string }>({
|
|
102
|
-
ttlSeconds: 60,
|
|
103
|
-
projectPath: TEST_PROJECT_PATH,
|
|
104
|
-
}),
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
// Resolve in background
|
|
108
|
-
Effect.runFork(
|
|
109
|
-
Effect.gen(function* (_) {
|
|
110
|
-
yield* _(Effect.sleep("100 millis"));
|
|
111
|
-
yield* _(
|
|
112
|
-
resolveDeferred(
|
|
113
|
-
handle.url,
|
|
114
|
-
{ message: "resolved!" },
|
|
115
|
-
TEST_PROJECT_PATH,
|
|
116
|
-
),
|
|
117
|
-
);
|
|
118
|
-
}).pipe(Effect.provide(DurableDeferredLive)),
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
// Await resolution
|
|
122
|
-
const result = yield* _(handle.value);
|
|
123
|
-
expect(result).toEqual({ message: "resolved!" });
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
await Effect.runPromise(
|
|
127
|
-
program.pipe(Effect.provide(DurableDeferredLive)),
|
|
128
|
-
);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("fails with NotFoundError for non-existent URL", async () => {
|
|
132
|
-
const program = Effect.gen(function* (_) {
|
|
133
|
-
yield* _(
|
|
134
|
-
resolveDeferred(
|
|
135
|
-
"deferred:nonexistent",
|
|
136
|
-
{ value: 42 },
|
|
137
|
-
TEST_PROJECT_PATH,
|
|
138
|
-
),
|
|
139
|
-
);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const result = await Effect.runPromise(
|
|
143
|
-
program.pipe(
|
|
144
|
-
Effect.provide(DurableDeferredLive),
|
|
145
|
-
Effect.flip, // Flip to get the error
|
|
146
|
-
),
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
expect(result).toBeInstanceOf(NotFoundError);
|
|
150
|
-
expect((result as NotFoundError).url).toBe("deferred:nonexistent");
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe("reject", () => {
|
|
155
|
-
it("rejects a deferred with error", async () => {
|
|
156
|
-
const program = Effect.gen(function* (_) {
|
|
157
|
-
const handle = yield* _(
|
|
158
|
-
createDeferred<string>({
|
|
159
|
-
ttlSeconds: 60,
|
|
160
|
-
projectPath: TEST_PROJECT_PATH,
|
|
161
|
-
}),
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
// Reject in background
|
|
165
|
-
Effect.runFork(
|
|
166
|
-
Effect.gen(function* (_) {
|
|
167
|
-
yield* _(Effect.sleep("100 millis"));
|
|
168
|
-
yield* _(
|
|
169
|
-
rejectDeferred(
|
|
170
|
-
handle.url,
|
|
171
|
-
new Error("Something went wrong"),
|
|
172
|
-
TEST_PROJECT_PATH,
|
|
173
|
-
),
|
|
174
|
-
);
|
|
175
|
-
}).pipe(Effect.provide(DurableDeferredLive)),
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
// Await should fail
|
|
179
|
-
yield* _(handle.value);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
const result = await Effect.runPromise(
|
|
183
|
-
program.pipe(
|
|
184
|
-
Effect.provide(DurableDeferredLive),
|
|
185
|
-
Effect.flip, // Flip to get the error
|
|
186
|
-
),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
// Will be a NotFoundError since we map all errors to NotFoundError in awaitImpl
|
|
190
|
-
expect(result).toBeInstanceOf(NotFoundError);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("fails with NotFoundError for non-existent URL", async () => {
|
|
194
|
-
const program = Effect.gen(function* (_) {
|
|
195
|
-
yield* _(
|
|
196
|
-
rejectDeferred(
|
|
197
|
-
"deferred:nonexistent",
|
|
198
|
-
new Error("test"),
|
|
199
|
-
TEST_PROJECT_PATH,
|
|
200
|
-
),
|
|
201
|
-
);
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const result = await Effect.runPromise(
|
|
205
|
-
program.pipe(Effect.provide(DurableDeferredLive), Effect.flip),
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
expect(result).toBeInstanceOf(NotFoundError);
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
describe("timeout", () => {
|
|
213
|
-
it("times out when not resolved within TTL", async () => {
|
|
214
|
-
const program = Effect.gen(function* (_) {
|
|
215
|
-
const handle = yield* _(
|
|
216
|
-
createDeferred<string>({
|
|
217
|
-
ttlSeconds: 1, // 1 second timeout
|
|
218
|
-
projectPath: TEST_PROJECT_PATH,
|
|
219
|
-
}),
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
// Don't resolve, just wait for timeout
|
|
223
|
-
yield* _(handle.value);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
const result = await Effect.runPromise(
|
|
227
|
-
program.pipe(Effect.provide(DurableDeferredLive), Effect.flip),
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
expect(result).toBeInstanceOf(TimeoutError);
|
|
231
|
-
expect((result as TimeoutError).ttlSeconds).toBe(1);
|
|
232
|
-
}, 10000); // 10s test timeout
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
describe("concurrent access", () => {
|
|
236
|
-
it("handles multiple resolvers racing", async () => {
|
|
237
|
-
const program = Effect.gen(function* (_) {
|
|
238
|
-
const handle = yield* _(
|
|
239
|
-
createDeferred<number>({
|
|
240
|
-
ttlSeconds: 60,
|
|
241
|
-
projectPath: TEST_PROJECT_PATH,
|
|
242
|
-
}),
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
// Spawn multiple resolvers (first one wins)
|
|
246
|
-
Effect.runFork(
|
|
247
|
-
Effect.gen(function* (_) {
|
|
248
|
-
yield* _(Effect.sleep("50 millis"));
|
|
249
|
-
yield* _(resolveDeferred(handle.url, 1, TEST_PROJECT_PATH));
|
|
250
|
-
}).pipe(Effect.provide(DurableDeferredLive)),
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
Effect.runFork(
|
|
254
|
-
Effect.gen(function* (_) {
|
|
255
|
-
yield* _(Effect.sleep("100 millis"));
|
|
256
|
-
yield* _(resolveDeferred(handle.url, 2, TEST_PROJECT_PATH));
|
|
257
|
-
}).pipe(Effect.provide(DurableDeferredLive)),
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
const result = yield* _(handle.value);
|
|
261
|
-
expect(result).toBe(1); // First resolver wins
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
await Effect.runPromise(
|
|
265
|
-
program.pipe(Effect.provide(DurableDeferredLive)),
|
|
266
|
-
);
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it("handles sequential waiters on same deferred", async () => {
|
|
270
|
-
const program = Effect.gen(function* (_) {
|
|
271
|
-
const handle = yield* _(
|
|
272
|
-
createDeferred<string>({
|
|
273
|
-
ttlSeconds: 60,
|
|
274
|
-
projectPath: TEST_PROJECT_PATH,
|
|
275
|
-
}),
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
// Resolve immediately
|
|
279
|
-
yield* _(resolveDeferred(handle.url, "resolved", TEST_PROJECT_PATH));
|
|
280
|
-
|
|
281
|
-
// Wait for value
|
|
282
|
-
const result = yield* _(handle.value);
|
|
283
|
-
expect(result).toBe("resolved");
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
await Effect.runPromise(
|
|
287
|
-
program.pipe(Effect.provide(DurableDeferredLive)),
|
|
288
|
-
);
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
describe("cleanup", () => {
|
|
293
|
-
it("cleans up expired entries", async () => {
|
|
294
|
-
const program = Effect.gen(function* (_) {
|
|
295
|
-
// Create deferred with 1s TTL
|
|
296
|
-
const handle = yield* _(
|
|
297
|
-
createDeferred<string>({
|
|
298
|
-
ttlSeconds: 1,
|
|
299
|
-
projectPath: TEST_PROJECT_PATH,
|
|
300
|
-
}),
|
|
301
|
-
);
|
|
302
|
-
|
|
303
|
-
// Wait for expiry
|
|
304
|
-
yield* _(Effect.sleep("1500 millis"));
|
|
305
|
-
|
|
306
|
-
// Cleanup
|
|
307
|
-
const count = yield* _(cleanupDeferreds(TEST_PROJECT_PATH));
|
|
308
|
-
expect(count).toBeGreaterThanOrEqual(0);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
await Effect.runPromise(
|
|
312
|
-
program.pipe(Effect.provide(DurableDeferredLive)),
|
|
313
|
-
);
|
|
314
|
-
});
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
describe("type safety", () => {
|
|
318
|
-
it("preserves types through resolution", async () => {
|
|
319
|
-
interface TestData {
|
|
320
|
-
id: number;
|
|
321
|
-
name: string;
|
|
322
|
-
tags: string[];
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
const program = Effect.gen(function* (_) {
|
|
326
|
-
const handle = yield* _(
|
|
327
|
-
createDeferred<TestData>({
|
|
328
|
-
ttlSeconds: 60,
|
|
329
|
-
projectPath: TEST_PROJECT_PATH,
|
|
330
|
-
}),
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
Effect.runFork(
|
|
334
|
-
Effect.gen(function* (_) {
|
|
335
|
-
yield* _(Effect.sleep("100 millis"));
|
|
336
|
-
yield* _(
|
|
337
|
-
resolveDeferred(
|
|
338
|
-
handle.url,
|
|
339
|
-
{ id: 1, name: "test", tags: ["a", "b"] },
|
|
340
|
-
TEST_PROJECT_PATH,
|
|
341
|
-
),
|
|
342
|
-
);
|
|
343
|
-
}).pipe(Effect.provide(DurableDeferredLive)),
|
|
344
|
-
);
|
|
345
|
-
|
|
346
|
-
const result = yield* _(handle.value);
|
|
347
|
-
expect(result.id).toBe(1);
|
|
348
|
-
expect(result.name).toBe("test");
|
|
349
|
-
expect(result.tags).toEqual(["a", "b"]);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
await Effect.runPromise(
|
|
353
|
-
program.pipe(Effect.provide(DurableDeferredLive)),
|
|
354
|
-
);
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
});
|