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,418 +0,0 @@
1
- /**
2
- * DurableCursor Tests
3
- *
4
- * Tests for Effect-TS cursor service with checkpointing
5
- */
6
- import { describe, expect, it, beforeEach, afterEach } from "vitest";
7
- import { Effect } from "effect";
8
- import type { AgentRegisteredEvent } from "../events";
9
- import { DurableCursor, DurableCursorLayer, type CursorConfig } from "./cursor";
10
- import {
11
- appendEvent,
12
- closeDatabase,
13
- createEvent,
14
- resetDatabase,
15
- } from "../index";
16
-
17
- // ============================================================================
18
- // Test Utilities
19
- // ============================================================================
20
-
21
- const TEST_PROJECT = "/tmp/cursor-test";
22
-
23
- beforeEach(async () => {
24
- await resetDatabase(TEST_PROJECT);
25
- });
26
-
27
- afterEach(async () => {
28
- await closeDatabase(TEST_PROJECT);
29
- });
30
-
31
- async function cleanup() {
32
- await closeDatabase(TEST_PROJECT);
33
- }
34
-
35
- /**
36
- * Helper to run Effect programs with DurableCursor service
37
- */
38
- async function runWithCursor<A, E>(
39
- effect: Effect.Effect<A, E, DurableCursor>,
40
- ): Promise<A> {
41
- return Effect.runPromise(Effect.provide(effect, DurableCursorLayer));
42
- }
43
-
44
- // ============================================================================
45
- // Tests
46
- // ============================================================================
47
-
48
- describe("DurableCursor", () => {
49
- describe("create", () => {
50
- it("creates a cursor with initial position 0", async () => {
51
- await cleanup();
52
-
53
- const program = Effect.gen(function* () {
54
- const service = yield* DurableCursor;
55
- const cursor = yield* service.create({
56
- stream: "test-stream",
57
- checkpoint: "test-checkpoint",
58
- projectPath: TEST_PROJECT,
59
- });
60
-
61
- const position = yield* cursor.getPosition();
62
- return position;
63
- });
64
-
65
- const position = await runWithCursor(program);
66
- expect(position).toBe(0);
67
- });
68
-
69
- it("resumes from last checkpoint position", async () => {
70
- await cleanup();
71
-
72
- // First cursor - commit at sequence 5
73
- const program1 = Effect.gen(function* () {
74
- const service = yield* DurableCursor;
75
- const cursor = yield* service.create({
76
- stream: "test-stream",
77
- checkpoint: "test-checkpoint",
78
- projectPath: TEST_PROJECT,
79
- });
80
-
81
- yield* cursor.commit(5);
82
- return yield* cursor.getPosition();
83
- });
84
-
85
- await runWithCursor(program1);
86
-
87
- // Second cursor - should resume at 5
88
- const program2 = Effect.gen(function* () {
89
- const service = yield* DurableCursor;
90
- const cursor = yield* service.create({
91
- stream: "test-stream",
92
- checkpoint: "test-checkpoint",
93
- projectPath: TEST_PROJECT,
94
- });
95
-
96
- return yield* cursor.getPosition();
97
- });
98
-
99
- const position = await runWithCursor(program2);
100
- expect(position).toBe(5);
101
- });
102
-
103
- it("supports multiple independent checkpoints", async () => {
104
- await cleanup();
105
-
106
- const program = Effect.gen(function* () {
107
- const service = yield* DurableCursor;
108
-
109
- const cursor1 = yield* service.create({
110
- stream: "test-stream",
111
- checkpoint: "checkpoint-a",
112
- projectPath: TEST_PROJECT,
113
- });
114
-
115
- const cursor2 = yield* service.create({
116
- stream: "test-stream",
117
- checkpoint: "checkpoint-b",
118
- projectPath: TEST_PROJECT,
119
- });
120
-
121
- yield* cursor1.commit(10);
122
- yield* cursor2.commit(20);
123
-
124
- const pos1 = yield* cursor1.getPosition();
125
- const pos2 = yield* cursor2.getPosition();
126
-
127
- return { pos1, pos2 };
128
- });
129
-
130
- const result = await runWithCursor(program);
131
- expect(result.pos1).toBe(10);
132
- expect(result.pos2).toBe(20);
133
- });
134
- });
135
-
136
- describe("consume", () => {
137
- it("consumes events from current position", async () => {
138
- await cleanup();
139
-
140
- // Append test events
141
- const events = [
142
- createEvent("agent_registered", {
143
- project_key: "test-project",
144
- agent_name: "agent-1",
145
- program: "test",
146
- model: "test-model",
147
- }),
148
- createEvent("agent_registered", {
149
- project_key: "test-project",
150
- agent_name: "agent-2",
151
- program: "test",
152
- model: "test-model",
153
- }),
154
- createEvent("agent_registered", {
155
- project_key: "test-project",
156
- agent_name: "agent-3",
157
- program: "test",
158
- model: "test-model",
159
- }),
160
- ];
161
-
162
- for (const event of events) {
163
- await appendEvent(event, TEST_PROJECT);
164
- }
165
-
166
- // Create cursor and consume outside Effect.gen
167
- const program = Effect.gen(function* () {
168
- const service = yield* DurableCursor;
169
- return yield* service.create({
170
- stream: "test-stream",
171
- checkpoint: "test-consumer",
172
- projectPath: TEST_PROJECT,
173
- batchSize: 2,
174
- });
175
- });
176
-
177
- const cursor = await runWithCursor(program);
178
- const consumed: string[] = [];
179
-
180
- for await (const msg of cursor.consume<
181
- AgentRegisteredEvent & { id: number; sequence: number }
182
- >()) {
183
- consumed.push(msg.value.agent_name);
184
- await Effect.runPromise(msg.commit());
185
- }
186
-
187
- expect(consumed).toHaveLength(3);
188
- expect(consumed).toEqual(["agent-1", "agent-2", "agent-3"]);
189
- });
190
-
191
- it("resumes consumption from checkpoint", async () => {
192
- await cleanup();
193
-
194
- // Append test events
195
- const events = [
196
- createEvent("agent_registered", {
197
- project_key: "test-project",
198
- agent_name: "agent-1",
199
- program: "test",
200
- model: "test-model",
201
- }),
202
- createEvent("agent_registered", {
203
- project_key: "test-project",
204
- agent_name: "agent-2",
205
- program: "test",
206
- model: "test-model",
207
- }),
208
- createEvent("agent_registered", {
209
- project_key: "test-project",
210
- agent_name: "agent-3",
211
- program: "test",
212
- model: "test-model",
213
- }),
214
- ];
215
-
216
- for (const event of events) {
217
- await appendEvent(event, TEST_PROJECT);
218
- }
219
-
220
- // First consumer - consume first event only
221
- const program1 = Effect.gen(function* () {
222
- const service = yield* DurableCursor;
223
- return yield* service.create({
224
- stream: "test-stream",
225
- checkpoint: "resume-test",
226
- projectPath: TEST_PROJECT,
227
- });
228
- });
229
-
230
- const cursor1 = await runWithCursor(program1);
231
- const first: string[] = [];
232
-
233
- for await (const msg of cursor1.consume<
234
- AgentRegisteredEvent & { id: number; sequence: number }
235
- >()) {
236
- first.push(msg.value.agent_name);
237
- await Effect.runPromise(msg.commit());
238
- break; // Consume only first event
239
- }
240
-
241
- expect(first).toEqual(["agent-1"]);
242
-
243
- // Second consumer - should resume from checkpoint
244
- const program2 = Effect.gen(function* () {
245
- const service = yield* DurableCursor;
246
- return yield* service.create({
247
- stream: "test-stream",
248
- checkpoint: "resume-test",
249
- projectPath: TEST_PROJECT,
250
- });
251
- });
252
-
253
- const cursor2 = await runWithCursor(program2);
254
- const second: string[] = [];
255
-
256
- for await (const msg of cursor2.consume<
257
- AgentRegisteredEvent & { id: number; sequence: number }
258
- >()) {
259
- second.push(msg.value.agent_name);
260
- await Effect.runPromise(msg.commit());
261
- }
262
-
263
- expect(second).toEqual(["agent-2", "agent-3"]);
264
- });
265
-
266
- it("supports event type filtering", async () => {
267
- await cleanup();
268
-
269
- // Append mixed event types
270
- await appendEvent(
271
- createEvent("agent_registered", {
272
- project_key: "test-project",
273
- agent_name: "agent-1",
274
- program: "test",
275
- model: "test-model",
276
- }),
277
- TEST_PROJECT,
278
- );
279
-
280
- await appendEvent(
281
- createEvent("message_sent", {
282
- project_key: "test-project",
283
- from_agent: "agent-1",
284
- to_agents: ["agent-2"],
285
- subject: "test",
286
- body: "test message",
287
- importance: "normal",
288
- ack_required: false,
289
- }),
290
- TEST_PROJECT,
291
- );
292
-
293
- await appendEvent(
294
- createEvent("agent_registered", {
295
- project_key: "test-project",
296
- agent_name: "agent-2",
297
- program: "test",
298
- model: "test-model",
299
- }),
300
- TEST_PROJECT,
301
- );
302
-
303
- const program = Effect.gen(function* () {
304
- const service = yield* DurableCursor;
305
- return yield* service.create({
306
- stream: "test-stream",
307
- checkpoint: "filter-test",
308
- projectPath: TEST_PROJECT,
309
- types: ["agent_registered"],
310
- });
311
- });
312
-
313
- const cursor = await runWithCursor(program);
314
- const types: string[] = [];
315
-
316
- for await (const msg of cursor.consume()) {
317
- types.push(msg.value.type);
318
- await Effect.runPromise(msg.commit());
319
- }
320
-
321
- expect(types).toEqual(["agent_registered", "agent_registered"]);
322
- });
323
-
324
- it("commits update cursor position", async () => {
325
- await cleanup();
326
-
327
- // Append test events
328
- await appendEvent(
329
- createEvent("agent_registered", {
330
- project_key: "test-project",
331
- agent_name: "agent-1",
332
- program: "test",
333
- model: "test-model",
334
- }),
335
- TEST_PROJECT,
336
- );
337
-
338
- const program = Effect.gen(function* () {
339
- const service = yield* DurableCursor;
340
- return yield* service.create({
341
- stream: "test-stream",
342
- checkpoint: "commit-test",
343
- projectPath: TEST_PROJECT,
344
- });
345
- });
346
-
347
- const cursor = await runWithCursor(program);
348
- const initialPos = await Effect.runPromise(cursor.getPosition());
349
-
350
- let afterCommit = 0;
351
- let sequence = 0;
352
-
353
- for await (const msg of cursor.consume()) {
354
- await Effect.runPromise(msg.commit());
355
- afterCommit = await Effect.runPromise(cursor.getPosition());
356
- sequence = msg.sequence;
357
- break;
358
- }
359
-
360
- expect(initialPos).toBe(0);
361
- expect(afterCommit).toBe(sequence);
362
- expect(afterCommit).toBeGreaterThan(0);
363
- });
364
-
365
- it("handles empty streams gracefully", async () => {
366
- await cleanup();
367
-
368
- const program = Effect.gen(function* () {
369
- const service = yield* DurableCursor;
370
- return yield* service.create({
371
- stream: "empty-stream",
372
- checkpoint: "empty-test",
373
- projectPath: TEST_PROJECT,
374
- });
375
- });
376
-
377
- const cursor = await runWithCursor(program);
378
- const consumed: unknown[] = [];
379
-
380
- for await (const msg of cursor.consume()) {
381
- consumed.push(msg);
382
- }
383
-
384
- expect(consumed).toHaveLength(0);
385
- });
386
- });
387
-
388
- describe("commit", () => {
389
- it("persists position across cursor instances", async () => {
390
- await cleanup();
391
-
392
- const config: CursorConfig = {
393
- stream: "test-stream",
394
- checkpoint: "persist-test",
395
- projectPath: TEST_PROJECT,
396
- };
397
-
398
- // First cursor - commit position
399
- const program1 = Effect.gen(function* () {
400
- const service = yield* DurableCursor;
401
- const cursor = yield* service.create(config);
402
- yield* cursor.commit(42);
403
- });
404
-
405
- await runWithCursor(program1);
406
-
407
- // Second cursor - verify position persisted
408
- const program2 = Effect.gen(function* () {
409
- const service = yield* DurableCursor;
410
- const cursor = yield* service.create(config);
411
- return yield* cursor.getPosition();
412
- });
413
-
414
- const position = await runWithCursor(program2);
415
- expect(position).toBe(42);
416
- });
417
- });
418
- });
@@ -1,288 +0,0 @@
1
- /**
2
- * DurableCursor - Positioned event stream consumption with checkpointing
3
- *
4
- * Effect-TS service that wraps event stream reading with cursor state management.
5
- * Enables reliable event processing with resumable position tracking.
6
- *
7
- * @example
8
- * ```typescript
9
- * const program = Effect.gen(function* () {
10
- * const cursor = yield* DurableCursor;
11
- * const consumer = yield* cursor.create({
12
- * stream: "projects/foo/events",
13
- * checkpoint: "agents/bar/position"
14
- * });
15
- *
16
- * for await (const msg of consumer.consume()) {
17
- * yield* handleMessage(msg.value);
18
- * yield* msg.commit();
19
- * }
20
- * });
21
- * ```
22
- */
23
- import { Context, Effect, Ref, Stream } from "effect";
24
- import { getDatabase } from "../index";
25
- import { readEvents } from "../store";
26
- import type { AgentEvent } from "../events";
27
-
28
- // ============================================================================
29
- // Types
30
- // ============================================================================
31
-
32
- /**
33
- * Configuration for creating a cursor
34
- */
35
- export interface CursorConfig {
36
- /** Stream identifier (e.g. "projects/foo/events") */
37
- readonly stream: string;
38
- /** Checkpoint identifier (e.g. "agents/bar/position") */
39
- readonly checkpoint: string;
40
- /** Project path for database location */
41
- readonly projectPath?: string;
42
- /** Batch size for reading events (default: 100) */
43
- readonly batchSize?: number;
44
- /** Optional filters for event types */
45
- readonly types?: AgentEvent["type"][];
46
- }
47
-
48
- /**
49
- * A message from the cursor with commit capability
50
- */
51
- export interface CursorMessage<T = unknown> {
52
- /** The event value */
53
- readonly value: T;
54
- /** Event sequence number */
55
- readonly sequence: number;
56
- /** Commit this position to the checkpoint */
57
- readonly commit: () => Effect.Effect<void>;
58
- }
59
-
60
- /**
61
- * A cursor instance for consuming events
62
- */
63
- export interface Cursor {
64
- /** Get current position */
65
- readonly getPosition: () => Effect.Effect<number>;
66
- /** Consume events as an async iterable */
67
- readonly consume: <
68
- T = AgentEvent & { id: number; sequence: number },
69
- >() => AsyncIterable<CursorMessage<T>>;
70
- /** Update checkpoint position */
71
- readonly commit: (sequence: number) => Effect.Effect<void>;
72
- }
73
-
74
- /**
75
- * DurableCursor service interface
76
- */
77
- export interface DurableCursorService {
78
- /** Create a new cursor instance */
79
- readonly create: (config: CursorConfig) => Effect.Effect<Cursor>;
80
- }
81
-
82
- // ============================================================================
83
- // Service Definition
84
- // ============================================================================
85
-
86
- /**
87
- * DurableCursor Context.Tag
88
- */
89
- export class DurableCursor extends Context.Tag("DurableCursor")<
90
- DurableCursor,
91
- DurableCursorService
92
- >() {}
93
-
94
- // ============================================================================
95
- // Implementation
96
- // ============================================================================
97
-
98
- /**
99
- * Initialize cursor table schema
100
- */
101
- async function initializeCursorSchema(projectPath?: string): Promise<void> {
102
- const db = await getDatabase(projectPath);
103
- await db.exec(`
104
- CREATE TABLE IF NOT EXISTS cursors (
105
- id SERIAL PRIMARY KEY,
106
- stream TEXT NOT NULL,
107
- checkpoint TEXT NOT NULL,
108
- position BIGINT NOT NULL DEFAULT 0,
109
- updated_at BIGINT NOT NULL,
110
- UNIQUE(stream, checkpoint)
111
- );
112
-
113
- CREATE INDEX IF NOT EXISTS idx_cursors_stream ON cursors(stream);
114
- CREATE INDEX IF NOT EXISTS idx_cursors_checkpoint ON cursors(checkpoint);
115
- `);
116
- }
117
-
118
- /**
119
- * Load cursor position from database
120
- */
121
- async function loadCursorPosition(
122
- stream: string,
123
- checkpoint: string,
124
- projectPath?: string,
125
- ): Promise<number> {
126
- await initializeCursorSchema(projectPath);
127
- const db = await getDatabase(projectPath);
128
-
129
- const result = await db.query<{ position: string }>(
130
- `SELECT position FROM cursors WHERE stream = $1 AND checkpoint = $2`,
131
- [stream, checkpoint],
132
- );
133
-
134
- if (result.rows.length === 0) {
135
- // Initialize cursor at position 0
136
- await db.query(
137
- `INSERT INTO cursors (stream, checkpoint, position, updated_at)
138
- VALUES ($1, $2, 0, $3)
139
- ON CONFLICT (stream, checkpoint) DO NOTHING`,
140
- [stream, checkpoint, Date.now()],
141
- );
142
- return 0;
143
- }
144
-
145
- return parseInt(result.rows[0]?.position || "0");
146
- }
147
-
148
- /**
149
- * Save cursor position to database
150
- */
151
- async function saveCursorPosition(
152
- stream: string,
153
- checkpoint: string,
154
- position: number,
155
- projectPath?: string,
156
- ): Promise<void> {
157
- const db = await getDatabase(projectPath);
158
-
159
- await db.query(
160
- `INSERT INTO cursors (stream, checkpoint, position, updated_at)
161
- VALUES ($1, $2, $3, $4)
162
- ON CONFLICT (stream, checkpoint)
163
- DO UPDATE SET position = EXCLUDED.position, updated_at = EXCLUDED.updated_at`,
164
- [stream, checkpoint, position, Date.now()],
165
- );
166
- }
167
-
168
- /**
169
- * Create cursor implementation
170
- */
171
- function createCursorImpl(config: CursorConfig): Effect.Effect<Cursor> {
172
- return Effect.gen(function* () {
173
- // Load initial position from database
174
- const initialPosition = yield* Effect.promise(() =>
175
- loadCursorPosition(config.stream, config.checkpoint, config.projectPath),
176
- );
177
-
178
- // Create mutable reference for current position
179
- const positionRef = yield* Ref.make(initialPosition);
180
-
181
- // Commit function - updates database and reference
182
- const commitPosition = (sequence: number): Effect.Effect<void> =>
183
- Effect.gen(function* () {
184
- yield* Effect.promise(() =>
185
- saveCursorPosition(
186
- config.stream,
187
- config.checkpoint,
188
- sequence,
189
- config.projectPath,
190
- ),
191
- );
192
- yield* Ref.set(positionRef, sequence);
193
- });
194
-
195
- // Get current position
196
- const getPosition = (): Effect.Effect<number> => Ref.get(positionRef);
197
-
198
- // Consume events as async iterable
199
- const consume = <
200
- T = AgentEvent & { id: number; sequence: number },
201
- >(): AsyncIterable<CursorMessage<T>> => {
202
- const batchSize = config.batchSize ?? 100;
203
-
204
- return {
205
- [Symbol.asyncIterator]() {
206
- let currentBatch: Array<
207
- AgentEvent & { id: number; sequence: number }
208
- > = [];
209
- let batchIndex = 0;
210
- let done = false;
211
-
212
- return {
213
- async next(): Promise<IteratorResult<CursorMessage<T>>> {
214
- // Load next batch if current batch is exhausted
215
- if (batchIndex >= currentBatch.length && !done) {
216
- const currentPosition = await Effect.runPromise(
217
- Ref.get(positionRef),
218
- );
219
-
220
- const events = await readEvents(
221
- {
222
- afterSequence: currentPosition,
223
- limit: batchSize,
224
- types: config.types,
225
- },
226
- config.projectPath,
227
- );
228
-
229
- if (events.length === 0) {
230
- done = true;
231
- return { done: true, value: undefined };
232
- }
233
-
234
- currentBatch = events;
235
- batchIndex = 0;
236
- }
237
-
238
- // Return next message from current batch
239
- if (batchIndex < currentBatch.length) {
240
- const event = currentBatch[batchIndex++];
241
- if (!event) {
242
- done = true;
243
- return { done: true, value: undefined };
244
- }
245
-
246
- const message: CursorMessage<T> = {
247
- value: event as unknown as T,
248
- sequence: event.sequence,
249
- commit: () => commitPosition(event.sequence),
250
- };
251
-
252
- return { done: false, value: message };
253
- }
254
-
255
- done = true;
256
- return { done: true, value: undefined };
257
- },
258
- };
259
- },
260
- };
261
- };
262
-
263
- return {
264
- getPosition,
265
- consume,
266
- commit: commitPosition,
267
- };
268
- });
269
- }
270
-
271
- // ============================================================================
272
- // Layer
273
- // ============================================================================
274
-
275
- /**
276
- * Live implementation of DurableCursor service
277
- */
278
- export const DurableCursorLive = DurableCursor.of({
279
- create: createCursorImpl,
280
- });
281
-
282
- /**
283
- * Default layer for DurableCursor service
284
- */
285
- export const DurableCursorLayer = Context.make(
286
- DurableCursor,
287
- DurableCursorLive,
288
- );