tapeworm 0.5.0 → 0.6.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/CHANGELOG.md +17 -0
- package/LICENSE +22 -22
- package/README.md +332 -332
- package/biome.json +58 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/src/dispatch/dispatch_service.d.ts +8 -0
- package/dist/src/dispatch/dispatch_service.d.ts.map +1 -0
- package/dist/src/dispatch/dispatch_service.js +9 -0
- package/dist/src/dispatch/dispatch_service.js.map +1 -0
- package/dist/src/event.d.ts +10 -0
- package/dist/src/event.d.ts.map +1 -0
- package/dist/src/event.js +12 -0
- package/dist/src/event.js.map +1 -0
- package/dist/src/event_store.d.ts +29 -0
- package/dist/src/event_store.d.ts.map +1 -0
- package/dist/src/event_store.js +71 -0
- package/dist/src/event_store.js.map +1 -0
- package/dist/src/event_store_partition.d.ts +28 -0
- package/dist/src/event_store_partition.d.ts.map +1 -0
- package/dist/src/event_store_partition.js +130 -0
- package/dist/src/event_store_partition.js.map +1 -0
- package/dist/src/event_stream.d.ts +32 -0
- package/dist/src/event_stream.d.ts.map +1 -0
- package/dist/src/event_stream.js +142 -0
- package/dist/src/event_stream.js.map +1 -0
- package/dist/src/persistence/commit.d.ts +4 -0
- package/dist/src/persistence/commit.d.ts.map +1 -0
- package/dist/src/persistence/commit.js +11 -0
- package/dist/src/persistence/commit.js.map +1 -0
- package/dist/src/persistence/concurrency_error.d.ts +6 -0
- package/dist/src/persistence/concurrency_error.d.ts.map +1 -0
- package/dist/src/persistence/concurrency_error.js +13 -0
- package/dist/src/persistence/concurrency_error.js.map +1 -0
- package/dist/src/persistence/duplicate_commit_error.d.ts +6 -0
- package/dist/src/persistence/duplicate_commit_error.d.ts.map +1 -0
- package/dist/src/persistence/duplicate_commit_error.js +13 -0
- package/dist/src/persistence/duplicate_commit_error.js.map +1 -0
- package/dist/src/persistence/inmemory/inmemory_partition.d.ts +23 -0
- package/dist/src/persistence/inmemory/inmemory_partition.d.ts.map +1 -0
- package/dist/src/persistence/inmemory/inmemory_partition.js +138 -0
- package/dist/src/persistence/inmemory/inmemory_partition.js.map +1 -0
- package/dist/src/persistence/inmemory/inmemory_persistence.d.ts +13 -0
- package/dist/src/persistence/inmemory/inmemory_persistence.d.ts.map +1 -0
- package/dist/src/persistence/inmemory/inmemory_persistence.js +26 -0
- package/dist/src/persistence/inmemory/inmemory_persistence.js.map +1 -0
- package/dist/src/persistence/persistence_partition.d.ts +12 -0
- package/dist/src/persistence/persistence_partition.d.ts.map +1 -0
- package/dist/src/persistence/persistence_partition.js +21 -0
- package/dist/src/persistence/persistence_partition.js.map +1 -0
- package/dist/src/persistence/persistence_provider_template.d.ts +10 -0
- package/dist/src/persistence/persistence_provider_template.d.ts.map +1 -0
- package/dist/src/persistence/persistence_provider_template.js +11 -0
- package/dist/src/persistence/persistence_provider_template.js.map +1 -0
- package/dist/src/replication/master_replication_service.d.ts +7 -0
- package/dist/src/replication/master_replication_service.d.ts.map +1 -0
- package/dist/src/replication/master_replication_service.js +9 -0
- package/dist/src/replication/master_replication_service.js.map +1 -0
- package/dist/src/types.d.ts +68 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/index.ts +21 -0
- package/package.json +44 -46
- package/scripts/postbuild.js +27 -0
- package/src/dispatch/dispatch_service.ts +14 -0
- package/src/event.ts +24 -0
- package/src/event_store.ts +82 -0
- package/src/event_store_partition.ts +270 -0
- package/src/event_stream.ts +184 -0
- package/src/persistence/commit.ts +24 -0
- package/src/persistence/concurrency_error.ts +16 -0
- package/src/persistence/duplicate_commit_error.ts +16 -0
- package/src/persistence/inmemory/inmemory_partition.ts +194 -0
- package/src/persistence/inmemory/inmemory_persistence.ts +35 -0
- package/src/persistence/persistence_partition.ts +34 -0
- package/src/persistence/persistence_provider_template.ts +20 -0
- package/src/replication/master_replication_service.ts +12 -0
- package/src/types.ts +128 -0
- package/test/event_partition.ts +151 -0
- package/test/event_store.ts +25 -0
- package/test/event_stream.ts +160 -0
- package/test/persistence/inmemory.ts +139 -0
- package/tsconfig.json +13 -0
- package/vitest.config.ts +13 -0
- package/.eslintrc +0 -4
- package/.travis.yml +0 -4
- package/checkstyle-result.xml +0 -1
- package/index.js +0 -1
- package/lib/dispatch/dispatch_service.js +0 -7
- package/lib/event.js +0 -10
- package/lib/event_store.js +0 -53
- package/lib/event_store_partition.js +0 -148
- package/lib/event_stream.js +0 -150
- package/lib/persistence/commit.js +0 -12
- package/lib/persistence/concurrency_error.js +0 -10
- package/lib/persistence/duplicate_commit_error.js +0 -10
- package/lib/persistence/inmemory/inmemory_partition.js +0 -146
- package/lib/persistence/inmemory/inmemory_persistence.js +0 -25
- package/lib/persistence/persistence_partition.js +0 -23
- package/lib/persistence/persistence_provider_template.js +0 -17
- package/lib/replication/master_replication_service.js +0 -7
- package/test/event_partition.js +0 -232
- package/test/event_store.js +0 -36
- package/test/event_stream.js +0 -214
- package/test/persistence/inmemory.js +0 -162
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import Promise from "bluebird";
|
|
2
|
+
import _ from "lodash";
|
|
3
|
+
import type { ICommit, ISnapshot, NodeCallback } from "../../types";
|
|
4
|
+
import ConcurrencyError from "../concurrency_error";
|
|
5
|
+
import DuplicateCommitError from "../duplicate_commit_error";
|
|
6
|
+
|
|
7
|
+
interface IInMemoryPartition {
|
|
8
|
+
_commits: ICommit[];
|
|
9
|
+
_streamIndex: Record<string, ICommit[]>;
|
|
10
|
+
_commitIds: string[];
|
|
11
|
+
_commitConcurrencyCheck: string[];
|
|
12
|
+
_snapshots: Record<string, ISnapshot>;
|
|
13
|
+
_promisify<T>(value: T, callback?: NodeCallback<T>): Promise<T>;
|
|
14
|
+
truncateStreamFrom(streamId: string, commitSequence: number, remove?: boolean | NodeCallback<void>, callback?: NodeCallback<void>): Promise<void>;
|
|
15
|
+
applyCommitHeader(commitId: string, header: Record<string, unknown>, callback?: NodeCallback<ICommit>): Promise<ICommit>;
|
|
16
|
+
append(commit: ICommit, callback?: NodeCallback<ICommit>): Promise<ICommit>;
|
|
17
|
+
storeSnapshot(streamId: string, snapshot: Record<string, unknown> | undefined, version: number, callback?: NodeCallback<ISnapshot>): Promise<ISnapshot>;
|
|
18
|
+
loadSnapshot(streamId: string, callback?: NodeCallback<ISnapshot | undefined>): Promise<ISnapshot | undefined>;
|
|
19
|
+
markAsDispatched(commit: ICommit, callback?: NodeCallback<ICommit>): Promise<ICommit>;
|
|
20
|
+
getUndispatched(callback?: NodeCallback<ICommit[]>): Promise<ICommit[]>;
|
|
21
|
+
queryAll(callback?: NodeCallback<ICommit[]>): Promise<ICommit[]>;
|
|
22
|
+
getLatestCommit(streamId: string, callback?: NodeCallback<ICommit | undefined>): Promise<ICommit | undefined>;
|
|
23
|
+
queryStream(streamId: string, fromEventSequence?: number | NodeCallback<ICommit[]>, callback?: NodeCallback<ICommit[]>): Promise<ICommit[]>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var InMemoryPartition = function (this: IInMemoryPartition) {
|
|
27
|
+
this._commits = [];
|
|
28
|
+
this._streamIndex = {};
|
|
29
|
+
this._commitIds = [];
|
|
30
|
+
this._commitConcurrencyCheck = [];
|
|
31
|
+
this._snapshots = {};
|
|
32
|
+
} as unknown as new () => IInMemoryPartition;
|
|
33
|
+
|
|
34
|
+
function getConcurrencyKey(commit: ICommit): string {
|
|
35
|
+
return commit.streamId + "-" + commit.commitSequence;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
InMemoryPartition.prototype._promisify = function <T>(this: IInMemoryPartition, value: T, callback?: NodeCallback<T>): Promise<T> {
|
|
39
|
+
return Promise.resolve(value).nodeify(callback);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
InMemoryPartition.prototype.truncateStreamFrom = function (
|
|
43
|
+
this: IInMemoryPartition,
|
|
44
|
+
streamId: string,
|
|
45
|
+
commitSequence: number,
|
|
46
|
+
remove?: boolean | NodeCallback<void>,
|
|
47
|
+
callback?: NodeCallback<void>
|
|
48
|
+
) {
|
|
49
|
+
if (typeof remove === "function") {
|
|
50
|
+
callback = remove;
|
|
51
|
+
}
|
|
52
|
+
var self = this;
|
|
53
|
+
var commits = Array.from(this._streamIndex[streamId]);
|
|
54
|
+
for (var i = 0; i < commits.length; i++) {
|
|
55
|
+
var commit = commits[i];
|
|
56
|
+
|
|
57
|
+
if (commit.commitSequence >= commitSequence) {
|
|
58
|
+
//remove from commitId
|
|
59
|
+
self._commitIds = _.without(self._commitIds, commit.id);
|
|
60
|
+
self._commitConcurrencyCheck = _.without(self._commitConcurrencyCheck, getConcurrencyKey(commit));
|
|
61
|
+
self._commits = _.without(self._commits, commit);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
commits = commits.slice(0, Math.max(commitSequence, 0));
|
|
65
|
+
this._streamIndex[streamId] = commits;
|
|
66
|
+
return this._promisify(undefined as unknown as undefined, callback);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
InMemoryPartition.prototype.applyCommitHeader = function (
|
|
70
|
+
this: IInMemoryPartition,
|
|
71
|
+
commitId: string,
|
|
72
|
+
header: Record<string, unknown>,
|
|
73
|
+
callback?: NodeCallback<ICommit>
|
|
74
|
+
) {
|
|
75
|
+
var commit = _.find(this._commits, { id: commitId });
|
|
76
|
+
if (commit) {
|
|
77
|
+
_.assign(commit, header);
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error("Trying to apply header to missing commit: " + commitId);
|
|
80
|
+
}
|
|
81
|
+
return this._promisify(commit, callback);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
InMemoryPartition.prototype.append = function (this: IInMemoryPartition, commit: ICommit, callback?: NodeCallback<ICommit>) {
|
|
85
|
+
commit.isDispatched = false;
|
|
86
|
+
//check for duplicates
|
|
87
|
+
if (_.includes(this._commitIds, commit.id)) {
|
|
88
|
+
throw new DuplicateCommitError("Duplicate commit of " + commit.id);
|
|
89
|
+
}
|
|
90
|
+
var concurrencyKey = getConcurrencyKey(commit);
|
|
91
|
+
if (_.includes(this._commitConcurrencyCheck, concurrencyKey)) {
|
|
92
|
+
throw new ConcurrencyError("Concurrency error on stream " + commit.streamId);
|
|
93
|
+
}
|
|
94
|
+
//check concurrency
|
|
95
|
+
this._commits.push(commit);
|
|
96
|
+
this._commitIds.push(commit.id);
|
|
97
|
+
this._commitConcurrencyCheck.push(concurrencyKey);
|
|
98
|
+
var index = this._streamIndex[commit.streamId];
|
|
99
|
+
if (!index) {
|
|
100
|
+
index = this._streamIndex[commit.streamId] = [];
|
|
101
|
+
}
|
|
102
|
+
index.push(commit);
|
|
103
|
+
return this._promisify(commit, callback);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
InMemoryPartition.prototype.storeSnapshot = function (
|
|
107
|
+
this: IInMemoryPartition,
|
|
108
|
+
streamId: string,
|
|
109
|
+
snapshot: Record<string, unknown> | undefined,
|
|
110
|
+
version: number,
|
|
111
|
+
callback?: NodeCallback<ISnapshot>
|
|
112
|
+
) {
|
|
113
|
+
return this._promisify(
|
|
114
|
+
(this._snapshots[streamId] = {
|
|
115
|
+
id: streamId,
|
|
116
|
+
version: version,
|
|
117
|
+
snapshot: snapshot,
|
|
118
|
+
}),
|
|
119
|
+
callback
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Loads the latest snapshot
|
|
124
|
+
InMemoryPartition.prototype.loadSnapshot = function (this: IInMemoryPartition, streamId: string, callback?: NodeCallback<ISnapshot | undefined>) {
|
|
125
|
+
return this._promisify(this._snapshots[streamId], callback);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
InMemoryPartition.prototype.markAsDispatched = function (this: IInMemoryPartition, commit: ICommit, callback?: NodeCallback<ICommit>) {
|
|
129
|
+
commit.isDispatched = true;
|
|
130
|
+
return this._promisify(commit, callback);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
InMemoryPartition.prototype.getUndispatched = function (this: IInMemoryPartition, callback?: NodeCallback<ICommit[]>) {
|
|
134
|
+
var commits = this._commits;
|
|
135
|
+
var undispatched: ICommit[] = [];
|
|
136
|
+
for (var i = 0; i < commits.length; i++) {
|
|
137
|
+
if (!commits[i].isDispatched) {
|
|
138
|
+
undispatched.push(commits[i]);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return this._promisify(undispatched, callback);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
InMemoryPartition.prototype.queryAll = function (this: IInMemoryPartition, callback?: NodeCallback<ICommit[]>) {
|
|
145
|
+
return this._promisify(this._commits.slice(), callback);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
InMemoryPartition.prototype.getLatestCommit = function (this: IInMemoryPartition, streamId: string, callback?: NodeCallback<ICommit | undefined>) {
|
|
149
|
+
var result = this._streamIndex[streamId];
|
|
150
|
+
if (result) {
|
|
151
|
+
return this._promisify(result.slice().pop(), callback);
|
|
152
|
+
}
|
|
153
|
+
return this._promisify(undefined as ICommit | undefined, callback);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
InMemoryPartition.prototype.queryStream = function (
|
|
157
|
+
this: IInMemoryPartition,
|
|
158
|
+
streamId: string,
|
|
159
|
+
fromEventSequence?: number | NodeCallback<ICommit[]>,
|
|
160
|
+
callback?: NodeCallback<ICommit[]>
|
|
161
|
+
) {
|
|
162
|
+
if (_.isFunction(fromEventSequence)) {
|
|
163
|
+
callback = fromEventSequence as NodeCallback<ICommit[]>;
|
|
164
|
+
fromEventSequence = 0;
|
|
165
|
+
}
|
|
166
|
+
var result = this._streamIndex[streamId];
|
|
167
|
+
|
|
168
|
+
if (result) {
|
|
169
|
+
var sliced = result.slice();
|
|
170
|
+
if ((fromEventSequence as number) > 0) {
|
|
171
|
+
var startCommitId = 0;
|
|
172
|
+
var foundEvents = 0;
|
|
173
|
+
for (var i = 0; i < sliced.length; i++) {
|
|
174
|
+
foundEvents += sliced[0].events.length;
|
|
175
|
+
startCommitId++;
|
|
176
|
+
if (foundEvents >= (fromEventSequence as number)) {
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
var tooMany = foundEvents - (fromEventSequence as number);
|
|
181
|
+
|
|
182
|
+
sliced = sliced.slice(startCommitId - (tooMany > 0 ? 1 : 0));
|
|
183
|
+
if (tooMany > 0) {
|
|
184
|
+
sliced[0] = _.clone(sliced[0]); // avoid modifying reference
|
|
185
|
+
sliced[0].events = sliced[0].events.slice(sliced[0].events.length - tooMany);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return this._promisify(sliced, callback);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return this._promisify(result as ICommit[], callback);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export default InMemoryPartition;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Promise from "bluebird";
|
|
2
|
+
import type { NodeCallback } from "../../types";
|
|
3
|
+
import Partition from "./inmemory_partition";
|
|
4
|
+
|
|
5
|
+
type InMemoryPartitionInstance = InstanceType<typeof Partition>;
|
|
6
|
+
|
|
7
|
+
interface IInMemoryPersistence {
|
|
8
|
+
_partitions: Record<string, InMemoryPartitionInstance>;
|
|
9
|
+
_promisify<T>(value: T, callback?: NodeCallback<T>): Promise<T>;
|
|
10
|
+
openPartition(partitionId?: string, callback?: NodeCallback<InMemoryPartitionInstance>): Promise<InMemoryPartitionInstance>;
|
|
11
|
+
_getPartition(partitionId?: string): InMemoryPartitionInstance;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
var InMemoryPersistence = function InMemoryPersistance(this: IInMemoryPersistence) {
|
|
15
|
+
this._partitions = {};
|
|
16
|
+
} as unknown as new () => IInMemoryPersistence;
|
|
17
|
+
|
|
18
|
+
InMemoryPersistence.prototype._promisify = function <T>(this: IInMemoryPersistence, value: T, callback?: NodeCallback<T>): Promise<T> {
|
|
19
|
+
return Promise.resolve(value).nodeify(callback);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
InMemoryPersistence.prototype.openPartition = function (this: IInMemoryPersistence, partitionId?: string, callback?: NodeCallback<InMemoryPartitionInstance>) {
|
|
23
|
+
return this._promisify(this._getPartition(partitionId), callback);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
InMemoryPersistence.prototype._getPartition = function (this: IInMemoryPersistence, partitionId?: string): InMemoryPartitionInstance {
|
|
27
|
+
var currentPartitionId = partitionId || "master";
|
|
28
|
+
var partition = this._partitions[currentPartitionId];
|
|
29
|
+
if (partition == null) {
|
|
30
|
+
partition = this._partitions[currentPartitionId] = new Partition();
|
|
31
|
+
}
|
|
32
|
+
return partition;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default InMemoryPersistence;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ICommit, NodeCallback } from "../types";
|
|
2
|
+
|
|
3
|
+
interface IPersistencePartitionTemplate {
|
|
4
|
+
append(_commit: ICommit, _callback?: NodeCallback<ICommit>): void;
|
|
5
|
+
getUndispatched(_callback?: NodeCallback<ICommit[]>): void;
|
|
6
|
+
markAsDispatched(_commit: ICommit, _callback?: NodeCallback<void>): void;
|
|
7
|
+
queryAll(_callback?: NodeCallback<ICommit[]>): void;
|
|
8
|
+
queryStream(_streamId: string, _callback?: NodeCallback<ICommit[]>): void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Abstract template — subclasses must override all methods. */
|
|
12
|
+
var PersistencePartition = function () {} as unknown as new () => IPersistencePartitionTemplate;
|
|
13
|
+
|
|
14
|
+
PersistencePartition.prototype.append = function (_commit: ICommit, _callback?: NodeCallback<ICommit>) {
|
|
15
|
+
//add commit
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
PersistencePartition.prototype.getUndispatched = function (_callback?: NodeCallback<ICommit[]>) {
|
|
19
|
+
//return events which are not dispatched
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
PersistencePartition.prototype.markAsDispatched = function (_commit: ICommit, _callback?: NodeCallback<void>) {
|
|
23
|
+
//return events which are not dispatched
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
PersistencePartition.prototype.queryAll = function (_callback?: NodeCallback<ICommit[]>) {
|
|
27
|
+
//return Query
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
PersistencePartition.prototype.queryStream = function (_streamId: string, _callback?: NodeCallback<ICommit[]>) {
|
|
31
|
+
//return Query;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default PersistencePartition;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { NodeCallback } from "../types";
|
|
2
|
+
|
|
3
|
+
interface IPersistenceProviderTemplate {
|
|
4
|
+
openPartition(_partitionId?: string, _callback?: NodeCallback<unknown>): void;
|
|
5
|
+
deletePartition(_partitionId?: string, _callback?: NodeCallback<void>): void;
|
|
6
|
+
close(_callback?: NodeCallback<void>): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Abstract template — subclasses must override all methods. */
|
|
10
|
+
var PersistenceProvider = function () {} as unknown as new () => IPersistenceProviderTemplate;
|
|
11
|
+
|
|
12
|
+
PersistenceProvider.prototype.openPartition = function (_partitionId?: string, _callback?: NodeCallback<unknown>) {
|
|
13
|
+
// body...
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
PersistenceProvider.prototype.deletePartition = function (_partitionId?: string, _callback?: NodeCallback<void>) {};
|
|
17
|
+
|
|
18
|
+
PersistenceProvider.prototype.close = function (_callback?: NodeCallback<void>) {};
|
|
19
|
+
|
|
20
|
+
export default PersistenceProvider;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface IMasterReplicationService {
|
|
2
|
+
handle(_first_argument: unknown): void;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
/** Abstract template — subclasses must override handle. */
|
|
6
|
+
var MasterReplicatonService = function () {} as unknown as new () => IMasterReplicationService;
|
|
7
|
+
|
|
8
|
+
MasterReplicatonService.prototype.handle = function (_first_argument: unknown) {
|
|
9
|
+
// body...
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default MasterReplicatonService;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type Bluebird from "bluebird";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Node-style error-first callback.
|
|
5
|
+
*/
|
|
6
|
+
export type NodeCallback<T> = (err: Error | null, result?: T) => void;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimum event contract that tapeworm requires.
|
|
10
|
+
* Tapeworm reads `type` for delete detection and manages `version` internally.
|
|
11
|
+
* All other fields (payload, aggregateId, etc.) pass through untouched.
|
|
12
|
+
*/
|
|
13
|
+
export interface IBaseEvent {
|
|
14
|
+
id: string;
|
|
15
|
+
type: string;
|
|
16
|
+
version?: number;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A commit groups one or more events for atomic persistence.
|
|
22
|
+
*/
|
|
23
|
+
export interface ICommit<TEvent extends IBaseEvent = IBaseEvent> {
|
|
24
|
+
id: string;
|
|
25
|
+
partitionId: string;
|
|
26
|
+
streamId: string;
|
|
27
|
+
commitSequence: number;
|
|
28
|
+
events: TEvent[];
|
|
29
|
+
isDispatched?: boolean;
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Snapshot of aggregate state at a point in time.
|
|
35
|
+
*/
|
|
36
|
+
export interface ISnapshot {
|
|
37
|
+
id: string;
|
|
38
|
+
version: number;
|
|
39
|
+
snapshot?: Record<string, unknown>;
|
|
40
|
+
storedDateTime?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The persistence partition interface that all store implementations must satisfy.
|
|
45
|
+
* Methods marked optional are only implemented by some stores.
|
|
46
|
+
*/
|
|
47
|
+
export interface IPersistencePartition<TEvent extends IBaseEvent = IBaseEvent> {
|
|
48
|
+
append(
|
|
49
|
+
commit: ICommit<TEvent>,
|
|
50
|
+
callback?: NodeCallback<ICommit<TEvent>>,
|
|
51
|
+
): Bluebird<ICommit<TEvent>>;
|
|
52
|
+
queryAll(
|
|
53
|
+
callback?: NodeCallback<ICommit<TEvent>[]>,
|
|
54
|
+
): Bluebird<ICommit<TEvent>[]>;
|
|
55
|
+
queryStream(
|
|
56
|
+
streamId: string,
|
|
57
|
+
fromEventSequence?: number | NodeCallback<ICommit<TEvent>[]>,
|
|
58
|
+
callback?: NodeCallback<ICommit<TEvent>[]>,
|
|
59
|
+
): Bluebird<ICommit<TEvent>[]>;
|
|
60
|
+
getUndispatched(
|
|
61
|
+
callback?: NodeCallback<ICommit<TEvent>[]>,
|
|
62
|
+
): Bluebird<ICommit<TEvent>[]>;
|
|
63
|
+
markAsDispatched(
|
|
64
|
+
commit: ICommit<TEvent>,
|
|
65
|
+
callback?: NodeCallback<ICommit<TEvent>>,
|
|
66
|
+
): Bluebird<ICommit<TEvent>>;
|
|
67
|
+
|
|
68
|
+
// Optional methods — implemented by some stores
|
|
69
|
+
storeSnapshot?(
|
|
70
|
+
streamId: string,
|
|
71
|
+
snapshot: Record<string, unknown> | undefined,
|
|
72
|
+
version: number,
|
|
73
|
+
callback?: NodeCallback<ISnapshot>,
|
|
74
|
+
): Bluebird<ISnapshot>;
|
|
75
|
+
loadSnapshot?(
|
|
76
|
+
streamId: string,
|
|
77
|
+
callback?: NodeCallback<ISnapshot | undefined>,
|
|
78
|
+
): Bluebird<ISnapshot | undefined>;
|
|
79
|
+
removeSnapshot?(
|
|
80
|
+
streamId: string,
|
|
81
|
+
callback?: NodeCallback<void>,
|
|
82
|
+
): Bluebird<void>;
|
|
83
|
+
removeSnapshots?(
|
|
84
|
+
streamIds: string[],
|
|
85
|
+
callback?: NodeCallback<void>,
|
|
86
|
+
): Bluebird<void>;
|
|
87
|
+
getLatestCommit?(
|
|
88
|
+
streamId: string,
|
|
89
|
+
callback?: NodeCallback<ICommit<TEvent> | undefined>,
|
|
90
|
+
): Bluebird<ICommit<TEvent> | undefined>;
|
|
91
|
+
truncateStreamFrom?(
|
|
92
|
+
streamId: string,
|
|
93
|
+
commitSequence: number,
|
|
94
|
+
remove?: boolean | NodeCallback<void>,
|
|
95
|
+
callback?: NodeCallback<void>,
|
|
96
|
+
): Bluebird<void>;
|
|
97
|
+
applyCommitHeader?(
|
|
98
|
+
commitId: string,
|
|
99
|
+
header: Record<string, unknown>,
|
|
100
|
+
callback?: NodeCallback<ICommit<TEvent>>,
|
|
101
|
+
): Bluebird<ICommit<TEvent>>;
|
|
102
|
+
queryStreamWithSnapshot?(
|
|
103
|
+
streamId: string,
|
|
104
|
+
callback?: NodeCallback<unknown>,
|
|
105
|
+
): Bluebird<unknown>;
|
|
106
|
+
querySnapshotsByMaxDateTime?(
|
|
107
|
+
dateTime: string,
|
|
108
|
+
callback?: NodeCallback<ISnapshot[]>,
|
|
109
|
+
): Bluebird<ISnapshot[]>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Top-level persistence provider — manages partitions.
|
|
114
|
+
*/
|
|
115
|
+
export interface IPersistenceProvider<TEvent extends IBaseEvent = IBaseEvent> {
|
|
116
|
+
openPartition(
|
|
117
|
+
partitionId?: string,
|
|
118
|
+
callback?: NodeCallback<IPersistencePartition<TEvent>>,
|
|
119
|
+
): Bluebird<IPersistencePartition<TEvent>>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Dispatch handler for publishing committed events.
|
|
124
|
+
*/
|
|
125
|
+
export type DispatchHandler<TEvent extends IBaseEvent = IBaseEvent> = (
|
|
126
|
+
commit: ICommit<TEvent>,
|
|
127
|
+
done?: () => void,
|
|
128
|
+
) => void;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { v4 as uuid } from "uuid";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import EventStore, { Commit } from "..";
|
|
4
|
+
|
|
5
|
+
describe("Partition", () => {
|
|
6
|
+
describe("#append", () => {
|
|
7
|
+
it("should return commit if added", async (done) => {
|
|
8
|
+
const es = new EventStore();
|
|
9
|
+
const partition = await es.openPartition("location");
|
|
10
|
+
const c = await partition.append(new Commit("1", "location", "1", 0, []));
|
|
11
|
+
expect(c[0].events).toHaveLength(0);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("should return after all commits are persisted", async () => {
|
|
15
|
+
const es = new EventStore();
|
|
16
|
+
const partition = await es.openPartition("location");
|
|
17
|
+
const c = await partition.append([new Commit("1", "location", "1", 0, []), new Commit("2", "location", "1", 1, [])]);
|
|
18
|
+
expect(c).toHaveLength(2);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("#_truncateStreamFrom", () => {
|
|
23
|
+
it("should remove all commits ", async () => {
|
|
24
|
+
const es = new EventStore();
|
|
25
|
+
const partition = await es.openPartition("location");
|
|
26
|
+
await partition.append([new Commit("1", "location", "1", 0, []), new Commit("2", "location", "1", 1, [])]);
|
|
27
|
+
await partition._truncateStreamFrom("1", 0);
|
|
28
|
+
const stream = await partition.openStream("1");
|
|
29
|
+
expect(stream.getVersion()).toBe(-1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should remove all commits after commitSequence", async () => {
|
|
33
|
+
const es = new EventStore();
|
|
34
|
+
const partition = await es.openPartition("location");
|
|
35
|
+
await partition.append([new Commit("1", "location", "1", 0, [{}]), new Commit("2", "location", "1", 1, [{}])]);
|
|
36
|
+
await partition._truncateStreamFrom("1", 1);
|
|
37
|
+
const stream = await partition.openStream("1");
|
|
38
|
+
expect(stream.getVersion()).toBe(1);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("#_applyCommitHeader", () => {
|
|
43
|
+
it("should add to commit ", async () => {
|
|
44
|
+
const es = new EventStore();
|
|
45
|
+
const partition = await es.openPartition("location");
|
|
46
|
+
let commit = new Commit("1", "location", "1", 0, []);
|
|
47
|
+
await partition.append([commit, new Commit("2", "location", "1", 1, [])]);
|
|
48
|
+
commit = await partition._applyCommitHeader(commit.id, {
|
|
49
|
+
authorative: true,
|
|
50
|
+
});
|
|
51
|
+
expect(commit.authorative).toBeTruthy();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("should throw if commit id is unknown", async () => {
|
|
55
|
+
const es = new EventStore();
|
|
56
|
+
const partition = await es.openPartition("location");
|
|
57
|
+
const commit = new Commit("1", "location", "1", 0, []);
|
|
58
|
+
await partition.append([commit, new Commit("2", "location", "1", 1, [])]);
|
|
59
|
+
|
|
60
|
+
expect(() => partition._applyCommitHeader("ID MISSING", { authorative: true })).toThrow("Trying to apply header to missing commit: ID MISSING");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("#queryStreamWithSnapshot", () => {
|
|
65
|
+
it("queryStreamWithSnapshot should return snapshot and missing commits", async () => {
|
|
66
|
+
const es = new EventStore();
|
|
67
|
+
const streamId = "1";
|
|
68
|
+
const stream = await es.openPartition("location").call("openStream", streamId);
|
|
69
|
+
|
|
70
|
+
stream.append({ event: "123" });
|
|
71
|
+
stream.append({ event: "999" });
|
|
72
|
+
await stream.commit(uuid());
|
|
73
|
+
|
|
74
|
+
stream.append({ event: "666" });
|
|
75
|
+
stream.append({ event: "777" });
|
|
76
|
+
await stream.commit(uuid());
|
|
77
|
+
|
|
78
|
+
const part = await es.openPartition("location");
|
|
79
|
+
part.storeSnapshot(streamId, { test: "snapshot" }, 2);
|
|
80
|
+
const res = await part.queryStreamWithSnapshot(streamId);
|
|
81
|
+
|
|
82
|
+
expect(res.snapshot.version).toBe(2);
|
|
83
|
+
expect(res.commits).toHaveLength(1);
|
|
84
|
+
expect(res.commits[0].events).toHaveLength(2);
|
|
85
|
+
expect(res.commits[0].events[0].version).toBe(2);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("queryStreamWithSnapshot should return snapshot and no commit if up to date", async () => {
|
|
89
|
+
const es = new EventStore();
|
|
90
|
+
const streamId = "1";
|
|
91
|
+
const stream = await es.openPartition("location").call("openStream", streamId);
|
|
92
|
+
|
|
93
|
+
stream.append({ event: "123" });
|
|
94
|
+
stream.append({ event: "999" });
|
|
95
|
+
await stream.commit(uuid());
|
|
96
|
+
|
|
97
|
+
const part = await es.openPartition("location");
|
|
98
|
+
part.storeSnapshot(streamId, { test: "snapshot" }, 2);
|
|
99
|
+
|
|
100
|
+
const res = await part.queryStreamWithSnapshot(streamId);
|
|
101
|
+
expect(res.snapshot.version).toBe(2);
|
|
102
|
+
expect(res.commits).toHaveLength(0);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("#delete", () => {
|
|
107
|
+
it("should delete stream and all commits", async () => {
|
|
108
|
+
let didIGetaDeleteEvent = false;
|
|
109
|
+
|
|
110
|
+
const es = new EventStore(null, (commit) => {
|
|
111
|
+
if (commit.events[0].type === "$stream.deleted.event") {
|
|
112
|
+
didIGetaDeleteEvent = true;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const partition = await es.openPartition("location");
|
|
117
|
+
await partition.append([new Commit("1", "location", "1", 0, [{ type: "dummy.event" }]), new Commit("2", "location", "1", 1, [{ type: "dummy2.event" }])]);
|
|
118
|
+
await partition.delete("1", {
|
|
119
|
+
some: "header-value",
|
|
120
|
+
payload: { test: true },
|
|
121
|
+
type: "fail",
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await expect(partition.openStream("1")).rejects.toThrow("Stream is deleted");
|
|
125
|
+
expect(didIGetaDeleteEvent).toBeTruthy();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should delete and placeholder commit should have an id", async () => {
|
|
129
|
+
let didIGetaDeleteEvent = false;
|
|
130
|
+
|
|
131
|
+
const es = new EventStore(null, (commit) => {
|
|
132
|
+
if (commit.events[0].type === "$stream.deleted.event" && commit.id) {
|
|
133
|
+
didIGetaDeleteEvent = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
expect(commit.id).not.toBeNull();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const partition = await es.openPartition("location");
|
|
140
|
+
await partition.append([new Commit("1", "location", "1", 0, [{ type: "dummy.event" }]), new Commit("2", "location", "1", 1, [{ type: "dummy2.event" }])]);
|
|
141
|
+
await partition.delete("1", {
|
|
142
|
+
some: "header-value",
|
|
143
|
+
payload: { test: true },
|
|
144
|
+
type: "fail",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await expect(partition.openStream("1")).rejects.toThrow("Stream is deleted");
|
|
148
|
+
expect(didIGetaDeleteEvent).toBeTruthy();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Promise from "bluebird";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import EventStore from "..";
|
|
4
|
+
|
|
5
|
+
describe("event_store", () => {
|
|
6
|
+
describe("#openPartition", () => {
|
|
7
|
+
it("should return non null partition when using new id", async () => {
|
|
8
|
+
const es = new EventStore();
|
|
9
|
+
const partition = await es.openPartition("location");
|
|
10
|
+
expect(partition).not.toBeNull();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should return same instance when called multiple times", async () => {
|
|
14
|
+
const es = new EventStore();
|
|
15
|
+
const [p1, p2] = await Promise.all([es.openPartition("location"), es.openPartition("location")]);
|
|
16
|
+
expect(p1).toBe(p2);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should return different instances for different partitionIds", async () => {
|
|
20
|
+
const es = new EventStore();
|
|
21
|
+
const [p1, p2] = await Promise.all([es.openPartition("location"), es.openPartition("location2")]);
|
|
22
|
+
expect(p1).not.toBe(p2);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|