@event-driven-io/emmett-postgresql 0.43.0-beta.1 → 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 +1260 -813
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +174 -172
- package/dist/index.d.ts +174 -172
- package/dist/index.js +1744 -1297
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -48,14 +48,26 @@ var ConcurrencyError = class _ConcurrencyError extends EmmettError {
|
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
// ../emmett/dist/index.js
|
|
51
|
-
import { v4 as
|
|
51
|
+
import { v4 as uuid5 } from "uuid";
|
|
52
|
+
import { v7 as uuid2 } from "uuid";
|
|
52
53
|
import { v7 as uuid } from "uuid";
|
|
53
54
|
import retry from "async-retry";
|
|
54
|
-
import { v7 as
|
|
55
|
-
import { v4 as
|
|
55
|
+
import { v7 as uuid3 } from "uuid";
|
|
56
|
+
import { v4 as uuid4 } from "uuid";
|
|
57
|
+
import { v7 as uuid6 } from "uuid";
|
|
56
58
|
var emmettPrefix = "emt";
|
|
57
59
|
var defaultTag = `${emmettPrefix}:default`;
|
|
58
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
|
+
});
|
|
59
71
|
var STREAM_EXISTS = "STREAM_EXISTS";
|
|
60
72
|
var STREAM_DOES_NOT_EXIST = "STREAM_DOES_NOT_EXIST";
|
|
61
73
|
var NO_CONCURRENCY_CHECK = "NO_CONCURRENCY_CHECK";
|
|
@@ -76,6 +88,10 @@ var ExpectedVersionConflictError = class _ExpectedVersionConflictError extends C
|
|
|
76
88
|
Object.setPrototypeOf(this, _ExpectedVersionConflictError.prototype);
|
|
77
89
|
}
|
|
78
90
|
};
|
|
91
|
+
var isExpectedVersionConflictError = (error) => error instanceof ExpectedVersionConflictError || EmmettError.isInstanceOf(
|
|
92
|
+
error,
|
|
93
|
+
ExpectedVersionConflictError.Codes.ConcurrencyError
|
|
94
|
+
);
|
|
79
95
|
var isPrimitive = (value) => {
|
|
80
96
|
const type = typeof value;
|
|
81
97
|
return value === null || value === void 0 || type === "boolean" || type === "number" || type === "string" || type === "symbol" || type === "bigint";
|
|
@@ -281,27 +297,110 @@ var toNormalizedString = (value) => value.toString().padStart(19, "0");
|
|
|
281
297
|
var bigInt = {
|
|
282
298
|
toNormalizedString
|
|
283
299
|
};
|
|
284
|
-
var
|
|
285
|
-
|
|
286
|
-
|
|
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
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
};
|
|
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
|
+
}
|
|
287
341
|
}
|
|
342
|
+
return value;
|
|
288
343
|
};
|
|
289
|
-
var
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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(
|
|
294
350
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
295
|
-
(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
+
};
|
|
304
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 };
|
|
305
404
|
var asyncRetry = async (fn, opts) => {
|
|
306
405
|
if (opts === void 0 || opts.retries === 0) return fn();
|
|
307
406
|
return retry(
|
|
@@ -310,16 +409,16 @@ var asyncRetry = async (fn, opts) => {
|
|
|
310
409
|
const result = await fn();
|
|
311
410
|
if (opts?.shouldRetryResult && opts.shouldRetryResult(result)) {
|
|
312
411
|
throw new EmmettError(
|
|
313
|
-
`Retrying because of result: ${
|
|
412
|
+
`Retrying because of result: ${JSONSerializer.serialize(result)}`
|
|
314
413
|
);
|
|
315
414
|
}
|
|
316
415
|
return result;
|
|
317
|
-
} catch (
|
|
318
|
-
if (opts?.shouldRetryError && !opts.shouldRetryError(
|
|
319
|
-
bail(
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (opts?.shouldRetryError && !opts.shouldRetryError(error)) {
|
|
418
|
+
bail(error);
|
|
320
419
|
return void 0;
|
|
321
420
|
}
|
|
322
|
-
throw
|
|
421
|
+
throw error;
|
|
323
422
|
}
|
|
324
423
|
},
|
|
325
424
|
opts ?? { retries: 0 }
|
|
@@ -376,7 +475,7 @@ var bigIntProcessorCheckpoint = (value) => bigInt.toNormalizedString(value);
|
|
|
376
475
|
var parseBigIntProcessorCheckpoint = (value) => BigInt(value);
|
|
377
476
|
var defaultProcessorVersion = 1;
|
|
378
477
|
var defaultProcessorPartition = defaultTag;
|
|
379
|
-
var getProcessorInstanceId = (processorId) => `${processorId}:${
|
|
478
|
+
var getProcessorInstanceId = (processorId) => `${processorId}:${uuid3()}`;
|
|
380
479
|
var getProjectorId = (options) => `emt:processor:projector:${options.projectionName}`;
|
|
381
480
|
var reactor = (options) => {
|
|
382
481
|
const {
|
|
@@ -423,12 +522,13 @@ var reactor = (options) => {
|
|
|
423
522
|
id: processorId,
|
|
424
523
|
instanceId,
|
|
425
524
|
type,
|
|
525
|
+
canHandle,
|
|
426
526
|
init,
|
|
427
527
|
start: async (startOptions) => {
|
|
428
528
|
if (isActive) return;
|
|
429
529
|
await init(startOptions);
|
|
430
530
|
isActive = true;
|
|
431
|
-
closeSignal = onShutdown(() => close(
|
|
531
|
+
closeSignal = onShutdown(() => close(startOptions));
|
|
432
532
|
if (lastCheckpoint !== null)
|
|
433
533
|
return {
|
|
434
534
|
lastCheckpoint
|
|
@@ -555,9 +655,9 @@ var assertDeepEqual = (actual, expected, message2) => {
|
|
|
555
655
|
if (!deepEquals(actual, expected))
|
|
556
656
|
throw new AssertionError(
|
|
557
657
|
message2 ?? `subObj:
|
|
558
|
-
${
|
|
658
|
+
${JSONSerializer.serialize(expected)}
|
|
559
659
|
is not equal to
|
|
560
|
-
${
|
|
660
|
+
${JSONSerializer.serialize(actual)}`
|
|
561
661
|
);
|
|
562
662
|
};
|
|
563
663
|
function assertTrue(condition, message2) {
|
|
@@ -571,26 +671,29 @@ function assertEqual(expected, actual, message2) {
|
|
|
571
671
|
if (expected !== actual)
|
|
572
672
|
throw new AssertionError(
|
|
573
673
|
`${message2 ?? "Objects are not equal"}:
|
|
574
|
-
Expected: ${
|
|
575
|
-
Actual: ${
|
|
674
|
+
Expected: ${JSONSerializer.serialize(expected)}
|
|
675
|
+
Actual: ${JSONSerializer.serialize(actual)}`
|
|
576
676
|
);
|
|
577
677
|
}
|
|
578
678
|
function assertNotEqual(obj, other, message2) {
|
|
579
679
|
if (obj === other)
|
|
580
680
|
throw new AssertionError(
|
|
581
|
-
message2 ?? `Objects are equal: ${
|
|
681
|
+
message2 ?? `Objects are equal: ${JSONSerializer.serialize(obj)}`
|
|
582
682
|
);
|
|
583
683
|
}
|
|
584
684
|
function assertIsNotNull(result) {
|
|
585
685
|
assertNotEqual(result, null);
|
|
586
686
|
assertOk(result);
|
|
587
687
|
}
|
|
688
|
+
function assertIsNull(result) {
|
|
689
|
+
assertEqual(result, null);
|
|
690
|
+
}
|
|
588
691
|
var assertThatArray = (array) => {
|
|
589
692
|
return {
|
|
590
693
|
isEmpty: () => assertEqual(
|
|
591
694
|
array.length,
|
|
592
695
|
0,
|
|
593
|
-
`Array is not empty ${
|
|
696
|
+
`Array is not empty ${JSONSerializer.serialize(array)}`
|
|
594
697
|
),
|
|
595
698
|
isNotEmpty: () => assertNotEqual(array.length, 0, `Array is empty`),
|
|
596
699
|
hasSize: (length) => assertEqual(array.length, length),
|
|
@@ -690,6 +793,281 @@ var upcastRecordedMessage = (recordedMessage, options) => {
|
|
|
690
793
|
};
|
|
691
794
|
};
|
|
692
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 : {};
|
|
828
|
+
return {
|
|
829
|
+
...msg,
|
|
830
|
+
metadata: {
|
|
831
|
+
...existingMetadata,
|
|
832
|
+
action
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
};
|
|
836
|
+
var createWrappedInitialState = (initialState) => {
|
|
837
|
+
return () => ({
|
|
838
|
+
userState: initialState(),
|
|
839
|
+
processedInputIds: /* @__PURE__ */ new Set()
|
|
840
|
+
});
|
|
841
|
+
};
|
|
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
|
+
};
|
|
693
1071
|
|
|
694
1072
|
// src/eventStore/schema/readLastMessageGlobalPosition.ts
|
|
695
1073
|
import { singleOrNull, SQL } from "@event-driven-io/dumbo";
|
|
@@ -866,11 +1244,19 @@ var zipPostgreSQLEventStoreMessageBatchPullerStartFrom = (options) => {
|
|
|
866
1244
|
|
|
867
1245
|
// src/eventStore/consumers/postgreSQLEventStoreConsumer.ts
|
|
868
1246
|
import { dumbo as dumbo6 } from "@event-driven-io/dumbo";
|
|
869
|
-
import { v7 as
|
|
1247
|
+
import { v7 as uuid9 } from "uuid";
|
|
870
1248
|
|
|
871
1249
|
// src/eventStore/consumers/postgreSQLProcessor.ts
|
|
872
1250
|
import { dumbo as dumbo5 } from "@event-driven-io/dumbo";
|
|
873
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
|
+
|
|
874
1260
|
// src/eventStore/projections/locks/tryAcquireProjectionLock.ts
|
|
875
1261
|
import { single } from "@event-driven-io/dumbo";
|
|
876
1262
|
|
|
@@ -1208,7 +1594,7 @@ var toProcessorLockKey = ({
|
|
|
1208
1594
|
|
|
1209
1595
|
// src/eventStore/projections/management/projectionManagement.ts
|
|
1210
1596
|
import {
|
|
1211
|
-
JSONSerializer,
|
|
1597
|
+
JSONSerializer as JSONSerializer2,
|
|
1212
1598
|
single as single3,
|
|
1213
1599
|
singleOrNull as singleOrNull2,
|
|
1214
1600
|
SQL as SQL7
|
|
@@ -1339,7 +1725,7 @@ var registerProjection = async (execute, options) => {
|
|
|
1339
1725
|
const name = registration.projection.name;
|
|
1340
1726
|
const version = registration.projection.version ?? 1;
|
|
1341
1727
|
const kind = registration.projection.kind ?? registration.type;
|
|
1342
|
-
const definition =
|
|
1728
|
+
const definition = JSONSerializer2.serialize(registration.projection);
|
|
1343
1729
|
const lockKey = toProjectionLockKey({
|
|
1344
1730
|
projectionName: name,
|
|
1345
1731
|
partition,
|
|
@@ -1650,7 +2036,7 @@ var documentDoesNotExist = (options) => (assertOptions) => withCollection(
|
|
|
1650
2036
|
const result = await collection.findOne(
|
|
1651
2037
|
"withId" in options ? { _id: options.withId } : options.matchingFilter
|
|
1652
2038
|
);
|
|
1653
|
-
|
|
2039
|
+
assertIsNull(result);
|
|
1654
2040
|
},
|
|
1655
2041
|
{ ...options, ...assertOptions }
|
|
1656
2042
|
);
|
|
@@ -1699,140 +2085,382 @@ var expectPongoDocuments = {
|
|
|
1699
2085
|
|
|
1700
2086
|
// src/eventStore/projections/postgresProjectionSpec.ts
|
|
1701
2087
|
import {
|
|
1702
|
-
dumbo
|
|
1703
|
-
} from "@event-driven-io/dumbo";
|
|
1704
|
-
import { v4 as uuid6 } from "uuid";
|
|
1705
|
-
|
|
1706
|
-
// src/eventStore/postgreSQLEventStore.ts
|
|
1707
|
-
import {
|
|
1708
|
-
dumbo as dumbo3,
|
|
1709
|
-
fromDatabaseDriverType,
|
|
1710
|
-
getFormatter,
|
|
1711
|
-
SQL as SQL20
|
|
1712
|
-
} from "@event-driven-io/dumbo";
|
|
1713
|
-
|
|
1714
|
-
// src/eventStore/schema/index.ts
|
|
1715
|
-
import {
|
|
1716
|
-
dumbo as dumbo2,
|
|
1717
|
-
runSQLMigrations,
|
|
1718
|
-
sqlMigration as sqlMigration4
|
|
1719
|
-
} from "@event-driven-io/dumbo";
|
|
1720
|
-
|
|
1721
|
-
// src/eventStore/schema/appendToStream.ts
|
|
1722
|
-
import {
|
|
1723
|
-
DumboError,
|
|
1724
|
-
single as single4,
|
|
1725
|
-
SQL as SQL8,
|
|
1726
|
-
UniqueConstraintError
|
|
2088
|
+
dumbo
|
|
1727
2089
|
} from "@event-driven-io/dumbo";
|
|
1728
|
-
import { v4 as
|
|
1729
|
-
var
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
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
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
);
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
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,
|
|
2420
|
+
message_data,
|
|
2421
|
+
message_metadata,
|
|
2422
|
+
schema_version,
|
|
2423
|
+
message_id,
|
|
2424
|
+
message_type,
|
|
2425
|
+
message_kind
|
|
2426
|
+
FROM (
|
|
2427
|
+
SELECT *
|
|
2428
|
+
FROM
|
|
2429
|
+
unnest(v_message_ids, v_messages_data, v_messages_metadata, v_message_schema_versions, v_message_types, v_message_kinds)
|
|
2430
|
+
AS message(message_id, message_data, message_metadata, schema_version, message_type, message_kind)
|
|
2431
|
+
) AS message
|
|
2432
|
+
),
|
|
2433
|
+
all_messages_insert AS (
|
|
2434
|
+
INSERT INTO ${SQL8.identifier(messagesTable.name)}
|
|
2435
|
+
(stream_id, stream_position, partition, message_data, message_metadata, message_schema_version, message_type, message_kind, message_id, transaction_id)
|
|
2436
|
+
SELECT
|
|
2437
|
+
v_stream_id, ev.stream_position, v_partition, ev.message_data, ev.message_metadata, ev.schema_version, ev.message_type, ev.message_kind, ev.message_id, v_transaction_id
|
|
2438
|
+
FROM ev
|
|
2439
|
+
RETURNING global_position
|
|
2440
|
+
)
|
|
2441
|
+
SELECT
|
|
2442
|
+
array_agg(global_position ORDER BY global_position) INTO v_global_positions
|
|
2443
|
+
FROM
|
|
2444
|
+
all_messages_insert;
|
|
2445
|
+
|
|
2446
|
+
RETURN QUERY SELECT TRUE, v_next_stream_position, v_global_positions, v_transaction_id;
|
|
2447
|
+
END;
|
|
2448
|
+
$emt_append_to_stream$;
|
|
2449
|
+
`
|
|
2450
|
+
);
|
|
2451
|
+
var callAppendToStream = (params) => SQL8`SELECT * FROM emt_append_to_stream(
|
|
2452
|
+
${params.messageIds},
|
|
2453
|
+
${params.messagesData},
|
|
2454
|
+
${params.messagesMetadata},
|
|
2455
|
+
${params.schemaVersions},
|
|
2456
|
+
${params.messageTypes},
|
|
2457
|
+
${params.messageKinds},
|
|
2458
|
+
${params.streamId}::text,
|
|
2459
|
+
${params.streamType}::text,
|
|
2460
|
+
${params.expectedStreamPosition},
|
|
2461
|
+
${params.partition}::text
|
|
2462
|
+
)`;
|
|
2463
|
+
var appendToStream = (pool, streamName, streamType, messages, options) => pool.withTransaction(async (transaction) => {
|
|
1836
2464
|
const { execute } = transaction;
|
|
1837
2465
|
if (messages.length === 0)
|
|
1838
2466
|
return { success: false, result: { success: false } };
|
|
@@ -1844,7 +2472,7 @@ var appendToStream = (pool, streamName, streamType, messages, options) => pool.w
|
|
|
1844
2472
|
...e,
|
|
1845
2473
|
kind: e.kind ?? "Event",
|
|
1846
2474
|
metadata: {
|
|
1847
|
-
messageId:
|
|
2475
|
+
messageId: uuid8(),
|
|
1848
2476
|
..."metadata" in e ? e.metadata ?? {} : {}
|
|
1849
2477
|
}
|
|
1850
2478
|
}));
|
|
@@ -3388,1175 +4016,930 @@ BEGIN
|
|
|
3388
4016
|
WITH lock_check AS (
|
|
3389
4017
|
SELECT pg_try_advisory_xact_lock(p_lock_key) AS lock_acquired
|
|
3390
4018
|
),
|
|
3391
|
-
update_result AS (
|
|
3392
|
-
UPDATE emt_projections
|
|
3393
|
-
SET status = 'active',
|
|
3394
|
-
last_updated = now()
|
|
3395
|
-
WHERE name = p_name
|
|
3396
|
-
AND partition = p_partition
|
|
3397
|
-
AND version = p_version
|
|
3398
|
-
AND (SELECT lock_acquired FROM lock_check) = true
|
|
3399
|
-
RETURNING name
|
|
3400
|
-
)
|
|
3401
|
-
SELECT COUNT(*) > 0 INTO v_result FROM update_result;
|
|
3402
|
-
|
|
3403
|
-
RETURN v_result;
|
|
3404
|
-
END;
|
|
3405
|
-
$emt_activate_projection$;
|
|
3406
|
-
|
|
3407
|
-
CREATE OR REPLACE FUNCTION emt_deactivate_projection(
|
|
3408
|
-
p_lock_key BIGINT,
|
|
3409
|
-
p_name TEXT,
|
|
3410
|
-
p_partition TEXT,
|
|
3411
|
-
p_version INT
|
|
3412
|
-
)
|
|
3413
|
-
RETURNS BOOLEAN
|
|
3414
|
-
LANGUAGE plpgsql
|
|
3415
|
-
AS $emt_deactivate_projection$
|
|
3416
|
-
DECLARE
|
|
3417
|
-
v_result BOOLEAN;
|
|
3418
|
-
BEGIN
|
|
3419
|
-
WITH lock_check AS (
|
|
3420
|
-
SELECT pg_try_advisory_xact_lock(p_lock_key) AS lock_acquired
|
|
3421
|
-
),
|
|
3422
|
-
update_result AS (
|
|
3423
|
-
UPDATE emt_projections
|
|
3424
|
-
SET status = 'inactive',
|
|
3425
|
-
last_updated = now()
|
|
3426
|
-
WHERE name = p_name
|
|
3427
|
-
AND partition = p_partition
|
|
3428
|
-
AND version = p_version
|
|
3429
|
-
AND (SELECT lock_acquired FROM lock_check) = true
|
|
3430
|
-
RETURNING name
|
|
3431
|
-
)
|
|
3432
|
-
SELECT COUNT(*) > 0 INTO v_result FROM update_result;
|
|
3433
|
-
|
|
3434
|
-
RETURN v_result;
|
|
3435
|
-
END;
|
|
3436
|
-
$emt_deactivate_projection$;
|
|
3437
|
-
`;
|
|
3438
|
-
|
|
3439
|
-
// src/eventStore/schema/storeProcessorCheckpoint.ts
|
|
3440
|
-
import { single as single5, SQL as SQL13 } from "@event-driven-io/dumbo";
|
|
3441
|
-
var storeSubscriptionCheckpointSQL = createFunctionIfDoesNotExistSQL(
|
|
3442
|
-
"store_processor_checkpoint",
|
|
3443
|
-
SQL13`
|
|
3444
|
-
CREATE OR REPLACE FUNCTION store_processor_checkpoint(
|
|
3445
|
-
p_processor_id TEXT,
|
|
3446
|
-
p_version BIGINT,
|
|
3447
|
-
p_position TEXT,
|
|
3448
|
-
p_check_position TEXT,
|
|
3449
|
-
p_transaction_id xid8,
|
|
3450
|
-
p_partition TEXT DEFAULT '${SQL13.plain(defaultTag2)}',
|
|
3451
|
-
p_processor_instance_id TEXT DEFAULT '${SQL13.plain(unknownTag2)}'
|
|
3452
|
-
) RETURNS INT AS $spc$
|
|
3453
|
-
DECLARE
|
|
3454
|
-
current_position TEXT;
|
|
3455
|
-
BEGIN
|
|
3456
|
-
-- Handle the case when p_check_position is provided
|
|
3457
|
-
IF p_check_position IS NOT NULL THEN
|
|
3458
|
-
-- Try to update if the position matches p_check_position
|
|
3459
|
-
UPDATE "${SQL13.plain(processorsTable.name)}"
|
|
3460
|
-
SET
|
|
3461
|
-
"last_processed_checkpoint" = p_position,
|
|
3462
|
-
"last_processed_transaction_id" = p_transaction_id,
|
|
3463
|
-
"last_updated" = now()
|
|
3464
|
-
WHERE "processor_id" = p_processor_id
|
|
3465
|
-
AND "last_processed_checkpoint" = p_check_position
|
|
3466
|
-
AND "partition" = p_partition
|
|
3467
|
-
AND "version" = p_version;
|
|
3468
|
-
|
|
3469
|
-
IF FOUND THEN
|
|
3470
|
-
RETURN 1; -- Successfully updated
|
|
3471
|
-
END IF;
|
|
3472
|
-
|
|
3473
|
-
-- Retrieve the current position
|
|
3474
|
-
SELECT "last_processed_checkpoint" INTO current_position
|
|
3475
|
-
FROM "${SQL13.plain(processorsTable.name)}"
|
|
3476
|
-
WHERE "processor_id" = p_processor_id
|
|
3477
|
-
AND "partition" = p_partition
|
|
3478
|
-
AND "version" = p_version;
|
|
3479
|
-
|
|
3480
|
-
-- Return appropriate codes based on current position
|
|
3481
|
-
IF current_position = p_position THEN
|
|
3482
|
-
RETURN 0; -- Idempotent check: position already set
|
|
3483
|
-
ELSIF current_position > p_position THEN
|
|
3484
|
-
RETURN 3; -- Current ahead: another process has progressed further
|
|
3485
|
-
ELSE
|
|
3486
|
-
RETURN 2; -- Mismatch: check position doesn't match current
|
|
3487
|
-
END IF;
|
|
3488
|
-
END IF;
|
|
3489
|
-
|
|
3490
|
-
-- Handle the case when p_check_position is NULL: Insert if not exists
|
|
3491
|
-
BEGIN
|
|
3492
|
-
INSERT INTO "${SQL13.plain(processorsTable.name)}"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
|
|
3493
|
-
VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
|
|
3494
|
-
RETURN 1; -- Successfully inserted
|
|
3495
|
-
EXCEPTION WHEN unique_violation THEN
|
|
3496
|
-
-- If insertion failed, it means the row already exists
|
|
3497
|
-
SELECT "last_processed_checkpoint" INTO current_position
|
|
3498
|
-
FROM "${SQL13.plain(processorsTable.name)}"
|
|
3499
|
-
WHERE "processor_id" = p_processor_id
|
|
3500
|
-
AND "partition" = p_partition
|
|
3501
|
-
AND "version" = p_version;
|
|
3502
|
-
|
|
3503
|
-
IF current_position = p_position THEN
|
|
3504
|
-
RETURN 0; -- Idempotent check: position already set
|
|
3505
|
-
ELSIF current_position > p_position THEN
|
|
3506
|
-
RETURN 3; -- Current ahead: another process has progressed further
|
|
3507
|
-
ELSE
|
|
3508
|
-
RETURN 2; -- Insertion failed, row already exists with different position
|
|
3509
|
-
END IF;
|
|
3510
|
-
END;
|
|
3511
|
-
END;
|
|
3512
|
-
$spc$ LANGUAGE plpgsql;
|
|
3513
|
-
`
|
|
3514
|
-
);
|
|
3515
|
-
var callStoreProcessorCheckpoint = (params) => SQL13`
|
|
3516
|
-
SELECT store_processor_checkpoint(
|
|
3517
|
-
${params.processorId},
|
|
3518
|
-
${params.version},
|
|
3519
|
-
${params.position},
|
|
3520
|
-
${params.checkPosition},
|
|
3521
|
-
pg_current_xact_id(),
|
|
3522
|
-
${params.partition},
|
|
3523
|
-
${params.processorInstanceId}
|
|
3524
|
-
) as result;`;
|
|
3525
|
-
var storeProcessorCheckpoint = async (execute, options) => {
|
|
3526
|
-
try {
|
|
3527
|
-
const { result } = await single5(
|
|
3528
|
-
execute.command(
|
|
3529
|
-
callStoreProcessorCheckpoint({
|
|
3530
|
-
processorId: options.processorId,
|
|
3531
|
-
version: options.version ?? 1,
|
|
3532
|
-
position: options.newCheckpoint !== null ? options.newCheckpoint : null,
|
|
3533
|
-
checkPosition: options.lastProcessedCheckpoint !== null ? options.lastProcessedCheckpoint : null,
|
|
3534
|
-
partition: options.partition ?? defaultTag2,
|
|
3535
|
-
processorInstanceId: options.processorInstanceId ?? unknownTag2
|
|
3536
|
-
})
|
|
3537
|
-
)
|
|
3538
|
-
);
|
|
3539
|
-
return result === 1 ? { success: true, newCheckpoint: options.newCheckpoint } : {
|
|
3540
|
-
success: false,
|
|
3541
|
-
reason: result === 0 ? "IGNORED" : result === 3 ? "CURRENT_AHEAD" : "MISMATCH"
|
|
3542
|
-
};
|
|
3543
|
-
} catch (error) {
|
|
3544
|
-
console.log(error);
|
|
3545
|
-
throw error;
|
|
3546
|
-
}
|
|
3547
|
-
};
|
|
3548
|
-
|
|
3549
|
-
// src/eventStore/schema/tables.ts
|
|
3550
|
-
import { SQL as SQL15 } from "@event-driven-io/dumbo";
|
|
3551
|
-
|
|
3552
|
-
// src/eventStore/schema/migrations/0_43_0/index.ts
|
|
3553
|
-
import {
|
|
3554
|
-
dumbo,
|
|
3555
|
-
SQL as SQL14,
|
|
3556
|
-
sqlMigration as sqlMigration3
|
|
3557
|
-
} from "@event-driven-io/dumbo";
|
|
3558
|
-
var migration_0_43_0_cleanupLegacySubscriptionSQL = SQL14`
|
|
3559
|
-
DO $$
|
|
3560
|
-
BEGIN
|
|
3561
|
-
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'emt_subscriptions') THEN
|
|
3562
|
-
-- Restore clean emt_add_partition (remove creation of emt_subscriptions partitions)
|
|
3563
|
-
CREATE OR REPLACE FUNCTION emt_add_partition(partition_name TEXT) RETURNS void AS $fnpar$
|
|
3564
|
-
BEGIN
|
|
3565
|
-
PERFORM emt_add_table_partition('${SQL14.plain(messagesTable.name)}', partition_name);
|
|
3566
|
-
PERFORM emt_add_table_partition('${SQL14.plain(streamsTable.name)}', partition_name);
|
|
3567
|
-
|
|
3568
|
-
EXECUTE format('
|
|
3569
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3570
|
-
FOR VALUES IN (%L);',
|
|
3571
|
-
emt_sanitize_name('${SQL14.plain(processorsTable.name)}' || '_' || partition_name), '${SQL14.plain(processorsTable.name)}', partition_name
|
|
3572
|
-
);
|
|
3573
|
-
|
|
3574
|
-
EXECUTE format('
|
|
3575
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3576
|
-
FOR VALUES IN (%L);',
|
|
3577
|
-
emt_sanitize_name('${SQL14.plain(projectionsTable.name)}' || '_' || partition_name), '${SQL14.plain(projectionsTable.name)}', partition_name
|
|
3578
|
-
);
|
|
3579
|
-
END;
|
|
3580
|
-
$fnpar$ LANGUAGE plpgsql;
|
|
3581
|
-
|
|
3582
|
-
-- Drop old subscriptions table if it exists
|
|
3583
|
-
DROP TABLE IF EXISTS emt_subscriptions CASCADE;
|
|
3584
|
-
|
|
3585
|
-
-- Drop old function if it exists
|
|
3586
|
-
DROP FUNCTION IF EXISTS store_subscription_checkpoint(character varying, bigint, bigint, bigint, xid8, text);
|
|
3587
|
-
|
|
3588
|
-
-- Restore clean store_processor_checkpoint (remove dual-write logic)
|
|
3589
|
-
CREATE OR REPLACE FUNCTION store_processor_checkpoint(
|
|
3590
|
-
p_processor_id TEXT,
|
|
3591
|
-
p_version BIGINT,
|
|
3592
|
-
p_position TEXT,
|
|
3593
|
-
p_check_position TEXT,
|
|
3594
|
-
p_transaction_id xid8,
|
|
3595
|
-
p_partition TEXT DEFAULT '${SQL14.plain(defaultTag2)}',
|
|
3596
|
-
p_processor_instance_id TEXT DEFAULT 'emt:unknown'
|
|
3597
|
-
) RETURNS INT AS $fn$
|
|
3598
|
-
DECLARE
|
|
3599
|
-
current_position TEXT;
|
|
3600
|
-
BEGIN
|
|
3601
|
-
IF p_check_position IS NOT NULL THEN
|
|
3602
|
-
UPDATE "emt_processors"
|
|
3603
|
-
SET
|
|
3604
|
-
"last_processed_checkpoint" = p_position,
|
|
3605
|
-
"last_processed_transaction_id" = p_transaction_id,
|
|
3606
|
-
"last_updated" = now()
|
|
3607
|
-
WHERE "processor_id" = p_processor_id
|
|
3608
|
-
AND "last_processed_checkpoint" = p_check_position
|
|
3609
|
-
AND "partition" = p_partition
|
|
3610
|
-
AND "version" = p_version;
|
|
4019
|
+
update_result AS (
|
|
4020
|
+
UPDATE emt_projections
|
|
4021
|
+
SET status = 'active',
|
|
4022
|
+
last_updated = now()
|
|
4023
|
+
WHERE name = p_name
|
|
4024
|
+
AND partition = p_partition
|
|
4025
|
+
AND version = p_version
|
|
4026
|
+
AND (SELECT lock_acquired FROM lock_check) = true
|
|
4027
|
+
RETURNING name
|
|
4028
|
+
)
|
|
4029
|
+
SELECT COUNT(*) > 0 INTO v_result FROM update_result;
|
|
3611
4030
|
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
4031
|
+
RETURN v_result;
|
|
4032
|
+
END;
|
|
4033
|
+
$emt_activate_projection$;
|
|
3615
4034
|
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
4035
|
+
CREATE OR REPLACE FUNCTION emt_deactivate_projection(
|
|
4036
|
+
p_lock_key BIGINT,
|
|
4037
|
+
p_name TEXT,
|
|
4038
|
+
p_partition TEXT,
|
|
4039
|
+
p_version INT
|
|
4040
|
+
)
|
|
4041
|
+
RETURNS BOOLEAN
|
|
4042
|
+
LANGUAGE plpgsql
|
|
4043
|
+
AS $emt_deactivate_projection$
|
|
4044
|
+
DECLARE
|
|
4045
|
+
v_result BOOLEAN;
|
|
4046
|
+
BEGIN
|
|
4047
|
+
WITH lock_check AS (
|
|
4048
|
+
SELECT pg_try_advisory_xact_lock(p_lock_key) AS lock_acquired
|
|
4049
|
+
),
|
|
4050
|
+
update_result AS (
|
|
4051
|
+
UPDATE emt_projections
|
|
4052
|
+
SET status = 'inactive',
|
|
4053
|
+
last_updated = now()
|
|
4054
|
+
WHERE name = p_name
|
|
4055
|
+
AND partition = p_partition
|
|
4056
|
+
AND version = p_version
|
|
4057
|
+
AND (SELECT lock_acquired FROM lock_check) = true
|
|
4058
|
+
RETURNING name
|
|
4059
|
+
)
|
|
4060
|
+
SELECT COUNT(*) > 0 INTO v_result FROM update_result;
|
|
3621
4061
|
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
ELSE
|
|
3627
|
-
RETURN 2;
|
|
3628
|
-
END IF;
|
|
3629
|
-
END IF;
|
|
4062
|
+
RETURN v_result;
|
|
4063
|
+
END;
|
|
4064
|
+
$emt_deactivate_projection$;
|
|
4065
|
+
`;
|
|
3630
4066
|
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
4067
|
+
// src/eventStore/schema/storeProcessorCheckpoint.ts
|
|
4068
|
+
import { single as single5, SQL as SQL13 } from "@event-driven-io/dumbo";
|
|
4069
|
+
var storeSubscriptionCheckpointSQL = createFunctionIfDoesNotExistSQL(
|
|
4070
|
+
"store_processor_checkpoint",
|
|
4071
|
+
SQL13`
|
|
4072
|
+
CREATE OR REPLACE FUNCTION store_processor_checkpoint(
|
|
4073
|
+
p_processor_id TEXT,
|
|
4074
|
+
p_version BIGINT,
|
|
4075
|
+
p_position TEXT,
|
|
4076
|
+
p_check_position TEXT,
|
|
4077
|
+
p_transaction_id xid8,
|
|
4078
|
+
p_partition TEXT DEFAULT '${SQL13.plain(defaultTag2)}',
|
|
4079
|
+
p_processor_instance_id TEXT DEFAULT '${SQL13.plain(unknownTag2)}'
|
|
4080
|
+
) RETURNS INT AS $spc$
|
|
4081
|
+
DECLARE
|
|
4082
|
+
current_position TEXT;
|
|
4083
|
+
BEGIN
|
|
4084
|
+
-- Handle the case when p_check_position is provided
|
|
4085
|
+
IF p_check_position IS NOT NULL THEN
|
|
4086
|
+
-- Try to update if the position matches p_check_position
|
|
4087
|
+
UPDATE "${SQL13.plain(processorsTable.name)}"
|
|
4088
|
+
SET
|
|
4089
|
+
"last_processed_checkpoint" = p_position,
|
|
4090
|
+
"last_processed_transaction_id" = p_transaction_id,
|
|
4091
|
+
"last_updated" = now()
|
|
4092
|
+
WHERE "processor_id" = p_processor_id
|
|
4093
|
+
AND "last_processed_checkpoint" = p_check_position
|
|
4094
|
+
AND "partition" = p_partition
|
|
4095
|
+
AND "version" = p_version;
|
|
3641
4096
|
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
RETURN 2;
|
|
3646
|
-
END IF;
|
|
3647
|
-
END;
|
|
3648
|
-
END;
|
|
3649
|
-
$fn$ LANGUAGE plpgsql;
|
|
3650
|
-
END IF;
|
|
3651
|
-
END $$;
|
|
3652
|
-
`;
|
|
3653
|
-
var migration_0_43_0_cleanupLegacySubscription = sqlMigration3("emt:postgresql:eventstore:0.43.0:cleanup-legacy-subscription", [
|
|
3654
|
-
migration_0_43_0_cleanupLegacySubscriptionSQL
|
|
3655
|
-
]);
|
|
3656
|
-
var cleanupLegacySubscriptionTables = async (connectionString) => {
|
|
3657
|
-
const pool = dumbo({ connectionString });
|
|
3658
|
-
try {
|
|
3659
|
-
await pool.withTransaction(async ({ execute }) => {
|
|
3660
|
-
await execute.command(migration_0_43_0_cleanupLegacySubscriptionSQL);
|
|
3661
|
-
});
|
|
3662
|
-
} finally {
|
|
3663
|
-
await pool.close();
|
|
3664
|
-
}
|
|
3665
|
-
};
|
|
4097
|
+
IF FOUND THEN
|
|
4098
|
+
RETURN 1; -- Successfully updated
|
|
4099
|
+
END IF;
|
|
3666
4100
|
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
stream_type TEXT NOT NULL,
|
|
3674
|
-
stream_metadata JSONB NOT NULL,
|
|
3675
|
-
is_archived BOOLEAN NOT NULL DEFAULT FALSE,
|
|
3676
|
-
PRIMARY KEY (stream_id, partition, is_archived)
|
|
3677
|
-
) PARTITION BY LIST (partition);
|
|
3678
|
-
|
|
3679
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_streams_unique
|
|
3680
|
-
ON ${SQL15.identifier(streamsTable.name)}(stream_id, partition, is_archived)
|
|
3681
|
-
INCLUDE (stream_position);`;
|
|
3682
|
-
var messagesTableSQL = SQL15`
|
|
3683
|
-
CREATE SEQUENCE IF NOT EXISTS emt_global_message_position;
|
|
4101
|
+
-- Retrieve the current position
|
|
4102
|
+
SELECT "last_processed_checkpoint" INTO current_position
|
|
4103
|
+
FROM "${SQL13.plain(processorsTable.name)}"
|
|
4104
|
+
WHERE "processor_id" = p_processor_id
|
|
4105
|
+
AND "partition" = p_partition
|
|
4106
|
+
AND "version" = p_version;
|
|
3684
4107
|
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
message_schema_version TEXT NOT NULL,
|
|
3695
|
-
message_id TEXT NOT NULL,
|
|
3696
|
-
message_type TEXT NOT NULL,
|
|
3697
|
-
message_data JSONB NOT NULL,
|
|
3698
|
-
message_metadata JSONB NOT NULL,
|
|
3699
|
-
PRIMARY KEY (stream_id, stream_position, partition, is_archived)
|
|
3700
|
-
) PARTITION BY LIST (partition);`;
|
|
3701
|
-
var processorsTableSQL = SQL15`
|
|
3702
|
-
CREATE TABLE IF NOT EXISTS ${SQL15.identifier(processorsTable.name)}(
|
|
3703
|
-
last_processed_transaction_id XID8 NOT NULL,
|
|
3704
|
-
version INT NOT NULL DEFAULT 1,
|
|
3705
|
-
processor_id TEXT NOT NULL,
|
|
3706
|
-
partition TEXT NOT NULL DEFAULT '${SQL15.plain(defaultTag2)}',
|
|
3707
|
-
status TEXT NOT NULL DEFAULT 'stopped',
|
|
3708
|
-
last_processed_checkpoint TEXT NOT NULL,
|
|
3709
|
-
processor_instance_id TEXT DEFAULT '${SQL15.plain(unknownTag2)}',
|
|
3710
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
3711
|
-
last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
3712
|
-
PRIMARY KEY (processor_id, partition, version)
|
|
3713
|
-
) PARTITION BY LIST (partition);
|
|
3714
|
-
`;
|
|
3715
|
-
var projectionsTableSQL = SQL15`
|
|
3716
|
-
CREATE TABLE IF NOT EXISTS ${SQL15.identifier(projectionsTable.name)}(
|
|
3717
|
-
version INT NOT NULL DEFAULT 1,
|
|
3718
|
-
type VARCHAR(1) NOT NULL,
|
|
3719
|
-
name TEXT NOT NULL,
|
|
3720
|
-
partition TEXT NOT NULL DEFAULT '${SQL15.plain(defaultTag2)}',
|
|
3721
|
-
kind TEXT NOT NULL,
|
|
3722
|
-
status TEXT NOT NULL,
|
|
3723
|
-
definition JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
3724
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
3725
|
-
last_updated TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
3726
|
-
PRIMARY KEY (name, partition, version)
|
|
3727
|
-
) PARTITION BY LIST (partition);
|
|
3728
|
-
`;
|
|
3729
|
-
var sanitizeNameSQL = createFunctionIfDoesNotExistSQL(
|
|
3730
|
-
"emt_sanitize_name",
|
|
3731
|
-
SQL15`CREATE OR REPLACE FUNCTION emt_sanitize_name(input_name TEXT) RETURNS TEXT AS $emt_sanitize_name$
|
|
3732
|
-
BEGIN
|
|
3733
|
-
RETURN REGEXP_REPLACE(input_name, '[^a-zA-Z0-9_]', '_', 'g');
|
|
3734
|
-
END;
|
|
3735
|
-
$emt_sanitize_name$ LANGUAGE plpgsql;`
|
|
3736
|
-
);
|
|
3737
|
-
var addTablePartitions = createFunctionIfDoesNotExistSQL(
|
|
3738
|
-
"emt_add_table_partition",
|
|
3739
|
-
SQL15`
|
|
3740
|
-
CREATE OR REPLACE FUNCTION emt_add_table_partition(tableName TEXT, partition_name TEXT) RETURNS void AS $emt_add_table_partition$
|
|
3741
|
-
DECLARE
|
|
3742
|
-
v_main_partiton_name TEXT;
|
|
3743
|
-
v_active_partiton_name TEXT;
|
|
3744
|
-
v_archived_partiton_name TEXT;
|
|
3745
|
-
BEGIN
|
|
3746
|
-
v_main_partiton_name := emt_sanitize_name(tableName || '_' || partition_name);
|
|
3747
|
-
v_active_partiton_name := emt_sanitize_name(v_main_partiton_name || '_active');
|
|
3748
|
-
v_archived_partiton_name := emt_sanitize_name(v_main_partiton_name || '_archived');
|
|
4108
|
+
-- Return appropriate codes based on current position
|
|
4109
|
+
IF current_position = p_position THEN
|
|
4110
|
+
RETURN 0; -- Idempotent check: position already set
|
|
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;
|
|
3749
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;
|
|
3750
4130
|
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
EXECUTE format('
|
|
3759
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3760
|
-
FOR VALUES IN (FALSE);',
|
|
3761
|
-
v_active_partiton_name, v_main_partiton_name
|
|
3762
|
-
);
|
|
3763
|
-
|
|
3764
|
-
EXECUTE format('
|
|
3765
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3766
|
-
FOR VALUES IN (TRUE);',
|
|
3767
|
-
v_archived_partiton_name, v_main_partiton_name
|
|
3768
|
-
);
|
|
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;
|
|
3769
4138
|
END;
|
|
3770
|
-
|
|
4139
|
+
END;
|
|
4140
|
+
$spc$ LANGUAGE plpgsql;
|
|
4141
|
+
`
|
|
3771
4142
|
);
|
|
3772
|
-
var
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
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
|
+
};
|
|
3779
4176
|
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
FOR VALUES IN (%L);',
|
|
3783
|
-
emt_sanitize_name('${SQL15.plain(processorsTable.name)}' || '_' || partition_name), '${SQL15.plain(processorsTable.name)}', partition_name
|
|
3784
|
-
);
|
|
4177
|
+
// src/eventStore/schema/tables.ts
|
|
4178
|
+
import { SQL as SQL15 } from "@event-driven-io/dumbo";
|
|
3785
4179
|
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
);
|
|
3803
|
-
|
|
3804
|
-
EXECUTE format('
|
|
3805
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3806
|
-
FOR VALUES IN (FALSE);',
|
|
3807
|
-
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)}')
|
|
3808
|
-
);
|
|
3809
|
-
|
|
3810
|
-
EXECUTE format('
|
|
3811
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3812
|
-
FOR VALUES IN (TRUE);',
|
|
3813
|
-
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)}')
|
|
3814
|
-
);
|
|
3815
|
-
|
|
3816
|
-
-- For ${SQL15.plain(streamsTable.name)} table
|
|
3817
|
-
EXECUTE format('
|
|
3818
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3819
|
-
FOR VALUES IN (emt_sanitize_name(%L || ''__'' || %L)) PARTITION BY LIST (is_archived);',
|
|
3820
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || '${SQL15.plain(globalTag)}'), '${SQL15.plain(streamsTable.name)}', new_module, '${SQL15.plain(globalTag)}'
|
|
3821
|
-
);
|
|
3822
|
-
|
|
3823
|
-
EXECUTE format('
|
|
3824
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3825
|
-
FOR VALUES IN (FALSE);',
|
|
3826
|
-
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)}')
|
|
3827
|
-
);
|
|
3828
|
-
|
|
3829
|
-
EXECUTE format('
|
|
3830
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3831
|
-
FOR VALUES IN (TRUE);',
|
|
3832
|
-
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)}')
|
|
3833
|
-
);
|
|
3834
|
-
END;
|
|
3835
|
-
$$ LANGUAGE plpgsql;
|
|
3836
|
-
`;
|
|
3837
|
-
var addTenantSQL = SQL15`
|
|
3838
|
-
CREATE OR REPLACE FUNCTION add_tenant(new_module TEXT, new_tenant TEXT) RETURNS void AS $$
|
|
3839
|
-
BEGIN
|
|
3840
|
-
-- For ${SQL15.plain(messagesTable.name)} table
|
|
3841
|
-
EXECUTE format('
|
|
3842
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3843
|
-
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
3844
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant), '${SQL15.plain(messagesTable.name)}', new_module, new_tenant
|
|
3845
|
-
);
|
|
3846
|
-
|
|
3847
|
-
EXECUTE format('
|
|
3848
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3849
|
-
FOR VALUES IN (FALSE);',
|
|
3850
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant || '_active'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant)
|
|
3851
|
-
);
|
|
3852
|
-
|
|
3853
|
-
EXECUTE format('
|
|
3854
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3855
|
-
FOR VALUES IN (TRUE);',
|
|
3856
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant || '_archived'), emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || new_tenant)
|
|
3857
|
-
);
|
|
3858
|
-
|
|
3859
|
-
-- For ${SQL15.plain(streamsTable.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
|
+
|
|
3860
4196
|
EXECUTE format('
|
|
3861
4197
|
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3862
|
-
FOR VALUES IN (
|
|
3863
|
-
emt_sanitize_name('${
|
|
3864
|
-
);
|
|
3865
|
-
|
|
3866
|
-
EXECUTE format('
|
|
3867
|
-
CREATE TABLE IF NOT EXISTS %
|
|
3868
|
-
FOR VALUES IN (
|
|
3869
|
-
emt_sanitize_name('${
|
|
3870
|
-
);
|
|
3871
|
-
|
|
3872
|
-
EXECUTE format('
|
|
3873
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3874
|
-
FOR VALUES IN (TRUE);',
|
|
3875
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant || '_archived'), emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || new_tenant)
|
|
3876
|
-
);
|
|
3877
|
-
END;
|
|
3878
|
-
$$ LANGUAGE plpgsql;
|
|
3879
|
-
`;
|
|
3880
|
-
var addModuleForAllTenantsSQL = SQL15`
|
|
3881
|
-
CREATE OR REPLACE FUNCTION add_module_for_all_tenants(new_module TEXT) RETURNS void AS $$
|
|
3882
|
-
DECLARE
|
|
3883
|
-
tenant_record RECORD;
|
|
3884
|
-
BEGIN
|
|
3885
|
-
PERFORM add_module(new_module);
|
|
3886
|
-
|
|
3887
|
-
FOR tenant_record IN SELECT DISTINCT tenant FROM ${SQL15.plain(messagesTable.name)}
|
|
3888
|
-
LOOP
|
|
3889
|
-
-- For ${SQL15.plain(messagesTable.name)} table
|
|
3890
|
-
EXECUTE format('
|
|
3891
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3892
|
-
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
3893
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || new_module || '__' || tenant_record.tenant), '${SQL15.plain(messagesTable.name)}', new_module, tenant_record.tenant
|
|
3894
|
-
);
|
|
3895
|
-
|
|
3896
|
-
EXECUTE format('
|
|
3897
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3898
|
-
FOR VALUES IN (FALSE);',
|
|
3899
|
-
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)
|
|
3900
|
-
);
|
|
3901
|
-
|
|
3902
|
-
EXECUTE format('
|
|
3903
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3904
|
-
FOR VALUES IN (TRUE);',
|
|
3905
|
-
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)
|
|
3906
|
-
);
|
|
3907
|
-
|
|
3908
|
-
-- For ${SQL15.plain(streamsTable.name)} table
|
|
3909
|
-
EXECUTE format('
|
|
3910
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3911
|
-
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
3912
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || new_module || '__' || tenant_record.tenant), '${SQL15.plain(streamsTable.name)}', new_module, tenant_record.tenant
|
|
3913
|
-
);
|
|
3914
|
-
|
|
3915
|
-
EXECUTE format('
|
|
3916
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3917
|
-
FOR VALUES IN (FALSE);',
|
|
3918
|
-
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)
|
|
3919
|
-
);
|
|
3920
|
-
|
|
3921
|
-
EXECUTE format('
|
|
3922
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3923
|
-
FOR VALUES IN (TRUE);',
|
|
3924
|
-
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)
|
|
3925
|
-
);
|
|
3926
|
-
END LOOP;
|
|
3927
|
-
END;
|
|
3928
|
-
$$ LANGUAGE plpgsql;
|
|
3929
|
-
`;
|
|
3930
|
-
var addTenantForAllModulesSQL = SQL15`
|
|
3931
|
-
CREATE OR REPLACE FUNCTION add_tenant_for_all_modules(new_tenant TEXT) RETURNS void AS $$
|
|
3932
|
-
DECLARE
|
|
3933
|
-
module_record RECORD;
|
|
3934
|
-
BEGIN
|
|
3935
|
-
FOR module_record IN SELECT DISTINCT partitionname FROM pg_partman.part_config WHERE parent_table = '${SQL15.plain(messagesTable.name)}'
|
|
3936
|
-
LOOP
|
|
3937
|
-
-- For ${SQL15.plain(messagesTable.name)} table
|
|
3938
|
-
EXECUTE format('
|
|
3939
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3940
|
-
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
3941
|
-
emt_sanitize_name('${SQL15.plain(messagesTable.name)}_' || module_record.partitionname || '__' || new_tenant), '${SQL15.plain(messagesTable.name)}', module_record.partitionname, new_tenant
|
|
3942
|
-
);
|
|
3943
|
-
|
|
3944
|
-
EXECUTE format('
|
|
3945
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3946
|
-
FOR VALUES IN (FALSE);',
|
|
3947
|
-
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)
|
|
3948
|
-
);
|
|
3949
|
-
|
|
3950
|
-
EXECUTE format('
|
|
3951
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3952
|
-
FOR VALUES IN (TRUE);',
|
|
3953
|
-
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)
|
|
3954
|
-
);
|
|
3955
|
-
|
|
3956
|
-
-- For ${SQL15.plain(streamsTable.name)} table
|
|
3957
|
-
EXECUTE format('
|
|
3958
|
-
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
3959
|
-
FOR VALUES IN (emt_sanitize_name(''%s__%s'')) PARTITION BY LIST (is_archived);',
|
|
3960
|
-
emt_sanitize_name('${SQL15.plain(streamsTable.name)}_' || module_record.partitionname || '__' || new_tenant), '${SQL15.plain(streamsTable.name)}', module_record.partitionname, new_tenant
|
|
3961
|
-
);
|
|
3962
|
-
|
|
3963
|
-
EXECUTE format('
|
|
3964
|
-
CREATE TABLE IF NOT EXISTS %I_active PARTITION OF %I
|
|
3965
|
-
FOR VALUES IN (FALSE);',
|
|
3966
|
-
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)
|
|
3967
|
-
);
|
|
3968
|
-
|
|
3969
|
-
EXECUTE format('
|
|
3970
|
-
CREATE TABLE IF NOT EXISTS %I_archived PARTITION OF %I
|
|
3971
|
-
FOR VALUES IN (TRUE);',
|
|
3972
|
-
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)
|
|
3973
|
-
);
|
|
3974
|
-
END LOOP;
|
|
4198
|
+
FOR VALUES IN (%L);',
|
|
4199
|
+
emt_sanitize_name('${SQL14.plain(processorsTable.name)}' || '_' || partition_name), '${SQL14.plain(processorsTable.name)}', partition_name
|
|
4200
|
+
);
|
|
4201
|
+
|
|
4202
|
+
EXECUTE format('
|
|
4203
|
+
CREATE TABLE IF NOT EXISTS %I PARTITION OF %I
|
|
4204
|
+
FOR VALUES IN (%L);',
|
|
4205
|
+
emt_sanitize_name('${SQL14.plain(projectionsTable.name)}' || '_' || partition_name), '${SQL14.plain(projectionsTable.name)}', partition_name
|
|
4206
|
+
);
|
|
3975
4207
|
END;
|
|
3976
|
-
|
|
3977
|
-
`;
|
|
3978
|
-
var addDefaultPartitionSQL = SQL15`SELECT emt_add_partition('${SQL15.plain(defaultTag2)}');`;
|
|
4208
|
+
$fnpar$ LANGUAGE plpgsql;
|
|
3979
4209
|
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
var readProcessorCheckpoint = async (execute, options) => {
|
|
3983
|
-
const result = await singleOrNull3(
|
|
3984
|
-
execute.query(
|
|
3985
|
-
SQL16`SELECT last_processed_checkpoint
|
|
3986
|
-
FROM ${SQL16.identifier(processorsTable.name)}
|
|
3987
|
-
WHERE partition = ${options?.partition ?? defaultTag2} AND processor_id = ${options.processorId} AND version = ${options.version ?? 1}
|
|
3988
|
-
LIMIT 1`
|
|
3989
|
-
)
|
|
3990
|
-
);
|
|
3991
|
-
return {
|
|
3992
|
-
lastProcessedCheckpoint: result !== null ? result.last_processed_checkpoint : null
|
|
3993
|
-
};
|
|
3994
|
-
};
|
|
4210
|
+
-- Drop old subscriptions table if it exists
|
|
4211
|
+
DROP TABLE IF EXISTS emt_subscriptions CASCADE;
|
|
3995
4212
|
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
globalPosition: BigInt(row.global_position),
|
|
4023
|
-
checkpoint: bigIntProcessorCheckpoint(BigInt(row.global_position))
|
|
4024
|
-
};
|
|
4025
|
-
const event = {
|
|
4026
|
-
...rawEvent,
|
|
4027
|
-
kind: "Event",
|
|
4028
|
-
metadata
|
|
4029
|
-
};
|
|
4030
|
-
return upcastRecordedMessage(event, options?.schema?.versioning);
|
|
4031
|
-
}
|
|
4032
|
-
);
|
|
4033
|
-
return events.length > 0 ? {
|
|
4034
|
-
currentStreamVersion: events[events.length - 1].metadata.streamPosition,
|
|
4035
|
-
events,
|
|
4036
|
-
streamExists: true
|
|
4037
|
-
} : {
|
|
4038
|
-
currentStreamVersion: PostgreSQLEventStoreDefaultStreamVersion,
|
|
4039
|
-
events: [],
|
|
4040
|
-
streamExists: false
|
|
4041
|
-
};
|
|
4042
|
-
};
|
|
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$
|
|
4226
|
+
DECLARE
|
|
4227
|
+
current_position TEXT;
|
|
4228
|
+
BEGIN
|
|
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;
|
|
4043
4239
|
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4240
|
+
IF FOUND THEN
|
|
4241
|
+
RETURN 1;
|
|
4242
|
+
END IF;
|
|
4243
|
+
|
|
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 ;
|
|
4249
|
+
|
|
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
|
+
}
|
|
4055
4293
|
};
|
|
4056
4294
|
|
|
4057
|
-
// src/eventStore/schema/
|
|
4058
|
-
var
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
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;`
|
|
4078
4364
|
);
|
|
4079
|
-
var
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
);
|
|
4092
|
-
const nestedPool = dumbo2({ connectionString, connection: tx.connection });
|
|
4093
|
-
try {
|
|
4094
|
-
if (hooks?.onBeforeSchemaCreated) {
|
|
4095
|
-
await hooks.onBeforeSchemaCreated(context);
|
|
4096
|
-
}
|
|
4097
|
-
const result = await runSQLMigrations(
|
|
4098
|
-
nestedPool,
|
|
4099
|
-
eventStoreSchemaMigrations,
|
|
4100
|
-
options
|
|
4101
|
-
);
|
|
4102
|
-
if (hooks?.onAfterSchemaCreated) {
|
|
4103
|
-
await hooks.onAfterSchemaCreated(context);
|
|
4104
|
-
}
|
|
4105
|
-
return result;
|
|
4106
|
-
} finally {
|
|
4107
|
-
await nestedPool.close();
|
|
4108
|
-
}
|
|
4109
|
-
});
|
|
4110
|
-
};
|
|
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');
|
|
4111
4377
|
|
|
4112
|
-
// src/eventStore/schema/truncateTables.ts
|
|
4113
|
-
import { SQL as SQL19 } from "@event-driven-io/dumbo";
|
|
4114
|
-
var truncateTables = async (execute, options) => {
|
|
4115
|
-
await execute.command(
|
|
4116
|
-
SQL19`TRUNCATE TABLE
|
|
4117
|
-
${SQL19.identifier(streamsTable.name)},
|
|
4118
|
-
${SQL19.identifier(messagesTable.name)},
|
|
4119
|
-
${SQL19.identifier(processorsTable.name)},
|
|
4120
|
-
${SQL19.identifier(projectionsTable.name)}
|
|
4121
|
-
CASCADE${SQL19.plain(options?.resetSequences ? "; ALTER SEQUENCE emt_global_message_position RESTART WITH 1" : "")};`
|
|
4122
|
-
);
|
|
4123
|
-
};
|
|
4124
4378
|
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
var PostgreSQLEventStoreDefaultStreamVersion = 0n;
|
|
4131
|
-
var getPostgreSQLEventStore = (connectionString, options = defaultPostgreSQLOptions) => {
|
|
4132
|
-
const poolOptions = {
|
|
4133
|
-
connectionString,
|
|
4134
|
-
...options.connectionOptions ? options.connectionOptions : {}
|
|
4135
|
-
};
|
|
4136
|
-
const pool = "dumbo" in poolOptions ? poolOptions.dumbo : dumbo3(poolOptions);
|
|
4137
|
-
let migrateSchema = void 0;
|
|
4138
|
-
const autoGenerateSchema = options.schema?.autoMigration === void 0 || options.schema?.autoMigration !== "None";
|
|
4139
|
-
const inlineProjections = (options.projections ?? []).filter(({ type }) => type === "inline").map(({ projection: projection2 }) => projection2);
|
|
4140
|
-
const migrate = async (migrationOptions) => {
|
|
4141
|
-
if (!migrateSchema) {
|
|
4142
|
-
migrateSchema = createEventStoreSchema(
|
|
4143
|
-
connectionString,
|
|
4144
|
-
pool,
|
|
4145
|
-
{
|
|
4146
|
-
onBeforeSchemaCreated: async (context) => {
|
|
4147
|
-
if (options.hooks?.onBeforeSchemaCreated) {
|
|
4148
|
-
await options.hooks.onBeforeSchemaCreated(context);
|
|
4149
|
-
}
|
|
4150
|
-
},
|
|
4151
|
-
onAfterSchemaCreated: async (context) => {
|
|
4152
|
-
for (const projection2 of inlineProjections) {
|
|
4153
|
-
if (projection2.init) {
|
|
4154
|
-
await projection2.init({
|
|
4155
|
-
version: projection2.version ?? 1,
|
|
4156
|
-
status: "active",
|
|
4157
|
-
registrationType: "inline",
|
|
4158
|
-
context: { ...context, migrationOptions }
|
|
4159
|
-
});
|
|
4160
|
-
}
|
|
4161
|
-
}
|
|
4162
|
-
if (options.hooks?.onAfterSchemaCreated) {
|
|
4163
|
-
await options.hooks.onAfterSchemaCreated(context);
|
|
4164
|
-
}
|
|
4165
|
-
}
|
|
4166
|
-
},
|
|
4167
|
-
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
|
|
4168
4384
|
);
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
return migrate();
|
|
4175
|
-
};
|
|
4176
|
-
const beforeCommitHook = inlineProjections.length > 0 ? async (events, { transaction }) => handleProjections({
|
|
4177
|
-
projections: inlineProjections,
|
|
4178
|
-
// TODO: Add proper handling of global data
|
|
4179
|
-
// Currently it's not available as append doesn't return array of global position but just the last one
|
|
4180
|
-
events,
|
|
4181
|
-
...await transactionToPostgreSQLProjectionHandlerContext(
|
|
4182
|
-
connectionString,
|
|
4183
|
-
pool,
|
|
4184
|
-
transaction
|
|
4185
|
-
)
|
|
4186
|
-
}) : void 0;
|
|
4187
|
-
return {
|
|
4188
|
-
schema: {
|
|
4189
|
-
sql: () => SQL20.describe(
|
|
4190
|
-
schemaSQL,
|
|
4191
|
-
getFormatter(fromDatabaseDriverType(pool.driverType).databaseType)
|
|
4192
|
-
),
|
|
4193
|
-
print: () => console.log(
|
|
4194
|
-
SQL20.describe(
|
|
4195
|
-
schemaSQL,
|
|
4196
|
-
getFormatter(fromDatabaseDriverType(pool.driverType).databaseType)
|
|
4197
|
-
)
|
|
4198
|
-
),
|
|
4199
|
-
migrate,
|
|
4200
|
-
dangerous: {
|
|
4201
|
-
truncate: (truncateOptions) => pool.withTransaction(async (transaction) => {
|
|
4202
|
-
await ensureSchemaExists();
|
|
4203
|
-
await truncateTables(transaction.execute, truncateOptions);
|
|
4204
|
-
if (truncateOptions?.truncateProjections) {
|
|
4205
|
-
const projectionContext = await transactionToPostgreSQLProjectionHandlerContext(
|
|
4206
|
-
connectionString,
|
|
4207
|
-
pool,
|
|
4208
|
-
transaction
|
|
4209
|
-
);
|
|
4210
|
-
for (const projection2 of options?.projections ?? []) {
|
|
4211
|
-
if (projection2.projection.truncate)
|
|
4212
|
-
await projection2.projection.truncate(projectionContext);
|
|
4213
|
-
}
|
|
4214
|
-
}
|
|
4215
|
-
})
|
|
4216
|
-
}
|
|
4217
|
-
},
|
|
4218
|
-
async aggregateStream(streamName, options2) {
|
|
4219
|
-
const { evolve, initialState, read } = options2;
|
|
4220
|
-
const expectedStreamVersion = read?.expectedStreamVersion;
|
|
4221
|
-
let state = initialState();
|
|
4222
|
-
const result = await this.readStream(
|
|
4223
|
-
streamName,
|
|
4224
|
-
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
|
|
4225
4390
|
);
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
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
|
|
4231
4396
|
);
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
};
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
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
|
|
4248
4412
|
);
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
const appendResult = await appendToStream(
|
|
4255
|
-
// TODO: Fix this when introducing more drivers
|
|
4256
|
-
pool,
|
|
4257
|
-
streamName,
|
|
4258
|
-
streamType,
|
|
4259
|
-
downcastRecordedMessages(events, options2?.schema?.versioning),
|
|
4260
|
-
{
|
|
4261
|
-
...options2,
|
|
4262
|
-
beforeCommitHook
|
|
4263
|
-
}
|
|
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
|
|
4264
4418
|
);
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
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
|
|
4270
4473
|
);
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
},
|
|
4277
|
-
streamExists: async (streamName, options2) => {
|
|
4278
|
-
await ensureSchemaExists();
|
|
4279
|
-
return streamExists(pool.execute, streamName, options2);
|
|
4280
|
-
},
|
|
4281
|
-
consumer: (options2) => postgreSQLEventStoreConsumer({
|
|
4282
|
-
...options2 ?? {},
|
|
4283
|
-
pool,
|
|
4284
|
-
connectionString
|
|
4285
|
-
}),
|
|
4286
|
-
close: () => pool.close(),
|
|
4287
|
-
async withSession(callback) {
|
|
4288
|
-
return await pool.withConnection(async (connection) => {
|
|
4289
|
-
const storeOptions = {
|
|
4290
|
-
...options,
|
|
4291
|
-
connectionOptions: {
|
|
4292
|
-
connection
|
|
4293
|
-
},
|
|
4294
|
-
schema: {
|
|
4295
|
-
...options.schema ?? {},
|
|
4296
|
-
autoMigration: "None"
|
|
4297
|
-
}
|
|
4298
|
-
};
|
|
4299
|
-
const eventStore = getPostgreSQLEventStore(
|
|
4300
|
-
connectionString,
|
|
4301
|
-
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)
|
|
4302
4479
|
);
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
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)
|
|
4308
4485
|
);
|
|
4309
|
-
|
|
4310
|
-
|
|
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
|
|
4311
4621
|
};
|
|
4312
4622
|
};
|
|
4313
4623
|
|
|
4314
|
-
// src/eventStore/
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
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))
|
|
4342
4652
|
};
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
const run = async (pool) => {
|
|
4348
|
-
let globalPosition = 0n;
|
|
4349
|
-
const numberOfTimes = options2?.numberOfTimes ?? 1;
|
|
4350
|
-
for (const event of [
|
|
4351
|
-
...givenEvents,
|
|
4352
|
-
...Array.from({ length: numberOfTimes }).flatMap(() => events)
|
|
4353
|
-
]) {
|
|
4354
|
-
const metadata = {
|
|
4355
|
-
checkpoint: bigIntProcessorCheckpoint(++globalPosition),
|
|
4356
|
-
globalPosition,
|
|
4357
|
-
streamPosition: globalPosition,
|
|
4358
|
-
streamName: `test-${uuid6()}`,
|
|
4359
|
-
messageId: uuid6()
|
|
4360
|
-
};
|
|
4361
|
-
allEvents.push({
|
|
4362
|
-
...event,
|
|
4363
|
-
kind: "Event",
|
|
4364
|
-
metadata: {
|
|
4365
|
-
...metadata,
|
|
4366
|
-
..."metadata" in event ? event.metadata ?? {} : {}
|
|
4367
|
-
}
|
|
4368
|
-
});
|
|
4369
|
-
}
|
|
4370
|
-
await initialize(pool);
|
|
4371
|
-
await pool.withTransaction(async (transaction) => {
|
|
4372
|
-
await handleProjections({
|
|
4373
|
-
events: allEvents,
|
|
4374
|
-
projections: [projection2],
|
|
4375
|
-
...await transactionToPostgreSQLProjectionHandlerContext(
|
|
4376
|
-
connectionString,
|
|
4377
|
-
pool,
|
|
4378
|
-
transaction
|
|
4379
|
-
)
|
|
4380
|
-
});
|
|
4381
|
-
});
|
|
4382
|
-
};
|
|
4383
|
-
return {
|
|
4384
|
-
then: async (assert, message) => {
|
|
4385
|
-
const pool = dumbo4(dumoOptions);
|
|
4386
|
-
try {
|
|
4387
|
-
await run(pool);
|
|
4388
|
-
const succeeded = await assert({ pool, connectionString });
|
|
4389
|
-
if (succeeded !== void 0 && succeeded === false)
|
|
4390
|
-
assertFails(
|
|
4391
|
-
message ?? "Projection specification didn't match the criteria"
|
|
4392
|
-
);
|
|
4393
|
-
} finally {
|
|
4394
|
-
await pool.close();
|
|
4395
|
-
}
|
|
4396
|
-
},
|
|
4397
|
-
thenThrows: async (...args) => {
|
|
4398
|
-
const pool = dumbo4(dumoOptions);
|
|
4399
|
-
try {
|
|
4400
|
-
await run(pool);
|
|
4401
|
-
throw new AssertionError("Handler did not fail as expected");
|
|
4402
|
-
} catch (error) {
|
|
4403
|
-
if (error instanceof AssertionError) throw error;
|
|
4404
|
-
if (args.length === 0) return;
|
|
4405
|
-
if (!isErrorConstructor(args[0])) {
|
|
4406
|
-
assertTrue(
|
|
4407
|
-
args[0](error),
|
|
4408
|
-
`Error didn't match the error condition: ${error?.toString()}`
|
|
4409
|
-
);
|
|
4410
|
-
return;
|
|
4411
|
-
}
|
|
4412
|
-
assertTrue(
|
|
4413
|
-
error instanceof args[0],
|
|
4414
|
-
`Caught error is not an instance of the expected type: ${error?.toString()}`
|
|
4415
|
-
);
|
|
4416
|
-
if (args[1]) {
|
|
4417
|
-
assertTrue(
|
|
4418
|
-
args[1](error),
|
|
4419
|
-
`Error didn't match the error condition: ${error?.toString()}`
|
|
4420
|
-
);
|
|
4421
|
-
}
|
|
4422
|
-
} finally {
|
|
4423
|
-
await pool.close();
|
|
4424
|
-
}
|
|
4425
|
-
}
|
|
4426
|
-
};
|
|
4427
|
-
}
|
|
4428
|
-
};
|
|
4653
|
+
const event = {
|
|
4654
|
+
...rawEvent,
|
|
4655
|
+
kind: "Event",
|
|
4656
|
+
metadata
|
|
4429
4657
|
};
|
|
4658
|
+
return upcastRecordedMessage(event, options?.schema?.versioning);
|
|
4430
4659
|
}
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
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
|
|
4440
4669
|
};
|
|
4441
4670
|
};
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
};
|
|
4445
|
-
var
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
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;
|
|
4449
4683
|
};
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
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();
|
|
4454
4740
|
}
|
|
4455
|
-
})
|
|
4741
|
+
});
|
|
4456
4742
|
};
|
|
4457
4743
|
|
|
4458
|
-
// src/eventStore/
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
}
|
|
4468
|
-
var handleProjections = async (options) => {
|
|
4469
|
-
const {
|
|
4470
|
-
projections: allProjections,
|
|
4471
|
-
events,
|
|
4472
|
-
connection: { pool, transaction, connectionString },
|
|
4473
|
-
partition = defaultTag2
|
|
4474
|
-
} = options;
|
|
4475
|
-
const eventTypes = events.map((e) => e.type);
|
|
4476
|
-
const projections = allProjections.filter(
|
|
4477
|
-
(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" : "")};`
|
|
4478
4754
|
);
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
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
|
+
);
|
|
4490
4801
|
}
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
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
|
+
})
|
|
4514
4848
|
}
|
|
4515
|
-
}
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
if (initSQL) {
|
|
4534
|
-
if (Array.isArray(initSQL)) {
|
|
4535
|
-
await initOptions.context.execute.batchCommand(initSQL);
|
|
4536
|
-
} else {
|
|
4537
|
-
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);
|
|
4538
4867
|
}
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
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
|
|
4555
4894
|
}
|
|
4556
|
-
|
|
4557
|
-
|
|
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
|
+
});
|
|
4558
4941
|
}
|
|
4559
|
-
}
|
|
4942
|
+
};
|
|
4560
4943
|
};
|
|
4561
4944
|
|
|
4562
4945
|
// src/eventStore/consumers/postgreSQLProcessor.ts
|
|
@@ -4602,7 +4985,10 @@ var postgreSQLProcessingScope = (options) => {
|
|
|
4602
4985
|
connectionString,
|
|
4603
4986
|
pool,
|
|
4604
4987
|
client,
|
|
4605
|
-
transaction
|
|
4988
|
+
transaction,
|
|
4989
|
+
messageStore: getPostgreSQLEventStore(connectionString, {
|
|
4990
|
+
connectionOptions: { client }
|
|
4991
|
+
})
|
|
4606
4992
|
}
|
|
4607
4993
|
});
|
|
4608
4994
|
});
|
|
@@ -4616,7 +5002,8 @@ var getProcessorPool = (options) => {
|
|
|
4616
5002
|
const processorConnectionString = "connectionString" in poolOptions ? poolOptions.connectionString ?? null : null;
|
|
4617
5003
|
const processorPool = "dumbo" in poolOptions ? poolOptions.dumbo : processorConnectionString ? dumbo5({
|
|
4618
5004
|
connectionString: processorConnectionString,
|
|
4619
|
-
...poolOptions
|
|
5005
|
+
...poolOptions,
|
|
5006
|
+
serialization: options.serialization
|
|
4620
5007
|
}) : null;
|
|
4621
5008
|
return {
|
|
4622
5009
|
pool: processorPool,
|
|
@@ -4704,6 +5091,53 @@ var postgreSQLProjector = (options) => {
|
|
|
4704
5091
|
});
|
|
4705
5092
|
return processor;
|
|
4706
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
|
+
};
|
|
4707
5141
|
var postgreSQLReactor = (options) => {
|
|
4708
5142
|
const {
|
|
4709
5143
|
processorId = options.processorId,
|
|
@@ -4758,7 +5192,10 @@ var postgreSQLEventStoreConsumer = (options) => {
|
|
|
4758
5192
|
let abortController = null;
|
|
4759
5193
|
let start;
|
|
4760
5194
|
let messagePuller;
|
|
4761
|
-
const pool = options.pool ? options.pool : dumbo6({
|
|
5195
|
+
const pool = options.pool ? options.pool : dumbo6({
|
|
5196
|
+
connectionString: options.connectionString,
|
|
5197
|
+
serialization: options.serialization
|
|
5198
|
+
});
|
|
4762
5199
|
const eachBatch = async (messagesBatch) => {
|
|
4763
5200
|
const activeProcessors = processors.filter((s) => s.isActive);
|
|
4764
5201
|
if (activeProcessors.length === 0)
|
|
@@ -4788,7 +5225,8 @@ var postgreSQLEventStoreConsumer = (options) => {
|
|
|
4788
5225
|
connectionString: options.connectionString,
|
|
4789
5226
|
pool,
|
|
4790
5227
|
client: void 0,
|
|
4791
|
-
transaction: void 0
|
|
5228
|
+
transaction: void 0,
|
|
5229
|
+
messageStore: void 0
|
|
4792
5230
|
}
|
|
4793
5231
|
};
|
|
4794
5232
|
const stopProcessors = () => Promise.all(processors.map((p) => p.close(processorContext)));
|
|
@@ -4798,10 +5236,10 @@ var postgreSQLEventStoreConsumer = (options) => {
|
|
|
4798
5236
|
if (messagePuller) {
|
|
4799
5237
|
abortController?.abort();
|
|
4800
5238
|
await messagePuller.stop();
|
|
4801
|
-
messagePuller = void 0;
|
|
4802
|
-
abortController = null;
|
|
4803
5239
|
}
|
|
4804
5240
|
await start;
|
|
5241
|
+
messagePuller = void 0;
|
|
5242
|
+
abortController = null;
|
|
4805
5243
|
await stopProcessors();
|
|
4806
5244
|
};
|
|
4807
5245
|
const init = async () => {
|
|
@@ -4815,7 +5253,7 @@ var postgreSQLEventStoreConsumer = (options) => {
|
|
|
4815
5253
|
isInitialized = true;
|
|
4816
5254
|
};
|
|
4817
5255
|
return {
|
|
4818
|
-
consumerId: options.consumerId ??
|
|
5256
|
+
consumerId: options.consumerId ?? uuid9(),
|
|
4819
5257
|
get isRunning() {
|
|
4820
5258
|
return isRunning;
|
|
4821
5259
|
},
|
|
@@ -4837,6 +5275,14 @@ var postgreSQLEventStoreConsumer = (options) => {
|
|
|
4837
5275
|
);
|
|
4838
5276
|
return processor;
|
|
4839
5277
|
},
|
|
5278
|
+
workflowProcessor: (options2) => {
|
|
5279
|
+
const processor = postgreSQLWorkflowProcessor(options2);
|
|
5280
|
+
processors.push(
|
|
5281
|
+
// TODO: change that
|
|
5282
|
+
processor
|
|
5283
|
+
);
|
|
5284
|
+
return processor;
|
|
5285
|
+
},
|
|
4840
5286
|
start: () => {
|
|
4841
5287
|
if (isRunning) return start;
|
|
4842
5288
|
if (processors.length === 0)
|
|
@@ -4986,6 +5432,7 @@ export {
|
|
|
4986
5432
|
postgreSQLRawBatchSQLProjection,
|
|
4987
5433
|
postgreSQLRawSQLProjection,
|
|
4988
5434
|
postgreSQLReactor,
|
|
5435
|
+
postgreSQLWorkflowProcessor,
|
|
4989
5436
|
processorsTable,
|
|
4990
5437
|
processorsTableSQL,
|
|
4991
5438
|
projectionsTable,
|