@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 +21 -0
- package/README.md +17 -0
- package/esm/adapter.d.ts +96 -0
- package/esm/adapter.d.ts.map +1 -0
- package/esm/adapter.js +10 -0
- package/esm/errors.d.ts +38 -0
- package/esm/errors.d.ts.map +1 -0
- package/esm/errors.js +86 -0
- package/esm/eventlog.d.ts +171 -0
- package/esm/eventlog.d.ts.map +1 -0
- package/esm/eventlog.js +250 -0
- package/esm/in-memory.d.ts +55 -0
- package/esm/in-memory.d.ts.map +1 -0
- package/esm/in-memory.js +209 -0
- package/esm/index.d.ts +72 -0
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +67 -0
- package/esm/package.json +3 -0
- package/package.json +24 -0
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
|
+
```
|
package/esm/adapter.d.ts
ADDED
|
@@ -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 {};
|
package/esm/errors.d.ts
ADDED
|
@@ -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"}
|
package/esm/eventlog.js
ADDED
|
@@ -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"}
|
package/esm/in-memory.js
ADDED
|
@@ -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";
|
package/esm/package.json
ADDED
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
|
+
}
|