@eventualize/core 1.0.0 → 2.0.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/dist/EvDbStream.test.d.ts +1 -0
- package/dist/EvDbStream.test.js +21 -0
- package/dist/EvDbStream.test.js.map +1 -0
- package/package.json +18 -4
- package/src/EvDbEventStore.ts +0 -302
- package/src/EvDbStream.ts +0 -184
- package/src/EvDbStreamFactory.ts +0 -290
- package/src/EvDbView.ts +0 -100
- package/src/EvDbViewFactory.ts +0 -123
- package/tsconfig.json +0 -18
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { test, describe } from 'node:test';
|
|
2
|
+
import * as assert from 'node:assert';
|
|
3
|
+
describe('EvDbStream', () => {
|
|
4
|
+
test('module can be imported', async () => {
|
|
5
|
+
const module = await import('./EvDbStream.js');
|
|
6
|
+
assert.ok(module.default, 'EvDbStream should be exported as default');
|
|
7
|
+
});
|
|
8
|
+
});
|
|
9
|
+
describe('EvDbView', () => {
|
|
10
|
+
test('module can be imported', async () => {
|
|
11
|
+
const module = await import('./EvDbView.js');
|
|
12
|
+
assert.ok(module.EvDbView, 'EvDbView should be exported');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe('EvDbEventStore', () => {
|
|
16
|
+
test('module can be imported', async () => {
|
|
17
|
+
const module = await import('./EvDbEventStore.js');
|
|
18
|
+
assert.ok(module.EvDbEventStoreBuilder, 'EvDbEventStoreBuilder should be exported');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=EvDbStream.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EvDbStream.test.js","sourceRoot":"","sources":["../src/EvDbStream.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,IAAI,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAC;QAC/C,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,0CAA0C,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,UAAU,EAAE,GAAG,EAAE;IACxB,IAAI,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QAC7C,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,6BAA6B,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,IAAI,CAAC,wBAAwB,EAAE,KAAK,IAAI,EAAE;QACxC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,qBAAqB,CAAC,CAAC;QACnD,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,qBAAqB,EAAE,0CAA0C,CAAC,CAAC;IACtF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventualize/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/outsidenote/eventualize-js",
|
|
8
|
+
"directory": "packages/core"
|
|
9
|
+
},
|
|
5
10
|
"main": "dist/index.js",
|
|
6
11
|
"types": "dist/index.d.ts",
|
|
7
12
|
"exports": {
|
|
@@ -9,7 +14,13 @@
|
|
|
9
14
|
"import": "./dist/index.js",
|
|
10
15
|
"types": "./dist/index.d.ts"
|
|
11
16
|
},
|
|
12
|
-
"./*":
|
|
17
|
+
"./*": {
|
|
18
|
+
"import": "./dist/*.js",
|
|
19
|
+
"types": "./dist/*.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
13
24
|
},
|
|
14
25
|
"scripts": {
|
|
15
26
|
"build": "tsc --build",
|
|
@@ -18,8 +29,11 @@
|
|
|
18
29
|
"test": "echo not tests implemented yet."
|
|
19
30
|
},
|
|
20
31
|
"dependencies": {
|
|
21
|
-
"@eventualize/types": "
|
|
32
|
+
"@eventualize/types": "2.0.0"
|
|
22
33
|
},
|
|
23
34
|
"license": "ISC",
|
|
24
|
-
"type": "module"
|
|
35
|
+
"type": "module",
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
]
|
|
25
39
|
}
|
package/src/EvDbEventStore.ts
DELETED
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
import EvDbStream from './EvDbStream.js';
|
|
2
|
-
import IEvDbStorageSnapshotAdapter from '@eventualize/types/IEvDbStorageSnapshotAdapter';
|
|
3
|
-
import IEvDbStorageStreamAdapter from '@eventualize/types/IEvDbStorageStreamAdapter';
|
|
4
|
-
import IEvDbEventPayload from "@eventualize/types/IEvDbEventPayload";
|
|
5
|
-
import { EvDbStreamFactory } from './EvDbStreamFactory.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Combined storage adapter interface
|
|
9
|
-
*/
|
|
10
|
-
export interface IEvDbStorageAdapter extends IEvDbStorageStreamAdapter, IEvDbStorageSnapshotAdapter { }
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Storage configuration - either separate adapters or combined
|
|
14
|
-
*/
|
|
15
|
-
interface StorageConfig {
|
|
16
|
-
streamAdapter: IEvDbStorageStreamAdapter;
|
|
17
|
-
snapshotAdapter: IEvDbStorageSnapshotAdapter;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Registry of stream factories by stream type
|
|
22
|
-
*/
|
|
23
|
-
interface StreamFactoryRegistry {
|
|
24
|
-
[streamType: string]: EvDbStreamFactory<any, any>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Store class - manages stream creation with configured adapters and factories
|
|
29
|
-
*/
|
|
30
|
-
class BaseStore {
|
|
31
|
-
private readonly storage: StorageConfig;
|
|
32
|
-
private readonly streamFactories: StreamFactoryRegistry = {};
|
|
33
|
-
|
|
34
|
-
public constructor(storage: StorageConfig, streamFactories: StreamFactoryRegistry) {
|
|
35
|
-
this.storage = storage;
|
|
36
|
-
this.streamFactories = streamFactories;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Create a stream instance by stream type and ID
|
|
41
|
-
*/
|
|
42
|
-
public createStream(streamType: string, streamId: string): EvDbStream {
|
|
43
|
-
const factory = this.streamFactories[streamType];
|
|
44
|
-
|
|
45
|
-
if (!factory) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
`No stream factory registered for stream type: ${streamType}. ` +
|
|
48
|
-
`Available types: ${Object.keys(this.streamFactories).join(', ')}`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const stream = factory.create(
|
|
53
|
-
streamId,
|
|
54
|
-
this.storage.streamAdapter,
|
|
55
|
-
this.storage.snapshotAdapter
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
return stream;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get a stream instance from the event store by stream type and ID
|
|
63
|
-
*/
|
|
64
|
-
public async getStream(streamType: string, streamId: string): Promise<EvDbStream> {
|
|
65
|
-
const factory = this.streamFactories[streamType];
|
|
66
|
-
|
|
67
|
-
if (!factory) {
|
|
68
|
-
throw new Error(
|
|
69
|
-
`No stream factory registered for stream type: ${streamType}. ` +
|
|
70
|
-
`Available types: ${Object.keys(this.streamFactories).join(', ')}`
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return factory.get(
|
|
75
|
-
streamId,
|
|
76
|
-
this.storage.streamAdapter,
|
|
77
|
-
this.storage.snapshotAdapter
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Check if a stream type is registered
|
|
83
|
-
*/
|
|
84
|
-
public hasStreamType(streamType: string): boolean {
|
|
85
|
-
return streamType in this.streamFactories;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get all registered stream types
|
|
90
|
-
*/
|
|
91
|
-
public getStreamTypes(): string[] {
|
|
92
|
-
return Object.keys(this.streamFactories);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Create a builder for configuring the store
|
|
97
|
-
*/
|
|
98
|
-
public static builder(): BaseStoreBuilder {
|
|
99
|
-
return new BaseStoreBuilder();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public async close(): Promise<void> {
|
|
103
|
-
await Promise.all([this.storage.snapshotAdapter.close(), this.storage.streamAdapter.close()]);
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Builder for creating Store instances
|
|
110
|
-
*/
|
|
111
|
-
class BaseStoreBuilder {
|
|
112
|
-
private streamAdapter?: IEvDbStorageStreamAdapter;
|
|
113
|
-
private snapshotAdapter?: IEvDbStorageSnapshotAdapter;
|
|
114
|
-
private streamFactories: StreamFactoryRegistry = {};
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Configure with separate stream and snapshot adapters
|
|
118
|
-
*/
|
|
119
|
-
public withAdapters(
|
|
120
|
-
streamAdapter: IEvDbStorageStreamAdapter,
|
|
121
|
-
snapshotAdapter: IEvDbStorageSnapshotAdapter
|
|
122
|
-
): this {
|
|
123
|
-
this.streamAdapter = streamAdapter;
|
|
124
|
-
this.snapshotAdapter = snapshotAdapter;
|
|
125
|
-
return this;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Configure with a combined adapter that implements both interfaces
|
|
130
|
-
*/
|
|
131
|
-
public withAdapter(adapter: IEvDbStorageAdapter): this {
|
|
132
|
-
this.streamAdapter = adapter;
|
|
133
|
-
this.snapshotAdapter = adapter;
|
|
134
|
-
return this;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Register a stream factory
|
|
139
|
-
*/
|
|
140
|
-
public withStreamFactory<TEvents extends IEvDbEventPayload, TStreamType extends string>(
|
|
141
|
-
factory: EvDbStreamFactory<TEvents, TStreamType>
|
|
142
|
-
): this {
|
|
143
|
-
const streamType = factory.getStreamType();
|
|
144
|
-
if (this.streamFactories[streamType]) {
|
|
145
|
-
throw new Error(`Stream factory for type '${streamType}' is already registered`);
|
|
146
|
-
}
|
|
147
|
-
this.streamFactories[streamType] = factory;
|
|
148
|
-
return this;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Register multiple stream factories at once
|
|
153
|
-
*/
|
|
154
|
-
public withStreamFactories(
|
|
155
|
-
factories: ReadonlyArray<EvDbStreamFactory<any, string>>
|
|
156
|
-
): this {
|
|
157
|
-
factories.forEach(factory => {
|
|
158
|
-
this.withStreamFactory(factory);
|
|
159
|
-
});
|
|
160
|
-
return this;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Build the store instance
|
|
165
|
-
*/
|
|
166
|
-
public build(): BaseStore {
|
|
167
|
-
if (!this.streamAdapter || !this.snapshotAdapter) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
'Storage adapters must be configured. Use withAdapter() or withAdapters()'
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (Object.keys(this.streamFactories).length === 0) {
|
|
174
|
-
console.warn('Store created with no stream factories registered');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return new BaseStore(
|
|
178
|
-
{
|
|
179
|
-
streamAdapter: this.streamAdapter,
|
|
180
|
-
snapshotAdapter: this.snapshotAdapter
|
|
181
|
-
},
|
|
182
|
-
this.streamFactories
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ============================================================================
|
|
188
|
-
// TYPED STORE - Type-safe stream creation
|
|
189
|
-
// ============================================================================
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Helper type to create method signatures for each stream factory
|
|
193
|
-
*/
|
|
194
|
-
export type StreamCreatorMethods<TStreamTypes extends Record<string, EvDbStreamFactory<any, any>>> = {
|
|
195
|
-
[K in keyof TStreamTypes as `create${K & string}`]: (streamId: string) => EvDbStream;
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
export type StreamMap = Record<string, any>;
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Type-safe store that knows about specific stream types
|
|
202
|
-
*/
|
|
203
|
-
export class EvDbEventStore<TStreamTypes extends Record<string, EvDbStreamFactory<any, any>>> {
|
|
204
|
-
constructor(private readonly store: BaseStore) {
|
|
205
|
-
// Create dynamic methods for each stream type
|
|
206
|
-
this.createDynamicMethods();
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Create a stream with type-safe stream type parameter (fallback method)
|
|
211
|
-
*/
|
|
212
|
-
public createStream<K extends keyof TStreamTypes & string>(
|
|
213
|
-
streamType: K,
|
|
214
|
-
streamId: string,
|
|
215
|
-
): EvDbStream {
|
|
216
|
-
return this.store.createStream(streamType, streamId);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Fetch a stream from the event store with type-safe stream type parameter (fallback method)
|
|
221
|
-
*/
|
|
222
|
-
public getStream<K extends keyof TStreamTypes & string>(
|
|
223
|
-
streamType: K,
|
|
224
|
-
streamId: string
|
|
225
|
-
): Promise<EvDbStream> {
|
|
226
|
-
|
|
227
|
-
return this.store.getStream(streamType, streamId);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Create dynamic methods for each registered stream factory
|
|
232
|
-
*/
|
|
233
|
-
private createDynamicMethods(): void {
|
|
234
|
-
const streamTypes = this.store.getStreamTypes();
|
|
235
|
-
|
|
236
|
-
for (const streamType of streamTypes) {
|
|
237
|
-
const methodName = `create${streamType}` as keyof this;
|
|
238
|
-
|
|
239
|
-
// Create the dynamic method
|
|
240
|
-
(this as any)[methodName] = (streamId: string) => {
|
|
241
|
-
return this.store.createStream(streamType, streamId);
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Get the underlying store
|
|
248
|
-
*/
|
|
249
|
-
public getStore(): BaseStore {
|
|
250
|
-
return this.store;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Make TypedStore callable with dynamic methods
|
|
255
|
-
// export interface TypedStore<TStreamTypes extends Record<string, StreamFactory<any>>>
|
|
256
|
-
// extends StreamCreatorMethods<TStreamTypes> { }
|
|
257
|
-
|
|
258
|
-
export type TypedStoreType<
|
|
259
|
-
TStreamTypes extends Record<string, EvDbStreamFactory<any, any>>
|
|
260
|
-
> = StreamCreatorMethods<TStreamTypes>;
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Type-safe store builder
|
|
264
|
-
*/
|
|
265
|
-
export class EvDbEventStoreBuilder<
|
|
266
|
-
TStreamTypes extends Record<string, EvDbStreamFactory<any, string>> = {}
|
|
267
|
-
> {
|
|
268
|
-
private builder = BaseStore.builder();
|
|
269
|
-
|
|
270
|
-
public withAdapters(
|
|
271
|
-
streamAdapter: IEvDbStorageStreamAdapter,
|
|
272
|
-
snapshotAdapter: IEvDbStorageSnapshotAdapter
|
|
273
|
-
): this {
|
|
274
|
-
this.builder.withAdapters(streamAdapter, snapshotAdapter);
|
|
275
|
-
return this;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
public withAdapter(adapter: IEvDbStorageAdapter): this {
|
|
279
|
-
this.builder.withAdapter(adapter);
|
|
280
|
-
return this;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Track stream type in generics
|
|
284
|
-
public withStreamFactory<
|
|
285
|
-
F extends EvDbStreamFactory<any, string>,
|
|
286
|
-
K extends F extends EvDbStreamFactory<any, infer ST> ? ST : never
|
|
287
|
-
>(
|
|
288
|
-
factory: F
|
|
289
|
-
): EvDbEventStoreBuilder<TStreamTypes & Record<K, F>> {
|
|
290
|
-
this.builder.withStreamFactory(factory);
|
|
291
|
-
// return as the new type that tracks K
|
|
292
|
-
return this as unknown as EvDbEventStoreBuilder<TStreamTypes & Record<K, F>>;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
public build(): EvDbEventStore<TStreamTypes> & StreamCreatorMethods<TStreamTypes> {
|
|
296
|
-
const store = new EvDbEventStore(this.builder.build());
|
|
297
|
-
return store as EvDbEventStore<TStreamTypes> & StreamCreatorMethods<TStreamTypes>;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
export type EvDbEventStoreType<TStreams extends StreamMap = StreamMap> =
|
|
302
|
-
EvDbEventStore<TStreams> & StreamCreatorMethods<TStreams>;
|
package/src/EvDbStream.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import EvDbEvent from "@eventualize/types/EvDbEvent";
|
|
2
|
-
import EvDbMessage from "@eventualize/types/EvDbMessage";
|
|
3
|
-
import IEvDbStorageStreamAdapter from "@eventualize/types/IEvDbStorageStreamAdapter";
|
|
4
|
-
import IEvDbView from "@eventualize/types/IEvDbView";
|
|
5
|
-
import EvDbStreamAddress from "@eventualize/types/EvDbStreamAddress";
|
|
6
|
-
import IEvDbViewStore from "@eventualize/types/IEvDbViewStore";
|
|
7
|
-
import IEvDbStreamStore from "@eventualize/types/IEvDbStreamStore";
|
|
8
|
-
import IEvDbStreamStoreData from "@eventualize/types/IEvDbStreamStoreData";
|
|
9
|
-
import IEvDbEventPayload from "@eventualize/types/IEvDbEventPayload";
|
|
10
|
-
import StreamStoreAffected from "@eventualize/types/StreamStoreAffected";
|
|
11
|
-
import IEvDbEventMetadata from "@eventualize/types/IEvDbEventMetadata";
|
|
12
|
-
import EvDbStreamCursor from "@eventualize/types/EvDbStreamCursor";
|
|
13
|
-
import OCCException from "@eventualize/types/OCCException";
|
|
14
|
-
import { EvDbStreamType } from "@eventualize/types/primitiveTypes";
|
|
15
|
-
import EVDbMessagesProducer from "@eventualize/types/EvDbMessagesProducer";
|
|
16
|
-
import { EvDbView } from "./EvDbView.js"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
type ImmutableIEvDbView = Readonly<EvDbView<unknown>>;
|
|
20
|
-
export type ImmutableIEvDbViewMap = Readonly<Record<string, ImmutableIEvDbView>>;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export default class EvDbStream implements IEvDbStreamStore, IEvDbStreamStoreData {
|
|
25
|
-
|
|
26
|
-
protected _pendingEvents: ReadonlyArray<EvDbEvent> = [];
|
|
27
|
-
protected _pendingMessages: ReadonlyArray<EvDbMessage> = [];
|
|
28
|
-
|
|
29
|
-
private static readonly ASSEMBLY_NAME = {
|
|
30
|
-
name: 'evdb-core',
|
|
31
|
-
version: '1.0.0'
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
private static readonly DEFAULT_CAPTURE_BY =
|
|
35
|
-
`${EvDbStream.ASSEMBLY_NAME.name}-${EvDbStream.ASSEMBLY_NAME.version}`;
|
|
36
|
-
|
|
37
|
-
private readonly _storageAdapter: IEvDbStorageStreamAdapter;
|
|
38
|
-
|
|
39
|
-
// Views
|
|
40
|
-
protected readonly _views: ImmutableIEvDbViewMap;
|
|
41
|
-
|
|
42
|
-
getViews(): ImmutableIEvDbViewMap {
|
|
43
|
-
return this._views;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
getView(viewName: string): Readonly<IEvDbView> | undefined {
|
|
47
|
-
return this._views[viewName];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Events
|
|
51
|
-
/**
|
|
52
|
-
* Unspecialized events
|
|
53
|
-
*/
|
|
54
|
-
getEvents(): ReadonlyArray<EvDbEvent> {
|
|
55
|
-
return this._pendingEvents;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// StreamAddress
|
|
59
|
-
public streamAddress: EvDbStreamAddress;
|
|
60
|
-
|
|
61
|
-
// StoredOffset
|
|
62
|
-
public storedOffset: number;
|
|
63
|
-
|
|
64
|
-
// Constructor
|
|
65
|
-
public constructor(
|
|
66
|
-
streamType: EvDbStreamType,
|
|
67
|
-
views: ImmutableIEvDbView[],
|
|
68
|
-
storageAdapter: IEvDbStorageStreamAdapter,
|
|
69
|
-
streamId: string,
|
|
70
|
-
lastStoredOffset: number,
|
|
71
|
-
protected messagesProducer: EVDbMessagesProducer
|
|
72
|
-
) {
|
|
73
|
-
this._views = views.reduce((acc, view) => {
|
|
74
|
-
const viewName = view.address.viewName;
|
|
75
|
-
acc[viewName] = view;
|
|
76
|
-
return acc;
|
|
77
|
-
}, {} as Record<string, IEvDbViewStore>) as ImmutableIEvDbViewMap;
|
|
78
|
-
this._storageAdapter = storageAdapter;
|
|
79
|
-
this.streamAddress = new EvDbStreamAddress(streamType, streamId);
|
|
80
|
-
this.storedOffset = lastStoredOffset;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
protected appendEvent(
|
|
84
|
-
payload: IEvDbEventPayload,
|
|
85
|
-
capturedBy?: string | null
|
|
86
|
-
): IEvDbEventMetadata {
|
|
87
|
-
capturedBy = capturedBy ?? EvDbStream.DEFAULT_CAPTURE_BY;
|
|
88
|
-
// const json = JSON.stringify(payload); // Or use custom serializer
|
|
89
|
-
|
|
90
|
-
const cursor = this.getNextCursor(this._pendingEvents);
|
|
91
|
-
const e = new EvDbEvent(
|
|
92
|
-
payload.payloadType,
|
|
93
|
-
cursor,
|
|
94
|
-
payload,
|
|
95
|
-
new Date(),
|
|
96
|
-
capturedBy,
|
|
97
|
-
);
|
|
98
|
-
this._pendingEvents = [...this._pendingEvents, e];
|
|
99
|
-
|
|
100
|
-
// Apply to views
|
|
101
|
-
for (const view of Object.values(this._views)) {
|
|
102
|
-
view.applyEvent(e);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Outbox producer
|
|
106
|
-
const viewsStates = Object.fromEntries(Object.entries(this._views)
|
|
107
|
-
.map(([k, v]) => {
|
|
108
|
-
return [k, (v as EvDbView<unknown>).state]
|
|
109
|
-
}));
|
|
110
|
-
const producedMessages = this.messagesProducer(e, viewsStates);
|
|
111
|
-
this._pendingMessages = [...this._pendingMessages, ...producedMessages]
|
|
112
|
-
|
|
113
|
-
return e;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private getNextCursor(events: ReadonlyArray<EvDbEvent>): EvDbStreamCursor {
|
|
117
|
-
if (events.length === 0) {
|
|
118
|
-
return new EvDbStreamCursor(this.streamAddress, this.storedOffset + 1);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const lastEvent = events[events.length - 1];
|
|
122
|
-
const offset = lastEvent.streamCursor.offset;
|
|
123
|
-
return new EvDbStreamCursor(this.streamAddress, offset + 1);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// AppendToOutbox
|
|
127
|
-
/**
|
|
128
|
-
* Put a row into the publication (out-box pattern).
|
|
129
|
-
*/
|
|
130
|
-
public appendToOutbox(e: EvDbMessage): void {
|
|
131
|
-
this._pendingMessages = [...this._pendingMessages, e];
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// StoreAsync
|
|
135
|
-
public async store(): Promise<StreamStoreAffected> {
|
|
136
|
-
// Telemetry
|
|
137
|
-
// const tags = this.streamAddress.toOtelTags();
|
|
138
|
-
// const duration = EvDbStream._sysMeters.measureStoreEventsDuration(tags);
|
|
139
|
-
// const activity = EvDbStream._trace.startActivity(tags, 'EvDb.Store');
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
if (this._pendingEvents.length === 0) {
|
|
143
|
-
return StreamStoreAffected.Empty;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const affected = await this._storageAdapter.storeStreamAsync(
|
|
147
|
-
this._pendingEvents,
|
|
148
|
-
this._pendingMessages,
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const lastEvent = this._pendingEvents[this._pendingEvents.length - 1];
|
|
152
|
-
this.storedOffset = lastEvent.streamCursor.offset;
|
|
153
|
-
this._pendingEvents = [];
|
|
154
|
-
this._pendingMessages = [];
|
|
155
|
-
|
|
156
|
-
const viewSaveTasks = Object.values(this._views).map(v => v.store());
|
|
157
|
-
await Promise.all(viewSaveTasks);
|
|
158
|
-
|
|
159
|
-
return affected;
|
|
160
|
-
} catch (error) {
|
|
161
|
-
if (error instanceof OCCException) {
|
|
162
|
-
throw error;
|
|
163
|
-
}
|
|
164
|
-
throw error;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// CountOfPendingEvents
|
|
170
|
-
/**
|
|
171
|
-
* number of events that were not stored yet.
|
|
172
|
-
*/
|
|
173
|
-
public get countOfPendingEvents(): number {
|
|
174
|
-
return this._pendingEvents.length;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Notifications
|
|
178
|
-
/**
|
|
179
|
-
* Unspecialized messages
|
|
180
|
-
*/
|
|
181
|
-
public getMessages(): ReadonlyArray<EvDbMessage> {
|
|
182
|
-
return this._pendingMessages;
|
|
183
|
-
}
|
|
184
|
-
}
|
package/src/EvDbStreamFactory.ts
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
import IEvDbStorageSnapshotAdapter from '@eventualize/types/IEvDbStorageSnapshotAdapter';
|
|
2
|
-
import IEvDbStorageStreamAdapter from '@eventualize/types/IEvDbStorageStreamAdapter';
|
|
3
|
-
import IEvDbEventPayload from "@eventualize/types/IEvDbEventPayload";
|
|
4
|
-
import EvDbStreamCursor from '@eventualize/types/EvDbStreamCursor';
|
|
5
|
-
import EvDbStreamAddress from '@eventualize/types/EvDbStreamAddress';
|
|
6
|
-
import EVDbMessagesProducer from '@eventualize/types/EvDbMessagesProducer';
|
|
7
|
-
|
|
8
|
-
import EvDbStream from './EvDbStream.js';
|
|
9
|
-
import { EvDbView } from './EvDbView.js';
|
|
10
|
-
import { ViewFactory, createViewFactory, EvDbStreamEventHandlersMap } from './EvDbViewFactory.js';
|
|
11
|
-
import EvDbEvent from '@eventualize/types/EvDbEvent.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Configuration for creating a stream factory
|
|
15
|
-
*/
|
|
16
|
-
export interface EvDbStreamFactoryConfig<TEvents extends IEvDbEventPayload, TStreamType extends string> {
|
|
17
|
-
streamType: TStreamType;
|
|
18
|
-
viewFactories: ViewFactory<any, TEvents>[];
|
|
19
|
-
eventTypes: EventTypeConfig<TEvents>[];
|
|
20
|
-
viewNames: string[]; // Track view names for accessor creation
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Configuration for each event type
|
|
25
|
-
*/
|
|
26
|
-
export interface EventTypeConfig<
|
|
27
|
-
TEvent extends IEvDbEventPayload,
|
|
28
|
-
> {
|
|
29
|
-
eventClass: new (...args: any[]) => TEvent;
|
|
30
|
-
eventName: string;
|
|
31
|
-
eventMessagesProducer?: EVDbMessagesProducer;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Type helper to extract event methods
|
|
36
|
-
*/
|
|
37
|
-
type EventMethods<TEvents extends IEvDbEventPayload> = {
|
|
38
|
-
[K in TEvents as `appendEvent${K['payloadType']}`]: (event: K) => Promise<void>;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Type helper to create view accessors map
|
|
43
|
-
*/
|
|
44
|
-
type ViewAccessors<TViews extends Record<string, EvDbView<any>>> = {
|
|
45
|
-
readonly views: TViews;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Combined stream type with event methods and view accessors
|
|
50
|
-
*/
|
|
51
|
-
export type StreamWithEventMethods<TEvents extends IEvDbEventPayload, TViews extends Record<string, EvDbView<any>> = {}> =
|
|
52
|
-
EvDbStream & EventMethods<TEvents> & ViewAccessors<TViews>;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Stream Factory - creates stream instances with configured views and dynamic event methods
|
|
56
|
-
*/
|
|
57
|
-
export class EvDbStreamFactory<TEvents extends IEvDbEventPayload, TStreamType extends string, TViews extends Record<string, EvDbView<any>> = {}> {
|
|
58
|
-
private DynamicStreamClass: new (
|
|
59
|
-
streamType: string,
|
|
60
|
-
views: EvDbView<any>[],
|
|
61
|
-
streamStorageAdapter: IEvDbStorageStreamAdapter,
|
|
62
|
-
streamId: string,
|
|
63
|
-
lastStreamOffset: number,
|
|
64
|
-
) => StreamWithEventMethods<TEvents, TViews>;
|
|
65
|
-
|
|
66
|
-
constructor(private readonly config: EvDbStreamFactoryConfig<TEvents, TStreamType>) {
|
|
67
|
-
this.DynamicStreamClass = this.createDynamicStreamClass();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Creates a dynamic stream class with event-specific methods and view accessors
|
|
72
|
-
*/
|
|
73
|
-
private createDynamicStreamClass() {
|
|
74
|
-
const eventTypes = this.config.eventTypes;
|
|
75
|
-
const viewNames = this.config.viewNames;
|
|
76
|
-
|
|
77
|
-
const messagesProducer: EVDbMessagesProducer = (event: EvDbEvent, viewsState: Readonly<Record<string, unknown>>) => {
|
|
78
|
-
const eventType = eventTypes.find(e => e.eventName === event.eventType);
|
|
79
|
-
if (!eventType || !eventType.eventMessagesProducer) return [];
|
|
80
|
-
return eventType.eventMessagesProducer(event, viewsState);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
class DynamicStream extends EvDbStream {
|
|
84
|
-
public readonly views: Record<string, EvDbView<any>> = {};
|
|
85
|
-
|
|
86
|
-
constructor(
|
|
87
|
-
streamType: string,
|
|
88
|
-
views: EvDbView<any>[],
|
|
89
|
-
streamStorageAdapter: IEvDbStorageStreamAdapter,
|
|
90
|
-
streamId: string,
|
|
91
|
-
lastStreamOffset: number,
|
|
92
|
-
) {
|
|
93
|
-
super(streamType, views, streamStorageAdapter, streamId, lastStreamOffset, messagesProducer);
|
|
94
|
-
|
|
95
|
-
// Create view accessors
|
|
96
|
-
views.forEach((view, index) => {
|
|
97
|
-
const viewName = viewNames[index];
|
|
98
|
-
if (viewName) {
|
|
99
|
-
this.views[viewName] = view;
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Add dynamic methods for each event type
|
|
106
|
-
eventTypes.forEach(({ eventName }) => {
|
|
107
|
-
const methodName = `appendEvent${eventName}`;
|
|
108
|
-
(DynamicStream.prototype as any)[methodName] = async function (event: IEvDbEventPayload) {
|
|
109
|
-
return this.appendEvent(event);
|
|
110
|
-
};
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
return DynamicStream as any;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Creates a stream instance with all configured views and dynamic event methods
|
|
118
|
-
*/
|
|
119
|
-
public create(
|
|
120
|
-
streamId: string,
|
|
121
|
-
streamStorageAdapter: IEvDbStorageStreamAdapter,
|
|
122
|
-
snapshotStorageAdapter: IEvDbStorageSnapshotAdapter,
|
|
123
|
-
lastStreamOffset: number = 0
|
|
124
|
-
): StreamWithEventMethods<TEvents, TViews> {
|
|
125
|
-
const views = this.createViews(streamId, snapshotStorageAdapter);
|
|
126
|
-
|
|
127
|
-
return new this.DynamicStreamClass(
|
|
128
|
-
this.config.streamType,
|
|
129
|
-
views,
|
|
130
|
-
streamStorageAdapter,
|
|
131
|
-
streamId,
|
|
132
|
-
lastStreamOffset,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
private createViews(streamId: string, snapshotStorageAdapter: IEvDbStorageSnapshotAdapter): Array<EvDbView<any>> {
|
|
137
|
-
const views = this.config.viewFactories.map(factory =>
|
|
138
|
-
factory.create(streamId, snapshotStorageAdapter)
|
|
139
|
-
);
|
|
140
|
-
return views;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private getViews(streamId: string, snapshotStorageAdapter: IEvDbStorageSnapshotAdapter): Promise<EvDbView<any>>[] {
|
|
144
|
-
const getViewPromises = this.config.viewFactories.map(viewFactory =>
|
|
145
|
-
viewFactory.get(streamId, snapshotStorageAdapter)
|
|
146
|
-
);
|
|
147
|
-
return getViewPromises;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Fetches from storage a stream instance with all configured views and dynamic event methods
|
|
152
|
-
*/
|
|
153
|
-
public async get(
|
|
154
|
-
streamId: string,
|
|
155
|
-
streamStorageAdapter: IEvDbStorageStreamAdapter,
|
|
156
|
-
snapshotStorageAdapter: IEvDbStorageSnapshotAdapter
|
|
157
|
-
): Promise<StreamWithEventMethods<TEvents, TViews>> {
|
|
158
|
-
const streamAddress = new EvDbStreamAddress(this.config.streamType, streamId);
|
|
159
|
-
|
|
160
|
-
const views = await Promise.all(this.getViews(streamId, snapshotStorageAdapter));
|
|
161
|
-
|
|
162
|
-
if (!views.length) {
|
|
163
|
-
const lastStreamOffset = await streamStorageAdapter.getLastOffsetAsync(streamAddress);
|
|
164
|
-
const stream = this.create(streamId, streamStorageAdapter, snapshotStorageAdapter, lastStreamOffset);
|
|
165
|
-
return stream;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const lowestViewOffset = views.reduce((lowestOffset: number, currentView: EvDbView<any>) =>
|
|
169
|
-
Math.min(lowestOffset, currentView.storeOffset),
|
|
170
|
-
Number.MAX_VALUE
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
const streamCursor = new EvDbStreamCursor(streamAddress, lowestViewOffset + 1);
|
|
174
|
-
const events = await streamStorageAdapter.getEventsAsync(streamCursor);
|
|
175
|
-
|
|
176
|
-
let streamOffset = lowestViewOffset;
|
|
177
|
-
for await (const event of events) {
|
|
178
|
-
views.forEach(view => view.applyEvent(event));
|
|
179
|
-
streamOffset = event.streamCursor.offset;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return new this.DynamicStreamClass(
|
|
183
|
-
this.config.streamType,
|
|
184
|
-
views,
|
|
185
|
-
streamStorageAdapter,
|
|
186
|
-
streamId,
|
|
187
|
-
streamOffset
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
public getStreamType(): TStreamType {
|
|
192
|
-
return this.config.streamType;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Factory function to create a StreamFactory
|
|
198
|
-
*/
|
|
199
|
-
export function createEvDbStreamFactory<TEvents extends IEvDbEventPayload, TStreamType extends string, TViews extends Record<string, EvDbView<any>> = {}>(
|
|
200
|
-
config: EvDbStreamFactoryConfig<TEvents, TStreamType>
|
|
201
|
-
): EvDbStreamFactory<TEvents, TStreamType, TViews> {
|
|
202
|
-
return new EvDbStreamFactory(config);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Fluent builder for creating stream factories with inferred event types
|
|
207
|
-
*/
|
|
208
|
-
export class StreamFactoryBuilder<
|
|
209
|
-
TStreamType extends string,
|
|
210
|
-
TEvents extends IEvDbEventPayload = never,
|
|
211
|
-
TViews extends Record<string, EvDbView<any>> = {}
|
|
212
|
-
> {
|
|
213
|
-
private viewFactories: ViewFactory<any, TEvents>[] = [];
|
|
214
|
-
private eventTypes: EventTypeConfig<any>[] = [];
|
|
215
|
-
private viewNames: string[] = [];
|
|
216
|
-
|
|
217
|
-
constructor(private streamType: TStreamType) { }
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Register event type for dynamic method generation - infers the event name from class name
|
|
221
|
-
*/
|
|
222
|
-
withEventType<TEvent extends IEvDbEventPayload>(
|
|
223
|
-
eventClass: new (...args: any[]) => TEvent,
|
|
224
|
-
eventMessagesProducer?: EVDbMessagesProducer): StreamFactoryBuilder<
|
|
225
|
-
TStreamType,
|
|
226
|
-
TEvents | TEvent,
|
|
227
|
-
TViews
|
|
228
|
-
> {
|
|
229
|
-
// Use the class name as the event name
|
|
230
|
-
const eventName = eventClass.name;
|
|
231
|
-
|
|
232
|
-
this.eventTypes.push({ eventClass, eventName, eventMessagesProducer } as EventTypeConfig<TEvent>);
|
|
233
|
-
return this as any;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Add a view with inline handler definition
|
|
238
|
-
* This can only be called AFTER withEventType calls to ensure type safety
|
|
239
|
-
*/
|
|
240
|
-
public withView<TViewName extends string, TState>(
|
|
241
|
-
viewName: TViewName,
|
|
242
|
-
stateClass: new (...args: any[]) => TState,
|
|
243
|
-
handlers: EvDbStreamEventHandlersMap<TState, TEvents>
|
|
244
|
-
): StreamFactoryBuilder<TStreamType, TEvents, TViews & Record<TViewName, EvDbView<TState>>> {
|
|
245
|
-
// Create default state instance
|
|
246
|
-
const defaultState = new stateClass();
|
|
247
|
-
|
|
248
|
-
// Create the view factory
|
|
249
|
-
const viewFactory = createViewFactory<TState, TEvents>({
|
|
250
|
-
viewName,
|
|
251
|
-
streamType: this.streamType,
|
|
252
|
-
defaultState,
|
|
253
|
-
handlers
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
this.viewFactories.push(viewFactory);
|
|
257
|
-
this.viewNames.push(viewName);
|
|
258
|
-
return this as any;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Add a pre-created view factory (legacy support)
|
|
263
|
-
*/
|
|
264
|
-
public withViewFactory<TViewName extends string, TState>(
|
|
265
|
-
viewName: TViewName,
|
|
266
|
-
viewFactory: ViewFactory<TState, TEvents>
|
|
267
|
-
): StreamFactoryBuilder<TStreamType, TEvents, TViews & Record<TViewName, EvDbView<TState>>> {
|
|
268
|
-
this.viewFactories.push(viewFactory);
|
|
269
|
-
this.viewNames.push(viewName);
|
|
270
|
-
return this as any;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Build the stream factory
|
|
275
|
-
*/
|
|
276
|
-
public build() {
|
|
277
|
-
const factory = new EvDbStreamFactory({
|
|
278
|
-
streamType: this.streamType,
|
|
279
|
-
viewFactories: this.viewFactories,
|
|
280
|
-
eventTypes: this.eventTypes,
|
|
281
|
-
viewNames: this.viewNames
|
|
282
|
-
}) as EvDbStreamFactory<TEvents, TStreamType, TViews>;
|
|
283
|
-
|
|
284
|
-
// Return factory with type helper for stream type extraction
|
|
285
|
-
return Object.assign(factory, {
|
|
286
|
-
// This is a type-only property for extracting the stream type
|
|
287
|
-
StreamType: null as unknown as StreamWithEventMethods<TEvents, TViews>
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
}
|
package/src/EvDbView.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import IEvDbViewStore, { IEvDbViewStoreGeneric } from "@eventualize/types/IEvDbViewStore";
|
|
2
|
-
import EvDbViewAddress from "@eventualize/types/EvDbViewAddress";
|
|
3
|
-
import IEvDbStorageSnapshotAdapter from "@eventualize/types/IEvDbStorageSnapshotAdapter";
|
|
4
|
-
import EvDbEvent from "@eventualize/types/src/EvDbEvent";
|
|
5
|
-
import { EvDbStoredSnapshotData } from "@eventualize/types/EvDbStoredSnapshotData";
|
|
6
|
-
import { EvDbStoredSnapshotResult } from "@eventualize/types/EvDbStoredSnapshotResult";
|
|
7
|
-
import IEvDbEventMetadata from "@eventualize/types/IEvDbEventMetadata";
|
|
8
|
-
import IEvDbEventPayload from "@eventualize/types/IEvDbEventPayload";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export abstract class EvDbViewRaw implements IEvDbViewStore {
|
|
12
|
-
private _memoryOffset: number;
|
|
13
|
-
private _storeOffset: number;
|
|
14
|
-
private _storedAt: Date;
|
|
15
|
-
|
|
16
|
-
protected constructor(
|
|
17
|
-
private readonly _storageAdapter: IEvDbStorageSnapshotAdapter,
|
|
18
|
-
public readonly address: EvDbViewAddress,
|
|
19
|
-
snapshot: EvDbStoredSnapshotResult<any>
|
|
20
|
-
|
|
21
|
-
) {
|
|
22
|
-
const storeOffset = snapshot.offset ?? 0;
|
|
23
|
-
this._memoryOffset = storeOffset;
|
|
24
|
-
this._storeOffset = storeOffset;
|
|
25
|
-
|
|
26
|
-
this._storedAt = snapshot.storedAt ?? new Date();
|
|
27
|
-
}
|
|
28
|
-
public abstract getSnapshotData(): EvDbStoredSnapshotData;
|
|
29
|
-
|
|
30
|
-
get storedAt(): Date { return this._storedAt };
|
|
31
|
-
get storeOffset(): number { return this._storeOffset };
|
|
32
|
-
get memoryOffset(): number { return this._memoryOffset };
|
|
33
|
-
|
|
34
|
-
shouldStoreSnapshot(offsetGapFromLastSave: number, durationSinceLastSaveMs: number): boolean {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
applyEvent(e: EvDbEvent): void {
|
|
38
|
-
const offset = e.streamCursor.offset;
|
|
39
|
-
if (this.memoryOffset >= offset) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
this.onApplyEvent(e);
|
|
43
|
-
this._memoryOffset = offset;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async store(): Promise<void> {
|
|
47
|
-
const eventsSinceLatestSnapshot = this.memoryOffset - this.storeOffset;
|
|
48
|
-
const secondsSinceLatestSnapshot = new Date().getTime() - this.storedAt.getTime();
|
|
49
|
-
if (!this.shouldStoreSnapshot(eventsSinceLatestSnapshot, secondsSinceLatestSnapshot)) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
const snapshotData = this.getSnapshotData();
|
|
53
|
-
await this._storageAdapter.storeSnapshotAsync(snapshotData);
|
|
54
|
-
this._storeOffset = this._memoryOffset;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
protected abstract onApplyEvent(e: EvDbEvent): void;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export abstract class EvDbView<TState> extends EvDbViewRaw implements IEvDbViewStoreGeneric<TState> {
|
|
61
|
-
protected getDefaultState(): TState {
|
|
62
|
-
return this.defaultState;
|
|
63
|
-
};
|
|
64
|
-
protected _state: TState = this.getDefaultState();
|
|
65
|
-
get state(): TState { return this._state }
|
|
66
|
-
// public getState(): TState {
|
|
67
|
-
// return this._state;
|
|
68
|
-
// }
|
|
69
|
-
|
|
70
|
-
public constructor(
|
|
71
|
-
address: EvDbViewAddress,
|
|
72
|
-
storageAdapter: IEvDbStorageSnapshotAdapter,
|
|
73
|
-
snapshot: EvDbStoredSnapshotResult<TState>,
|
|
74
|
-
public readonly defaultState: TState
|
|
75
|
-
) {
|
|
76
|
-
super(storageAdapter, address, snapshot);
|
|
77
|
-
if (snapshot.offset === 0)
|
|
78
|
-
this._state = this.getDefaultState();
|
|
79
|
-
else
|
|
80
|
-
this._state = snapshot.state;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
getSnapshotData(): EvDbStoredSnapshotData {
|
|
86
|
-
return EvDbStoredSnapshotData.fromAddress(
|
|
87
|
-
this.address,
|
|
88
|
-
this.memoryOffset,
|
|
89
|
-
this.storeOffset,
|
|
90
|
-
this._state
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
public abstract handleOnApply(oldState: TState, event: IEvDbEventPayload, metadata: IEvDbEventMetadata): TState
|
|
95
|
-
|
|
96
|
-
protected onApplyEvent(e: EvDbEvent): void {
|
|
97
|
-
this._state = this.handleOnApply(this._state, e.payload, e);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
}
|
package/src/EvDbViewFactory.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { EvDbView } from './EvDbView.js';
|
|
2
|
-
import IEvDbEventPayload from "@eventualize/types/IEvDbEventPayload";
|
|
3
|
-
import IEvDbEventMetadata from '@eventualize/types/IEvDbEventMetadata';
|
|
4
|
-
import IEvDbStorageSnapshotAdapter from '@eventualize/types/IEvDbStorageSnapshotAdapter';
|
|
5
|
-
import EvDbViewAddress from '@eventualize/types/EvDbViewAddress';
|
|
6
|
-
import EvDbStreamAddress from '@eventualize/types/EvDbStreamAddress';
|
|
7
|
-
import { EvDbStoredSnapshotResult } from '@eventualize/types/EvDbStoredSnapshotResult';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Handler function type for applying an event to state
|
|
11
|
-
*/
|
|
12
|
-
export type EvDbViewEventHandler<TState, TEvent extends IEvDbEventPayload> = (
|
|
13
|
-
oldState: TState,
|
|
14
|
-
event: TEvent,
|
|
15
|
-
metadata: IEvDbEventMetadata
|
|
16
|
-
) => TState;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Map of event handlers - one handler per event type in the union
|
|
20
|
-
* Key is the payloadType string, value is the handler function
|
|
21
|
-
*/
|
|
22
|
-
export type EvDbStreamEventHandlersMap<TState, TEvents extends IEvDbEventPayload> = {
|
|
23
|
-
[K in TEvents['payloadType']]: EvDbViewEventHandler<
|
|
24
|
-
TState,
|
|
25
|
-
Extract<TEvents, { payloadType: K }>
|
|
26
|
-
>;
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Configuration for creating a view
|
|
30
|
-
*/
|
|
31
|
-
export interface ViewConfig<TState, TEvents extends IEvDbEventPayload> {
|
|
32
|
-
viewName: string;
|
|
33
|
-
streamType: string;
|
|
34
|
-
defaultState: TState;
|
|
35
|
-
handlers: Partial<EvDbStreamEventHandlersMap<TState, TEvents>>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Generic View class that uses the handlers map
|
|
40
|
-
*/
|
|
41
|
-
class GenericView<TState, TEvents extends IEvDbEventPayload> extends EvDbView<TState> {
|
|
42
|
-
|
|
43
|
-
constructor(
|
|
44
|
-
viewAddress: EvDbViewAddress,
|
|
45
|
-
storageAdapter: IEvDbStorageSnapshotAdapter,
|
|
46
|
-
snapshot: EvDbStoredSnapshotResult<TState>,
|
|
47
|
-
public readonly config: ViewConfig<TState, TEvents>
|
|
48
|
-
) {
|
|
49
|
-
super(viewAddress, storageAdapter, snapshot, config.defaultState);
|
|
50
|
-
this.config = config;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Dynamically applies events based on the handlers map
|
|
56
|
-
*/
|
|
57
|
-
public handleOnApply(oldState: TState, event: TEvents, metadata: IEvDbEventMetadata): TState {
|
|
58
|
-
const payloadType = event.payloadType as keyof typeof this.config.handlers;
|
|
59
|
-
const handler = this.config.handlers[payloadType];
|
|
60
|
-
|
|
61
|
-
if (!handler) {
|
|
62
|
-
// console.warn(`No handler found for event type: ${event.payloadType}`);
|
|
63
|
-
return oldState;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return handler(oldState, event as any, metadata);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* View Factory - creates view instances with the handlers map
|
|
72
|
-
*/
|
|
73
|
-
export class ViewFactory<TState, TEvents extends IEvDbEventPayload> {
|
|
74
|
-
constructor(private readonly config: ViewConfig<TState, TEvents>) { }
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Creates a view instance
|
|
78
|
-
*/
|
|
79
|
-
public create(
|
|
80
|
-
streamId: string,
|
|
81
|
-
storageAdapter: IEvDbStorageSnapshotAdapter
|
|
82
|
-
): EvDbView<TState> {
|
|
83
|
-
const streamAddress = new EvDbStreamAddress(this.config.streamType, streamId);
|
|
84
|
-
const viewAddress = new EvDbViewAddress(streamAddress, this.config.viewName);
|
|
85
|
-
|
|
86
|
-
return new GenericView<TState, TEvents>(
|
|
87
|
-
viewAddress,
|
|
88
|
-
storageAdapter,
|
|
89
|
-
EvDbStoredSnapshotResult.getEmptyState<TState>(),
|
|
90
|
-
this.config
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Get a view instance from event store
|
|
96
|
-
*/
|
|
97
|
-
public async get(
|
|
98
|
-
streamId: string,
|
|
99
|
-
storageAdapter: IEvDbStorageSnapshotAdapter
|
|
100
|
-
): Promise<EvDbView<TState>> {
|
|
101
|
-
const streamAddress = new EvDbStreamAddress(this.config.streamType, streamId);
|
|
102
|
-
const viewAddress = new EvDbViewAddress(streamAddress, this.config.viewName);
|
|
103
|
-
|
|
104
|
-
const snapshot = await storageAdapter.getSnapshotAsync(viewAddress)
|
|
105
|
-
|
|
106
|
-
return new GenericView<TState, TEvents>(
|
|
107
|
-
viewAddress,
|
|
108
|
-
storageAdapter,
|
|
109
|
-
snapshot,
|
|
110
|
-
this.config
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Factory function to create a ViewFactory
|
|
117
|
-
*/
|
|
118
|
-
export function createViewFactory<TState, TEvents extends IEvDbEventPayload>(
|
|
119
|
-
config: ViewConfig<TState, TEvents>
|
|
120
|
-
): ViewFactory<TState, TEvents> {
|
|
121
|
-
return new ViewFactory(config);
|
|
122
|
-
}
|
|
123
|
-
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"outDir": "./dist",
|
|
5
|
-
"rootDir": "./src",
|
|
6
|
-
"moduleResolution": "node",
|
|
7
|
-
"module": "es2020",
|
|
8
|
-
"target": "es2020",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
},
|
|
12
|
-
"references": [
|
|
13
|
-
{
|
|
14
|
-
"path": "../types"
|
|
15
|
-
}
|
|
16
|
-
],
|
|
17
|
-
"include": ["src/**/*"]
|
|
18
|
-
}
|