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