@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/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/projections/postgresProjectionSpec.ts
869
- const PostgreSQLProjectionSpec = { for: (options) => {
870
- {
871
- const { projection, ...restOptions } = options;
872
- const dumboOptions = {
873
- ...restOptions,
874
- serialization: projection.serialization
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/projections/postgreSQLProjection.ts
982
- const transactionToPostgreSQLProjectionHandlerContext = async (connectionString, pool, transaction) => ({
983
- execute: transaction.execute,
984
- connection: {
985
- connectionString,
986
- client: await transaction.connection.open(),
987
- transaction,
988
- pool
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 = (pool, streamName, streamType, messages, options) => pool.withTransaction(async (transaction) => {
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 appendEventsRaw(execute, streamName, streamType, messagesToAppend, { expectedStreamVersion });
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
- transactionId: transaction_id
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 appendEventsRaw = (execute, streamId, streamType, messages, options) => (0, _event_driven_io_dumbo.single)(execute.command(callAppendToStream({
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
- BEGIN
2534
- INSERT INTO "emt_processors"("processor_id", "version", "last_processed_checkpoint", "partition", "last_processed_transaction_id", "created_at", "last_updated")
2535
- VALUES (p_processor_id, p_version, p_position, p_partition, p_transaction_id, now(), now());
2536
- RETURN 1;
2537
- EXCEPTION WHEN unique_violation THEN
2538
- SELECT "last_processed_checkpoint" INTO current_position
2539
- FROM "emt_processors"
2540
- WHERE "processor_id" = p_processor_id
2541
- AND "partition" = p_partition
2542
- AND "version" = p_version;
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
- IF current_position = p_position THEN
2545
- RETURN 0;
2546
- ELSE
2547
- RETURN 2;
2548
- END IF;
2549
- END;
2550
- END;
2551
- $fn$ LANGUAGE plpgsql;
2552
- END IF;
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 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]);
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 = [migration_0_43_0_cleanupLegacySubscription];
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
- return { lastProcessedCheckpoint: result !== null ? result.last_processed_checkpoint : null };
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: BigInt(row.global_position),
2620
- checkpoint: (0, _event_driven_io_emmett.bigIntProcessorCheckpoint)(BigInt(row.global_position))
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/schema/truncateTables.ts
2690
- const truncateTables = async (execute, options) => {
2691
- await execute.command(_event_driven_io_dumbo.SQL`TRUNCATE TABLE
2692
- ${_event_driven_io_dumbo.SQL.identifier(streamsTable.name)},
2693
- ${_event_driven_io_dumbo.SQL.identifier(messagesTable.name)},
2694
- ${_event_driven_io_dumbo.SQL.identifier(processorsTable.name)},
2695
- ${_event_driven_io_dumbo.SQL.identifier(projectionsTable.name)}
2696
- CASCADE${_event_driven_io_dumbo.SQL.plain(options?.resetSequences ? "; ALTER SEQUENCE emt_global_message_position RESTART WITH 1" : "")};`);
2697
- };
2698
-
2699
- //#endregion
2700
- //#region src/eventStore/postgreSQLEventStore.ts
2701
- const defaultPostgreSQLOptions = {
2702
- projections: [],
2703
- schema: { autoMigration: "CreateOrUpdate" }
2704
- };
2705
- const PostgreSQLEventStoreDefaultStreamVersion = 0n;
2706
- const getPostgreSQLEventStore = (connectionString, options = defaultPostgreSQLOptions) => {
2707
- const poolOptions = {
2708
- connectionString,
2709
- ...options.connectionOptions ? options.connectionOptions : {}
2710
- };
2711
- const pool = "dumbo" in poolOptions ? poolOptions.dumbo : (0, _event_driven_io_dumbo.dumbo)({
2712
- ...poolOptions,
2713
- serialization: options.serialization
2714
- });
2715
- let migrateSchema = void 0;
2716
- const autoGenerateSchema = options.schema?.autoMigration === void 0 || options.schema?.autoMigration !== "None";
2717
- const inlineProjections = (options.projections ?? []).filter(({ type }) => type === "inline").map(({ projection }) => projection);
2718
- const migrate = async (migrationOptions) => {
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
- }, migrationOptions);
2737
- return migrateSchema;
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
- schema: {
2757
- sql: () => _event_driven_io_dumbo.SQL.describe(schemaSQL, (0, _event_driven_io_dumbo.getFormatter)((0, _event_driven_io_dumbo.fromDatabaseDriverType)(pool.driverType).databaseType)),
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
- streamExists: async (streamName, options) => {
2808
- await ensureSchemaExists();
2809
- return streamExists(pool.execute, streamName, options);
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
- consumer: (options) => postgreSQLEventStoreConsumer({
2812
- ...options ?? {},
2813
- pool,
2814
- connectionString
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.readLastMessageGlobalPosition = readLastMessageGlobalPosition;
3501
+ exports.readLastMessageCheckpoint = readLastMessageCheckpoint;
3296
3502
  exports.readMessagesBatch = readMessagesBatch;
3297
3503
  exports.readProcessorCheckpoint = readProcessorCheckpoint;
3298
3504
  exports.readProjectionInfo = readProjectionInfo;