@event-driven-io/emmett-postgresql 0.43.0-apha.2 → 0.43.0-beta.10

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