@anabranch/eventlog 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Frodi Karlsson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @anabranch/eventlog
2
+
3
+ Event log with Task/Stream semantics. In-memory adapter for event-sourced
4
+ systems with cursor-based consumption.
5
+
6
+ ## Usage
7
+
8
+ ```ts
9
+ import {} from "@anabranch/eventlog";
10
+ ```
11
+
12
+ ## API
13
+
14
+ ###
15
+
16
+ ```ts
17
+ ```
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Event log adapter interface for event-sourced systems.
3
+ *
4
+ * Implement this interface to create drivers for specific event stores.
5
+ * The EventLog class wraps adapters with Task/Stream semantics.
6
+ *
7
+ * For connection lifecycle management, use EventLogConnector which produces adapters.
8
+ * The adapter's close() method releases the connection rather than terminating it.
9
+ */
10
+ /** A single event in the log. */
11
+ export interface Event<T = unknown> {
12
+ /** Unique event identifier (globally unique) */
13
+ id: string;
14
+ /** Topic/stream this event belongs to */
15
+ topic: string;
16
+ /** Event payload */
17
+ data: T;
18
+ /** Partition key for ordering guarantees */
19
+ partitionKey: string;
20
+ /** Event sequence number within the topic */
21
+ sequenceNumber: number;
22
+ /** Timestamp when the event was created */
23
+ timestamp: number;
24
+ /** Optional metadata attached to the event */
25
+ metadata?: Record<string, unknown>;
26
+ }
27
+ /** A batch of events consumed from a topic. */
28
+ export interface EventBatch<T = unknown> {
29
+ /** Topic these events belong to */
30
+ topic: string;
31
+ /** Consumer group that consumed these events */
32
+ consumerGroup: string;
33
+ /** Events in this batch */
34
+ events: Event<T>[];
35
+ /** Cursor position for this batch (opaque to caller) */
36
+ cursor: string;
37
+ }
38
+ /**
39
+ * Event log adapter interface for low-level operations.
40
+ */
41
+ export interface EventLogAdapter {
42
+ /** Append an event to a topic. Returns the event ID. */
43
+ append<T>(topic: string, data: T, options?: AppendOptions): Promise<string>;
44
+ /** Get a single event by ID. */
45
+ get<T>(topic: string, sequenceNumber: number): Promise<Event<T> | null>;
46
+ /** List events in a topic with optional filtering. */
47
+ list<T>(topic: string, options?: ListOptions): Promise<Event<T>[]>;
48
+ /** Consume events from a topic as a streaming async iterable. */
49
+ consume<T>(topic: string, consumerGroup: string, options?: ConsumeOptions): AsyncIterable<EventBatch<T>>;
50
+ /** Commit a cursor position for a consumer group. */
51
+ commitCursor(topic: string, consumerGroup: string, cursor: string): Promise<void>;
52
+ /** Get the committed cursor position for a consumer group. */
53
+ getCursor(topic: string, consumerGroup: string): Promise<string | null>;
54
+ /** Close the adapter connection. */
55
+ close(): Promise<void>;
56
+ }
57
+ /** Options for appending an event. */
58
+ export interface AppendOptions {
59
+ /** Partition key for ordering guarantees within the topic */
60
+ partitionKey?: string;
61
+ /** Optional metadata attached to the event */
62
+ metadata?: Record<string, unknown>;
63
+ /** Timestamp for the event (defaults to now) */
64
+ timestamp?: number;
65
+ }
66
+ /** Options for listing events. */
67
+ export interface ListOptions {
68
+ /** Starting sequence number (inclusive) */
69
+ fromSequenceNumber?: number;
70
+ /** Maximum number of events to return */
71
+ limit?: number;
72
+ /** Filter by partition key */
73
+ partitionKey?: string;
74
+ }
75
+ /** Options for consuming events. */
76
+ export interface ConsumeOptions {
77
+ /** Signal for cancellation */
78
+ signal?: AbortSignal;
79
+ /** Starting cursor (null means from beginning) */
80
+ cursor?: string | null;
81
+ /** Batch size for events */
82
+ batchSize?: number;
83
+ }
84
+ /** Connector that produces connected EventLogAdapter instances. */
85
+ export interface EventLogConnector {
86
+ /** Acquire a connected adapter. */
87
+ connect(signal?: AbortSignal): Promise<EventLogAdapter>;
88
+ /** Close all connections and clean up resources. */
89
+ end(): Promise<void>;
90
+ }
91
+ /** Event log configuration options. */
92
+ export interface EventLogOptions {
93
+ /** Default partition key if not specified */
94
+ defaultPartitionKey?: string;
95
+ }
96
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
package/esm/adapter.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Event log adapter interface for event-sourced systems.
3
+ *
4
+ * Implement this interface to create drivers for specific event stores.
5
+ * The EventLog class wraps adapters with Task/Stream semantics.
6
+ *
7
+ * For connection lifecycle management, use EventLogConnector which produces adapters.
8
+ * The adapter's close() method releases the connection rather than terminating it.
9
+ */
10
+ export {};
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Error thrown when an event log connection cannot be established.
3
+ */
4
+ export declare class EventLogConnectionFailed extends Error {
5
+ name: string;
6
+ constructor(message: string, cause?: unknown);
7
+ }
8
+ /** Error thrown when an append operation fails. */
9
+ export declare class EventLogAppendFailed extends Error {
10
+ name: string;
11
+ constructor(topic: string, message: string, cause?: unknown);
12
+ }
13
+ /** Error thrown when getting an event fails. */
14
+ export declare class EventLogGetFailed extends Error {
15
+ name: string;
16
+ constructor(topic: string, sequenceNumber: number, message: string, cause?: unknown);
17
+ }
18
+ /** Error thrown when listing events fails. */
19
+ export declare class EventLogListFailed extends Error {
20
+ name: string;
21
+ constructor(topic: string, message: string, cause?: unknown);
22
+ }
23
+ /** Error thrown when committing a cursor fails. */
24
+ export declare class EventLogCommitCursorFailed extends Error {
25
+ name: string;
26
+ constructor(topic: string, consumerGroup: string, message: string, cause?: unknown);
27
+ }
28
+ /** Error thrown when getting a cursor fails. */
29
+ export declare class EventLogGetCursorFailed extends Error {
30
+ name: string;
31
+ constructor(topic: string, consumerGroup: string, message: string, cause?: unknown);
32
+ }
33
+ /** Error thrown when closing an event log connection fails. */
34
+ export declare class EventLogCloseFailed extends Error {
35
+ name: string;
36
+ constructor(message: string, cause?: unknown);
37
+ }
38
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;IACxC,IAAI,SAA8B;gBAC/B,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAG7C;AAED,mDAAmD;AACnD,qBAAa,oBAAqB,SAAQ,KAAK;IACpC,IAAI,SAA0B;gBAC3B,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAG5D;AAED,gDAAgD;AAChD,qBAAa,iBAAkB,SAAQ,KAAK;IACjC,IAAI,SAAuB;gBAElC,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO;CAOlB;AAED,8CAA8C;AAC9C,qBAAa,kBAAmB,SAAQ,KAAK;IAClC,IAAI,SAAwB;gBACzB,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAG5D;AAED,mDAAmD;AACnD,qBAAa,0BAA2B,SAAQ,KAAK;IAC1C,IAAI,SAAgC;gBAE3C,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO;CAOlB;AAED,gDAAgD;AAChD,qBAAa,uBAAwB,SAAQ,KAAK;IACvC,IAAI,SAA6B;gBAExC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,OAAO;CAOlB;AAED,+DAA+D;AAC/D,qBAAa,mBAAoB,SAAQ,KAAK;IACnC,IAAI,SAAyB;gBAC1B,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAG7C"}
package/esm/errors.js ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Error thrown when an event log connection cannot be established.
3
+ */
4
+ export class EventLogConnectionFailed extends Error {
5
+ constructor(message, cause) {
6
+ super(`Event log connection failed: ${message}`, { cause });
7
+ Object.defineProperty(this, "name", {
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true,
11
+ value: "EventLogConnectionFailed"
12
+ });
13
+ }
14
+ }
15
+ /** Error thrown when an append operation fails. */
16
+ export class EventLogAppendFailed extends Error {
17
+ constructor(topic, message, cause) {
18
+ super(`Failed to append to ${topic}: ${message}`, { cause });
19
+ Object.defineProperty(this, "name", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: "EventLogAppendFailed"
24
+ });
25
+ }
26
+ }
27
+ /** Error thrown when getting an event fails. */
28
+ export class EventLogGetFailed extends Error {
29
+ constructor(topic, sequenceNumber, message, cause) {
30
+ super(`Failed to get event ${sequenceNumber} from ${topic}: ${message}`, { cause });
31
+ Object.defineProperty(this, "name", {
32
+ enumerable: true,
33
+ configurable: true,
34
+ writable: true,
35
+ value: "EventLogGetFailed"
36
+ });
37
+ }
38
+ }
39
+ /** Error thrown when listing events fails. */
40
+ export class EventLogListFailed extends Error {
41
+ constructor(topic, message, cause) {
42
+ super(`Failed to list events from ${topic}: ${message}`, { cause });
43
+ Object.defineProperty(this, "name", {
44
+ enumerable: true,
45
+ configurable: true,
46
+ writable: true,
47
+ value: "EventLogListFailed"
48
+ });
49
+ }
50
+ }
51
+ /** Error thrown when committing a cursor fails. */
52
+ export class EventLogCommitCursorFailed extends Error {
53
+ constructor(topic, consumerGroup, message, cause) {
54
+ super(`Failed to commit cursor for ${consumerGroup} on ${topic}: ${message}`, { cause });
55
+ Object.defineProperty(this, "name", {
56
+ enumerable: true,
57
+ configurable: true,
58
+ writable: true,
59
+ value: "EventLogCommitCursorFailed"
60
+ });
61
+ }
62
+ }
63
+ /** Error thrown when getting a cursor fails. */
64
+ export class EventLogGetCursorFailed extends Error {
65
+ constructor(topic, consumerGroup, message, cause) {
66
+ super(`Failed to get cursor for ${consumerGroup} on ${topic}: ${message}`, { cause });
67
+ Object.defineProperty(this, "name", {
68
+ enumerable: true,
69
+ configurable: true,
70
+ writable: true,
71
+ value: "EventLogGetCursorFailed"
72
+ });
73
+ }
74
+ }
75
+ /** Error thrown when closing an event log connection fails. */
76
+ export class EventLogCloseFailed extends Error {
77
+ constructor(message, cause) {
78
+ super(`Event log close failed: ${message}`, { cause });
79
+ Object.defineProperty(this, "name", {
80
+ enumerable: true,
81
+ configurable: true,
82
+ writable: true,
83
+ value: "EventLogCloseFailed"
84
+ });
85
+ }
86
+ }
@@ -0,0 +1,171 @@
1
+ import { Source, Task } from "anabranch";
2
+ import type { AppendOptions, ConsumeOptions, Event, EventBatch, EventLogAdapter, EventLogConnector, ListOptions } from "./adapter.js";
3
+ import { EventLogAppendFailed, EventLogCloseFailed, EventLogCommitCursorFailed, EventLogConnectionFailed, EventLogGetCursorFailed, EventLogGetFailed, EventLogListFailed } from "./errors.js";
4
+ /**
5
+ * EventLog wrapper with Task/Stream semantics for event-sourced systems.
6
+ *
7
+ * @example Basic usage
8
+ * ```ts
9
+ * import { EventLog, createInMemory } from "@anabranch/eventlog";
10
+ *
11
+ * const connector = createInMemory();
12
+ * const log = await EventLog.connect(connector).run();
13
+ *
14
+ * // Append an event
15
+ * const eventId = await log.append("users", { action: "created", userId: 123 }).run();
16
+ *
17
+ * // Get a specific event
18
+ * const event = await log.get("users", 0).run();
19
+ *
20
+ * // List events
21
+ * const events = await log.list("users").run();
22
+ *
23
+ * await log.close().run();
24
+ * ```
25
+ *
26
+ * @example Consuming events as a stream
27
+ * ```ts
28
+ * const { successes, errors } = await log
29
+ * .consume("users", "my-consumer-group")
30
+ * .withConcurrency(5)
31
+ * .map(async (batch) => {
32
+ * for (const event of batch.events) {
33
+ * await handleEvent(event.data);
34
+ * }
35
+ * // Explicitly commit after successful processing!
36
+ * await log.commit(batch.topic, batch.consumerGroup, batch.cursor).run();
37
+ * })
38
+ * .partition();
39
+ * ```
40
+ */
41
+ export declare class EventLog {
42
+ private readonly adapter;
43
+ constructor(adapter: EventLogAdapter);
44
+ /**
45
+ * Connect to an event log via a connector.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const log = await EventLog.connect(createInMemory()).run();
50
+ * ```
51
+ */
52
+ static connect(connector: EventLogConnector): Task<EventLog, EventLogConnectionFailed>;
53
+ /**
54
+ * Release the connection back to its source.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * await log.close().run();
59
+ * ```
60
+ */
61
+ close(): Task<void, EventLogCloseFailed>;
62
+ /**
63
+ * Append an event to a topic.
64
+ *
65
+ * @example Basic append
66
+ * ```ts
67
+ * const eventId = await log.append("users", { action: "created", userId: 123 }).run();
68
+ * ```
69
+ *
70
+ * @example With partition key
71
+ * ```ts
72
+ * const eventId = await log.append("orders", orderData, {
73
+ * partitionKey: orderData.userId,
74
+ * }).run();
75
+ * ```
76
+ */
77
+ append<T>(topic: string, data: T, options?: AppendOptions): Task<string, EventLogAppendFailed>;
78
+ /**
79
+ * Get a single event by topic and sequence number.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const event = await log.get("users", 0).run();
84
+ * if (event) {
85
+ * console.log(event.data);
86
+ * }
87
+ * ```
88
+ */
89
+ get<T>(topic: string, sequenceNumber: number): Task<Event<T> | null, EventLogGetFailed>;
90
+ /**
91
+ * List events in a topic with optional filtering and pagination.
92
+ *
93
+ * @example List all events
94
+ * ```ts
95
+ * const events = await log.list("users").run();
96
+ * ```
97
+ *
98
+ * @example With pagination
99
+ * ```ts
100
+ * const events = await log.list("users", {
101
+ * fromSequenceNumber: 100,
102
+ * limit: 50,
103
+ * }).run();
104
+ * ```
105
+ *
106
+ * @example Filtered by partition key
107
+ * ```ts
108
+ * const events = await log.list("orders", {
109
+ * partitionKey: "user-123",
110
+ * }).run();
111
+ * ```
112
+ */
113
+ list<T>(topic: string, options?: ListOptions): Task<Event<T>[], EventLogListFailed>;
114
+ /**
115
+ * Consume events from a topic as a Source for streaming.
116
+ *
117
+ * Note: You must manually commit the cursor after processing to guarantee
118
+ * at-least-once delivery. Auto-commit is intentionally omitted to prevent
119
+ * data loss when using concurrent processing.
120
+ *
121
+ * @example Basic consumption
122
+ * ```ts
123
+ * const { successes, errors } = await log
124
+ * .consume("users", "processor-1")
125
+ * .withConcurrency(5)
126
+ * .map(async (batch) => {
127
+ * for (const event of batch.events) {
128
+ * await handleEvent(event.data);
129
+ * }
130
+ * await log.commit(batch.topic, batch.consumerGroup, batch.cursor).run();
131
+ * })
132
+ * .partition();
133
+ * ```
134
+ *
135
+ * @example From specific cursor position
136
+ * ```ts
137
+ * const lastCursor = await log.getCommittedCursor("users", "processor-1").run();
138
+ * const { successes } = await log
139
+ * .consume("users", "processor-1", { cursor: lastCursor })
140
+ * .tap(async (batch) => {
141
+ * for (const event of batch.events) {
142
+ * console.log(event);
143
+ * }
144
+ * })
145
+ * .partition();
146
+ * ```
147
+ */
148
+ consume<T>(topic: string, consumerGroup: string, options?: ConsumeOptions): Source<EventBatch<T>, EventLogListFailed>;
149
+ /**
150
+ * Commit a cursor position for a consumer group.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * await log.commit("users", "processor-1", cursor).run();
155
+ * ```
156
+ */
157
+ commit(topic: string, consumerGroup: string, cursor: string): Task<void, EventLogCommitCursorFailed>;
158
+ /**
159
+ * Get the committed cursor position for a consumer group.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * const cursor = await log.getCommittedCursor("users", "processor-1").run();
164
+ * if (cursor) {
165
+ * console.log(`Resuming from cursor: ${cursor}`);
166
+ * }
167
+ * ```
168
+ */
169
+ getCommittedCursor(topic: string, consumerGroup: string): Task<string | null, EventLogGetCursorFailed>;
170
+ }
171
+ //# sourceMappingURL=eventlog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eventlog.d.ts","sourceRoot":"","sources":["../src/eventlog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,KAAK,EACL,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,WAAW,EACZ,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,0BAA0B,EAC1B,wBAAwB,EACxB,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,eAAe;IAErD;;;;;;;OAOG;IACH,MAAM,CAAC,OAAO,CACZ,SAAS,EAAE,iBAAiB,GAC3B,IAAI,CAAC,QAAQ,EAAE,wBAAwB,CAAC;IAa3C;;;;;;;OAOG;IACH,KAAK,IAAI,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAaxC;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,CAAC,EACN,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,EACP,OAAO,CAAC,EAAE,aAAa,GACtB,IAAI,CAAC,MAAM,EAAE,oBAAoB,CAAC;IAcrC;;;;;;;;;;OAUG;IACH,GAAG,CAAC,CAAC,EACH,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,GACrB,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,iBAAiB,CAAC;IAe3C;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,IAAI,CAAC,CAAC,EACJ,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,WAAW,GACpB,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,kBAAkB,CAAC;IAcvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACH,OAAO,CAAC,CAAC,EACP,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,OAAO,CAAC,EAAE,cAAc,GACvB,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC;IAmB5C;;;;;;;OAOG;IACH,MAAM,CACJ,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,GACb,IAAI,CAAC,IAAI,EAAE,0BAA0B,CAAC;IAezC;;;;;;;;;;OAUG;IACH,kBAAkB,CAChB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,IAAI,CAAC,MAAM,GAAG,IAAI,EAAE,uBAAuB,CAAC;CAchD"}
@@ -0,0 +1,250 @@
1
+ import { Source, Task } from "anabranch";
2
+ import { EventLogAppendFailed, EventLogCloseFailed, EventLogCommitCursorFailed, EventLogConnectionFailed, EventLogGetCursorFailed, EventLogGetFailed, EventLogListFailed, } from "./errors.js";
3
+ /**
4
+ * EventLog wrapper with Task/Stream semantics for event-sourced systems.
5
+ *
6
+ * @example Basic usage
7
+ * ```ts
8
+ * import { EventLog, createInMemory } from "@anabranch/eventlog";
9
+ *
10
+ * const connector = createInMemory();
11
+ * const log = await EventLog.connect(connector).run();
12
+ *
13
+ * // Append an event
14
+ * const eventId = await log.append("users", { action: "created", userId: 123 }).run();
15
+ *
16
+ * // Get a specific event
17
+ * const event = await log.get("users", 0).run();
18
+ *
19
+ * // List events
20
+ * const events = await log.list("users").run();
21
+ *
22
+ * await log.close().run();
23
+ * ```
24
+ *
25
+ * @example Consuming events as a stream
26
+ * ```ts
27
+ * const { successes, errors } = await log
28
+ * .consume("users", "my-consumer-group")
29
+ * .withConcurrency(5)
30
+ * .map(async (batch) => {
31
+ * for (const event of batch.events) {
32
+ * await handleEvent(event.data);
33
+ * }
34
+ * // Explicitly commit after successful processing!
35
+ * await log.commit(batch.topic, batch.consumerGroup, batch.cursor).run();
36
+ * })
37
+ * .partition();
38
+ * ```
39
+ */
40
+ export class EventLog {
41
+ constructor(adapter) {
42
+ Object.defineProperty(this, "adapter", {
43
+ enumerable: true,
44
+ configurable: true,
45
+ writable: true,
46
+ value: adapter
47
+ });
48
+ }
49
+ /**
50
+ * Connect to an event log via a connector.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const log = await EventLog.connect(createInMemory()).run();
55
+ * ```
56
+ */
57
+ static connect(connector) {
58
+ return Task.of(async () => {
59
+ try {
60
+ return new EventLog(await connector.connect());
61
+ }
62
+ catch (error) {
63
+ throw new EventLogConnectionFailed(error instanceof Error ? error.message : String(error), error);
64
+ }
65
+ });
66
+ }
67
+ /**
68
+ * Release the connection back to its source.
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * await log.close().run();
73
+ * ```
74
+ */
75
+ close() {
76
+ return Task.of(async () => {
77
+ try {
78
+ await this.adapter.close();
79
+ }
80
+ catch (error) {
81
+ throw new EventLogCloseFailed(error instanceof Error ? error.message : String(error), error);
82
+ }
83
+ });
84
+ }
85
+ /**
86
+ * Append an event to a topic.
87
+ *
88
+ * @example Basic append
89
+ * ```ts
90
+ * const eventId = await log.append("users", { action: "created", userId: 123 }).run();
91
+ * ```
92
+ *
93
+ * @example With partition key
94
+ * ```ts
95
+ * const eventId = await log.append("orders", orderData, {
96
+ * partitionKey: orderData.userId,
97
+ * }).run();
98
+ * ```
99
+ */
100
+ append(topic, data, options) {
101
+ return Task.of(async () => {
102
+ try {
103
+ return await this.adapter.append(topic, data, options);
104
+ }
105
+ catch (error) {
106
+ throw new EventLogAppendFailed(topic, error instanceof Error ? error.message : String(error), error);
107
+ }
108
+ });
109
+ }
110
+ /**
111
+ * Get a single event by topic and sequence number.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * const event = await log.get("users", 0).run();
116
+ * if (event) {
117
+ * console.log(event.data);
118
+ * }
119
+ * ```
120
+ */
121
+ get(topic, sequenceNumber) {
122
+ return Task.of(async () => {
123
+ try {
124
+ return await this.adapter.get(topic, sequenceNumber);
125
+ }
126
+ catch (error) {
127
+ throw new EventLogGetFailed(topic, sequenceNumber, error instanceof Error ? error.message : String(error), error);
128
+ }
129
+ });
130
+ }
131
+ /**
132
+ * List events in a topic with optional filtering and pagination.
133
+ *
134
+ * @example List all events
135
+ * ```ts
136
+ * const events = await log.list("users").run();
137
+ * ```
138
+ *
139
+ * @example With pagination
140
+ * ```ts
141
+ * const events = await log.list("users", {
142
+ * fromSequenceNumber: 100,
143
+ * limit: 50,
144
+ * }).run();
145
+ * ```
146
+ *
147
+ * @example Filtered by partition key
148
+ * ```ts
149
+ * const events = await log.list("orders", {
150
+ * partitionKey: "user-123",
151
+ * }).run();
152
+ * ```
153
+ */
154
+ list(topic, options) {
155
+ return Task.of(async () => {
156
+ try {
157
+ return await this.adapter.list(topic, options);
158
+ }
159
+ catch (error) {
160
+ throw new EventLogListFailed(topic, error instanceof Error ? error.message : String(error), error);
161
+ }
162
+ });
163
+ }
164
+ /**
165
+ * Consume events from a topic as a Source for streaming.
166
+ *
167
+ * Note: You must manually commit the cursor after processing to guarantee
168
+ * at-least-once delivery. Auto-commit is intentionally omitted to prevent
169
+ * data loss when using concurrent processing.
170
+ *
171
+ * @example Basic consumption
172
+ * ```ts
173
+ * const { successes, errors } = await log
174
+ * .consume("users", "processor-1")
175
+ * .withConcurrency(5)
176
+ * .map(async (batch) => {
177
+ * for (const event of batch.events) {
178
+ * await handleEvent(event.data);
179
+ * }
180
+ * await log.commit(batch.topic, batch.consumerGroup, batch.cursor).run();
181
+ * })
182
+ * .partition();
183
+ * ```
184
+ *
185
+ * @example From specific cursor position
186
+ * ```ts
187
+ * const lastCursor = await log.getCommittedCursor("users", "processor-1").run();
188
+ * const { successes } = await log
189
+ * .consume("users", "processor-1", { cursor: lastCursor })
190
+ * .tap(async (batch) => {
191
+ * for (const event of batch.events) {
192
+ * console.log(event);
193
+ * }
194
+ * })
195
+ * .partition();
196
+ * ```
197
+ */
198
+ consume(topic, consumerGroup, options) {
199
+ const adapter = this.adapter;
200
+ return Source.from(async function* () {
201
+ try {
202
+ for await (const batch of adapter.consume(topic, consumerGroup, options)) {
203
+ yield batch;
204
+ }
205
+ }
206
+ catch (error) {
207
+ throw new EventLogListFailed(topic, error instanceof Error ? error.message : String(error), error);
208
+ }
209
+ });
210
+ }
211
+ /**
212
+ * Commit a cursor position for a consumer group.
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * await log.commit("users", "processor-1", cursor).run();
217
+ * ```
218
+ */
219
+ commit(topic, consumerGroup, cursor) {
220
+ return Task.of(async () => {
221
+ try {
222
+ await this.adapter.commitCursor(topic, consumerGroup, cursor);
223
+ }
224
+ catch (error) {
225
+ throw new EventLogCommitCursorFailed(topic, consumerGroup, error instanceof Error ? error.message : String(error), error);
226
+ }
227
+ });
228
+ }
229
+ /**
230
+ * Get the committed cursor position for a consumer group.
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * const cursor = await log.getCommittedCursor("users", "processor-1").run();
235
+ * if (cursor) {
236
+ * console.log(`Resuming from cursor: ${cursor}`);
237
+ * }
238
+ * ```
239
+ */
240
+ getCommittedCursor(topic, consumerGroup) {
241
+ return Task.of(async () => {
242
+ try {
243
+ return await this.adapter.getCursor(topic, consumerGroup);
244
+ }
245
+ catch (error) {
246
+ throw new EventLogGetCursorFailed(topic, consumerGroup, error instanceof Error ? error.message : String(error), error);
247
+ }
248
+ });
249
+ }
250
+ }
@@ -0,0 +1,55 @@
1
+ import type { EventLogAdapter, EventLogConnector, EventLogOptions } from "./adapter.js";
2
+ /**
3
+ * Creates an in-memory event log connector using a simple event store.
4
+ *
5
+ * Events are stored in memory only and will be lost on process restart.
6
+ * Useful for testing and development.
7
+ *
8
+ * @example Basic usage
9
+ * ```ts
10
+ * import { EventLog, createInMemory } from "@anabranch/eventlog";
11
+ *
12
+ * const connector = createInMemory();
13
+ * const log = await EventLog.connect(connector).run();
14
+ *
15
+ * // Append an event
16
+ * const eventId = await log.append("users", { action: "created", userId: 123 }).run();
17
+ *
18
+ * // List events
19
+ * const events = await log.list("users").run();
20
+ * ```
21
+ *
22
+ * @example With partition key
23
+ * ```ts
24
+ * await log.append("orders", { orderId: 456, total: 99.99 }, {
25
+ * partitionKey: "user-123"
26
+ * }).run();
27
+ * ```
28
+ *
29
+ * @example Consuming events
30
+ * ```ts
31
+ * const connector = createInMemory();
32
+ * const log = await EventLog.connect(connector).run();
33
+ *
34
+ * // Append some events first
35
+ * await log.append("notifications", { type: "email" }).run();
36
+ * await log.append("notifications", { type: "sms" }).run();
37
+ *
38
+ * // Consume events
39
+ * for await (const batch of log.consume("notifications", "my-consumer-group")) {
40
+ * for (const event of batch.events) {
41
+ * console.log(event.data);
42
+ * }
43
+ * }
44
+ * ```
45
+ */
46
+ export declare function createInMemory(options?: InMemoryOptions): InMemoryConnector;
47
+ /** In-memory event log connector options. */
48
+ export interface InMemoryOptions extends EventLogOptions {
49
+ }
50
+ /** In-memory event log connector. */
51
+ export interface InMemoryConnector extends EventLogConnector {
52
+ connect(): Promise<EventLogAdapter>;
53
+ end(): Promise<void>;
54
+ }
55
+ //# sourceMappingURL=in-memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory.d.ts","sourceRoot":"","sources":["../src/in-memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,eAAe,EACf,iBAAiB,EACjB,eAAe,EAEhB,MAAM,cAAc,CAAC;AAsBtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,iBAAiB,CA0O3E;AAED,6CAA6C;AAC7C,MAAM,WAAW,eAAgB,SAAQ,eAAe;CAAG;AAE3D,qCAAqC;AACrC,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IAC1D,OAAO,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACpC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB"}
@@ -0,0 +1,209 @@
1
+ import { EventLogAppendFailed, EventLogCommitCursorFailed, EventLogGetCursorFailed, EventLogGetFailed, EventLogListFailed, } from "./errors.js";
2
+ function generateId() {
3
+ return crypto.randomUUID();
4
+ }
5
+ /**
6
+ * Creates an in-memory event log connector using a simple event store.
7
+ *
8
+ * Events are stored in memory only and will be lost on process restart.
9
+ * Useful for testing and development.
10
+ *
11
+ * @example Basic usage
12
+ * ```ts
13
+ * import { EventLog, createInMemory } from "@anabranch/eventlog";
14
+ *
15
+ * const connector = createInMemory();
16
+ * const log = await EventLog.connect(connector).run();
17
+ *
18
+ * // Append an event
19
+ * const eventId = await log.append("users", { action: "created", userId: 123 }).run();
20
+ *
21
+ * // List events
22
+ * const events = await log.list("users").run();
23
+ * ```
24
+ *
25
+ * @example With partition key
26
+ * ```ts
27
+ * await log.append("orders", { orderId: 456, total: 99.99 }, {
28
+ * partitionKey: "user-123"
29
+ * }).run();
30
+ * ```
31
+ *
32
+ * @example Consuming events
33
+ * ```ts
34
+ * const connector = createInMemory();
35
+ * const log = await EventLog.connect(connector).run();
36
+ *
37
+ * // Append some events first
38
+ * await log.append("notifications", { type: "email" }).run();
39
+ * await log.append("notifications", { type: "sms" }).run();
40
+ *
41
+ * // Consume events
42
+ * for await (const batch of log.consume("notifications", "my-consumer-group")) {
43
+ * for (const event of batch.events) {
44
+ * console.log(event.data);
45
+ * }
46
+ * }
47
+ * ```
48
+ */
49
+ export function createInMemory(options) {
50
+ const topics = new Map();
51
+ const consumerGroups = new Map();
52
+ let ended = false;
53
+ const defaultOptions = {
54
+ defaultPartitionKey: "default",
55
+ };
56
+ const opts = {
57
+ ...defaultOptions,
58
+ ...options,
59
+ };
60
+ const getOrCreateTopic = (topic) => {
61
+ let state = topics.get(topic);
62
+ if (!state) {
63
+ state = {
64
+ events: [],
65
+ nextSequenceNumber: 0,
66
+ };
67
+ topics.set(topic, state);
68
+ }
69
+ return state;
70
+ };
71
+ const adapter = {
72
+ append(topic, data, appendOptions) {
73
+ if (ended) {
74
+ return Promise.reject(new EventLogAppendFailed(topic, "Connector ended"));
75
+ }
76
+ const state = getOrCreateTopic(topic);
77
+ const id = generateId();
78
+ const sequenceNumber = state.nextSequenceNumber++;
79
+ const partitionKey = appendOptions?.partitionKey ??
80
+ opts.defaultPartitionKey;
81
+ const event = {
82
+ id,
83
+ topic,
84
+ data,
85
+ partitionKey,
86
+ sequenceNumber,
87
+ timestamp: appendOptions?.timestamp ?? Date.now(),
88
+ metadata: appendOptions?.metadata,
89
+ };
90
+ state.events.push(event);
91
+ return Promise.resolve(id);
92
+ },
93
+ get(topic, sequenceNumber) {
94
+ if (ended) {
95
+ return Promise.reject(new EventLogGetFailed(topic, sequenceNumber, "Connector ended"));
96
+ }
97
+ const state = topics.get(topic);
98
+ if (!state) {
99
+ return Promise.resolve(null);
100
+ }
101
+ const event = state.events[sequenceNumber];
102
+ return Promise.resolve(event ?? null);
103
+ },
104
+ list(topic, listOptions) {
105
+ if (ended) {
106
+ return Promise.reject(new EventLogListFailed(topic, "Connector ended"));
107
+ }
108
+ const state = topics.get(topic);
109
+ if (!state) {
110
+ return Promise.resolve([]);
111
+ }
112
+ let events = state.events;
113
+ if (listOptions?.fromSequenceNumber !== undefined) {
114
+ events = events.filter((e) => e.sequenceNumber >= listOptions.fromSequenceNumber);
115
+ }
116
+ if (listOptions?.partitionKey !== undefined) {
117
+ events = events.filter((e) => e.partitionKey === listOptions.partitionKey);
118
+ }
119
+ if (listOptions?.limit !== undefined) {
120
+ events = events.slice(0, listOptions.limit);
121
+ }
122
+ return Promise.resolve(events);
123
+ },
124
+ async *consume(topic, consumerGroup, consumeOptions) {
125
+ if (ended) {
126
+ throw new Error("Connector ended");
127
+ }
128
+ let state = topics.get(topic);
129
+ if (!state) {
130
+ state = {
131
+ events: [],
132
+ nextSequenceNumber: 0,
133
+ };
134
+ topics.set(topic, state);
135
+ }
136
+ let consumerState = consumerGroups.get(consumerGroup);
137
+ if (!consumerState) {
138
+ consumerState = { cursors: new Map() };
139
+ consumerGroups.set(consumerGroup, consumerState);
140
+ }
141
+ const batchSize = consumeOptions?.batchSize ?? 10;
142
+ const startSequence = consumeOptions?.cursor
143
+ ? parseInt(consumeOptions.cursor, 10) + 1
144
+ : 0;
145
+ const signal = consumeOptions?.signal;
146
+ let currentSequence = startSequence;
147
+ while (!signal?.aborted) {
148
+ const events = [];
149
+ for (let i = 0; i < batchSize; i++) {
150
+ const event = state.events[currentSequence];
151
+ if (!event)
152
+ break;
153
+ events.push(event);
154
+ currentSequence++;
155
+ }
156
+ if (events.length === 0) {
157
+ await new Promise((resolve) => setTimeout(resolve, 100));
158
+ continue;
159
+ }
160
+ const cursor = String(currentSequence - 1);
161
+ yield {
162
+ topic,
163
+ consumerGroup,
164
+ events,
165
+ cursor,
166
+ };
167
+ }
168
+ },
169
+ commitCursor(topic, consumerGroup, cursor) {
170
+ if (ended) {
171
+ return Promise.reject(new EventLogCommitCursorFailed(topic, consumerGroup, "Connector ended"));
172
+ }
173
+ let state = consumerGroups.get(consumerGroup);
174
+ if (!state) {
175
+ state = { cursors: new Map() };
176
+ consumerGroups.set(consumerGroup, state);
177
+ }
178
+ state.cursors.set(topic, cursor);
179
+ return Promise.resolve();
180
+ },
181
+ getCursor(topic, consumerGroup) {
182
+ if (ended) {
183
+ return Promise.reject(new EventLogGetCursorFailed(topic, consumerGroup, "Connector ended"));
184
+ }
185
+ const state = consumerGroups.get(consumerGroup);
186
+ if (!state) {
187
+ return Promise.resolve(null);
188
+ }
189
+ return Promise.resolve(state.cursors.get(topic) ?? null);
190
+ },
191
+ close() {
192
+ return Promise.resolve();
193
+ },
194
+ };
195
+ return {
196
+ connect() {
197
+ if (ended) {
198
+ return Promise.reject(new Error("Connector ended"));
199
+ }
200
+ return Promise.resolve(adapter);
201
+ },
202
+ end() {
203
+ ended = true;
204
+ topics.clear();
205
+ consumerGroups.clear();
206
+ return Promise.resolve();
207
+ },
208
+ };
209
+ }
package/esm/index.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @anabranch/eventlog
3
+ *
4
+ * Event log primitives with Task/Stream semantics for event-sourced systems.
5
+ * Integrates with anabranch's {@linkcode Task}, {@linkcode Stream}, {@linkcode Source}, and
6
+ * {@linkcode Channel} types for composable error handling and concurrent processing.
7
+ *
8
+ * ## Adapters vs Connectors
9
+ *
10
+ * An **EventLogConnector** produces connected **EventLogAdapter** instances. Use connectors for
11
+ * production code to properly manage connection lifecycles:
12
+ *
13
+ * - **Connector**: Manages connection pool/lifecycle, produces adapters
14
+ * - **Adapter**: Low-level append/get/list/consume interface
15
+ * - **EventLog**: Wrapper providing Task/Stream methods over an adapter
16
+ *
17
+ * ## Core Types
18
+ *
19
+ * - {@link EventLogConnector} - Interface for connection factories
20
+ * - {@link EventLogAdapter} - Low-level event log operations interface
21
+ * - {@link EventLog} - High-level wrapper with Task/Stream methods
22
+ * - {@link Event} - Single event envelope with id, data, sequence number
23
+ * - {@link EventBatch} - Batch of events consumed from a topic
24
+ *
25
+ * ## Error Types
26
+ *
27
+ * All errors are typed for catchable handling:
28
+ * - {@link EventLogConnectionFailed} - Connection establishment failed
29
+ * - {@link EventLogAppendFailed} - Append operation failed
30
+ * - {@link EventLogGetFailed} - Get event operation failed
31
+ * - {@link EventLogListFailed} - List events operation failed
32
+ * - {@link EventLogCommitCursorFailed} - Cursor commit failed
33
+ * - {@link EventLogGetCursorFailed} - Get cursor operation failed
34
+ *
35
+ * @example Basic usage with Task semantics
36
+ * ```ts
37
+ * import { EventLog, createInMemory } from "@anabranch/eventlog";
38
+ *
39
+ * const connector = createInMemory();
40
+ * const log = await EventLog.connect(connector).run();
41
+ *
42
+ * const eventId = await log.append("users", { action: "created", userId: 123 }).run();
43
+ * const events = await log.list("users").run();
44
+ * ```
45
+ *
46
+ * @example Consuming events as a stream with auto-commit
47
+ * ```ts
48
+ * const connector = createInMemory();
49
+ * const log = await EventLog.connect(connector).run();
50
+ *
51
+ * const { successes, errors } = await log
52
+ * .consume("users", "my-processor")
53
+ * .withConcurrency(5)
54
+ * .map(async (batch) => {
55
+ * for (const event of batch.events) {
56
+ * await processEvent(event.data);
57
+ * }
58
+ * })
59
+ * .partition();
60
+ * ```
61
+ *
62
+ * @module
63
+ */
64
+ export { EventLog } from "./eventlog.js";
65
+ export type { Event, EventBatch, EventLogAdapter, EventLogConnector, } from "./adapter.js";
66
+ export type { AppendOptions, ConsumeOptions, EventLogOptions, ListOptions, } from "./adapter.js";
67
+ export * from "./errors.js";
68
+ export { createInMemory } from "./in-memory.js";
69
+ export type { InMemoryConnector, InMemoryOptions } from "./in-memory.js";
70
+ export { Task } from "anabranch";
71
+ export type { Source, Stream } from "anabranch";
72
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EACV,KAAK,EACL,UAAU,EACV,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,aAAa,EACb,cAAc,EACd,eAAe,EACf,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC"}
package/esm/index.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @anabranch/eventlog
3
+ *
4
+ * Event log primitives with Task/Stream semantics for event-sourced systems.
5
+ * Integrates with anabranch's {@linkcode Task}, {@linkcode Stream}, {@linkcode Source}, and
6
+ * {@linkcode Channel} types for composable error handling and concurrent processing.
7
+ *
8
+ * ## Adapters vs Connectors
9
+ *
10
+ * An **EventLogConnector** produces connected **EventLogAdapter** instances. Use connectors for
11
+ * production code to properly manage connection lifecycles:
12
+ *
13
+ * - **Connector**: Manages connection pool/lifecycle, produces adapters
14
+ * - **Adapter**: Low-level append/get/list/consume interface
15
+ * - **EventLog**: Wrapper providing Task/Stream methods over an adapter
16
+ *
17
+ * ## Core Types
18
+ *
19
+ * - {@link EventLogConnector} - Interface for connection factories
20
+ * - {@link EventLogAdapter} - Low-level event log operations interface
21
+ * - {@link EventLog} - High-level wrapper with Task/Stream methods
22
+ * - {@link Event} - Single event envelope with id, data, sequence number
23
+ * - {@link EventBatch} - Batch of events consumed from a topic
24
+ *
25
+ * ## Error Types
26
+ *
27
+ * All errors are typed for catchable handling:
28
+ * - {@link EventLogConnectionFailed} - Connection establishment failed
29
+ * - {@link EventLogAppendFailed} - Append operation failed
30
+ * - {@link EventLogGetFailed} - Get event operation failed
31
+ * - {@link EventLogListFailed} - List events operation failed
32
+ * - {@link EventLogCommitCursorFailed} - Cursor commit failed
33
+ * - {@link EventLogGetCursorFailed} - Get cursor operation failed
34
+ *
35
+ * @example Basic usage with Task semantics
36
+ * ```ts
37
+ * import { EventLog, createInMemory } from "@anabranch/eventlog";
38
+ *
39
+ * const connector = createInMemory();
40
+ * const log = await EventLog.connect(connector).run();
41
+ *
42
+ * const eventId = await log.append("users", { action: "created", userId: 123 }).run();
43
+ * const events = await log.list("users").run();
44
+ * ```
45
+ *
46
+ * @example Consuming events as a stream with auto-commit
47
+ * ```ts
48
+ * const connector = createInMemory();
49
+ * const log = await EventLog.connect(connector).run();
50
+ *
51
+ * const { successes, errors } = await log
52
+ * .consume("users", "my-processor")
53
+ * .withConcurrency(5)
54
+ * .map(async (batch) => {
55
+ * for (const event of batch.events) {
56
+ * await processEvent(event.data);
57
+ * }
58
+ * })
59
+ * .partition();
60
+ * ```
61
+ *
62
+ * @module
63
+ */
64
+ export { EventLog } from "./eventlog.js";
65
+ export * from "./errors.js";
66
+ export { createInMemory } from "./in-memory.js";
67
+ export { Task } from "anabranch";
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@anabranch/eventlog",
3
+ "version": "0.1.0",
4
+ "description": "Event log with Task/Stream semantics. In-memory adapter for event-sourced systems with cursor-based consumption.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/frodi-karlsson/anabranch.git"
8
+ },
9
+ "license": "MIT",
10
+ "bugs": {
11
+ "url": "https://github.com/frodi-karlsson/anabranch.git"
12
+ },
13
+ "module": "./esm/index.js",
14
+ "exports": {
15
+ ".": {
16
+ "import": "./esm/index.js"
17
+ }
18
+ },
19
+ "scripts": {},
20
+ "dependencies": {
21
+ "anabranch": "^0"
22
+ },
23
+ "_generatedBy": "dnt@dev"
24
+ }