@event-driven-io/emmett-postgresql 0.43.0-apha.2 → 0.43.0-beta.10
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 +1723 -1305
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +199 -198
- package/dist/index.d.ts +199 -198
- package/dist/index.js +2086 -1668
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -1,64 +1,3 @@
|
|
|
1
|
-
// src/eventStore/consumers/messageBatchProcessing/index.ts
|
|
2
|
-
import "@event-driven-io/dumbo";
|
|
3
|
-
|
|
4
|
-
// src/eventStore/schema/readLastMessageGlobalPosition.ts
|
|
5
|
-
import { singleOrNull, SQL } from "@event-driven-io/dumbo";
|
|
6
|
-
|
|
7
|
-
// src/eventStore/schema/typing.ts
|
|
8
|
-
var emmettPrefix = "emt";
|
|
9
|
-
var globalTag = "global";
|
|
10
|
-
var defaultTag = `${emmettPrefix}:default`;
|
|
11
|
-
var unknownTag = `${emmettPrefix}:unknown`;
|
|
12
|
-
var globalNames = {
|
|
13
|
-
module: `${emmettPrefix}:module:${globalTag}`,
|
|
14
|
-
tenant: `${emmettPrefix}:tenant:${globalTag}`
|
|
15
|
-
};
|
|
16
|
-
var columns = {
|
|
17
|
-
partition: {
|
|
18
|
-
name: "partition"
|
|
19
|
-
},
|
|
20
|
-
isArchived: { name: "is_archived" }
|
|
21
|
-
};
|
|
22
|
-
var streamsTable = {
|
|
23
|
-
name: `${emmettPrefix}_streams`,
|
|
24
|
-
columns: {
|
|
25
|
-
partition: columns.partition,
|
|
26
|
-
isArchived: columns.isArchived
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
var messagesTable = {
|
|
30
|
-
name: `${emmettPrefix}_messages`,
|
|
31
|
-
columns: {
|
|
32
|
-
partition: columns.partition,
|
|
33
|
-
isArchived: columns.isArchived
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
var processorsTable = {
|
|
37
|
-
name: `${emmettPrefix}_processors`
|
|
38
|
-
};
|
|
39
|
-
var projectionsTable = {
|
|
40
|
-
name: `${emmettPrefix}_projections`
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// src/eventStore/schema/readLastMessageGlobalPosition.ts
|
|
44
|
-
var readLastMessageGlobalPosition = async (execute, options) => {
|
|
45
|
-
const result = await singleOrNull(
|
|
46
|
-
execute.query(
|
|
47
|
-
SQL`SELECT global_position
|
|
48
|
-
FROM ${SQL.identifier(messagesTable.name)}
|
|
49
|
-
WHERE partition = ${options?.partition ?? defaultTag} AND is_archived = FALSE AND transaction_id < pg_snapshot_xmin(pg_current_snapshot())
|
|
50
|
-
ORDER BY transaction_id, global_position
|
|
51
|
-
LIMIT 1`
|
|
52
|
-
)
|
|
53
|
-
);
|
|
54
|
-
return {
|
|
55
|
-
currentGlobalPosition: result !== null ? BigInt(result.global_position) : null
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// src/eventStore/schema/readMessagesBatch.ts
|
|
60
|
-
import { mapRows, SQL as SQL2 } from "@event-driven-io/dumbo";
|
|
61
|
-
|
|
62
1
|
// ../emmett/dist/chunk-AZDDB5SF.js
|
|
63
2
|
var isNumber = (val) => typeof val === "number" && val === val;
|
|
64
3
|
var isBigint = (val) => typeof val === "bigint" && val === val;
|
|
@@ -109,14 +48,26 @@ var ConcurrencyError = class _ConcurrencyError extends EmmettError {
|
|
|
109
48
|
};
|
|
110
49
|
|
|
111
50
|
// ../emmett/dist/index.js
|
|
112
|
-
import { v4 as
|
|
51
|
+
import { v4 as uuid5 } from "uuid";
|
|
52
|
+
import { v7 as uuid2 } from "uuid";
|
|
113
53
|
import { v7 as uuid } from "uuid";
|
|
114
54
|
import retry from "async-retry";
|
|
115
|
-
import {
|
|
116
|
-
import {
|
|
117
|
-
|
|
118
|
-
var
|
|
119
|
-
var
|
|
55
|
+
import { v7 as uuid3 } from "uuid";
|
|
56
|
+
import { v4 as uuid4 } from "uuid";
|
|
57
|
+
import { v7 as uuid6 } from "uuid";
|
|
58
|
+
var emmettPrefix = "emt";
|
|
59
|
+
var defaultTag = `${emmettPrefix}:default`;
|
|
60
|
+
var unknownTag = `${emmettPrefix}:unknown`;
|
|
61
|
+
var canCreateEventStoreSession = (eventStore) => "withSession" in eventStore;
|
|
62
|
+
var nulloSessionFactory = (eventStore) => ({
|
|
63
|
+
withSession: (callback) => {
|
|
64
|
+
const nulloSession = {
|
|
65
|
+
eventStore,
|
|
66
|
+
close: () => Promise.resolve()
|
|
67
|
+
};
|
|
68
|
+
return callback(nulloSession);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
120
71
|
var STREAM_EXISTS = "STREAM_EXISTS";
|
|
121
72
|
var STREAM_DOES_NOT_EXIST = "STREAM_DOES_NOT_EXIST";
|
|
122
73
|
var NO_CONCURRENCY_CHECK = "NO_CONCURRENCY_CHECK";
|
|
@@ -137,6 +88,10 @@ var ExpectedVersionConflictError = class _ExpectedVersionConflictError extends C
|
|
|
137
88
|
Object.setPrototypeOf(this, _ExpectedVersionConflictError.prototype);
|
|
138
89
|
}
|
|
139
90
|
};
|
|
91
|
+
var isExpectedVersionConflictError = (error) => error instanceof ExpectedVersionConflictError || EmmettError.isInstanceOf(
|
|
92
|
+
error,
|
|
93
|
+
ExpectedVersionConflictError.Codes.ConcurrencyError
|
|
94
|
+
);
|
|
140
95
|
var isPrimitive = (value) => {
|
|
141
96
|
const type = typeof value;
|
|
142
97
|
return value === null || value === void 0 || type === "boolean" || type === "number" || type === "string" || type === "symbol" || type === "bigint";
|
|
@@ -342,27 +297,110 @@ var toNormalizedString = (value) => value.toString().padStart(19, "0");
|
|
|
342
297
|
var bigInt = {
|
|
343
298
|
toNormalizedString
|
|
344
299
|
};
|
|
345
|
-
var
|
|
346
|
-
|
|
347
|
-
|
|
300
|
+
var bigIntReplacer = (_key, value) => {
|
|
301
|
+
return typeof value === "bigint" ? value.toString() : value;
|
|
302
|
+
};
|
|
303
|
+
var dateReplacer = (_key, value) => {
|
|
304
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
305
|
+
};
|
|
306
|
+
var isFirstLetterNumeric = (str) => {
|
|
307
|
+
const c = str.charCodeAt(0);
|
|
308
|
+
return c >= 48 && c <= 57;
|
|
309
|
+
};
|
|
310
|
+
var isFirstLetterNumericOrMinus = (str) => {
|
|
311
|
+
const c = str.charCodeAt(0);
|
|
312
|
+
return c >= 48 && c <= 57 || c === 45;
|
|
313
|
+
};
|
|
314
|
+
var bigIntReviver = (_key, value, context) => {
|
|
315
|
+
if (typeof value === "number" && Number.isInteger(value) && !Number.isSafeInteger(value)) {
|
|
316
|
+
try {
|
|
317
|
+
return BigInt(context?.source ?? value.toString());
|
|
318
|
+
} catch {
|
|
319
|
+
return value;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (typeof value === "string" && value.length > 15) {
|
|
323
|
+
if (isFirstLetterNumericOrMinus(value)) {
|
|
324
|
+
const num = Number(value);
|
|
325
|
+
if (Number.isFinite(num) && !Number.isSafeInteger(num)) {
|
|
326
|
+
try {
|
|
327
|
+
return BigInt(value);
|
|
328
|
+
} catch {
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
348
332
|
}
|
|
333
|
+
return value;
|
|
349
334
|
};
|
|
350
|
-
var
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
(_, v) => typeof v === "bigint" ? v.toString() : v
|
|
357
|
-
);
|
|
358
|
-
},
|
|
359
|
-
parse: (text, options) => {
|
|
360
|
-
const parsed = JSON.parse(text, options?.reviver);
|
|
361
|
-
if (options?.typeCheck && !options?.typeCheck(parsed))
|
|
362
|
-
throw new ParseError(text);
|
|
363
|
-
return options?.map ? options.map(parsed) : parsed;
|
|
335
|
+
var dateReviver = (_key, value) => {
|
|
336
|
+
if (typeof value === "string" && value.length === 24 && isFirstLetterNumeric(value) && value[10] === "T" && value[23] === "Z") {
|
|
337
|
+
const date = new Date(value);
|
|
338
|
+
if (!isNaN(date.getTime())) {
|
|
339
|
+
return date;
|
|
340
|
+
}
|
|
364
341
|
}
|
|
342
|
+
return value;
|
|
343
|
+
};
|
|
344
|
+
var composeJSONReplacers = (...replacers) => {
|
|
345
|
+
const filteredReplacers = replacers.filter((r) => r !== void 0);
|
|
346
|
+
if (filteredReplacers.length === 0) return void 0;
|
|
347
|
+
return (key, value) => (
|
|
348
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
349
|
+
filteredReplacers.reduce(
|
|
350
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
351
|
+
(accValue, replacer) => replacer(key, accValue),
|
|
352
|
+
value
|
|
353
|
+
)
|
|
354
|
+
);
|
|
355
|
+
};
|
|
356
|
+
var composeJSONRevivers = (...revivers) => {
|
|
357
|
+
const filteredRevivers = revivers.filter((r) => r !== void 0);
|
|
358
|
+
if (filteredRevivers.length === 0) return void 0;
|
|
359
|
+
return (key, value, context) => (
|
|
360
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
361
|
+
filteredRevivers.reduce(
|
|
362
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
363
|
+
(accValue, reviver) => reviver(key, accValue, context),
|
|
364
|
+
value
|
|
365
|
+
)
|
|
366
|
+
);
|
|
367
|
+
};
|
|
368
|
+
var JSONReplacer = (opts) => composeJSONReplacers(
|
|
369
|
+
opts?.replacer,
|
|
370
|
+
opts?.failOnBigIntSerialization !== true ? JSONReplacers.bigInt : void 0,
|
|
371
|
+
opts?.useDefaultDateSerialization !== true ? JSONReplacers.date : void 0
|
|
372
|
+
);
|
|
373
|
+
var JSONReviver = (opts) => composeJSONRevivers(
|
|
374
|
+
opts?.reviver,
|
|
375
|
+
opts?.parseBigInts === true ? JSONRevivers.bigInt : void 0,
|
|
376
|
+
opts?.parseDates === true ? JSONRevivers.date : void 0
|
|
377
|
+
);
|
|
378
|
+
var JSONReplacers = {
|
|
379
|
+
bigInt: bigIntReplacer,
|
|
380
|
+
date: dateReplacer
|
|
381
|
+
};
|
|
382
|
+
var JSONRevivers = {
|
|
383
|
+
bigInt: bigIntReviver,
|
|
384
|
+
date: dateReviver
|
|
385
|
+
};
|
|
386
|
+
var jsonSerializer = (options) => {
|
|
387
|
+
const defaultReplacer = JSONReplacer(options);
|
|
388
|
+
const defaultReviver = JSONReviver(options);
|
|
389
|
+
return {
|
|
390
|
+
serialize: (object, serializerOptions) => JSON.stringify(
|
|
391
|
+
object,
|
|
392
|
+
serializerOptions ? JSONReplacer(serializerOptions) : defaultReplacer
|
|
393
|
+
),
|
|
394
|
+
deserialize: (payload, deserializerOptions) => JSON.parse(
|
|
395
|
+
payload,
|
|
396
|
+
deserializerOptions ? JSONReviver(deserializerOptions) : defaultReviver
|
|
397
|
+
)
|
|
398
|
+
};
|
|
365
399
|
};
|
|
400
|
+
var JSONSerializer = Object.assign(jsonSerializer(), {
|
|
401
|
+
from: (options) => options?.serialization?.serializer ?? (options?.serialization?.options ? jsonSerializer(options?.serialization?.options) : JSONSerializer)
|
|
402
|
+
});
|
|
403
|
+
var NoRetries = { retries: 0 };
|
|
366
404
|
var asyncRetry = async (fn, opts) => {
|
|
367
405
|
if (opts === void 0 || opts.retries === 0) return fn();
|
|
368
406
|
return retry(
|
|
@@ -371,16 +409,16 @@ var asyncRetry = async (fn, opts) => {
|
|
|
371
409
|
const result = await fn();
|
|
372
410
|
if (opts?.shouldRetryResult && opts.shouldRetryResult(result)) {
|
|
373
411
|
throw new EmmettError(
|
|
374
|
-
`Retrying because of result: ${
|
|
412
|
+
`Retrying because of result: ${JSONSerializer.serialize(result)}`
|
|
375
413
|
);
|
|
376
414
|
}
|
|
377
415
|
return result;
|
|
378
|
-
} catch (
|
|
379
|
-
if (opts?.shouldRetryError && !opts.shouldRetryError(
|
|
380
|
-
bail(
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (opts?.shouldRetryError && !opts.shouldRetryError(error)) {
|
|
418
|
+
bail(error);
|
|
381
419
|
return void 0;
|
|
382
420
|
}
|
|
383
|
-
throw
|
|
421
|
+
throw error;
|
|
384
422
|
}
|
|
385
423
|
},
|
|
386
424
|
opts ?? { retries: 0 }
|
|
@@ -421,191 +459,23 @@ var hashText = async (text) => {
|
|
|
421
459
|
const view = new BigInt64Array(hashBuffer, 0, 1);
|
|
422
460
|
return view[0];
|
|
423
461
|
};
|
|
424
|
-
var AssertionError = class extends Error {
|
|
425
|
-
constructor(message2) {
|
|
426
|
-
super(message2);
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
var isSubset = (superObj, subObj) => {
|
|
430
|
-
const sup = superObj;
|
|
431
|
-
const sub = subObj;
|
|
432
|
-
assertOk(sup);
|
|
433
|
-
assertOk(sub);
|
|
434
|
-
return Object.keys(sub).every((ele) => {
|
|
435
|
-
if (typeof sub[ele] == "object") {
|
|
436
|
-
return isSubset(sup[ele], sub[ele]);
|
|
437
|
-
}
|
|
438
|
-
return sub[ele] === sup[ele];
|
|
439
|
-
});
|
|
440
|
-
};
|
|
441
|
-
var assertFails = (message2) => {
|
|
442
|
-
throw new AssertionError(message2 ?? "That should not ever happened, right?");
|
|
443
|
-
};
|
|
444
|
-
var assertDeepEqual = (actual, expected, message2) => {
|
|
445
|
-
if (!deepEquals(actual, expected))
|
|
446
|
-
throw new AssertionError(
|
|
447
|
-
message2 ?? `subObj:
|
|
448
|
-
${JSONParser.stringify(expected)}
|
|
449
|
-
is not equal to
|
|
450
|
-
${JSONParser.stringify(actual)}`
|
|
451
|
-
);
|
|
452
|
-
};
|
|
453
|
-
function assertTrue(condition, message2) {
|
|
454
|
-
if (condition !== true)
|
|
455
|
-
throw new AssertionError(message2 ?? `Condition is false`);
|
|
456
|
-
}
|
|
457
|
-
function assertOk(obj, message2) {
|
|
458
|
-
if (!obj) throw new AssertionError(message2 ?? `Condition is not truthy`);
|
|
459
|
-
}
|
|
460
|
-
function assertEqual(expected, actual, message2) {
|
|
461
|
-
if (expected !== actual)
|
|
462
|
-
throw new AssertionError(
|
|
463
|
-
`${message2 ?? "Objects are not equal"}:
|
|
464
|
-
Expected: ${JSONParser.stringify(expected)}
|
|
465
|
-
Actual: ${JSONParser.stringify(actual)}`
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
function assertNotEqual(obj, other, message2) {
|
|
469
|
-
if (obj === other)
|
|
470
|
-
throw new AssertionError(
|
|
471
|
-
message2 ?? `Objects are equal: ${JSONParser.stringify(obj)}`
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
function assertIsNotNull(result) {
|
|
475
|
-
assertNotEqual(result, null);
|
|
476
|
-
assertOk(result);
|
|
477
|
-
}
|
|
478
|
-
var assertThatArray = (array) => {
|
|
479
|
-
return {
|
|
480
|
-
isEmpty: () => assertEqual(
|
|
481
|
-
array.length,
|
|
482
|
-
0,
|
|
483
|
-
`Array is not empty ${JSONParser.stringify(array)}`
|
|
484
|
-
),
|
|
485
|
-
isNotEmpty: () => assertNotEqual(array.length, 0, `Array is empty`),
|
|
486
|
-
hasSize: (length) => assertEqual(array.length, length),
|
|
487
|
-
containsElements: (other) => {
|
|
488
|
-
assertTrue(other.every((ts) => array.some((o) => deepEquals(ts, o))));
|
|
489
|
-
},
|
|
490
|
-
containsElementsMatching: (other) => {
|
|
491
|
-
assertTrue(other.every((ts) => array.some((o) => isSubset(o, ts))));
|
|
492
|
-
},
|
|
493
|
-
containsOnlyElementsMatching: (other) => {
|
|
494
|
-
assertEqual(array.length, other.length, `Arrays lengths don't match`);
|
|
495
|
-
assertTrue(other.every((ts) => array.some((o) => isSubset(o, ts))));
|
|
496
|
-
},
|
|
497
|
-
containsExactlyInAnyOrder: (other) => {
|
|
498
|
-
assertEqual(array.length, other.length);
|
|
499
|
-
assertTrue(array.every((ts) => other.some((o) => deepEquals(ts, o))));
|
|
500
|
-
},
|
|
501
|
-
containsExactlyInAnyOrderElementsOf: (other) => {
|
|
502
|
-
assertEqual(array.length, other.length);
|
|
503
|
-
assertTrue(array.every((ts) => other.some((o) => deepEquals(ts, o))));
|
|
504
|
-
},
|
|
505
|
-
containsExactlyElementsOf: (other) => {
|
|
506
|
-
assertEqual(array.length, other.length);
|
|
507
|
-
for (let i = 0; i < array.length; i++) {
|
|
508
|
-
assertTrue(deepEquals(array[i], other[i]));
|
|
509
|
-
}
|
|
510
|
-
},
|
|
511
|
-
containsExactly: (elem) => {
|
|
512
|
-
assertEqual(array.length, 1);
|
|
513
|
-
assertTrue(deepEquals(array[0], elem));
|
|
514
|
-
},
|
|
515
|
-
contains: (elem) => {
|
|
516
|
-
assertTrue(array.some((a) => deepEquals(a, elem)));
|
|
517
|
-
},
|
|
518
|
-
containsOnlyOnceElementsOf: (other) => {
|
|
519
|
-
assertTrue(
|
|
520
|
-
other.map((o) => array.filter((a) => deepEquals(a, o)).length).filter((a) => a === 1).length === other.length
|
|
521
|
-
);
|
|
522
|
-
},
|
|
523
|
-
containsAnyOf: (other) => {
|
|
524
|
-
assertTrue(array.some((a) => other.some((o) => deepEquals(a, o))));
|
|
525
|
-
},
|
|
526
|
-
allMatch: (matches) => {
|
|
527
|
-
assertTrue(array.every(matches));
|
|
528
|
-
},
|
|
529
|
-
anyMatches: (matches) => {
|
|
530
|
-
assertTrue(array.some(matches));
|
|
531
|
-
},
|
|
532
|
-
allMatchAsync: async (matches) => {
|
|
533
|
-
for (const item of array) {
|
|
534
|
-
assertTrue(await matches(item));
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
};
|
|
539
|
-
var downcastRecordedMessage = (recordedMessage, options) => {
|
|
540
|
-
if (!options?.downcast)
|
|
541
|
-
return recordedMessage;
|
|
542
|
-
const downcasted = options.downcast(
|
|
543
|
-
recordedMessage
|
|
544
|
-
);
|
|
545
|
-
return {
|
|
546
|
-
...recordedMessage,
|
|
547
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
548
|
-
data: downcasted.data,
|
|
549
|
-
..."metadata" in recordedMessage || "metadata" in downcasted ? {
|
|
550
|
-
metadata: {
|
|
551
|
-
..."metadata" in recordedMessage ? recordedMessage.metadata : {},
|
|
552
|
-
..."metadata" in downcasted ? downcasted.metadata : {}
|
|
553
|
-
}
|
|
554
|
-
} : {}
|
|
555
|
-
};
|
|
556
|
-
};
|
|
557
|
-
var downcastRecordedMessages = (recordedMessages, options) => {
|
|
558
|
-
if (!options?.downcast)
|
|
559
|
-
return recordedMessages;
|
|
560
|
-
return recordedMessages.map(
|
|
561
|
-
(recordedMessage) => downcastRecordedMessage(recordedMessage, options)
|
|
562
|
-
);
|
|
563
|
-
};
|
|
564
|
-
var upcastRecordedMessage = (recordedMessage, options) => {
|
|
565
|
-
if (!options?.upcast)
|
|
566
|
-
return recordedMessage;
|
|
567
|
-
const upcasted = options.upcast(
|
|
568
|
-
recordedMessage
|
|
569
|
-
);
|
|
570
|
-
return {
|
|
571
|
-
...recordedMessage,
|
|
572
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
573
|
-
data: upcasted.data,
|
|
574
|
-
..."metadata" in recordedMessage || "metadata" in upcasted ? {
|
|
575
|
-
metadata: {
|
|
576
|
-
..."metadata" in recordedMessage ? recordedMessage.metadata : {},
|
|
577
|
-
..."metadata" in upcasted ? upcasted.metadata : {}
|
|
578
|
-
}
|
|
579
|
-
} : {}
|
|
580
|
-
};
|
|
581
|
-
};
|
|
582
462
|
var getCheckpoint = (message2) => {
|
|
583
|
-
return
|
|
584
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
585
|
-
message2.metadata.checkpoint
|
|
586
|
-
) : "globalPosition" in message2.metadata && // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
587
|
-
isBigint(message2.metadata.globalPosition) ? (
|
|
588
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
589
|
-
message2.metadata.globalPosition
|
|
590
|
-
) : "streamPosition" in message2.metadata && // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
591
|
-
isBigint(message2.metadata.streamPosition) ? (
|
|
592
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
593
|
-
message2.metadata.streamPosition
|
|
594
|
-
) : null;
|
|
463
|
+
return message2.metadata.checkpoint;
|
|
595
464
|
};
|
|
596
465
|
var wasMessageHandled = (message2, checkpoint) => {
|
|
597
466
|
const messageCheckpoint = getCheckpoint(message2);
|
|
598
|
-
|
|
599
|
-
return messageCheckpoint !== null && messageCheckpoint !== void 0 && checkpointBigint !== null && checkpointBigint !== void 0 && messageCheckpoint <= checkpointBigint;
|
|
467
|
+
return messageCheckpoint !== null && messageCheckpoint !== void 0 && checkpoint !== null && checkpoint !== void 0 && messageCheckpoint <= checkpoint;
|
|
600
468
|
};
|
|
601
469
|
var MessageProcessorType = {
|
|
602
470
|
PROJECTOR: "projector",
|
|
603
471
|
REACTOR: "reactor"
|
|
604
472
|
};
|
|
605
473
|
var defaultProcessingMessageProcessingScope = (handler, partialContext) => handler(partialContext);
|
|
474
|
+
var bigIntProcessorCheckpoint = (value) => bigInt.toNormalizedString(value);
|
|
475
|
+
var parseBigIntProcessorCheckpoint = (value) => BigInt(value);
|
|
606
476
|
var defaultProcessorVersion = 1;
|
|
607
|
-
var defaultProcessorPartition =
|
|
608
|
-
var getProcessorInstanceId = (processorId) => `${processorId}:${
|
|
477
|
+
var defaultProcessorPartition = defaultTag;
|
|
478
|
+
var getProcessorInstanceId = (processorId) => `${processorId}:${uuid3()}`;
|
|
609
479
|
var getProjectorId = (options) => `emt:processor:projector:${options.projectionName}`;
|
|
610
480
|
var reactor = (options) => {
|
|
611
481
|
const {
|
|
@@ -652,12 +522,13 @@ var reactor = (options) => {
|
|
|
652
522
|
id: processorId,
|
|
653
523
|
instanceId,
|
|
654
524
|
type,
|
|
525
|
+
canHandle,
|
|
655
526
|
init,
|
|
656
527
|
start: async (startOptions) => {
|
|
657
528
|
if (isActive) return;
|
|
658
529
|
await init(startOptions);
|
|
659
530
|
isActive = true;
|
|
660
|
-
closeSignal = onShutdown(() => close(
|
|
531
|
+
closeSignal = onShutdown(() => close(startOptions));
|
|
661
532
|
if (lastCheckpoint !== null)
|
|
662
533
|
return {
|
|
663
534
|
lastCheckpoint
|
|
@@ -760,138 +631,638 @@ var projector = (options) => {
|
|
|
760
631
|
eachMessage: async (event2, context) => projection2.handle([event2], context)
|
|
761
632
|
});
|
|
762
633
|
};
|
|
763
|
-
var
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
const
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
634
|
+
var AssertionError = class extends Error {
|
|
635
|
+
constructor(message2) {
|
|
636
|
+
super(message2);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
var isSubset = (superObj, subObj) => {
|
|
640
|
+
const sup = superObj;
|
|
641
|
+
const sub = subObj;
|
|
642
|
+
assertOk(sup);
|
|
643
|
+
assertOk(sub);
|
|
644
|
+
return Object.keys(sub).every((ele) => {
|
|
645
|
+
if (sub[ele] !== null && typeof sub[ele] == "object") {
|
|
646
|
+
return isSubset(sup[ele], sub[ele]);
|
|
647
|
+
}
|
|
648
|
+
return sub[ele] === sup[ele];
|
|
649
|
+
});
|
|
650
|
+
};
|
|
651
|
+
var assertFails = (message2) => {
|
|
652
|
+
throw new AssertionError(message2 ?? "That should not ever happened, right?");
|
|
653
|
+
};
|
|
654
|
+
var assertDeepEqual = (actual, expected, message2) => {
|
|
655
|
+
if (!deepEquals(actual, expected))
|
|
656
|
+
throw new AssertionError(
|
|
657
|
+
message2 ?? `subObj:
|
|
658
|
+
${JSONSerializer.serialize(expected)}
|
|
659
|
+
is not equal to
|
|
660
|
+
${JSONSerializer.serialize(actual)}`
|
|
661
|
+
);
|
|
662
|
+
};
|
|
663
|
+
function assertTrue(condition, message2) {
|
|
664
|
+
if (condition !== true)
|
|
665
|
+
throw new AssertionError(message2 ?? `Condition is false`);
|
|
666
|
+
}
|
|
667
|
+
function assertOk(obj, message2) {
|
|
668
|
+
if (!obj) throw new AssertionError(message2 ?? `Condition is not truthy`);
|
|
669
|
+
}
|
|
670
|
+
function assertEqual(expected, actual, message2) {
|
|
671
|
+
if (expected !== actual)
|
|
672
|
+
throw new AssertionError(
|
|
673
|
+
`${message2 ?? "Objects are not equal"}:
|
|
674
|
+
Expected: ${JSONSerializer.serialize(expected)}
|
|
675
|
+
Actual: ${JSONSerializer.serialize(actual)}`
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
function assertNotEqual(obj, other, message2) {
|
|
679
|
+
if (obj === other)
|
|
680
|
+
throw new AssertionError(
|
|
681
|
+
message2 ?? `Objects are equal: ${JSONSerializer.serialize(obj)}`
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
function assertIsNotNull(result) {
|
|
685
|
+
assertNotEqual(result, null);
|
|
686
|
+
assertOk(result);
|
|
687
|
+
}
|
|
688
|
+
function assertIsNull(result) {
|
|
689
|
+
assertEqual(result, null);
|
|
690
|
+
}
|
|
691
|
+
var assertThatArray = (array) => {
|
|
692
|
+
return {
|
|
693
|
+
isEmpty: () => assertEqual(
|
|
694
|
+
array.length,
|
|
695
|
+
0,
|
|
696
|
+
`Array is not empty ${JSONSerializer.serialize(array)}`
|
|
780
697
|
),
|
|
781
|
-
(
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
698
|
+
isNotEmpty: () => assertNotEqual(array.length, 0, `Array is empty`),
|
|
699
|
+
hasSize: (length) => assertEqual(array.length, length),
|
|
700
|
+
containsElements: (other) => {
|
|
701
|
+
assertTrue(other.every((ts) => array.some((o) => deepEquals(ts, o))));
|
|
702
|
+
},
|
|
703
|
+
containsElementsMatching: (other) => {
|
|
704
|
+
assertTrue(other.every((ts) => array.some((o) => isSubset(o, ts))));
|
|
705
|
+
},
|
|
706
|
+
containsOnlyElementsMatching: (other) => {
|
|
707
|
+
assertEqual(array.length, other.length, `Arrays lengths don't match`);
|
|
708
|
+
assertTrue(other.every((ts) => array.some((o) => isSubset(o, ts))));
|
|
709
|
+
},
|
|
710
|
+
containsExactlyInAnyOrder: (other) => {
|
|
711
|
+
assertEqual(array.length, other.length);
|
|
712
|
+
assertTrue(array.every((ts) => other.some((o) => deepEquals(ts, o))));
|
|
713
|
+
},
|
|
714
|
+
containsExactlyInAnyOrderElementsOf: (other) => {
|
|
715
|
+
assertEqual(array.length, other.length);
|
|
716
|
+
assertTrue(array.every((ts) => other.some((o) => deepEquals(ts, o))));
|
|
717
|
+
},
|
|
718
|
+
containsExactlyElementsOf: (other) => {
|
|
719
|
+
assertEqual(array.length, other.length);
|
|
720
|
+
for (let i = 0; i < array.length; i++) {
|
|
721
|
+
assertTrue(deepEquals(array[i], other[i]));
|
|
722
|
+
}
|
|
723
|
+
},
|
|
724
|
+
containsExactly: (elem) => {
|
|
725
|
+
assertEqual(array.length, 1);
|
|
726
|
+
assertTrue(deepEquals(array[0], elem));
|
|
727
|
+
},
|
|
728
|
+
contains: (elem) => {
|
|
729
|
+
assertTrue(array.some((a) => deepEquals(a, elem)));
|
|
730
|
+
},
|
|
731
|
+
containsOnlyOnceElementsOf: (other) => {
|
|
732
|
+
assertTrue(
|
|
733
|
+
other.map((o) => array.filter((a) => deepEquals(a, o)).length).filter((a) => a === 1).length === other.length
|
|
734
|
+
);
|
|
735
|
+
},
|
|
736
|
+
containsAnyOf: (other) => {
|
|
737
|
+
assertTrue(array.some((a) => other.some((o) => deepEquals(a, o))));
|
|
738
|
+
},
|
|
739
|
+
allMatch: (matches) => {
|
|
740
|
+
assertTrue(array.every(matches));
|
|
741
|
+
},
|
|
742
|
+
anyMatches: (matches) => {
|
|
743
|
+
assertTrue(array.some(matches));
|
|
744
|
+
},
|
|
745
|
+
allMatchAsync: async (matches) => {
|
|
746
|
+
for (const item of array) {
|
|
747
|
+
assertTrue(await matches(item));
|
|
748
|
+
}
|
|
799
749
|
}
|
|
800
|
-
);
|
|
801
|
-
return messages.length > 0 ? {
|
|
802
|
-
currentGlobalPosition: messages[messages.length - 1].metadata.globalPosition,
|
|
803
|
-
messages,
|
|
804
|
-
areMessagesLeft: messages.length === batchSize
|
|
805
|
-
} : {
|
|
806
|
-
currentGlobalPosition: "from" in options ? options.from : "after" in options ? options.after : 0n,
|
|
807
|
-
messages: [],
|
|
808
|
-
areMessagesLeft: false
|
|
809
750
|
};
|
|
810
751
|
};
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
const pullMessages = async (options) => {
|
|
826
|
-
const after = options.startFrom === "BEGINNING" ? 0n : options.startFrom === "END" ? (await readLastMessageGlobalPosition(executor)).currentGlobalPosition ?? 0n : options.startFrom.lastCheckpoint;
|
|
827
|
-
const readMessagesOptions = {
|
|
828
|
-
after,
|
|
829
|
-
batchSize
|
|
830
|
-
};
|
|
831
|
-
let waitTime = 100;
|
|
832
|
-
while (isRunning && !signal?.aborted) {
|
|
833
|
-
const { messages, currentGlobalPosition, areMessagesLeft } = await readMessagesBatch(executor, readMessagesOptions);
|
|
834
|
-
if (messages.length > 0) {
|
|
835
|
-
const result = await eachBatch(messages);
|
|
836
|
-
if (result && result.type === "STOP") {
|
|
837
|
-
isRunning = false;
|
|
838
|
-
break;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
readMessagesOptions.after = currentGlobalPosition;
|
|
842
|
-
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
843
|
-
if (stopWhen?.noMessagesLeft === true && !areMessagesLeft) {
|
|
844
|
-
isRunning = false;
|
|
845
|
-
break;
|
|
752
|
+
var downcastRecordedMessage = (recordedMessage, options) => {
|
|
753
|
+
if (!options?.downcast)
|
|
754
|
+
return recordedMessage;
|
|
755
|
+
const downcasted = options.downcast(
|
|
756
|
+
recordedMessage
|
|
757
|
+
);
|
|
758
|
+
return {
|
|
759
|
+
...recordedMessage,
|
|
760
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
761
|
+
data: downcasted.data,
|
|
762
|
+
..."metadata" in recordedMessage || "metadata" in downcasted ? {
|
|
763
|
+
metadata: {
|
|
764
|
+
..."metadata" in recordedMessage ? recordedMessage.metadata : {},
|
|
765
|
+
..."metadata" in downcasted ? downcasted.metadata : {}
|
|
846
766
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
767
|
+
} : {}
|
|
768
|
+
};
|
|
769
|
+
};
|
|
770
|
+
var downcastRecordedMessages = (recordedMessages, options) => {
|
|
771
|
+
if (!options?.downcast)
|
|
772
|
+
return recordedMessages;
|
|
773
|
+
return recordedMessages.map(
|
|
774
|
+
(recordedMessage) => downcastRecordedMessage(recordedMessage, options)
|
|
775
|
+
);
|
|
776
|
+
};
|
|
777
|
+
var upcastRecordedMessage = (recordedMessage, options) => {
|
|
778
|
+
if (!options?.upcast)
|
|
779
|
+
return recordedMessage;
|
|
780
|
+
const upcasted = options.upcast(
|
|
781
|
+
recordedMessage
|
|
782
|
+
);
|
|
783
|
+
return {
|
|
784
|
+
...recordedMessage,
|
|
785
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
786
|
+
data: upcasted.data,
|
|
787
|
+
..."metadata" in recordedMessage || "metadata" in upcasted ? {
|
|
788
|
+
metadata: {
|
|
789
|
+
..."metadata" in recordedMessage ? recordedMessage.metadata : {},
|
|
790
|
+
..."metadata" in upcasted ? upcasted.metadata : {}
|
|
851
791
|
}
|
|
852
|
-
}
|
|
792
|
+
} : {}
|
|
853
793
|
};
|
|
794
|
+
};
|
|
795
|
+
var projection = (definition) => definition;
|
|
796
|
+
var WorkflowHandlerStreamVersionConflictRetryOptions = {
|
|
797
|
+
retries: 3,
|
|
798
|
+
minTimeout: 100,
|
|
799
|
+
factor: 1.5,
|
|
800
|
+
shouldRetryError: isExpectedVersionConflictError
|
|
801
|
+
};
|
|
802
|
+
var fromWorkflowHandlerRetryOptions = (retryOptions) => {
|
|
803
|
+
if (retryOptions === void 0) return NoRetries;
|
|
804
|
+
if ("onVersionConflict" in retryOptions) {
|
|
805
|
+
if (typeof retryOptions.onVersionConflict === "boolean")
|
|
806
|
+
return WorkflowHandlerStreamVersionConflictRetryOptions;
|
|
807
|
+
else if (typeof retryOptions.onVersionConflict === "number")
|
|
808
|
+
return {
|
|
809
|
+
...WorkflowHandlerStreamVersionConflictRetryOptions,
|
|
810
|
+
retries: retryOptions.onVersionConflict
|
|
811
|
+
};
|
|
812
|
+
else return retryOptions.onVersionConflict;
|
|
813
|
+
}
|
|
814
|
+
return retryOptions;
|
|
815
|
+
};
|
|
816
|
+
var emptyHandlerResult = (nextExpectedStreamVersion = 0n) => ({
|
|
817
|
+
newMessages: [],
|
|
818
|
+
createdNewStream: false,
|
|
819
|
+
nextExpectedStreamVersion
|
|
820
|
+
});
|
|
821
|
+
var createInputMetadata = (originalMessageId, action) => ({
|
|
822
|
+
originalMessageId,
|
|
823
|
+
input: true,
|
|
824
|
+
action
|
|
825
|
+
});
|
|
826
|
+
var tagOutputMessage = (msg, action) => {
|
|
827
|
+
const existingMetadata = "metadata" in msg && msg.metadata ? msg.metadata : {};
|
|
854
828
|
return {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
if (isRunning) return start;
|
|
860
|
-
isRunning = true;
|
|
861
|
-
start = (async () => {
|
|
862
|
-
return pullMessages(options);
|
|
863
|
-
})();
|
|
864
|
-
return start;
|
|
865
|
-
},
|
|
866
|
-
stop: async () => {
|
|
867
|
-
if (!isRunning) return;
|
|
868
|
-
isRunning = false;
|
|
869
|
-
await start;
|
|
829
|
+
...msg,
|
|
830
|
+
metadata: {
|
|
831
|
+
...existingMetadata,
|
|
832
|
+
action
|
|
870
833
|
}
|
|
871
834
|
};
|
|
872
835
|
};
|
|
873
|
-
var
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
836
|
+
var createWrappedInitialState = (initialState) => {
|
|
837
|
+
return () => ({
|
|
838
|
+
userState: initialState(),
|
|
839
|
+
processedInputIds: /* @__PURE__ */ new Set()
|
|
840
|
+
});
|
|
878
841
|
};
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
842
|
+
var createWrappedEvolve = (evolve, workflowName, separateInputInboxFromProcessing) => {
|
|
843
|
+
return (state, event2) => {
|
|
844
|
+
const metadata = event2.metadata;
|
|
845
|
+
let processedInputIds = state.processedInputIds;
|
|
846
|
+
if (metadata?.input === true && typeof metadata?.originalMessageId === "string") {
|
|
847
|
+
processedInputIds = new Set(state.processedInputIds);
|
|
848
|
+
processedInputIds.add(metadata.originalMessageId);
|
|
849
|
+
}
|
|
850
|
+
if (separateInputInboxFromProcessing && metadata?.input === true) {
|
|
851
|
+
return {
|
|
852
|
+
userState: state.userState,
|
|
853
|
+
processedInputIds
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
const eventType = event2.type;
|
|
857
|
+
const eventForEvolve = eventType.startsWith(`${workflowName}:`) ? {
|
|
858
|
+
...event2,
|
|
859
|
+
type: eventType.replace(`${workflowName}:`, "")
|
|
860
|
+
} : event2;
|
|
861
|
+
return {
|
|
862
|
+
userState: evolve(state.userState, eventForEvolve),
|
|
863
|
+
processedInputIds
|
|
864
|
+
};
|
|
865
|
+
};
|
|
866
|
+
};
|
|
867
|
+
var workflowStreamName = ({
|
|
868
|
+
workflowName,
|
|
869
|
+
workflowId
|
|
870
|
+
}) => `emt:workflow:${workflowName}:${workflowId}`;
|
|
871
|
+
var WorkflowHandler = (options) => async (store, message2, handleOptions) => asyncRetry(
|
|
872
|
+
async () => {
|
|
873
|
+
const result = await withSession2(store, async ({ eventStore }) => {
|
|
874
|
+
const {
|
|
875
|
+
workflow: { evolve, initialState, decide, name: workflowName },
|
|
876
|
+
getWorkflowId: getWorkflowId2
|
|
877
|
+
} = options;
|
|
878
|
+
const inputMessageId = (
|
|
879
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
880
|
+
("metadata" in message2 && message2.metadata?.messageId ? (
|
|
881
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
882
|
+
message2.metadata.messageId
|
|
883
|
+
) : void 0) ?? uuid6()
|
|
884
|
+
);
|
|
885
|
+
const messageWithMetadata = {
|
|
886
|
+
...message2,
|
|
887
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
888
|
+
metadata: {
|
|
889
|
+
messageId: inputMessageId,
|
|
890
|
+
...message2.metadata
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
const workflowId = getWorkflowId2(messageWithMetadata);
|
|
894
|
+
if (!workflowId) {
|
|
895
|
+
return emptyHandlerResult();
|
|
896
|
+
}
|
|
897
|
+
const streamName = options.mapWorkflowId ? options.mapWorkflowId(workflowId) : workflowStreamName({ workflowName, workflowId });
|
|
898
|
+
const messageType = messageWithMetadata.type;
|
|
899
|
+
const hasWorkflowPrefix = messageType.startsWith(`${workflowName}:`);
|
|
900
|
+
if (options.separateInputInboxFromProcessing && !hasWorkflowPrefix) {
|
|
901
|
+
const inputMetadata2 = createInputMetadata(
|
|
902
|
+
inputMessageId,
|
|
903
|
+
"InitiatedBy"
|
|
904
|
+
);
|
|
905
|
+
const inputToStore2 = {
|
|
906
|
+
type: `${workflowName}:${messageWithMetadata.type}`,
|
|
907
|
+
data: messageWithMetadata.data,
|
|
908
|
+
kind: messageWithMetadata.kind,
|
|
909
|
+
metadata: inputMetadata2
|
|
910
|
+
};
|
|
911
|
+
const appendResult2 = await eventStore.appendToStream(
|
|
912
|
+
streamName,
|
|
913
|
+
[inputToStore2],
|
|
914
|
+
{
|
|
915
|
+
...handleOptions,
|
|
916
|
+
expectedStreamVersion: handleOptions?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK
|
|
917
|
+
}
|
|
918
|
+
);
|
|
919
|
+
return {
|
|
920
|
+
...appendResult2,
|
|
921
|
+
newMessages: []
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
const wrappedInitialState = createWrappedInitialState(initialState);
|
|
925
|
+
const wrappedEvolve = createWrappedEvolve(
|
|
926
|
+
evolve,
|
|
927
|
+
workflowName,
|
|
928
|
+
options.separateInputInboxFromProcessing ?? false
|
|
929
|
+
);
|
|
930
|
+
const aggregationResult = await eventStore.aggregateStream(streamName, {
|
|
931
|
+
evolve: wrappedEvolve,
|
|
932
|
+
initialState: wrappedInitialState,
|
|
933
|
+
read: {
|
|
934
|
+
...handleOptions,
|
|
935
|
+
// expected stream version is passed to fail fast
|
|
936
|
+
// if stream is in the wrong state
|
|
937
|
+
expectedStreamVersion: handleOptions?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
const { currentStreamVersion } = aggregationResult;
|
|
941
|
+
const { userState: state, processedInputIds } = aggregationResult.state;
|
|
942
|
+
if (processedInputIds.has(inputMessageId)) {
|
|
943
|
+
return emptyHandlerResult(currentStreamVersion);
|
|
944
|
+
}
|
|
945
|
+
const messageForDecide = hasWorkflowPrefix ? {
|
|
946
|
+
...messageWithMetadata,
|
|
947
|
+
type: messageType.replace(`${workflowName}:`, "")
|
|
948
|
+
} : messageWithMetadata;
|
|
949
|
+
const result2 = decide(messageForDecide, state);
|
|
950
|
+
const inputMetadata = createInputMetadata(
|
|
951
|
+
inputMessageId,
|
|
952
|
+
aggregationResult.streamExists ? "Received" : "InitiatedBy"
|
|
953
|
+
);
|
|
954
|
+
const inputToStore = {
|
|
955
|
+
type: `${workflowName}:${messageWithMetadata.type}`,
|
|
956
|
+
data: messageWithMetadata.data,
|
|
957
|
+
kind: messageWithMetadata.kind,
|
|
958
|
+
metadata: inputMetadata
|
|
959
|
+
};
|
|
960
|
+
const outputMessages = (Array.isArray(result2) ? result2 : [result2]).filter((msg) => msg !== void 0 && msg !== null);
|
|
961
|
+
const outputCommandTypes = options.outputs?.commands ?? [];
|
|
962
|
+
const taggedOutputMessages = outputMessages.map((msg) => {
|
|
963
|
+
const action = outputCommandTypes.includes(
|
|
964
|
+
msg.type
|
|
965
|
+
) ? "Sent" : "Published";
|
|
966
|
+
return tagOutputMessage(msg, action);
|
|
967
|
+
});
|
|
968
|
+
const messagesToAppend = options.separateInputInboxFromProcessing && hasWorkflowPrefix ? [...taggedOutputMessages] : [inputToStore, ...taggedOutputMessages];
|
|
969
|
+
if (messagesToAppend.length === 0) {
|
|
970
|
+
return emptyHandlerResult(currentStreamVersion);
|
|
971
|
+
}
|
|
972
|
+
const expectedStreamVersion = handleOptions?.expectedStreamVersion ?? (aggregationResult.streamExists ? currentStreamVersion : STREAM_DOES_NOT_EXIST);
|
|
973
|
+
const appendResult = await eventStore.appendToStream(
|
|
974
|
+
streamName,
|
|
975
|
+
// TODO: Fix this cast
|
|
976
|
+
messagesToAppend,
|
|
977
|
+
{
|
|
978
|
+
...handleOptions,
|
|
979
|
+
expectedStreamVersion
|
|
980
|
+
}
|
|
981
|
+
);
|
|
982
|
+
return {
|
|
983
|
+
...appendResult,
|
|
984
|
+
newMessages: outputMessages
|
|
985
|
+
};
|
|
986
|
+
});
|
|
987
|
+
return result;
|
|
988
|
+
},
|
|
989
|
+
fromWorkflowHandlerRetryOptions(
|
|
990
|
+
handleOptions && "retry" in handleOptions ? handleOptions.retry : options.retry
|
|
991
|
+
)
|
|
992
|
+
);
|
|
993
|
+
var withSession2 = (eventStore, callback) => {
|
|
994
|
+
const sessionFactory = canCreateEventStoreSession(eventStore) ? eventStore : nulloSessionFactory(eventStore);
|
|
995
|
+
return sessionFactory.withSession(callback);
|
|
996
|
+
};
|
|
997
|
+
var getWorkflowId = (options) => `emt:processor:workflow:${options.workflowName}`;
|
|
998
|
+
var workflowProcessor = (options) => {
|
|
999
|
+
const { workflow, ...rest } = options;
|
|
1000
|
+
const inputs = [...options.inputs.commands, ...options.inputs.events];
|
|
1001
|
+
let canHandle = inputs;
|
|
1002
|
+
if (options.separateInputInboxFromProcessing)
|
|
1003
|
+
canHandle = [
|
|
1004
|
+
...canHandle,
|
|
1005
|
+
...options.inputs.commands.map((t) => `${workflow.name}:${t}`),
|
|
1006
|
+
...options.inputs.events.map((t) => `${workflow.name}:${t}`)
|
|
1007
|
+
];
|
|
1008
|
+
if (options.outputHandler)
|
|
1009
|
+
canHandle = [...canHandle, ...options.outputHandler.canHandle];
|
|
1010
|
+
const handle = WorkflowHandler(options);
|
|
1011
|
+
return reactor({
|
|
1012
|
+
...rest,
|
|
1013
|
+
processorId: options.processorId ?? getWorkflowId({ workflowName: workflow.name }),
|
|
1014
|
+
canHandle,
|
|
1015
|
+
type: MessageProcessorType.PROJECTOR,
|
|
1016
|
+
eachMessage: async (message2, context) => {
|
|
1017
|
+
const messageType = message2.type;
|
|
1018
|
+
const metadata = message2.metadata;
|
|
1019
|
+
const isInput = metadata?.input === true;
|
|
1020
|
+
if (isInput || inputs.includes(messageType)) {
|
|
1021
|
+
const result = await handle(
|
|
1022
|
+
context.connection.messageStore,
|
|
1023
|
+
message2,
|
|
1024
|
+
context
|
|
1025
|
+
);
|
|
1026
|
+
if (options.stopAfter && result.newMessages.length > 0) {
|
|
1027
|
+
for (const outputMessage of result.newMessages) {
|
|
1028
|
+
if (options.stopAfter(
|
|
1029
|
+
outputMessage
|
|
1030
|
+
)) {
|
|
1031
|
+
return { type: "STOP", reason: "Stop condition reached" };
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
if (options.outputHandler?.canHandle.includes(messageType) === true) {
|
|
1038
|
+
const handledOutputMessages = await options.outputHandler.handle(
|
|
1039
|
+
message2,
|
|
1040
|
+
context
|
|
1041
|
+
);
|
|
1042
|
+
if (handledOutputMessages instanceof EmmettError) {
|
|
1043
|
+
return {
|
|
1044
|
+
type: "STOP",
|
|
1045
|
+
reason: "Routing error",
|
|
1046
|
+
error: handledOutputMessages
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
const messagesToAppend = Array.isArray(handledOutputMessages) ? handledOutputMessages : handledOutputMessages ? [handledOutputMessages] : [];
|
|
1050
|
+
if (messagesToAppend.length === 0) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
const workflowId = options.getWorkflowId(
|
|
1054
|
+
message2
|
|
1055
|
+
);
|
|
1056
|
+
if (!workflowId) return;
|
|
1057
|
+
const streamName = options.mapWorkflowId ? options.mapWorkflowId(workflowId) : workflowStreamName({
|
|
1058
|
+
workflowName: workflow.name,
|
|
1059
|
+
workflowId
|
|
1060
|
+
});
|
|
1061
|
+
await context.connection.messageStore.appendToStream(
|
|
1062
|
+
streamName,
|
|
1063
|
+
messagesToAppend
|
|
1064
|
+
);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
};
|
|
1071
|
+
|
|
1072
|
+
// src/eventStore/schema/readLastMessageGlobalPosition.ts
|
|
1073
|
+
import { singleOrNull, SQL } from "@event-driven-io/dumbo";
|
|
1074
|
+
|
|
1075
|
+
// src/eventStore/schema/typing.ts
|
|
1076
|
+
var emmettPrefix2 = "emt";
|
|
1077
|
+
var globalTag = "global";
|
|
1078
|
+
var defaultTag2 = `${emmettPrefix2}:default`;
|
|
1079
|
+
var unknownTag2 = `${emmettPrefix2}:unknown`;
|
|
1080
|
+
var globalNames = {
|
|
1081
|
+
module: `${emmettPrefix2}:module:${globalTag}`,
|
|
1082
|
+
tenant: `${emmettPrefix2}:tenant:${globalTag}`
|
|
1083
|
+
};
|
|
1084
|
+
var columns = {
|
|
1085
|
+
partition: {
|
|
1086
|
+
name: "partition"
|
|
1087
|
+
},
|
|
1088
|
+
isArchived: { name: "is_archived" }
|
|
1089
|
+
};
|
|
1090
|
+
var streamsTable = {
|
|
1091
|
+
name: `${emmettPrefix2}_streams`,
|
|
1092
|
+
columns: {
|
|
1093
|
+
partition: columns.partition,
|
|
1094
|
+
isArchived: columns.isArchived
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
var messagesTable = {
|
|
1098
|
+
name: `${emmettPrefix2}_messages`,
|
|
1099
|
+
columns: {
|
|
1100
|
+
partition: columns.partition,
|
|
1101
|
+
isArchived: columns.isArchived
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
var processorsTable = {
|
|
1105
|
+
name: `${emmettPrefix2}_processors`
|
|
1106
|
+
};
|
|
1107
|
+
var projectionsTable = {
|
|
1108
|
+
name: `${emmettPrefix2}_projections`
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// src/eventStore/schema/readLastMessageGlobalPosition.ts
|
|
1112
|
+
var readLastMessageGlobalPosition = async (execute, options) => {
|
|
1113
|
+
const result = await singleOrNull(
|
|
1114
|
+
execute.query(
|
|
1115
|
+
SQL`SELECT global_position
|
|
1116
|
+
FROM ${SQL.identifier(messagesTable.name)}
|
|
1117
|
+
WHERE partition = ${options?.partition ?? defaultTag2} AND is_archived = FALSE AND transaction_id < pg_snapshot_xmin(pg_current_snapshot())
|
|
1118
|
+
ORDER BY transaction_id, global_position
|
|
1119
|
+
LIMIT 1`
|
|
1120
|
+
)
|
|
1121
|
+
);
|
|
1122
|
+
return {
|
|
1123
|
+
currentGlobalPosition: result !== null ? BigInt(result.global_position) : null
|
|
1124
|
+
};
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
// src/eventStore/schema/readMessagesBatch.ts
|
|
1128
|
+
import { mapRows, SQL as SQL2 } from "@event-driven-io/dumbo";
|
|
1129
|
+
var readMessagesBatch = async (execute, options) => {
|
|
1130
|
+
const from = "from" in options ? options.from : void 0;
|
|
1131
|
+
const after = "after" in options ? options.after : void 0;
|
|
1132
|
+
const batchSize = "batchSize" in options ? options.batchSize : options.to - options.from;
|
|
1133
|
+
const fromCondition = from !== void 0 ? SQL2`AND global_position >= ${from}` : after !== void 0 ? SQL2`AND global_position > ${after}` : SQL2.EMPTY;
|
|
1134
|
+
const toCondition = "to" in options ? SQL2`AND global_position <= ${options.to}` : SQL2.EMPTY;
|
|
1135
|
+
const limitCondition = "batchSize" in options ? SQL2`LIMIT ${options.batchSize}` : SQL2.EMPTY;
|
|
1136
|
+
const messages = await mapRows(
|
|
1137
|
+
execute.query(
|
|
1138
|
+
SQL2`
|
|
1139
|
+
SELECT stream_id, stream_position, global_position, message_data, message_metadata, message_schema_version, message_type, message_id
|
|
1140
|
+
FROM ${SQL2.identifier(messagesTable.name)}
|
|
1141
|
+
WHERE partition = ${options?.partition ?? defaultTag2} AND is_archived = FALSE AND transaction_id < pg_snapshot_xmin(pg_current_snapshot()) ${fromCondition} ${toCondition}
|
|
1142
|
+
ORDER BY transaction_id, global_position
|
|
1143
|
+
${limitCondition}`
|
|
1144
|
+
),
|
|
1145
|
+
(row) => {
|
|
1146
|
+
const rawEvent = {
|
|
1147
|
+
type: row.message_type,
|
|
1148
|
+
data: row.message_data,
|
|
1149
|
+
metadata: row.message_metadata
|
|
1150
|
+
};
|
|
1151
|
+
const metadata = {
|
|
1152
|
+
..."metadata" in rawEvent ? rawEvent.metadata ?? {} : {},
|
|
1153
|
+
messageId: row.message_id,
|
|
1154
|
+
streamName: row.stream_id,
|
|
1155
|
+
streamPosition: BigInt(row.stream_position),
|
|
1156
|
+
globalPosition: BigInt(row.global_position),
|
|
1157
|
+
checkpoint: bigIntProcessorCheckpoint(BigInt(row.global_position))
|
|
1158
|
+
};
|
|
1159
|
+
return {
|
|
1160
|
+
...rawEvent,
|
|
1161
|
+
kind: "Event",
|
|
1162
|
+
metadata
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
);
|
|
1166
|
+
return messages.length > 0 ? {
|
|
1167
|
+
currentGlobalPosition: messages[messages.length - 1].metadata.globalPosition,
|
|
1168
|
+
messages,
|
|
1169
|
+
areMessagesLeft: messages.length === batchSize
|
|
1170
|
+
} : {
|
|
1171
|
+
currentGlobalPosition: "from" in options ? options.from : "after" in options ? options.after : 0n,
|
|
1172
|
+
messages: [],
|
|
1173
|
+
areMessagesLeft: false
|
|
1174
|
+
};
|
|
1175
|
+
};
|
|
1176
|
+
|
|
1177
|
+
// src/eventStore/consumers/messageBatchProcessing/index.ts
|
|
1178
|
+
var DefaultPostgreSQLEventStoreProcessorBatchSize = 100;
|
|
1179
|
+
var DefaultPostgreSQLEventStoreProcessorPullingFrequencyInMs = 50;
|
|
1180
|
+
var postgreSQLEventStoreMessageBatchPuller = ({
|
|
1181
|
+
executor,
|
|
1182
|
+
batchSize,
|
|
1183
|
+
eachBatch,
|
|
1184
|
+
pullingFrequencyInMs,
|
|
1185
|
+
stopWhen,
|
|
1186
|
+
signal
|
|
1187
|
+
}) => {
|
|
1188
|
+
let isRunning = false;
|
|
1189
|
+
let start;
|
|
1190
|
+
const pullMessages = async (options) => {
|
|
1191
|
+
const after = options.startFrom === "BEGINNING" ? 0n : options.startFrom === "END" ? (await readLastMessageGlobalPosition(executor)).currentGlobalPosition ?? 0n : parseBigIntProcessorCheckpoint(options.startFrom.lastCheckpoint);
|
|
1192
|
+
const readMessagesOptions = {
|
|
1193
|
+
after,
|
|
1194
|
+
batchSize
|
|
1195
|
+
};
|
|
1196
|
+
let waitTime = 100;
|
|
1197
|
+
while (isRunning && !signal?.aborted) {
|
|
1198
|
+
const { messages, currentGlobalPosition, areMessagesLeft } = await readMessagesBatch(executor, readMessagesOptions);
|
|
1199
|
+
if (messages.length > 0) {
|
|
1200
|
+
const result = await eachBatch(messages);
|
|
1201
|
+
if (result && result.type === "STOP") {
|
|
1202
|
+
isRunning = false;
|
|
1203
|
+
break;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
readMessagesOptions.after = currentGlobalPosition;
|
|
1207
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
1208
|
+
if (stopWhen?.noMessagesLeft === true && !areMessagesLeft) {
|
|
1209
|
+
isRunning = false;
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1212
|
+
if (!areMessagesLeft) {
|
|
1213
|
+
waitTime = Math.min(waitTime * 2, 1e3);
|
|
1214
|
+
} else {
|
|
1215
|
+
waitTime = pullingFrequencyInMs;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
return {
|
|
1220
|
+
get isRunning() {
|
|
1221
|
+
return isRunning;
|
|
1222
|
+
},
|
|
1223
|
+
start: (options) => {
|
|
1224
|
+
if (isRunning) return start;
|
|
1225
|
+
isRunning = true;
|
|
1226
|
+
start = (async () => {
|
|
1227
|
+
return pullMessages(options);
|
|
1228
|
+
})();
|
|
1229
|
+
return start;
|
|
1230
|
+
},
|
|
1231
|
+
stop: async () => {
|
|
1232
|
+
if (!isRunning) return;
|
|
1233
|
+
isRunning = false;
|
|
1234
|
+
await start;
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
};
|
|
1238
|
+
var zipPostgreSQLEventStoreMessageBatchPullerStartFrom = (options) => {
|
|
1239
|
+
if (options.length === 0 || options.some((o) => o === void 0 || o === "BEGINNING"))
|
|
1240
|
+
return "BEGINNING";
|
|
1241
|
+
if (options.every((o) => o === "END")) return "END";
|
|
1242
|
+
return options.filter((o) => o !== void 0 && o !== "BEGINNING" && o !== "END").sort((a, b) => a > b ? 1 : -1)[0];
|
|
1243
|
+
};
|
|
1244
|
+
|
|
1245
|
+
// src/eventStore/consumers/postgreSQLEventStoreConsumer.ts
|
|
1246
|
+
import { dumbo as dumbo6 } from "@event-driven-io/dumbo";
|
|
1247
|
+
import { v7 as uuid9 } from "uuid";
|
|
1248
|
+
|
|
1249
|
+
// src/eventStore/consumers/postgreSQLProcessor.ts
|
|
1250
|
+
import { dumbo as dumbo5 } from "@event-driven-io/dumbo";
|
|
1251
|
+
|
|
1252
|
+
// src/eventStore/postgreSQLEventStore.ts
|
|
1253
|
+
import {
|
|
1254
|
+
dumbo as dumbo4,
|
|
1255
|
+
fromDatabaseDriverType,
|
|
1256
|
+
getFormatter,
|
|
1257
|
+
SQL as SQL20
|
|
1258
|
+
} from "@event-driven-io/dumbo";
|
|
1259
|
+
|
|
1260
|
+
// src/eventStore/projections/locks/tryAcquireProjectionLock.ts
|
|
1261
|
+
import { single } from "@event-driven-io/dumbo";
|
|
1262
|
+
|
|
1263
|
+
// src/eventStore/schema/projections/projectionsLocks.ts
|
|
1264
|
+
import { SQL as SQL4 } from "@event-driven-io/dumbo";
|
|
1265
|
+
|
|
895
1266
|
// src/eventStore/schema/createFunctionIfDoesNotExist.ts
|
|
896
1267
|
import { SQL as SQL3 } from "@event-driven-io/dumbo";
|
|
897
1268
|
var createFunctionIfDoesNotExistSQL = (functionName, functionDefinition) => SQL3`
|
|
@@ -903,31 +1274,115 @@ END IF;
|
|
|
903
1274
|
END $$;
|
|
904
1275
|
`;
|
|
905
1276
|
|
|
906
|
-
// src/eventStore/schema/
|
|
907
|
-
var
|
|
908
|
-
"
|
|
1277
|
+
// src/eventStore/schema/projections/projectionsLocks.ts
|
|
1278
|
+
var tryAcquireProjectionLockSQL = createFunctionIfDoesNotExistSQL(
|
|
1279
|
+
"emt_try_acquire_projection_lock",
|
|
909
1280
|
SQL4`
|
|
910
|
-
CREATE OR REPLACE FUNCTION
|
|
911
|
-
p_lock_key
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
p_processor_instance_id TEXT DEFAULT '${SQL4.plain(unknownTag)}',
|
|
916
|
-
p_projection_name TEXT DEFAULT NULL,
|
|
917
|
-
p_projection_type VARCHAR(1) DEFAULT NULL,
|
|
918
|
-
p_projection_kind TEXT DEFAULT NULL,
|
|
919
|
-
p_lock_timeout_seconds INT DEFAULT 300
|
|
1281
|
+
CREATE OR REPLACE FUNCTION emt_try_acquire_projection_lock(
|
|
1282
|
+
p_lock_key BIGINT,
|
|
1283
|
+
p_partition TEXT,
|
|
1284
|
+
p_name TEXT,
|
|
1285
|
+
p_version INT
|
|
920
1286
|
)
|
|
921
|
-
RETURNS TABLE (acquired BOOLEAN,
|
|
1287
|
+
RETURNS TABLE (acquired BOOLEAN, is_active BOOLEAN)
|
|
922
1288
|
LANGUAGE plpgsql
|
|
923
|
-
AS $
|
|
1289
|
+
AS $emt_try_acquire_projection_lock$
|
|
1290
|
+
BEGIN
|
|
1291
|
+
RETURN QUERY
|
|
1292
|
+
WITH lock_check AS (
|
|
1293
|
+
SELECT pg_try_advisory_xact_lock_shared(p_lock_key) AS acquired
|
|
1294
|
+
),
|
|
1295
|
+
status_check AS (
|
|
1296
|
+
SELECT status = 'active' AS is_active
|
|
1297
|
+
FROM ${SQL4.plain(projectionsTable.name)}
|
|
1298
|
+
WHERE partition = p_partition AND name = p_name AND version = p_version
|
|
1299
|
+
)
|
|
1300
|
+
SELECT
|
|
1301
|
+
COALESCE((SELECT lc.acquired FROM lock_check lc), false),
|
|
1302
|
+
COALESCE((SELECT sc.is_active FROM status_check sc), true);
|
|
1303
|
+
END;
|
|
1304
|
+
$emt_try_acquire_projection_lock$;
|
|
1305
|
+
`
|
|
1306
|
+
);
|
|
1307
|
+
var callTryAcquireProjectionLock = (params) => SQL4`SELECT * FROM emt_try_acquire_projection_lock(${params.lockKey}, ${params.partition}, ${params.name}, ${params.version});`;
|
|
1308
|
+
|
|
1309
|
+
// src/eventStore/projections/locks/tryAcquireProjectionLock.ts
|
|
1310
|
+
var tryAcquireProjectionLock = async (execute, {
|
|
1311
|
+
lockKey,
|
|
1312
|
+
projectionName,
|
|
1313
|
+
partition,
|
|
1314
|
+
version
|
|
1315
|
+
}) => {
|
|
1316
|
+
const lockKeyBigInt = isBigint(lockKey) ? lockKey : await hashText(lockKey);
|
|
1317
|
+
const { acquired, is_active } = await single(
|
|
1318
|
+
execute.query(
|
|
1319
|
+
callTryAcquireProjectionLock({
|
|
1320
|
+
lockKey: lockKeyBigInt.toString(),
|
|
1321
|
+
partition,
|
|
1322
|
+
name: projectionName,
|
|
1323
|
+
version
|
|
1324
|
+
})
|
|
1325
|
+
)
|
|
1326
|
+
);
|
|
1327
|
+
return acquired === true && is_active === true;
|
|
1328
|
+
};
|
|
1329
|
+
|
|
1330
|
+
// src/eventStore/projections/locks/postgreSQLProjectionLock.ts
|
|
1331
|
+
var postgreSQLProjectionLock = (options) => {
|
|
1332
|
+
let acquired = false;
|
|
1333
|
+
const lockKey = options.lockKey ?? toProjectionLockKey(options);
|
|
1334
|
+
return {
|
|
1335
|
+
tryAcquire: async (context) => {
|
|
1336
|
+
if (acquired) {
|
|
1337
|
+
return true;
|
|
1338
|
+
}
|
|
1339
|
+
acquired = await tryAcquireProjectionLock(context.execute, {
|
|
1340
|
+
...options,
|
|
1341
|
+
lockKey
|
|
1342
|
+
});
|
|
1343
|
+
return acquired;
|
|
1344
|
+
},
|
|
1345
|
+
release: (_context) => {
|
|
1346
|
+
if (!acquired) return;
|
|
1347
|
+
acquired = false;
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
};
|
|
1351
|
+
var toProjectionLockKey = ({
|
|
1352
|
+
projectionName,
|
|
1353
|
+
partition,
|
|
1354
|
+
version
|
|
1355
|
+
}) => `${partition}:${projectionName}:${version}`;
|
|
1356
|
+
|
|
1357
|
+
// src/eventStore/projections/locks/tryAcquireProcessorLock.ts
|
|
1358
|
+
import { single as single2 } from "@event-driven-io/dumbo";
|
|
1359
|
+
|
|
1360
|
+
// src/eventStore/schema/processors/processorsLocks.ts
|
|
1361
|
+
import { SQL as SQL5 } from "@event-driven-io/dumbo";
|
|
1362
|
+
var tryAcquireProcessorLockSQL = createFunctionIfDoesNotExistSQL(
|
|
1363
|
+
"emt_try_acquire_processor_lock",
|
|
1364
|
+
SQL5`
|
|
1365
|
+
CREATE OR REPLACE FUNCTION emt_try_acquire_processor_lock(
|
|
1366
|
+
p_lock_key BIGINT,
|
|
1367
|
+
p_processor_id TEXT,
|
|
1368
|
+
p_version INT,
|
|
1369
|
+
p_partition TEXT DEFAULT '${SQL5.plain(defaultTag2)}',
|
|
1370
|
+
p_processor_instance_id TEXT DEFAULT '${SQL5.plain(unknownTag2)}',
|
|
1371
|
+
p_projection_name TEXT DEFAULT NULL,
|
|
1372
|
+
p_projection_type VARCHAR(1) DEFAULT NULL,
|
|
1373
|
+
p_projection_kind TEXT DEFAULT NULL,
|
|
1374
|
+
p_lock_timeout_seconds INT DEFAULT 300
|
|
1375
|
+
)
|
|
1376
|
+
RETURNS TABLE (acquired BOOLEAN, checkpoint TEXT)
|
|
1377
|
+
LANGUAGE plpgsql
|
|
1378
|
+
AS $emt_try_acquire_processor_lock$
|
|
924
1379
|
BEGIN
|
|
925
1380
|
RETURN QUERY
|
|
926
1381
|
WITH lock_check AS (
|
|
927
1382
|
SELECT pg_try_advisory_xact_lock(p_lock_key) AS lock_acquired
|
|
928
1383
|
),
|
|
929
1384
|
ownership_check AS (
|
|
930
|
-
INSERT INTO ${
|
|
1385
|
+
INSERT INTO ${SQL5.plain(processorsTable.name)} (
|
|
931
1386
|
processor_id,
|
|
932
1387
|
partition,
|
|
933
1388
|
version,
|
|
@@ -938,20 +1393,20 @@ BEGIN
|
|
|
938
1393
|
created_at,
|
|
939
1394
|
last_updated
|
|
940
1395
|
)
|
|
941
|
-
SELECT p_processor_id, p_partition, p_version, p_processor_instance_id, 'running', '${
|
|
1396
|
+
SELECT p_processor_id, p_partition, p_version, p_processor_instance_id, 'running', '${SQL5.plain(bigInt.toNormalizedString(0n))}', '0'::xid8, now(), now()
|
|
942
1397
|
WHERE (SELECT lock_acquired FROM lock_check) = true
|
|
943
1398
|
ON CONFLICT (processor_id, partition, version) DO UPDATE
|
|
944
1399
|
SET processor_instance_id = p_processor_instance_id,
|
|
945
1400
|
status = 'running',
|
|
946
1401
|
last_updated = now()
|
|
947
|
-
WHERE ${
|
|
948
|
-
OR ${
|
|
949
|
-
OR ${
|
|
950
|
-
OR ${
|
|
1402
|
+
WHERE ${SQL5.plain(processorsTable.name)}.processor_instance_id = p_processor_instance_id
|
|
1403
|
+
OR ${SQL5.plain(processorsTable.name)}.processor_instance_id = '${SQL5.plain(unknownTag2)}'
|
|
1404
|
+
OR ${SQL5.plain(processorsTable.name)}.status = 'stopped'
|
|
1405
|
+
OR ${SQL5.plain(processorsTable.name)}.last_updated < now() - (p_lock_timeout_seconds || ' seconds')::interval
|
|
951
1406
|
RETURNING last_processed_checkpoint
|
|
952
1407
|
),
|
|
953
1408
|
projection_status AS (
|
|
954
|
-
INSERT INTO ${
|
|
1409
|
+
INSERT INTO ${SQL5.plain(projectionsTable.name)} (
|
|
955
1410
|
name,
|
|
956
1411
|
partition,
|
|
957
1412
|
version,
|
|
@@ -976,13 +1431,13 @@ $emt_try_acquire_processor_lock$;
|
|
|
976
1431
|
);
|
|
977
1432
|
var releaseProcessorLockSQL = createFunctionIfDoesNotExistSQL(
|
|
978
1433
|
"emt_release_processor_lock",
|
|
979
|
-
|
|
1434
|
+
SQL5`
|
|
980
1435
|
CREATE OR REPLACE FUNCTION emt_release_processor_lock(
|
|
981
1436
|
p_lock_key BIGINT,
|
|
982
1437
|
p_processor_id TEXT,
|
|
983
1438
|
p_partition TEXT,
|
|
984
1439
|
p_version INT,
|
|
985
|
-
p_processor_instance_id TEXT DEFAULT '${
|
|
1440
|
+
p_processor_instance_id TEXT DEFAULT '${SQL5.plain(unknownTag2)}',
|
|
986
1441
|
p_projection_name TEXT DEFAULT NULL
|
|
987
1442
|
)
|
|
988
1443
|
RETURNS BOOLEAN
|
|
@@ -992,7 +1447,7 @@ DECLARE
|
|
|
992
1447
|
v_rows_updated INT;
|
|
993
1448
|
BEGIN
|
|
994
1449
|
IF p_projection_name IS NOT NULL THEN
|
|
995
|
-
UPDATE ${
|
|
1450
|
+
UPDATE ${SQL5.plain(projectionsTable.name)}
|
|
996
1451
|
SET status = 'active',
|
|
997
1452
|
last_updated = now()
|
|
998
1453
|
WHERE partition = p_partition
|
|
@@ -1000,9 +1455,9 @@ BEGIN
|
|
|
1000
1455
|
AND version = p_version;
|
|
1001
1456
|
END IF;
|
|
1002
1457
|
|
|
1003
|
-
UPDATE ${
|
|
1458
|
+
UPDATE ${SQL5.plain(processorsTable.name)}
|
|
1004
1459
|
SET status = 'stopped',
|
|
1005
|
-
processor_instance_id = '${
|
|
1460
|
+
processor_instance_id = '${SQL5.plain(unknownTag2)}',
|
|
1006
1461
|
last_updated = now()
|
|
1007
1462
|
WHERE processor_id = p_processor_id
|
|
1008
1463
|
AND partition = p_partition
|
|
@@ -1018,7 +1473,7 @@ END;
|
|
|
1018
1473
|
$emt_release_processor_lock$;
|
|
1019
1474
|
`
|
|
1020
1475
|
);
|
|
1021
|
-
var callTryAcquireProcessorLock = (params) =>
|
|
1476
|
+
var callTryAcquireProcessorLock = (params) => SQL5`
|
|
1022
1477
|
SELECT * FROM emt_try_acquire_processor_lock(
|
|
1023
1478
|
${params.lockKey},
|
|
1024
1479
|
${params.processorId},
|
|
@@ -1031,7 +1486,7 @@ var callTryAcquireProcessorLock = (params) => SQL4`
|
|
|
1031
1486
|
${params.lockTimeoutSeconds}
|
|
1032
1487
|
);
|
|
1033
1488
|
`;
|
|
1034
|
-
var callReleaseProcessorLock = (params) =>
|
|
1489
|
+
var callReleaseProcessorLock = (params) => SQL5`SELECT emt_release_processor_lock(
|
|
1035
1490
|
${params.lockKey},
|
|
1036
1491
|
${params.processorId},
|
|
1037
1492
|
${params.partition},
|
|
@@ -1044,7 +1499,7 @@ var callReleaseProcessorLock = (params) => SQL4`SELECT emt_release_processor_loc
|
|
|
1044
1499
|
var PROCESSOR_LOCK_DEFAULT_TIMEOUT_SECONDS = 300;
|
|
1045
1500
|
var tryAcquireProcessorLock = async (execute, options) => {
|
|
1046
1501
|
const lockKeyBigInt = isBigint(options.lockKey) ? options.lockKey : await hashText(options.lockKey);
|
|
1047
|
-
const { acquired, checkpoint } = await
|
|
1502
|
+
const { acquired, checkpoint } = await single2(
|
|
1048
1503
|
execute.command(
|
|
1049
1504
|
callTryAcquireProcessorLock({
|
|
1050
1505
|
lockKey: lockKeyBigInt.toString(),
|
|
@@ -1062,7 +1517,7 @@ var tryAcquireProcessorLock = async (execute, options) => {
|
|
|
1062
1517
|
return acquired ? { acquired: true, checkpoint } : { acquired: false };
|
|
1063
1518
|
};
|
|
1064
1519
|
var tryAcquireProcessorLockWithRetry = async (execute, options) => {
|
|
1065
|
-
const policy = options.
|
|
1520
|
+
const policy = options.lockAcquisitionPolicy ?? DefaultPostgreSQLProcessorLockPolicy;
|
|
1066
1521
|
if (policy.type === "retry") {
|
|
1067
1522
|
return asyncRetry(() => tryAcquireProcessorLock(execute, options), {
|
|
1068
1523
|
retries: policy.retries - 1,
|
|
@@ -1075,7 +1530,7 @@ var tryAcquireProcessorLockWithRetry = async (execute, options) => {
|
|
|
1075
1530
|
};
|
|
1076
1531
|
var releaseProcessorLock = async (execute, options) => {
|
|
1077
1532
|
const lockKeyBigInt = isBigint(options.lockKey) ? options.lockKey : await hashText(options.lockKey);
|
|
1078
|
-
const { result } = await
|
|
1533
|
+
const { result } = await single2(
|
|
1079
1534
|
execute.command(
|
|
1080
1535
|
callReleaseProcessorLock({
|
|
1081
1536
|
lockKey: lockKeyBigInt.toString(),
|
|
@@ -1090,90 +1545,6 @@ var releaseProcessorLock = async (execute, options) => {
|
|
|
1090
1545
|
return result;
|
|
1091
1546
|
};
|
|
1092
1547
|
|
|
1093
|
-
// src/eventStore/projections/locks/tryAcquireProjectionLock.ts
|
|
1094
|
-
import { single as single2 } from "@event-driven-io/dumbo";
|
|
1095
|
-
|
|
1096
|
-
// src/eventStore/schema/projections/projectionsLocks.ts
|
|
1097
|
-
import { SQL as SQL5 } from "@event-driven-io/dumbo";
|
|
1098
|
-
var tryAcquireProjectionLockSQL = createFunctionIfDoesNotExistSQL(
|
|
1099
|
-
"emt_try_acquire_projection_lock",
|
|
1100
|
-
SQL5`
|
|
1101
|
-
CREATE OR REPLACE FUNCTION emt_try_acquire_projection_lock(
|
|
1102
|
-
p_lock_key BIGINT,
|
|
1103
|
-
p_partition TEXT,
|
|
1104
|
-
p_name TEXT,
|
|
1105
|
-
p_version INT
|
|
1106
|
-
)
|
|
1107
|
-
RETURNS TABLE (acquired BOOLEAN, is_active BOOLEAN)
|
|
1108
|
-
LANGUAGE plpgsql
|
|
1109
|
-
AS $emt_try_acquire_projection_lock$
|
|
1110
|
-
BEGIN
|
|
1111
|
-
RETURN QUERY
|
|
1112
|
-
WITH lock_check AS (
|
|
1113
|
-
SELECT pg_try_advisory_xact_lock_shared(p_lock_key) AS acquired
|
|
1114
|
-
),
|
|
1115
|
-
status_check AS (
|
|
1116
|
-
SELECT status = 'active' AS is_active
|
|
1117
|
-
FROM ${SQL5.plain(projectionsTable.name)}
|
|
1118
|
-
WHERE partition = p_partition AND name = p_name AND version = p_version
|
|
1119
|
-
)
|
|
1120
|
-
SELECT
|
|
1121
|
-
COALESCE((SELECT lc.acquired FROM lock_check lc), false),
|
|
1122
|
-
COALESCE((SELECT sc.is_active FROM status_check sc), true);
|
|
1123
|
-
END;
|
|
1124
|
-
$emt_try_acquire_projection_lock$;
|
|
1125
|
-
`
|
|
1126
|
-
);
|
|
1127
|
-
var callTryAcquireProjectionLock = (params) => SQL5`SELECT * FROM emt_try_acquire_projection_lock(${params.lockKey}, ${params.partition}, ${params.name}, ${params.version});`;
|
|
1128
|
-
|
|
1129
|
-
// src/eventStore/projections/locks/tryAcquireProjectionLock.ts
|
|
1130
|
-
var tryAcquireProjectionLock = async (execute, {
|
|
1131
|
-
lockKey,
|
|
1132
|
-
projectionName,
|
|
1133
|
-
partition,
|
|
1134
|
-
version
|
|
1135
|
-
}) => {
|
|
1136
|
-
const lockKeyBigInt = isBigint(lockKey) ? lockKey : await hashText(lockKey);
|
|
1137
|
-
const { acquired, is_active } = await single2(
|
|
1138
|
-
execute.query(
|
|
1139
|
-
callTryAcquireProjectionLock({
|
|
1140
|
-
lockKey: lockKeyBigInt.toString(),
|
|
1141
|
-
partition,
|
|
1142
|
-
name: projectionName,
|
|
1143
|
-
version
|
|
1144
|
-
})
|
|
1145
|
-
)
|
|
1146
|
-
);
|
|
1147
|
-
return acquired === true && is_active === true;
|
|
1148
|
-
};
|
|
1149
|
-
|
|
1150
|
-
// src/eventStore/projections/locks/postgreSQLProjectionLock.ts
|
|
1151
|
-
var postgreSQLProjectionLock = (options) => {
|
|
1152
|
-
let acquired = false;
|
|
1153
|
-
const lockKey = options.lockKey ?? toProjectionLockKey(options);
|
|
1154
|
-
return {
|
|
1155
|
-
tryAcquire: async (context) => {
|
|
1156
|
-
if (acquired) {
|
|
1157
|
-
return true;
|
|
1158
|
-
}
|
|
1159
|
-
acquired = await tryAcquireProjectionLock(context.execute, {
|
|
1160
|
-
...options,
|
|
1161
|
-
lockKey
|
|
1162
|
-
});
|
|
1163
|
-
return acquired;
|
|
1164
|
-
},
|
|
1165
|
-
release: (_context) => {
|
|
1166
|
-
if (!acquired) return;
|
|
1167
|
-
acquired = false;
|
|
1168
|
-
}
|
|
1169
|
-
};
|
|
1170
|
-
};
|
|
1171
|
-
var toProjectionLockKey = ({
|
|
1172
|
-
projectionName,
|
|
1173
|
-
partition,
|
|
1174
|
-
version
|
|
1175
|
-
}) => `${partition}:${projectionName}:${version}`;
|
|
1176
|
-
|
|
1177
1548
|
// src/eventStore/projections/locks/postgreSQLProcessorLock.ts
|
|
1178
1549
|
var DefaultPostgreSQLProcessorLockPolicy = {
|
|
1179
1550
|
type: "fail"
|
|
@@ -1190,7 +1561,7 @@ var postgreSQLProcessorLock = (options) => {
|
|
|
1190
1561
|
...options,
|
|
1191
1562
|
lockKey
|
|
1192
1563
|
});
|
|
1193
|
-
if (!result.acquired && options.
|
|
1564
|
+
if (!result.acquired && options.lockAcquisitionPolicy?.type !== "skip") {
|
|
1194
1565
|
throw new EmmettError(
|
|
1195
1566
|
`Failed to acquire lock for processor '${options.processorId}'`
|
|
1196
1567
|
);
|
|
@@ -1223,7 +1594,7 @@ var toProcessorLockKey = ({
|
|
|
1223
1594
|
|
|
1224
1595
|
// src/eventStore/projections/management/projectionManagement.ts
|
|
1225
1596
|
import {
|
|
1226
|
-
JSONSerializer,
|
|
1597
|
+
JSONSerializer as JSONSerializer2,
|
|
1227
1598
|
single as single3,
|
|
1228
1599
|
singleOrNull as singleOrNull2,
|
|
1229
1600
|
SQL as SQL7
|
|
@@ -1354,7 +1725,7 @@ var registerProjection = async (execute, options) => {
|
|
|
1354
1725
|
const name = registration.projection.name;
|
|
1355
1726
|
const version = registration.projection.version ?? 1;
|
|
1356
1727
|
const kind = registration.projection.kind ?? registration.type;
|
|
1357
|
-
const definition =
|
|
1728
|
+
const definition = JSONSerializer2.serialize(registration.projection);
|
|
1358
1729
|
const lockKey = toProjectionLockKey({
|
|
1359
1730
|
projectionName: name,
|
|
1360
1731
|
partition,
|
|
@@ -1575,7 +1946,6 @@ var pongoSingleStreamProjection = (options) => {
|
|
|
1575
1946
|
};
|
|
1576
1947
|
|
|
1577
1948
|
// src/eventStore/projections/pongo/pongoProjectionSpec.ts
|
|
1578
|
-
import "@event-driven-io/dumbo";
|
|
1579
1949
|
import {
|
|
1580
1950
|
pongoClient as pongoClient2
|
|
1581
1951
|
} from "@event-driven-io/pongo";
|
|
@@ -1666,7 +2036,7 @@ var documentDoesNotExist = (options) => (assertOptions) => withCollection(
|
|
|
1666
2036
|
const result = await collection.findOne(
|
|
1667
2037
|
"withId" in options ? { _id: options.withId } : options.matchingFilter
|
|
1668
2038
|
);
|
|
1669
|
-
|
|
2039
|
+
assertIsNull(result);
|
|
1670
2040
|
},
|
|
1671
2041
|
{ ...options, ...assertOptions }
|
|
1672
2042
|
);
|
|
@@ -1715,100 +2085,338 @@ var expectPongoDocuments = {
|
|
|
1715
2085
|
|
|
1716
2086
|
// src/eventStore/projections/postgresProjectionSpec.ts
|
|
1717
2087
|
import {
|
|
1718
|
-
dumbo
|
|
1719
|
-
} from "@event-driven-io/dumbo";
|
|
1720
|
-
import "@event-driven-io/dumbo/pg";
|
|
1721
|
-
import { v4 as uuid6 } from "uuid";
|
|
1722
|
-
|
|
1723
|
-
// src/eventStore/postgreSQLEventStore.ts
|
|
1724
|
-
import {
|
|
1725
|
-
dumbo as dumbo3,
|
|
1726
|
-
fromDatabaseDriverType,
|
|
1727
|
-
getFormatter,
|
|
1728
|
-
SQL as SQL20
|
|
1729
|
-
} from "@event-driven-io/dumbo";
|
|
1730
|
-
import "@event-driven-io/dumbo/pg";
|
|
1731
|
-
import "pg";
|
|
1732
|
-
|
|
1733
|
-
// src/eventStore/schema/index.ts
|
|
1734
|
-
import {
|
|
1735
|
-
dumbo as dumbo2,
|
|
1736
|
-
runSQLMigrations,
|
|
1737
|
-
sqlMigration as sqlMigration4
|
|
1738
|
-
} from "@event-driven-io/dumbo";
|
|
1739
|
-
|
|
1740
|
-
// src/eventStore/schema/appendToStream.ts
|
|
1741
|
-
import {
|
|
1742
|
-
DumboError,
|
|
1743
|
-
single as single4,
|
|
1744
|
-
SQL as SQL8,
|
|
1745
|
-
UniqueConstraintError
|
|
2088
|
+
dumbo
|
|
1746
2089
|
} from "@event-driven-io/dumbo";
|
|
1747
|
-
import "
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
2090
|
+
import { v4 as uuid7 } from "uuid";
|
|
2091
|
+
var PostgreSQLProjectionSpec = {
|
|
2092
|
+
for: (options) => {
|
|
2093
|
+
{
|
|
2094
|
+
const { projection: projection2, ...restOptions } = options;
|
|
2095
|
+
const dumboOptions = {
|
|
2096
|
+
...restOptions,
|
|
2097
|
+
serialization: projection2.serialization
|
|
2098
|
+
};
|
|
2099
|
+
const { connectionString } = dumboOptions;
|
|
2100
|
+
let wasInitialised = false;
|
|
2101
|
+
const initialize = async (pool) => {
|
|
2102
|
+
const eventStore = getPostgreSQLEventStore(connectionString, {
|
|
2103
|
+
// TODO: This will need to change when we support other drivers
|
|
2104
|
+
connectionOptions: { dumbo: pool }
|
|
2105
|
+
});
|
|
2106
|
+
if (wasInitialised) return;
|
|
2107
|
+
wasInitialised = true;
|
|
2108
|
+
await eventStore.schema.migrate();
|
|
2109
|
+
if (projection2.init)
|
|
2110
|
+
await pool.withTransaction(async (transaction) => {
|
|
2111
|
+
await projection2.init({
|
|
2112
|
+
registrationType: "async",
|
|
2113
|
+
version: projection2.version ?? 1,
|
|
2114
|
+
status: "active",
|
|
2115
|
+
context: await transactionToPostgreSQLProjectionHandlerContext(
|
|
2116
|
+
connectionString,
|
|
2117
|
+
pool,
|
|
2118
|
+
transaction
|
|
2119
|
+
)
|
|
2120
|
+
});
|
|
2121
|
+
});
|
|
2122
|
+
};
|
|
2123
|
+
return (givenEvents) => {
|
|
2124
|
+
return {
|
|
2125
|
+
when: (events, options2) => {
|
|
2126
|
+
const allEvents = [];
|
|
2127
|
+
const run = async (pool) => {
|
|
2128
|
+
let globalPosition = 0n;
|
|
2129
|
+
const numberOfTimes = options2?.numberOfTimes ?? 1;
|
|
2130
|
+
for (const event of [
|
|
2131
|
+
...givenEvents,
|
|
2132
|
+
...Array.from({ length: numberOfTimes }).flatMap(() => events)
|
|
2133
|
+
]) {
|
|
2134
|
+
const metadata = {
|
|
2135
|
+
checkpoint: bigIntProcessorCheckpoint(++globalPosition),
|
|
2136
|
+
globalPosition,
|
|
2137
|
+
streamPosition: globalPosition,
|
|
2138
|
+
streamName: `test-${uuid7()}`,
|
|
2139
|
+
messageId: uuid7()
|
|
2140
|
+
};
|
|
2141
|
+
allEvents.push({
|
|
2142
|
+
...event,
|
|
2143
|
+
kind: "Event",
|
|
2144
|
+
metadata: {
|
|
2145
|
+
...metadata,
|
|
2146
|
+
..."metadata" in event ? event.metadata ?? {} : {}
|
|
2147
|
+
}
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
await initialize(pool);
|
|
2151
|
+
await pool.withTransaction(async (transaction) => {
|
|
2152
|
+
await handleProjections({
|
|
2153
|
+
events: allEvents,
|
|
2154
|
+
projections: [projection2],
|
|
2155
|
+
...await transactionToPostgreSQLProjectionHandlerContext(
|
|
2156
|
+
connectionString,
|
|
2157
|
+
pool,
|
|
2158
|
+
transaction
|
|
2159
|
+
)
|
|
2160
|
+
});
|
|
2161
|
+
});
|
|
2162
|
+
};
|
|
2163
|
+
return {
|
|
2164
|
+
then: async (assert, message) => {
|
|
2165
|
+
const pool = dumbo(dumboOptions);
|
|
2166
|
+
try {
|
|
2167
|
+
await run(pool);
|
|
2168
|
+
const succeeded = await assert({ pool, connectionString });
|
|
2169
|
+
if (succeeded !== void 0 && succeeded === false)
|
|
2170
|
+
assertFails(
|
|
2171
|
+
message ?? "Projection specification didn't match the criteria"
|
|
2172
|
+
);
|
|
2173
|
+
} finally {
|
|
2174
|
+
await pool.close();
|
|
2175
|
+
}
|
|
2176
|
+
},
|
|
2177
|
+
thenThrows: async (...args) => {
|
|
2178
|
+
const pool = dumbo(dumboOptions);
|
|
2179
|
+
try {
|
|
2180
|
+
await run(pool);
|
|
2181
|
+
throw new AssertionError("Handler did not fail as expected");
|
|
2182
|
+
} catch (error) {
|
|
2183
|
+
if (error instanceof AssertionError) throw error;
|
|
2184
|
+
if (args.length === 0) return;
|
|
2185
|
+
if (!isErrorConstructor(args[0])) {
|
|
2186
|
+
assertTrue(
|
|
2187
|
+
args[0](error),
|
|
2188
|
+
`Error didn't match the error condition: ${error?.toString()}`
|
|
2189
|
+
);
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
assertTrue(
|
|
2193
|
+
error instanceof args[0],
|
|
2194
|
+
`Caught error is not an instance of the expected type: ${error?.toString()}`
|
|
2195
|
+
);
|
|
2196
|
+
if (args[1]) {
|
|
2197
|
+
assertTrue(
|
|
2198
|
+
args[1](error),
|
|
2199
|
+
`Error didn't match the error condition: ${error?.toString()}`
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
} finally {
|
|
2203
|
+
await pool.close();
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
};
|
|
2213
|
+
var eventInStream = (streamName, event) => {
|
|
2214
|
+
return {
|
|
2215
|
+
...event,
|
|
2216
|
+
metadata: {
|
|
2217
|
+
...event.metadata ?? {},
|
|
2218
|
+
streamName: event.metadata?.streamName ?? streamName
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
};
|
|
2222
|
+
var eventsInStream = (streamName, events) => {
|
|
2223
|
+
return events.map((e) => eventInStream(streamName, e));
|
|
2224
|
+
};
|
|
2225
|
+
var newEventsInStream = eventsInStream;
|
|
2226
|
+
var assertSQLQueryResultMatches = (sql, rows) => async ({ pool: { execute } }) => {
|
|
2227
|
+
const result = await execute.query(sql);
|
|
2228
|
+
assertThatArray(rows).containsExactlyInAnyOrder(result.rows);
|
|
2229
|
+
};
|
|
2230
|
+
var expectSQL = {
|
|
2231
|
+
query: (sql) => ({
|
|
2232
|
+
resultRows: {
|
|
2233
|
+
toBeTheSame: (rows) => assertSQLQueryResultMatches(sql, rows)
|
|
2234
|
+
}
|
|
2235
|
+
})
|
|
2236
|
+
};
|
|
2237
|
+
|
|
2238
|
+
// src/eventStore/projections/postgreSQLProjection.ts
|
|
2239
|
+
var transactionToPostgreSQLProjectionHandlerContext = async (connectionString, pool, transaction) => ({
|
|
2240
|
+
execute: transaction.execute,
|
|
2241
|
+
connection: {
|
|
2242
|
+
connectionString,
|
|
2243
|
+
client: await transaction.connection.open(),
|
|
2244
|
+
transaction,
|
|
2245
|
+
pool
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
var handleProjections = async (options) => {
|
|
2249
|
+
const {
|
|
2250
|
+
projections: allProjections,
|
|
2251
|
+
events,
|
|
2252
|
+
connection: { pool, transaction, connectionString },
|
|
2253
|
+
partition = defaultTag2
|
|
2254
|
+
} = options;
|
|
2255
|
+
const eventTypes = events.map((e) => e.type);
|
|
2256
|
+
const projections = allProjections.filter(
|
|
2257
|
+
(p) => p.canHandle.some((type) => eventTypes.includes(type))
|
|
2258
|
+
);
|
|
2259
|
+
const client = await transaction.connection.open();
|
|
2260
|
+
for (const projection2 of projections) {
|
|
2261
|
+
if (projection2.name) {
|
|
2262
|
+
const lockAcquired = await postgreSQLProjectionLock({
|
|
2263
|
+
projectionName: projection2.name,
|
|
2264
|
+
partition,
|
|
2265
|
+
version: projection2.version ?? 1
|
|
2266
|
+
}).tryAcquire({ execute: transaction.execute });
|
|
2267
|
+
if (!lockAcquired) {
|
|
2268
|
+
continue;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
await projection2.handle(events, {
|
|
2272
|
+
connection: {
|
|
2273
|
+
connectionString,
|
|
2274
|
+
pool,
|
|
2275
|
+
client,
|
|
2276
|
+
transaction
|
|
2277
|
+
},
|
|
2278
|
+
execute: transaction.execute
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
var postgreSQLProjection = (definition) => projection({
|
|
2283
|
+
...definition,
|
|
2284
|
+
init: async (options) => {
|
|
2285
|
+
await registerProjection(options.context.execute, {
|
|
2286
|
+
// TODO: pass partition from options
|
|
2287
|
+
partition: defaultTag2,
|
|
2288
|
+
status: "active",
|
|
2289
|
+
registration: {
|
|
2290
|
+
type: "async",
|
|
2291
|
+
// TODO: fix this
|
|
2292
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
|
|
2293
|
+
projection: definition
|
|
2294
|
+
}
|
|
2295
|
+
});
|
|
2296
|
+
if (definition.init) {
|
|
2297
|
+
await definition.init(options);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
});
|
|
2301
|
+
var postgreSQLRawBatchSQLProjection = (options) => postgreSQLProjection({
|
|
2302
|
+
name: options.name,
|
|
2303
|
+
kind: options.kind ?? "emt:projections:postgresql:raw_sql:batch",
|
|
2304
|
+
version: options.version,
|
|
2305
|
+
canHandle: options.canHandle,
|
|
2306
|
+
eventsOptions: options.eventsOptions,
|
|
2307
|
+
handle: async (events, context) => {
|
|
2308
|
+
const sqls = await options.evolve(events, context);
|
|
2309
|
+
await context.execute.batchCommand(sqls);
|
|
2310
|
+
},
|
|
2311
|
+
init: async (initOptions) => {
|
|
2312
|
+
const initSQL = options.init ? await options.init(initOptions) : void 0;
|
|
2313
|
+
if (initSQL) {
|
|
2314
|
+
if (Array.isArray(initSQL)) {
|
|
2315
|
+
await initOptions.context.execute.batchCommand(initSQL);
|
|
2316
|
+
} else {
|
|
2317
|
+
await initOptions.context.execute.command(initSQL);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
});
|
|
2322
|
+
var postgreSQLRawSQLProjection = (options) => {
|
|
2323
|
+
const { evolve, kind, ...rest } = options;
|
|
2324
|
+
return postgreSQLRawBatchSQLProjection({
|
|
2325
|
+
kind: kind ?? "emt:projections:postgresql:raw:_sql:single",
|
|
2326
|
+
...rest,
|
|
2327
|
+
evolve: async (events, context) => {
|
|
2328
|
+
const sqls = [];
|
|
2329
|
+
for (const event of events) {
|
|
2330
|
+
const pendingSqls = await evolve(event, context);
|
|
2331
|
+
if (Array.isArray(pendingSqls)) {
|
|
2332
|
+
sqls.push(...pendingSqls);
|
|
2333
|
+
} else {
|
|
2334
|
+
sqls.push(pendingSqls);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
return sqls;
|
|
2338
|
+
}
|
|
2339
|
+
});
|
|
2340
|
+
};
|
|
2341
|
+
|
|
2342
|
+
// src/eventStore/schema/index.ts
|
|
2343
|
+
import {
|
|
2344
|
+
dumbo as dumbo3,
|
|
2345
|
+
runSQLMigrations,
|
|
2346
|
+
sqlMigration as sqlMigration4
|
|
2347
|
+
} from "@event-driven-io/dumbo";
|
|
2348
|
+
|
|
2349
|
+
// src/eventStore/schema/appendToStream.ts
|
|
2350
|
+
import {
|
|
2351
|
+
DumboError,
|
|
2352
|
+
single as single4,
|
|
2353
|
+
SQL as SQL8,
|
|
2354
|
+
UniqueConstraintError
|
|
2355
|
+
} from "@event-driven-io/dumbo";
|
|
2356
|
+
import { v4 as uuid8 } from "uuid";
|
|
2357
|
+
var appendToStreamSQL = createFunctionIfDoesNotExistSQL(
|
|
2358
|
+
"emt_append_to_stream",
|
|
2359
|
+
SQL8`CREATE OR REPLACE FUNCTION emt_append_to_stream(
|
|
2360
|
+
v_message_ids text[],
|
|
2361
|
+
v_messages_data jsonb[],
|
|
2362
|
+
v_messages_metadata jsonb[],
|
|
2363
|
+
v_message_schema_versions text[],
|
|
2364
|
+
v_message_types text[],
|
|
2365
|
+
v_message_kinds text[],
|
|
2366
|
+
v_stream_id text,
|
|
2367
|
+
v_stream_type text,
|
|
2368
|
+
v_expected_stream_position bigint DEFAULT NULL,
|
|
2369
|
+
v_partition text DEFAULT emt_sanitize_name('default_partition')
|
|
2370
|
+
) RETURNS TABLE (
|
|
2371
|
+
success boolean,
|
|
2372
|
+
next_stream_position bigint,
|
|
2373
|
+
global_positions bigint[],
|
|
2374
|
+
transaction_id xid8
|
|
2375
|
+
) LANGUAGE plpgsql
|
|
2376
|
+
AS $emt_append_to_stream$
|
|
2377
|
+
DECLARE
|
|
2378
|
+
v_next_stream_position bigint;
|
|
2379
|
+
v_position bigint;
|
|
2380
|
+
v_updated_rows int;
|
|
2381
|
+
v_transaction_id xid8;
|
|
2382
|
+
v_global_positions bigint[];
|
|
2383
|
+
BEGIN
|
|
2384
|
+
v_transaction_id := pg_current_xact_id();
|
|
2385
|
+
|
|
2386
|
+
IF v_expected_stream_position IS NULL THEN
|
|
2387
|
+
SELECT COALESCE(
|
|
2388
|
+
(SELECT stream_position
|
|
2389
|
+
FROM ${SQL8.identifier(streamsTable.name)}
|
|
2390
|
+
WHERE stream_id = v_stream_id
|
|
2391
|
+
AND partition = v_partition
|
|
2392
|
+
AND is_archived = FALSE
|
|
2393
|
+
LIMIT 1),
|
|
2394
|
+
0
|
|
2395
|
+
) INTO v_expected_stream_position;
|
|
2396
|
+
END IF;
|
|
2397
|
+
|
|
2398
|
+
v_next_stream_position := v_expected_stream_position + array_upper(v_messages_data, 1);
|
|
2399
|
+
|
|
2400
|
+
IF v_expected_stream_position = 0 THEN
|
|
2401
|
+
INSERT INTO ${SQL8.identifier(streamsTable.name)}
|
|
2402
|
+
(stream_id, stream_position, partition, stream_type, stream_metadata, is_archived)
|
|
2403
|
+
VALUES
|
|
2404
|
+
(v_stream_id, v_next_stream_position, v_partition, v_stream_type, '{}', FALSE);
|
|
2405
|
+
ELSE
|
|
2406
|
+
UPDATE ${SQL8.identifier(streamsTable.name)} as s
|
|
2407
|
+
SET stream_position = v_next_stream_position
|
|
2408
|
+
WHERE stream_id = v_stream_id AND stream_position = v_expected_stream_position AND partition = v_partition AND is_archived = FALSE;
|
|
2409
|
+
|
|
2410
|
+
get diagnostics v_updated_rows = row_count;
|
|
2411
|
+
|
|
2412
|
+
IF v_updated_rows = 0 THEN
|
|
2413
|
+
RETURN QUERY SELECT FALSE, NULL::bigint, NULL::bigint[], NULL::xid8;
|
|
2414
|
+
RETURN;
|
|
2415
|
+
END IF;
|
|
2416
|
+
END IF;
|
|
2417
|
+
|
|
2418
|
+
WITH ev AS (
|
|
2419
|
+
SELECT row_number() OVER () + v_expected_stream_position AS stream_position,
|
|
1812
2420
|
message_data,
|
|
1813
2421
|
message_metadata,
|
|
1814
2422
|
schema_version,
|
|
@@ -1864,7 +2472,7 @@ var appendToStream = (pool, streamName, streamType, messages, options) => pool.w
|
|
|
1864
2472
|
...e,
|
|
1865
2473
|
kind: e.kind ?? "Event",
|
|
1866
2474
|
metadata: {
|
|
1867
|
-
messageId:
|
|
2475
|
+
messageId: uuid8(),
|
|
1868
2476
|
..."metadata" in e ? e.metadata ?? {} : {}
|
|
1869
2477
|
}
|
|
1870
2478
|
}));
|
|
@@ -1942,7 +2550,7 @@ var appendEventsRaw = (execute, streamId, streamType, messages, options) => sing
|
|
|
1942
2550
|
streamId,
|
|
1943
2551
|
streamType,
|
|
1944
2552
|
expectedStreamPosition: options?.expectedStreamVersion ?? null,
|
|
1945
|
-
partition: options?.partition ??
|
|
2553
|
+
partition: options?.partition ?? defaultTag2
|
|
1946
2554
|
})
|
|
1947
2555
|
)
|
|
1948
2556
|
);
|
|
@@ -2437,7 +3045,7 @@ BEGIN
|
|
|
2437
3045
|
END;
|
|
2438
3046
|
$fnpar$ LANGUAGE plpgsql;
|
|
2439
3047
|
|
|
2440
|
-
PERFORM emt_add_partition('${SQL11.plain(
|
|
3048
|
+
PERFORM emt_add_partition('${SQL11.plain(defaultTag2)}');
|
|
2441
3049
|
|
|
2442
3050
|
-- 3. Copy data from old table to new table
|
|
2443
3051
|
INSERT INTO "emt_processors"
|
|
@@ -2553,7 +3161,7 @@ BEGIN
|
|
|
2553
3161
|
p_position TEXT,
|
|
2554
3162
|
p_check_position TEXT,
|
|
2555
3163
|
p_transaction_id xid8,
|
|
2556
|
-
p_partition TEXT DEFAULT '${SQL11.plain(
|
|
3164
|
+
p_partition TEXT DEFAULT '${SQL11.plain(defaultTag2)}',
|
|
2557
3165
|
p_processor_instance_id TEXT DEFAULT 'emt:unknown'
|
|
2558
3166
|
) RETURNS INT AS $fn2$
|
|
2559
3167
|
DECLARE
|
|
@@ -2688,7 +3296,7 @@ CREATE OR REPLACE FUNCTION emt_try_acquire_processor_lock(
|
|
|
2688
3296
|
p_lock_key BIGINT,
|
|
2689
3297
|
p_processor_id TEXT,
|
|
2690
3298
|
p_version INT,
|
|
2691
|
-
p_partition TEXT DEFAULT '${SQL11.plain(
|
|
3299
|
+
p_partition TEXT DEFAULT '${SQL11.plain(defaultTag2)}',
|
|
2692
3300
|
p_processor_instance_id TEXT DEFAULT 'emt:unknown',
|
|
2693
3301
|
p_projection_name TEXT DEFAULT NULL,
|
|
2694
3302
|
p_projection_type VARCHAR(1) DEFAULT NULL,
|
|
@@ -3467,8 +4075,8 @@ CREATE OR REPLACE FUNCTION store_processor_checkpoint(
|
|
|
3467
4075
|
p_position TEXT,
|
|
3468
4076
|
p_check_position TEXT,
|
|
3469
4077
|
p_transaction_id xid8,
|
|
3470
|
-
p_partition TEXT DEFAULT '${SQL13.plain(
|
|
3471
|
-
p_processor_instance_id TEXT DEFAULT '${SQL13.plain(
|
|
4078
|
+
p_partition TEXT DEFAULT '${SQL13.plain(defaultTag2)}',
|
|
4079
|
+
p_processor_instance_id TEXT DEFAULT '${SQL13.plain(unknownTag2)}'
|
|
3472
4080
|
) RETURNS INT AS $spc$
|
|
3473
4081
|
DECLARE
|
|
3474
4082
|
current_position TEXT;
|
|
@@ -3500,1083 +4108,838 @@ BEGIN
|
|
|
3500
4108
|
-- Return appropriate codes based on current position
|
|
3501
4109
|
IF current_position = p_position THEN
|
|
3502
4110
|
RETURN 0; -- Idempotent check: position already set
|
|
3503
|
-
ELSIF current_position > p_position THEN
|
|
3504
|
-
RETURN 3; -- Current ahead: another process has progressed further
|
|
3505
|
-
ELSE
|
|
3506
|
-
RETURN 2; -- Mismatch: check position doesn't match current
|
|
3507
|
-
END IF;
|
|
3508
|
-
END IF;
|
|
3509
|
-
|
|
3510
|
-
-- Handle the case when p_check_position is NULL: Insert if not exists
|
|
3511
|
-
BEGIN
|
|
3512
|
-
INSERT INTO "${SQL13.plain(processorsTable.name)}"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
|
|
3513
|
-
VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
|
|
3514
|
-
RETURN 1; -- Successfully inserted
|
|
3515
|
-
EXCEPTION WHEN unique_violation THEN
|
|
3516
|
-
-- If insertion failed, it means the row already exists
|
|
3517
|
-
SELECT "last_processed_checkpoint" INTO current_position
|
|
3518
|
-
FROM "${SQL13.plain(processorsTable.name)}"
|
|
3519
|
-
WHERE "processor_id" = p_processor_id
|
|
3520
|
-
AND "partition" = p_partition
|
|
3521
|
-
AND "version" = p_version;
|
|
3522
|
-
|
|
3523
|
-
IF current_position = p_position THEN
|
|
3524
|
-
RETURN 0; -- Idempotent check: position already set
|
|
3525
|
-
ELSIF current_position > p_position THEN
|
|
3526
|
-
RETURN 3; -- Current ahead: another process has progressed further
|
|
3527
|
-
ELSE
|
|
3528
|
-
RETURN 2; -- Insertion failed, row already exists with different position
|
|
3529
|
-
END IF;
|
|
3530
|
-
END;
|
|
3531
|
-
END;
|
|
3532
|
-
$spc$ LANGUAGE plpgsql;
|
|
3533
|
-
`
|
|
3534
|
-
);
|
|
3535
|
-
var callStoreProcessorCheckpoint = (params) => SQL13`
|
|
3536
|
-
SELECT store_processor_checkpoint(
|
|
3537
|
-
${params.processorId},
|
|
3538
|
-
${params.version},
|
|
3539
|
-
${params.position},
|
|
3540
|
-
${params.checkPosition},
|
|
3541
|
-
pg_current_xact_id(),
|
|
3542
|
-
${params.partition},
|
|
3543
|
-
${params.processorInstanceId}
|
|
3544
|
-
) as result;`;
|
|
3545
|
-
var storeProcessorCheckpoint = async (execute, options) => {
|
|
3546
|
-
try {
|
|
3547
|
-
const { result } = await single5(
|
|
3548
|
-
execute.command(
|
|
3549
|
-
callStoreProcessorCheckpoint({
|
|
3550
|
-
processorId: options.processorId,
|
|
3551
|
-
version: options.version ?? 1,
|
|
3552
|
-
position: options.newCheckpoint !== null ? bigInt.toNormalizedString(options.newCheckpoint) : null,
|
|
3553
|
-
checkPosition: options.lastProcessedCheckpoint !== null ? bigInt.toNormalizedString(options.lastProcessedCheckpoint) : null,
|
|
3554
|
-
partition: options.partition ?? defaultTag,
|
|
3555
|
-
processorInstanceId: options.processorInstanceId ?? unknownTag
|
|
3556
|
-
})
|
|
3557
|
-
)
|
|
3558
|
-
);
|
|
3559
|
-
return result === 1 ? { success: true, newCheckpoint: options.newCheckpoint } : {
|
|
3560
|
-
success: false,
|
|
3561
|
-
reason: result === 0 ? "IGNORED" : result === 3 ? "CURRENT_AHEAD" : "MISMATCH"
|
|
3562
|
-
};
|
|
3563
|
-
} catch (error) {
|
|
3564
|
-
console.log(error);
|
|
3565
|
-
throw error;
|
|
3566
|
-
}
|
|
3567
|
-
};
|
|
3568
|
-
|
|
3569
|
-
// src/eventStore/schema/tables.ts
|
|
3570
|
-
import { SQL as SQL15 } from "@event-driven-io/dumbo";
|
|
3571
|
-
|
|
3572
|
-
// src/eventStore/schema/migrations/0_43_0/index.ts
|
|
3573
|
-
import {
|
|
3574
|
-
dumbo,
|
|
3575
|
-
SQL as SQL14,
|
|
3576
|
-
sqlMigration as sqlMigration3
|
|
3577
|
-
} from "@event-driven-io/dumbo";
|
|
3578
|
-
var migration_0_43_0_cleanupLegacySubscriptionSQL = SQL14`
|
|
3579
|
-
DO $$
|
|
3580
|
-
BEGIN
|
|
3581
|
-
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'emt_subscriptions') THEN
|
|
3582
|
-
-- Restore clean emt_add_partition (remove creation of emt_subscriptions partitions)
|
|
3583
|
-
CREATE OR REPLACE FUNCTION emt_add_partition(partition_name TEXT) RETURNS void AS $fnpar$
|
|
3584
|
-
BEGIN
|
|
3585
|
-
PERFORM emt_add_table_partition('${SQL14.plain(messagesTable.name)}', partition_name);
|
|
3586
|
-
PERFORM emt_add_table_partition('${SQL14.plain(streamsTable.name)}', partition_name);
|
|
3587
|
-
|
|
3588
|
-
EXECUTE format('
|
|
3589
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3590
|
-
FOR VALUES IN (%L);',
|
|
3591
|
-
emt_sanitize_name('${SQL14.plain(processorsTable.name)}' || '_' || partition_name), '${SQL14.plain(processorsTable.name)}', partition_name
|
|
3592
|
-
);
|
|
3593
|
-
|
|
3594
|
-
EXECUTE format('
|
|
3595
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3596
|
-
FOR VALUES IN (%L);',
|
|
3597
|
-
emt_sanitize_name('${SQL14.plain(projectionsTable.name)}' || '_' || partition_name), '${SQL14.plain(projectionsTable.name)}', partition_name
|
|
3598
|
-
);
|
|
3599
|
-
END;
|
|
3600
|
-
$fnpar$ LANGUAGE plpgsql;
|
|
3601
|
-
|
|
3602
|
-
-- Drop old subscriptions table if it exists
|
|
3603
|
-
DROP TABLE IF EXISTS emt_subscriptions CASCADE;
|
|
3604
|
-
|
|
3605
|
-
-- Drop old function if it exists
|
|
3606
|
-
DROP FUNCTION IF EXISTS store_subscription_checkpoint(character varying, bigint, bigint, bigint, xid8, text);
|
|
3607
|
-
|
|
3608
|
-
-- Restore clean store_processor_checkpoint (remove dual-write logic)
|
|
3609
|
-
CREATE OR REPLACE FUNCTION store_processor_checkpoint(
|
|
3610
|
-
p_processor_id TEXT,
|
|
3611
|
-
p_version BIGINT,
|
|
3612
|
-
p_position TEXT,
|
|
3613
|
-
p_check_position TEXT,
|
|
3614
|
-
p_transaction_id xid8,
|
|
3615
|
-
p_partition TEXT DEFAULT '${SQL14.plain(defaultTag)}',
|
|
3616
|
-
p_processor_instance_id TEXT DEFAULT 'emt:unknown'
|
|
3617
|
-
) RETURNS INT AS $fn$
|
|
3618
|
-
DECLARE
|
|
3619
|
-
current_position TEXT;
|
|
3620
|
-
BEGIN
|
|
3621
|
-
IF p_check_position IS NOT NULL THEN
|
|
3622
|
-
UPDATE "emt_processors"
|
|
3623
|
-
SET
|
|
3624
|
-
"last_processed_checkpoint" = p_position,
|
|
3625
|
-
"last_processed_transaction_id" = p_transaction_id,
|
|
3626
|
-
"last_updated" = now()
|
|
3627
|
-
WHERE "processor_id" = p_processor_id
|
|
3628
|
-
AND "last_processed_checkpoint" = p_check_position
|
|
3629
|
-
AND "partition" = p_partition
|
|
3630
|
-
AND "version" = p_version;
|
|
3631
|
-
|
|
3632
|
-
IF FOUND THEN
|
|
3633
|
-
RETURN 1;
|
|
3634
|
-
END IF;
|
|
3635
|
-
|
|
3636
|
-
SELECT "last_processed_checkpoint" INTO current_position
|
|
3637
|
-
FROM "emt_processors"
|
|
3638
|
-
WHERE "processor_id" = p_processor_id
|
|
3639
|
-
AND "partition" = p_partition
|
|
3640
|
-
AND "version" = p_version ;
|
|
3641
|
-
|
|
3642
|
-
IF current_position = p_position THEN
|
|
3643
|
-
RETURN 0;
|
|
3644
|
-
ELSIF current_position > p_position THEN
|
|
3645
|
-
RETURN 3;
|
|
3646
|
-
ELSE
|
|
3647
|
-
RETURN 2;
|
|
3648
|
-
END IF;
|
|
3649
|
-
END IF;
|
|
3650
|
-
|
|
3651
|
-
BEGIN
|
|
3652
|
-
INSERT INTO "emt_processors"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
|
|
3653
|
-
VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
|
|
3654
|
-
RETURN 1;
|
|
3655
|
-
EXCEPTION WHEN unique_violation THEN
|
|
3656
|
-
SELECT "last_processed_checkpoint" INTO current_position
|
|
3657
|
-
FROM "emt_processors"
|
|
3658
|
-
WHERE "processor_id" = p_processor_id
|
|
3659
|
-
AND "partition" = p_partition
|
|
3660
|
-
AND "version" = p_version;
|
|
3661
|
-
|
|
3662
|
-
IF current_position = p_position THEN
|
|
3663
|
-
RETURN 0;
|
|
3664
|
-
ELSE
|
|
3665
|
-
RETURN 2;
|
|
3666
|
-
END IF;
|
|
3667
|
-
END;
|
|
3668
|
-
END;
|
|
3669
|
-
$fn$ LANGUAGE plpgsql;
|
|
3670
|
-
END IF;
|
|
3671
|
-
END $$;
|
|
3672
|
-
`;
|
|
3673
|
-
var migration_0_43_0_cleanupLegacySubscription = sqlMigration3("emt:postgresql:eventstore:0.43.0:cleanup-legacy-subscription", [
|
|
3674
|
-
migration_0_43_0_cleanupLegacySubscriptionSQL
|
|
3675
|
-
]);
|
|
3676
|
-
var cleanupLegacySubscriptionTables = async (connectionString) => {
|
|
3677
|
-
const pool = dumbo({ connectionString });
|
|
3678
|
-
try {
|
|
3679
|
-
await pool.withTransaction(async ({ execute }) => {
|
|
3680
|
-
await execute.command(migration_0_43_0_cleanupLegacySubscriptionSQL);
|
|
3681
|
-
});
|
|
3682
|
-
} finally {
|
|
3683
|
-
await pool.close();
|
|
3684
|
-
}
|
|
3685
|
-
};
|
|
3686
|
-
|
|
3687
|
-
// src/eventStore/schema/tables.ts
|
|
3688
|
-
var streamsTableSQL = SQL15`
|
|
3689
|
-
CREATE TABLE IF NOT EXISTS ${SQL15.identifier(streamsTable.name)}(
|
|
3690
|
-
stream_id TEXT NOT NULL,
|
|
3691
|
-
stream_position BIGINT NOT NULL,
|
|
3692
|
-
partition TEXT NOT NULL DEFAULT '${SQL15.plain(defaultTag)}',
|
|
3693
|
-
stream_type TEXT NOT NULL,
|
|
3694
|
-
stream_metadata JSONB NOT NULL,
|
|
3695
|
-
is_archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
3696
|
-
PRIMARY KEY (stream_id, partition, is_archived)
|
|
3697
|
-
) PARTITION BY LIST (partition);
|
|
3698
|
-
|
|
3699
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_streams_unique
|
|
3700
|
-
ON ${SQL15.identifier(streamsTable.name)}(stream_id, partition, is_archived)
|
|
3701
|
-
INCLUDE (stream_position);`;
|
|
3702
|
-
var messagesTableSQL = SQL15`
|
|
3703
|
-
CREATE SEQUENCE IF NOT EXISTS emt_global_message_position;
|
|
3704
|
-
|
|
3705
|
-
CREATE TABLE IF NOT EXISTS ${SQL15.identifier(messagesTable.name)}(
|
|
3706
|
-
stream_position BIGINT NOT NULL,
|
|
3707
|
-
global_position BIGINT DEFAULT nextval('emt_global_message_position'),
|
|
3708
|
-
transaction_id XID8 NOT NULL,
|
|
3709
|
-
created TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
3710
|
-
is_archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
3711
|
-
message_kind VARCHAR(1) NOT NULL DEFAULT 'E',
|
|
3712
|
-
stream_id TEXT NOT NULL,
|
|
3713
|
-
partition TEXT NOT NULL DEFAULT '${SQL15.plain(defaultTag)}',
|
|
3714
|
-
message_schema_version TEXT NOT NULL,
|
|
3715
|
-
message_id TEXT NOT NULL,
|
|
3716
|
-
message_type TEXT NOT NULL,
|
|
3717
|
-
message_data JSONB NOT NULL,
|
|
3718
|
-
message_metadata JSONB NOT NULL,
|
|
3719
|
-
PRIMARY KEY (stream_id, stream_position, partition, is_archived)
|
|
3720
|
-
) PARTITION BY LIST (partition);`;
|
|
3721
|
-
var processorsTableSQL = SQL15`
|
|
3722
|
-
CREATE TABLE IF NOT EXISTS ${SQL15.identifier(processorsTable.name)}(
|
|
3723
|
-
last_processed_transaction_id XID8 NOT NULL,
|
|
3724
|
-
version INT NOT NULL DEFAULT 1,
|
|
3725
|
-
processor_id TEXT NOT NULL,
|
|
3726
|
-
partition TEXT NOT NULL DEFAULT '${SQL15.plain(defaultTag)}',
|
|
3727
|
-
status TEXT NOT NULL DEFAULT 'stopped',
|
|
3728
|
-
last_processed_checkpoint TEXT NOT NULL,
|
|
3729
|
-
processor_instance_id TEXT DEFAULT '${SQL15.plain(unknownTag)}',
|
|
3730
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
3731
|
-
last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
3732
|
-
PRIMARY KEY (processor_id, partition, version)
|
|
3733
|
-
) PARTITION BY LIST (partition);
|
|
3734
|
-
`;
|
|
3735
|
-
var projectionsTableSQL = SQL15`
|
|
3736
|
-
CREATE TABLE IF NOT EXISTS ${SQL15.identifier(projectionsTable.name)}(
|
|
3737
|
-
version INT NOT NULL DEFAULT 1,
|
|
3738
|
-
type VARCHAR(1) NOT NULL,
|
|
3739
|
-
name TEXT NOT NULL,
|
|
3740
|
-
partition TEXT NOT NULL DEFAULT '${SQL15.plain(defaultTag)}',
|
|
3741
|
-
kind TEXT NOT NULL,
|
|
3742
|
-
status TEXT NOT NULL,
|
|
3743
|
-
definition JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
3744
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
3745
|
-
last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
3746
|
-
PRIMARY KEY (name, partition, version)
|
|
3747
|
-
) PARTITION BY LIST (partition);
|
|
3748
|
-
`;
|
|
3749
|
-
var sanitizeNameSQL = createFunctionIfDoesNotExistSQL(
|
|
3750
|
-
"emt_sanitize_name",
|
|
3751
|
-
SQL15`CREATE OR REPLACE FUNCTION emt_sanitize_name(input_name TEXT) RETURNS TEXT AS $emt_sanitize_name$
|
|
3752
|
-
BEGIN
|
|
3753
|
-
RETURN REGEXP_REPLACE(input_name, '[^a-zA-Z0-9_]', '_', 'g');
|
|
3754
|
-
END;
|
|
3755
|
-
$emt_sanitize_name$ LANGUAGE plpgsql;`
|
|
3756
|
-
);
|
|
3757
|
-
var addTablePartitions = createFunctionIfDoesNotExistSQL(
|
|
3758
|
-
"emt_add_table_partition",
|
|
3759
|
-
SQL15`
|
|
3760
|
-
CREATE OR REPLACE FUNCTION emt_add_table_partition(tableName TEXT, partition_name TEXT) RETURNS void AS $emt_add_table_partition$
|
|
3761
|
-
DECLARE
|
|
3762
|
-
v_main_partiton_name TEXT;
|
|
3763
|
-
v_active_partiton_name TEXT;
|
|
3764
|
-
v_archived_partiton_name TEXT;
|
|
3765
|
-
BEGIN
|
|
3766
|
-
v_main_partiton_name := emt_sanitize_name(tableName || '_' || partition_name);
|
|
3767
|
-
v_active_partiton_name := emt_sanitize_name(v_main_partiton_name || '_active');
|
|
3768
|
-
v_archived_partiton_name := emt_sanitize_name(v_main_partiton_name || '_archived');
|
|
4111
|
+
ELSIF current_position > p_position THEN
|
|
4112
|
+
RETURN 3; -- Current ahead: another process has progressed further
|
|
4113
|
+
ELSE
|
|
4114
|
+
RETURN 2; -- Mismatch: check position doesn't match current
|
|
4115
|
+
END IF;
|
|
4116
|
+
END IF;
|
|
3769
4117
|
|
|
4118
|
+
-- Handle the case when p_check_position is NULL: Insert if not exists
|
|
4119
|
+
BEGIN
|
|
4120
|
+
INSERT INTO "${SQL13.plain(processorsTable.name)}"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
|
|
4121
|
+
VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
|
|
4122
|
+
RETURN 1; -- Successfully inserted
|
|
4123
|
+
EXCEPTION WHEN unique_violation THEN
|
|
4124
|
+
-- If insertion failed, it means the row already exists
|
|
4125
|
+
SELECT "last_processed_checkpoint" INTO current_position
|
|
4126
|
+
FROM "${SQL13.plain(processorsTable.name)}"
|
|
4127
|
+
WHERE "processor_id" = p_processor_id
|
|
4128
|
+
AND "partition" = p_partition
|
|
4129
|
+
AND "version" = p_version;
|
|
3770
4130
|
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
EXECUTE format('
|
|
3779
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3780
|
-
FOR VALUES IN (FALSE);',
|
|
3781
|
-
v_active_partiton_name, v_main_partiton_name
|
|
3782
|
-
);
|
|
3783
|
-
|
|
3784
|
-
EXECUTE format('
|
|
3785
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3786
|
-
FOR VALUES IN (TRUE);',
|
|
3787
|
-
v_archived_partiton_name, v_main_partiton_name
|
|
3788
|
-
);
|
|
4131
|
+
IF current_position = p_position THEN
|
|
4132
|
+
RETURN 0; -- Idempotent check: position already set
|
|
4133
|
+
ELSIF current_position > p_position THEN
|
|
4134
|
+
RETURN 3; -- Current ahead: another process has progressed further
|
|
4135
|
+
ELSE
|
|
4136
|
+
RETURN 2; -- Insertion failed, row already exists with different position
|
|
4137
|
+
END IF;
|
|
3789
4138
|
END;
|
|
3790
|
-
|
|
4139
|
+
END;
|
|
4140
|
+
$spc$ LANGUAGE plpgsql;
|
|
4141
|
+
`
|
|
3791
4142
|
);
|
|
3792
|
-
var
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
4143
|
+
var callStoreProcessorCheckpoint = (params) => SQL13`
|
|
4144
|
+
SELECT store_processor_checkpoint(
|
|
4145
|
+
${params.processorId},
|
|
4146
|
+
${params.version},
|
|
4147
|
+
${params.position},
|
|
4148
|
+
${params.checkPosition},
|
|
4149
|
+
pg_current_xact_id(),
|
|
4150
|
+
${params.partition},
|
|
4151
|
+
${params.processorInstanceId}
|
|
4152
|
+
) as result;`;
|
|
4153
|
+
var storeProcessorCheckpoint = async (execute, options) => {
|
|
4154
|
+
try {
|
|
4155
|
+
const { result } = await single5(
|
|
4156
|
+
execute.command(
|
|
4157
|
+
callStoreProcessorCheckpoint({
|
|
4158
|
+
processorId: options.processorId,
|
|
4159
|
+
version: options.version ?? 1,
|
|
4160
|
+
position: options.newCheckpoint !== null ? options.newCheckpoint : null,
|
|
4161
|
+
checkPosition: options.lastProcessedCheckpoint !== null ? options.lastProcessedCheckpoint : null,
|
|
4162
|
+
partition: options.partition ?? defaultTag2,
|
|
4163
|
+
processorInstanceId: options.processorInstanceId ?? unknownTag2
|
|
4164
|
+
})
|
|
4165
|
+
)
|
|
4166
|
+
);
|
|
4167
|
+
return result === 1 ? { success: true, newCheckpoint: options.newCheckpoint } : {
|
|
4168
|
+
success: false,
|
|
4169
|
+
reason: result === 0 ? "IGNORED" : result === 3 ? "CURRENT_AHEAD" : "MISMATCH"
|
|
4170
|
+
};
|
|
4171
|
+
} catch (error) {
|
|
4172
|
+
console.log(error);
|
|
4173
|
+
throw error;
|
|
4174
|
+
}
|
|
4175
|
+
};
|
|
3799
4176
|
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
FOR VALUES IN (%L);',
|
|
3803
|
-
emt_sanitize_name('${SQL15.plain(processorsTable.name)}' || '_' || partition_name), '${SQL15.plain(processorsTable.name)}', partition_name
|
|
3804
|
-
);
|
|
4177
|
+
// src/eventStore/schema/tables.ts
|
|
4178
|
+
import { SQL as SQL15 } from "@event-driven-io/dumbo";
|
|
3805
4179
|
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
);
|
|
3823
|
-
|
|
3824
|
-
EXECUTE format('
|
|
3825
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3826
|
-
FOR VALUES IN (FALSE);',
|
|
3827
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}' || '_active'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}')
|
|
3828
|
-
);
|
|
3829
|
-
|
|
3830
|
-
EXECUTE format('
|
|
3831
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3832
|
-
FOR VALUES IN (TRUE);',
|
|
3833
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}' || '_archived'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}')
|
|
3834
|
-
);
|
|
3835
|
-
|
|
3836
|
-
-- For ${SQL15.plain(streamsTable.name)} table
|
|
3837
|
-
EXECUTE format('
|
|
3838
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3839
|
-
FOR VALUES IN (emt_sanitize_name(%L || ''__'' || %L)) PARTITION BY LIST (is_archived);',
|
|
3840
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}'), '${SQL15.plain(streamsTable.name)}', new_module, '${SQL15.plain(globalTag)}'
|
|
3841
|
-
);
|
|
3842
|
-
|
|
3843
|
-
EXECUTE format('
|
|
3844
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3845
|
-
FOR VALUES IN (FALSE);',
|
|
3846
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}' || '_active'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}')
|
|
3847
|
-
);
|
|
3848
|
-
|
|
3849
|
-
EXECUTE format('
|
|
3850
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3851
|
-
FOR VALUES IN (TRUE);',
|
|
3852
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}' || '_archived'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}')
|
|
3853
|
-
);
|
|
3854
|
-
END;
|
|
3855
|
-
$$ LANGUAGE plpgsql;
|
|
3856
|
-
`;
|
|
3857
|
-
var addTenantSQL = SQL15`
|
|
3858
|
-
CREATE OR REPLACE FUNCTION add_tenant(new_module TEXT, new_tenant TEXT) RETURNS void AS $$
|
|
3859
|
-
BEGIN
|
|
3860
|
-
-- For ${SQL15.plain(messagesTable.name)} table
|
|
4180
|
+
// src/eventStore/schema/migrations/0_43_0/index.ts
|
|
4181
|
+
import {
|
|
4182
|
+
dumbo as dumbo2,
|
|
4183
|
+
SQL as SQL14,
|
|
4184
|
+
sqlMigration as sqlMigration3
|
|
4185
|
+
} from "@event-driven-io/dumbo";
|
|
4186
|
+
var migration_0_43_0_cleanupLegacySubscriptionSQL = SQL14`
|
|
4187
|
+
DO $$
|
|
4188
|
+
BEGIN
|
|
4189
|
+
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'emt_subscriptions') THEN
|
|
4190
|
+
-- Restore clean emt_add_partition (remove creation of emt_subscriptions partitions)
|
|
4191
|
+
CREATE OR REPLACE FUNCTION emt_add_partition(partition_name TEXT) RETURNS void AS $fnpar$
|
|
4192
|
+
BEGIN
|
|
4193
|
+
PERFORM emt_add_table_partition('${SQL14.plain(messagesTable.name)}', partition_name);
|
|
4194
|
+
PERFORM emt_add_table_partition('${SQL14.plain(streamsTable.name)}', partition_name);
|
|
4195
|
+
|
|
3861
4196
|
EXECUTE format('
|
|
3862
4197
|
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3863
|
-
FOR VALUES IN (
|
|
3864
|
-
emt_sanitize_name('${
|
|
3865
|
-
);
|
|
3866
|
-
|
|
3867
|
-
EXECUTE format('
|
|
3868
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3869
|
-
FOR VALUES IN (FALSE);',
|
|
3870
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant || '_active'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant)
|
|
3871
|
-
);
|
|
3872
|
-
|
|
3873
|
-
EXECUTE format('
|
|
3874
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3875
|
-
FOR VALUES IN (TRUE);',
|
|
3876
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant || '_archived'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant)
|
|
4198
|
+
FOR VALUES IN (%L);',
|
|
4199
|
+
emt_sanitize_name('${SQL14.plain(processorsTable.name)}' || '_' || partition_name), '${SQL14.plain(processorsTable.name)}', partition_name
|
|
3877
4200
|
);
|
|
3878
|
-
|
|
3879
|
-
-- For ${SQL15.plain(streamsTable.name)} table
|
|
4201
|
+
|
|
3880
4202
|
EXECUTE format('
|
|
3881
4203
|
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3882
|
-
FOR VALUES IN (
|
|
3883
|
-
emt_sanitize_name('${
|
|
3884
|
-
);
|
|
3885
|
-
|
|
3886
|
-
EXECUTE format('
|
|
3887
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3888
|
-
FOR VALUES IN (FALSE);',
|
|
3889
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant || '_active'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant)
|
|
3890
|
-
);
|
|
3891
|
-
|
|
3892
|
-
EXECUTE format('
|
|
3893
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3894
|
-
FOR VALUES IN (TRUE);',
|
|
3895
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant || '_archived'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant)
|
|
4204
|
+
FOR VALUES IN (%L);',
|
|
4205
|
+
emt_sanitize_name('${SQL14.plain(projectionsTable.name)}' || '_' || partition_name), '${SQL14.plain(projectionsTable.name)}', partition_name
|
|
3896
4206
|
);
|
|
3897
4207
|
END;
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
EXECUTE format('
|
|
3917
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3918
|
-
FOR VALUES IN (FALSE);',
|
|
3919
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant || '_active'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant)
|
|
3920
|
-
);
|
|
3921
|
-
|
|
3922
|
-
EXECUTE format('
|
|
3923
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3924
|
-
FOR VALUES IN (TRUE);',
|
|
3925
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant || '_archived'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant)
|
|
3926
|
-
);
|
|
3927
|
-
|
|
3928
|
-
-- For ${SQL15.plain(streamsTable.name)} table
|
|
3929
|
-
EXECUTE format('
|
|
3930
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3931
|
-
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
3932
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant), '${SQL15.plain(streamsTable.name)}', new_module, tenant_record.tenant
|
|
3933
|
-
);
|
|
3934
|
-
|
|
3935
|
-
EXECUTE format('
|
|
3936
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3937
|
-
FOR VALUES IN (FALSE);',
|
|
3938
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant || '_active'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant)
|
|
3939
|
-
);
|
|
3940
|
-
|
|
3941
|
-
EXECUTE format('
|
|
3942
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3943
|
-
FOR VALUES IN (TRUE);',
|
|
3944
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant || '_archived'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant)
|
|
3945
|
-
);
|
|
3946
|
-
END LOOP;
|
|
3947
|
-
END;
|
|
3948
|
-
$$ LANGUAGE plpgsql;
|
|
3949
|
-
`;
|
|
3950
|
-
var addTenantForAllModulesSQL = SQL15`
|
|
3951
|
-
CREATE OR REPLACE FUNCTION add_tenant_for_all_modules(new_tenant TEXT) RETURNS void AS $$
|
|
4208
|
+
$fnpar$ LANGUAGE plpgsql;
|
|
4209
|
+
|
|
4210
|
+
-- Drop old subscriptions table if it exists
|
|
4211
|
+
DROP TABLE IF EXISTS emt_subscriptions CASCADE;
|
|
4212
|
+
|
|
4213
|
+
-- Drop old function if it exists
|
|
4214
|
+
DROP FUNCTION IF EXISTS store_subscription_checkpoint(character varying, bigint, bigint, bigint, xid8, text);
|
|
4215
|
+
|
|
4216
|
+
-- Restore clean store_processor_checkpoint (remove dual-write logic)
|
|
4217
|
+
CREATE OR REPLACE FUNCTION store_processor_checkpoint(
|
|
4218
|
+
p_processor_id TEXT,
|
|
4219
|
+
p_version BIGINT,
|
|
4220
|
+
p_position TEXT,
|
|
4221
|
+
p_check_position TEXT,
|
|
4222
|
+
p_transaction_id xid8,
|
|
4223
|
+
p_partition TEXT DEFAULT '${SQL14.plain(defaultTag2)}',
|
|
4224
|
+
p_processor_instance_id TEXT DEFAULT 'emt:unknown'
|
|
4225
|
+
) RETURNS INT AS $fn$
|
|
3952
4226
|
DECLARE
|
|
3953
|
-
|
|
4227
|
+
current_position TEXT;
|
|
3954
4228
|
BEGIN
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3966
|
-
FOR VALUES IN (FALSE);',
|
|
3967
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_active'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant)
|
|
3968
|
-
);
|
|
3969
|
-
|
|
3970
|
-
EXECUTE format('
|
|
3971
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3972
|
-
FOR VALUES IN (TRUE);',
|
|
3973
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_archived'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant)
|
|
3974
|
-
);
|
|
3975
|
-
|
|
3976
|
-
-- For ${SQL15.plain(streamsTable.name)} table
|
|
3977
|
-
EXECUTE format('
|
|
3978
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3979
|
-
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
3980
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant), '${SQL15.plain(streamsTable.name)}', module_record.partitionname, new_tenant
|
|
3981
|
-
);
|
|
3982
|
-
|
|
3983
|
-
EXECUTE format('
|
|
3984
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3985
|
-
FOR VALUES IN (FALSE);',
|
|
3986
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_active'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant)
|
|
3987
|
-
);
|
|
3988
|
-
|
|
3989
|
-
EXECUTE format('
|
|
3990
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3991
|
-
FOR VALUES IN (TRUE);',
|
|
3992
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_archived'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant)
|
|
3993
|
-
);
|
|
3994
|
-
END LOOP;
|
|
3995
|
-
END;
|
|
3996
|
-
$$ LANGUAGE plpgsql;
|
|
3997
|
-
`;
|
|
3998
|
-
var addDefaultPartitionSQL = SQL15`SELECT emt_add_partition('${SQL15.plain(defaultTag)}');`;
|
|
4229
|
+
IF p_check_position IS NOT NULL THEN
|
|
4230
|
+
UPDATE "emt_processors"
|
|
4231
|
+
SET
|
|
4232
|
+
"last_processed_checkpoint" = p_position,
|
|
4233
|
+
"last_processed_transaction_id" = p_transaction_id,
|
|
4234
|
+
"last_updated" = now()
|
|
4235
|
+
WHERE "processor_id" = p_processor_id
|
|
4236
|
+
AND "last_processed_checkpoint" = p_check_position
|
|
4237
|
+
AND "partition" = p_partition
|
|
4238
|
+
AND "version" = p_version;
|
|
3999
4239
|
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
const result = await singleOrNull3(
|
|
4004
|
-
execute.query(
|
|
4005
|
-
SQL16`SELECT last_processed_checkpoint
|
|
4006
|
-
FROM ${SQL16.identifier(processorsTable.name)}
|
|
4007
|
-
WHERE partition = ${options?.partition ?? defaultTag} AND processor_id = ${options.processorId} AND version = ${options.version ?? 1}
|
|
4008
|
-
LIMIT 1`
|
|
4009
|
-
)
|
|
4010
|
-
);
|
|
4011
|
-
return {
|
|
4012
|
-
lastProcessedCheckpoint: result !== null ? BigInt(result.last_processed_checkpoint) : null
|
|
4013
|
-
};
|
|
4014
|
-
};
|
|
4240
|
+
IF FOUND THEN
|
|
4241
|
+
RETURN 1;
|
|
4242
|
+
END IF;
|
|
4015
4243
|
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
options?.to ?? (options?.maxCount ? (options.from ?? 0n) + options.maxCount : NaN)
|
|
4022
|
-
);
|
|
4023
|
-
const toCondition = !isNaN(to) ? `AND stream_position <= ${to}` : "";
|
|
4024
|
-
const events = await mapRows2(
|
|
4025
|
-
execute.query(
|
|
4026
|
-
SQL17`SELECT stream_id, stream_position, global_position, message_data, message_metadata, message_schema_version, message_type, message_id
|
|
4027
|
-
FROM ${SQL17.identifier(messagesTable.name)}
|
|
4028
|
-
WHERE stream_id = ${streamId} AND partition = ${options?.partition ?? defaultTag} AND is_archived = FALSE ${SQL17.plain(fromCondition)} ${SQL17.plain(toCondition)}
|
|
4029
|
-
ORDER BY stream_position ASC`
|
|
4030
|
-
),
|
|
4031
|
-
(row) => {
|
|
4032
|
-
const rawEvent = {
|
|
4033
|
-
type: row.message_type,
|
|
4034
|
-
data: row.message_data,
|
|
4035
|
-
metadata: row.message_metadata
|
|
4036
|
-
};
|
|
4037
|
-
const metadata = {
|
|
4038
|
-
..."metadata" in rawEvent ? rawEvent.metadata ?? {} : {},
|
|
4039
|
-
messageId: row.message_id,
|
|
4040
|
-
streamName: streamId,
|
|
4041
|
-
streamPosition: BigInt(row.stream_position),
|
|
4042
|
-
globalPosition: BigInt(row.global_position)
|
|
4043
|
-
};
|
|
4044
|
-
const event = {
|
|
4045
|
-
...rawEvent,
|
|
4046
|
-
kind: "Event",
|
|
4047
|
-
metadata
|
|
4048
|
-
};
|
|
4049
|
-
return upcastRecordedMessage(event, options?.schema?.versioning);
|
|
4050
|
-
}
|
|
4051
|
-
);
|
|
4052
|
-
return events.length > 0 ? {
|
|
4053
|
-
currentStreamVersion: events[events.length - 1].metadata.streamPosition,
|
|
4054
|
-
events,
|
|
4055
|
-
streamExists: true
|
|
4056
|
-
} : {
|
|
4057
|
-
currentStreamVersion: PostgreSQLEventStoreDefaultStreamVersion,
|
|
4058
|
-
events: [],
|
|
4059
|
-
streamExists: false
|
|
4060
|
-
};
|
|
4061
|
-
};
|
|
4244
|
+
SELECT "last_processed_checkpoint" INTO current_position
|
|
4245
|
+
FROM "emt_processors"
|
|
4246
|
+
WHERE "processor_id" = p_processor_id
|
|
4247
|
+
AND "partition" = p_partition
|
|
4248
|
+
AND "version" = p_version ;
|
|
4062
4249
|
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4250
|
+
IF current_position = p_position THEN
|
|
4251
|
+
RETURN 0;
|
|
4252
|
+
ELSIF current_position > p_position THEN
|
|
4253
|
+
RETURN 3;
|
|
4254
|
+
ELSE
|
|
4255
|
+
RETURN 2;
|
|
4256
|
+
END IF;
|
|
4257
|
+
END IF;
|
|
4258
|
+
|
|
4259
|
+
BEGIN
|
|
4260
|
+
INSERT INTO "emt_processors"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
|
|
4261
|
+
VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
|
|
4262
|
+
RETURN 1;
|
|
4263
|
+
EXCEPTION WHEN unique_violation THEN
|
|
4264
|
+
SELECT "last_processed_checkpoint" INTO current_position
|
|
4265
|
+
FROM "emt_processors"
|
|
4266
|
+
WHERE "processor_id" = p_processor_id
|
|
4267
|
+
AND "partition" = p_partition
|
|
4268
|
+
AND "version" = p_version;
|
|
4269
|
+
|
|
4270
|
+
IF current_position = p_position THEN
|
|
4271
|
+
RETURN 0;
|
|
4272
|
+
ELSE
|
|
4273
|
+
RETURN 2;
|
|
4274
|
+
END IF;
|
|
4275
|
+
END;
|
|
4276
|
+
END;
|
|
4277
|
+
$fn$ LANGUAGE plpgsql;
|
|
4278
|
+
END IF;
|
|
4279
|
+
END $$;
|
|
4280
|
+
`;
|
|
4281
|
+
var migration_0_43_0_cleanupLegacySubscription = sqlMigration3("emt:postgresql:eventstore:0.43.0:cleanup-legacy-subscription", [
|
|
4282
|
+
migration_0_43_0_cleanupLegacySubscriptionSQL
|
|
4283
|
+
]);
|
|
4284
|
+
var cleanupLegacySubscriptionTables = async (connectionString) => {
|
|
4285
|
+
const pool = dumbo2({ connectionString });
|
|
4286
|
+
try {
|
|
4287
|
+
await pool.withTransaction(async ({ execute }) => {
|
|
4288
|
+
await execute.command(migration_0_43_0_cleanupLegacySubscriptionSQL);
|
|
4289
|
+
});
|
|
4290
|
+
} finally {
|
|
4291
|
+
await pool.close();
|
|
4292
|
+
}
|
|
4074
4293
|
};
|
|
4075
4294
|
|
|
4076
|
-
// src/eventStore/schema/
|
|
4077
|
-
var
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4295
|
+
// src/eventStore/schema/tables.ts
|
|
4296
|
+
var streamsTableSQL = SQL15`
|
|
4297
|
+
CREATE TABLE IF NOT EXISTS ${SQL15.identifier(streamsTable.name)}(
|
|
4298
|
+
stream_id TEXT NOT NULL,
|
|
4299
|
+
stream_position BIGINT NOT NULL,
|
|
4300
|
+
partition TEXT NOT NULL DEFAULT '${SQL15.plain(defaultTag2)}',
|
|
4301
|
+
stream_type TEXT NOT NULL,
|
|
4302
|
+
stream_metadata JSONB NOT NULL,
|
|
4303
|
+
is_archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
4304
|
+
PRIMARY KEY (stream_id, partition, is_archived)
|
|
4305
|
+
) PARTITION BY LIST (partition);
|
|
4306
|
+
|
|
4307
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_streams_unique
|
|
4308
|
+
ON ${SQL15.identifier(streamsTable.name)}(stream_id, partition, is_archived)
|
|
4309
|
+
INCLUDE (stream_position);`;
|
|
4310
|
+
var messagesTableSQL = SQL15`
|
|
4311
|
+
CREATE SEQUENCE IF NOT EXISTS emt_global_message_position;
|
|
4312
|
+
|
|
4313
|
+
CREATE TABLE IF NOT EXISTS ${SQL15.identifier(messagesTable.name)}(
|
|
4314
|
+
stream_position BIGINT NOT NULL,
|
|
4315
|
+
global_position BIGINT DEFAULT nextval('emt_global_message_position'),
|
|
4316
|
+
transaction_id XID8 NOT NULL,
|
|
4317
|
+
created TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
4318
|
+
is_archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
4319
|
+
message_kind VARCHAR(1) NOT NULL DEFAULT 'E',
|
|
4320
|
+
stream_id TEXT NOT NULL,
|
|
4321
|
+
partition TEXT NOT NULL DEFAULT '${SQL15.plain(defaultTag2)}',
|
|
4322
|
+
message_schema_version TEXT NOT NULL,
|
|
4323
|
+
message_id TEXT NOT NULL,
|
|
4324
|
+
message_type TEXT NOT NULL,
|
|
4325
|
+
message_data JSONB NOT NULL,
|
|
4326
|
+
message_metadata JSONB NOT NULL,
|
|
4327
|
+
PRIMARY KEY (stream_id, stream_position, partition, is_archived)
|
|
4328
|
+
) PARTITION BY LIST (partition);`;
|
|
4329
|
+
var processorsTableSQL = SQL15`
|
|
4330
|
+
CREATE TABLE IF NOT EXISTS ${SQL15.identifier(processorsTable.name)}(
|
|
4331
|
+
last_processed_transaction_id XID8 NOT NULL,
|
|
4332
|
+
version INT NOT NULL DEFAULT 1,
|
|
4333
|
+
processor_id TEXT NOT NULL,
|
|
4334
|
+
partition TEXT NOT NULL DEFAULT '${SQL15.plain(defaultTag2)}',
|
|
4335
|
+
status TEXT NOT NULL DEFAULT 'stopped',
|
|
4336
|
+
last_processed_checkpoint TEXT NOT NULL,
|
|
4337
|
+
processor_instance_id TEXT DEFAULT '${SQL15.plain(unknownTag2)}',
|
|
4338
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
4339
|
+
last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
4340
|
+
PRIMARY KEY (processor_id, partition, version)
|
|
4341
|
+
) PARTITION BY LIST (partition);
|
|
4342
|
+
`;
|
|
4343
|
+
var projectionsTableSQL = SQL15`
|
|
4344
|
+
CREATE TABLE IF NOT EXISTS ${SQL15.identifier(projectionsTable.name)}(
|
|
4345
|
+
version INT NOT NULL DEFAULT 1,
|
|
4346
|
+
type VARCHAR(1) NOT NULL,
|
|
4347
|
+
name TEXT NOT NULL,
|
|
4348
|
+
partition TEXT NOT NULL DEFAULT '${SQL15.plain(defaultTag2)}',
|
|
4349
|
+
kind TEXT NOT NULL,
|
|
4350
|
+
status TEXT NOT NULL,
|
|
4351
|
+
definition JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
4352
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
4353
|
+
last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
4354
|
+
PRIMARY KEY (name, partition, version)
|
|
4355
|
+
) PARTITION BY LIST (partition);
|
|
4356
|
+
`;
|
|
4357
|
+
var sanitizeNameSQL = createFunctionIfDoesNotExistSQL(
|
|
4358
|
+
"emt_sanitize_name",
|
|
4359
|
+
SQL15`CREATE OR REPLACE FUNCTION emt_sanitize_name(input_name TEXT) RETURNS TEXT AS $emt_sanitize_name$
|
|
4360
|
+
BEGIN
|
|
4361
|
+
RETURN REGEXP_REPLACE(input_name, '[^a-zA-Z0-9_]', '_', 'g');
|
|
4362
|
+
END;
|
|
4363
|
+
$emt_sanitize_name$ LANGUAGE plpgsql;`
|
|
4097
4364
|
);
|
|
4098
|
-
var
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
);
|
|
4111
|
-
const nestedPool = dumbo2({ connectionString, connection: tx.connection });
|
|
4112
|
-
try {
|
|
4113
|
-
if (hooks?.onBeforeSchemaCreated) {
|
|
4114
|
-
await hooks.onBeforeSchemaCreated(context);
|
|
4115
|
-
}
|
|
4116
|
-
const result = await runSQLMigrations(
|
|
4117
|
-
nestedPool,
|
|
4118
|
-
eventStoreSchemaMigrations,
|
|
4119
|
-
options
|
|
4120
|
-
);
|
|
4121
|
-
if (hooks?.onAfterSchemaCreated) {
|
|
4122
|
-
await hooks.onAfterSchemaCreated(context);
|
|
4123
|
-
}
|
|
4124
|
-
return result;
|
|
4125
|
-
} finally {
|
|
4126
|
-
await nestedPool.close();
|
|
4127
|
-
}
|
|
4128
|
-
});
|
|
4129
|
-
};
|
|
4365
|
+
var addTablePartitions = createFunctionIfDoesNotExistSQL(
|
|
4366
|
+
"emt_add_table_partition",
|
|
4367
|
+
SQL15`
|
|
4368
|
+
CREATE OR REPLACE FUNCTION emt_add_table_partition(tableName TEXT, partition_name TEXT) RETURNS void AS $emt_add_table_partition$
|
|
4369
|
+
DECLARE
|
|
4370
|
+
v_main_partiton_name TEXT;
|
|
4371
|
+
v_active_partiton_name TEXT;
|
|
4372
|
+
v_archived_partiton_name TEXT;
|
|
4373
|
+
BEGIN
|
|
4374
|
+
v_main_partiton_name := emt_sanitize_name(tableName || '_' || partition_name);
|
|
4375
|
+
v_active_partiton_name := emt_sanitize_name(v_main_partiton_name || '_active');
|
|
4376
|
+
v_archived_partiton_name := emt_sanitize_name(v_main_partiton_name || '_archived');
|
|
4130
4377
|
|
|
4131
|
-
// src/eventStore/schema/truncateTables.ts
|
|
4132
|
-
import { SQL as SQL19 } from "@event-driven-io/dumbo";
|
|
4133
|
-
var truncateTables = async (execute, options) => {
|
|
4134
|
-
await execute.command(
|
|
4135
|
-
SQL19`TRUNCATE TABLE
|
|
4136
|
-
${SQL19.identifier(streamsTable.name)},
|
|
4137
|
-
${SQL19.identifier(messagesTable.name)},
|
|
4138
|
-
${SQL19.identifier(processorsTable.name)},
|
|
4139
|
-
${SQL19.identifier(projectionsTable.name)}
|
|
4140
|
-
CASCADE${SQL19.plain(options?.resetSequences ? "; ALTER SEQUENCE emt_global_message_position RESTART WITH 1" : "")};`
|
|
4141
|
-
);
|
|
4142
|
-
};
|
|
4143
4378
|
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
var PostgreSQLEventStoreDefaultStreamVersion = 0n;
|
|
4150
|
-
var getPostgreSQLEventStore = (connectionString, options = defaultPostgreSQLOptions) => {
|
|
4151
|
-
const poolOptions = {
|
|
4152
|
-
connectionString,
|
|
4153
|
-
...options.connectionOptions ? options.connectionOptions : {}
|
|
4154
|
-
};
|
|
4155
|
-
const pool = "dumbo" in poolOptions ? poolOptions.dumbo : dumbo3(poolOptions);
|
|
4156
|
-
let migrateSchema = void 0;
|
|
4157
|
-
const autoGenerateSchema = options.schema?.autoMigration === void 0 || options.schema?.autoMigration !== "None";
|
|
4158
|
-
const inlineProjections = (options.projections ?? []).filter(({ type }) => type === "inline").map(({ projection: projection2 }) => projection2);
|
|
4159
|
-
const migrate = async (migrationOptions) => {
|
|
4160
|
-
if (!migrateSchema) {
|
|
4161
|
-
migrateSchema = createEventStoreSchema(
|
|
4162
|
-
connectionString,
|
|
4163
|
-
pool,
|
|
4164
|
-
{
|
|
4165
|
-
onBeforeSchemaCreated: async (context) => {
|
|
4166
|
-
if (options.hooks?.onBeforeSchemaCreated) {
|
|
4167
|
-
await options.hooks.onBeforeSchemaCreated(context);
|
|
4168
|
-
}
|
|
4169
|
-
},
|
|
4170
|
-
onAfterSchemaCreated: async (context) => {
|
|
4171
|
-
for (const projection2 of inlineProjections) {
|
|
4172
|
-
if (projection2.init) {
|
|
4173
|
-
await projection2.init({
|
|
4174
|
-
version: projection2.version ?? 1,
|
|
4175
|
-
status: "active",
|
|
4176
|
-
registrationType: "inline",
|
|
4177
|
-
context: { ...context, migrationOptions }
|
|
4178
|
-
});
|
|
4179
|
-
}
|
|
4180
|
-
}
|
|
4181
|
-
if (options.hooks?.onAfterSchemaCreated) {
|
|
4182
|
-
await options.hooks.onAfterSchemaCreated(context);
|
|
4183
|
-
}
|
|
4184
|
-
}
|
|
4185
|
-
},
|
|
4186
|
-
migrationOptions
|
|
4379
|
+
-- create default partition
|
|
4380
|
+
EXECUTE format('
|
|
4381
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4382
|
+
FOR VALUES IN (%L) PARTITION BY LIST (is_archived);',
|
|
4383
|
+
v_main_partiton_name, tableName, partition_name
|
|
4187
4384
|
);
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
return migrate();
|
|
4194
|
-
};
|
|
4195
|
-
const beforeCommitHook = inlineProjections.length > 0 ? async (events, { transaction }) => handleProjections({
|
|
4196
|
-
projections: inlineProjections,
|
|
4197
|
-
// TODO: Add proper handling of global data
|
|
4198
|
-
// Currently it's not available as append doesn't return array of global position but just the last one
|
|
4199
|
-
events,
|
|
4200
|
-
...await transactionToPostgreSQLProjectionHandlerContext(
|
|
4201
|
-
connectionString,
|
|
4202
|
-
pool,
|
|
4203
|
-
transaction
|
|
4204
|
-
)
|
|
4205
|
-
}) : void 0;
|
|
4206
|
-
return {
|
|
4207
|
-
schema: {
|
|
4208
|
-
sql: () => SQL20.describe(
|
|
4209
|
-
schemaSQL,
|
|
4210
|
-
getFormatter(fromDatabaseDriverType(pool.driverType).databaseType)
|
|
4211
|
-
),
|
|
4212
|
-
print: () => console.log(
|
|
4213
|
-
SQL20.describe(
|
|
4214
|
-
schemaSQL,
|
|
4215
|
-
getFormatter(fromDatabaseDriverType(pool.driverType).databaseType)
|
|
4216
|
-
)
|
|
4217
|
-
),
|
|
4218
|
-
migrate,
|
|
4219
|
-
dangerous: {
|
|
4220
|
-
truncate: (truncateOptions) => pool.withTransaction(async (transaction) => {
|
|
4221
|
-
await ensureSchemaExists();
|
|
4222
|
-
await truncateTables(transaction.execute, truncateOptions);
|
|
4223
|
-
if (truncateOptions?.truncateProjections) {
|
|
4224
|
-
const projectionContext = await transactionToPostgreSQLProjectionHandlerContext(
|
|
4225
|
-
connectionString,
|
|
4226
|
-
pool,
|
|
4227
|
-
transaction
|
|
4228
|
-
);
|
|
4229
|
-
for (const projection2 of options?.projections ?? []) {
|
|
4230
|
-
if (projection2.projection.truncate)
|
|
4231
|
-
await projection2.projection.truncate(projectionContext);
|
|
4232
|
-
}
|
|
4233
|
-
}
|
|
4234
|
-
})
|
|
4235
|
-
}
|
|
4236
|
-
},
|
|
4237
|
-
async aggregateStream(streamName, options2) {
|
|
4238
|
-
const { evolve, initialState, read } = options2;
|
|
4239
|
-
const expectedStreamVersion = read?.expectedStreamVersion;
|
|
4240
|
-
let state = initialState();
|
|
4241
|
-
const result = await this.readStream(
|
|
4242
|
-
streamName,
|
|
4243
|
-
read
|
|
4385
|
+
|
|
4386
|
+
EXECUTE format('
|
|
4387
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4388
|
+
FOR VALUES IN (FALSE);',
|
|
4389
|
+
v_active_partiton_name, v_main_partiton_name
|
|
4244
4390
|
);
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4391
|
+
|
|
4392
|
+
EXECUTE format('
|
|
4393
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4394
|
+
FOR VALUES IN (TRUE);',
|
|
4395
|
+
v_archived_partiton_name, v_main_partiton_name
|
|
4250
4396
|
);
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
};
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
options2
|
|
4397
|
+
END;
|
|
4398
|
+
$emt_add_table_partition$ LANGUAGE plpgsql;`
|
|
4399
|
+
);
|
|
4400
|
+
var addPartitionSQL = createFunctionIfDoesNotExistSQL(
|
|
4401
|
+
"emt_add_partition",
|
|
4402
|
+
SQL15`
|
|
4403
|
+
CREATE OR REPLACE FUNCTION emt_add_partition(partition_name TEXT) RETURNS void AS $emt_add_partition$
|
|
4404
|
+
BEGIN
|
|
4405
|
+
PERFORM emt_add_table_partition('${SQL15.plain(messagesTable.name)}', partition_name);
|
|
4406
|
+
PERFORM emt_add_table_partition('${SQL15.plain(streamsTable.name)}', partition_name);
|
|
4407
|
+
|
|
4408
|
+
EXECUTE format('
|
|
4409
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4410
|
+
FOR VALUES IN (%L);',
|
|
4411
|
+
emt_sanitize_name('${SQL15.plain(processorsTable.name)}' || '_' || partition_name), '${SQL15.plain(processorsTable.name)}', partition_name
|
|
4267
4412
|
);
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
const appendResult = await appendToStream(
|
|
4274
|
-
// TODO: Fix this when introducing more drivers
|
|
4275
|
-
pool,
|
|
4276
|
-
streamName,
|
|
4277
|
-
streamType,
|
|
4278
|
-
downcastRecordedMessages(events, options2?.schema?.versioning),
|
|
4279
|
-
{
|
|
4280
|
-
...options2,
|
|
4281
|
-
beforeCommitHook
|
|
4282
|
-
}
|
|
4413
|
+
|
|
4414
|
+
EXECUTE format('
|
|
4415
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4416
|
+
FOR VALUES IN (%L);',
|
|
4417
|
+
emt_sanitize_name('${SQL15.plain(projectionsTable.name)}' || '_' || partition_name), '${SQL15.plain(projectionsTable.name)}', partition_name
|
|
4283
4418
|
);
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4419
|
+
END;
|
|
4420
|
+
$emt_add_partition$ LANGUAGE plpgsql;`
|
|
4421
|
+
);
|
|
4422
|
+
var addModuleSQL = SQL15`
|
|
4423
|
+
CREATE OR REPLACE FUNCTION add_module(new_module TEXT) RETURNS void AS $$
|
|
4424
|
+
BEGIN
|
|
4425
|
+
-- For ${SQL15.plain(messagesTable.name)} table
|
|
4426
|
+
EXECUTE format('
|
|
4427
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4428
|
+
FOR VALUES IN (emt_sanitize_name(%L || ''__'' || %L)) PARTITION BY LIST (is_archived);',
|
|
4429
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}'), '${SQL15.plain(messagesTable.name)}', new_module, '${SQL15.plain(globalTag)}'
|
|
4430
|
+
);
|
|
4431
|
+
|
|
4432
|
+
EXECUTE format('
|
|
4433
|
+
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
4434
|
+
FOR VALUES IN (FALSE);',
|
|
4435
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}' || '_active'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}')
|
|
4436
|
+
);
|
|
4437
|
+
|
|
4438
|
+
EXECUTE format('
|
|
4439
|
+
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
4440
|
+
FOR VALUES IN (TRUE);',
|
|
4441
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}' || '_archived'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}')
|
|
4442
|
+
);
|
|
4443
|
+
|
|
4444
|
+
-- For ${SQL15.plain(streamsTable.name)} table
|
|
4445
|
+
EXECUTE format('
|
|
4446
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4447
|
+
FOR VALUES IN (emt_sanitize_name(%L || ''__'' || %L)) PARTITION BY LIST (is_archived);',
|
|
4448
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}'), '${SQL15.plain(streamsTable.name)}', new_module, '${SQL15.plain(globalTag)}'
|
|
4449
|
+
);
|
|
4450
|
+
|
|
4451
|
+
EXECUTE format('
|
|
4452
|
+
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
4453
|
+
FOR VALUES IN (FALSE);',
|
|
4454
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}' || '_active'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}')
|
|
4455
|
+
);
|
|
4456
|
+
|
|
4457
|
+
EXECUTE format('
|
|
4458
|
+
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
4459
|
+
FOR VALUES IN (TRUE);',
|
|
4460
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}' || '_archived'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}')
|
|
4461
|
+
);
|
|
4462
|
+
END;
|
|
4463
|
+
$$ LANGUAGE plpgsql;
|
|
4464
|
+
`;
|
|
4465
|
+
var addTenantSQL = SQL15`
|
|
4466
|
+
CREATE OR REPLACE FUNCTION add_tenant(new_module TEXT, new_tenant TEXT) RETURNS void AS $$
|
|
4467
|
+
BEGIN
|
|
4468
|
+
-- For ${SQL15.plain(messagesTable.name)} table
|
|
4469
|
+
EXECUTE format('
|
|
4470
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4471
|
+
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
4472
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant), '${SQL15.plain(messagesTable.name)}', new_module, new_tenant
|
|
4289
4473
|
);
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
},
|
|
4296
|
-
streamExists: async (streamName, options2) => {
|
|
4297
|
-
await ensureSchemaExists();
|
|
4298
|
-
return streamExists(pool.execute, streamName, options2);
|
|
4299
|
-
},
|
|
4300
|
-
consumer: (options2) => postgreSQLEventStoreConsumer({
|
|
4301
|
-
...options2 ?? {},
|
|
4302
|
-
pool,
|
|
4303
|
-
connectionString
|
|
4304
|
-
}),
|
|
4305
|
-
close: () => pool.close(),
|
|
4306
|
-
async withSession(callback) {
|
|
4307
|
-
return await pool.withConnection(async (connection) => {
|
|
4308
|
-
const storeOptions = {
|
|
4309
|
-
...options,
|
|
4310
|
-
connectionOptions: {
|
|
4311
|
-
connection
|
|
4312
|
-
},
|
|
4313
|
-
schema: {
|
|
4314
|
-
...options.schema ?? {},
|
|
4315
|
-
autoMigration: "None"
|
|
4316
|
-
}
|
|
4317
|
-
};
|
|
4318
|
-
const eventStore = getPostgreSQLEventStore(
|
|
4319
|
-
connectionString,
|
|
4320
|
-
storeOptions
|
|
4474
|
+
|
|
4475
|
+
EXECUTE format('
|
|
4476
|
+
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
4477
|
+
FOR VALUES IN (FALSE);',
|
|
4478
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant || '_active'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant)
|
|
4321
4479
|
);
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4480
|
+
|
|
4481
|
+
EXECUTE format('
|
|
4482
|
+
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
4483
|
+
FOR VALUES IN (TRUE);',
|
|
4484
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant || '_archived'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant)
|
|
4327
4485
|
);
|
|
4328
|
-
|
|
4329
|
-
|
|
4486
|
+
|
|
4487
|
+
-- For ${SQL15.plain(streamsTable.name)} table
|
|
4488
|
+
EXECUTE format('
|
|
4489
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4490
|
+
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
4491
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant), '${SQL15.plain(streamsTable.name)}', new_module, new_tenant
|
|
4492
|
+
);
|
|
4493
|
+
|
|
4494
|
+
EXECUTE format('
|
|
4495
|
+
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
4496
|
+
FOR VALUES IN (FALSE);',
|
|
4497
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant || '_active'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant)
|
|
4498
|
+
);
|
|
4499
|
+
|
|
4500
|
+
EXECUTE format('
|
|
4501
|
+
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
4502
|
+
FOR VALUES IN (TRUE);',
|
|
4503
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant || '_archived'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant)
|
|
4504
|
+
);
|
|
4505
|
+
END;
|
|
4506
|
+
$$ LANGUAGE plpgsql;
|
|
4507
|
+
`;
|
|
4508
|
+
var addModuleForAllTenantsSQL = SQL15`
|
|
4509
|
+
CREATE OR REPLACE FUNCTION add_module_for_all_tenants(new_module TEXT) RETURNS void AS $$
|
|
4510
|
+
DECLARE
|
|
4511
|
+
tenant_record RECORD;
|
|
4512
|
+
BEGIN
|
|
4513
|
+
PERFORM add_module(new_module);
|
|
4514
|
+
|
|
4515
|
+
FOR tenant_record IN SELECT DISTINCT tenant FROM ${SQL15.plain(messagesTable.name)}
|
|
4516
|
+
LOOP
|
|
4517
|
+
-- For ${SQL15.plain(messagesTable.name)} table
|
|
4518
|
+
EXECUTE format('
|
|
4519
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4520
|
+
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
4521
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant), '${SQL15.plain(messagesTable.name)}', new_module, tenant_record.tenant
|
|
4522
|
+
);
|
|
4523
|
+
|
|
4524
|
+
EXECUTE format('
|
|
4525
|
+
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
4526
|
+
FOR VALUES IN (FALSE);',
|
|
4527
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant || '_active'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant)
|
|
4528
|
+
);
|
|
4529
|
+
|
|
4530
|
+
EXECUTE format('
|
|
4531
|
+
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
4532
|
+
FOR VALUES IN (TRUE);',
|
|
4533
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant || '_archived'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant)
|
|
4534
|
+
);
|
|
4535
|
+
|
|
4536
|
+
-- For ${SQL15.plain(streamsTable.name)} table
|
|
4537
|
+
EXECUTE format('
|
|
4538
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4539
|
+
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
4540
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant), '${SQL15.plain(streamsTable.name)}', new_module, tenant_record.tenant
|
|
4541
|
+
);
|
|
4542
|
+
|
|
4543
|
+
EXECUTE format('
|
|
4544
|
+
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
4545
|
+
FOR VALUES IN (FALSE);',
|
|
4546
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant || '_active'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant)
|
|
4547
|
+
);
|
|
4548
|
+
|
|
4549
|
+
EXECUTE format('
|
|
4550
|
+
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
4551
|
+
FOR VALUES IN (TRUE);',
|
|
4552
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant || '_archived'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant)
|
|
4553
|
+
);
|
|
4554
|
+
END LOOP;
|
|
4555
|
+
END;
|
|
4556
|
+
$$ LANGUAGE plpgsql;
|
|
4557
|
+
`;
|
|
4558
|
+
var addTenantForAllModulesSQL = SQL15`
|
|
4559
|
+
CREATE OR REPLACE FUNCTION add_tenant_for_all_modules(new_tenant TEXT) RETURNS void AS $$
|
|
4560
|
+
DECLARE
|
|
4561
|
+
module_record RECORD;
|
|
4562
|
+
BEGIN
|
|
4563
|
+
FOR module_record IN SELECT DISTINCT partitionname FROM pg_partman.part_config WHERE parent_table = '${SQL15.plain(messagesTable.name)}'
|
|
4564
|
+
LOOP
|
|
4565
|
+
-- For ${SQL15.plain(messagesTable.name)} table
|
|
4566
|
+
EXECUTE format('
|
|
4567
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4568
|
+
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
4569
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant), '${SQL15.plain(messagesTable.name)}', module_record.partitionname, new_tenant
|
|
4570
|
+
);
|
|
4571
|
+
|
|
4572
|
+
EXECUTE format('
|
|
4573
|
+
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
4574
|
+
FOR VALUES IN (FALSE);',
|
|
4575
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_active'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant)
|
|
4576
|
+
);
|
|
4577
|
+
|
|
4578
|
+
EXECUTE format('
|
|
4579
|
+
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
4580
|
+
FOR VALUES IN (TRUE);',
|
|
4581
|
+
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_archived'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant)
|
|
4582
|
+
);
|
|
4583
|
+
|
|
4584
|
+
-- For ${SQL15.plain(streamsTable.name)} table
|
|
4585
|
+
EXECUTE format('
|
|
4586
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4587
|
+
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
4588
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant), '${SQL15.plain(streamsTable.name)}', module_record.partitionname, new_tenant
|
|
4589
|
+
);
|
|
4590
|
+
|
|
4591
|
+
EXECUTE format('
|
|
4592
|
+
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
4593
|
+
FOR VALUES IN (FALSE);',
|
|
4594
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_active'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant)
|
|
4595
|
+
);
|
|
4596
|
+
|
|
4597
|
+
EXECUTE format('
|
|
4598
|
+
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
4599
|
+
FOR VALUES IN (TRUE);',
|
|
4600
|
+
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant || '_archived'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant)
|
|
4601
|
+
);
|
|
4602
|
+
END LOOP;
|
|
4603
|
+
END;
|
|
4604
|
+
$$ LANGUAGE plpgsql;
|
|
4605
|
+
`;
|
|
4606
|
+
var addDefaultPartitionSQL = SQL15`SELECT emt_add_partition('${SQL15.plain(defaultTag2)}');`;
|
|
4607
|
+
|
|
4608
|
+
// src/eventStore/schema/readProcessorCheckpoint.ts
|
|
4609
|
+
import { singleOrNull as singleOrNull3, SQL as SQL16 } from "@event-driven-io/dumbo";
|
|
4610
|
+
var readProcessorCheckpoint = async (execute, options) => {
|
|
4611
|
+
const result = await singleOrNull3(
|
|
4612
|
+
execute.query(
|
|
4613
|
+
SQL16`SELECT last_processed_checkpoint
|
|
4614
|
+
FROM ${SQL16.identifier(processorsTable.name)}
|
|
4615
|
+
WHERE partition = ${options?.partition ?? defaultTag2} AND processor_id = ${options.processorId} AND version = ${options.version ?? 1}
|
|
4616
|
+
LIMIT 1`
|
|
4617
|
+
)
|
|
4618
|
+
);
|
|
4619
|
+
return {
|
|
4620
|
+
lastProcessedCheckpoint: result !== null ? result.last_processed_checkpoint : null
|
|
4330
4621
|
};
|
|
4331
4622
|
};
|
|
4332
4623
|
|
|
4333
|
-
// src/eventStore/
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4624
|
+
// src/eventStore/schema/readStream.ts
|
|
4625
|
+
import { mapRows as mapRows2, SQL as SQL17 } from "@event-driven-io/dumbo";
|
|
4626
|
+
var readStream = async (execute, streamId, options) => {
|
|
4627
|
+
const fromCondition = options?.from ? `AND stream_position >= ${options.from}` : "";
|
|
4628
|
+
const to = Number(
|
|
4629
|
+
options?.to ?? (options?.maxCount ? (options.from ?? 0n) + options.maxCount : NaN)
|
|
4630
|
+
);
|
|
4631
|
+
const toCondition = !isNaN(to) ? `AND stream_position <= ${to}` : "";
|
|
4632
|
+
const events = await mapRows2(
|
|
4633
|
+
execute.query(
|
|
4634
|
+
SQL17`SELECT stream_id, stream_position, global_position, message_data, message_metadata, message_schema_version, message_type, message_id
|
|
4635
|
+
FROM ${SQL17.identifier(messagesTable.name)}
|
|
4636
|
+
WHERE stream_id = ${streamId} AND partition = ${options?.partition ?? defaultTag2} AND is_archived = FALSE ${SQL17.plain(fromCondition)} ${SQL17.plain(toCondition)}
|
|
4637
|
+
ORDER BY stream_position ASC`
|
|
4638
|
+
),
|
|
4639
|
+
(row) => {
|
|
4640
|
+
const rawEvent = {
|
|
4641
|
+
type: row.message_type,
|
|
4642
|
+
data: row.message_data,
|
|
4643
|
+
metadata: row.message_metadata
|
|
4644
|
+
};
|
|
4645
|
+
const metadata = {
|
|
4646
|
+
..."metadata" in rawEvent ? rawEvent.metadata ?? {} : {},
|
|
4647
|
+
messageId: row.message_id,
|
|
4648
|
+
streamName: streamId,
|
|
4649
|
+
streamPosition: BigInt(row.stream_position),
|
|
4650
|
+
globalPosition: BigInt(row.global_position),
|
|
4651
|
+
checkpoint: bigIntProcessorCheckpoint(BigInt(row.global_position))
|
|
4361
4652
|
};
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
const run = async (pool) => {
|
|
4367
|
-
let globalPosition = 0n;
|
|
4368
|
-
const numberOfTimes = options2?.numberOfTimes ?? 1;
|
|
4369
|
-
for (const event of [
|
|
4370
|
-
...givenEvents,
|
|
4371
|
-
...Array.from({ length: numberOfTimes }).flatMap(() => events)
|
|
4372
|
-
]) {
|
|
4373
|
-
const metadata = {
|
|
4374
|
-
globalPosition: ++globalPosition,
|
|
4375
|
-
streamPosition: globalPosition,
|
|
4376
|
-
streamName: `test-${uuid6()}`,
|
|
4377
|
-
messageId: uuid6()
|
|
4378
|
-
};
|
|
4379
|
-
allEvents.push({
|
|
4380
|
-
...event,
|
|
4381
|
-
kind: "Event",
|
|
4382
|
-
metadata: {
|
|
4383
|
-
...metadata,
|
|
4384
|
-
..."metadata" in event ? event.metadata ?? {} : {}
|
|
4385
|
-
}
|
|
4386
|
-
});
|
|
4387
|
-
}
|
|
4388
|
-
await initialize(pool);
|
|
4389
|
-
await pool.withTransaction(async (transaction) => {
|
|
4390
|
-
await handleProjections({
|
|
4391
|
-
events: allEvents,
|
|
4392
|
-
projections: [projection2],
|
|
4393
|
-
...await transactionToPostgreSQLProjectionHandlerContext(
|
|
4394
|
-
connectionString,
|
|
4395
|
-
pool,
|
|
4396
|
-
transaction
|
|
4397
|
-
)
|
|
4398
|
-
});
|
|
4399
|
-
});
|
|
4400
|
-
};
|
|
4401
|
-
return {
|
|
4402
|
-
then: async (assert, message) => {
|
|
4403
|
-
const pool = dumbo4(dumoOptions);
|
|
4404
|
-
try {
|
|
4405
|
-
await run(pool);
|
|
4406
|
-
const succeeded = await assert({ pool, connectionString });
|
|
4407
|
-
if (succeeded !== void 0 && succeeded === false)
|
|
4408
|
-
assertFails(
|
|
4409
|
-
message ?? "Projection specification didn't match the criteria"
|
|
4410
|
-
);
|
|
4411
|
-
} finally {
|
|
4412
|
-
await pool.close();
|
|
4413
|
-
}
|
|
4414
|
-
},
|
|
4415
|
-
thenThrows: async (...args) => {
|
|
4416
|
-
const pool = dumbo4(dumoOptions);
|
|
4417
|
-
try {
|
|
4418
|
-
await run(pool);
|
|
4419
|
-
throw new AssertionError("Handler did not fail as expected");
|
|
4420
|
-
} catch (error) {
|
|
4421
|
-
if (error instanceof AssertionError) throw error;
|
|
4422
|
-
if (args.length === 0) return;
|
|
4423
|
-
if (!isErrorConstructor(args[0])) {
|
|
4424
|
-
assertTrue(
|
|
4425
|
-
args[0](error),
|
|
4426
|
-
`Error didn't match the error condition: ${error?.toString()}`
|
|
4427
|
-
);
|
|
4428
|
-
return;
|
|
4429
|
-
}
|
|
4430
|
-
assertTrue(
|
|
4431
|
-
error instanceof args[0],
|
|
4432
|
-
`Caught error is not an instance of the expected type: ${error?.toString()}`
|
|
4433
|
-
);
|
|
4434
|
-
if (args[1]) {
|
|
4435
|
-
assertTrue(
|
|
4436
|
-
args[1](error),
|
|
4437
|
-
`Error didn't match the error condition: ${error?.toString()}`
|
|
4438
|
-
);
|
|
4439
|
-
}
|
|
4440
|
-
} finally {
|
|
4441
|
-
await pool.close();
|
|
4442
|
-
}
|
|
4443
|
-
}
|
|
4444
|
-
};
|
|
4445
|
-
}
|
|
4446
|
-
};
|
|
4653
|
+
const event = {
|
|
4654
|
+
...rawEvent,
|
|
4655
|
+
kind: "Event",
|
|
4656
|
+
metadata
|
|
4447
4657
|
};
|
|
4658
|
+
return upcastRecordedMessage(event, options?.schema?.versioning);
|
|
4448
4659
|
}
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4660
|
+
);
|
|
4661
|
+
return events.length > 0 ? {
|
|
4662
|
+
currentStreamVersion: events[events.length - 1].metadata.streamPosition,
|
|
4663
|
+
events,
|
|
4664
|
+
streamExists: true
|
|
4665
|
+
} : {
|
|
4666
|
+
currentStreamVersion: PostgreSQLEventStoreDefaultStreamVersion,
|
|
4667
|
+
events: [],
|
|
4668
|
+
streamExists: false
|
|
4458
4669
|
};
|
|
4459
4670
|
};
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
};
|
|
4463
|
-
var
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4671
|
+
|
|
4672
|
+
// src/eventStore/schema/streamExists.ts
|
|
4673
|
+
import { SQL as SQL18 } from "@event-driven-io/dumbo";
|
|
4674
|
+
var streamExists = async (execute, streamId, options) => {
|
|
4675
|
+
const queryResult = await execute.query(
|
|
4676
|
+
SQL18`SELECT EXISTS (
|
|
4677
|
+
SELECT 1
|
|
4678
|
+
from ${SQL18.identifier(streamsTable.name)}
|
|
4679
|
+
WHERE stream_id = ${streamId} AND partition = ${options?.partition ?? defaultTag2} AND is_archived = FALSE)
|
|
4680
|
+
`
|
|
4681
|
+
);
|
|
4682
|
+
return queryResult.rows[0]?.exists ?? false;
|
|
4467
4683
|
};
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4684
|
+
|
|
4685
|
+
// src/eventStore/schema/index.ts
|
|
4686
|
+
var schemaSQL = [
|
|
4687
|
+
streamsTableSQL,
|
|
4688
|
+
messagesTableSQL,
|
|
4689
|
+
projectionsTableSQL,
|
|
4690
|
+
processorsTableSQL,
|
|
4691
|
+
sanitizeNameSQL,
|
|
4692
|
+
addTablePartitions,
|
|
4693
|
+
addPartitionSQL,
|
|
4694
|
+
appendToStreamSQL,
|
|
4695
|
+
addDefaultPartitionSQL,
|
|
4696
|
+
storeSubscriptionCheckpointSQL,
|
|
4697
|
+
tryAcquireProcessorLockSQL,
|
|
4698
|
+
releaseProcessorLockSQL,
|
|
4699
|
+
registerProjectionSQL,
|
|
4700
|
+
activateProjectionSQL,
|
|
4701
|
+
deactivateProjectionSQL
|
|
4702
|
+
];
|
|
4703
|
+
var schemaMigration = sqlMigration4(
|
|
4704
|
+
"emt:postgresql:eventstore:initial",
|
|
4705
|
+
schemaSQL
|
|
4706
|
+
);
|
|
4707
|
+
var eventStoreSchemaMigrations = [
|
|
4708
|
+
migration_0_38_7_and_older,
|
|
4709
|
+
migration_0_42_0_FromSubscriptionsToProcessors,
|
|
4710
|
+
migration_0_42_0_2_AddProcessorProjectionFunctions,
|
|
4711
|
+
schemaMigration
|
|
4712
|
+
];
|
|
4713
|
+
var createEventStoreSchema = (connectionString, pool, hooks, options) => {
|
|
4714
|
+
return pool.withTransaction(async (tx) => {
|
|
4715
|
+
const context = await transactionToPostgreSQLProjectionHandlerContext(
|
|
4716
|
+
connectionString,
|
|
4717
|
+
pool,
|
|
4718
|
+
tx
|
|
4719
|
+
);
|
|
4720
|
+
const nestedPool = dumbo3({
|
|
4721
|
+
connectionString,
|
|
4722
|
+
connection: tx.connection,
|
|
4723
|
+
serialization: options?.serialization
|
|
4724
|
+
});
|
|
4725
|
+
try {
|
|
4726
|
+
if (hooks?.onBeforeSchemaCreated) {
|
|
4727
|
+
await hooks.onBeforeSchemaCreated(context);
|
|
4728
|
+
}
|
|
4729
|
+
const result = await runSQLMigrations(
|
|
4730
|
+
nestedPool,
|
|
4731
|
+
eventStoreSchemaMigrations,
|
|
4732
|
+
options
|
|
4733
|
+
);
|
|
4734
|
+
if (hooks?.onAfterSchemaCreated) {
|
|
4735
|
+
await hooks.onAfterSchemaCreated(context);
|
|
4736
|
+
}
|
|
4737
|
+
return result;
|
|
4738
|
+
} finally {
|
|
4739
|
+
await nestedPool.close();
|
|
4472
4740
|
}
|
|
4473
|
-
})
|
|
4741
|
+
});
|
|
4474
4742
|
};
|
|
4475
4743
|
|
|
4476
|
-
// src/eventStore/
|
|
4477
|
-
import "@event-driven-io/dumbo";
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
}
|
|
4487
|
-
});
|
|
4488
|
-
var handleProjections = async (options) => {
|
|
4489
|
-
const {
|
|
4490
|
-
projections: allProjections,
|
|
4491
|
-
events,
|
|
4492
|
-
connection: { pool, transaction, connectionString },
|
|
4493
|
-
partition = defaultTag
|
|
4494
|
-
} = options;
|
|
4495
|
-
const eventTypes = events.map((e) => e.type);
|
|
4496
|
-
const projections = allProjections.filter(
|
|
4497
|
-
(p) => p.canHandle.some((type) => eventTypes.includes(type))
|
|
4744
|
+
// src/eventStore/schema/truncateTables.ts
|
|
4745
|
+
import { SQL as SQL19 } from "@event-driven-io/dumbo";
|
|
4746
|
+
var truncateTables = async (execute, options) => {
|
|
4747
|
+
await execute.command(
|
|
4748
|
+
SQL19`TRUNCATE TABLE
|
|
4749
|
+
${SQL19.identifier(streamsTable.name)},
|
|
4750
|
+
${SQL19.identifier(messagesTable.name)},
|
|
4751
|
+
${SQL19.identifier(processorsTable.name)},
|
|
4752
|
+
${SQL19.identifier(projectionsTable.name)}
|
|
4753
|
+
CASCADE${SQL19.plain(options?.resetSequences ? "; ALTER SEQUENCE emt_global_message_position RESTART WITH 1" : "")};`
|
|
4498
4754
|
);
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4755
|
+
};
|
|
4756
|
+
|
|
4757
|
+
// src/eventStore/postgreSQLEventStore.ts
|
|
4758
|
+
var defaultPostgreSQLOptions = {
|
|
4759
|
+
projections: [],
|
|
4760
|
+
schema: { autoMigration: "CreateOrUpdate" }
|
|
4761
|
+
};
|
|
4762
|
+
var PostgreSQLEventStoreDefaultStreamVersion = 0n;
|
|
4763
|
+
var getPostgreSQLEventStore = (connectionString, options = defaultPostgreSQLOptions) => {
|
|
4764
|
+
const poolOptions = {
|
|
4765
|
+
connectionString,
|
|
4766
|
+
...options.connectionOptions ? options.connectionOptions : {}
|
|
4767
|
+
};
|
|
4768
|
+
const pool = "dumbo" in poolOptions ? poolOptions.dumbo : dumbo4({ ...poolOptions, serialization: options.serialization });
|
|
4769
|
+
let migrateSchema = void 0;
|
|
4770
|
+
const autoGenerateSchema = options.schema?.autoMigration === void 0 || options.schema?.autoMigration !== "None";
|
|
4771
|
+
const inlineProjections = (options.projections ?? []).filter(({ type }) => type === "inline").map(({ projection: projection2 }) => projection2);
|
|
4772
|
+
const migrate = async (migrationOptions) => {
|
|
4773
|
+
if (!migrateSchema) {
|
|
4774
|
+
migrateSchema = createEventStoreSchema(
|
|
4775
|
+
connectionString,
|
|
4776
|
+
pool,
|
|
4777
|
+
{
|
|
4778
|
+
onBeforeSchemaCreated: async (context) => {
|
|
4779
|
+
if (options.hooks?.onBeforeSchemaCreated) {
|
|
4780
|
+
await options.hooks.onBeforeSchemaCreated(context);
|
|
4781
|
+
}
|
|
4782
|
+
},
|
|
4783
|
+
onAfterSchemaCreated: async (context) => {
|
|
4784
|
+
for (const projection2 of inlineProjections) {
|
|
4785
|
+
if (projection2.init) {
|
|
4786
|
+
await projection2.init({
|
|
4787
|
+
version: projection2.version ?? 1,
|
|
4788
|
+
status: "active",
|
|
4789
|
+
registrationType: "inline",
|
|
4790
|
+
context: { ...context, migrationOptions }
|
|
4791
|
+
});
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
if (options.hooks?.onAfterSchemaCreated) {
|
|
4795
|
+
await options.hooks.onAfterSchemaCreated(context);
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
},
|
|
4799
|
+
migrationOptions
|
|
4800
|
+
);
|
|
4510
4801
|
}
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4802
|
+
return migrateSchema;
|
|
4803
|
+
};
|
|
4804
|
+
const ensureSchemaExists = () => {
|
|
4805
|
+
if (!autoGenerateSchema) return Promise.resolve();
|
|
4806
|
+
return migrate();
|
|
4807
|
+
};
|
|
4808
|
+
const beforeCommitHook = inlineProjections.length > 0 ? async (events, { transaction }) => handleProjections({
|
|
4809
|
+
projections: inlineProjections,
|
|
4810
|
+
// TODO: Add proper handling of global data
|
|
4811
|
+
// Currently it's not available as append doesn't return array of global position but just the last one
|
|
4812
|
+
events,
|
|
4813
|
+
...await transactionToPostgreSQLProjectionHandlerContext(
|
|
4814
|
+
connectionString,
|
|
4815
|
+
pool,
|
|
4816
|
+
transaction
|
|
4817
|
+
)
|
|
4818
|
+
}) : void 0;
|
|
4819
|
+
return {
|
|
4820
|
+
schema: {
|
|
4821
|
+
sql: () => SQL20.describe(
|
|
4822
|
+
schemaSQL,
|
|
4823
|
+
getFormatter(fromDatabaseDriverType(pool.driverType).databaseType)
|
|
4824
|
+
),
|
|
4825
|
+
print: () => console.log(
|
|
4826
|
+
SQL20.describe(
|
|
4827
|
+
schemaSQL,
|
|
4828
|
+
getFormatter(fromDatabaseDriverType(pool.driverType).databaseType)
|
|
4829
|
+
)
|
|
4830
|
+
),
|
|
4831
|
+
migrate,
|
|
4832
|
+
dangerous: {
|
|
4833
|
+
truncate: (truncateOptions) => pool.withTransaction(async (transaction) => {
|
|
4834
|
+
await ensureSchemaExists();
|
|
4835
|
+
await truncateTables(transaction.execute, truncateOptions);
|
|
4836
|
+
if (truncateOptions?.truncateProjections) {
|
|
4837
|
+
const projectionContext = await transactionToPostgreSQLProjectionHandlerContext(
|
|
4838
|
+
connectionString,
|
|
4839
|
+
pool,
|
|
4840
|
+
transaction
|
|
4841
|
+
);
|
|
4842
|
+
for (const projection2 of options?.projections ?? []) {
|
|
4843
|
+
if (projection2.projection.truncate)
|
|
4844
|
+
await projection2.projection.truncate(projectionContext);
|
|
4845
|
+
}
|
|
4846
|
+
}
|
|
4847
|
+
})
|
|
4534
4848
|
}
|
|
4535
|
-
}
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
if (initSQL) {
|
|
4554
|
-
if (Array.isArray(initSQL)) {
|
|
4555
|
-
await initOptions.context.execute.batchCommand(initSQL);
|
|
4556
|
-
} else {
|
|
4557
|
-
await initOptions.context.execute.command(initSQL);
|
|
4849
|
+
},
|
|
4850
|
+
async aggregateStream(streamName, options2) {
|
|
4851
|
+
const { evolve, initialState, read } = options2;
|
|
4852
|
+
const expectedStreamVersion = read?.expectedStreamVersion;
|
|
4853
|
+
let state = initialState();
|
|
4854
|
+
const result = await this.readStream(
|
|
4855
|
+
streamName,
|
|
4856
|
+
read
|
|
4857
|
+
);
|
|
4858
|
+
const currentStreamVersion = result.currentStreamVersion;
|
|
4859
|
+
assertExpectedVersionMatchesCurrent(
|
|
4860
|
+
currentStreamVersion,
|
|
4861
|
+
expectedStreamVersion,
|
|
4862
|
+
PostgreSQLEventStoreDefaultStreamVersion
|
|
4863
|
+
);
|
|
4864
|
+
for (const event of result.events) {
|
|
4865
|
+
if (!event) continue;
|
|
4866
|
+
state = evolve(state, event);
|
|
4558
4867
|
}
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4868
|
+
return {
|
|
4869
|
+
currentStreamVersion,
|
|
4870
|
+
state,
|
|
4871
|
+
streamExists: result.streamExists
|
|
4872
|
+
};
|
|
4873
|
+
},
|
|
4874
|
+
readStream: async (streamName, readOptions) => {
|
|
4875
|
+
await ensureSchemaExists();
|
|
4876
|
+
return readStream(pool.execute, streamName, {
|
|
4877
|
+
...readOptions,
|
|
4878
|
+
serialization: options.serialization ?? readOptions?.serialization
|
|
4879
|
+
});
|
|
4880
|
+
},
|
|
4881
|
+
appendToStream: async (streamName, events, appendOptions) => {
|
|
4882
|
+
await ensureSchemaExists();
|
|
4883
|
+
const [firstPart, ...rest] = streamName.split("-");
|
|
4884
|
+
const streamType = firstPart && rest.length > 0 ? firstPart : unknownTag2;
|
|
4885
|
+
const appendResult = await appendToStream(
|
|
4886
|
+
// TODO: Fix this when introducing more drivers
|
|
4887
|
+
pool,
|
|
4888
|
+
streamName,
|
|
4889
|
+
streamType,
|
|
4890
|
+
downcastRecordedMessages(events, appendOptions?.schema?.versioning),
|
|
4891
|
+
{
|
|
4892
|
+
...appendOptions,
|
|
4893
|
+
beforeCommitHook
|
|
4575
4894
|
}
|
|
4576
|
-
|
|
4577
|
-
|
|
4895
|
+
);
|
|
4896
|
+
if (!appendResult.success)
|
|
4897
|
+
throw new ExpectedVersionConflictError(
|
|
4898
|
+
-1n,
|
|
4899
|
+
//TODO: Return actual version in case of error
|
|
4900
|
+
appendOptions?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK
|
|
4901
|
+
);
|
|
4902
|
+
return {
|
|
4903
|
+
nextExpectedStreamVersion: appendResult.nextStreamPosition,
|
|
4904
|
+
lastEventGlobalPosition: appendResult.globalPositions[appendResult.globalPositions.length - 1],
|
|
4905
|
+
createdNewStream: appendResult.nextStreamPosition >= BigInt(events.length)
|
|
4906
|
+
};
|
|
4907
|
+
},
|
|
4908
|
+
streamExists: async (streamName, options2) => {
|
|
4909
|
+
await ensureSchemaExists();
|
|
4910
|
+
return streamExists(pool.execute, streamName, options2);
|
|
4911
|
+
},
|
|
4912
|
+
consumer: (options2) => postgreSQLEventStoreConsumer({
|
|
4913
|
+
...options2 ?? {},
|
|
4914
|
+
pool,
|
|
4915
|
+
connectionString
|
|
4916
|
+
}),
|
|
4917
|
+
close: () => pool.close(),
|
|
4918
|
+
async withSession(callback) {
|
|
4919
|
+
return await pool.withConnection(async (connection) => {
|
|
4920
|
+
const storeOptions = {
|
|
4921
|
+
...options,
|
|
4922
|
+
connectionOptions: {
|
|
4923
|
+
connection
|
|
4924
|
+
},
|
|
4925
|
+
schema: {
|
|
4926
|
+
...options.schema ?? {},
|
|
4927
|
+
autoMigration: "None"
|
|
4928
|
+
}
|
|
4929
|
+
};
|
|
4930
|
+
const eventStore = getPostgreSQLEventStore(
|
|
4931
|
+
connectionString,
|
|
4932
|
+
storeOptions
|
|
4933
|
+
);
|
|
4934
|
+
return ensureSchemaExists().then(
|
|
4935
|
+
() => callback({
|
|
4936
|
+
eventStore,
|
|
4937
|
+
close: () => Promise.resolve()
|
|
4938
|
+
})
|
|
4939
|
+
);
|
|
4940
|
+
});
|
|
4578
4941
|
}
|
|
4579
|
-
}
|
|
4942
|
+
};
|
|
4580
4943
|
};
|
|
4581
4944
|
|
|
4582
4945
|
// src/eventStore/consumers/postgreSQLProcessor.ts
|
|
@@ -4586,10 +4949,10 @@ var postgreSQLCheckpointer = () => ({
|
|
|
4586
4949
|
return { lastCheckpoint: result?.lastProcessedCheckpoint };
|
|
4587
4950
|
},
|
|
4588
4951
|
store: async (options, context) => {
|
|
4589
|
-
const
|
|
4952
|
+
const newCheckpoint = getCheckpoint(options.message);
|
|
4590
4953
|
const result = await storeProcessorCheckpoint(context.execute, {
|
|
4591
4954
|
lastProcessedCheckpoint: options.lastCheckpoint,
|
|
4592
|
-
newCheckpoint
|
|
4955
|
+
newCheckpoint,
|
|
4593
4956
|
processorId: options.processorId,
|
|
4594
4957
|
partition: options.partition,
|
|
4595
4958
|
version: options.version
|
|
@@ -4622,7 +4985,10 @@ var postgreSQLProcessingScope = (options) => {
|
|
|
4622
4985
|
connectionString,
|
|
4623
4986
|
pool,
|
|
4624
4987
|
client,
|
|
4625
|
-
transaction
|
|
4988
|
+
transaction,
|
|
4989
|
+
messageStore: getPostgreSQLEventStore(connectionString, {
|
|
4990
|
+
connectionOptions: { client }
|
|
4991
|
+
})
|
|
4626
4992
|
}
|
|
4627
4993
|
});
|
|
4628
4994
|
});
|
|
@@ -4636,7 +5002,8 @@ var getProcessorPool = (options) => {
|
|
|
4636
5002
|
const processorConnectionString = "connectionString" in poolOptions ? poolOptions.connectionString ?? null : null;
|
|
4637
5003
|
const processorPool = "dumbo" in poolOptions ? poolOptions.dumbo : processorConnectionString ? dumbo5({
|
|
4638
5004
|
connectionString: processorConnectionString,
|
|
4639
|
-
...poolOptions
|
|
5005
|
+
...poolOptions,
|
|
5006
|
+
serialization: options.serialization
|
|
4640
5007
|
}) : null;
|
|
4641
5008
|
return {
|
|
4642
5009
|
pool: processorPool,
|
|
@@ -4663,8 +5030,7 @@ var postgreSQLProjector = (options) => {
|
|
|
4663
5030
|
processorInstanceId = getProcessorInstanceId(processorId),
|
|
4664
5031
|
version = defaultProcessorVersion,
|
|
4665
5032
|
partition = defaultProcessorPartition,
|
|
4666
|
-
|
|
4667
|
-
lockTimeoutSeconds
|
|
5033
|
+
lock
|
|
4668
5034
|
} = options;
|
|
4669
5035
|
const { pool, connectionString, close } = getProcessorPool(options);
|
|
4670
5036
|
const processorLock = postgreSQLProcessorLock({
|
|
@@ -4673,13 +5039,13 @@ var postgreSQLProjector = (options) => {
|
|
|
4673
5039
|
partition,
|
|
4674
5040
|
processorInstanceId,
|
|
4675
5041
|
projection: options.projection ? {
|
|
4676
|
-
name: options.projection.name ??
|
|
4677
|
-
kind: options.projection.kind ??
|
|
5042
|
+
name: options.projection.name ?? unknownTag,
|
|
5043
|
+
kind: options.projection.kind ?? unknownTag,
|
|
4678
5044
|
version: options.projection.version ?? version,
|
|
4679
5045
|
handlingType: "async"
|
|
4680
5046
|
} : void 0,
|
|
4681
|
-
|
|
4682
|
-
lockTimeoutSeconds
|
|
5047
|
+
lockAcquisitionPolicy: lock?.acquisitionPolicy ?? DefaultPostgreSQLProcessorLockPolicy,
|
|
5048
|
+
lockTimeoutSeconds: lock?.timeoutSeconds
|
|
4683
5049
|
});
|
|
4684
5050
|
const hooks = wrapHooksWithProcessorLocks(
|
|
4685
5051
|
{
|
|
@@ -4725,13 +5091,60 @@ var postgreSQLProjector = (options) => {
|
|
|
4725
5091
|
});
|
|
4726
5092
|
return processor;
|
|
4727
5093
|
};
|
|
5094
|
+
var postgreSQLWorkflowProcessor = (options) => {
|
|
5095
|
+
const {
|
|
5096
|
+
processorId = options.processorId ?? getWorkflowId({
|
|
5097
|
+
workflowName: options.workflow.name ?? "unknown"
|
|
5098
|
+
}),
|
|
5099
|
+
processorInstanceId = getProcessorInstanceId(processorId),
|
|
5100
|
+
version = defaultProcessorVersion,
|
|
5101
|
+
partition = defaultProcessorPartition,
|
|
5102
|
+
lock
|
|
5103
|
+
} = options;
|
|
5104
|
+
const { pool, connectionString, close } = getProcessorPool(options);
|
|
5105
|
+
const processorLock = postgreSQLProcessorLock({
|
|
5106
|
+
processorId,
|
|
5107
|
+
version,
|
|
5108
|
+
partition,
|
|
5109
|
+
processorInstanceId,
|
|
5110
|
+
projection: void 0,
|
|
5111
|
+
lockAcquisitionPolicy: lock?.acquisitionPolicy ?? DefaultPostgreSQLProcessorLockPolicy,
|
|
5112
|
+
lockTimeoutSeconds: lock?.timeoutSeconds
|
|
5113
|
+
});
|
|
5114
|
+
const hooks = wrapHooksWithProcessorLocks(
|
|
5115
|
+
{
|
|
5116
|
+
...options.hooks ?? {},
|
|
5117
|
+
onClose: close ? async (context) => {
|
|
5118
|
+
if (options.hooks?.onClose)
|
|
5119
|
+
await options.hooks?.onClose(context);
|
|
5120
|
+
if (close) await close();
|
|
5121
|
+
} : options.hooks?.onClose
|
|
5122
|
+
},
|
|
5123
|
+
processorLock
|
|
5124
|
+
);
|
|
5125
|
+
return workflowProcessor({
|
|
5126
|
+
...options,
|
|
5127
|
+
processorId,
|
|
5128
|
+
processorInstanceId,
|
|
5129
|
+
version,
|
|
5130
|
+
partition,
|
|
5131
|
+
hooks,
|
|
5132
|
+
processingScope: postgreSQLProcessingScope({
|
|
5133
|
+
pool,
|
|
5134
|
+
connectionString,
|
|
5135
|
+
processorId,
|
|
5136
|
+
partition
|
|
5137
|
+
}),
|
|
5138
|
+
checkpoints: postgreSQLCheckpointer()
|
|
5139
|
+
});
|
|
5140
|
+
};
|
|
4728
5141
|
var postgreSQLReactor = (options) => {
|
|
4729
5142
|
const {
|
|
4730
5143
|
processorId = options.processorId,
|
|
4731
5144
|
processorInstanceId = getProcessorInstanceId(processorId),
|
|
4732
5145
|
version = defaultProcessorVersion,
|
|
4733
5146
|
partition = defaultProcessorPartition,
|
|
4734
|
-
|
|
5147
|
+
lock
|
|
4735
5148
|
} = options;
|
|
4736
5149
|
const { pool, connectionString, close } = getProcessorPool(options);
|
|
4737
5150
|
const processorLock = postgreSQLProcessorLock({
|
|
@@ -4740,7 +5153,8 @@ var postgreSQLReactor = (options) => {
|
|
|
4740
5153
|
partition,
|
|
4741
5154
|
processorInstanceId,
|
|
4742
5155
|
projection: void 0,
|
|
4743
|
-
|
|
5156
|
+
lockAcquisitionPolicy: lock?.acquisitionPolicy ?? DefaultPostgreSQLProcessorLockPolicy,
|
|
5157
|
+
lockTimeoutSeconds: lock?.timeoutSeconds
|
|
4744
5158
|
});
|
|
4745
5159
|
const hooks = wrapHooksWithProcessorLocks(
|
|
4746
5160
|
{
|
|
@@ -4768,14 +5182,6 @@ var postgreSQLReactor = (options) => {
|
|
|
4768
5182
|
checkpoints: postgreSQLCheckpointer()
|
|
4769
5183
|
});
|
|
4770
5184
|
};
|
|
4771
|
-
var postgreSQLMessageProcessor = (options) => {
|
|
4772
|
-
if ("projection" in options) {
|
|
4773
|
-
return postgreSQLProjector(
|
|
4774
|
-
options
|
|
4775
|
-
);
|
|
4776
|
-
}
|
|
4777
|
-
return postgreSQLReactor(options);
|
|
4778
|
-
};
|
|
4779
5185
|
|
|
4780
5186
|
// src/eventStore/consumers/postgreSQLEventStoreConsumer.ts
|
|
4781
5187
|
var postgreSQLEventStoreConsumer = (options) => {
|
|
@@ -4786,7 +5192,10 @@ var postgreSQLEventStoreConsumer = (options) => {
|
|
|
4786
5192
|
let abortController = null;
|
|
4787
5193
|
let start;
|
|
4788
5194
|
let messagePuller;
|
|
4789
|
-
const pool = options.pool ? options.pool : dumbo6({
|
|
5195
|
+
const pool = options.pool ? options.pool : dumbo6({
|
|
5196
|
+
connectionString: options.connectionString,
|
|
5197
|
+
serialization: options.serialization
|
|
5198
|
+
});
|
|
4790
5199
|
const eachBatch = async (messagesBatch) => {
|
|
4791
5200
|
const activeProcessors = processors.filter((s) => s.isActive);
|
|
4792
5201
|
if (activeProcessors.length === 0)
|
|
@@ -4816,7 +5225,8 @@ var postgreSQLEventStoreConsumer = (options) => {
|
|
|
4816
5225
|
connectionString: options.connectionString,
|
|
4817
5226
|
pool,
|
|
4818
5227
|
client: void 0,
|
|
4819
|
-
transaction: void 0
|
|
5228
|
+
transaction: void 0,
|
|
5229
|
+
messageStore: void 0
|
|
4820
5230
|
}
|
|
4821
5231
|
};
|
|
4822
5232
|
const stopProcessors = () => Promise.all(processors.map((p) => p.close(processorContext)));
|
|
@@ -4826,10 +5236,10 @@ var postgreSQLEventStoreConsumer = (options) => {
|
|
|
4826
5236
|
if (messagePuller) {
|
|
4827
5237
|
abortController?.abort();
|
|
4828
5238
|
await messagePuller.stop();
|
|
4829
|
-
messagePuller = void 0;
|
|
4830
|
-
abortController = null;
|
|
4831
5239
|
}
|
|
4832
5240
|
await start;
|
|
5241
|
+
messagePuller = void 0;
|
|
5242
|
+
abortController = null;
|
|
4833
5243
|
await stopProcessors();
|
|
4834
5244
|
};
|
|
4835
5245
|
const init = async () => {
|
|
@@ -4843,7 +5253,7 @@ var postgreSQLEventStoreConsumer = (options) => {
|
|
|
4843
5253
|
isInitialized = true;
|
|
4844
5254
|
};
|
|
4845
5255
|
return {
|
|
4846
|
-
consumerId: options.consumerId ??
|
|
5256
|
+
consumerId: options.consumerId ?? uuid9(),
|
|
4847
5257
|
get isRunning() {
|
|
4848
5258
|
return isRunning;
|
|
4849
5259
|
},
|
|
@@ -4865,6 +5275,14 @@ var postgreSQLEventStoreConsumer = (options) => {
|
|
|
4865
5275
|
);
|
|
4866
5276
|
return processor;
|
|
4867
5277
|
},
|
|
5278
|
+
workflowProcessor: (options2) => {
|
|
5279
|
+
const processor = postgreSQLWorkflowProcessor(options2);
|
|
5280
|
+
processors.push(
|
|
5281
|
+
// TODO: change that
|
|
5282
|
+
processor
|
|
5283
|
+
);
|
|
5284
|
+
return processor;
|
|
5285
|
+
},
|
|
4868
5286
|
start: () => {
|
|
4869
5287
|
if (isRunning) return start;
|
|
4870
5288
|
if (processors.length === 0)
|
|
@@ -4926,27 +5344,27 @@ var rebuildPostgreSQLProjections = (options) => {
|
|
|
4926
5344
|
...options,
|
|
4927
5345
|
stopWhen: { noMessagesLeft: true }
|
|
4928
5346
|
});
|
|
5347
|
+
const lock = { acquisitionPolicy: defaultRebuildLockPolicy, ...options.lock };
|
|
4929
5348
|
const projections = "projections" in options ? options.projections.map(
|
|
4930
5349
|
(p) => "projection" in p ? {
|
|
4931
|
-
lockPolicy: defaultRebuildLockPolicy,
|
|
4932
5350
|
truncateOnStart: true,
|
|
4933
5351
|
processorId: getProjectorId({
|
|
4934
|
-
projectionName: p.projection.name ??
|
|
5352
|
+
projectionName: p.projection.name ?? unknownTag
|
|
4935
5353
|
}),
|
|
4936
5354
|
...p
|
|
4937
5355
|
} : {
|
|
4938
5356
|
projection: p,
|
|
4939
5357
|
processorId: getProjectorId({
|
|
4940
|
-
projectionName: p.name ??
|
|
5358
|
+
projectionName: p.name ?? unknownTag
|
|
4941
5359
|
}),
|
|
4942
|
-
truncateOnStart: true
|
|
4943
|
-
lockPolicy: defaultRebuildLockPolicy
|
|
5360
|
+
truncateOnStart: true
|
|
4944
5361
|
}
|
|
4945
5362
|
) : [options];
|
|
4946
5363
|
for (const projectionDefinition of projections) {
|
|
4947
5364
|
consumer.projector({
|
|
4948
5365
|
...projectionDefinition,
|
|
4949
|
-
truncateOnStart: projectionDefinition.truncateOnStart ?? true
|
|
5366
|
+
truncateOnStart: projectionDefinition.truncateOnStart ?? true,
|
|
5367
|
+
lock
|
|
4950
5368
|
});
|
|
4951
5369
|
}
|
|
4952
5370
|
return consumer;
|
|
@@ -4982,13 +5400,13 @@ export {
|
|
|
4982
5400
|
deactivateProjection,
|
|
4983
5401
|
deactivateProjectionSQL,
|
|
4984
5402
|
defaultPostgreSQLOptions,
|
|
4985
|
-
defaultTag,
|
|
5403
|
+
defaultTag2 as defaultTag,
|
|
4986
5404
|
documentDoesNotExist,
|
|
4987
5405
|
documentExists,
|
|
4988
5406
|
documentMatchingExists,
|
|
4989
5407
|
documentsAreTheSame,
|
|
4990
5408
|
documentsMatchingHaveCount,
|
|
4991
|
-
emmettPrefix,
|
|
5409
|
+
emmettPrefix2 as emmettPrefix,
|
|
4992
5410
|
eventInStream,
|
|
4993
5411
|
eventStoreSchemaMigrations,
|
|
4994
5412
|
eventsInStream,
|
|
@@ -5007,7 +5425,6 @@ export {
|
|
|
5007
5425
|
postgreSQLCheckpointer,
|
|
5008
5426
|
postgreSQLEventStoreConsumer,
|
|
5009
5427
|
postgreSQLEventStoreMessageBatchPuller,
|
|
5010
|
-
postgreSQLMessageProcessor,
|
|
5011
5428
|
postgreSQLProcessorLock,
|
|
5012
5429
|
postgreSQLProjection,
|
|
5013
5430
|
postgreSQLProjectionLock,
|
|
@@ -5015,6 +5432,7 @@ export {
|
|
|
5015
5432
|
postgreSQLRawBatchSQLProjection,
|
|
5016
5433
|
postgreSQLRawSQLProjection,
|
|
5017
5434
|
postgreSQLReactor,
|
|
5435
|
+
postgreSQLWorkflowProcessor,
|
|
5018
5436
|
processorsTable,
|
|
5019
5437
|
processorsTableSQL,
|
|
5020
5438
|
projectionsTable,
|
|
@@ -5041,7 +5459,7 @@ export {
|
|
|
5041
5459
|
transactionToPostgreSQLProjectionHandlerContext,
|
|
5042
5460
|
tryAcquireProcessorLockSQL,
|
|
5043
5461
|
tryAcquireProjectionLockSQL,
|
|
5044
|
-
unknownTag,
|
|
5462
|
+
unknownTag2 as unknownTag,
|
|
5045
5463
|
zipPostgreSQLEventStoreMessageBatchPullerStartFrom
|
|
5046
5464
|
};
|
|
5047
5465
|
//# sourceMappingURL=index.js.map
|