@house-rules/event-log 0.0.0-stage → 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/AGENTS.md +65 -0
- package/LICENSE +21 -0
- package/README.md +60 -2
- package/dist/adapters/memory/event.d.ts +3 -0
- package/dist/adapters/memory/event.js +143 -0
- package/dist/adapters/postgres/event.d.ts +4 -0
- package/dist/adapters/postgres/event.js +95 -0
- package/dist/contract.d.ts +5 -0
- package/dist/contract.js +4 -0
- package/dist/event/bind-event-log.d.ts +3 -0
- package/dist/event/bind-event-log.js +17 -0
- package/dist/event/delivery.d.ts +87 -0
- package/dist/event/delivery.js +44 -0
- package/dist/event/event-log.d.ts +136 -0
- package/dist/event/event-log.js +172 -0
- package/dist/event/event.d.ts +60 -0
- package/dist/event/event.js +46 -0
- package/dist/event/operations.d.ts +126 -0
- package/dist/event/operations.js +94 -0
- package/dist/event/options.d.ts +33 -0
- package/dist/event/options.js +28 -0
- package/dist/event/permissions.d.ts +8 -0
- package/dist/event/permissions.js +7 -0
- package/dist/event/source.d.ts +4 -0
- package/dist/event/source.js +15 -0
- package/dist/facade.d.ts +278 -0
- package/dist/facade.js +19 -0
- package/dist/index.d.ts +131 -0
- package/dist/index.js +15 -0
- package/dist/storage.d.ts +22 -0
- package/dist/storage.js +3 -0
- package/migrations/0001_event_log.sql +49 -0
- package/package.json +58 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ConsumerOptions } from "./delivery.js";
|
|
2
|
+
import { Effect, Schema } from "effect";
|
|
3
|
+
import { EventInputInvalid } from "./event.js";
|
|
4
|
+
export const deliveryDefaults = {
|
|
5
|
+
maxAttempts: 5,
|
|
6
|
+
backoff: { baseMs: 1000, capMs: 300000 },
|
|
7
|
+
};
|
|
8
|
+
export const validateOptions = Effect.fn("EventLog.validateOptions")(function* (options) {
|
|
9
|
+
const consumers = yield* Schema.decodeEffect(Schema.Array(ConsumerOptions))(options.consumers).pipe(Effect.mapError(() => new EventInputInvalid({ message: "Invalid event consumer options." })));
|
|
10
|
+
const names = new Set();
|
|
11
|
+
for (const consumer of consumers) {
|
|
12
|
+
if (names.has(consumer.name) ||
|
|
13
|
+
consumer.kinds.some((kind) => !Object.hasOwn(options.registry, kind)) ||
|
|
14
|
+
(consumer.backoff !== undefined && consumer.backoff.baseMs > consumer.backoff.capMs)) {
|
|
15
|
+
return yield* new EventInputInvalid({ message: "Invalid event consumer registration." });
|
|
16
|
+
}
|
|
17
|
+
names.add(consumer.name);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
registry: { ...options.registry },
|
|
21
|
+
consumers: consumers.map((consumer) => ({
|
|
22
|
+
...consumer,
|
|
23
|
+
kinds: [...new Set(consumer.kinds)],
|
|
24
|
+
maxAttempts: consumer.maxAttempts ?? deliveryDefaults.maxAttempts,
|
|
25
|
+
backoff: consumer.backoff ?? deliveryDefaults.backoff,
|
|
26
|
+
})),
|
|
27
|
+
};
|
|
28
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const EventLogPermissions: {
|
|
2
|
+
readonly record: "event-log:record";
|
|
3
|
+
readonly read: "event-log:read";
|
|
4
|
+
readonly deliver: "event-log:deliver";
|
|
5
|
+
readonly deadLetters: "event-log:dead-letters";
|
|
6
|
+
readonly replay: "event-log:replay";
|
|
7
|
+
};
|
|
8
|
+
export type EventLogPermission = (typeof EventLogPermissions)[keyof typeof EventLogPermissions];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EventSource, EventSourceUnavailable } from "@house-rules/application";
|
|
2
|
+
import { Effect, Layer, Schema, Stream } from "effect";
|
|
3
|
+
import { DeliveryToken } from "./delivery.js";
|
|
4
|
+
export const eventSource = (log) => Layer.unwrap(Effect.gen(function* () {
|
|
5
|
+
const unavailable = (cause) => new EventSourceUnavailable({ cause });
|
|
6
|
+
const token = Schema.decodeUnknownEffect(DeliveryToken);
|
|
7
|
+
return Layer.succeed(EventSource, {
|
|
8
|
+
consumers: yield* log.consumers(),
|
|
9
|
+
claimDue: (consumer) => log.claimDue({ consumer }).pipe(Effect.mapError(unavailable)),
|
|
10
|
+
takeNext: (input) => token(input).pipe(Effect.flatMap(log.takeNext), Effect.map((delivery) => delivery?.event ?? null), Effect.mapError(unavailable)),
|
|
11
|
+
settleSuccess: (input) => token(input).pipe(Effect.flatMap(log.settleSuccess), Effect.mapError(unavailable)),
|
|
12
|
+
settleFailure: (input, summary) => token(input).pipe(Effect.flatMap((decoded) => log.settleFailure(decoded, summary)), Effect.mapError(unavailable)),
|
|
13
|
+
wake: Stream.unwrap(log.wakeups()).pipe(Stream.mapError(unavailable)),
|
|
14
|
+
});
|
|
15
|
+
}));
|
package/dist/facade.d.ts
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { Effect, Layer } from "effect";
|
|
2
|
+
import { type EventLogOptions } from "./event/options.js";
|
|
3
|
+
import { EventStore } from "./storage.js";
|
|
4
|
+
declare const EventLog_base: import("effect/Context").ServiceClass<EventLog, "event-log", {
|
|
5
|
+
consumers: () => Effect.Effect<{
|
|
6
|
+
name: string;
|
|
7
|
+
kinds: string[];
|
|
8
|
+
}[], never, never>;
|
|
9
|
+
record: (actor: {
|
|
10
|
+
readonly id: string & import("effect/Brand").Brand<"ActorId">;
|
|
11
|
+
} | null, fact: {
|
|
12
|
+
readonly kind: string;
|
|
13
|
+
readonly subject: {
|
|
14
|
+
readonly kind: string;
|
|
15
|
+
readonly id: string;
|
|
16
|
+
};
|
|
17
|
+
readonly payload: unknown;
|
|
18
|
+
readonly scopeId: string | null;
|
|
19
|
+
}, requestId?: string | null | undefined) => Effect.Effect<{
|
|
20
|
+
kind: string;
|
|
21
|
+
subject: {
|
|
22
|
+
readonly kind: string;
|
|
23
|
+
readonly id: string;
|
|
24
|
+
};
|
|
25
|
+
scopeId: string | null;
|
|
26
|
+
id: string;
|
|
27
|
+
actorId: string | null;
|
|
28
|
+
occurredAt: number;
|
|
29
|
+
requestId: string | null;
|
|
30
|
+
payload: unknown;
|
|
31
|
+
}, import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
32
|
+
read: (query?: {
|
|
33
|
+
readonly after?: string;
|
|
34
|
+
readonly kinds?: readonly string[];
|
|
35
|
+
readonly subject?: {
|
|
36
|
+
readonly kind: string;
|
|
37
|
+
readonly id: string;
|
|
38
|
+
};
|
|
39
|
+
readonly scopeId?: string;
|
|
40
|
+
readonly requestId?: string;
|
|
41
|
+
readonly from?: number;
|
|
42
|
+
readonly to?: number;
|
|
43
|
+
readonly limit?: number;
|
|
44
|
+
} | undefined) => Effect.Effect<{
|
|
45
|
+
kind: string;
|
|
46
|
+
subject: {
|
|
47
|
+
readonly kind: string;
|
|
48
|
+
readonly id: string;
|
|
49
|
+
};
|
|
50
|
+
scopeId: string | null;
|
|
51
|
+
id: string;
|
|
52
|
+
actorId: string | null;
|
|
53
|
+
occurredAt: number;
|
|
54
|
+
requestId: string | null;
|
|
55
|
+
payload: unknown;
|
|
56
|
+
}[], import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable, never>;
|
|
57
|
+
due: () => Effect.Effect<{
|
|
58
|
+
consumer: string;
|
|
59
|
+
}[], import("./contract.ts").EventStoreUnavailable, never>;
|
|
60
|
+
claimDue: (query?: {
|
|
61
|
+
readonly consumer?: string;
|
|
62
|
+
readonly limit?: number;
|
|
63
|
+
} | undefined) => Effect.Effect<{
|
|
64
|
+
readonly consumer: string;
|
|
65
|
+
readonly eventId: string;
|
|
66
|
+
readonly attempt: number;
|
|
67
|
+
readonly replay: boolean;
|
|
68
|
+
readonly generation: string;
|
|
69
|
+
}[], import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
70
|
+
takeNext: (token: {
|
|
71
|
+
readonly consumer: string;
|
|
72
|
+
readonly eventId: string;
|
|
73
|
+
readonly attempt: number;
|
|
74
|
+
readonly replay: boolean;
|
|
75
|
+
readonly generation: string;
|
|
76
|
+
}) => Effect.Effect<{
|
|
77
|
+
event: {
|
|
78
|
+
kind: string;
|
|
79
|
+
subject: {
|
|
80
|
+
readonly kind: string;
|
|
81
|
+
readonly id: string;
|
|
82
|
+
};
|
|
83
|
+
scopeId: string | null;
|
|
84
|
+
id: string;
|
|
85
|
+
actorId: string | null;
|
|
86
|
+
occurredAt: number;
|
|
87
|
+
requestId: string | null;
|
|
88
|
+
payload: unknown;
|
|
89
|
+
};
|
|
90
|
+
token: {
|
|
91
|
+
readonly consumer: string;
|
|
92
|
+
readonly eventId: string;
|
|
93
|
+
readonly attempt: number;
|
|
94
|
+
readonly replay: boolean;
|
|
95
|
+
readonly generation: string;
|
|
96
|
+
};
|
|
97
|
+
} | null, import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
98
|
+
settleSuccess: (token: {
|
|
99
|
+
readonly consumer: string;
|
|
100
|
+
readonly eventId: string;
|
|
101
|
+
readonly attempt: number;
|
|
102
|
+
readonly replay: boolean;
|
|
103
|
+
readonly generation: string;
|
|
104
|
+
}) => Effect.Effect<boolean, import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
105
|
+
settleFailure: (token: {
|
|
106
|
+
readonly consumer: string;
|
|
107
|
+
readonly eventId: string;
|
|
108
|
+
readonly attempt: number;
|
|
109
|
+
readonly replay: boolean;
|
|
110
|
+
readonly generation: string;
|
|
111
|
+
}, lastError: string) => Effect.Effect<boolean, import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
112
|
+
listDeadLetters: (query?: {
|
|
113
|
+
readonly consumer?: string;
|
|
114
|
+
readonly after?: {
|
|
115
|
+
readonly eventId: string;
|
|
116
|
+
readonly consumer: string;
|
|
117
|
+
};
|
|
118
|
+
readonly limit?: number;
|
|
119
|
+
} | undefined) => Effect.Effect<readonly {
|
|
120
|
+
readonly consumer: string;
|
|
121
|
+
readonly eventId: string;
|
|
122
|
+
readonly lastError: string;
|
|
123
|
+
readonly attempts: number;
|
|
124
|
+
readonly failedAt: number;
|
|
125
|
+
}[], import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable, never>;
|
|
126
|
+
replay: (consumer: string, eventId: string) => Effect.Effect<"already_queued" | "busy" | "enqueued", import("./contract.ts").DeadLetterNotFound | import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
127
|
+
wakeups: () => Effect.Effect<import("effect/Stream").Stream<void, import("./contract.ts").EventStoreUnavailable, never>, never, never>;
|
|
128
|
+
}> & {
|
|
129
|
+
readonly id: "event-log";
|
|
130
|
+
} & {
|
|
131
|
+
readonly make: (options: Readonly<{
|
|
132
|
+
registry: Readonly<Record<string, import("@house-rules/domain").PlainSchema>>;
|
|
133
|
+
consumers: ReadonlyArray<import("./contract.ts").ConsumerOptions>;
|
|
134
|
+
}>) => Effect.Effect<{
|
|
135
|
+
consumers: () => Effect.Effect<{
|
|
136
|
+
name: string;
|
|
137
|
+
kinds: string[];
|
|
138
|
+
}[], never, never>;
|
|
139
|
+
record: (actor: {
|
|
140
|
+
readonly id: string & import("effect/Brand").Brand<"ActorId">;
|
|
141
|
+
} | null, fact: {
|
|
142
|
+
readonly kind: string;
|
|
143
|
+
readonly subject: {
|
|
144
|
+
readonly kind: string;
|
|
145
|
+
readonly id: string;
|
|
146
|
+
};
|
|
147
|
+
readonly payload: unknown;
|
|
148
|
+
readonly scopeId: string | null;
|
|
149
|
+
}, requestId?: string | null | undefined) => Effect.Effect<{
|
|
150
|
+
kind: string;
|
|
151
|
+
subject: {
|
|
152
|
+
readonly kind: string;
|
|
153
|
+
readonly id: string;
|
|
154
|
+
};
|
|
155
|
+
scopeId: string | null;
|
|
156
|
+
id: string;
|
|
157
|
+
actorId: string | null;
|
|
158
|
+
occurredAt: number;
|
|
159
|
+
requestId: string | null;
|
|
160
|
+
payload: unknown;
|
|
161
|
+
}, import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
162
|
+
read: (query?: {
|
|
163
|
+
readonly after?: string;
|
|
164
|
+
readonly kinds?: readonly string[];
|
|
165
|
+
readonly subject?: {
|
|
166
|
+
readonly kind: string;
|
|
167
|
+
readonly id: string;
|
|
168
|
+
};
|
|
169
|
+
readonly scopeId?: string;
|
|
170
|
+
readonly requestId?: string;
|
|
171
|
+
readonly from?: number;
|
|
172
|
+
readonly to?: number;
|
|
173
|
+
readonly limit?: number;
|
|
174
|
+
} | undefined) => Effect.Effect<{
|
|
175
|
+
kind: string;
|
|
176
|
+
subject: {
|
|
177
|
+
readonly kind: string;
|
|
178
|
+
readonly id: string;
|
|
179
|
+
};
|
|
180
|
+
scopeId: string | null;
|
|
181
|
+
id: string;
|
|
182
|
+
actorId: string | null;
|
|
183
|
+
occurredAt: number;
|
|
184
|
+
requestId: string | null;
|
|
185
|
+
payload: unknown;
|
|
186
|
+
}[], import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable, never>;
|
|
187
|
+
due: () => Effect.Effect<{
|
|
188
|
+
consumer: string;
|
|
189
|
+
}[], import("./contract.ts").EventStoreUnavailable, never>;
|
|
190
|
+
claimDue: (query?: {
|
|
191
|
+
readonly consumer?: string;
|
|
192
|
+
readonly limit?: number;
|
|
193
|
+
} | undefined) => Effect.Effect<{
|
|
194
|
+
readonly consumer: string;
|
|
195
|
+
readonly eventId: string;
|
|
196
|
+
readonly attempt: number;
|
|
197
|
+
readonly replay: boolean;
|
|
198
|
+
readonly generation: string;
|
|
199
|
+
}[], import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
200
|
+
takeNext: (token: {
|
|
201
|
+
readonly consumer: string;
|
|
202
|
+
readonly eventId: string;
|
|
203
|
+
readonly attempt: number;
|
|
204
|
+
readonly replay: boolean;
|
|
205
|
+
readonly generation: string;
|
|
206
|
+
}) => Effect.Effect<{
|
|
207
|
+
event: {
|
|
208
|
+
kind: string;
|
|
209
|
+
subject: {
|
|
210
|
+
readonly kind: string;
|
|
211
|
+
readonly id: string;
|
|
212
|
+
};
|
|
213
|
+
scopeId: string | null;
|
|
214
|
+
id: string;
|
|
215
|
+
actorId: string | null;
|
|
216
|
+
occurredAt: number;
|
|
217
|
+
requestId: string | null;
|
|
218
|
+
payload: unknown;
|
|
219
|
+
};
|
|
220
|
+
token: {
|
|
221
|
+
readonly consumer: string;
|
|
222
|
+
readonly eventId: string;
|
|
223
|
+
readonly attempt: number;
|
|
224
|
+
readonly replay: boolean;
|
|
225
|
+
readonly generation: string;
|
|
226
|
+
};
|
|
227
|
+
} | null, import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
228
|
+
settleSuccess: (token: {
|
|
229
|
+
readonly consumer: string;
|
|
230
|
+
readonly eventId: string;
|
|
231
|
+
readonly attempt: number;
|
|
232
|
+
readonly replay: boolean;
|
|
233
|
+
readonly generation: string;
|
|
234
|
+
}) => Effect.Effect<boolean, import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
235
|
+
settleFailure: (token: {
|
|
236
|
+
readonly consumer: string;
|
|
237
|
+
readonly eventId: string;
|
|
238
|
+
readonly attempt: number;
|
|
239
|
+
readonly replay: boolean;
|
|
240
|
+
readonly generation: string;
|
|
241
|
+
}, lastError: string) => Effect.Effect<boolean, import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
242
|
+
listDeadLetters: (query?: {
|
|
243
|
+
readonly consumer?: string;
|
|
244
|
+
readonly after?: {
|
|
245
|
+
readonly eventId: string;
|
|
246
|
+
readonly consumer: string;
|
|
247
|
+
};
|
|
248
|
+
readonly limit?: number;
|
|
249
|
+
} | undefined) => Effect.Effect<readonly {
|
|
250
|
+
readonly consumer: string;
|
|
251
|
+
readonly eventId: string;
|
|
252
|
+
readonly lastError: string;
|
|
253
|
+
readonly attempts: number;
|
|
254
|
+
readonly failedAt: number;
|
|
255
|
+
}[], import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable, never>;
|
|
256
|
+
replay: (consumer: string, eventId: string) => Effect.Effect<"already_queued" | "busy" | "enqueued", import("./contract.ts").DeadLetterNotFound | import("./contract.ts").EventInputInvalid | import("./contract.ts").EventStoreUnavailable | import("@house-rules/domain").NoOpenUnit, never>;
|
|
257
|
+
wakeups: () => Effect.Effect<import("effect/Stream").Stream<void, import("./contract.ts").EventStoreUnavailable, never>, never, never>;
|
|
258
|
+
}, import("./contract.ts").EventInputInvalid, EventStore>;
|
|
259
|
+
readonly layer: (options: Readonly<{
|
|
260
|
+
registry: Readonly<Record<string, import("@house-rules/domain").PlainSchema>>;
|
|
261
|
+
consumers: ReadonlyArray<import("./contract.ts").ConsumerOptions>;
|
|
262
|
+
}>) => import("@house-rules/domain").ModuleLayer<EventLog, import("./contract.ts").EventInputInvalid, EventStore>;
|
|
263
|
+
};
|
|
264
|
+
export declare class EventLog extends EventLog_base {
|
|
265
|
+
static readonly source: Layer.Layer<import("@house-rules/application").EventSource, never, EventLog>;
|
|
266
|
+
static readonly presets: {
|
|
267
|
+
readonly default: {
|
|
268
|
+
readonly maxAttempts: 5;
|
|
269
|
+
readonly backoff: {
|
|
270
|
+
readonly baseMs: 1000;
|
|
271
|
+
readonly capMs: 300000;
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
};
|
|
275
|
+
static readonly layerMemory: (options: EventLogOptions) => Layer.Layer<EventLog, import("./contract.ts").EventInputInvalid, never>;
|
|
276
|
+
static readonly layerPostgres: (options: EventLogOptions) => Layer.Layer<EventLog, import("./contract.ts").EventInputInvalid, import("@effect/sql-pg/PgClient").PgClient>;
|
|
277
|
+
}
|
|
278
|
+
export {};
|
package/dist/facade.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineModule } from "@house-rules/domain";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
import { memoryEventStore } from "./adapters/memory/event.js";
|
|
4
|
+
import { postgresEventStore } from "./adapters/postgres/event.js";
|
|
5
|
+
import { makeEventLog } from "./event/event-log.js";
|
|
6
|
+
import { validateOptions, deliveryDefaults } from "./event/options.js";
|
|
7
|
+
import { EventStore } from "./storage.js";
|
|
8
|
+
import { eventSource } from "./event/source.js";
|
|
9
|
+
export class EventLog extends defineModule()("event-log", {
|
|
10
|
+
make: (options) => Effect.gen(function* () {
|
|
11
|
+
const settings = yield* validateOptions(options);
|
|
12
|
+
return makeEventLog(yield* EventStore, settings);
|
|
13
|
+
}),
|
|
14
|
+
}) {
|
|
15
|
+
static source = Layer.unwrap(Effect.map(EventLog, eventSource));
|
|
16
|
+
static presets = { default: deliveryDefaults };
|
|
17
|
+
static layerMemory = (options) => this.layer(options).pipe(Layer.provide(memoryEventStore()));
|
|
18
|
+
static layerPostgres = (options) => this.layer(options).pipe(Layer.provide(postgresEventStore));
|
|
19
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { EventLog } from "./facade.js";
|
|
2
|
+
import { bindEventLog } from "./event/bind-event-log.js";
|
|
3
|
+
export declare const EventLogMount: {
|
|
4
|
+
map: {
|
|
5
|
+
name: string;
|
|
6
|
+
kind: "package";
|
|
7
|
+
package: string;
|
|
8
|
+
};
|
|
9
|
+
service: typeof EventLog;
|
|
10
|
+
operations: (import("@house-rules/domain").Operation<"EventLog.claimDue", import("effect/Schema").Struct<{
|
|
11
|
+
readonly consumer: import("effect/Schema").optionalKey<import("effect/Schema").NonEmptyString>;
|
|
12
|
+
readonly limit: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
|
|
13
|
+
}>, import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
14
|
+
readonly consumer: import("effect/Schema").NonEmptyString;
|
|
15
|
+
readonly eventId: import("effect/Schema").String;
|
|
16
|
+
readonly attempt: import("effect/Schema").Int;
|
|
17
|
+
readonly replay: import("effect/Schema").Boolean;
|
|
18
|
+
readonly generation: import("effect/Schema").String;
|
|
19
|
+
}>>, import("effect/Schema").Union<readonly [typeof import("./contract.js").EventInputInvalid, typeof import("./contract.js").EventStoreUnavailable]>, NoInfer<readonly []>, false, false, true, false> | import("@house-rules/domain").Operation<"EventLog.due", import("effect/Schema").Struct<{}>, import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
20
|
+
readonly consumer: import("effect/Schema").NonEmptyString;
|
|
21
|
+
}>>, import("effect/Schema").Union<readonly [typeof import("./contract.js").EventInputInvalid, typeof import("./contract.js").EventStoreUnavailable]>, NoInfer<readonly []>, true, false, false, false> | import("@house-rules/domain").Operation<"EventLog.listDeadLetters", import("effect/Schema").Struct<{
|
|
22
|
+
readonly consumer: import("effect/Schema").optionalKey<import("effect/Schema").NonEmptyString>;
|
|
23
|
+
readonly after: import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
|
|
24
|
+
readonly eventId: import("effect/Schema").String;
|
|
25
|
+
readonly consumer: import("effect/Schema").NonEmptyString;
|
|
26
|
+
}>>;
|
|
27
|
+
readonly limit: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
|
|
28
|
+
}>, import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
29
|
+
readonly consumer: import("effect/Schema").NonEmptyString;
|
|
30
|
+
readonly eventId: import("effect/Schema").String;
|
|
31
|
+
readonly lastError: import("effect/Schema").String;
|
|
32
|
+
readonly attempts: import("effect/Schema").Int;
|
|
33
|
+
readonly failedAt: import("effect/Schema").Int;
|
|
34
|
+
}>>, import("effect/Schema").Union<readonly [typeof import("./contract.js").EventInputInvalid, typeof import("./contract.js").EventStoreUnavailable]>, NoInfer<readonly []>, true, false, false, false> | import("@house-rules/domain").Operation<"EventLog.read", import("effect/Schema").Struct<{
|
|
35
|
+
readonly after: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
36
|
+
readonly kinds: import("effect/Schema").optionalKey<import("effect/Schema").$Array<import("effect/Schema").NonEmptyString>>;
|
|
37
|
+
readonly subject: import("effect/Schema").optionalKey<import("effect/Schema").Struct<{
|
|
38
|
+
readonly kind: import("effect/Schema").NonEmptyString;
|
|
39
|
+
readonly id: import("effect/Schema").NonEmptyString;
|
|
40
|
+
}>>;
|
|
41
|
+
readonly scopeId: import("effect/Schema").optionalKey<import("effect/Schema").NonEmptyString>;
|
|
42
|
+
readonly requestId: import("effect/Schema").optionalKey<import("effect/Schema").String>;
|
|
43
|
+
readonly from: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
|
|
44
|
+
readonly to: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
|
|
45
|
+
readonly limit: import("effect/Schema").optionalKey<import("effect/Schema").Int>;
|
|
46
|
+
}>, import("effect/Schema").$Array<import("effect/Schema").Struct<{
|
|
47
|
+
readonly kind: import("effect/Schema").NonEmptyString;
|
|
48
|
+
readonly subject: import("effect/Schema").Struct<{
|
|
49
|
+
readonly kind: import("effect/Schema").NonEmptyString;
|
|
50
|
+
readonly id: import("effect/Schema").NonEmptyString;
|
|
51
|
+
}>;
|
|
52
|
+
readonly payload: import("effect/Schema").Unknown;
|
|
53
|
+
readonly scopeId: import("effect/Schema").NullOr<import("effect/Schema").NonEmptyString>;
|
|
54
|
+
readonly id: import("effect/Schema").String;
|
|
55
|
+
readonly actorId: import("effect/Schema").NullOr<import("effect/Schema").NonEmptyString>;
|
|
56
|
+
readonly occurredAt: import("effect/Schema").Int;
|
|
57
|
+
readonly requestId: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
58
|
+
}>>, import("effect/Schema").Union<readonly [typeof import("./contract.js").EventInputInvalid, typeof import("./contract.js").EventStoreUnavailable]>, NoInfer<readonly []>, true, false, false, false> | import("@house-rules/domain").Operation<"EventLog.record", import("effect/Schema").Struct<{
|
|
59
|
+
readonly kind: import("effect/Schema").NonEmptyString;
|
|
60
|
+
readonly subject: import("effect/Schema").Struct<{
|
|
61
|
+
readonly kind: import("effect/Schema").NonEmptyString;
|
|
62
|
+
readonly id: import("effect/Schema").NonEmptyString;
|
|
63
|
+
}>;
|
|
64
|
+
readonly payload: import("effect/Schema").Unknown;
|
|
65
|
+
readonly scopeId: import("effect/Schema").NullOr<import("effect/Schema").NonEmptyString>;
|
|
66
|
+
}>, import("effect/Schema").Struct<{
|
|
67
|
+
readonly kind: import("effect/Schema").NonEmptyString;
|
|
68
|
+
readonly subject: import("effect/Schema").Struct<{
|
|
69
|
+
readonly kind: import("effect/Schema").NonEmptyString;
|
|
70
|
+
readonly id: import("effect/Schema").NonEmptyString;
|
|
71
|
+
}>;
|
|
72
|
+
readonly payload: import("effect/Schema").Unknown;
|
|
73
|
+
readonly scopeId: import("effect/Schema").NullOr<import("effect/Schema").NonEmptyString>;
|
|
74
|
+
readonly id: import("effect/Schema").String;
|
|
75
|
+
readonly actorId: import("effect/Schema").NullOr<import("effect/Schema").NonEmptyString>;
|
|
76
|
+
readonly occurredAt: import("effect/Schema").Int;
|
|
77
|
+
readonly requestId: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
78
|
+
}>, import("effect/Schema").Union<readonly [typeof import("./contract.js").EventInputInvalid, typeof import("./contract.js").EventStoreUnavailable]>, NoInfer<readonly []>, false, false, true, false> | import("@house-rules/domain").Operation<"EventLog.replay", import("effect/Schema").Struct<{
|
|
79
|
+
readonly consumer: import("effect/Schema").NonEmptyString;
|
|
80
|
+
readonly eventId: import("effect/Schema").String;
|
|
81
|
+
}>, import("effect/Schema").Literals<readonly ["enqueued", "busy", "already_queued"]>, import("effect/Schema").Union<readonly [typeof import("./contract.js").EventInputInvalid, typeof import("./contract.js").EventStoreUnavailable, typeof import("./contract.js").DeadLetterNotFound]>, NoInfer<readonly []>, false, false, true, true> | import("@house-rules/domain").Operation<"EventLog.settleFailure", import("effect/Schema").Struct<{
|
|
82
|
+
readonly token: import("effect/Schema").Struct<{
|
|
83
|
+
readonly consumer: import("effect/Schema").NonEmptyString;
|
|
84
|
+
readonly eventId: import("effect/Schema").String;
|
|
85
|
+
readonly attempt: import("effect/Schema").Int;
|
|
86
|
+
readonly replay: import("effect/Schema").Boolean;
|
|
87
|
+
readonly generation: import("effect/Schema").String;
|
|
88
|
+
}>;
|
|
89
|
+
readonly lastError: import("effect/Schema").String;
|
|
90
|
+
}>, import("effect/Schema").Boolean, import("effect/Schema").Union<readonly [typeof import("./contract.js").EventInputInvalid, typeof import("./contract.js").EventStoreUnavailable]>, NoInfer<readonly []>, false, false, true, false> | import("@house-rules/domain").Operation<"EventLog.settleSuccess", import("effect/Schema").Struct<{
|
|
91
|
+
readonly consumer: import("effect/Schema").NonEmptyString;
|
|
92
|
+
readonly eventId: import("effect/Schema").String;
|
|
93
|
+
readonly attempt: import("effect/Schema").Int;
|
|
94
|
+
readonly replay: import("effect/Schema").Boolean;
|
|
95
|
+
readonly generation: import("effect/Schema").String;
|
|
96
|
+
}>, import("effect/Schema").Boolean, import("effect/Schema").Union<readonly [typeof import("./contract.js").EventInputInvalid, typeof import("./contract.js").EventStoreUnavailable]>, NoInfer<readonly []>, false, false, true, false> | import("@house-rules/domain").Operation<"EventLog.takeNext", import("effect/Schema").Struct<{
|
|
97
|
+
readonly consumer: import("effect/Schema").NonEmptyString;
|
|
98
|
+
readonly eventId: import("effect/Schema").String;
|
|
99
|
+
readonly attempt: import("effect/Schema").Int;
|
|
100
|
+
readonly replay: import("effect/Schema").Boolean;
|
|
101
|
+
readonly generation: import("effect/Schema").String;
|
|
102
|
+
}>, import("effect/Schema").NullOr<import("effect/Schema").Struct<{
|
|
103
|
+
readonly token: import("effect/Schema").Struct<{
|
|
104
|
+
readonly consumer: import("effect/Schema").NonEmptyString;
|
|
105
|
+
readonly eventId: import("effect/Schema").String;
|
|
106
|
+
readonly attempt: import("effect/Schema").Int;
|
|
107
|
+
readonly replay: import("effect/Schema").Boolean;
|
|
108
|
+
readonly generation: import("effect/Schema").String;
|
|
109
|
+
}>;
|
|
110
|
+
readonly event: import("effect/Schema").Struct<{
|
|
111
|
+
readonly kind: import("effect/Schema").NonEmptyString;
|
|
112
|
+
readonly subject: import("effect/Schema").Struct<{
|
|
113
|
+
readonly kind: import("effect/Schema").NonEmptyString;
|
|
114
|
+
readonly id: import("effect/Schema").NonEmptyString;
|
|
115
|
+
}>;
|
|
116
|
+
readonly payload: import("effect/Schema").Unknown;
|
|
117
|
+
readonly scopeId: import("effect/Schema").NullOr<import("effect/Schema").NonEmptyString>;
|
|
118
|
+
readonly id: import("effect/Schema").String;
|
|
119
|
+
readonly actorId: import("effect/Schema").NullOr<import("effect/Schema").NonEmptyString>;
|
|
120
|
+
readonly occurredAt: import("effect/Schema").Int;
|
|
121
|
+
readonly requestId: import("effect/Schema").NullOr<import("effect/Schema").String>;
|
|
122
|
+
}>;
|
|
123
|
+
}>>, import("effect/Schema").Union<readonly [typeof import("./contract.js").EventInputInvalid, typeof import("./contract.js").EventStoreUnavailable]>, NoInfer<readonly []>, false, false, true, false>)[];
|
|
124
|
+
bind: typeof bindEventLog;
|
|
125
|
+
};
|
|
126
|
+
export { EventLog } from "./facade.js";
|
|
127
|
+
export { bindEventLog } from "./event/bind-event-log.js";
|
|
128
|
+
export { EventStore } from "./storage.js";
|
|
129
|
+
export { memoryEventStore } from "./adapters/memory/event.js";
|
|
130
|
+
export { postgresEventStore } from "./adapters/postgres/event.js";
|
|
131
|
+
export { EventId, EventFact, StoredEvent, EventQuery, EventInputInvalid, EventStoreUnavailable, DeadLetterNotFound, DeliveryToken, ReplayResult, Delivery, DeadLetter, DeadLetterQuery, DeadLetterCursor, ConsumerOptions, EventLogOperations, EventLogPermissions, type EventLogPermission, type EventLogOptions, } from "./contract.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { EventLog } from "./facade.js";
|
|
2
|
+
import { EventLogOperations } from "./event/operations.js";
|
|
3
|
+
import { bindEventLog } from "./event/bind-event-log.js";
|
|
4
|
+
export const EventLogMount = {
|
|
5
|
+
map: { name: "event-log", kind: "package", package: "@house-rules/event-log" },
|
|
6
|
+
service: EventLog,
|
|
7
|
+
operations: Object.values(EventLogOperations),
|
|
8
|
+
bind: bindEventLog,
|
|
9
|
+
};
|
|
10
|
+
export { EventLog } from "./facade.js";
|
|
11
|
+
export { bindEventLog } from "./event/bind-event-log.js";
|
|
12
|
+
export { EventStore } from "./storage.js";
|
|
13
|
+
export { memoryEventStore } from "./adapters/memory/event.js";
|
|
14
|
+
export { postgresEventStore } from "./adapters/postgres/event.js";
|
|
15
|
+
export { EventId, EventFact, StoredEvent, EventQuery, EventInputInvalid, EventStoreUnavailable, DeadLetterNotFound, DeliveryToken, ReplayResult, Delivery, DeadLetter, DeadLetterQuery, DeadLetterCursor, ConsumerOptions, EventLogOperations, EventLogPermissions, } from "./contract.js";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { NoOpenUnit } from "@house-rules/domain";
|
|
2
|
+
import { Context, type Effect, type Stream } from "effect";
|
|
3
|
+
import type { Candidate, DeadLetter, DeadLetterQuery } from "./event/delivery.js";
|
|
4
|
+
import type { EventQuery, EventStoreUnavailable, StoredEvent } from "./event/event.js";
|
|
5
|
+
type Write<A = void> = Effect.Effect<A, EventStoreUnavailable | NoOpenUnit>;
|
|
6
|
+
declare const EventStore_base: Context.ServiceClass<EventStore, "@house-rules/event-log/EventStore", {
|
|
7
|
+
readonly append: (event: Omit<StoredEvent, "id">) => Write<StoredEvent>;
|
|
8
|
+
readonly read: (query: EventQuery) => Effect.Effect<ReadonlyArray<StoredEvent>, EventStoreUnavailable>;
|
|
9
|
+
readonly lock: (consumer: string) => Write<boolean>;
|
|
10
|
+
readonly candidate: (consumer: string, kinds: ReadonlyArray<string>) => Effect.Effect<Candidate | undefined, EventStoreUnavailable>;
|
|
11
|
+
readonly retry: (consumer: string, candidate: Candidate) => Write;
|
|
12
|
+
readonly enqueueReplay: (consumer: string, candidate: Candidate) => Write<boolean>;
|
|
13
|
+
readonly advance: (consumer: string, eventId: string, replay: boolean) => Write;
|
|
14
|
+
readonly deadLetter: (row: DeadLetter) => Write;
|
|
15
|
+
readonly listDeadLetters: (query: DeadLetterQuery) => Effect.Effect<ReadonlyArray<DeadLetter>, EventStoreUnavailable>;
|
|
16
|
+
readonly findDeadLetter: (consumer: string, eventId: string) => Effect.Effect<DeadLetter | undefined, EventStoreUnavailable>;
|
|
17
|
+
readonly clearDeadLetter: (consumer: string, eventId: string) => Write;
|
|
18
|
+
readonly wakeups: Stream.Stream<void, EventStoreUnavailable>;
|
|
19
|
+
}>;
|
|
20
|
+
export declare class EventStore extends EventStore_base {
|
|
21
|
+
}
|
|
22
|
+
export {};
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
CREATE TABLE event_log_sequence (
|
|
2
|
+
singleton boolean PRIMARY KEY DEFAULT true CHECK (singleton),
|
|
3
|
+
cursor bigint NOT NULL DEFAULT 0
|
|
4
|
+
);
|
|
5
|
+
--> statement-breakpoint
|
|
6
|
+
INSERT INTO event_log_sequence (singleton) VALUES (true);
|
|
7
|
+
--> statement-breakpoint
|
|
8
|
+
CREATE TABLE event_log_events (
|
|
9
|
+
id bigint PRIMARY KEY,
|
|
10
|
+
kind text NOT NULL,
|
|
11
|
+
subject_kind text NOT NULL,
|
|
12
|
+
subject_id text NOT NULL,
|
|
13
|
+
payload jsonb NOT NULL,
|
|
14
|
+
scope_id text,
|
|
15
|
+
actor_id text,
|
|
16
|
+
occurred_at bigint NOT NULL,
|
|
17
|
+
request_id text
|
|
18
|
+
);
|
|
19
|
+
--> statement-breakpoint
|
|
20
|
+
CREATE INDEX event_log_events_kind_cursor ON event_log_events (kind, id);
|
|
21
|
+
--> statement-breakpoint
|
|
22
|
+
CREATE TABLE event_log_consumers (
|
|
23
|
+
name text PRIMARY KEY,
|
|
24
|
+
cursor bigint NOT NULL DEFAULT 0
|
|
25
|
+
);
|
|
26
|
+
--> statement-breakpoint
|
|
27
|
+
CREATE TABLE event_log_pending (
|
|
28
|
+
consumer text NOT NULL REFERENCES event_log_consumers (name),
|
|
29
|
+
event_id bigint NOT NULL REFERENCES event_log_events (id),
|
|
30
|
+
attempts integer NOT NULL CHECK (attempts >= 0),
|
|
31
|
+
next_due bigint NOT NULL,
|
|
32
|
+
replay boolean NOT NULL,
|
|
33
|
+
generation text NOT NULL,
|
|
34
|
+
last_error text NOT NULL,
|
|
35
|
+
PRIMARY KEY (consumer, event_id)
|
|
36
|
+
);
|
|
37
|
+
--> statement-breakpoint
|
|
38
|
+
CREATE INDEX event_log_pending_replay_cursor ON event_log_pending (consumer, event_id) WHERE replay = true;
|
|
39
|
+
--> statement-breakpoint
|
|
40
|
+
CREATE TABLE event_log_dead_letters (
|
|
41
|
+
consumer text NOT NULL REFERENCES event_log_consumers (name),
|
|
42
|
+
event_id bigint NOT NULL REFERENCES event_log_events (id),
|
|
43
|
+
last_error text NOT NULL,
|
|
44
|
+
attempts integer NOT NULL CHECK (attempts > 0),
|
|
45
|
+
failed_at bigint NOT NULL,
|
|
46
|
+
PRIMARY KEY (consumer, event_id)
|
|
47
|
+
);
|
|
48
|
+
--> statement-breakpoint
|
|
49
|
+
CREATE VIEW event_log_history AS SELECT id, kind, subject_kind, subject_id, payload, scope_id, actor_id, occurred_at, request_id FROM event_log_events;
|