@reventlessdev/reventless-postgres 3.0.0-alpha.14 → 3.0.0-alpha.140

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 (143) hide show
  1. package/CHANGELOG.md +1013 -0
  2. package/README.md +83 -0
  3. package/lib/bs/.compiler.log +2 -2
  4. package/lib/bs/ReventlessPostgres.cmt +0 -0
  5. package/lib/bs/ReventlessPostgres.mlmap +0 -2
  6. package/lib/bs/compiler-info.json +4 -4
  7. package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  8. package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  9. package/lib/bs/src/DcbEventLogStorage_Postgres.ast +0 -0
  10. package/lib/bs/src/DcbEventLogStorage_Postgres.res +1 -1
  11. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  12. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  13. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.ast +0 -0
  14. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.res +43 -42
  15. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.res.mjs +4 -4
  16. package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
  17. package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
  18. package/lib/bs/src/EventLogChangeFeed.ast +0 -0
  19. package/lib/bs/src/EventLogChangeFeed.res +15 -6
  20. package/lib/bs/src/EventLogChangeFeed.res.mjs +3 -3
  21. package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  22. package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  23. package/lib/bs/src/EventLogStorage_Postgres.ast +0 -0
  24. package/lib/bs/src/EventLogStorage_Postgres.res +1 -1
  25. package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  26. package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  27. package/lib/bs/src/EventLogStorage_Postgres_Ops.ast +0 -0
  28. package/lib/bs/src/EventLogStorage_Postgres_Ops.res +24 -14
  29. package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmi +0 -0
  30. package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmt +0 -0
  31. package/lib/bs/src/PgChangeFeed.ast +0 -0
  32. package/lib/bs/src/PgChangeFeed.res +21 -9
  33. package/lib/bs/src/PgChangeFeed.res.mjs +6 -6
  34. package/lib/bs/src/PgDriver-ReventlessPostgres.cmi +0 -0
  35. package/lib/bs/src/PgDriver-ReventlessPostgres.cmt +0 -0
  36. package/lib/bs/src/PgDriver-ReventlessPostgres.cmti +0 -0
  37. package/lib/bs/src/PgDriver.ast +0 -0
  38. package/lib/bs/src/PgDriver.iast +0 -0
  39. package/lib/bs/src/PgDriver.res +1 -2
  40. package/lib/bs/src/PgSchema-ReventlessPostgres.cmi +0 -0
  41. package/lib/bs/src/PgSchema-ReventlessPostgres.cmt +0 -0
  42. package/lib/bs/src/PgSchema.ast +0 -0
  43. package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
  44. package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
  45. package/lib/bs/src/QueryDbStorage_Postgres.ast +0 -0
  46. package/lib/bs/src/QueryDbStorage_Postgres.res +7 -2
  47. package/lib/bs/src/QueryDbStorage_Postgres.res.mjs +1 -1
  48. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  49. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  50. package/lib/bs/src/QueryDbStorage_Postgres_Ops.ast +0 -0
  51. package/lib/bs/src/QueryDbStorage_Postgres_Ops.res +14 -14
  52. package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
  53. package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
  54. package/lib/bs/src/QueryEnginePostgres.ast +0 -0
  55. package/lib/bs/src/QueryEnginePostgres.res +141 -42
  56. package/lib/bs/src/QueryEnginePostgres.res.mjs +53 -30
  57. package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
  58. package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
  59. package/lib/bs/tests/PostgresIntegrationTest.ast +0 -0
  60. package/lib/bs/tests/PostgresIntegrationTest.res +157 -104
  61. package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
  62. package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
  63. package/lib/bs/tests/QueryEnginePostgresListPageTest.ast +0 -0
  64. package/lib/bs/tests/QueryEnginePostgresListPageTest.res +29 -6
  65. package/lib/bs/tests/QueryEnginePostgresListPageTest.res.mjs +4 -4
  66. package/lib/ocaml/.compiler.log +2 -2
  67. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  68. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  69. package/lib/ocaml/DcbEventLogStorage_Postgres.ast +0 -0
  70. package/lib/ocaml/DcbEventLogStorage_Postgres.res +1 -1
  71. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  72. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  73. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.ast +0 -0
  74. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.res +43 -42
  75. package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
  76. package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
  77. package/lib/ocaml/EventLogChangeFeed.ast +0 -0
  78. package/lib/ocaml/EventLogChangeFeed.res +15 -6
  79. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  80. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  81. package/lib/ocaml/EventLogStorage_Postgres.ast +0 -0
  82. package/lib/ocaml/EventLogStorage_Postgres.res +1 -1
  83. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  84. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  85. package/lib/ocaml/EventLogStorage_Postgres_Ops.ast +0 -0
  86. package/lib/ocaml/EventLogStorage_Postgres_Ops.res +24 -14
  87. package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmi +0 -0
  88. package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmt +0 -0
  89. package/lib/ocaml/PgChangeFeed.ast +0 -0
  90. package/lib/ocaml/PgChangeFeed.res +21 -9
  91. package/lib/ocaml/PgDriver-ReventlessPostgres.cmi +0 -0
  92. package/lib/ocaml/PgDriver-ReventlessPostgres.cmt +0 -0
  93. package/lib/ocaml/PgDriver-ReventlessPostgres.cmti +0 -0
  94. package/lib/ocaml/PgDriver.ast +0 -0
  95. package/lib/ocaml/PgDriver.iast +0 -0
  96. package/lib/ocaml/PgDriver.res +1 -2
  97. package/lib/ocaml/PgSchema-ReventlessPostgres.cmi +0 -0
  98. package/lib/ocaml/PgSchema-ReventlessPostgres.cmt +0 -0
  99. package/lib/ocaml/PgSchema.ast +0 -0
  100. package/lib/ocaml/PostgresIntegrationTest.res +157 -104
  101. package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
  102. package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
  103. package/lib/ocaml/QueryDbStorage_Postgres.ast +0 -0
  104. package/lib/ocaml/QueryDbStorage_Postgres.res +7 -2
  105. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  106. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  107. package/lib/ocaml/QueryDbStorage_Postgres_Ops.ast +0 -0
  108. package/lib/ocaml/QueryDbStorage_Postgres_Ops.res +14 -14
  109. package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
  110. package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
  111. package/lib/ocaml/QueryEnginePostgres.ast +0 -0
  112. package/lib/ocaml/QueryEnginePostgres.res +141 -42
  113. package/lib/ocaml/QueryEnginePostgresListPageTest.res +29 -6
  114. package/lib/ocaml/ReventlessPostgres.cmt +0 -0
  115. package/package.json +13 -13
  116. package/rescript.json +2 -1
  117. package/src/DcbEventLogStorage_Postgres.res +1 -1
  118. package/src/DcbEventLogStorage_Postgres_Ops.res +43 -42
  119. package/src/DcbEventLogStorage_Postgres_Ops.res.mjs +4 -4
  120. package/src/EventLogChangeFeed.res +15 -6
  121. package/src/EventLogChangeFeed.res.mjs +3 -3
  122. package/src/EventLogStorage_Postgres.res +1 -1
  123. package/src/EventLogStorage_Postgres_Ops.res +24 -14
  124. package/src/PgChangeFeed.res +21 -9
  125. package/src/PgChangeFeed.res.mjs +6 -6
  126. package/src/PgDriver.res +1 -2
  127. package/src/QueryDbStorage_Postgres.res +7 -2
  128. package/src/QueryDbStorage_Postgres.res.mjs +1 -1
  129. package/src/QueryDbStorage_Postgres_Ops.res +14 -14
  130. package/src/QueryEnginePostgres.res +141 -42
  131. package/src/QueryEnginePostgres.res.mjs +53 -30
  132. package/tests/PostgresIntegrationTest.res +157 -104
  133. package/tests/QueryEnginePostgresListPageTest.res +29 -6
  134. package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
  135. package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmj +0 -0
  136. package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
  137. package/lib/ocaml/PostgresIntegrationTest.ast +0 -0
  138. package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
  139. package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmj +0 -0
  140. package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
  141. package/lib/ocaml/QueryEnginePostgresListPageTest.ast +0 -0
  142. package/tests/PostgresIntegrationTest.res.mjs +0 -377
  143. package/tests/QueryEnginePostgresListPageTest.res.mjs +0 -461
@@ -100,7 +100,11 @@ switch processEnv->Dict.get("PG_URL") {
100
100
 
101
101
  testPromise("append + read round-trips events with tags", async () => {
102
102
  let (_n, ops, _s) = makeLog("dcb-rt")
103
- let e1 = stored("ItemAdded", [{key: "itemId", value: "x1"}], jsonObj([("n", JSON.Encode.string("w"))]))
103
+ let e1 = stored(
104
+ "ItemAdded",
105
+ [{key: "itemId", value: "x1"}],
106
+ jsonObj([("n", JSON.Encode.string("w"))]),
107
+ )
104
108
  let r = await ops.append([e1])
105
109
  switch r {
106
110
  | Ok(_) => expect(true)->toBe(true)
@@ -116,7 +120,12 @@ switch processEnv->Dict.get("PG_URL") {
116
120
 
117
121
  testPromise("conditional append conflicts when the boundary already has an event", async () => {
118
122
  let (_n, ops, _s) = makeLog("dcb-cond")
119
- let mk = who => stored("Reserved", [{key: "seatId", value: "s1"}], jsonObj([("who", JSON.Encode.string(who))]))
123
+ let mk = who =>
124
+ stored(
125
+ "Reserved",
126
+ [{key: "seatId", value: "s1"}],
127
+ jsonObj([("who", JSON.Encode.string(who))]),
128
+ )
120
129
  let r1 = await ops.append([mk("a")])
121
130
  switch r1 {
122
131
  | Ok(_) => expect(true)->toBe(true)
@@ -134,37 +143,46 @@ switch processEnv->Dict.get("PG_URL") {
134
143
  })
135
144
 
136
145
  let writeSkew = (strategy, logName) =>
137
- testPromise(`write-skew: exactly one of two concurrent appends wins (${logName})`, async () => {
138
- let (_n, ops, _s) = DcbEventLogStorage_Postgres.makeStorage(
139
- ~pool,
140
- ~name=logName,
141
- ~indexes=[],
142
- ~partitionTag=DcbTag.Simple({key: "k"}),
143
- ~opts,
144
- ~lockStrategy=strategy,
145
- )
146
- let cond: DcbTag.appendCondition = {
147
- query: [{eventTypes: ["Reserved"], tags: [{key: "seatId", value: "hot"}]}],
148
- }
149
- let mk = who => stored("Reserved", [{key: "seatId", value: "hot"}], jsonObj([("who", JSON.Encode.string(who))]))
150
- // Dispatch BOTH before awaiting either — genuinely concurrent on the pool.
151
- let p1 = ops.append([mk("a")], ~condition=cond)
152
- let p2 = ops.append([mk("b")], ~condition=cond)
153
- let r1 = await p1
154
- let r2 = await p2
155
- let isOk = r => switch r {
156
- | Ok(_) => true
157
- | _ => false
158
- }
159
- let isConflict = r => r == Error(ReventlessInfra.DcbEventLog.Conflict)
160
- let oks = [r1, r2]->Array.filter(isOk)->Array.length
161
- let conflicts = [r1, r2]->Array.filter(isConflict)->Array.length
162
- expect(oks)->toBe(1)
163
- expect(conflicts)->toBe(1)
164
- // And only one event actually landed on the boundary.
165
- let read = await ops.read(~query=[{tags: [{key: "seatId", value: "hot"}]}])
166
- expect(read.events->Array.length)->toBe(1)
167
- })
146
+ testPromise(
147
+ `write-skew: exactly one of two concurrent appends wins (${logName})`,
148
+ async () => {
149
+ let (_n, ops, _s) = DcbEventLogStorage_Postgres.makeStorage(
150
+ ~pool,
151
+ ~name=logName,
152
+ ~indexes=[],
153
+ ~partitionTag=DcbTag.Simple({key: "k"}),
154
+ ~opts,
155
+ ~lockStrategy=strategy,
156
+ )
157
+ let cond: DcbTag.appendCondition = {
158
+ query: [{eventTypes: ["Reserved"], tags: [{key: "seatId", value: "hot"}]}],
159
+ }
160
+ let mk = who =>
161
+ stored(
162
+ "Reserved",
163
+ [{key: "seatId", value: "hot"}],
164
+ jsonObj([("who", JSON.Encode.string(who))]),
165
+ )
166
+ // Dispatch BOTH before awaiting either — genuinely concurrent on the pool.
167
+ let p1 = ops.append([mk("a")], ~condition=cond)
168
+ let p2 = ops.append([mk("b")], ~condition=cond)
169
+ let r1 = await p1
170
+ let r2 = await p2
171
+ let isOk = r =>
172
+ switch r {
173
+ | Ok(_) => true
174
+ | _ => false
175
+ }
176
+ let isConflict = r => r == Error(ReventlessInfra.DcbEventLog.Conflict)
177
+ let oks = [r1, r2]->Array.filter(isOk)->Array.length
178
+ let conflicts = [r1, r2]->Array.filter(isConflict)->Array.length
179
+ expect(oks)->toBe(1)
180
+ expect(conflicts)->toBe(1)
181
+ // And only one event actually landed on the boundary.
182
+ let read = await ops.read(~query=[{tags: [{key: "seatId", value: "hot"}]}])
183
+ expect(read.events->Array.length)->toBe(1)
184
+ },
185
+ )
168
186
 
169
187
  writeSkew(#AdvisoryLocks, "dcb-skew-advisory")
170
188
  writeSkew(#RowLocks, "dcb-skew-rows")
@@ -176,17 +194,31 @@ switch processEnv->Dict.get("PG_URL") {
176
194
  }
177
195
  let n = 12
178
196
  // Dispatch all N before awaiting any — genuinely concurrent on the pool.
179
- let promises = Array.make(~length=n, 0)->Array.mapWithIndex((_, i) =>
180
- ops.append(
181
- [stored("Claimed", [{key: "prizeId", value: "p1"}], jsonObj([("who", JSON.Encode.int(i))]))],
182
- ~condition=cond,
197
+ let promises =
198
+ Array.make(~length=n, 0)->Array.mapWithIndex(
199
+ (_, i) =>
200
+ ops.append(
201
+ [
202
+ stored(
203
+ "Claimed",
204
+ [{key: "prizeId", value: "p1"}],
205
+ jsonObj([("who", JSON.Encode.int(i))]),
206
+ ),
207
+ ],
208
+ ~condition=cond,
209
+ ),
183
210
  )
184
- )
185
211
  let results = await Promise.all(promises)
186
- let oks = results->Array.filter(r => switch r {
187
- | Ok(_) => true
188
- | _ => false
189
- })->Array.length
212
+ let oks =
213
+ results
214
+ ->Array.filter(
215
+ r =>
216
+ switch r {
217
+ | Ok(_) => true
218
+ | _ => false
219
+ },
220
+ )
221
+ ->Array.length
190
222
  expect(oks)->toBe(1)
191
223
  // No lost updates: exactly one Claimed landed on the boundary.
192
224
  let read = await ops.read(~query=[{tags: [{key: "prizeId", value: "p1"}]}])
@@ -195,7 +227,8 @@ switch processEnv->Dict.get("PG_URL") {
195
227
 
196
228
  testPromise("cursors are monotonic and usable as `after`", async () => {
197
229
  let (_n, ops, _s) = makeLog("dcb-cursor")
198
- let mk = i => stored("Tick", [{key: "streamId", value: "c1"}], jsonObj([("i", JSON.Encode.int(i))]))
230
+ let mk = i =>
231
+ stored("Tick", [{key: "streamId", value: "c1"}], jsonObj([("i", JSON.Encode.int(i))]))
199
232
  let _ = await ops.append([mk(0)])
200
233
  let mid = switch await ops.append([mk(1)]) {
201
234
  | Ok(pos) => pos
@@ -222,83 +255,103 @@ switch processEnv->Dict.get("PG_URL") {
222
255
  let _ = await ops.append([mk(0), mk(1), mk(2)])
223
256
 
224
257
  let collected = ref(0)
225
- let processed = await PgChangeFeed.drain(pool, ~logName="feed-log", ~subscriber="sub-1", ~handle=async evs => {
226
- collected := collected.contents + evs->Array.length
227
- })
258
+ let processed = await PgChangeFeed.drain(
259
+ pool,
260
+ ~logName="feed-log",
261
+ ~subscriber="sub-1",
262
+ ~handle=async evs => {
263
+ collected := collected.contents + evs->Array.length
264
+ },
265
+ )
228
266
  expect(processed)->toBe(3)
229
267
  expect(collected.contents)->toBe(3)
230
268
 
231
269
  // A second drain from the saved checkpoint sees nothing new.
232
- let again = await PgChangeFeed.drain(pool, ~logName="feed-log", ~subscriber="sub-1", ~handle=async _ => ())
270
+ let again = await PgChangeFeed.drain(
271
+ pool,
272
+ ~logName="feed-log",
273
+ ~subscriber="sub-1",
274
+ ~handle=async _ => (),
275
+ )
233
276
  expect(again)->toBe(0)
234
277
  })
235
278
  })
236
279
 
237
280
  describe("classic change feed (event_log, B2.5)", () => {
238
- testPromise("drains classic events in commit order, with fields, then checkpoints", async () => {
239
- let (_n, ops, _s) = EventLogStorage_Postgres.makeStorage(~pool, ~name="cf-orders", ~opts)
240
- let _ = await ops.append(0, "agg-1", [evJson("m0", 1), evJson("m1", 2)])
241
- let _ = await ops.append(0, "agg-2", [evJson("m2", 3)])
281
+ testPromise(
282
+ "drains classic events in commit order, with fields, then checkpoints",
283
+ async () => {
284
+ let (_n, ops, _s) = EventLogStorage_Postgres.makeStorage(~pool, ~name="cf-orders", ~opts)
285
+ let _ = await ops.append(0, "agg-1", [evJson("m0", 1), evJson("m1", 2)])
286
+ let _ = await ops.append(0, "agg-2", [evJson("m2", 3)])
242
287
 
243
- let collected = ref([])
244
- let processed = await EventLogChangeFeed.drain(
245
- pool,
246
- ~logName="cf-orders",
247
- ~subscriber="cf-sub",
248
- ~handle=async evs => collected := collected.contents->Array.concat(evs),
249
- )
250
- expect(processed)->toBe(3)
251
- expect(collected.contents->Array.length)->toBe(3)
288
+ let collected = ref([])
289
+ let processed = await EventLogChangeFeed.drain(
290
+ pool,
291
+ ~logName="cf-orders",
292
+ ~subscriber="cf-sub",
293
+ ~handle=async evs => collected := collected.contents->Array.concat(evs),
294
+ )
295
+ expect(processed)->toBe(3)
296
+ expect(collected.contents->Array.length)->toBe(3)
252
297
 
253
- // Fields round-trip from the stored row.
254
- let first = collected.contents->Array.getUnsafe(0)
255
- expect(first.aggregateId)->toBe("agg-1")
256
- expect(first.seqNr)->toBe(0)
257
- expect(first.msgId)->toEqual(Some("m0"))
298
+ // Fields round-trip from the stored row.
299
+ let first = collected.contents->Array.getUnsafe(0)
300
+ expect(first.aggregateId)->toBe("agg-1")
301
+ expect(first.seqNr)->toBe(0)
302
+ expect(first.msgId)->toEqual(Some("m0"))
258
303
 
259
- // Cursors are strictly increasing in drain order.
260
- let cursors = collected.contents->Array.map(e => e.cursor)
261
- let increasing = ref(true)
262
- for i in 1 to cursors->Array.length - 1 {
263
- if cursors->Array.getUnsafe(i) <= cursors->Array.getUnsafe(i - 1) {
264
- increasing := false
304
+ // Cursors are strictly increasing in drain order.
305
+ let cursors = collected.contents->Array.map(e => e.cursor)
306
+ let increasing = ref(true)
307
+ for i in 1 to cursors->Array.length - 1 {
308
+ if cursors->Array.getUnsafe(i) <= cursors->Array.getUnsafe(i - 1) {
309
+ increasing := false
310
+ }
265
311
  }
266
- }
267
- expect(increasing.contents)->toBe(true)
312
+ expect(increasing.contents)->toBe(true)
268
313
 
269
- // Second drain from the saved checkpoint sees nothing new.
270
- let again = await EventLogChangeFeed.drain(
271
- pool,
272
- ~logName="cf-orders",
273
- ~subscriber="cf-sub",
274
- ~handle=async _ => (),
275
- )
276
- expect(again)->toBe(0)
277
- })
314
+ // Second drain from the saved checkpoint sees nothing new.
315
+ let again = await EventLogChangeFeed.drain(
316
+ pool,
317
+ ~logName="cf-orders",
318
+ ~subscriber="cf-sub",
319
+ ~handle=async _ => (),
320
+ )
321
+ expect(again)->toBe(0)
322
+ },
323
+ )
278
324
 
279
- testPromise("concurrent appends across aggregates: every event drained exactly once", async () => {
280
- let (_n, ops, _s) = EventLogStorage_Postgres.makeStorage(~pool, ~name="cf-concurrent", ~opts)
281
- let n = 20
282
- // One event per aggregate, all dispatched before awaiting — genuinely
283
- // concurrent on the pool, so global_seq is assigned in interleaved/undefined
284
- // commit order. The xmin fence must still drain every event exactly once.
285
- let promises =
286
- Array.make(~length=n, 0)->Array.mapWithIndex((_, i) =>
287
- ops.append(0, "agg-" ++ Int.toString(i), [evJson("m" ++ Int.toString(i), i)])
325
+ testPromise(
326
+ "concurrent appends across aggregates: every event drained exactly once",
327
+ async () => {
328
+ let (_n, ops, _s) = EventLogStorage_Postgres.makeStorage(
329
+ ~pool,
330
+ ~name="cf-concurrent",
331
+ ~opts,
288
332
  )
289
- let _ = await Promise.all(promises)
333
+ let n = 20
334
+ // One event per aggregate, all dispatched before awaiting — genuinely
335
+ // concurrent on the pool, so global_seq is assigned in interleaved/undefined
336
+ // commit order. The xmin fence must still drain every event exactly once.
337
+ let promises =
338
+ Array.make(~length=n, 0)->Array.mapWithIndex(
339
+ (_, i) => ops.append(0, "agg-" ++ Int.toString(i), [evJson("m" ++ Int.toString(i), i)]),
340
+ )
341
+ let _ = await Promise.all(promises)
290
342
 
291
- let collected = ref([])
292
- let _ = await EventLogChangeFeed.drain(
293
- pool,
294
- ~logName="cf-concurrent",
295
- ~subscriber="cf-conc-sub",
296
- ~handle=async evs => collected := collected.contents->Array.concat(evs),
297
- )
298
- // No skips: all N events present. No dupes: N distinct cursors.
299
- expect(collected.contents->Array.length)->toBe(n)
300
- let distinctCursors = collected.contents->Array.map(e => e.cursor)->Set.fromArray
301
- expect(distinctCursors->Set.size)->toBe(n)
302
- })
343
+ let collected = ref([])
344
+ let _ = await EventLogChangeFeed.drain(
345
+ pool,
346
+ ~logName="cf-concurrent",
347
+ ~subscriber="cf-conc-sub",
348
+ ~handle=async evs => collected := collected.contents->Array.concat(evs),
349
+ )
350
+ // No skips: all N events present. No dupes: N distinct cursors.
351
+ expect(collected.contents->Array.length)->toBe(n)
352
+ let distinctCursors = collected.contents->Array.map(e => e.cursor)->Set.fromArray
353
+ expect(distinctCursors->Set.size)->toBe(n)
354
+ },
355
+ )
303
356
  })
304
357
  }
@@ -6,7 +6,7 @@
6
6
  // that pure module (so every existing `QueryDbStorage_Postgres.*` reference keeps
7
7
  // working) and adds only the deploy-time `Pulumi.Output`-shaped `storage` record
8
8
  // and the `Make` provider functor.
9
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
9
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
10
10
 
11
11
  open ReventlessCore
12
12
  open Reventless.ReadModel
@@ -26,7 +26,11 @@ let makeStorage = (
26
26
  }
27
27
 
28
28
  // Standalone/deploy storage: inject the pool via a provider module.
29
- module Make = (P: {let pool: PgDriver.pool}) => {
29
+ module Make = (
30
+ P: {
31
+ let pool: PgDriver.pool
32
+ },
33
+ ) => {
30
34
  type api = unit
31
35
  type role = unit
32
36
 
@@ -37,6 +41,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
37
41
  ~ttl as _=?,
38
42
  ~api as _,
39
43
  ~apiRole as _,
44
+ ~owner as _,
40
45
  ~opts as _,
41
46
  ) => makeStorage(~pool=P.pool, ~name, ~indexes, ~subIdField)
42
47
  }
@@ -6,7 +6,7 @@
6
6
  // a deploy-time dep into its ESM import graph (which fails resolution on real
7
7
  // Lambda). The thin `QueryDbStorage_Postgres` wrapper `include`s this and adds the
8
8
  // deploy-time `Pulumi.Output`-shaped `storage` record + the `Make` functor.
9
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
9
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
10
10
 
11
11
  open ReventlessCore
12
12
  open Reventless.ReadModel
@@ -14,14 +14,12 @@ open Reventless.ReadModel
14
14
  let tableName = (name: string): string => "qdb_" ++ name->String.replaceAll("-", "_")
15
15
 
16
16
  // Collapse anything outside [A-Za-z0-9_] to _ — predictable, injection-safe idents.
17
- let sanitizeIdent = (s: string): string =>
18
- s->String.replaceRegExp(%re("/[^A-Za-z0-9_]/g"), "_")
17
+ let sanitizeIdent = (s: string): string => s->String.replaceRegExp(/[^A-Za-z0-9_]/g, "_")
19
18
 
20
19
  let notExpiredClause = "(expires_at IS NULL OR expires_at > extract(epoch from now()))"
21
20
 
22
21
  // JSONB text extraction for a single top-level field.
23
- let jsonField = (field: string): string =>
24
- `(item->>'${field->String.replaceAll("'", "''")}')`
22
+ let jsonField = (field: string): string => `(item->>'${field->String.replaceAll("'", "''")}')`
25
23
 
26
24
  let compositeExpr = (fields: array<string>, sep: string): string => {
27
25
  let escapedSep = sep->String.replaceAll("'", "''")
@@ -120,10 +118,13 @@ let makeOperations = (
120
118
 
121
119
  let rowsFor = async (id: string): array<JSON.t> => {
122
120
  await ready()
123
- (await pool->PgDriver.query(
124
- `SELECT item FROM ${table} WHERE partition_key = $1 AND ${notExpiredClause} ORDER BY sub_key ASC`,
125
- [JSON.Encode.string(id)],
126
- ))->Array.map(decodeItem)
121
+
122
+ (
123
+ await pool->PgDriver.query(
124
+ `SELECT item FROM ${table} WHERE partition_key = $1 AND ${notExpiredClause} ORDER BY sub_key ASC`,
125
+ [JSON.Encode.string(id)],
126
+ )
127
+ )->Array.map(decodeItem)
127
128
  }
128
129
 
129
130
  let saveOne = async (id: string, state: JSON.t, ttl: option<int>) => {
@@ -149,11 +150,10 @@ let makeOperations = (
149
150
 
150
151
  let load: QueryDb.load<string, JSON.t> = async id => Ok(await rowsFor(id))
151
152
 
152
- let loadStream: QueryDb.loadStream<string, JSON.t> =
153
- id =>
154
- Effect.promise(() => rowsFor(id))
155
- ->Stream.fromEffect
156
- ->Stream.flatMap(arr => Stream.fromIterable(arr))
153
+ let loadStream: QueryDb.loadStream<string, JSON.t> = id =>
154
+ Effect.promise(() => rowsFor(id))
155
+ ->Stream.fromEffect
156
+ ->Stream.flatMap(arr => Stream.fromIterable(arr))
157
157
 
158
158
  let save: QueryDb.save<string, JSON.t> = async (id, state, _saveMode, ttl) => {
159
159
  await saveOne(id, state, ttl)
Binary file