@reventlessdev/reventless-aws 3.0.0-alpha.174 → 3.0.0-alpha.176

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/package.json +10 -10
  3. package/src/Platform.res +115 -31
  4. package/src/Platform.res.mjs +217 -44
  5. package/src/adapter/DcbEventLog/DcbBackend.res +2 -2
  6. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res +4 -1
  7. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream.res.mjs +1 -1
  8. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res +14 -4
  9. package/src/adapter/EventCollector/EventCollectorChannel_DynamoDbStream_Runtime.res.mjs +19 -14
  10. package/src/adapter/EventLog/EventLogBackend.res +82 -0
  11. package/src/adapter/EventLog/EventLogBackend.res.mjs +53 -0
  12. package/src/adapter/EventLog/EventLogStorage.res +15 -0
  13. package/src/adapter/EventLog/EventLogStorage.res.mjs +20 -1
  14. package/src/adapter/EventLog/EventLogStorage_Postgres.res +37 -0
  15. package/src/adapter/EventLog/EventLogStorage_Postgres.res.mjs +21 -0
  16. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res +26 -11
  17. package/src/adapter/Postgres/PgChangeFeedRelay_Builder.res.mjs +15 -6
  18. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +78 -7
  19. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +30 -2
  20. package/src/adapter/Postgres/PgProjectionFeed.res +100 -0
  21. package/src/adapter/Postgres/PgProjectionFeed.res.mjs +54 -0
  22. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res +192 -0
  23. package/src/adapter/QueryDb/PgQueryResolver_Lambda.res.mjs +141 -0
  24. package/src/adapter/QueryDb/QueryDbBackend.res +52 -0
  25. package/src/adapter/QueryDb/QueryDbBackend.res.mjs +44 -0
  26. package/src/adapter/QueryDb/QueryDbResolvers.res +48 -0
  27. package/src/adapter/QueryDb/QueryDbResolvers.res.mjs +17 -1
  28. package/src/adapter/QueryDb/QueryDbStorage.res +44 -0
  29. package/src/adapter/QueryDb/QueryDbStorage.res.mjs +34 -1
  30. package/src/adapter/QueryDb/QueryDbStorage_Postgres.res +43 -0
  31. package/src/adapter/QueryDb/QueryDbStorage_Postgres.res.mjs +21 -0
  32. package/src/adapter/QueryDb/QueryDbStorage_Postgres_Runtime.res +19 -0
  33. package/src/adapter/QueryDb/QueryDbStorage_Postgres_Runtime.res.mjs +15 -0
  34. package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res +12 -1
  35. package/src/adapter/QueryEngine/QueryEngine_DynamoDb.res.mjs +2 -1
  36. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res +83 -3
  37. package/src/adapter/Runtime/AggregateRuntime_Builder_Single.res.mjs +46 -3
  38. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res +83 -3
  39. package/src/adapter/Runtime/AggregateRuntime_Builder_Single_Async.res.mjs +48 -3
  40. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res +130 -5
  41. package/src/adapter/Runtime/EventCollectorRuntime_Builder_Single.res.mjs +66 -6
  42. package/src/adapter/Runtime/PgChangeFeedRelayEntryPoint.mjs +15 -10
  43. package/src/adapter/Runtime/ReadModelEntryPoint.mjs +32 -15
  44. package/src/adapter/Runtime/StateViewSliceEntryPoint.mjs +26 -12
  45. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res +138 -8
  46. package/src/adapter/Runtime/StateViewSliceRuntime_Builder_Single.res.mjs +76 -11
  47. package/src/components/Aggregate_Builder_Single.res +12 -5
  48. package/src/components/Aggregate_Builder_Single.res.mjs +12 -6
  49. package/src/components/Aggregate_Builder_Single_Async.res +12 -5
  50. package/src/components/Aggregate_Builder_Single_Async.res.mjs +12 -6
  51. package/src/components/ReadModel_Builder_NoResolver.res.mjs +1 -1
  52. package/src/components/ReadModel_Builder_NoResolver_Stream.res +13 -4
  53. package/src/components/ReadModel_Builder_NoResolver_Stream.res.mjs +14 -9
  54. package/src/components/ReadModel_Builder_Single.res +14 -5
  55. package/src/components/ReadModel_Builder_Single.res.mjs +15 -12
  56. package/src/components/ReadModel_Builder_Single_Stream.res +14 -5
  57. package/src/components/ReadModel_Builder_Single_Stream.res.mjs +15 -12
  58. package/src/components/StateViewSlice_Builder.res +10 -4
  59. package/src/components/StateViewSlice_Builder.res.mjs +8 -10
  60. package/src/components/StateViewSlice_Builder_Stream.res +10 -4
  61. package/src/components/StateViewSlice_Builder_Stream.res.mjs +8 -10
  62. package/src/plugin/runtime/PluginRuntime_Builder.res +12 -0
  63. package/src/plugin/runtime/PluginRuntime_Builder.res.mjs +4 -0
  64. package/tests/EventCollectorChannelStreamRuntimeTest.res +55 -0
  65. package/tests/EventCollectorChannelStreamRuntimeTest.res.mjs +78 -0
  66. package/tests/PgChangeFeedRelay_IntegrationTest.res +259 -0
  67. package/tests/PgChangeFeedRelay_IntegrationTest.res.mjs +238 -0
  68. package/tests/PgPipeline_IntegrationTest.res +208 -0
  69. package/tests/PgPipeline_IntegrationTest.res.mjs +260 -0
  70. package/tests/PgQueryResolver_LambdaTest.res +206 -0
  71. package/tests/PgQueryResolver_LambdaTest.res.mjs +259 -0
@@ -0,0 +1,259 @@
1
+ // Live-Postgres integration suite for the change-feed relay (B2.4).
2
+ //
3
+ // Skipped unless PG_URL is set, so the default `pnpm test` stays dependency-free.
4
+ // Run against a real database with e.g.:
5
+ // PG_URL=postgres://postgres:postgres@localhost:5432/postgres pnpm test
6
+ //
7
+ // Exercises the deployed propagation path end-to-end minus AWS: DCB events are
8
+ // appended to a real `dcb_event` log (classic events to `event_log`), then
9
+ // `PgChangeFeedRelay_Runtime.relayWithPool` / `relayClassicWithPool` drains the
10
+ // feed, transforms each event into the EventCollector `{id, meta, event}` body,
11
+ // and hands the batch to an injected `sendBatch` (standing in for the SQS
12
+ // SendMessage the entry point wires). Asserts the emitted bodies, the partition-tag
13
+ // `id` derivation, and the checkpoints (a second drain sees nothing new; per-log
14
+ // subscribers stay isolated).
15
+
16
+ open JestGlobals
17
+ open ReventlessCore
18
+ open Reventless
19
+
20
+ @val external processEnv: dict<string> = "process.env"
21
+ let opts: Pulumi.CustomResourceOptions.t = {}
22
+
23
+ let jsonObj = pairs => JSON.Encode.object(Dict.fromArray(pairs))
24
+
25
+ let stored = (eventType, tags, data): DcbEventLog_Adapter.rawStoredEvent => {
26
+ eventType,
27
+ data,
28
+ tags,
29
+ meta: ReventlessCore.Message.generateMeta(~service="relay-it"),
30
+ }
31
+
32
+ // Collect every JSON body handed to sendBatch across all drain pages.
33
+ let capturingSendBatch = sink => async jsons => sink := sink.contents->Array.concat(jsons)
34
+
35
+ let idOf = json =>
36
+ json->JSON.Decode.object->Option.flatMap(o => o->Dict.get("id"))->Option.flatMap(JSON.Decode.string)
37
+
38
+ switch processEnv->Dict.get("PG_URL") {
39
+ | None =>
40
+ testSync("Postgres relay integration (skipped — set PG_URL to run)", () =>
41
+ expect(true)->toBe(true)
42
+ )
43
+ | Some(url) =>
44
+ let pool = ReventlessPostgres.PgDriver.makePool({connectionString: url})
45
+
46
+ let makeLog = name =>
47
+ ReventlessPostgres.DcbEventLogStorage_Postgres.makeStorage(
48
+ ~pool,
49
+ ~name,
50
+ ~indexes=[],
51
+ ~partitionTag=DcbTag.Simple({key: "orderId"}),
52
+ ~opts,
53
+ )
54
+
55
+ beforeAllAsync(async () => {
56
+ await ReventlessPostgres.PgSchema.ensureSchema(pool)
57
+ await ReventlessPostgres.PgSchema.truncateAll(pool)
58
+ })
59
+ afterAll(() => {
60
+ let _ = pool->ReventlessPostgres.PgDriver.endPool
61
+ })
62
+
63
+ describe("PgChangeFeedRelay end-to-end (append → drain → EventCollector bodies)", () => {
64
+ testPromise("relays each appended DCB event as an {id, meta, event} body", async () => {
65
+ let (_n, ops, _s) = makeLog("relay-basic")
66
+ let mk = i =>
67
+ stored("OrderPlaced", [{key: "orderId", value: "o-1"}], jsonObj([("i", JSON.Encode.int(i))]))
68
+ let _ = await ops.append([mk(0), mk(1), mk(2)])
69
+
70
+ let sink = ref([])
71
+ let processed = await PgChangeFeedRelay_Runtime.relayWithPool(
72
+ ~pool,
73
+ ~logName="relay-basic",
74
+ ~subscriber="relay-it",
75
+ ~sendBatch=capturingSendBatch(sink),
76
+ )
77
+
78
+ expect(processed)->toBe(3)
79
+ expect(sink.contents->Array.length)->toBe(3)
80
+
81
+ // Every body carries the derived id, the event type, and the producing service.
82
+ let first = sink.contents->Array.getUnsafe(0)
83
+ expect(idOf(first))->toEqual(Some("orderId:o-1"))
84
+ let obj = first->JSON.Decode.object->Option.getOrThrow
85
+ expect(obj->Dict.get("event")->Option.getOrThrow->JSON.stringify->String.includes("OrderPlaced"))->toBe(true)
86
+ let metaObj = obj->Dict.get("meta")->Option.flatMap(JSON.Decode.object)->Option.getOrThrow
87
+ expect(metaObj->Dict.get("service"))->toEqual(Some(JSON.Encode.string("relay-it")))
88
+ })
89
+
90
+ testPromise("checkpoints — a second drain from the same subscriber sees nothing new", async () => {
91
+ let (_n, ops, _s) = makeLog("relay-checkpoint")
92
+ let mk = i => stored("Fed", [{key: "orderId", value: "f"}], jsonObj([("i", JSON.Encode.int(i))]))
93
+ let _ = await ops.append([mk(0), mk(1)])
94
+
95
+ let sink = ref([])
96
+ let first = await PgChangeFeedRelay_Runtime.relayWithPool(
97
+ ~pool,
98
+ ~logName="relay-checkpoint",
99
+ ~subscriber="relay-it",
100
+ ~sendBatch=capturingSendBatch(sink),
101
+ )
102
+ expect(first)->toBe(2)
103
+
104
+ let second = await PgChangeFeedRelay_Runtime.relayWithPool(
105
+ ~pool,
106
+ ~logName="relay-checkpoint",
107
+ ~subscriber="relay-it",
108
+ ~sendBatch=capturingSendBatch(sink),
109
+ )
110
+ expect(second)->toBe(0)
111
+ // No extra bodies were emitted on the second pass.
112
+ expect(sink.contents->Array.length)->toBe(2)
113
+ })
114
+
115
+ testPromise("classic: relays each appended event_log event as an {id, meta, event} body", async () => {
116
+ let (_n, ops, _s) =
117
+ ReventlessPostgres.EventLogStorage_Postgres.makeStorage(
118
+ ~pool,
119
+ ~name="ClassicRelayBasicEventLog",
120
+ ~opts=(),
121
+ )
122
+ // The flat on-disk item the classic append stores verbatim — same shape the
123
+ // DynamoDB path puts (id, position, event, data, decomposed meta).
124
+ let flatItem = (~id, ~seq, ~eventType, ~data) =>
125
+ [
126
+ ("id", JSON.Encode.string(id)),
127
+ ("position", JSON.Encode.string(seq->Int.toString->String.padStart(9, "0"))),
128
+ ("event", JSON.Encode.string(eventType)),
129
+ ("data", data),
130
+ ]
131
+ ->Array.concat(
132
+ ReventlessCore.Message.generateMeta(~service="relay-it")->ReventlessCore.Message.decomposeMeta,
133
+ )
134
+ ->Dict.fromArray
135
+ ->JSON.Encode.object
136
+ let _ = await ops.append(
137
+ 0,
138
+ "agg-1",
139
+ [
140
+ flatItem(~id="agg-1", ~seq=0, ~eventType="NameUpdated", ~data=jsonObj([("n", JSON.Encode.int(0))])),
141
+ flatItem(~id="agg-1", ~seq=1, ~eventType="NameUpdated", ~data=jsonObj([("n", JSON.Encode.int(1))])),
142
+ ],
143
+ )
144
+
145
+ let sink = ref([])
146
+ let processed = await PgChangeFeedRelay_Runtime.relayClassicWithPool(
147
+ ~pool,
148
+ ~logName="ClassicRelayBasicEventLog",
149
+ ~subscriber="relay-it:ClassicRelayBasicEventLog",
150
+ ~sendBatch=capturingSendBatch(sink),
151
+ )
152
+
153
+ expect(processed)->toBe(2)
154
+ expect(sink.contents->Array.length)->toBe(2)
155
+ let first = sink.contents->Array.getUnsafe(0)
156
+ expect(idOf(first))->toEqual(Some("agg-1"))
157
+ let obj = first->JSON.Decode.object->Option.getOrThrow
158
+ expect(
159
+ obj->Dict.get("event")->Option.getOrThrow->JSON.stringify->String.includes("NameUpdated"),
160
+ )->toBe(true)
161
+ let metaObj = obj->Dict.get("meta")->Option.flatMap(JSON.Decode.object)->Option.getOrThrow
162
+ expect(metaObj->Dict.get("service"))->toEqual(Some(JSON.Encode.string("relay-it")))
163
+
164
+ // Checkpointed: a second drain from the same subscriber sees nothing new.
165
+ let second = await PgChangeFeedRelay_Runtime.relayClassicWithPool(
166
+ ~pool,
167
+ ~logName="ClassicRelayBasicEventLog",
168
+ ~subscriber="relay-it:ClassicRelayBasicEventLog",
169
+ ~sendBatch=capturingSendBatch(sink),
170
+ )
171
+ expect(second)->toBe(0)
172
+ expect(sink.contents->Array.length)->toBe(2)
173
+ })
174
+
175
+ testPromise("classic: per-log subscribers keep checkpoints isolated across logs", async () => {
176
+ // event_log_subscription keys by subscriber alone. With a SHARED subscriber,
177
+ // draining log A after log B's events were appended would save a cursor past
178
+ // B's rows and silently skip them — the clobber the per-log subscriber fixes.
179
+ let mkOps = name => {
180
+ let (_n, ops, _s) =
181
+ ReventlessPostgres.EventLogStorage_Postgres.makeStorage(~pool, ~name, ~opts=())
182
+ ops
183
+ }
184
+ let opsB = mkOps("ClassicRelayIsoBEventLog")
185
+ let opsA = mkOps("ClassicRelayIsoAEventLog")
186
+ let meta = () =>
187
+ ReventlessCore.Message.generateMeta(~service="relay-it")->ReventlessCore.Message.decomposeMeta
188
+ let item = (id, eventType) =>
189
+ [
190
+ ("id", JSON.Encode.string(id)),
191
+ ("position", JSON.Encode.string("000000000")),
192
+ ("event", JSON.Encode.string(eventType)),
193
+ ("data", jsonObj([])),
194
+ ]
195
+ ->Array.concat(meta())
196
+ ->Dict.fromArray
197
+ ->JSON.Encode.object
198
+ // B's event commits FIRST (lower global_seq), then A's.
199
+ let _ = await opsB.append(0, "b-1", [item("b-1", "BHappened")])
200
+ let _ = await opsA.append(0, "a-1", [item("a-1", "AHappened")])
201
+
202
+ // Drain A first — its checkpoint lands PAST B's rows in global_seq order.
203
+ let sinkA = ref([])
204
+ let drainedA = await PgChangeFeedRelay_Runtime.relayClassicWithPool(
205
+ ~pool,
206
+ ~logName="ClassicRelayIsoAEventLog",
207
+ ~subscriber="relay-it:ClassicRelayIsoAEventLog",
208
+ ~sendBatch=capturingSendBatch(sinkA),
209
+ )
210
+ expect(drainedA)->toBe(1)
211
+
212
+ // B still sees its event because its subscriber has its own checkpoint.
213
+ let sinkB = ref([])
214
+ let drainedB = await PgChangeFeedRelay_Runtime.relayClassicWithPool(
215
+ ~pool,
216
+ ~logName="ClassicRelayIsoBEventLog",
217
+ ~subscriber="relay-it:ClassicRelayIsoBEventLog",
218
+ ~sendBatch=capturingSendBatch(sinkB),
219
+ )
220
+ expect(drainedB)->toBe(1)
221
+ expect(idOf(sinkB.contents->Array.getUnsafe(0)))->toEqual(Some("b-1"))
222
+ })
223
+
224
+ testPromise("partitionTag pins the id to its tag on a multi-tag event", async () => {
225
+ let (_n, ops, _s) =
226
+ ReventlessPostgres.DcbEventLogStorage_Postgres.makeStorage(
227
+ ~pool,
228
+ ~name="relay-pt",
229
+ ~indexes=[],
230
+ ~partitionTag=DcbTag.Simple({key: "customerId"}),
231
+ ~opts,
232
+ )
233
+ let ev =
234
+ stored(
235
+ "OrderPlaced",
236
+ [{key: "orderId", value: "o-9"}, {key: "customerId", value: "c-9"}],
237
+ jsonObj([("n", JSON.Encode.int(1))]),
238
+ )
239
+ let _ = await ops.append([ev])
240
+
241
+ // The relay receives the sury-encoded partition tag exactly as the builder emits it.
242
+ let partitionTagJson =
243
+ DcbTag.Simple({key: "customerId"})->S.reverseConvertToJsonOrThrow(
244
+ Reventless.DcbTag.derivedPartitionTagSchema,
245
+ )
246
+
247
+ let sink = ref([])
248
+ let _ = await PgChangeFeedRelay_Runtime.relayWithPool(
249
+ ~pool,
250
+ ~logName="relay-pt",
251
+ ~subscriber="relay-it",
252
+ ~partitionTagJson,
253
+ ~sendBatch=capturingSendBatch(sink),
254
+ )
255
+ // Without the tag the id would be "orderId:o-9" (first tag); the tag pins it.
256
+ expect(idOf(sink.contents->Array.getUnsafe(0)))->toEqual(Some("customerId:c-9"))
257
+ })
258
+ })
259
+ }
@@ -0,0 +1,238 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as S from "sury/src/S.res.mjs";
4
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
6
+ import * as DcbTag$Reventless from "@reventlessdev/reventless-spec/src/components/DcbTag.res.mjs";
7
+ import * as Message$ReventlessCore from "@reventlessdev/reventless-core/src/Message.res.mjs";
8
+ import * as PgDriver$ReventlessPostgres from "@reventlessdev/reventless-postgres/src/PgDriver.res.mjs";
9
+ import * as PgSchema$ReventlessPostgres from "@reventlessdev/reventless-postgres/src/PgSchema.res.mjs";
10
+ import * as PgChangeFeedRelay_Runtime$ReventlessAws from "../src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs";
11
+ import * as EventLogStorage_Postgres$ReventlessPostgres from "@reventlessdev/reventless-postgres/src/EventLogStorage_Postgres.res.mjs";
12
+ import * as DcbEventLogStorage_Postgres$ReventlessPostgres from "@reventlessdev/reventless-postgres/src/DcbEventLogStorage_Postgres.res.mjs";
13
+
14
+ let opts = {};
15
+
16
+ function jsonObj(pairs) {
17
+ return Object.fromEntries(pairs);
18
+ }
19
+
20
+ function stored(eventType, tags, data) {
21
+ return {
22
+ eventType: eventType,
23
+ data: data,
24
+ tags: tags,
25
+ meta: Message$ReventlessCore.generateMeta("relay-it", undefined, undefined, undefined, undefined, undefined, undefined, undefined)
26
+ };
27
+ }
28
+
29
+ function capturingSendBatch(sink) {
30
+ return async jsons => {
31
+ sink.contents = sink.contents.concat(jsons);
32
+ };
33
+ }
34
+
35
+ function idOf(json) {
36
+ return Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(json), o => o["id"]), Stdlib_JSON.Decode.string);
37
+ }
38
+
39
+ let url = process.env["PG_URL"];
40
+
41
+ if (url !== undefined) {
42
+ let pool = PgDriver$ReventlessPostgres.makePool({
43
+ connectionString: url
44
+ });
45
+ let makeLog = name => DcbEventLogStorage_Postgres$ReventlessPostgres.makeStorage(pool, name, [], {
46
+ TAG: "Simple",
47
+ _0: {
48
+ key: "orderId"
49
+ }
50
+ }, undefined, opts, undefined, undefined);
51
+ globalThis.beforeAll(async () => {
52
+ await PgSchema$ReventlessPostgres.ensureSchema(pool);
53
+ return await PgSchema$ReventlessPostgres.truncateAll(pool);
54
+ });
55
+ globalThis.afterAll(() => {
56
+ PgDriver$ReventlessPostgres.endPool(pool);
57
+ });
58
+ globalThis.describe("PgChangeFeedRelay end-to-end (append → drain → EventCollector bodies)", () => {
59
+ globalThis.test("relays each appended DCB event as an {id, meta, event} body", async () => {
60
+ let match = makeLog("relay-basic");
61
+ let mk = i => stored("OrderPlaced", [{
62
+ key: "orderId",
63
+ value: "o-1"
64
+ }], Object.fromEntries([[
65
+ "i",
66
+ i
67
+ ]]));
68
+ await match[1].append([
69
+ mk(0),
70
+ mk(1),
71
+ mk(2)
72
+ ], undefined);
73
+ let sink = {
74
+ contents: []
75
+ };
76
+ let processed = await PgChangeFeedRelay_Runtime$ReventlessAws.relayWithPool(pool, "relay-basic", "relay-it", undefined, capturingSendBatch(sink));
77
+ globalThis.expect(processed).toBe(3);
78
+ globalThis.expect(sink.contents.length).toBe(3);
79
+ let first = sink.contents[0];
80
+ globalThis.expect(idOf(first)).toEqual("orderId:o-1");
81
+ let obj = Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.object(first), undefined);
82
+ globalThis.expect(JSON.stringify(Stdlib_Option.getOrThrow(obj["event"], undefined)).includes("OrderPlaced")).toBe(true);
83
+ let metaObj = Stdlib_Option.getOrThrow(Stdlib_Option.flatMap(obj["meta"], Stdlib_JSON.Decode.object), undefined);
84
+ globalThis.expect(metaObj["service"]).toEqual("relay-it");
85
+ });
86
+ globalThis.test("checkpoints — a second drain from the same subscriber sees nothing new", async () => {
87
+ let match = makeLog("relay-checkpoint");
88
+ let mk = i => stored("Fed", [{
89
+ key: "orderId",
90
+ value: "f"
91
+ }], Object.fromEntries([[
92
+ "i",
93
+ i
94
+ ]]));
95
+ await match[1].append([
96
+ mk(0),
97
+ mk(1)
98
+ ], undefined);
99
+ let sink = {
100
+ contents: []
101
+ };
102
+ let first = await PgChangeFeedRelay_Runtime$ReventlessAws.relayWithPool(pool, "relay-checkpoint", "relay-it", undefined, capturingSendBatch(sink));
103
+ globalThis.expect(first).toBe(2);
104
+ let second = await PgChangeFeedRelay_Runtime$ReventlessAws.relayWithPool(pool, "relay-checkpoint", "relay-it", undefined, capturingSendBatch(sink));
105
+ globalThis.expect(second).toBe(0);
106
+ globalThis.expect(sink.contents.length).toBe(2);
107
+ });
108
+ globalThis.test("classic: relays each appended event_log event as an {id, meta, event} body", async () => {
109
+ let match = EventLogStorage_Postgres$ReventlessPostgres.makeStorage(pool, "ClassicRelayBasicEventLog", undefined, undefined);
110
+ let flatItem = (id, seq, eventType, data) => Object.fromEntries([
111
+ [
112
+ "id",
113
+ id
114
+ ],
115
+ [
116
+ "position",
117
+ seq.toString().padStart(9, "0")
118
+ ],
119
+ [
120
+ "event",
121
+ eventType
122
+ ],
123
+ [
124
+ "data",
125
+ data
126
+ ]
127
+ ].concat(Message$ReventlessCore.decomposeMeta(Message$ReventlessCore.generateMeta("relay-it", undefined, undefined, undefined, undefined, undefined, undefined, undefined))));
128
+ await match[1].append(0, "agg-1", [
129
+ flatItem("agg-1", 0, "NameUpdated", Object.fromEntries([[
130
+ "n",
131
+ 0
132
+ ]])),
133
+ flatItem("agg-1", 1, "NameUpdated", Object.fromEntries([[
134
+ "n",
135
+ 1
136
+ ]]))
137
+ ]);
138
+ let sink = {
139
+ contents: []
140
+ };
141
+ let processed = await PgChangeFeedRelay_Runtime$ReventlessAws.relayClassicWithPool(pool, "ClassicRelayBasicEventLog", "relay-it:ClassicRelayBasicEventLog", capturingSendBatch(sink));
142
+ globalThis.expect(processed).toBe(2);
143
+ globalThis.expect(sink.contents.length).toBe(2);
144
+ let first = sink.contents[0];
145
+ globalThis.expect(idOf(first)).toEqual("agg-1");
146
+ let obj = Stdlib_Option.getOrThrow(Stdlib_JSON.Decode.object(first), undefined);
147
+ globalThis.expect(JSON.stringify(Stdlib_Option.getOrThrow(obj["event"], undefined)).includes("NameUpdated")).toBe(true);
148
+ let metaObj = Stdlib_Option.getOrThrow(Stdlib_Option.flatMap(obj["meta"], Stdlib_JSON.Decode.object), undefined);
149
+ globalThis.expect(metaObj["service"]).toEqual("relay-it");
150
+ let second = await PgChangeFeedRelay_Runtime$ReventlessAws.relayClassicWithPool(pool, "ClassicRelayBasicEventLog", "relay-it:ClassicRelayBasicEventLog", capturingSendBatch(sink));
151
+ globalThis.expect(second).toBe(0);
152
+ globalThis.expect(sink.contents.length).toBe(2);
153
+ });
154
+ globalThis.test("classic: per-log subscribers keep checkpoints isolated across logs", async () => {
155
+ let mkOps = name => EventLogStorage_Postgres$ReventlessPostgres.makeStorage(pool, name, undefined, undefined)[1];
156
+ let opsB = mkOps("ClassicRelayIsoBEventLog");
157
+ let opsA = mkOps("ClassicRelayIsoAEventLog");
158
+ let meta = () => Message$ReventlessCore.decomposeMeta(Message$ReventlessCore.generateMeta("relay-it", undefined, undefined, undefined, undefined, undefined, undefined, undefined));
159
+ let item = (id, eventType) => Object.fromEntries([
160
+ [
161
+ "id",
162
+ id
163
+ ],
164
+ [
165
+ "position",
166
+ "000000000"
167
+ ],
168
+ [
169
+ "event",
170
+ eventType
171
+ ],
172
+ [
173
+ "data",
174
+ Object.fromEntries([])
175
+ ]
176
+ ].concat(meta()));
177
+ await opsB.append(0, "b-1", [item("b-1", "BHappened")]);
178
+ await opsA.append(0, "a-1", [item("a-1", "AHappened")]);
179
+ let sinkA = {
180
+ contents: []
181
+ };
182
+ let drainedA = await PgChangeFeedRelay_Runtime$ReventlessAws.relayClassicWithPool(pool, "ClassicRelayIsoAEventLog", "relay-it:ClassicRelayIsoAEventLog", capturingSendBatch(sinkA));
183
+ globalThis.expect(drainedA).toBe(1);
184
+ let sinkB = {
185
+ contents: []
186
+ };
187
+ let drainedB = await PgChangeFeedRelay_Runtime$ReventlessAws.relayClassicWithPool(pool, "ClassicRelayIsoBEventLog", "relay-it:ClassicRelayIsoBEventLog", capturingSendBatch(sinkB));
188
+ globalThis.expect(drainedB).toBe(1);
189
+ globalThis.expect(idOf(sinkB.contents[0])).toEqual("b-1");
190
+ });
191
+ globalThis.test("partitionTag pins the id to its tag on a multi-tag event", async () => {
192
+ let match = DcbEventLogStorage_Postgres$ReventlessPostgres.makeStorage(pool, "relay-pt", [], {
193
+ TAG: "Simple",
194
+ _0: {
195
+ key: "customerId"
196
+ }
197
+ }, undefined, opts, undefined, undefined);
198
+ let ev = stored("OrderPlaced", [
199
+ {
200
+ key: "orderId",
201
+ value: "o-9"
202
+ },
203
+ {
204
+ key: "customerId",
205
+ value: "c-9"
206
+ }
207
+ ], Object.fromEntries([[
208
+ "n",
209
+ 1
210
+ ]]));
211
+ await match[1].append([ev], undefined);
212
+ let partitionTagJson = S.reverseConvertToJsonOrThrow({
213
+ TAG: "Simple",
214
+ _0: {
215
+ key: "customerId"
216
+ }
217
+ }, DcbTag$Reventless.derivedPartitionTagSchema);
218
+ let sink = {
219
+ contents: []
220
+ };
221
+ await PgChangeFeedRelay_Runtime$ReventlessAws.relayWithPool(pool, "relay-pt", "relay-it", partitionTagJson, capturingSendBatch(sink));
222
+ globalThis.expect(idOf(sink.contents[0])).toEqual("customerId:c-9");
223
+ });
224
+ });
225
+ } else {
226
+ globalThis.test("Postgres relay integration (skipped — set PG_URL to run)", () => {
227
+ globalThis.expect(true).toBe(true);
228
+ });
229
+ }
230
+
231
+ export {
232
+ opts,
233
+ jsonObj,
234
+ stored,
235
+ capturingSendBatch,
236
+ idOf,
237
+ }
238
+ /* url Not a pure module */