@event-driven-io/emmett-postgresql 0.43.0-beta.17 → 0.43.0-beta.18
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/README.md +1 -1
- package/dist/index.cjs +642 -436
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -33
- package/dist/index.d.ts +54 -33
- package/dist/index.js +644 -440
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import { AssertionError, EmmettError, ExpectedVersionConflictError, NO_CONCURRENCY_CHECK, STREAM_DOES_NOT_EXIST, STREAM_EXISTS, assertDeepEqual, assertEqual, assertExpectedVersionMatchesCurrent, assertFails, assertIsNotNull, assertIsNull, assertThatArray, assertTrue, asyncAwaiter, asyncRetry, bigInt, bigIntProcessorCheckpoint, defaultProcessorPartition, defaultProcessorVersion, downcastRecordedMessages, getCheckpoint, getProcessorInstanceId, getProjectorId, getWorkflowId, hashText, isBigint, isErrorConstructor,
|
|
2
|
-
import { DumboError, JSONSerializer, SQL, UniqueConstraintError, dumbo, fromDatabaseDriverType, getFormatter, mapRows, runSQLMigrations, single, singleOrNull, sqlMigration } from "@event-driven-io/dumbo";
|
|
3
|
-
import { v4, v7 } from "uuid";
|
|
1
|
+
import { AssertionError, EmmettError, ExpectedVersionConflictError, JSONSerializer, NO_CONCURRENCY_CHECK, STREAM_DOES_NOT_EXIST, STREAM_EXISTS, assertDeepEqual, assertEqual, assertExpectedVersionMatchesCurrent, assertFails, assertIsNotNull, assertIsNull, assertThatArray, assertTrue, asyncAwaiter, asyncRetry, bigInt, bigIntProcessorCheckpoint, defaultProcessorPartition, defaultProcessorVersion, downcastRecordedMessages, getCheckpoint, getProcessorInstanceId, getProjectorId, getWorkflowId, hashText, isBigint, isErrorConstructor, projection, projector, reactor, reduceAsync, unknownTag, upcastRecordedMessage, workflowProcessor } from "@event-driven-io/emmett";
|
|
2
|
+
import { DumboError, JSONSerializer as JSONSerializer$1, SQL, UniqueConstraintError, dumbo, fromDatabaseDriverType, getFormatter, mapRows, runSQLMigrations, single, singleOrNull, sqlMigration } from "@event-driven-io/dumbo";
|
|
4
3
|
import { pongoClient } from "@event-driven-io/pongo";
|
|
5
4
|
import { pgDriver } from "@event-driven-io/pongo/pg";
|
|
5
|
+
import { v4, v7 } from "uuid";
|
|
6
|
+
|
|
7
|
+
//#region src/eventStore/schema/createFunctionIfDoesNotExist.ts
|
|
8
|
+
const createFunctionIfDoesNotExistSQL = (functionName, functionDefinition) => SQL`
|
|
9
|
+
DO $$
|
|
10
|
+
BEGIN
|
|
11
|
+
IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = '${SQL.plain(functionName)}') THEN
|
|
12
|
+
${functionDefinition}
|
|
13
|
+
END IF;
|
|
14
|
+
END $$;
|
|
15
|
+
`;
|
|
6
16
|
|
|
17
|
+
//#endregion
|
|
7
18
|
//#region src/eventStore/schema/typing.ts
|
|
8
19
|
const emmettPrefix = "emt";
|
|
9
20
|
const globalTag = "global";
|
|
@@ -34,145 +45,6 @@ const messagesTable = {
|
|
|
34
45
|
const processorsTable = { name: `${"emt"}_processors` };
|
|
35
46
|
const projectionsTable = { name: `${"emt"}_projections` };
|
|
36
47
|
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/eventStore/schema/readLastMessageGlobalPosition.ts
|
|
39
|
-
const readLastMessageGlobalPosition = async (execute, options) => {
|
|
40
|
-
const result = await singleOrNull(execute.query(SQL`SELECT global_position
|
|
41
|
-
FROM ${SQL.identifier(messagesTable.name)}
|
|
42
|
-
WHERE partition = ${options?.partition ?? defaultTag} AND is_archived = FALSE AND transaction_id < pg_snapshot_xmin(pg_current_snapshot())
|
|
43
|
-
ORDER BY transaction_id DESC, global_position DESC
|
|
44
|
-
LIMIT 1`));
|
|
45
|
-
return { currentGlobalPosition: result !== null ? BigInt(result.global_position) : null };
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
//#endregion
|
|
49
|
-
//#region src/eventStore/schema/readMessagesBatch.ts
|
|
50
|
-
const readMessagesBatch = async (execute, options) => {
|
|
51
|
-
const from = "from" in options ? options.from : void 0;
|
|
52
|
-
const after = "after" in options ? options.after : void 0;
|
|
53
|
-
const batchSize = "batchSize" in options ? options.batchSize : options.to - options.from;
|
|
54
|
-
const fromCondition = from !== void 0 ? SQL`AND global_position >= ${from}` : after !== void 0 ? SQL`AND global_position > ${after}` : SQL.EMPTY;
|
|
55
|
-
const toCondition = "to" in options ? SQL`AND global_position <= ${options.to}` : SQL.EMPTY;
|
|
56
|
-
const limitCondition = "batchSize" in options ? SQL`LIMIT ${options.batchSize}` : SQL.EMPTY;
|
|
57
|
-
const messages = await mapRows(execute.query(SQL`
|
|
58
|
-
SELECT stream_id, stream_position, global_position, message_data, message_metadata, message_schema_version, message_type, message_id
|
|
59
|
-
FROM ${SQL.identifier(messagesTable.name)}
|
|
60
|
-
WHERE partition = ${options?.partition ?? defaultTag} AND is_archived = FALSE AND transaction_id < pg_snapshot_xmin(pg_current_snapshot()) ${fromCondition} ${toCondition}
|
|
61
|
-
ORDER BY transaction_id, global_position
|
|
62
|
-
${limitCondition}`), (row) => {
|
|
63
|
-
const rawEvent = {
|
|
64
|
-
type: row.message_type,
|
|
65
|
-
data: row.message_data,
|
|
66
|
-
metadata: row.message_metadata
|
|
67
|
-
};
|
|
68
|
-
const metadata = {
|
|
69
|
-
..."metadata" in rawEvent ? rawEvent.metadata ?? {} : {},
|
|
70
|
-
messageId: row.message_id,
|
|
71
|
-
streamName: row.stream_id,
|
|
72
|
-
streamPosition: BigInt(row.stream_position),
|
|
73
|
-
globalPosition: BigInt(row.global_position),
|
|
74
|
-
checkpoint: bigIntProcessorCheckpoint(BigInt(row.global_position))
|
|
75
|
-
};
|
|
76
|
-
return {
|
|
77
|
-
...rawEvent,
|
|
78
|
-
kind: "Event",
|
|
79
|
-
metadata
|
|
80
|
-
};
|
|
81
|
-
});
|
|
82
|
-
return messages.length > 0 ? {
|
|
83
|
-
currentGlobalPosition: messages[messages.length - 1].metadata.globalPosition,
|
|
84
|
-
messages,
|
|
85
|
-
areMessagesLeft: messages.length === batchSize
|
|
86
|
-
} : {
|
|
87
|
-
currentGlobalPosition: "from" in options ? options.from : "after" in options ? options.after : 0n,
|
|
88
|
-
messages: [],
|
|
89
|
-
areMessagesLeft: false
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
//#endregion
|
|
94
|
-
//#region src/eventStore/consumers/messageBatchProcessing/index.ts
|
|
95
|
-
const DefaultPostgreSQLEventStoreProcessorBatchSize = 100;
|
|
96
|
-
const DefaultPostgreSQLEventStoreProcessorPullingFrequencyInMs = 50;
|
|
97
|
-
const postgreSQLEventStoreMessageBatchPuller = ({ executor, batchSize, eachBatch, pullingFrequencyInMs, stopWhen, signal }) => {
|
|
98
|
-
let isRunning = false;
|
|
99
|
-
let start;
|
|
100
|
-
const pullMessages = async (options) => {
|
|
101
|
-
try {
|
|
102
|
-
let after;
|
|
103
|
-
try {
|
|
104
|
-
after = options.startFrom === "BEGINNING" ? 0n : options.startFrom === "END" ? (await readLastMessageGlobalPosition(executor)).currentGlobalPosition ?? 0n : parseBigIntProcessorCheckpoint(options.startFrom.lastCheckpoint);
|
|
105
|
-
} catch (error) {
|
|
106
|
-
console.log("Error occurred during reading last processor checkpoint:", error);
|
|
107
|
-
options.started?.reject(error);
|
|
108
|
-
throw error;
|
|
109
|
-
}
|
|
110
|
-
options.started?.resolve();
|
|
111
|
-
const readMessagesOptions = {
|
|
112
|
-
after,
|
|
113
|
-
batchSize
|
|
114
|
-
};
|
|
115
|
-
let waitTime = 100;
|
|
116
|
-
while (isRunning && !signal?.aborted) {
|
|
117
|
-
const { messages, currentGlobalPosition, areMessagesLeft } = await readMessagesBatch(executor, readMessagesOptions);
|
|
118
|
-
if (messages.length > 0) {
|
|
119
|
-
const result = await eachBatch(messages);
|
|
120
|
-
if (result && result.type === "STOP") {
|
|
121
|
-
isRunning = false;
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
readMessagesOptions.after = currentGlobalPosition;
|
|
126
|
-
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
127
|
-
if (stopWhen?.noMessagesLeft === true && !areMessagesLeft) {
|
|
128
|
-
console.log(`No messages left to process after reaching global position ${currentGlobalPosition}. Stopping the puller.`);
|
|
129
|
-
isRunning = false;
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
if (!areMessagesLeft) waitTime = Math.min(waitTime * 2, 1e3);
|
|
133
|
-
else waitTime = pullingFrequencyInMs;
|
|
134
|
-
}
|
|
135
|
-
} catch (error) {
|
|
136
|
-
console.log("Error occurred during message pulling:", error);
|
|
137
|
-
throw error;
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
return {
|
|
141
|
-
get isRunning() {
|
|
142
|
-
return isRunning;
|
|
143
|
-
},
|
|
144
|
-
start: (options) => {
|
|
145
|
-
if (isRunning) return start;
|
|
146
|
-
isRunning = true;
|
|
147
|
-
start = (async () => {
|
|
148
|
-
return pullMessages(options);
|
|
149
|
-
})();
|
|
150
|
-
return start;
|
|
151
|
-
},
|
|
152
|
-
stop: async () => {
|
|
153
|
-
if (!isRunning) return;
|
|
154
|
-
isRunning = false;
|
|
155
|
-
await start;
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
};
|
|
159
|
-
const zipPostgreSQLEventStoreMessageBatchPullerStartFrom = (options) => {
|
|
160
|
-
if (options.length === 0 || options.some((o) => o === void 0 || o === "BEGINNING")) return "BEGINNING";
|
|
161
|
-
if (options.every((o) => o === "END")) return "END";
|
|
162
|
-
return options.filter((o) => o !== void 0 && o !== "BEGINNING" && o !== "END").sort((a, b) => a > b ? 1 : -1)[0];
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
//#endregion
|
|
166
|
-
//#region src/eventStore/schema/createFunctionIfDoesNotExist.ts
|
|
167
|
-
const createFunctionIfDoesNotExistSQL = (functionName, functionDefinition) => SQL`
|
|
168
|
-
DO $$
|
|
169
|
-
BEGIN
|
|
170
|
-
IF NOT EXISTS (SELECT 1 FROM pg_proc WHERE proname = '${SQL.plain(functionName)}') THEN
|
|
171
|
-
${functionDefinition}
|
|
172
|
-
END IF;
|
|
173
|
-
END $$;
|
|
174
|
-
`;
|
|
175
|
-
|
|
176
48
|
//#endregion
|
|
177
49
|
//#region src/eventStore/schema/projections/projectionsLocks.ts
|
|
178
50
|
const tryAcquireProjectionLockSQL = createFunctionIfDoesNotExistSQL("emt_try_acquire_projection_lock", SQL`
|
|
@@ -580,7 +452,7 @@ const registerProjection = async (execute, options) => {
|
|
|
580
452
|
const name = registration.projection.name;
|
|
581
453
|
const version = registration.projection.version ?? 1;
|
|
582
454
|
const kind = registration.projection.kind ?? registration.type;
|
|
583
|
-
const definition = JSONSerializer.serialize(registration.projection);
|
|
455
|
+
const definition = JSONSerializer$1.serialize(registration.projection);
|
|
584
456
|
const lockKeyBigInt = await hashText(toProjectionLockKey({
|
|
585
457
|
projectionName: name,
|
|
586
458
|
partition,
|
|
@@ -864,127 +736,280 @@ const expectPongoDocuments = { fromCollection: (collectionName) => {
|
|
|
864
736
|
} };
|
|
865
737
|
|
|
866
738
|
//#endregion
|
|
867
|
-
//#region src/eventStore/
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
const { connectionString } = dumboOptions;
|
|
876
|
-
let wasInitialised = false;
|
|
877
|
-
const initialize = async (pool) => {
|
|
878
|
-
const eventStore = getPostgreSQLEventStore(connectionString, { connectionOptions: { dumbo: pool } });
|
|
879
|
-
if (wasInitialised) return;
|
|
880
|
-
wasInitialised = true;
|
|
881
|
-
await eventStore.schema.migrate();
|
|
882
|
-
if (projection.init) await pool.withTransaction(async (transaction) => {
|
|
883
|
-
await projection.init({
|
|
884
|
-
registrationType: "async",
|
|
885
|
-
version: projection.version ?? 1,
|
|
886
|
-
status: "active",
|
|
887
|
-
context: await transactionToPostgreSQLProjectionHandlerContext(connectionString, pool, transaction)
|
|
888
|
-
});
|
|
889
|
-
});
|
|
890
|
-
};
|
|
891
|
-
return (givenEvents) => {
|
|
892
|
-
return { when: (events, options) => {
|
|
893
|
-
const allEvents = [];
|
|
894
|
-
const run = async (pool) => {
|
|
895
|
-
let globalPosition = 0n;
|
|
896
|
-
const numberOfTimes = options?.numberOfTimes ?? 1;
|
|
897
|
-
for (const event of [...givenEvents, ...Array.from({ length: numberOfTimes }).flatMap(() => events)]) {
|
|
898
|
-
const metadata = {
|
|
899
|
-
checkpoint: bigIntProcessorCheckpoint(++globalPosition),
|
|
900
|
-
globalPosition,
|
|
901
|
-
streamPosition: globalPosition,
|
|
902
|
-
streamName: `test-${v4()}`,
|
|
903
|
-
messageId: v4()
|
|
904
|
-
};
|
|
905
|
-
allEvents.push({
|
|
906
|
-
...event,
|
|
907
|
-
kind: "Event",
|
|
908
|
-
metadata: {
|
|
909
|
-
...metadata,
|
|
910
|
-
..."metadata" in event ? event.metadata ?? {} : {}
|
|
911
|
-
}
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
await initialize(pool);
|
|
915
|
-
await pool.withTransaction(async (transaction) => {
|
|
916
|
-
await handleProjections({
|
|
917
|
-
events: allEvents,
|
|
918
|
-
projections: [projection],
|
|
919
|
-
...await transactionToPostgreSQLProjectionHandlerContext(connectionString, pool, transaction)
|
|
920
|
-
});
|
|
921
|
-
});
|
|
922
|
-
};
|
|
923
|
-
return {
|
|
924
|
-
then: async (assert, message) => {
|
|
925
|
-
const pool = dumbo(dumboOptions);
|
|
926
|
-
try {
|
|
927
|
-
await run(pool);
|
|
928
|
-
const succeeded = await assert({
|
|
929
|
-
pool,
|
|
930
|
-
connectionString
|
|
931
|
-
});
|
|
932
|
-
if (succeeded !== void 0 && succeeded === false) assertFails(message ?? "Projection specification didn't match the criteria");
|
|
933
|
-
} finally {
|
|
934
|
-
await pool.close();
|
|
935
|
-
}
|
|
936
|
-
},
|
|
937
|
-
thenThrows: async (...args) => {
|
|
938
|
-
const pool = dumbo(dumboOptions);
|
|
939
|
-
try {
|
|
940
|
-
await run(pool);
|
|
941
|
-
throw new AssertionError("Handler did not fail as expected");
|
|
942
|
-
} catch (error) {
|
|
943
|
-
if (error instanceof AssertionError) throw error;
|
|
944
|
-
if (args.length === 0) return;
|
|
945
|
-
if (!isErrorConstructor(args[0])) {
|
|
946
|
-
assertTrue(args[0](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
947
|
-
return;
|
|
948
|
-
}
|
|
949
|
-
assertTrue(error instanceof args[0], `Caught error is not an instance of the expected type: ${error?.toString()}`);
|
|
950
|
-
if (args[1]) assertTrue(args[1](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
951
|
-
} finally {
|
|
952
|
-
await pool.close();
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
};
|
|
956
|
-
} };
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
} };
|
|
960
|
-
const eventInStream = (streamName, event) => {
|
|
961
|
-
return {
|
|
962
|
-
...event,
|
|
963
|
-
metadata: {
|
|
964
|
-
...event.metadata ?? {},
|
|
965
|
-
streamName: event.metadata?.streamName ?? streamName
|
|
966
|
-
}
|
|
967
|
-
};
|
|
968
|
-
};
|
|
969
|
-
const eventsInStream = (streamName, events) => {
|
|
970
|
-
return events.map((e) => eventInStream(streamName, e));
|
|
971
|
-
};
|
|
972
|
-
const newEventsInStream = eventsInStream;
|
|
973
|
-
const assertSQLQueryResultMatches = (sql, rows) => async ({ pool: { execute } }) => {
|
|
974
|
-
const result = await execute.query(sql);
|
|
975
|
-
assertThatArray(rows).containsExactlyInAnyOrder(result.rows);
|
|
739
|
+
//#region src/eventStore/schema/truncateTables.ts
|
|
740
|
+
const truncateTables = async (execute, options) => {
|
|
741
|
+
await execute.command(SQL`TRUNCATE TABLE
|
|
742
|
+
${SQL.identifier(streamsTable.name)},
|
|
743
|
+
${SQL.identifier(messagesTable.name)},
|
|
744
|
+
${SQL.identifier(processorsTable.name)},
|
|
745
|
+
${SQL.identifier(projectionsTable.name)}
|
|
746
|
+
CASCADE${SQL.plain(options?.resetSequences ? "; ALTER SEQUENCE emt_global_message_position RESTART WITH 1" : "")};`);
|
|
976
747
|
};
|
|
977
|
-
const expectSQL = { query: (sql) => ({ resultRows: { toBeTheSame: (rows) => assertSQLQueryResultMatches(sql, rows) } }) };
|
|
978
748
|
|
|
979
749
|
//#endregion
|
|
980
|
-
//#region src/eventStore/
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
750
|
+
//#region src/eventStore/postgreSQLEventStore.ts
|
|
751
|
+
const defaultPostgreSQLOptions = {
|
|
752
|
+
projections: [],
|
|
753
|
+
schema: { autoMigration: "CreateOrUpdate" }
|
|
754
|
+
};
|
|
755
|
+
const PostgreSQLEventStoreDefaultStreamVersion = 0n;
|
|
756
|
+
const getPostgreSQLEventStore = (connectionString, options = defaultPostgreSQLOptions) => {
|
|
757
|
+
const poolOptions = {
|
|
758
|
+
connectionString,
|
|
759
|
+
...options.connectionOptions ? options.connectionOptions : {}
|
|
760
|
+
};
|
|
761
|
+
const pool = "dumbo" in poolOptions ? poolOptions.dumbo : dumbo({
|
|
762
|
+
...poolOptions,
|
|
763
|
+
serialization: options.serialization
|
|
764
|
+
});
|
|
765
|
+
let migrateSchema = void 0;
|
|
766
|
+
const autoGenerateSchema = options.schema?.autoMigration === void 0 || options.schema?.autoMigration !== "None";
|
|
767
|
+
const inlineProjections = (options.projections ?? []).filter(({ type }) => type === "inline").map(({ projection }) => projection);
|
|
768
|
+
const migrate = async (migrationOptions) => {
|
|
769
|
+
if (!migrateSchema) {
|
|
770
|
+
migrateSchema = createEventStoreSchema(connectionString, pool, {
|
|
771
|
+
onBeforeSchemaCreated: async (context) => {
|
|
772
|
+
if (options.hooks?.onBeforeSchemaCreated) await options.hooks.onBeforeSchemaCreated(context);
|
|
773
|
+
},
|
|
774
|
+
onAfterSchemaCreated: async (context) => {
|
|
775
|
+
for (const projection of inlineProjections) if (projection.init) await projection.init({
|
|
776
|
+
version: projection.version ?? 1,
|
|
777
|
+
status: "active",
|
|
778
|
+
registrationType: "inline",
|
|
779
|
+
context: {
|
|
780
|
+
...context,
|
|
781
|
+
migrationOptions
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
if (options.hooks?.onAfterSchemaCreated) await options.hooks.onAfterSchemaCreated(context);
|
|
785
|
+
}
|
|
786
|
+
}, migrationOptions);
|
|
787
|
+
return migrateSchema;
|
|
788
|
+
}
|
|
789
|
+
const result = await migrateSchema;
|
|
790
|
+
if (migrationOptions?.dryRun) migrateSchema = void 0;
|
|
791
|
+
return {
|
|
792
|
+
applied: [],
|
|
793
|
+
skipped: result.applied.concat(result.skipped)
|
|
794
|
+
};
|
|
795
|
+
};
|
|
796
|
+
const ensureSchemaExists = () => {
|
|
797
|
+
if (!autoGenerateSchema) return Promise.resolve();
|
|
798
|
+
return migrate();
|
|
799
|
+
};
|
|
800
|
+
const beforeCommitHook = inlineProjections.length > 0 ? async (events, { transaction }) => handleProjections({
|
|
801
|
+
projections: inlineProjections,
|
|
802
|
+
events,
|
|
803
|
+
...await transactionToPostgreSQLProjectionHandlerContext(connectionString, pool, transaction)
|
|
804
|
+
}) : void 0;
|
|
805
|
+
return {
|
|
806
|
+
schema: {
|
|
807
|
+
sql: () => SQL.describe(schemaSQL, getFormatter(fromDatabaseDriverType(pool.driverType).databaseType)),
|
|
808
|
+
print: () => console.log(SQL.describe(schemaSQL, getFormatter(fromDatabaseDriverType(pool.driverType).databaseType))),
|
|
809
|
+
migrate,
|
|
810
|
+
dangerous: { truncate: (truncateOptions) => pool.withTransaction(async (transaction) => {
|
|
811
|
+
await ensureSchemaExists();
|
|
812
|
+
await truncateTables(transaction.execute, truncateOptions);
|
|
813
|
+
if (truncateOptions?.truncateProjections) {
|
|
814
|
+
const projectionContext = await transactionToPostgreSQLProjectionHandlerContext(connectionString, pool, transaction);
|
|
815
|
+
for (const projection of options?.projections ?? []) if (projection.projection.truncate) await projection.projection.truncate(projectionContext);
|
|
816
|
+
}
|
|
817
|
+
}) }
|
|
818
|
+
},
|
|
819
|
+
async aggregateStream(streamName, options) {
|
|
820
|
+
const { evolve, initialState, read } = options;
|
|
821
|
+
const expectedStreamVersion = read?.expectedStreamVersion;
|
|
822
|
+
let state = initialState();
|
|
823
|
+
const result = await this.readStream(streamName, read);
|
|
824
|
+
const currentStreamVersion = result.currentStreamVersion;
|
|
825
|
+
assertExpectedVersionMatchesCurrent(currentStreamVersion, expectedStreamVersion, PostgreSQLEventStoreDefaultStreamVersion);
|
|
826
|
+
for (const event of result.events) {
|
|
827
|
+
if (!event) continue;
|
|
828
|
+
state = evolve(state, event);
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
currentStreamVersion,
|
|
832
|
+
state,
|
|
833
|
+
streamExists: result.streamExists
|
|
834
|
+
};
|
|
835
|
+
},
|
|
836
|
+
readStream: async (streamName, readOptions) => {
|
|
837
|
+
await ensureSchemaExists();
|
|
838
|
+
return readStream(pool.execute, streamName, {
|
|
839
|
+
...readOptions,
|
|
840
|
+
serialization: options.serialization ?? readOptions?.serialization
|
|
841
|
+
});
|
|
842
|
+
},
|
|
843
|
+
appendToStream: async (streamName, events, appendOptions) => {
|
|
844
|
+
await ensureSchemaExists();
|
|
845
|
+
const [firstPart, ...rest] = streamName.split("-");
|
|
846
|
+
const streamType = firstPart && rest.length > 0 ? firstPart : unknownTag;
|
|
847
|
+
const appendResult = await pool.withConnection(async (connection) => appendToStream(connection, streamName, streamType, downcastRecordedMessages(events, appendOptions?.schema?.versioning), {
|
|
848
|
+
...appendOptions,
|
|
849
|
+
beforeCommitHook
|
|
850
|
+
}));
|
|
851
|
+
if (!appendResult.success) throw new ExpectedVersionConflictError(-1n, appendOptions?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK);
|
|
852
|
+
return {
|
|
853
|
+
nextExpectedStreamVersion: appendResult.nextStreamPosition,
|
|
854
|
+
lastEventGlobalPosition: PostgreSQLEventStoreCheckpoint.toProcessorCheckpoint(appendResult.checkpoints[appendResult.checkpoints.length - 1]),
|
|
855
|
+
createdNewStream: appendResult.nextStreamPosition >= BigInt(events.length)
|
|
856
|
+
};
|
|
857
|
+
},
|
|
858
|
+
streamExists: async (streamName, options) => {
|
|
859
|
+
await ensureSchemaExists();
|
|
860
|
+
return streamExists(pool.execute, streamName, options);
|
|
861
|
+
},
|
|
862
|
+
consumer: (options) => postgreSQLEventStoreConsumer({
|
|
863
|
+
...options ?? {},
|
|
864
|
+
pool,
|
|
865
|
+
connectionString
|
|
866
|
+
}),
|
|
867
|
+
close: () => pool.close(),
|
|
868
|
+
async withSession(callback) {
|
|
869
|
+
return await pool.withConnection(async (connection) => {
|
|
870
|
+
const eventStore = getPostgreSQLEventStore(connectionString, {
|
|
871
|
+
...options,
|
|
872
|
+
connectionOptions: { connection },
|
|
873
|
+
schema: {
|
|
874
|
+
...options.schema ?? {},
|
|
875
|
+
autoMigration: "None"
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
return ensureSchemaExists().then(() => callback({
|
|
879
|
+
eventStore,
|
|
880
|
+
close: () => Promise.resolve()
|
|
881
|
+
}));
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
};
|
|
886
|
+
|
|
887
|
+
//#endregion
|
|
888
|
+
//#region src/eventStore/projections/postgresProjectionSpec.ts
|
|
889
|
+
const PostgreSQLProjectionSpec = { for: (options) => {
|
|
890
|
+
{
|
|
891
|
+
const { projection, ...restOptions } = options;
|
|
892
|
+
const dumboOptions = {
|
|
893
|
+
...restOptions,
|
|
894
|
+
serialization: projection.serialization
|
|
895
|
+
};
|
|
896
|
+
const { connectionString } = dumboOptions;
|
|
897
|
+
let wasInitialised = false;
|
|
898
|
+
const initialize = async (pool) => {
|
|
899
|
+
const eventStore = getPostgreSQLEventStore(connectionString, { connectionOptions: { dumbo: pool } });
|
|
900
|
+
if (wasInitialised) return;
|
|
901
|
+
wasInitialised = true;
|
|
902
|
+
await eventStore.schema.migrate();
|
|
903
|
+
if (projection.init) await pool.withTransaction(async (transaction) => {
|
|
904
|
+
await projection.init({
|
|
905
|
+
registrationType: "async",
|
|
906
|
+
version: projection.version ?? 1,
|
|
907
|
+
status: "active",
|
|
908
|
+
context: await transactionToPostgreSQLProjectionHandlerContext(connectionString, pool, transaction)
|
|
909
|
+
});
|
|
910
|
+
});
|
|
911
|
+
};
|
|
912
|
+
return (givenEvents) => {
|
|
913
|
+
return { when: (events, options) => {
|
|
914
|
+
const allEvents = [];
|
|
915
|
+
const run = async (pool) => {
|
|
916
|
+
let globalPosition = 0n;
|
|
917
|
+
const numberOfTimes = options?.numberOfTimes ?? 1;
|
|
918
|
+
for (const event of [...givenEvents, ...Array.from({ length: numberOfTimes }).flatMap(() => events)]) {
|
|
919
|
+
const checkpoint = PostgreSQLEventStoreCheckpoint.toProcessorCheckpoint({
|
|
920
|
+
transactionId: ++globalPosition,
|
|
921
|
+
globalPosition
|
|
922
|
+
});
|
|
923
|
+
const metadata = {
|
|
924
|
+
checkpoint,
|
|
925
|
+
globalPosition: checkpoint,
|
|
926
|
+
streamPosition: globalPosition,
|
|
927
|
+
streamName: `test-${v4()}`,
|
|
928
|
+
messageId: v4()
|
|
929
|
+
};
|
|
930
|
+
allEvents.push({
|
|
931
|
+
...event,
|
|
932
|
+
kind: "Event",
|
|
933
|
+
metadata: {
|
|
934
|
+
...metadata,
|
|
935
|
+
..."metadata" in event ? event.metadata ?? {} : {}
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
await initialize(pool);
|
|
940
|
+
await pool.withTransaction(async (transaction) => {
|
|
941
|
+
await handleProjections({
|
|
942
|
+
events: allEvents,
|
|
943
|
+
projections: [projection],
|
|
944
|
+
...await transactionToPostgreSQLProjectionHandlerContext(connectionString, pool, transaction)
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
};
|
|
948
|
+
return {
|
|
949
|
+
then: async (assert, message) => {
|
|
950
|
+
const pool = dumbo(dumboOptions);
|
|
951
|
+
try {
|
|
952
|
+
await run(pool);
|
|
953
|
+
const succeeded = await assert({
|
|
954
|
+
pool,
|
|
955
|
+
connectionString
|
|
956
|
+
});
|
|
957
|
+
if (succeeded !== void 0 && succeeded === false) assertFails(message ?? "Projection specification didn't match the criteria");
|
|
958
|
+
} finally {
|
|
959
|
+
await pool.close();
|
|
960
|
+
}
|
|
961
|
+
},
|
|
962
|
+
thenThrows: async (...args) => {
|
|
963
|
+
const pool = dumbo(dumboOptions);
|
|
964
|
+
try {
|
|
965
|
+
await run(pool);
|
|
966
|
+
throw new AssertionError("Handler did not fail as expected");
|
|
967
|
+
} catch (error) {
|
|
968
|
+
if (error instanceof AssertionError) throw error;
|
|
969
|
+
if (args.length === 0) return;
|
|
970
|
+
if (!isErrorConstructor(args[0])) {
|
|
971
|
+
assertTrue(args[0](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
assertTrue(error instanceof args[0], `Caught error is not an instance of the expected type: ${error?.toString()}`);
|
|
975
|
+
if (args[1]) assertTrue(args[1](error), `Error didn't match the error condition: ${error?.toString()}`);
|
|
976
|
+
} finally {
|
|
977
|
+
await pool.close();
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
};
|
|
981
|
+
} };
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
} };
|
|
985
|
+
const eventInStream = (streamName, event) => {
|
|
986
|
+
return {
|
|
987
|
+
...event,
|
|
988
|
+
metadata: {
|
|
989
|
+
...event.metadata ?? {},
|
|
990
|
+
streamName: event.metadata?.streamName ?? streamName
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
};
|
|
994
|
+
const eventsInStream = (streamName, events) => {
|
|
995
|
+
return events.map((e) => eventInStream(streamName, e));
|
|
996
|
+
};
|
|
997
|
+
const newEventsInStream = eventsInStream;
|
|
998
|
+
const assertSQLQueryResultMatches = (sql, rows) => async ({ pool: { execute } }) => {
|
|
999
|
+
const result = await execute.query(sql);
|
|
1000
|
+
assertThatArray(rows).containsExactlyInAnyOrder(result.rows);
|
|
1001
|
+
};
|
|
1002
|
+
const expectSQL = { query: (sql) => ({ resultRows: { toBeTheSame: (rows) => assertSQLQueryResultMatches(sql, rows) } }) };
|
|
1003
|
+
|
|
1004
|
+
//#endregion
|
|
1005
|
+
//#region src/eventStore/projections/postgreSQLProjection.ts
|
|
1006
|
+
const transactionToPostgreSQLProjectionHandlerContext = async (connectionString, pool, transaction) => ({
|
|
1007
|
+
execute: transaction.execute,
|
|
1008
|
+
connection: {
|
|
1009
|
+
connectionString,
|
|
1010
|
+
client: await transaction.connection.open(),
|
|
1011
|
+
transaction,
|
|
1012
|
+
pool
|
|
988
1013
|
}
|
|
989
1014
|
});
|
|
990
1015
|
const handleProjections = async (options) => {
|
|
@@ -1058,6 +1083,74 @@ const postgreSQLRawSQLProjection = (options) => {
|
|
|
1058
1083
|
});
|
|
1059
1084
|
};
|
|
1060
1085
|
|
|
1086
|
+
//#endregion
|
|
1087
|
+
//#region src/eventStore/schema/readMessagesBatch.ts
|
|
1088
|
+
const PostgreSQLEventStoreCheckpoint = {
|
|
1089
|
+
default: {
|
|
1090
|
+
transactionId: 0n,
|
|
1091
|
+
globalPosition: 0n
|
|
1092
|
+
},
|
|
1093
|
+
parse: (checkPoint) => {
|
|
1094
|
+
if (checkPoint === void 0 || checkPoint === null) return PostgreSQLEventStoreCheckpoint.default;
|
|
1095
|
+
const [transactionId, globalPosition] = checkPoint.split(":");
|
|
1096
|
+
return {
|
|
1097
|
+
transactionId: BigInt(transactionId),
|
|
1098
|
+
globalPosition: BigInt(globalPosition)
|
|
1099
|
+
};
|
|
1100
|
+
},
|
|
1101
|
+
toProcessorCheckpoint: (checkPoint) => `${checkPoint.transactionId.toString().padStart(20, "0")}:${bigIntProcessorCheckpoint(checkPoint.globalPosition)}`
|
|
1102
|
+
};
|
|
1103
|
+
const readMessagesBatch = async (execute, options) => {
|
|
1104
|
+
const from = "from" in options ? options.from : void 0;
|
|
1105
|
+
const after = "after" in options ? options.after : void 0;
|
|
1106
|
+
const batchSize = "batchSize" in options ? options.batchSize : options.to.globalPosition - options.from.globalPosition;
|
|
1107
|
+
const fromCondition = from !== void 0 ? SQL`AND (transaction_id, global_position) >= (${from.transactionId}, ${from.globalPosition})` : after !== void 0 ? SQL`AND (transaction_id, global_position) > (${after.transactionId}, ${after.globalPosition})` : SQL.EMPTY;
|
|
1108
|
+
const toCondition = "to" in options ? SQL`AND (transaction_id, global_position) <= (${options.to.transactionId}, ${options.to.globalPosition})` : SQL.EMPTY;
|
|
1109
|
+
const limitCondition = "batchSize" in options ? SQL`LIMIT ${options.batchSize}` : SQL.EMPTY;
|
|
1110
|
+
const query = SQL`
|
|
1111
|
+
SELECT stream_id, stream_position, global_position, message_data, message_metadata, message_schema_version, message_type, message_id, transaction_id
|
|
1112
|
+
FROM ${SQL.identifier(messagesTable.name)}
|
|
1113
|
+
WHERE partition = ${options?.partition ?? defaultTag}
|
|
1114
|
+
AND is_archived = FALSE
|
|
1115
|
+
AND transaction_id < pg_snapshot_xmin(pg_current_snapshot())
|
|
1116
|
+
${fromCondition} ${toCondition}
|
|
1117
|
+
ORDER BY transaction_id, global_position
|
|
1118
|
+
${limitCondition}`;
|
|
1119
|
+
const messages = await mapRows(execute.query(query), (row) => {
|
|
1120
|
+
const rawEvent = {
|
|
1121
|
+
type: row.message_type,
|
|
1122
|
+
data: row.message_data,
|
|
1123
|
+
metadata: row.message_metadata
|
|
1124
|
+
};
|
|
1125
|
+
const globalPosition = PostgreSQLEventStoreCheckpoint.toProcessorCheckpoint({
|
|
1126
|
+
transactionId: BigInt(row.transaction_id),
|
|
1127
|
+
globalPosition: BigInt(row.global_position)
|
|
1128
|
+
});
|
|
1129
|
+
const metadata = {
|
|
1130
|
+
..."metadata" in rawEvent ? rawEvent.metadata ?? {} : {},
|
|
1131
|
+
messageId: row.message_id,
|
|
1132
|
+
streamName: row.stream_id,
|
|
1133
|
+
streamPosition: BigInt(row.stream_position),
|
|
1134
|
+
globalPosition,
|
|
1135
|
+
checkpoint: globalPosition
|
|
1136
|
+
};
|
|
1137
|
+
return {
|
|
1138
|
+
...rawEvent,
|
|
1139
|
+
kind: "Event",
|
|
1140
|
+
metadata
|
|
1141
|
+
};
|
|
1142
|
+
});
|
|
1143
|
+
return messages.length > 0 ? {
|
|
1144
|
+
currentCheckpoint: PostgreSQLEventStoreCheckpoint.parse(messages[messages.length - 1].metadata.checkpoint),
|
|
1145
|
+
messages,
|
|
1146
|
+
areMessagesLeft: messages.length === batchSize
|
|
1147
|
+
} : {
|
|
1148
|
+
currentCheckpoint: "from" in options ? options.from : "after" in options ? options.after : PostgreSQLEventStoreCheckpoint.default,
|
|
1149
|
+
messages: [],
|
|
1150
|
+
areMessagesLeft: false
|
|
1151
|
+
};
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1061
1154
|
//#endregion
|
|
1062
1155
|
//#region src/eventStore/schema/appendToStream.ts
|
|
1063
1156
|
const appendToStreamSQL = createFunctionIfDoesNotExistSQL("emt_append_to_stream", SQL`CREATE OR REPLACE FUNCTION emt_append_to_stream(
|
|
@@ -1163,7 +1256,7 @@ const callAppendToStream = (params) => SQL`SELECT * FROM emt_append_to_stream(
|
|
|
1163
1256
|
${params.expectedStreamPosition},
|
|
1164
1257
|
${params.partition}::text
|
|
1165
1258
|
)`;
|
|
1166
|
-
const appendToStream = (
|
|
1259
|
+
const appendToStream = (connection, streamName, streamType, messages, options) => connection.withTransaction(async (transaction) => {
|
|
1167
1260
|
const { execute } = transaction;
|
|
1168
1261
|
if (messages.length === 0) return {
|
|
1169
1262
|
success: false,
|
|
@@ -1179,19 +1272,23 @@ const appendToStream = (pool, streamName, streamType, messages, options) => pool
|
|
|
1179
1272
|
..."metadata" in e ? e.metadata ?? {} : {}
|
|
1180
1273
|
}
|
|
1181
1274
|
}));
|
|
1182
|
-
const { success, next_stream_position, global_positions, transaction_id } = await
|
|
1275
|
+
const { success, next_stream_position, global_positions, transaction_id } = await appentToStreamRaw(execute, streamName, streamType, messagesToAppend, { expectedStreamVersion });
|
|
1183
1276
|
if (!success || next_stream_position === null || global_positions === null || global_positions.length === 0 || transaction_id == null) return {
|
|
1184
1277
|
success: false,
|
|
1185
1278
|
result: { success: false }
|
|
1186
1279
|
};
|
|
1187
1280
|
const nextStreamPosition = BigInt(next_stream_position);
|
|
1188
1281
|
const globalPositions = global_positions.map(BigInt);
|
|
1282
|
+
const transactionId = BigInt(transaction_id);
|
|
1189
1283
|
globalPositions.forEach((globalPosition, index) => {
|
|
1190
1284
|
messagesToAppend[index].metadata = {
|
|
1191
1285
|
...messagesToAppend[index].metadata,
|
|
1192
1286
|
streamName,
|
|
1193
1287
|
streamPosition: nextStreamPosition - BigInt(messagesToAppend.length) + BigInt(index + 1),
|
|
1194
|
-
globalPosition
|
|
1288
|
+
globalPosition: PostgreSQLEventStoreCheckpoint.toProcessorCheckpoint({
|
|
1289
|
+
transactionId,
|
|
1290
|
+
globalPosition
|
|
1291
|
+
})
|
|
1195
1292
|
};
|
|
1196
1293
|
});
|
|
1197
1294
|
if (options?.beforeCommitHook) await options.beforeCommitHook(messagesToAppend, { transaction });
|
|
@@ -1200,8 +1297,10 @@ const appendToStream = (pool, streamName, streamType, messages, options) => pool
|
|
|
1200
1297
|
result: {
|
|
1201
1298
|
success: true,
|
|
1202
1299
|
nextStreamPosition,
|
|
1203
|
-
globalPositions
|
|
1204
|
-
|
|
1300
|
+
checkpoints: globalPositions.map((globalPosition) => ({
|
|
1301
|
+
transactionId,
|
|
1302
|
+
globalPosition
|
|
1303
|
+
}))
|
|
1205
1304
|
}
|
|
1206
1305
|
};
|
|
1207
1306
|
} catch (error) {
|
|
@@ -1220,7 +1319,7 @@ const toExpectedVersion = (expected) => {
|
|
|
1220
1319
|
return expected;
|
|
1221
1320
|
};
|
|
1222
1321
|
const isOptimisticConcurrencyError = (error) => DumboError.isInstanceOf(error, { errorType: UniqueConstraintError.ErrorType });
|
|
1223
|
-
const
|
|
1322
|
+
const appentToStreamRaw = (execute, streamId, streamType, messages, options) => single(execute.command(callAppendToStream({
|
|
1224
1323
|
messageIds: messages.map((e) => e.metadata.messageId),
|
|
1225
1324
|
messagesData: messages.map((e) => e.data),
|
|
1226
1325
|
messagesMetadata: messages.map((e) => {
|
|
@@ -1253,7 +1352,7 @@ DECLARE
|
|
|
1253
1352
|
BEGIN
|
|
1254
1353
|
-- Handle the case when p_check_position is provided
|
|
1255
1354
|
IF p_check_position IS NOT NULL THEN
|
|
1256
|
-
-- Try to update if the position matches p_check_position
|
|
1355
|
+
-- Try to update if the position matches p_check_position (new format)
|
|
1257
1356
|
UPDATE "${SQL.plain(processorsTable.name)}"
|
|
1258
1357
|
SET
|
|
1259
1358
|
"last_processed_checkpoint" = p_position,
|
|
@@ -1268,6 +1367,38 @@ BEGIN
|
|
|
1268
1367
|
RETURN 1; -- Successfully updated
|
|
1269
1368
|
END IF;
|
|
1270
1369
|
|
|
1370
|
+
-- TODO: Remove once all deployments have run the 0.43.0 migration.
|
|
1371
|
+
-- Handles mixed-format scenarios during blue-green deployment.
|
|
1372
|
+
IF p_check_position LIKE '%:%' THEN
|
|
1373
|
+
-- new code, stored value still in old format (plain globalpos)
|
|
1374
|
+
UPDATE "${SQL.plain(processorsTable.name)}"
|
|
1375
|
+
SET
|
|
1376
|
+
"last_processed_checkpoint" = p_position,
|
|
1377
|
+
"last_processed_transaction_id" = p_transaction_id,
|
|
1378
|
+
"last_updated" = now()
|
|
1379
|
+
WHERE "processor_id" = p_processor_id
|
|
1380
|
+
AND "last_processed_checkpoint" = split_part(p_check_position, ':', 2)
|
|
1381
|
+
AND "last_processed_checkpoint" NOT LIKE '%:%'
|
|
1382
|
+
AND "partition" = p_partition
|
|
1383
|
+
AND "version" = p_version;
|
|
1384
|
+
ELSE
|
|
1385
|
+
-- old code, stored value already migrated to new format (txid:globalpos)
|
|
1386
|
+
UPDATE "${SQL.plain(processorsTable.name)}"
|
|
1387
|
+
SET
|
|
1388
|
+
"last_processed_checkpoint" = p_position,
|
|
1389
|
+
"last_processed_transaction_id" = p_transaction_id,
|
|
1390
|
+
"last_updated" = now()
|
|
1391
|
+
WHERE "processor_id" = p_processor_id
|
|
1392
|
+
AND split_part("last_processed_checkpoint", ':', 2) = p_check_position
|
|
1393
|
+
AND "last_processed_checkpoint" LIKE '%:%'
|
|
1394
|
+
AND "partition" = p_partition
|
|
1395
|
+
AND "version" = p_version;
|
|
1396
|
+
END IF;
|
|
1397
|
+
|
|
1398
|
+
IF FOUND THEN
|
|
1399
|
+
RETURN 1; -- Successfully updated (mixed-format fallback)
|
|
1400
|
+
END IF;
|
|
1401
|
+
|
|
1271
1402
|
-- Retrieve the current position
|
|
1272
1403
|
SELECT "last_processed_checkpoint" INTO current_position
|
|
1273
1404
|
FROM "${SQL.plain(processorsTable.name)}"
|
|
@@ -2520,42 +2651,158 @@ IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'emt_subscriptions') THEN
|
|
|
2520
2651
|
AND "partition" = p_partition
|
|
2521
2652
|
AND "version" = p_version ;
|
|
2522
2653
|
|
|
2523
|
-
IF current_position = p_position THEN
|
|
2524
|
-
RETURN 0;
|
|
2525
|
-
ELSIF current_position > p_position THEN
|
|
2526
|
-
RETURN 3;
|
|
2527
|
-
ELSE
|
|
2528
|
-
RETURN 2;
|
|
2529
|
-
END IF;
|
|
2654
|
+
IF current_position = p_position THEN
|
|
2655
|
+
RETURN 0;
|
|
2656
|
+
ELSIF current_position > p_position THEN
|
|
2657
|
+
RETURN 3;
|
|
2658
|
+
ELSE
|
|
2659
|
+
RETURN 2;
|
|
2660
|
+
END IF;
|
|
2661
|
+
END IF;
|
|
2662
|
+
|
|
2663
|
+
BEGIN
|
|
2664
|
+
INSERT INTO "emt_processors"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
|
|
2665
|
+
VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
|
|
2666
|
+
RETURN 1;
|
|
2667
|
+
EXCEPTION WHEN unique_violation THEN
|
|
2668
|
+
SELECT "last_processed_checkpoint" INTO current_position
|
|
2669
|
+
FROM "emt_processors"
|
|
2670
|
+
WHERE "processor_id" = p_processor_id
|
|
2671
|
+
AND "partition" = p_partition
|
|
2672
|
+
AND "version" = p_version;
|
|
2673
|
+
|
|
2674
|
+
IF current_position = p_position THEN
|
|
2675
|
+
RETURN 0;
|
|
2676
|
+
ELSE
|
|
2677
|
+
RETURN 2;
|
|
2678
|
+
END IF;
|
|
2679
|
+
END;
|
|
2680
|
+
END;
|
|
2681
|
+
$fn$ LANGUAGE plpgsql;
|
|
2682
|
+
END IF;
|
|
2683
|
+
END $$;
|
|
2684
|
+
`;
|
|
2685
|
+
const migration_0_43_0_cleanupLegacySubscription = sqlMigration("emt:postgresql:eventstore:0.43.0:cleanup-legacy-subscription", [migration_0_43_0_cleanupLegacySubscriptionSQL]);
|
|
2686
|
+
const migration_0_43_0_updateStoreProcessorCheckpointSQL = SQL`
|
|
2687
|
+
CREATE OR REPLACE FUNCTION store_processor_checkpoint(
|
|
2688
|
+
p_processor_id TEXT,
|
|
2689
|
+
p_version BIGINT,
|
|
2690
|
+
p_position TEXT,
|
|
2691
|
+
p_check_position TEXT,
|
|
2692
|
+
p_transaction_id xid8,
|
|
2693
|
+
p_partition TEXT DEFAULT '${SQL.plain(defaultTag)}',
|
|
2694
|
+
p_processor_instance_id TEXT DEFAULT '${SQL.plain(unknownTag$1)}'
|
|
2695
|
+
) RETURNS INT AS $spc$
|
|
2696
|
+
DECLARE
|
|
2697
|
+
current_position TEXT;
|
|
2698
|
+
BEGIN
|
|
2699
|
+
IF p_check_position IS NOT NULL THEN
|
|
2700
|
+
UPDATE "${SQL.plain(processorsTable.name)}"
|
|
2701
|
+
SET
|
|
2702
|
+
"last_processed_checkpoint" = p_position,
|
|
2703
|
+
"last_processed_transaction_id" = p_transaction_id,
|
|
2704
|
+
"last_updated" = now()
|
|
2705
|
+
WHERE "processor_id" = p_processor_id
|
|
2706
|
+
AND "last_processed_checkpoint" = p_check_position
|
|
2707
|
+
AND "partition" = p_partition
|
|
2708
|
+
AND "version" = p_version;
|
|
2709
|
+
|
|
2710
|
+
IF FOUND THEN
|
|
2711
|
+
RETURN 1;
|
|
2712
|
+
END IF;
|
|
2713
|
+
|
|
2714
|
+
-- TODO: Remove once all deployments have run the 0.43.0 migration.
|
|
2715
|
+
-- Handles mixed-format scenarios during blue-green deployment.
|
|
2716
|
+
IF p_check_position LIKE '%:%' THEN
|
|
2717
|
+
-- new code, stored value still in old format (plain globalpos)
|
|
2718
|
+
UPDATE "${SQL.plain(processorsTable.name)}"
|
|
2719
|
+
SET
|
|
2720
|
+
"last_processed_checkpoint" = p_position,
|
|
2721
|
+
"last_processed_transaction_id" = p_transaction_id,
|
|
2722
|
+
"last_updated" = now()
|
|
2723
|
+
WHERE "processor_id" = p_processor_id
|
|
2724
|
+
AND "last_processed_checkpoint" = split_part(p_check_position, ':', 2)
|
|
2725
|
+
AND "last_processed_checkpoint" NOT LIKE '%:%'
|
|
2726
|
+
AND "partition" = p_partition
|
|
2727
|
+
AND "version" = p_version;
|
|
2728
|
+
ELSE
|
|
2729
|
+
-- old code, stored value already migrated to new format (txid:globalpos)
|
|
2730
|
+
UPDATE "${SQL.plain(processorsTable.name)}"
|
|
2731
|
+
SET
|
|
2732
|
+
"last_processed_checkpoint" = p_position,
|
|
2733
|
+
"last_processed_transaction_id" = p_transaction_id,
|
|
2734
|
+
"last_updated" = now()
|
|
2735
|
+
WHERE "processor_id" = p_processor_id
|
|
2736
|
+
AND split_part("last_processed_checkpoint", ':', 2) = p_check_position
|
|
2737
|
+
AND "last_processed_checkpoint" LIKE '%:%'
|
|
2738
|
+
AND "partition" = p_partition
|
|
2739
|
+
AND "version" = p_version;
|
|
2740
|
+
END IF;
|
|
2741
|
+
|
|
2742
|
+
IF FOUND THEN
|
|
2743
|
+
RETURN 1;
|
|
2744
|
+
END IF;
|
|
2745
|
+
|
|
2746
|
+
SELECT "last_processed_checkpoint" INTO current_position
|
|
2747
|
+
FROM "${SQL.plain(processorsTable.name)}"
|
|
2748
|
+
WHERE "processor_id" = p_processor_id
|
|
2749
|
+
AND "partition" = p_partition
|
|
2750
|
+
AND "version" = p_version;
|
|
2751
|
+
|
|
2752
|
+
IF current_position = p_position THEN
|
|
2753
|
+
RETURN 0;
|
|
2754
|
+
ELSIF current_position > p_position THEN
|
|
2755
|
+
RETURN 3;
|
|
2756
|
+
ELSE
|
|
2757
|
+
RETURN 2;
|
|
2530
2758
|
END IF;
|
|
2759
|
+
END IF;
|
|
2531
2760
|
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2761
|
+
BEGIN
|
|
2762
|
+
INSERT INTO "${SQL.plain(processorsTable.name)}"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
|
|
2763
|
+
VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
|
|
2764
|
+
RETURN 1;
|
|
2765
|
+
EXCEPTION WHEN unique_violation THEN
|
|
2766
|
+
SELECT "last_processed_checkpoint" INTO current_position
|
|
2767
|
+
FROM "${SQL.plain(processorsTable.name)}"
|
|
2768
|
+
WHERE "processor_id" = p_processor_id
|
|
2769
|
+
AND "partition" = p_partition
|
|
2770
|
+
AND "version" = p_version;
|
|
2542
2771
|
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
END
|
|
2772
|
+
IF current_position = p_position THEN
|
|
2773
|
+
RETURN 0;
|
|
2774
|
+
ELSIF current_position > p_position THEN
|
|
2775
|
+
RETURN 3;
|
|
2776
|
+
ELSE
|
|
2777
|
+
RETURN 2;
|
|
2778
|
+
END IF;
|
|
2779
|
+
END;
|
|
2780
|
+
END;
|
|
2781
|
+
$spc$ LANGUAGE plpgsql;
|
|
2782
|
+
`;
|
|
2783
|
+
const migration_0_43_0_updateStoreProcessorCheckpoint = sqlMigration("emt:postgresql:eventstore:0.43.0:update-store-processor-checkpoint", [migration_0_43_0_updateStoreProcessorCheckpointSQL]);
|
|
2784
|
+
const migration_0_43_0_upgradeCheckpointFormatSQL = SQL`
|
|
2785
|
+
DO $$
|
|
2786
|
+
BEGIN
|
|
2787
|
+
IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = '${SQL.plain(processorsTable.name)}') THEN
|
|
2788
|
+
UPDATE "${SQL.plain(processorsTable.name)}" p
|
|
2789
|
+
SET last_processed_checkpoint =
|
|
2790
|
+
lpad(m.transaction_id::text, 20, '0') || ':' || p.last_processed_checkpoint
|
|
2791
|
+
FROM "${SQL.plain(messagesTable.name)}" m
|
|
2792
|
+
WHERE m.global_position = p.last_processed_checkpoint::bigint
|
|
2793
|
+
AND p.last_processed_checkpoint NOT LIKE '%:%';
|
|
2794
|
+
END IF;
|
|
2552
2795
|
END $$;
|
|
2553
2796
|
`;
|
|
2554
|
-
const
|
|
2797
|
+
const migration_0_43_0_upgradeCheckpointFormat = sqlMigration("emt:postgresql:eventstore:0.43.0:upgrade-checkpoint-format", [migration_0_43_0_upgradeCheckpointFormatSQL]);
|
|
2555
2798
|
|
|
2556
2799
|
//#endregion
|
|
2557
2800
|
//#region src/eventStore/schema/migrations/0_43_0/index.ts
|
|
2558
|
-
const migrations_0_43_0 = [
|
|
2801
|
+
const migrations_0_43_0 = [
|
|
2802
|
+
migration_0_43_0_cleanupLegacySubscription,
|
|
2803
|
+
migration_0_43_0_updateStoreProcessorCheckpoint,
|
|
2804
|
+
migration_0_43_0_upgradeCheckpointFormat
|
|
2805
|
+
];
|
|
2559
2806
|
|
|
2560
2807
|
//#endregion
|
|
2561
2808
|
//#region src/eventStore/schema/migrations/index.ts
|
|
@@ -2585,14 +2832,42 @@ const pastEventStoreSchemaMigrations = [
|
|
|
2585
2832
|
];
|
|
2586
2833
|
const eventStoreSchemaMigrations = [...pastEventStoreSchemaMigrations, schemaMigration$1];
|
|
2587
2834
|
|
|
2835
|
+
//#endregion
|
|
2836
|
+
//#region src/eventStore/schema/readLastMessageCheckpoint.ts
|
|
2837
|
+
const readLastMessageCheckpoint = async (execute, options) => {
|
|
2838
|
+
const result = await singleOrNull(execute.query(SQL`SELECT transaction_id, global_position
|
|
2839
|
+
FROM ${SQL.identifier(messagesTable.name)}
|
|
2840
|
+
WHERE partition = ${options?.partition ?? defaultTag} AND is_archived = FALSE AND transaction_id < pg_snapshot_xmin(pg_current_snapshot())
|
|
2841
|
+
ORDER BY transaction_id DESC, global_position DESC
|
|
2842
|
+
LIMIT 1`));
|
|
2843
|
+
return { currentCheckpoint: result !== null ? {
|
|
2844
|
+
transactionId: BigInt(result.transaction_id),
|
|
2845
|
+
globalPosition: BigInt(result.global_position)
|
|
2846
|
+
} : null };
|
|
2847
|
+
};
|
|
2848
|
+
|
|
2588
2849
|
//#endregion
|
|
2589
2850
|
//#region src/eventStore/schema/readProcessorCheckpoint.ts
|
|
2851
|
+
const resolveTransactionId = async (execute, rawCheckpoint) => {
|
|
2852
|
+
if (rawCheckpoint.includes(":")) return rawCheckpoint;
|
|
2853
|
+
const globalPosition = BigInt(rawCheckpoint);
|
|
2854
|
+
if (globalPosition === 0n) return PostgreSQLEventStoreCheckpoint.toProcessorCheckpoint(PostgreSQLEventStoreCheckpoint.default);
|
|
2855
|
+
const row = await single(execute.query(SQL`SELECT transaction_id
|
|
2856
|
+
FROM ${SQL.identifier(messagesTable.name)}
|
|
2857
|
+
WHERE global_position = ${globalPosition}
|
|
2858
|
+
LIMIT 1`));
|
|
2859
|
+
return PostgreSQLEventStoreCheckpoint.toProcessorCheckpoint({
|
|
2860
|
+
transactionId: BigInt(row.transaction_id),
|
|
2861
|
+
globalPosition
|
|
2862
|
+
});
|
|
2863
|
+
};
|
|
2590
2864
|
const readProcessorCheckpoint = async (execute, options) => {
|
|
2591
2865
|
const result = await singleOrNull(execute.query(SQL`SELECT last_processed_checkpoint
|
|
2592
2866
|
FROM ${SQL.identifier(processorsTable.name)}
|
|
2593
2867
|
WHERE partition = ${options?.partition ?? defaultTag} AND processor_id = ${options.processorId} AND version = ${options.version ?? 1}
|
|
2594
2868
|
LIMIT 1`));
|
|
2595
|
-
|
|
2869
|
+
if (result === null) return { lastProcessedCheckpoint: null };
|
|
2870
|
+
return { lastProcessedCheckpoint: await resolveTransactionId(execute, result.last_processed_checkpoint) };
|
|
2596
2871
|
};
|
|
2597
2872
|
|
|
2598
2873
|
//#endregion
|
|
@@ -2601,7 +2876,7 @@ const readStream = async (execute, streamId, options) => {
|
|
|
2601
2876
|
const fromCondition = options?.from ? `AND stream_position >= ${options.from}` : "";
|
|
2602
2877
|
const to = Number(options?.to ?? (options?.maxCount ? (options.from ?? 0n) + options.maxCount : NaN));
|
|
2603
2878
|
const toCondition = !isNaN(to) ? `AND stream_position <= ${to}` : "";
|
|
2604
|
-
const events = await mapRows(execute.query(SQL`SELECT stream_id, stream_position, global_position, message_data, message_metadata, message_schema_version, message_type, message_id
|
|
2879
|
+
const events = await mapRows(execute.query(SQL`SELECT stream_id, stream_position, global_position, message_data, message_metadata, message_schema_version, message_type, message_id, transaction_id
|
|
2605
2880
|
FROM ${SQL.identifier(messagesTable.name)}
|
|
2606
2881
|
WHERE stream_id = ${streamId} AND partition = ${options?.partition ?? defaultTag} AND is_archived = FALSE ${SQL.plain(fromCondition)} ${SQL.plain(toCondition)}
|
|
2607
2882
|
ORDER BY stream_position ASC`), (row) => {
|
|
@@ -2610,13 +2885,17 @@ const readStream = async (execute, streamId, options) => {
|
|
|
2610
2885
|
data: row.message_data,
|
|
2611
2886
|
metadata: row.message_metadata
|
|
2612
2887
|
};
|
|
2888
|
+
const globalPosition = PostgreSQLEventStoreCheckpoint.toProcessorCheckpoint({
|
|
2889
|
+
transactionId: BigInt(row.transaction_id),
|
|
2890
|
+
globalPosition: BigInt(row.global_position)
|
|
2891
|
+
});
|
|
2613
2892
|
const metadata = {
|
|
2614
2893
|
..."metadata" in rawEvent ? rawEvent.metadata ?? {} : {},
|
|
2615
2894
|
messageId: row.message_id,
|
|
2616
2895
|
streamName: streamId,
|
|
2617
2896
|
streamPosition: BigInt(row.stream_position),
|
|
2618
|
-
globalPosition
|
|
2619
|
-
checkpoint:
|
|
2897
|
+
globalPosition,
|
|
2898
|
+
checkpoint: globalPosition
|
|
2620
2899
|
};
|
|
2621
2900
|
return upcastRecordedMessage({
|
|
2622
2901
|
...rawEvent,
|
|
@@ -2685,152 +2964,76 @@ const createEventStoreSchema = (connectionString, pool, hooks, options) => {
|
|
|
2685
2964
|
};
|
|
2686
2965
|
|
|
2687
2966
|
//#endregion
|
|
2688
|
-
//#region src/eventStore/
|
|
2689
|
-
const
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
}
|
|
2704
|
-
|
|
2705
|
-
const
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
if (!migrateSchema) {
|
|
2719
|
-
migrateSchema = createEventStoreSchema(connectionString, pool, {
|
|
2720
|
-
onBeforeSchemaCreated: async (context) => {
|
|
2721
|
-
if (options.hooks?.onBeforeSchemaCreated) await options.hooks.onBeforeSchemaCreated(context);
|
|
2722
|
-
},
|
|
2723
|
-
onAfterSchemaCreated: async (context) => {
|
|
2724
|
-
for (const projection of inlineProjections) if (projection.init) await projection.init({
|
|
2725
|
-
version: projection.version ?? 1,
|
|
2726
|
-
status: "active",
|
|
2727
|
-
registrationType: "inline",
|
|
2728
|
-
context: {
|
|
2729
|
-
...context,
|
|
2730
|
-
migrationOptions
|
|
2731
|
-
}
|
|
2732
|
-
});
|
|
2733
|
-
if (options.hooks?.onAfterSchemaCreated) await options.hooks.onAfterSchemaCreated(context);
|
|
2967
|
+
//#region src/eventStore/consumers/messageBatchProcessing/index.ts
|
|
2968
|
+
const DefaultPostgreSQLEventStoreProcessorBatchSize = 100;
|
|
2969
|
+
const DefaultPostgreSQLEventStoreProcessorPullingFrequencyInMs = 50;
|
|
2970
|
+
const postgreSQLEventStoreMessageBatchPuller = ({ executor, batchSize, eachBatch, pullingFrequencyInMs, stopWhen, signal }) => {
|
|
2971
|
+
let isRunning = false;
|
|
2972
|
+
let start;
|
|
2973
|
+
const pullMessages = async (options) => {
|
|
2974
|
+
try {
|
|
2975
|
+
let after;
|
|
2976
|
+
try {
|
|
2977
|
+
after = options.startFrom === "BEGINNING" ? PostgreSQLEventStoreCheckpoint.default : options.startFrom === "END" ? (await readLastMessageCheckpoint(executor)).currentCheckpoint ?? PostgreSQLEventStoreCheckpoint.default : PostgreSQLEventStoreCheckpoint.parse(options.startFrom.lastCheckpoint);
|
|
2978
|
+
} catch (error) {
|
|
2979
|
+
console.log("Error occurred during reading last processor checkpoint:", error);
|
|
2980
|
+
options.started?.reject(error);
|
|
2981
|
+
throw error;
|
|
2982
|
+
}
|
|
2983
|
+
options.started?.resolve();
|
|
2984
|
+
const readMessagesOptions = {
|
|
2985
|
+
after,
|
|
2986
|
+
batchSize
|
|
2987
|
+
};
|
|
2988
|
+
let waitTime = 100;
|
|
2989
|
+
while (isRunning && !signal?.aborted) {
|
|
2990
|
+
const { messages, currentCheckpoint, areMessagesLeft } = await readMessagesBatch(executor, readMessagesOptions);
|
|
2991
|
+
if (messages.length > 0) {
|
|
2992
|
+
const result = await eachBatch(messages);
|
|
2993
|
+
if (result && result.type === "STOP") {
|
|
2994
|
+
isRunning = false;
|
|
2995
|
+
break;
|
|
2996
|
+
}
|
|
2734
2997
|
}
|
|
2735
|
-
|
|
2736
|
-
|
|
2998
|
+
readMessagesOptions.after = currentCheckpoint;
|
|
2999
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
3000
|
+
if (stopWhen?.noMessagesLeft === true && !areMessagesLeft) {
|
|
3001
|
+
console.log(`No messages left to process after reaching checkpoint ${JSONSerializer.serialize(readMessagesOptions.after)}. Stopping the puller.`);
|
|
3002
|
+
isRunning = false;
|
|
3003
|
+
break;
|
|
3004
|
+
}
|
|
3005
|
+
if (!areMessagesLeft) waitTime = Math.min(waitTime * 2, 1e3);
|
|
3006
|
+
else waitTime = pullingFrequencyInMs;
|
|
3007
|
+
}
|
|
3008
|
+
} catch (error) {
|
|
3009
|
+
console.log("Error occurred during message pulling:", error);
|
|
3010
|
+
throw error;
|
|
2737
3011
|
}
|
|
2738
|
-
const result = await migrateSchema;
|
|
2739
|
-
if (migrationOptions?.dryRun) migrateSchema = void 0;
|
|
2740
|
-
return {
|
|
2741
|
-
applied: [],
|
|
2742
|
-
skipped: result.applied.concat(result.skipped)
|
|
2743
|
-
};
|
|
2744
|
-
};
|
|
2745
|
-
const ensureSchemaExists = () => {
|
|
2746
|
-
if (!autoGenerateSchema) return Promise.resolve();
|
|
2747
|
-
return migrate();
|
|
2748
3012
|
};
|
|
2749
|
-
const beforeCommitHook = inlineProjections.length > 0 ? async (events, { transaction }) => handleProjections({
|
|
2750
|
-
projections: inlineProjections,
|
|
2751
|
-
events,
|
|
2752
|
-
...await transactionToPostgreSQLProjectionHandlerContext(connectionString, pool, transaction)
|
|
2753
|
-
}) : void 0;
|
|
2754
3013
|
return {
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
print: () => console.log(SQL.describe(schemaSQL, getFormatter(fromDatabaseDriverType(pool.driverType).databaseType))),
|
|
2758
|
-
migrate,
|
|
2759
|
-
dangerous: { truncate: (truncateOptions) => pool.withTransaction(async (transaction) => {
|
|
2760
|
-
await ensureSchemaExists();
|
|
2761
|
-
await truncateTables(transaction.execute, truncateOptions);
|
|
2762
|
-
if (truncateOptions?.truncateProjections) {
|
|
2763
|
-
const projectionContext = await transactionToPostgreSQLProjectionHandlerContext(connectionString, pool, transaction);
|
|
2764
|
-
for (const projection of options?.projections ?? []) if (projection.projection.truncate) await projection.projection.truncate(projectionContext);
|
|
2765
|
-
}
|
|
2766
|
-
}) }
|
|
2767
|
-
},
|
|
2768
|
-
async aggregateStream(streamName, options) {
|
|
2769
|
-
const { evolve, initialState, read } = options;
|
|
2770
|
-
const expectedStreamVersion = read?.expectedStreamVersion;
|
|
2771
|
-
let state = initialState();
|
|
2772
|
-
const result = await this.readStream(streamName, read);
|
|
2773
|
-
const currentStreamVersion = result.currentStreamVersion;
|
|
2774
|
-
assertExpectedVersionMatchesCurrent(currentStreamVersion, expectedStreamVersion, PostgreSQLEventStoreDefaultStreamVersion);
|
|
2775
|
-
for (const event of result.events) {
|
|
2776
|
-
if (!event) continue;
|
|
2777
|
-
state = evolve(state, event);
|
|
2778
|
-
}
|
|
2779
|
-
return {
|
|
2780
|
-
currentStreamVersion,
|
|
2781
|
-
state,
|
|
2782
|
-
streamExists: result.streamExists
|
|
2783
|
-
};
|
|
2784
|
-
},
|
|
2785
|
-
readStream: async (streamName, readOptions) => {
|
|
2786
|
-
await ensureSchemaExists();
|
|
2787
|
-
return readStream(pool.execute, streamName, {
|
|
2788
|
-
...readOptions,
|
|
2789
|
-
serialization: options.serialization ?? readOptions?.serialization
|
|
2790
|
-
});
|
|
2791
|
-
},
|
|
2792
|
-
appendToStream: async (streamName, events, appendOptions) => {
|
|
2793
|
-
await ensureSchemaExists();
|
|
2794
|
-
const [firstPart, ...rest] = streamName.split("-");
|
|
2795
|
-
const appendResult = await appendToStream(pool, streamName, firstPart && rest.length > 0 ? firstPart : unknownTag, downcastRecordedMessages(events, appendOptions?.schema?.versioning), {
|
|
2796
|
-
...appendOptions,
|
|
2797
|
-
beforeCommitHook
|
|
2798
|
-
});
|
|
2799
|
-
if (!appendResult.success) throw new ExpectedVersionConflictError(-1n, appendOptions?.expectedStreamVersion ?? NO_CONCURRENCY_CHECK);
|
|
2800
|
-
return {
|
|
2801
|
-
nextExpectedStreamVersion: appendResult.nextStreamPosition,
|
|
2802
|
-
lastEventGlobalPosition: appendResult.globalPositions[appendResult.globalPositions.length - 1],
|
|
2803
|
-
createdNewStream: appendResult.nextStreamPosition >= BigInt(events.length)
|
|
2804
|
-
};
|
|
3014
|
+
get isRunning() {
|
|
3015
|
+
return isRunning;
|
|
2805
3016
|
},
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
3017
|
+
start: (options) => {
|
|
3018
|
+
if (isRunning) return start;
|
|
3019
|
+
isRunning = true;
|
|
3020
|
+
start = (async () => {
|
|
3021
|
+
return pullMessages(options);
|
|
3022
|
+
})();
|
|
3023
|
+
return start;
|
|
2809
3024
|
},
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
}),
|
|
2815
|
-
close: () => pool.close(),
|
|
2816
|
-
async withSession(callback) {
|
|
2817
|
-
return await pool.withConnection(async (connection) => {
|
|
2818
|
-
const eventStore = getPostgreSQLEventStore(connectionString, {
|
|
2819
|
-
...options,
|
|
2820
|
-
connectionOptions: { connection },
|
|
2821
|
-
schema: {
|
|
2822
|
-
...options.schema ?? {},
|
|
2823
|
-
autoMigration: "None"
|
|
2824
|
-
}
|
|
2825
|
-
});
|
|
2826
|
-
return ensureSchemaExists().then(() => callback({
|
|
2827
|
-
eventStore,
|
|
2828
|
-
close: () => Promise.resolve()
|
|
2829
|
-
}));
|
|
2830
|
-
});
|
|
3025
|
+
stop: async () => {
|
|
3026
|
+
if (!isRunning) return;
|
|
3027
|
+
isRunning = false;
|
|
3028
|
+
await start;
|
|
2831
3029
|
}
|
|
2832
3030
|
};
|
|
2833
3031
|
};
|
|
3032
|
+
const zipPostgreSQLEventStoreMessageBatchPullerStartFrom = (options) => {
|
|
3033
|
+
if (options.length === 0 || options.some((o) => o === void 0 || o === "BEGINNING")) return "BEGINNING";
|
|
3034
|
+
if (options.every((o) => o === "END")) return "END";
|
|
3035
|
+
return options.filter((o) => o !== void 0 && o !== "BEGINNING" && o !== "END").sort((a, b) => a > b ? 1 : -1)[0];
|
|
3036
|
+
};
|
|
2834
3037
|
|
|
2835
3038
|
//#endregion
|
|
2836
3039
|
//#region src/eventStore/consumers/postgreSQLProcessor.ts
|
|
@@ -3042,6 +3245,7 @@ const postgreSQLEventStoreConsumer = (options) => {
|
|
|
3042
3245
|
let start;
|
|
3043
3246
|
let messagePuller;
|
|
3044
3247
|
const startedAwaiter = asyncAwaiter();
|
|
3248
|
+
const isOwnPool = !options.pool;
|
|
3045
3249
|
const pool = options.pool ? options.pool : dumbo({
|
|
3046
3250
|
connectionString: options.connectionString,
|
|
3047
3251
|
serialization: options.serialization
|
|
@@ -3172,7 +3376,7 @@ const postgreSQLEventStoreConsumer = (options) => {
|
|
|
3172
3376
|
throw error;
|
|
3173
3377
|
}
|
|
3174
3378
|
})));
|
|
3175
|
-
console.log(`Starting message pulling with start position: ${JSONSerializer.serialize(startFrom)}. Waiting for messages...`);
|
|
3379
|
+
console.log(`Starting message pulling with start position: ${JSONSerializer$1.serialize(startFrom)}. Waiting for messages...`);
|
|
3176
3380
|
await messagePuller.start({
|
|
3177
3381
|
startFrom,
|
|
3178
3382
|
started: startedAwaiter
|
|
@@ -3190,7 +3394,7 @@ const postgreSQLEventStoreConsumer = (options) => {
|
|
|
3190
3394
|
stop,
|
|
3191
3395
|
close: async () => {
|
|
3192
3396
|
await stop();
|
|
3193
|
-
await pool.close();
|
|
3397
|
+
if (isOwnPool) await pool.close();
|
|
3194
3398
|
}
|
|
3195
3399
|
};
|
|
3196
3400
|
};
|
|
@@ -3230,5 +3434,5 @@ const rebuildPostgreSQLProjections = (options) => {
|
|
|
3230
3434
|
};
|
|
3231
3435
|
|
|
3232
3436
|
//#endregion
|
|
3233
|
-
export { DefaultPostgreSQLEventStoreProcessorBatchSize, DefaultPostgreSQLEventStoreProcessorPullingFrequencyInMs, DefaultPostgreSQLProcessorLockPolicy, PostgreSQLEventStoreDefaultStreamVersion, PostgreSQLProjectionSpec, activateProjection, activateProjectionSQL, addDefaultPartitionSQL, addModuleForAllTenantsSQL, addModuleSQL, addPartitionSQL, addTablePartitions, addTenantForAllModulesSQL, addTenantSQL, appendToStream, appendToStreamSQL, assertSQLQueryResultMatches, callActivateProjection, callAppendToStream, callDeactivateProjection, callRegisterProjection, callReleaseProcessorLock, callStoreProcessorCheckpoint, callTryAcquireProcessorLock, callTryAcquireProjectionLock, createEventStoreSchema, currentPostgreSQLEventStoreSchemaVersion, deactivateProjection, deactivateProjectionSQL, defaultPostgreSQLOptions, documentDoesNotExist, documentExists, documentMatchingExists, documentsAreTheSame, documentsMatchingHaveCount, eventInStream, eventStoreSchemaMigrations, eventsInStream, expectPongoDocuments, expectSQL, getPostgreSQLEventStore, handleProjections, messagesTableSQL, newEventsInStream, pastEventStoreSchemaMigrations, pongoMultiStreamProjection, pongoProjection, pongoSingleStreamProjection, postgreSQLCheckpointer, postgreSQLEventStoreConsumer, postgreSQLEventStoreMessageBatchPuller, postgreSQLProcessorLock, postgreSQLProjection, postgreSQLProjectionLock, postgreSQLProjector, postgreSQLRawBatchSQLProjection, postgreSQLRawSQLProjection, postgreSQLReactor, postgreSQLWorkflowProcessor, processorsTableSQL, projectionsTableSQL,
|
|
3437
|
+
export { DefaultPostgreSQLEventStoreProcessorBatchSize, DefaultPostgreSQLEventStoreProcessorPullingFrequencyInMs, DefaultPostgreSQLProcessorLockPolicy, PostgreSQLEventStoreCheckpoint, PostgreSQLEventStoreDefaultStreamVersion, PostgreSQLProjectionSpec, activateProjection, activateProjectionSQL, addDefaultPartitionSQL, addModuleForAllTenantsSQL, addModuleSQL, addPartitionSQL, addTablePartitions, addTenantForAllModulesSQL, addTenantSQL, appendToStream, appendToStreamSQL, appentToStreamRaw, assertSQLQueryResultMatches, callActivateProjection, callAppendToStream, callDeactivateProjection, callRegisterProjection, callReleaseProcessorLock, callStoreProcessorCheckpoint, callTryAcquireProcessorLock, callTryAcquireProjectionLock, createEventStoreSchema, currentPostgreSQLEventStoreSchemaVersion, deactivateProjection, deactivateProjectionSQL, defaultPostgreSQLOptions, documentDoesNotExist, documentExists, documentMatchingExists, documentsAreTheSame, documentsMatchingHaveCount, eventInStream, eventStoreSchemaMigrations, eventsInStream, expectPongoDocuments, expectSQL, getPostgreSQLEventStore, handleProjections, messagesTableSQL, newEventsInStream, pastEventStoreSchemaMigrations, pongoMultiStreamProjection, pongoProjection, pongoSingleStreamProjection, postgreSQLCheckpointer, postgreSQLEventStoreConsumer, postgreSQLEventStoreMessageBatchPuller, postgreSQLProcessorLock, postgreSQLProjection, postgreSQLProjectionLock, postgreSQLProjector, postgreSQLRawBatchSQLProjection, postgreSQLRawSQLProjection, postgreSQLReactor, postgreSQLWorkflowProcessor, processorsTableSQL, projectionsTableSQL, readLastMessageCheckpoint, readMessagesBatch, readProcessorCheckpoint, readProjectionInfo, readStream, rebuildPostgreSQLProjections, registerProjection, registerProjectionSQL, releaseProcessorLockSQL, sanitizeNameSQL, schemaMigration, schemaSQL, storeProcessorCheckpoint, storeSubscriptionCheckpointSQL, streamExists, streamsTableSQL, toProcessorLockKey, toProjectionLockKey, transactionToPostgreSQLProjectionHandlerContext, tryAcquireProcessorLockSQL, tryAcquireProjectionLockSQL, zipPostgreSQLEventStoreMessageBatchPullerStartFrom };
|
|
3234
3438
|
//# sourceMappingURL=index.js.map
|