@event-driven-io/emmett-sqlite 0.32.0 → 0.33.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/index.cjs +758 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -1
- package/dist/index.d.ts +84 -1
- package/dist/index.js +757 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1 +1,758 @@
|
|
|
1
|
+
// src/connection/sqliteConnection.ts
|
|
2
|
+
import sqlite3 from "sqlite3";
|
|
3
|
+
var isSQLiteError = (error) => {
|
|
4
|
+
if (error instanceof Error && "code" in error) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
return false;
|
|
8
|
+
};
|
|
9
|
+
var InMemorySQLiteDatabase = ":memory:";
|
|
10
|
+
var sqliteConnection = (options) => {
|
|
11
|
+
const db = new sqlite3.Database(options.fileName ?? InMemorySQLiteDatabase);
|
|
12
|
+
return {
|
|
13
|
+
close: () => db.close(),
|
|
14
|
+
command: (sql2, params) => new Promise((resolve, reject) => {
|
|
15
|
+
db.run(sql2, params ?? [], (err) => {
|
|
16
|
+
if (err) {
|
|
17
|
+
reject(err);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
resolve();
|
|
21
|
+
});
|
|
22
|
+
}),
|
|
23
|
+
query: (sql2, params) => new Promise((resolve, reject) => {
|
|
24
|
+
db.all(sql2, params ?? [], (err, result) => {
|
|
25
|
+
if (err) {
|
|
26
|
+
reject(err);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
resolve(result);
|
|
30
|
+
});
|
|
31
|
+
}),
|
|
32
|
+
querySingle: (sql2, params) => new Promise((resolve, reject) => {
|
|
33
|
+
db.get(sql2, params ?? [], (err, result) => {
|
|
34
|
+
if (err) {
|
|
35
|
+
reject(err);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
resolve(result);
|
|
39
|
+
});
|
|
40
|
+
})
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// ../emmett/dist/chunk-4E7QLAH5.js
|
|
45
|
+
var isNumber = (val) => typeof val === "number" && val === val;
|
|
46
|
+
var isString = (val) => typeof val === "string";
|
|
47
|
+
var EmmettError = class _EmmettError extends Error {
|
|
48
|
+
errorCode;
|
|
49
|
+
constructor(options) {
|
|
50
|
+
const errorCode = options && typeof options === "object" && "errorCode" in options ? options.errorCode : isNumber(options) ? options : 500;
|
|
51
|
+
const message = options && typeof options === "object" && "message" in options ? options.message : isString(options) ? options : `Error with status code '${errorCode}' ocurred during Emmett processing`;
|
|
52
|
+
super(message);
|
|
53
|
+
this.errorCode = errorCode;
|
|
54
|
+
Object.setPrototypeOf(this, _EmmettError.prototype);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var ConcurrencyError = class _ConcurrencyError extends EmmettError {
|
|
58
|
+
constructor(current, expected, message) {
|
|
59
|
+
super({
|
|
60
|
+
errorCode: 412,
|
|
61
|
+
message: message ?? `Expected version ${expected.toString()} does not match current ${current?.toString()}`
|
|
62
|
+
});
|
|
63
|
+
this.current = current;
|
|
64
|
+
this.expected = expected;
|
|
65
|
+
Object.setPrototypeOf(this, _ConcurrencyError.prototype);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ../emmett/dist/index.js
|
|
70
|
+
import { v4 as uuid3 } from "uuid";
|
|
71
|
+
import { TransformStream } from "web-streams-polyfill";
|
|
72
|
+
import { v4 as uuid2 } from "uuid";
|
|
73
|
+
import { v4 as uuid } from "uuid";
|
|
74
|
+
import { TransformStream as TransformStream2 } from "web-streams-polyfill";
|
|
75
|
+
import retry from "async-retry";
|
|
76
|
+
import { ReadableStream } from "web-streams-polyfill";
|
|
77
|
+
import "web-streams-polyfill";
|
|
78
|
+
import { TransformStream as TransformStream3 } from "web-streams-polyfill";
|
|
79
|
+
import { TransformStream as TransformStream4 } from "web-streams-polyfill";
|
|
80
|
+
import { TransformStream as TransformStream5 } from "web-streams-polyfill";
|
|
81
|
+
import {
|
|
82
|
+
TransformStream as TransformStream6
|
|
83
|
+
} from "web-streams-polyfill";
|
|
84
|
+
import { TransformStream as TransformStream7 } from "web-streams-polyfill";
|
|
85
|
+
import { TransformStream as TransformStream8 } from "web-streams-polyfill";
|
|
86
|
+
import { TransformStream as TransformStream9 } from "web-streams-polyfill";
|
|
87
|
+
import { TransformStream as TransformStream10 } from "web-streams-polyfill";
|
|
88
|
+
import { TransformStream as TransformStream11 } from "web-streams-polyfill";
|
|
89
|
+
var STREAM_EXISTS = "STREAM_EXISTS";
|
|
90
|
+
var STREAM_DOES_NOT_EXIST = "STREAM_DOES_NOT_EXIST";
|
|
91
|
+
var NO_CONCURRENCY_CHECK = "NO_CONCURRENCY_CHECK";
|
|
92
|
+
var matchesExpectedVersion = (current, expected, defaultVersion) => {
|
|
93
|
+
if (expected === NO_CONCURRENCY_CHECK) return true;
|
|
94
|
+
if (expected == STREAM_DOES_NOT_EXIST) return current === defaultVersion;
|
|
95
|
+
if (expected == STREAM_EXISTS) return current !== defaultVersion;
|
|
96
|
+
return current === expected;
|
|
97
|
+
};
|
|
98
|
+
var assertExpectedVersionMatchesCurrent = (current, expected, defaultVersion) => {
|
|
99
|
+
expected ??= NO_CONCURRENCY_CHECK;
|
|
100
|
+
if (!matchesExpectedVersion(current, expected, defaultVersion))
|
|
101
|
+
throw new ExpectedVersionConflictError(current, expected);
|
|
102
|
+
};
|
|
103
|
+
var ExpectedVersionConflictError = class _ExpectedVersionConflictError extends ConcurrencyError {
|
|
104
|
+
constructor(current, expected) {
|
|
105
|
+
super(current?.toString(), expected?.toString());
|
|
106
|
+
Object.setPrototypeOf(this, _ExpectedVersionConflictError.prototype);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var notifyAboutNoActiveReadersStream = (onNoActiveReaderCallback, options = {}) => new NotifyAboutNoActiveReadersStream(onNoActiveReaderCallback, options);
|
|
110
|
+
var NotifyAboutNoActiveReadersStream = class extends TransformStream2 {
|
|
111
|
+
constructor(onNoActiveReaderCallback, options = {}) {
|
|
112
|
+
super({
|
|
113
|
+
cancel: (reason) => {
|
|
114
|
+
console.log("Stream was canceled. Reason:", reason);
|
|
115
|
+
this.stopChecking();
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
this.onNoActiveReaderCallback = onNoActiveReaderCallback;
|
|
119
|
+
this.streamId = options?.streamId ?? uuid();
|
|
120
|
+
this.onNoActiveReaderCallback = onNoActiveReaderCallback;
|
|
121
|
+
this.startChecking(options?.intervalCheckInMs ?? 20);
|
|
122
|
+
}
|
|
123
|
+
checkInterval = null;
|
|
124
|
+
streamId;
|
|
125
|
+
_isStopped = false;
|
|
126
|
+
get hasActiveSubscribers() {
|
|
127
|
+
return !this._isStopped;
|
|
128
|
+
}
|
|
129
|
+
startChecking(interval) {
|
|
130
|
+
this.checkInterval = setInterval(() => {
|
|
131
|
+
this.checkNoActiveReader();
|
|
132
|
+
}, interval);
|
|
133
|
+
}
|
|
134
|
+
stopChecking() {
|
|
135
|
+
if (!this.checkInterval) return;
|
|
136
|
+
clearInterval(this.checkInterval);
|
|
137
|
+
this.checkInterval = null;
|
|
138
|
+
this._isStopped = true;
|
|
139
|
+
this.onNoActiveReaderCallback(this);
|
|
140
|
+
}
|
|
141
|
+
checkNoActiveReader() {
|
|
142
|
+
if (!this.readable.locked && !this._isStopped) {
|
|
143
|
+
this.stopChecking();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var asyncRetry = async (fn, opts) => {
|
|
148
|
+
if (opts === void 0 || opts.retries === 0) return fn();
|
|
149
|
+
return retry(
|
|
150
|
+
async (bail) => {
|
|
151
|
+
try {
|
|
152
|
+
return await fn();
|
|
153
|
+
} catch (error2) {
|
|
154
|
+
if (opts?.shouldRetryError && !opts.shouldRetryError(error2)) {
|
|
155
|
+
bail(error2);
|
|
156
|
+
}
|
|
157
|
+
throw error2;
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
opts ?? { retries: 0 }
|
|
161
|
+
);
|
|
162
|
+
};
|
|
163
|
+
var ParseError = class extends Error {
|
|
164
|
+
constructor(text) {
|
|
165
|
+
super(`Cannot parse! ${text}`);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
var JSONParser = {
|
|
169
|
+
stringify: (value, options) => {
|
|
170
|
+
return JSON.stringify(
|
|
171
|
+
options?.map ? options.map(value) : value,
|
|
172
|
+
//TODO: Consider adding support to DateTime and adding specific format to mark that's a bigint
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
174
|
+
(_, v) => typeof v === "bigint" ? v.toString() : v
|
|
175
|
+
);
|
|
176
|
+
},
|
|
177
|
+
parse: (text, options) => {
|
|
178
|
+
const parsed = JSON.parse(text, options?.reviver);
|
|
179
|
+
if (options?.typeCheck && !options?.typeCheck(parsed))
|
|
180
|
+
throw new ParseError(text);
|
|
181
|
+
return options?.map ? options.map(parsed) : parsed;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var filter = (filter2) => new TransformStream3({
|
|
185
|
+
transform(chunk, controller) {
|
|
186
|
+
if (filter2(chunk)) {
|
|
187
|
+
controller.enqueue(chunk);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
var map = (map2) => new TransformStream4({
|
|
192
|
+
transform(chunk, controller) {
|
|
193
|
+
controller.enqueue(map2(chunk));
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
var reduce = (reducer, initialValue) => new ReduceTransformStream(reducer, initialValue);
|
|
197
|
+
var ReduceTransformStream = class extends TransformStream5 {
|
|
198
|
+
accumulator;
|
|
199
|
+
reducer;
|
|
200
|
+
constructor(reducer, initialValue) {
|
|
201
|
+
super({
|
|
202
|
+
transform: (chunk) => {
|
|
203
|
+
this.accumulator = this.reducer(this.accumulator, chunk);
|
|
204
|
+
},
|
|
205
|
+
flush: (controller) => {
|
|
206
|
+
controller.enqueue(this.accumulator);
|
|
207
|
+
controller.terminate();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
this.accumulator = initialValue;
|
|
211
|
+
this.reducer = reducer;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
var retryStream = (createSourceStream, handleChunk2, retryOptions = { forever: true, minTimeout: 25 }) => new TransformStream6({
|
|
215
|
+
start(controller) {
|
|
216
|
+
asyncRetry(
|
|
217
|
+
() => onRestream(createSourceStream, handleChunk2, controller),
|
|
218
|
+
retryOptions
|
|
219
|
+
).catch((error2) => {
|
|
220
|
+
controller.error(error2);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
var onRestream = async (createSourceStream, handleChunk2, controller) => {
|
|
225
|
+
const sourceStream = createSourceStream();
|
|
226
|
+
const reader = sourceStream.getReader();
|
|
227
|
+
try {
|
|
228
|
+
let done;
|
|
229
|
+
do {
|
|
230
|
+
const result = await reader.read();
|
|
231
|
+
done = result.done;
|
|
232
|
+
await handleChunk2(result, controller);
|
|
233
|
+
if (done) {
|
|
234
|
+
controller.terminate();
|
|
235
|
+
}
|
|
236
|
+
} while (!done);
|
|
237
|
+
} finally {
|
|
238
|
+
reader.releaseLock();
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
var skip = (limit) => new SkipTransformStream(limit);
|
|
242
|
+
var SkipTransformStream = class extends TransformStream7 {
|
|
243
|
+
count = 0;
|
|
244
|
+
skip;
|
|
245
|
+
constructor(skip2) {
|
|
246
|
+
super({
|
|
247
|
+
transform: (chunk, controller) => {
|
|
248
|
+
this.count++;
|
|
249
|
+
if (this.count > this.skip) {
|
|
250
|
+
controller.enqueue(chunk);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
this.skip = skip2;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
var stopAfter = (stopCondition) => new TransformStream8({
|
|
258
|
+
transform(chunk, controller) {
|
|
259
|
+
controller.enqueue(chunk);
|
|
260
|
+
if (stopCondition(chunk)) {
|
|
261
|
+
controller.terminate();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
var stopOn = (stopCondition) => new TransformStream9({
|
|
266
|
+
async transform(chunk, controller) {
|
|
267
|
+
if (!stopCondition(chunk)) {
|
|
268
|
+
controller.enqueue(chunk);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
await Promise.resolve();
|
|
272
|
+
controller.terminate();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
var take = (limit) => new TakeTransformStream(limit);
|
|
276
|
+
var TakeTransformStream = class extends TransformStream10 {
|
|
277
|
+
count = 0;
|
|
278
|
+
limit;
|
|
279
|
+
constructor(limit) {
|
|
280
|
+
super({
|
|
281
|
+
transform: (chunk, controller) => {
|
|
282
|
+
if (this.count < this.limit) {
|
|
283
|
+
this.count++;
|
|
284
|
+
controller.enqueue(chunk);
|
|
285
|
+
} else {
|
|
286
|
+
controller.terminate();
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
this.limit = limit;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
var waitAtMost = (waitTimeInMs) => new TransformStream11({
|
|
294
|
+
start(controller) {
|
|
295
|
+
const timeoutId = setTimeout(() => {
|
|
296
|
+
controller.terminate();
|
|
297
|
+
}, waitTimeInMs);
|
|
298
|
+
const originalTerminate = controller.terminate.bind(controller);
|
|
299
|
+
controller.terminate = () => {
|
|
300
|
+
clearTimeout(timeoutId);
|
|
301
|
+
originalTerminate();
|
|
302
|
+
};
|
|
303
|
+
},
|
|
304
|
+
transform(chunk, controller) {
|
|
305
|
+
controller.enqueue(chunk);
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
var streamTransformations = {
|
|
309
|
+
filter,
|
|
310
|
+
take,
|
|
311
|
+
TakeTransformStream,
|
|
312
|
+
skip,
|
|
313
|
+
SkipTransformStream,
|
|
314
|
+
map,
|
|
315
|
+
notifyAboutNoActiveReadersStream,
|
|
316
|
+
NotifyAboutNoActiveReadersStream,
|
|
317
|
+
reduce,
|
|
318
|
+
ReduceTransformStream,
|
|
319
|
+
retry: retryStream,
|
|
320
|
+
stopAfter,
|
|
321
|
+
stopOn,
|
|
322
|
+
waitAtMost
|
|
323
|
+
};
|
|
324
|
+
var { retry: retry2 } = streamTransformations;
|
|
325
|
+
|
|
326
|
+
// src/eventStore/schema/appendToStream.ts
|
|
327
|
+
import { v4 as uuid4 } from "uuid";
|
|
328
|
+
|
|
329
|
+
// src/eventStore/schema/typing.ts
|
|
330
|
+
var emmettPrefix = "emt";
|
|
331
|
+
var globalTag = "global";
|
|
332
|
+
var defaultTag = "emt:default";
|
|
333
|
+
var globalNames = {
|
|
334
|
+
module: `${emmettPrefix}:module:${globalTag}`
|
|
335
|
+
};
|
|
336
|
+
var columns = {
|
|
337
|
+
partition: {
|
|
338
|
+
name: "partition"
|
|
339
|
+
},
|
|
340
|
+
isArchived: { name: "is_archived" }
|
|
341
|
+
};
|
|
342
|
+
var streamsTable = {
|
|
343
|
+
name: `${emmettPrefix}_streams`,
|
|
344
|
+
columns: {
|
|
345
|
+
partition: columns.partition,
|
|
346
|
+
isArchived: columns.isArchived
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
var messagesTable = {
|
|
350
|
+
name: `${emmettPrefix}_messages`,
|
|
351
|
+
columns: {
|
|
352
|
+
partition: columns.partition,
|
|
353
|
+
isArchived: columns.isArchived
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
// src/eventStore/schema/appendToStream.ts
|
|
358
|
+
var appendToStream = async (db, streamName, streamType, messages, options) => {
|
|
359
|
+
if (messages.length === 0) return { success: false };
|
|
360
|
+
const expectedStreamVersion = toExpectedVersion(
|
|
361
|
+
options?.expectedStreamVersion
|
|
362
|
+
);
|
|
363
|
+
const messagesToAppend = messages.map(
|
|
364
|
+
(m, i) => ({
|
|
365
|
+
...m,
|
|
366
|
+
kind: m.kind ?? "Event",
|
|
367
|
+
metadata: {
|
|
368
|
+
streamName,
|
|
369
|
+
messageId: uuid4(),
|
|
370
|
+
streamPosition: BigInt(i + 1),
|
|
371
|
+
..."metadata" in m ? m.metadata ?? {} : {}
|
|
372
|
+
}
|
|
373
|
+
})
|
|
374
|
+
);
|
|
375
|
+
let result;
|
|
376
|
+
await db.command(`BEGIN TRANSACTION`);
|
|
377
|
+
try {
|
|
378
|
+
result = await appendToStreamRaw(
|
|
379
|
+
db,
|
|
380
|
+
streamName,
|
|
381
|
+
streamType,
|
|
382
|
+
messagesToAppend,
|
|
383
|
+
{
|
|
384
|
+
expectedStreamVersion
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
if (options?.preCommitHook) options.preCommitHook(messagesToAppend);
|
|
388
|
+
} catch (err) {
|
|
389
|
+
await db.command(`ROLLBACK`);
|
|
390
|
+
throw err;
|
|
391
|
+
}
|
|
392
|
+
if (result.success == null || !result.success) {
|
|
393
|
+
await db.command(`ROLLBACK`);
|
|
394
|
+
return result;
|
|
395
|
+
}
|
|
396
|
+
await db.command(`COMMIT`);
|
|
397
|
+
return result;
|
|
398
|
+
};
|
|
399
|
+
var toExpectedVersion = (expected) => {
|
|
400
|
+
if (expected === void 0) return null;
|
|
401
|
+
if (expected === NO_CONCURRENCY_CHECK) return null;
|
|
402
|
+
if (expected == STREAM_DOES_NOT_EXIST) return null;
|
|
403
|
+
if (expected == STREAM_EXISTS) return null;
|
|
404
|
+
return expected;
|
|
405
|
+
};
|
|
406
|
+
var appendToStreamRaw = async (db, streamId, streamType, messages, options) => {
|
|
407
|
+
let streamPosition;
|
|
408
|
+
let globalPosition;
|
|
409
|
+
try {
|
|
410
|
+
let expectedStreamVersion = options?.expectedStreamVersion ?? null;
|
|
411
|
+
if (expectedStreamVersion == null) {
|
|
412
|
+
expectedStreamVersion = await getLastStreamPosition(
|
|
413
|
+
db,
|
|
414
|
+
streamId,
|
|
415
|
+
expectedStreamVersion
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
let position;
|
|
419
|
+
if (expectedStreamVersion === 0n) {
|
|
420
|
+
position = await db.querySingle(
|
|
421
|
+
`INSERT INTO ${streamsTable.name}
|
|
422
|
+
(stream_id, stream_position, partition, stream_type, stream_metadata, is_archived)
|
|
423
|
+
VALUES (
|
|
424
|
+
?,
|
|
425
|
+
?,
|
|
426
|
+
?,
|
|
427
|
+
?,
|
|
428
|
+
'[]',
|
|
429
|
+
false
|
|
430
|
+
)
|
|
431
|
+
RETURNING stream_position;
|
|
432
|
+
`,
|
|
433
|
+
[
|
|
434
|
+
streamId,
|
|
435
|
+
messages.length,
|
|
436
|
+
options?.partition ?? streamsTable.columns.partition,
|
|
437
|
+
streamType
|
|
438
|
+
]
|
|
439
|
+
);
|
|
440
|
+
} else {
|
|
441
|
+
position = await db.querySingle(
|
|
442
|
+
`UPDATE ${streamsTable.name}
|
|
443
|
+
SET stream_position = stream_position + ?
|
|
444
|
+
WHERE stream_id = ?
|
|
445
|
+
AND partition = ?
|
|
446
|
+
AND is_archived = false
|
|
447
|
+
RETURNING stream_position;
|
|
448
|
+
`,
|
|
449
|
+
[
|
|
450
|
+
messages.length,
|
|
451
|
+
streamId,
|
|
452
|
+
options?.partition ?? streamsTable.columns.partition
|
|
453
|
+
]
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (position == null) {
|
|
457
|
+
throw new Error("Could not find stream position");
|
|
458
|
+
}
|
|
459
|
+
streamPosition = BigInt(position.stream_position);
|
|
460
|
+
if (expectedStreamVersion != null) {
|
|
461
|
+
const expectedStreamPositionAfterSave = BigInt(expectedStreamVersion) + BigInt(messages.length);
|
|
462
|
+
if (streamPosition !== expectedStreamPositionAfterSave) {
|
|
463
|
+
return {
|
|
464
|
+
success: false
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const { sqlString, values } = buildMessageInsertQuery(
|
|
469
|
+
messages,
|
|
470
|
+
expectedStreamVersion,
|
|
471
|
+
streamId,
|
|
472
|
+
options?.partition?.toString() ?? defaultTag
|
|
473
|
+
);
|
|
474
|
+
const returningId = await db.querySingle(sqlString, values);
|
|
475
|
+
if (returningId?.global_position == null) {
|
|
476
|
+
throw new Error("Could not find global position");
|
|
477
|
+
}
|
|
478
|
+
globalPosition = BigInt(returningId.global_position);
|
|
479
|
+
} catch (err) {
|
|
480
|
+
if (isSQLiteError(err) && isOptimisticConcurrencyError(err)) {
|
|
481
|
+
return {
|
|
482
|
+
success: false
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
throw err;
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
success: true,
|
|
489
|
+
nextStreamPosition: streamPosition,
|
|
490
|
+
lastGlobalPosition: globalPosition
|
|
491
|
+
};
|
|
492
|
+
};
|
|
493
|
+
var isOptimisticConcurrencyError = (error) => {
|
|
494
|
+
return error?.errno !== void 0 && error.errno === 19;
|
|
495
|
+
};
|
|
496
|
+
async function getLastStreamPosition(db, streamId, expectedStreamVersion) {
|
|
497
|
+
const result = await db.querySingle(
|
|
498
|
+
`SELECT CAST(stream_position AS VARCHAR) AS stream_position FROM ${streamsTable.name} WHERE stream_id = ?`,
|
|
499
|
+
[streamId]
|
|
500
|
+
);
|
|
501
|
+
if (result?.stream_position == null) {
|
|
502
|
+
expectedStreamVersion = 0n;
|
|
503
|
+
} else {
|
|
504
|
+
expectedStreamVersion = BigInt(result.stream_position);
|
|
505
|
+
}
|
|
506
|
+
return expectedStreamVersion;
|
|
507
|
+
}
|
|
508
|
+
var buildMessageInsertQuery = (messages, expectedStreamVersion, streamId, partition) => {
|
|
509
|
+
const query = messages.reduce(
|
|
510
|
+
(queryBuilder, message) => {
|
|
511
|
+
if (message.metadata?.streamPosition == null || typeof message.metadata.streamPosition !== "bigint") {
|
|
512
|
+
throw new Error("Stream position is required");
|
|
513
|
+
}
|
|
514
|
+
const streamPosition = BigInt(message.metadata.streamPosition) + BigInt(expectedStreamVersion);
|
|
515
|
+
queryBuilder.parameterMarkers.push(`(?,?,?,?,?,?,?,?,?,?)`);
|
|
516
|
+
queryBuilder.values.push(
|
|
517
|
+
streamId,
|
|
518
|
+
streamPosition.toString() ?? 0,
|
|
519
|
+
partition ?? defaultTag,
|
|
520
|
+
message.kind === "Event" ? "E" : "C",
|
|
521
|
+
JSONParser.stringify(message.data),
|
|
522
|
+
JSONParser.stringify(message.metadata),
|
|
523
|
+
expectedStreamVersion?.toString() ?? 0,
|
|
524
|
+
message.type,
|
|
525
|
+
message.metadata.messageId,
|
|
526
|
+
false
|
|
527
|
+
);
|
|
528
|
+
return queryBuilder;
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
parameterMarkers: [],
|
|
532
|
+
values: []
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
const sqlString = `
|
|
536
|
+
INSERT INTO ${messagesTable.name} (
|
|
537
|
+
stream_id,
|
|
538
|
+
stream_position,
|
|
539
|
+
partition,
|
|
540
|
+
message_kind,
|
|
541
|
+
message_data,
|
|
542
|
+
message_metadata,
|
|
543
|
+
message_schema_version,
|
|
544
|
+
message_type,
|
|
545
|
+
message_id,
|
|
546
|
+
is_archived
|
|
547
|
+
)
|
|
548
|
+
VALUES ${query.parameterMarkers.join(", ")}
|
|
549
|
+
RETURNING
|
|
550
|
+
CAST(global_position as VARCHAR) AS global_position
|
|
551
|
+
`;
|
|
552
|
+
return { sqlString, values: query.values };
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// src/eventStore/SQLiteEventStore.ts
|
|
556
|
+
var SQLiteEventStoreDefaultStreamVersion = 0n;
|
|
557
|
+
var getSQLiteEventStore = (options) => {
|
|
558
|
+
let schemaMigrated = false;
|
|
559
|
+
let autoGenerateSchema = false;
|
|
560
|
+
let database;
|
|
561
|
+
const fileName = options.fileName ?? InMemorySQLiteDatabase;
|
|
562
|
+
const isInMemory = fileName === InMemorySQLiteDatabase;
|
|
563
|
+
const createConnection = () => {
|
|
564
|
+
if (database != null) {
|
|
565
|
+
return database;
|
|
566
|
+
}
|
|
567
|
+
return sqliteConnection({
|
|
568
|
+
fileName
|
|
569
|
+
});
|
|
570
|
+
};
|
|
571
|
+
const closeConnection = () => {
|
|
572
|
+
if (isInMemory) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
if (database != null) {
|
|
576
|
+
database.close();
|
|
577
|
+
database = null;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
const withConnection = async (handler) => {
|
|
581
|
+
if (database == null) {
|
|
582
|
+
database = createConnection();
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
await ensureSchemaExists(database);
|
|
586
|
+
return await handler(database);
|
|
587
|
+
} finally {
|
|
588
|
+
closeConnection();
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
if (options) {
|
|
592
|
+
autoGenerateSchema = options.schema?.autoMigration === void 0 || options.schema?.autoMigration !== "None";
|
|
593
|
+
}
|
|
594
|
+
const ensureSchemaExists = async (connection) => {
|
|
595
|
+
if (!autoGenerateSchema) return Promise.resolve();
|
|
596
|
+
if (!schemaMigrated) {
|
|
597
|
+
await createEventStoreSchema(connection);
|
|
598
|
+
schemaMigrated = true;
|
|
599
|
+
}
|
|
600
|
+
return Promise.resolve();
|
|
601
|
+
};
|
|
602
|
+
return {
|
|
603
|
+
async aggregateStream(streamName, options2) {
|
|
604
|
+
const { evolve, initialState, read } = options2;
|
|
605
|
+
const expectedStreamVersion = read?.expectedStreamVersion;
|
|
606
|
+
let state = initialState();
|
|
607
|
+
if (typeof streamName !== "string") {
|
|
608
|
+
throw new Error("Stream name is not string");
|
|
609
|
+
}
|
|
610
|
+
if (database == null) {
|
|
611
|
+
database = createConnection();
|
|
612
|
+
}
|
|
613
|
+
const result = await withConnection(
|
|
614
|
+
(db) => readStream(db, streamName, options2.read)
|
|
615
|
+
);
|
|
616
|
+
const currentStreamVersion = result.currentStreamVersion;
|
|
617
|
+
assertExpectedVersionMatchesCurrent(
|
|
618
|
+
currentStreamVersion,
|
|
619
|
+
expectedStreamVersion,
|
|
620
|
+
SQLiteEventStoreDefaultStreamVersion
|
|
621
|
+
);
|
|
622
|
+
for (const event of result.events) {
|
|
623
|
+
if (!event) continue;
|
|
624
|
+
state = evolve(state, event);
|
|
625
|
+
}
|
|
626
|
+
return {
|
|
627
|
+
currentStreamVersion,
|
|
628
|
+
state,
|
|
629
|
+
streamExists: result.streamExists
|
|
630
|
+
};
|
|
631
|
+
},
|
|
632
|
+
readStream: async (streamName, options2) => withConnection((db) => readStream(db, streamName, options2)),
|
|
633
|
+
appendToStream: async (streamName, events, options2) => {
|
|
634
|
+
if (database == null) {
|
|
635
|
+
database = createConnection();
|
|
636
|
+
}
|
|
637
|
+
const [firstPart, ...rest] = streamName.split("-");
|
|
638
|
+
const streamType = firstPart && rest.length > 0 ? firstPart : "emt:unknown";
|
|
639
|
+
const appendResult = await withConnection(
|
|
640
|
+
(db) => appendToStream(db, streamName, streamType, events, options2)
|
|
641
|
+
);
|
|
642
|
+
if (!appendResult.success)
|
|
643
|
+
throw new ExpectedVersionConflictError(
|
|
644
|
+
-1n,
|
|
645
|
+
//TODO: Return actual version in case of error
|
|
646
|
+
options2?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK
|
|
647
|
+
);
|
|
648
|
+
return {
|
|
649
|
+
nextExpectedStreamVersion: appendResult.nextStreamPosition,
|
|
650
|
+
lastEventGlobalPosition: appendResult.lastGlobalPosition,
|
|
651
|
+
createdNewStream: appendResult.nextStreamPosition >= BigInt(events.length)
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
// src/eventStore/schema/readStream.ts
|
|
658
|
+
var readStream = async (db, streamId, options) => {
|
|
659
|
+
const fromCondition = options && "from" in options ? `AND stream_position >= ${options.from}` : "";
|
|
660
|
+
const to = Number(
|
|
661
|
+
options && "to" in options ? options.to : options && "maxCount" in options && options.maxCount ? options.from + options.maxCount : NaN
|
|
662
|
+
);
|
|
663
|
+
const toCondition = !isNaN(to) ? `AND stream_position <= ${to}` : "";
|
|
664
|
+
const results = await db.query(
|
|
665
|
+
`SELECT stream_id, stream_position, global_position, message_data, message_metadata, message_schema_version, message_type, message_id
|
|
666
|
+
FROM ${messagesTable.name}
|
|
667
|
+
WHERE stream_id = ? AND partition = ? AND is_archived = FALSE ${fromCondition} ${toCondition}`,
|
|
668
|
+
[streamId, options?.partition ?? defaultTag]
|
|
669
|
+
);
|
|
670
|
+
const messages = results.map((row) => {
|
|
671
|
+
const rawEvent = {
|
|
672
|
+
type: row.message_type,
|
|
673
|
+
data: JSONParser.parse(row.message_data),
|
|
674
|
+
metadata: JSONParser.parse(row.message_metadata)
|
|
675
|
+
};
|
|
676
|
+
const metadata = {
|
|
677
|
+
..."metadata" in rawEvent ? rawEvent.metadata ?? {} : {},
|
|
678
|
+
messageId: row.message_id,
|
|
679
|
+
streamName: streamId,
|
|
680
|
+
streamPosition: BigInt(row.stream_position),
|
|
681
|
+
globalPosition: BigInt(row.global_position)
|
|
682
|
+
};
|
|
683
|
+
return {
|
|
684
|
+
...rawEvent,
|
|
685
|
+
kind: "Event",
|
|
686
|
+
metadata
|
|
687
|
+
};
|
|
688
|
+
});
|
|
689
|
+
return messages.length > 0 ? {
|
|
690
|
+
currentStreamVersion: messages[messages.length - 1].metadata.streamPosition,
|
|
691
|
+
events: messages,
|
|
692
|
+
streamExists: true
|
|
693
|
+
} : {
|
|
694
|
+
currentStreamVersion: SQLiteEventStoreDefaultStreamVersion,
|
|
695
|
+
events: [],
|
|
696
|
+
streamExists: false
|
|
697
|
+
};
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
// src/eventStore/schema/tables.ts
|
|
701
|
+
var sql = (sql2) => sql2;
|
|
702
|
+
var streamsTableSQL = sql(
|
|
703
|
+
`CREATE TABLE IF NOT EXISTS ${streamsTable.name}(
|
|
704
|
+
stream_id TEXT NOT NULL,
|
|
705
|
+
stream_position BIGINT NOT NULL DEFAULT 0,
|
|
706
|
+
partition TEXT NOT NULL DEFAULT '${globalTag}',
|
|
707
|
+
stream_type TEXT NOT NULL,
|
|
708
|
+
stream_metadata JSONB NOT NULL,
|
|
709
|
+
is_archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
710
|
+
PRIMARY KEY (stream_id, stream_position, partition, is_archived),
|
|
711
|
+
UNIQUE (stream_id, partition, is_archived)
|
|
712
|
+
);`
|
|
713
|
+
);
|
|
714
|
+
var messagesTableSQL = sql(
|
|
715
|
+
`CREATE TABLE IF NOT EXISTS ${messagesTable.name}(
|
|
716
|
+
stream_id TEXT NOT NULL,
|
|
717
|
+
stream_position BIGINT NOT NULL,
|
|
718
|
+
partition TEXT NOT NULL DEFAULT '${globalTag}',
|
|
719
|
+
message_kind CHAR(1) NOT NULL DEFAULT 'E',
|
|
720
|
+
message_data JSONB NOT NULL,
|
|
721
|
+
message_metadata JSONB NOT NULL,
|
|
722
|
+
message_schema_version TEXT NOT NULL,
|
|
723
|
+
message_type TEXT NOT NULL,
|
|
724
|
+
message_id TEXT NOT NULL,
|
|
725
|
+
is_archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
726
|
+
global_position INTEGER PRIMARY KEY,
|
|
727
|
+
created DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
728
|
+
UNIQUE (stream_id, stream_position, partition, is_archived)
|
|
729
|
+
);
|
|
730
|
+
`
|
|
731
|
+
);
|
|
732
|
+
var schemaSQL = [streamsTableSQL, messagesTableSQL];
|
|
733
|
+
var createEventStoreSchema = async (db) => {
|
|
734
|
+
for (const sql2 of schemaSQL) {
|
|
735
|
+
await db.command(sql2);
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
export {
|
|
739
|
+
InMemorySQLiteDatabase,
|
|
740
|
+
SQLiteEventStoreDefaultStreamVersion,
|
|
741
|
+
appendToStream,
|
|
742
|
+
createEventStoreSchema,
|
|
743
|
+
defaultTag,
|
|
744
|
+
emmettPrefix,
|
|
745
|
+
getSQLiteEventStore,
|
|
746
|
+
globalNames,
|
|
747
|
+
globalTag,
|
|
748
|
+
isSQLiteError,
|
|
749
|
+
messagesTable,
|
|
750
|
+
messagesTableSQL,
|
|
751
|
+
readStream,
|
|
752
|
+
schemaSQL,
|
|
753
|
+
sql,
|
|
754
|
+
sqliteConnection,
|
|
755
|
+
streamsTable,
|
|
756
|
+
streamsTableSQL
|
|
757
|
+
};
|
|
1
758
|
//# sourceMappingURL=index.js.map
|