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,27 @@
|
|
|
1
|
+
// Append CJS backward-compatibility shim to compiled ESM output.
|
|
2
|
+
//
|
|
3
|
+
// Without this, `require('tapeworm')` returns `{ default: EventStore, ... }`
|
|
4
|
+
// instead of the EventStore constructor directly. The shim restores the
|
|
5
|
+
// legacy behavior: `const EventStore = require('tapeworm')` works, and
|
|
6
|
+
// named exports like `require('tapeworm').Commit` remain accessible because
|
|
7
|
+
// EventStore already carries them as own properties (set in event_store.ts).
|
|
8
|
+
//
|
|
9
|
+
// This runs as a postbuild step so the source stays pure ESM (no
|
|
10
|
+
// `module.exports` that would conflict with Vite/vitest).
|
|
11
|
+
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
|
|
15
|
+
const file = path.join(__dirname, "..", "dist", "index.js");
|
|
16
|
+
const src = fs.readFileSync(file, "utf8");
|
|
17
|
+
|
|
18
|
+
const shim = [
|
|
19
|
+
"",
|
|
20
|
+
"// CJS backward compat — added by scripts/postbuild.js",
|
|
21
|
+
"module.exports = exports.default;",
|
|
22
|
+
"Object.assign(module.exports, exports);",
|
|
23
|
+
"module.exports.default = exports.default;",
|
|
24
|
+
"",
|
|
25
|
+
].join("\n");
|
|
26
|
+
|
|
27
|
+
fs.writeFileSync(file, src + shim);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ICommit, NodeCallback } from "../types";
|
|
2
|
+
|
|
3
|
+
interface IDispatchService {
|
|
4
|
+
dispatch(_commit: ICommit, _callback?: NodeCallback<void>): void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** Abstract template — subclasses must override dispatch. */
|
|
8
|
+
var DispatchService = function () {} as unknown as new () => IDispatchService;
|
|
9
|
+
|
|
10
|
+
DispatchService.prototype.dispatch = function (_commit: ICommit, _callback?: NodeCallback<void>) {
|
|
11
|
+
// body...
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default DispatchService;
|
package/src/event.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IBaseEvent } from "./types";
|
|
2
|
+
|
|
3
|
+
interface IEvent extends IBaseEvent {
|
|
4
|
+
payload: Record<string, unknown>;
|
|
5
|
+
timestamp: Date;
|
|
6
|
+
metadata: Record<string, unknown>;
|
|
7
|
+
revision: number | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
var Event = function (this: IEvent, id: string, type: string, payload: Record<string, unknown>, metadata?: Record<string, unknown>) {
|
|
11
|
+
this.id = id;
|
|
12
|
+
this.type = type;
|
|
13
|
+
this.payload = payload;
|
|
14
|
+
this.timestamp = new Date();
|
|
15
|
+
this.metadata = metadata || {};
|
|
16
|
+
this.revision = null; //filled by eventstore on append
|
|
17
|
+
} as unknown as new (
|
|
18
|
+
id: string,
|
|
19
|
+
type: string,
|
|
20
|
+
payload: Record<string, unknown>,
|
|
21
|
+
metadata?: Record<string, unknown>
|
|
22
|
+
) => IEvent;
|
|
23
|
+
|
|
24
|
+
export default Event;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import Promise from "bluebird";
|
|
2
|
+
import Event from "./event";
|
|
3
|
+
import Partition from "./event_store_partition";
|
|
4
|
+
import EventStream from "./event_stream";
|
|
5
|
+
import Commit from "./persistence/commit";
|
|
6
|
+
import ConcurrencyError from "./persistence/concurrency_error";
|
|
7
|
+
import DuplicateCommitError from "./persistence/duplicate_commit_error";
|
|
8
|
+
import InMemoryPersistenceStore from "./persistence/inmemory/inmemory_persistence";
|
|
9
|
+
import type { DispatchHandler, IPersistencePartition, IPersistenceProvider, NodeCallback } from "./types";
|
|
10
|
+
|
|
11
|
+
type PartitionInstance = InstanceType<typeof Partition>;
|
|
12
|
+
|
|
13
|
+
var _PENDING: Record<string, never> = {};
|
|
14
|
+
|
|
15
|
+
interface IEventStore {
|
|
16
|
+
_store: IPersistenceProvider;
|
|
17
|
+
_dispatchService?: DispatchHandler;
|
|
18
|
+
_partitions: Record<string, PartitionInstance | typeof _PENDING>;
|
|
19
|
+
openPartition(partitionId?: string, callback?: NodeCallback<PartitionInstance>): Promise<PartitionInstance>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var EventStore = function (this: IEventStore, persistenceStore?: IPersistenceProvider, dispatchService?: DispatchHandler) {
|
|
23
|
+
this._store = persistenceStore || new InMemoryPersistenceStore();
|
|
24
|
+
this._dispatchService = dispatchService;
|
|
25
|
+
this._partitions = {};
|
|
26
|
+
} as unknown as {
|
|
27
|
+
new (persistenceStore?: IPersistenceProvider, dispatchService?: DispatchHandler): IEventStore;
|
|
28
|
+
Event: typeof Event;
|
|
29
|
+
EventStream: typeof EventStream;
|
|
30
|
+
EventStorePartition: typeof Partition;
|
|
31
|
+
Commit: typeof Commit;
|
|
32
|
+
ConcurrencyError: typeof ConcurrencyError;
|
|
33
|
+
DuplicateCommitError: typeof DuplicateCommitError;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
EventStore.prototype.openPartition = function (this: IEventStore, partitionId?: string, callback?: NodeCallback<PartitionInstance>) {
|
|
37
|
+
var partition: PartitionInstance | typeof _PENDING | null = null;
|
|
38
|
+
var pid = partitionId || "master";
|
|
39
|
+
partition = this._partitions[pid];
|
|
40
|
+
var self = this;
|
|
41
|
+
if (!partition) {
|
|
42
|
+
this._partitions[pid] = _PENDING;
|
|
43
|
+
return this._store
|
|
44
|
+
.openPartition(pid)
|
|
45
|
+
.then(function (persistencePartition: IPersistencePartition) {
|
|
46
|
+
var p = new Partition(pid, persistencePartition, self._dispatchService);
|
|
47
|
+
return (self._partitions[pid] = p);
|
|
48
|
+
})
|
|
49
|
+
.nodeify(callback);
|
|
50
|
+
} else {
|
|
51
|
+
//_PENDING is set when the partition is loading, wait 5ms at a time to see if it has been loaded
|
|
52
|
+
if (this._partitions[pid] === _PENDING) {
|
|
53
|
+
var promise = new Promise<PartitionInstance>(function (resolve) {
|
|
54
|
+
var resolver = function () {
|
|
55
|
+
var partition = self._partitions[pid];
|
|
56
|
+
if (partition === _PENDING) {
|
|
57
|
+
setTimeout(resolver, 5);
|
|
58
|
+
} else {
|
|
59
|
+
resolve(partition as PartitionInstance);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
resolver();
|
|
63
|
+
});
|
|
64
|
+
return promise.nodeify(callback);
|
|
65
|
+
} else {
|
|
66
|
+
return Promise.resolve(this._partitions[pid] as PartitionInstance).nodeify(callback);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
EventStore.Event = Event;
|
|
72
|
+
EventStore.EventStream = EventStream;
|
|
73
|
+
EventStore.EventStorePartition = Partition;
|
|
74
|
+
EventStore.Commit = Commit;
|
|
75
|
+
EventStore.ConcurrencyError = ConcurrencyError;
|
|
76
|
+
EventStore.DuplicateCommitError = DuplicateCommitError;
|
|
77
|
+
|
|
78
|
+
export default EventStore;
|
|
79
|
+
|
|
80
|
+
// Named exports for destructured imports: import EventStore, { Event, Commit } from 'tapeworm'
|
|
81
|
+
export { Event, EventStream, Commit, ConcurrencyError, DuplicateCommitError };
|
|
82
|
+
export { Partition as EventStorePartition };
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import Promise from "bluebird";
|
|
2
|
+
import _ from "lodash";
|
|
3
|
+
import { v4 as uuid } from "uuid";
|
|
4
|
+
import EventStream from "./event_stream";
|
|
5
|
+
import type {
|
|
6
|
+
DispatchHandler,
|
|
7
|
+
IBaseEvent,
|
|
8
|
+
ICommit,
|
|
9
|
+
IPersistencePartition,
|
|
10
|
+
ISnapshot,
|
|
11
|
+
NodeCallback,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
type EventStreamInstance = InstanceType<typeof EventStream>;
|
|
15
|
+
|
|
16
|
+
interface IEventStorePartition {
|
|
17
|
+
_partitionId: string;
|
|
18
|
+
_persistencePartition: IPersistencePartition;
|
|
19
|
+
_dispatchService?: DispatchHandler;
|
|
20
|
+
openStream(
|
|
21
|
+
streamId: string,
|
|
22
|
+
writeOnly?: boolean | NodeCallback<EventStreamInstance>,
|
|
23
|
+
callback?: NodeCallback<EventStreamInstance>,
|
|
24
|
+
): Promise<EventStreamInstance>;
|
|
25
|
+
append(
|
|
26
|
+
commits: ICommit | ICommit[],
|
|
27
|
+
callback?: NodeCallback<unknown>,
|
|
28
|
+
): Promise<ICommit[]>;
|
|
29
|
+
delete(
|
|
30
|
+
streamId: string,
|
|
31
|
+
deleteEvent?: Record<string, unknown>,
|
|
32
|
+
): Promise<void>;
|
|
33
|
+
_queryStream(
|
|
34
|
+
streamId: string,
|
|
35
|
+
callback?: NodeCallback<ICommit[]>,
|
|
36
|
+
): Promise<ICommit[]>;
|
|
37
|
+
_queryAll(callback?: NodeCallback<ICommit[]>): Promise<ICommit[]>;
|
|
38
|
+
_queryStreamWithSnapshotFallback(
|
|
39
|
+
streamId: string,
|
|
40
|
+
callback?: NodeCallback<unknown>,
|
|
41
|
+
): Promise<unknown>;
|
|
42
|
+
_truncateStreamFrom(
|
|
43
|
+
streamId: string,
|
|
44
|
+
commitSequence: number,
|
|
45
|
+
callback?: NodeCallback<void>,
|
|
46
|
+
): Promise<void>;
|
|
47
|
+
_applyCommitHeader(
|
|
48
|
+
commitId: string,
|
|
49
|
+
header: Record<string, unknown>,
|
|
50
|
+
callback?: NodeCallback<ICommit>,
|
|
51
|
+
): Promise<ICommit>;
|
|
52
|
+
// Dynamically delegated methods — may or may not exist at runtime
|
|
53
|
+
storeSnapshot?: IPersistencePartition["storeSnapshot"];
|
|
54
|
+
loadSnapshot?: IPersistencePartition["loadSnapshot"];
|
|
55
|
+
queryStream?: IPersistencePartition["queryStream"];
|
|
56
|
+
queryStreamWithSnapshot?: IPersistencePartition["queryStreamWithSnapshot"];
|
|
57
|
+
removeSnapshot?: IPersistencePartition["removeSnapshot"];
|
|
58
|
+
getLatestCommit?: IPersistencePartition["getLatestCommit"];
|
|
59
|
+
querySnapshotsByMaxDateTime?: IPersistencePartition["querySnapshotsByMaxDateTime"];
|
|
60
|
+
[key: string]: unknown;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
var EventStorePartition = function (
|
|
64
|
+
this: IEventStorePartition,
|
|
65
|
+
partitionId: string,
|
|
66
|
+
persistencePartition: IPersistencePartition,
|
|
67
|
+
dispatchService?: DispatchHandler,
|
|
68
|
+
) {
|
|
69
|
+
this._partitionId = partitionId;
|
|
70
|
+
this._persistencePartition = persistencePartition;
|
|
71
|
+
this._dispatchService = dispatchService;
|
|
72
|
+
|
|
73
|
+
var self = this;
|
|
74
|
+
_.forEach(
|
|
75
|
+
[
|
|
76
|
+
"storeSnapshot",
|
|
77
|
+
"loadSnapshot",
|
|
78
|
+
"queryStream",
|
|
79
|
+
"queryStreamWithSnapshot",
|
|
80
|
+
"removeSnapshot",
|
|
81
|
+
"getLatestCommit",
|
|
82
|
+
"querySnapshotsByMaxDateTime",
|
|
83
|
+
] as const,
|
|
84
|
+
function (what: string) {
|
|
85
|
+
// biome-ignore lint/complexity/noBannedTypes: dynamic method delegation requires generic function type
|
|
86
|
+
var pp = self._persistencePartition as unknown as Record<
|
|
87
|
+
string,
|
|
88
|
+
Function
|
|
89
|
+
>;
|
|
90
|
+
if (pp[what]) {
|
|
91
|
+
self[what] = function () {
|
|
92
|
+
return pp[what].apply(self._persistencePartition, arguments);
|
|
93
|
+
};
|
|
94
|
+
} else {
|
|
95
|
+
if (what === "queryStreamWithSnapshot") {
|
|
96
|
+
// fallback function
|
|
97
|
+
self[what] = function () {
|
|
98
|
+
return self._queryStreamWithSnapshotFallback.apply(
|
|
99
|
+
self._persistencePartition,
|
|
100
|
+
arguments as unknown as [string, NodeCallback<unknown>?],
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
} as unknown as new (
|
|
108
|
+
partitionId: string,
|
|
109
|
+
persistencePartition: IPersistencePartition,
|
|
110
|
+
dispatchService?: DispatchHandler,
|
|
111
|
+
) => IEventStorePartition;
|
|
112
|
+
|
|
113
|
+
EventStorePartition.prototype.openStream = function (
|
|
114
|
+
this: IEventStorePartition,
|
|
115
|
+
streamId: string,
|
|
116
|
+
writeOnly?: boolean | NodeCallback<EventStreamInstance>,
|
|
117
|
+
callback?: NodeCallback<EventStreamInstance>,
|
|
118
|
+
) {
|
|
119
|
+
if (typeof writeOnly === "function") {
|
|
120
|
+
callback = writeOnly;
|
|
121
|
+
writeOnly = false;
|
|
122
|
+
}
|
|
123
|
+
var stream = new EventStream(
|
|
124
|
+
this as unknown as ConstructorParameters<typeof EventStream>[0],
|
|
125
|
+
streamId,
|
|
126
|
+
writeOnly as boolean,
|
|
127
|
+
);
|
|
128
|
+
return stream._prepareStream(callback);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
EventStorePartition.prototype.append = function (
|
|
132
|
+
this: IEventStorePartition,
|
|
133
|
+
commits: ICommit | ICommit[],
|
|
134
|
+
callback?: NodeCallback<unknown>,
|
|
135
|
+
) {
|
|
136
|
+
//pre hooks
|
|
137
|
+
var self = this;
|
|
138
|
+
|
|
139
|
+
if (!_.isArray(commits)) {
|
|
140
|
+
commits = [commits];
|
|
141
|
+
}
|
|
142
|
+
return Promise.each(commits, function (commit: ICommit) {
|
|
143
|
+
return self._persistencePartition
|
|
144
|
+
.append(commit, callback as NodeCallback<ICommit>)
|
|
145
|
+
.then(function (r: ICommit) {
|
|
146
|
+
var done = function () {
|
|
147
|
+
self._persistencePartition.markAsDispatched(commit);
|
|
148
|
+
};
|
|
149
|
+
if (self._dispatchService) {
|
|
150
|
+
self._dispatchService(commit, done);
|
|
151
|
+
}
|
|
152
|
+
return r;
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
//post hooks
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Delete function in tapeworm.
|
|
160
|
+
* Triggers a $stream.deleted.event, truncates the stream, and removes snapshots.
|
|
161
|
+
*/
|
|
162
|
+
EventStorePartition.prototype.delete = function (
|
|
163
|
+
this: IEventStorePartition,
|
|
164
|
+
streamId: string,
|
|
165
|
+
deleteEvent?: Record<string, unknown>,
|
|
166
|
+
) {
|
|
167
|
+
var self = this;
|
|
168
|
+
var event = Object.assign({}, deleteEvent, {
|
|
169
|
+
id: uuid(),
|
|
170
|
+
type: "$stream.deleted.event",
|
|
171
|
+
payload: Object.assign(
|
|
172
|
+
{},
|
|
173
|
+
{ dateOfDeletion: new Date() },
|
|
174
|
+
(deleteEvent && (deleteEvent.payload as Record<string, unknown>)) || {},
|
|
175
|
+
),
|
|
176
|
+
});
|
|
177
|
+
return this._truncateStreamFrom(streamId, -1)
|
|
178
|
+
.then(function () {
|
|
179
|
+
return self.openStream(streamId, false);
|
|
180
|
+
})
|
|
181
|
+
.then(function (stream: EventStreamInstance) {
|
|
182
|
+
stream.append(event as IBaseEvent);
|
|
183
|
+
return stream.commit(uuid());
|
|
184
|
+
})
|
|
185
|
+
.then(function () {
|
|
186
|
+
// support legacy/unsupported approach
|
|
187
|
+
if (self._persistencePartition.removeSnapshot) {
|
|
188
|
+
return self._persistencePartition.removeSnapshot(streamId);
|
|
189
|
+
} else if (self._persistencePartition.removeSnapshots) {
|
|
190
|
+
return self._persistencePartition.removeSnapshots([streamId]);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/*** UNDOCUMENTED API ***/
|
|
196
|
+
|
|
197
|
+
EventStorePartition.prototype._queryStream = function (
|
|
198
|
+
this: IEventStorePartition,
|
|
199
|
+
streamId: string,
|
|
200
|
+
callback?: NodeCallback<ICommit[]>,
|
|
201
|
+
) {
|
|
202
|
+
return this._persistencePartition.queryStream(streamId, callback);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
EventStorePartition.prototype._queryAll = function (
|
|
206
|
+
this: IEventStorePartition,
|
|
207
|
+
callback?: NodeCallback<ICommit[]>,
|
|
208
|
+
) {
|
|
209
|
+
return this._persistencePartition.queryAll(callback);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
EventStorePartition.prototype._queryStreamWithSnapshotFallback = function (
|
|
213
|
+
this: IPersistencePartition,
|
|
214
|
+
streamId: string,
|
|
215
|
+
callback?: NodeCallback<unknown>,
|
|
216
|
+
) {
|
|
217
|
+
var self = this;
|
|
218
|
+
return new Promise(function (
|
|
219
|
+
resolve: (value: unknown) => void,
|
|
220
|
+
reject: (reason: Error) => void,
|
|
221
|
+
) {
|
|
222
|
+
self.loadSnapshot?.(
|
|
223
|
+
streamId,
|
|
224
|
+
function (err: Error | null, snapshot?: ISnapshot) {
|
|
225
|
+
if (err) {
|
|
226
|
+
reject(err);
|
|
227
|
+
} else {
|
|
228
|
+
var snapshotVersion = (snapshot && snapshot.version) || -1;
|
|
229
|
+
self.queryStream(
|
|
230
|
+
streamId,
|
|
231
|
+
snapshotVersion,
|
|
232
|
+
function (_err: Error | null, res?: ICommit[]) {
|
|
233
|
+
resolve({ snapshot: snapshot, commits: res });
|
|
234
|
+
},
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
}).nodeify(callback);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/*** NEEDED FOR SYNCING ***/
|
|
243
|
+
|
|
244
|
+
EventStorePartition.prototype._truncateStreamFrom = function (
|
|
245
|
+
this: IEventStorePartition,
|
|
246
|
+
streamId: string,
|
|
247
|
+
commitSequence: number,
|
|
248
|
+
callback?: NodeCallback<void>,
|
|
249
|
+
) {
|
|
250
|
+
return this._persistencePartition.truncateStreamFrom?.(
|
|
251
|
+
streamId,
|
|
252
|
+
commitSequence,
|
|
253
|
+
callback,
|
|
254
|
+
);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
EventStorePartition.prototype._applyCommitHeader = function (
|
|
258
|
+
this: IEventStorePartition,
|
|
259
|
+
commitId: string,
|
|
260
|
+
header: Record<string, unknown>,
|
|
261
|
+
callback?: NodeCallback<ICommit>,
|
|
262
|
+
) {
|
|
263
|
+
return this._persistencePartition.applyCommitHeader?.(
|
|
264
|
+
commitId,
|
|
265
|
+
header,
|
|
266
|
+
callback,
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export default EventStorePartition;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import Promise from "bluebird";
|
|
2
|
+
import _ from "lodash";
|
|
3
|
+
import Commit from "./persistence/commit";
|
|
4
|
+
import type { IBaseEvent, ICommit, NodeCallback } from "./types";
|
|
5
|
+
|
|
6
|
+
/** Subset of EventStorePartition that EventStream depends on. */
|
|
7
|
+
interface IEventPartition {
|
|
8
|
+
_partitionId: string;
|
|
9
|
+
getLatestCommit?: (streamId: string) => Promise<ICommit | undefined>;
|
|
10
|
+
_queryStream: (streamId: string, callback?: NodeCallback<ICommit[]>) => Promise<ICommit[]>;
|
|
11
|
+
append: (commit: ICommit, callback?: NodeCallback<unknown>) => Promise<unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface IEventStream {
|
|
15
|
+
_partition: IEventPartition;
|
|
16
|
+
_streamId: string;
|
|
17
|
+
_writeOnly: boolean;
|
|
18
|
+
_uncommittedEvents: IBaseEvent[];
|
|
19
|
+
_committedEvents: IBaseEvent[];
|
|
20
|
+
_version: number;
|
|
21
|
+
_isDeleted: boolean;
|
|
22
|
+
_commitSequence: number;
|
|
23
|
+
_prepareStream(callback?: NodeCallback<IEventStream>): Promise<IEventStream>;
|
|
24
|
+
getVersion(): number;
|
|
25
|
+
append(event: IBaseEvent): void;
|
|
26
|
+
hasChanges(): boolean;
|
|
27
|
+
commit(commitId: string, callback?: NodeCallback<IEventStream>): Promise<IEventStream | undefined>;
|
|
28
|
+
_clearChanges(): void;
|
|
29
|
+
revertChanges(): void;
|
|
30
|
+
_buildCommit(commitId: string, events: IBaseEvent[]): ICommit;
|
|
31
|
+
getCommittedEvents(): IBaseEvent[];
|
|
32
|
+
getUncommittedEvents(): IBaseEvent[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// writeOnly - will never read entire stream from
|
|
36
|
+
var EventStream = function (this: IEventStream, eventPartition: IEventPartition, streamId: string, writeOnly?: boolean) {
|
|
37
|
+
if (streamId === undefined) {
|
|
38
|
+
throw new Error("StreamId must be defined!");
|
|
39
|
+
}
|
|
40
|
+
this._partition = eventPartition;
|
|
41
|
+
this._streamId = streamId;
|
|
42
|
+
this._writeOnly = writeOnly || false;
|
|
43
|
+
this._uncommittedEvents = [];
|
|
44
|
+
this._committedEvents = [];
|
|
45
|
+
this._version = 0;
|
|
46
|
+
this._isDeleted = false;
|
|
47
|
+
} as unknown as new (
|
|
48
|
+
eventPartition: IEventPartition,
|
|
49
|
+
streamId: string,
|
|
50
|
+
writeOnly?: boolean
|
|
51
|
+
) => IEventStream;
|
|
52
|
+
|
|
53
|
+
EventStream.prototype._prepareStream = function (this: IEventStream, callback?: NodeCallback<IEventStream>) {
|
|
54
|
+
this._committedEvents = [];
|
|
55
|
+
this._commitSequence = -1;
|
|
56
|
+
var self = this;
|
|
57
|
+
if (self._writeOnly === true && self._partition.getLatestCommit) {
|
|
58
|
+
// if stream should only be opened for appending commits, only really care about getting the correct commitSequence (from last commit)
|
|
59
|
+
return self._partition
|
|
60
|
+
.getLatestCommit(self._streamId)
|
|
61
|
+
.then(function (commit: ICommit | undefined) {
|
|
62
|
+
if (commit) {
|
|
63
|
+
self._commitSequence = commit.commitSequence;
|
|
64
|
+
var lastEvent = commit.events[commit.events.length - 1];
|
|
65
|
+
if (lastEvent && lastEvent.type === "$stream.deleted.event") {
|
|
66
|
+
self._isDeleted = true;
|
|
67
|
+
}
|
|
68
|
+
self._version = (lastEvent.version as number) + 1;
|
|
69
|
+
}
|
|
70
|
+
// if no commit is found, assume its a new stream, and keep default versions
|
|
71
|
+
})
|
|
72
|
+
.then(function () {
|
|
73
|
+
return self;
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
return self._partition
|
|
77
|
+
._queryStream(self._streamId, callback as unknown as NodeCallback<ICommit[]>)
|
|
78
|
+
.then(function (commits: ICommit[]) {
|
|
79
|
+
self._version = 0;
|
|
80
|
+
|
|
81
|
+
if (!commits || commits.length === 0) {
|
|
82
|
+
commits = [];
|
|
83
|
+
self._version = -1;
|
|
84
|
+
} else {
|
|
85
|
+
if (commits[0] && commits[0].events && commits[0].events[0] && commits[0].events[0].type === "$stream.deleted.event") {
|
|
86
|
+
throw new Error("Stream is deleted");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
var version = 0;
|
|
90
|
+
for (var i = 0; i < commits.length; i++) {
|
|
91
|
+
self._commitSequence++;
|
|
92
|
+
for (var j = 0; j < commits[i].events.length; j++) {
|
|
93
|
+
self._version++;
|
|
94
|
+
commits[i].events[j].version = version++;
|
|
95
|
+
self._committedEvents.push(commits[i].events[j]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.then(function () {
|
|
100
|
+
return self;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
EventStream.prototype.getVersion = function (this: IEventStream): number {
|
|
106
|
+
return this._version;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
EventStream.prototype.append = function (this: IEventStream, event: IBaseEvent): void {
|
|
110
|
+
this._uncommittedEvents.push(event);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
EventStream.prototype.hasChanges = function (this: IEventStream): boolean {
|
|
114
|
+
return this._uncommittedEvents.length > 0;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
EventStream.prototype.commit = function (this: IEventStream, commitId: string, callback?: NodeCallback<IEventStream>) {
|
|
118
|
+
var self = this;
|
|
119
|
+
|
|
120
|
+
if (this._isDeleted) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
"Stream is deleted, unable to commit: " +
|
|
123
|
+
this._uncommittedEvents.map(function (event: IBaseEvent) {
|
|
124
|
+
return event.type;
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!this.hasChanges()) {
|
|
130
|
+
//nothing to commit
|
|
131
|
+
return Promise.resolve().nodeify(callback);
|
|
132
|
+
} else {
|
|
133
|
+
var commit = this._buildCommit(commitId, this._uncommittedEvents);
|
|
134
|
+
return this._partition.append(commit, callback as NodeCallback<unknown>).then(function () {
|
|
135
|
+
//rebuild local state
|
|
136
|
+
var events = self._uncommittedEvents;
|
|
137
|
+
self._version = (events[events.length - 1].version as number) + 1;
|
|
138
|
+
if (self._writeOnly === true) {
|
|
139
|
+
self._commitSequence++;
|
|
140
|
+
self._clearChanges();
|
|
141
|
+
} else {
|
|
142
|
+
for (var i = 0; i < events.length; i++) {
|
|
143
|
+
self._committedEvents.push(events[i]);
|
|
144
|
+
}
|
|
145
|
+
self._clearChanges();
|
|
146
|
+
self._commitSequence++;
|
|
147
|
+
return self;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
EventStream.prototype._clearChanges = function (this: IEventStream): void {
|
|
154
|
+
this._uncommittedEvents = [];
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
EventStream.prototype.revertChanges = function (this: IEventStream): void {
|
|
158
|
+
//trunc the uncomitted events log
|
|
159
|
+
this._uncommittedEvents = [];
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
EventStream.prototype._buildCommit = function (this: IEventStream, commitId: string, events: IBaseEvent[]): ICommit {
|
|
163
|
+
var commitSequence = this._commitSequence;
|
|
164
|
+
var commit = new Commit(commitId, this._partition._partitionId, this._streamId, ++commitSequence, events);
|
|
165
|
+
var version = this._version == -1 ? 0 : this._version;
|
|
166
|
+
|
|
167
|
+
_.forEach(events, function (evt: IBaseEvent) {
|
|
168
|
+
evt.version = version++;
|
|
169
|
+
});
|
|
170
|
+
return commit;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
EventStream.prototype.getCommittedEvents = function (this: IEventStream): IBaseEvent[] {
|
|
174
|
+
if (this._writeOnly) {
|
|
175
|
+
throw new Error("Cannot access committed events when using writeOnly mode...");
|
|
176
|
+
}
|
|
177
|
+
return this._committedEvents.slice();
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
EventStream.prototype.getUncommittedEvents = function (this: IEventStream): IBaseEvent[] {
|
|
181
|
+
return this._uncommittedEvents.slice();
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export default EventStream;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { IBaseEvent, ICommit } from "../types";
|
|
2
|
+
|
|
3
|
+
var Commit = function <TEvent extends IBaseEvent = IBaseEvent>(
|
|
4
|
+
this: ICommit<TEvent>,
|
|
5
|
+
id: string,
|
|
6
|
+
partitionId: string,
|
|
7
|
+
streamId: string,
|
|
8
|
+
commitSequence: number,
|
|
9
|
+
events: TEvent[]
|
|
10
|
+
) {
|
|
11
|
+
this.id = id;
|
|
12
|
+
this.partitionId = partitionId;
|
|
13
|
+
this.streamId = streamId;
|
|
14
|
+
this.commitSequence = commitSequence;
|
|
15
|
+
this.events = events;
|
|
16
|
+
} as unknown as new <TEvent extends IBaseEvent = IBaseEvent>(
|
|
17
|
+
id: string,
|
|
18
|
+
partitionId: string,
|
|
19
|
+
streamId: string,
|
|
20
|
+
commitSequence: number,
|
|
21
|
+
events: TEvent[]
|
|
22
|
+
) => ICommit<TEvent>;
|
|
23
|
+
|
|
24
|
+
export default Commit;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import util from "node:util";
|
|
2
|
+
|
|
3
|
+
interface IConcurrencyError extends Error {
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
var ConcurrencyError = function (this: IConcurrencyError, message: string) {
|
|
8
|
+
Error.call(this);
|
|
9
|
+
this.message = message;
|
|
10
|
+
} as unknown as new (
|
|
11
|
+
message: string
|
|
12
|
+
) => IConcurrencyError;
|
|
13
|
+
|
|
14
|
+
util.inherits(ConcurrencyError, Error);
|
|
15
|
+
|
|
16
|
+
export default ConcurrencyError;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import util from "node:util";
|
|
2
|
+
|
|
3
|
+
interface IDuplicateCommitError extends Error {
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
var DuplicateCommitError = function (this: IDuplicateCommitError, message: string) {
|
|
8
|
+
Error.call(this);
|
|
9
|
+
this.message = message;
|
|
10
|
+
} as unknown as new (
|
|
11
|
+
message: string
|
|
12
|
+
) => IDuplicateCommitError;
|
|
13
|
+
|
|
14
|
+
util.inherits(DuplicateCommitError, Error);
|
|
15
|
+
|
|
16
|
+
export default DuplicateCommitError;
|