@reventlessdev/reventless-postgres 3.0.0-alpha.100 → 3.0.0-alpha.102

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 (83) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/lib/bs/.compiler.log +2 -2
  3. package/lib/bs/compiler-info.json +1 -1
  4. package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  5. package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  6. package/lib/bs/src/DcbEventLogStorage_Postgres.ast +0 -0
  7. package/lib/bs/src/DcbEventLogStorage_Postgres.res +1 -1
  8. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  9. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  10. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.ast +0 -0
  11. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.res +1 -1
  12. package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
  13. package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
  14. package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  15. package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  16. package/lib/bs/src/EventLogStorage_Postgres.ast +0 -0
  17. package/lib/bs/src/EventLogStorage_Postgres.res +1 -1
  18. package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  19. package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  20. package/lib/bs/src/EventLogStorage_Postgres_Ops.ast +0 -0
  21. package/lib/bs/src/EventLogStorage_Postgres_Ops.res +1 -1
  22. package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmi +0 -0
  23. package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmt +0 -0
  24. package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
  25. package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
  26. package/lib/bs/src/QueryDbStorage_Postgres.ast +0 -0
  27. package/lib/bs/src/QueryDbStorage_Postgres.res +1 -1
  28. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  29. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  30. package/lib/bs/src/QueryDbStorage_Postgres_Ops.ast +0 -0
  31. package/lib/bs/src/QueryDbStorage_Postgres_Ops.res +1 -1
  32. package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
  33. package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
  34. package/lib/bs/src/QueryEnginePostgres.ast +0 -0
  35. package/lib/bs/src/QueryEnginePostgres.res +43 -0
  36. package/lib/bs/src/QueryEnginePostgres.res.mjs +25 -2
  37. package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
  38. package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
  39. package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
  40. package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
  41. package/lib/bs/tests/QueryEnginePostgresListPageTest.res.mjs +4 -4
  42. package/lib/ocaml/.compiler.log +2 -2
  43. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  44. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  45. package/lib/ocaml/DcbEventLogStorage_Postgres.ast +0 -0
  46. package/lib/ocaml/DcbEventLogStorage_Postgres.res +1 -1
  47. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  48. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  49. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.ast +0 -0
  50. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.res +1 -1
  51. package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
  52. package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
  53. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  54. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  55. package/lib/ocaml/EventLogStorage_Postgres.ast +0 -0
  56. package/lib/ocaml/EventLogStorage_Postgres.res +1 -1
  57. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  58. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  59. package/lib/ocaml/EventLogStorage_Postgres_Ops.ast +0 -0
  60. package/lib/ocaml/EventLogStorage_Postgres_Ops.res +1 -1
  61. package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmi +0 -0
  62. package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmt +0 -0
  63. package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
  64. package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
  65. package/lib/ocaml/QueryDbStorage_Postgres.ast +0 -0
  66. package/lib/ocaml/QueryDbStorage_Postgres.res +1 -1
  67. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  68. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  69. package/lib/ocaml/QueryDbStorage_Postgres_Ops.ast +0 -0
  70. package/lib/ocaml/QueryDbStorage_Postgres_Ops.res +1 -1
  71. package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
  72. package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
  73. package/lib/ocaml/QueryEnginePostgres.ast +0 -0
  74. package/lib/ocaml/QueryEnginePostgres.res +43 -0
  75. package/package.json +6 -6
  76. package/src/DcbEventLogStorage_Postgres.res +1 -1
  77. package/src/DcbEventLogStorage_Postgres_Ops.res +1 -1
  78. package/src/EventLogStorage_Postgres.res +1 -1
  79. package/src/EventLogStorage_Postgres_Ops.res +1 -1
  80. package/src/QueryDbStorage_Postgres.res +1 -1
  81. package/src/QueryDbStorage_Postgres_Ops.res +1 -1
  82. package/src/QueryEnginePostgres.res +43 -0
  83. package/src/QueryEnginePostgres.res.mjs +25 -2
package/CHANGELOG.md CHANGED
@@ -3,6 +3,23 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.102 (2026-08-16)
7
+
8
+ **Note:** Version bump only for package @reventlessdev/reventless-postgres
9
+
10
+
11
+
12
+
13
+
14
+ # 3.0.0-alpha.101 (2026-08-16)
15
+
16
+ ### Features
17
+
18
+ * **core:** exclude retired rows from reads a caller may not widen ([662f31a](https://github.com/ReventlessDev/reventless-core/commit/662f31abb717bda5154199d349da6dcf8e2d3e78))
19
+ * **core:** let [@retired](https://github.com/retired) name a lifecycle state, not only a boolean ([6bb346b](https://github.com/ReventlessDev/reventless-core/commit/6bb346b4f6a5f33826fc24537953482a76067177))
20
+ * **core:** mark the state that retires a row, and allow more than one ([cb1461f](https://github.com/ReventlessDev/reventless-core/commit/cb1461f024d3ca3b53fd9c8b010a054e3fcc4555))
21
+
22
+
6
23
  # 3.0.0-alpha.100 (2026-08-15)
7
24
 
8
25
  **Note:** Version bump only for package @reventlessdev/reventless-postgres
@@ -1,2 +1,2 @@
1
- #Start(1786782398524)
2
- #Done(1786782404708)
1
+ #Start(1786902407421)
2
+ #Done(1786902412808)
@@ -4,5 +4,5 @@
4
4
  "bsc_hash": "75e3a59c95cc953608fd3dd6ea6ed68141bba4414a20a52f114261b8a48385fa",
5
5
  "rescript_config_hash": "af2af60a0fa33b8249a96ff2758f36482843126dc4e07b9302842d011e0ad8c6",
6
6
  "runtime_path": "/home/runner/work/reventless-core/reventless-core/node_modules/@rescript/runtime",
7
- "generated_at": "1786782404713"
7
+ "generated_at": "1786902412813"
8
8
  }
@@ -8,7 +8,7 @@
8
8
  // existing `DcbEventLogStorage_Postgres.*` reference keeps working) and adds only
9
9
  // the deploy-time `DcbEventLog_Adapter` shape, keeping the historic `makeStorage`
10
10
  // 3-tuple `(name, ops, adapter)` API unchanged.
11
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
11
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
12
12
 
13
13
  open ReventlessCore
14
14
  include DcbEventLogStorage_Postgres_Ops
@@ -6,7 +6,7 @@
6
6
  // would leak into the Lambda import graph and fail resolution on real Lambda. The
7
7
  // thin `DcbEventLogStorage_Postgres` wrapper `include`s this and adds the
8
8
  // deploy-time `Pulumi.Output`-shaped adapter for local/deploy consumers.
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
@@ -7,7 +7,7 @@
7
7
  // real Lambda). This module `include`s that pure module and adds only the
8
8
  // deploy-time `EventLog_Adapter` shape (`operations` wrapped in `Pulumi.Output`),
9
9
  // keeping the historic `makeStorage` 3-tuple `(name, ops, adapter)` API unchanged.
10
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
10
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
11
11
 
12
12
  open ReventlessCore
13
13
  include EventLogStorage_Postgres_Ops
@@ -7,7 +7,7 @@
7
7
  // in no Lambda — not the layer, not `/var/runtime`). The thin
8
8
  // `EventLogStorage_Postgres` wrapper adds the deploy-time `Pulumi.Output`-shaped
9
9
  // adapter on top for local/deploy consumers; the runtime imports `makeOps` here.
10
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
10
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
11
11
 
12
12
  open ReventlessCore
13
13
 
@@ -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
@@ -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
Binary file
@@ -168,6 +168,39 @@ module Make = (P: {let pool: PgDriver.pool}) => {
168
168
  let idExprC = `COALESCE(item->>'id', partition_key) COLLATE "C"`
169
169
  let jsonTextC = (field: string): string => `${col(field)} COLLATE "C"`
170
170
 
171
+ // "not retired", as jsonb rather than text. `->` keeps the value typed, so a
172
+ // JSON boolean true is distinguishable from the JSON string "true"; `->>`
173
+ // would render both as 'true' and hide a row the shared spec keeps, since that
174
+ // spec decodes strictly and reads a non-boolean as "no flag". `IS DISTINCT
175
+ // FROM` rather than `<>` so an absent key — NULL — keeps the row instead of
176
+ // making the whole predicate NULL and dropping it.
177
+ //
178
+ // The state form compares against JSON strings instead of `true`, one
179
+ // `IS DISTINCT FROM` per state, ANDed. Not `NOT IN`: that is ordinary
180
+ // equality, so an absent key makes the predicate NULL and drops a row this
181
+ // spec keeps. Same shape and same index behaviour as the single-state form —
182
+ // a handful of equality predicates over an enum-valued attribute cost what one
183
+ // over a boolean does — so the `@index` guidance carries over unchanged.
184
+ //
185
+ // A set naming no states withdraws nothing, so the predicate is vacuously
186
+ // true, matching what `isRetiredValue` answers for it.
187
+ let notRetiredC = (scope: Reventless.OwnerScope.retiredScope): string => {
188
+ let key = scope.field->String.replaceAll("'", "''")
189
+ let distinctFrom = lit => `(item->'${key}') IS DISTINCT FROM ${lit}`
190
+ switch scope.values {
191
+ | None => distinctFrom("'true'::jsonb")
192
+ | Some([]) => "TRUE"
193
+ | Some(states) =>
194
+ // Parenthesised though every caller ANDs these together already: a clause
195
+ // that is safe only because of where it is spliced is a clause that breaks
196
+ // the first time it is spliced somewhere else.
197
+ "(" ++
198
+ states
199
+ ->Array.map(state => distinctFrom(`'"${state->String.replaceAll("'", "''")}"'::jsonb`))
200
+ ->Array.join(" AND ") ++ ")"
201
+ }
202
+ }
203
+
171
204
  // Connection-list push-down. Builds `item->>'field'` predicates + keyset
172
205
  // WHERE + `ORDER BY … LIMIT pageSize+1` so a page reads only what it returns
173
206
  // rather than the whole read model. Reproduces `QueryDbListQuery` semantics
@@ -184,6 +217,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
184
217
  ~capability: GraphQL_FragmentGenerator.serverCapability,
185
218
  ~labelField as _,
186
219
  ~ownerScope: option<(string, string)>=?,
220
+ ~retiredScope: option<Reventless.OwnerScope.retiredScope>=?,
187
221
  ): option<JSON.t> => {
188
222
  let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
189
223
  let filterDict =
@@ -220,6 +254,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
220
254
  whereParts->Array.push(`${jsonTextC(field)} = ${b->param(JSON.Encode.string(required))}`)
221
255
  | None => ()
222
256
  }
257
+ switch retiredScope {
258
+ | Some(scope) => whereParts->Array.push(notRetiredC(scope))
259
+ | None => ()
260
+ }
223
261
  let valString = v =>
224
262
  switch v->JSON.Decode.string {
225
263
  | Some(s) => Some(s)
@@ -320,6 +358,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
320
358
  ~id: string,
321
359
  ~argsDict: dict<JSON.t>,
322
360
  ~ownerScope: option<(string, string)>=?,
361
+ ~retiredScope: option<Reventless.OwnerScope.retiredScope>=?,
323
362
  ): JSON.t => {
324
363
  let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
325
364
  let filterDict =
@@ -341,6 +380,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
341
380
  where->Array.push(`${jsonTextC(field)} = ${b->param(JSON.Encode.string(required))}`)
342
381
  | None => ()
343
382
  }
383
+ switch retiredScope {
384
+ | Some(scope) => where->Array.push(notRetiredC(scope))
385
+ | None => ()
386
+ }
344
387
  fstr("prefix")->Option.forEach(p =>
345
388
  where->Array.push(`starts_with(sub_key, ${b->param(JSON.Encode.string(p))})`)
346
389
  )
@@ -169,7 +169,23 @@ function Make(P) {
169
169
  };
170
170
  let idExprC = `COALESCE(item->>'id', partition_key) COLLATE "C"`;
171
171
  let jsonTextC = field => col(field) + ` COLLATE "C"`;
172
- let listPage = async (readModelName, argsDict, capability, param$1, ownerScope) => {
172
+ let notRetiredC = scope => {
173
+ let key = scope.field.replaceAll("'", "''");
174
+ let states = scope.values;
175
+ if (states !== undefined) {
176
+ if (states.length !== 0) {
177
+ return "(" + states.map(state => {
178
+ let lit = `'"` + state.replaceAll("'", "''") + `"'::jsonb`;
179
+ return `(item->'` + key + `') IS DISTINCT FROM ` + lit;
180
+ }).join(" AND ") + ")";
181
+ } else {
182
+ return "TRUE";
183
+ }
184
+ } else {
185
+ return `(item->'` + key + `') IS DISTINCT FROM ` + "'true'::jsonb";
186
+ }
187
+ };
188
+ let listPage = async (readModelName, argsDict, capability, param$1, ownerScope, retiredScope) => {
173
189
  let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
174
190
  let filterDict = Stdlib_Option.getOr(Stdlib_Option.flatMap(argsDict["filter"], Stdlib_JSON.Decode.object), {});
175
191
  let strNonEmpty = k => Stdlib_Option.mapOr(Stdlib_Option.flatMap(filterDict[k], Stdlib_JSON.Decode.string), false, s => s.length > 0);
@@ -186,6 +202,9 @@ function Make(P) {
186
202
  if (ownerScope !== undefined) {
187
203
  whereParts.push(jsonTextC(ownerScope[0]) + ` = ` + param(b, ownerScope[1]));
188
204
  }
205
+ if (retiredScope !== undefined) {
206
+ whereParts.push(notRetiredC(retiredScope));
207
+ }
189
208
  let valString = v => {
190
209
  let s = Stdlib_JSON.Decode.string(v);
191
210
  if (s !== undefined) {
@@ -234,7 +253,7 @@ function Make(P) {
234
253
  let cursorValueOf = item => Stdlib_Option.getOr(QueryDbListQuery$ReventlessCore.getFieldString(item, cursorField), QueryDbListQuery$ReventlessCore.getId(item));
235
254
  return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore, false, cursorValueOf);
236
255
  };
237
- let itemsPage = async (readModelName, param$1, id, argsDict, ownerScope) => {
256
+ let itemsPage = async (readModelName, param$1, id, argsDict, ownerScope, retiredScope) => {
238
257
  let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
239
258
  let filterDict = Stdlib_Option.getOr(Stdlib_Option.flatMap(argsDict["filter"], Stdlib_JSON.Decode.object), {});
240
259
  let fstr = k => Stdlib_Option.flatMap(filterDict[k], Stdlib_JSON.Decode.string);
@@ -255,6 +274,9 @@ function Make(P) {
255
274
  if (ownerScope !== undefined) {
256
275
  where.push(jsonTextC(ownerScope[0]) + ` = ` + param(b, ownerScope[1]));
257
276
  }
277
+ if (retiredScope !== undefined) {
278
+ where.push(notRetiredC(retiredScope));
279
+ }
258
280
  Stdlib_Option.forEach(fstr("prefix"), p => {
259
281
  where.push(`starts_with(sub_key, ` + param(b, p) + `)`);
260
282
  });
@@ -316,6 +338,7 @@ function Make(P) {
316
338
  byIds: byIds,
317
339
  idExprC: idExprC,
318
340
  jsonTextC: jsonTextC,
341
+ notRetiredC: notRetiredC,
319
342
  listPage: listPage,
320
343
  itemsPage: itemsPage
321
344
  };
@@ -122,7 +122,7 @@ function orderBy(f, dir) {
122
122
  }
123
123
 
124
124
  function expectedFor(argsDict) {
125
- return norm(QueryDbListQuery$ReventlessCore.run(seededItems, argsDict, capability, "name", param => {}, undefined));
125
+ return norm(QueryDbListQuery$ReventlessCore.run(seededItems, argsDict, capability, "name", param => {}, undefined, undefined));
126
126
  }
127
127
 
128
128
  let url = process.env["PG_URL"];
@@ -148,7 +148,7 @@ if (url !== undefined) {
148
148
  });
149
149
  let checkPushed = async (label, args) => {
150
150
  let argsDict = Object.fromEntries(args);
151
- let actual = await Eng.listPage(name, argsDict, capability, "name", undefined);
151
+ let actual = await Eng.listPage(name, argsDict, capability, "name", undefined, undefined);
152
152
  if (actual !== undefined) {
153
153
  globalThis.expect(norm(actual)).toEqual(expectedFor(argsDict));
154
154
  } else {
@@ -156,7 +156,7 @@ if (url !== undefined) {
156
156
  }
157
157
  };
158
158
  let checkFallback = async args => {
159
- let r = await Eng.listPage(name, Object.fromEntries(args), capability, "name", undefined);
159
+ let r = await Eng.listPage(name, Object.fromEntries(args), capability, "name", undefined, undefined);
160
160
  globalThis.expect(Stdlib_Option.isSome(r)).toBe(false);
161
161
  };
162
162
  globalThis.describe("QueryDb list push-down parity (Postgres ≡ QueryDbListQuery spec)", () => {
@@ -322,7 +322,7 @@ if (url !== undefined) {
322
322
  });
323
323
  let itemSeqs = conn => Stdlib_Array.filterMap(Stdlib_Option.getOr(Stdlib_Option.flatMap(field(conn, "edges"), Stdlib_JSON.Decode.array), []), e => Stdlib_Option.flatMap(field(e, "node"), n => str(n, "seq")));
324
324
  let pageInfoBool = (conn, key) => Stdlib_Option.getOr(Stdlib_Option.flatMap(field(Stdlib_Option.getOr(field(conn, "pageInfo"), null), key), Stdlib_JSON.Decode.bool), false);
325
- let itemsPage = args => Eng.itemsPage(itemsName, "seq", "order-1", Object.fromEntries(args), undefined);
325
+ let itemsPage = args => Eng.itemsPage(itemsName, "seq", "order-1", Object.fromEntries(args), undefined, undefined);
326
326
  globalThis.describe("QueryEnginePostgres itemsPage (sub-id keyset)", () => {
327
327
  globalThis.test("bare → all lines ASC, no next page", async () => {
328
328
  let r = await itemsPage([]);
@@ -1,2 +1,2 @@
1
- #Start(1786782398524)
2
- #Done(1786782404708)
1
+ #Start(1786902407421)
2
+ #Done(1786902412808)
@@ -8,7 +8,7 @@
8
8
  // existing `DcbEventLogStorage_Postgres.*` reference keeps working) and adds only
9
9
  // the deploy-time `DcbEventLog_Adapter` shape, keeping the historic `makeStorage`
10
10
  // 3-tuple `(name, ops, adapter)` API unchanged.
11
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
11
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
12
12
 
13
13
  open ReventlessCore
14
14
  include DcbEventLogStorage_Postgres_Ops
@@ -6,7 +6,7 @@
6
6
  // would leak into the Lambda import graph and fail resolution on real Lambda. The
7
7
  // thin `DcbEventLogStorage_Postgres` wrapper `include`s this and adds the
8
8
  // deploy-time `Pulumi.Output`-shaped adapter for local/deploy consumers.
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
@@ -7,7 +7,7 @@
7
7
  // real Lambda). This module `include`s that pure module and adds only the
8
8
  // deploy-time `EventLog_Adapter` shape (`operations` wrapped in `Pulumi.Output`),
9
9
  // keeping the historic `makeStorage` 3-tuple `(name, ops, adapter)` API unchanged.
10
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
10
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
11
11
 
12
12
  open ReventlessCore
13
13
  include EventLogStorage_Postgres_Ops
@@ -7,7 +7,7 @@
7
7
  // in no Lambda — not the layer, not `/var/runtime`). The thin
8
8
  // `EventLogStorage_Postgres` wrapper adds the deploy-time `Pulumi.Output`-shaped
9
9
  // adapter on top for local/deploy consumers; the runtime imports `makeOps` here.
10
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
10
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
11
11
 
12
12
  open ReventlessCore
13
13
 
@@ -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
@@ -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
Binary file
@@ -168,6 +168,39 @@ module Make = (P: {let pool: PgDriver.pool}) => {
168
168
  let idExprC = `COALESCE(item->>'id', partition_key) COLLATE "C"`
169
169
  let jsonTextC = (field: string): string => `${col(field)} COLLATE "C"`
170
170
 
171
+ // "not retired", as jsonb rather than text. `->` keeps the value typed, so a
172
+ // JSON boolean true is distinguishable from the JSON string "true"; `->>`
173
+ // would render both as 'true' and hide a row the shared spec keeps, since that
174
+ // spec decodes strictly and reads a non-boolean as "no flag". `IS DISTINCT
175
+ // FROM` rather than `<>` so an absent key — NULL — keeps the row instead of
176
+ // making the whole predicate NULL and dropping it.
177
+ //
178
+ // The state form compares against JSON strings instead of `true`, one
179
+ // `IS DISTINCT FROM` per state, ANDed. Not `NOT IN`: that is ordinary
180
+ // equality, so an absent key makes the predicate NULL and drops a row this
181
+ // spec keeps. Same shape and same index behaviour as the single-state form —
182
+ // a handful of equality predicates over an enum-valued attribute cost what one
183
+ // over a boolean does — so the `@index` guidance carries over unchanged.
184
+ //
185
+ // A set naming no states withdraws nothing, so the predicate is vacuously
186
+ // true, matching what `isRetiredValue` answers for it.
187
+ let notRetiredC = (scope: Reventless.OwnerScope.retiredScope): string => {
188
+ let key = scope.field->String.replaceAll("'", "''")
189
+ let distinctFrom = lit => `(item->'${key}') IS DISTINCT FROM ${lit}`
190
+ switch scope.values {
191
+ | None => distinctFrom("'true'::jsonb")
192
+ | Some([]) => "TRUE"
193
+ | Some(states) =>
194
+ // Parenthesised though every caller ANDs these together already: a clause
195
+ // that is safe only because of where it is spliced is a clause that breaks
196
+ // the first time it is spliced somewhere else.
197
+ "(" ++
198
+ states
199
+ ->Array.map(state => distinctFrom(`'"${state->String.replaceAll("'", "''")}"'::jsonb`))
200
+ ->Array.join(" AND ") ++ ")"
201
+ }
202
+ }
203
+
171
204
  // Connection-list push-down. Builds `item->>'field'` predicates + keyset
172
205
  // WHERE + `ORDER BY … LIMIT pageSize+1` so a page reads only what it returns
173
206
  // rather than the whole read model. Reproduces `QueryDbListQuery` semantics
@@ -184,6 +217,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
184
217
  ~capability: GraphQL_FragmentGenerator.serverCapability,
185
218
  ~labelField as _,
186
219
  ~ownerScope: option<(string, string)>=?,
220
+ ~retiredScope: option<Reventless.OwnerScope.retiredScope>=?,
187
221
  ): option<JSON.t> => {
188
222
  let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
189
223
  let filterDict =
@@ -220,6 +254,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
220
254
  whereParts->Array.push(`${jsonTextC(field)} = ${b->param(JSON.Encode.string(required))}`)
221
255
  | None => ()
222
256
  }
257
+ switch retiredScope {
258
+ | Some(scope) => whereParts->Array.push(notRetiredC(scope))
259
+ | None => ()
260
+ }
223
261
  let valString = v =>
224
262
  switch v->JSON.Decode.string {
225
263
  | Some(s) => Some(s)
@@ -320,6 +358,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
320
358
  ~id: string,
321
359
  ~argsDict: dict<JSON.t>,
322
360
  ~ownerScope: option<(string, string)>=?,
361
+ ~retiredScope: option<Reventless.OwnerScope.retiredScope>=?,
323
362
  ): JSON.t => {
324
363
  let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
325
364
  let filterDict =
@@ -341,6 +380,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
341
380
  where->Array.push(`${jsonTextC(field)} = ${b->param(JSON.Encode.string(required))}`)
342
381
  | None => ()
343
382
  }
383
+ switch retiredScope {
384
+ | Some(scope) => where->Array.push(notRetiredC(scope))
385
+ | None => ()
386
+ }
344
387
  fstr("prefix")->Option.forEach(p =>
345
388
  where->Array.push(`starts_with(sub_key, ${b->param(JSON.Encode.string(p))})`)
346
389
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-postgres",
3
- "version": "3.0.0-alpha.100",
3
+ "version": "3.0.0-alpha.102",
4
4
  "description": "Postgres storage backend for Reventless (classic + DCB event logs, query DB, change feed). Connection-string only — RDS/Aurora, any managed provider, or a local container.",
5
5
  "license": "Apache-2.0",
6
6
  "jest": {
@@ -22,18 +22,18 @@
22
22
  "dependencies": {
23
23
  "pg": "^8.13.1",
24
24
  "sury": "11.0.0-alpha.4",
25
- "@reventlessdev/reventless-core": "3.0.0-alpha.236",
26
25
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
27
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
28
26
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
29
- "@reventlessdev/reventless-infra": "3.0.0-alpha.142",
30
- "@reventlessdev/reventless-spec": "3.0.0-alpha.114"
27
+ "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
28
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.144",
29
+ "@reventlessdev/reventless-core": "3.0.0-alpha.238",
30
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.116"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/pg": "^8.11.10",
34
34
  "rescript": "12.3.0",
35
35
  "sury-ppx": "11.0.0-alpha.2",
36
- "@reventlessdev/reventless-ppx": "1.0.0-alpha.66"
36
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.68"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "rescript": "12.3.0"
@@ -8,7 +8,7 @@
8
8
  // existing `DcbEventLogStorage_Postgres.*` reference keeps working) and adds only
9
9
  // the deploy-time `DcbEventLog_Adapter` shape, keeping the historic `makeStorage`
10
10
  // 3-tuple `(name, ops, adapter)` API unchanged.
11
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
11
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
12
12
 
13
13
  open ReventlessCore
14
14
  include DcbEventLogStorage_Postgres_Ops
@@ -6,7 +6,7 @@
6
6
  // would leak into the Lambda import graph and fail resolution on real Lambda. The
7
7
  // thin `DcbEventLogStorage_Postgres` wrapper `include`s this and adds the
8
8
  // deploy-time `Pulumi.Output`-shaped adapter for local/deploy consumers.
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
@@ -7,7 +7,7 @@
7
7
  // real Lambda). This module `include`s that pure module and adds only the
8
8
  // deploy-time `EventLog_Adapter` shape (`operations` wrapped in `Pulumi.Output`),
9
9
  // keeping the historic `makeStorage` 3-tuple `(name, ops, adapter)` API unchanged.
10
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
10
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
11
11
 
12
12
  open ReventlessCore
13
13
  include EventLogStorage_Postgres_Ops
@@ -7,7 +7,7 @@
7
7
  // in no Lambda — not the layer, not `/var/runtime`). The thin
8
8
  // `EventLogStorage_Postgres` wrapper adds the deploy-time `Pulumi.Output`-shaped
9
9
  // adapter on top for local/deploy consumers; the runtime imports `makeOps` here.
10
- // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
10
+ // See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
11
11
 
12
12
  open ReventlessCore
13
13
 
@@ -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
@@ -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
@@ -168,6 +168,39 @@ module Make = (P: {let pool: PgDriver.pool}) => {
168
168
  let idExprC = `COALESCE(item->>'id', partition_key) COLLATE "C"`
169
169
  let jsonTextC = (field: string): string => `${col(field)} COLLATE "C"`
170
170
 
171
+ // "not retired", as jsonb rather than text. `->` keeps the value typed, so a
172
+ // JSON boolean true is distinguishable from the JSON string "true"; `->>`
173
+ // would render both as 'true' and hide a row the shared spec keeps, since that
174
+ // spec decodes strictly and reads a non-boolean as "no flag". `IS DISTINCT
175
+ // FROM` rather than `<>` so an absent key — NULL — keeps the row instead of
176
+ // making the whole predicate NULL and dropping it.
177
+ //
178
+ // The state form compares against JSON strings instead of `true`, one
179
+ // `IS DISTINCT FROM` per state, ANDed. Not `NOT IN`: that is ordinary
180
+ // equality, so an absent key makes the predicate NULL and drops a row this
181
+ // spec keeps. Same shape and same index behaviour as the single-state form —
182
+ // a handful of equality predicates over an enum-valued attribute cost what one
183
+ // over a boolean does — so the `@index` guidance carries over unchanged.
184
+ //
185
+ // A set naming no states withdraws nothing, so the predicate is vacuously
186
+ // true, matching what `isRetiredValue` answers for it.
187
+ let notRetiredC = (scope: Reventless.OwnerScope.retiredScope): string => {
188
+ let key = scope.field->String.replaceAll("'", "''")
189
+ let distinctFrom = lit => `(item->'${key}') IS DISTINCT FROM ${lit}`
190
+ switch scope.values {
191
+ | None => distinctFrom("'true'::jsonb")
192
+ | Some([]) => "TRUE"
193
+ | Some(states) =>
194
+ // Parenthesised though every caller ANDs these together already: a clause
195
+ // that is safe only because of where it is spliced is a clause that breaks
196
+ // the first time it is spliced somewhere else.
197
+ "(" ++
198
+ states
199
+ ->Array.map(state => distinctFrom(`'"${state->String.replaceAll("'", "''")}"'::jsonb`))
200
+ ->Array.join(" AND ") ++ ")"
201
+ }
202
+ }
203
+
171
204
  // Connection-list push-down. Builds `item->>'field'` predicates + keyset
172
205
  // WHERE + `ORDER BY … LIMIT pageSize+1` so a page reads only what it returns
173
206
  // rather than the whole read model. Reproduces `QueryDbListQuery` semantics
@@ -184,6 +217,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
184
217
  ~capability: GraphQL_FragmentGenerator.serverCapability,
185
218
  ~labelField as _,
186
219
  ~ownerScope: option<(string, string)>=?,
220
+ ~retiredScope: option<Reventless.OwnerScope.retiredScope>=?,
187
221
  ): option<JSON.t> => {
188
222
  let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
189
223
  let filterDict =
@@ -220,6 +254,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
220
254
  whereParts->Array.push(`${jsonTextC(field)} = ${b->param(JSON.Encode.string(required))}`)
221
255
  | None => ()
222
256
  }
257
+ switch retiredScope {
258
+ | Some(scope) => whereParts->Array.push(notRetiredC(scope))
259
+ | None => ()
260
+ }
223
261
  let valString = v =>
224
262
  switch v->JSON.Decode.string {
225
263
  | Some(s) => Some(s)
@@ -320,6 +358,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
320
358
  ~id: string,
321
359
  ~argsDict: dict<JSON.t>,
322
360
  ~ownerScope: option<(string, string)>=?,
361
+ ~retiredScope: option<Reventless.OwnerScope.retiredScope>=?,
323
362
  ): JSON.t => {
324
363
  let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
325
364
  let filterDict =
@@ -341,6 +380,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
341
380
  where->Array.push(`${jsonTextC(field)} = ${b->param(JSON.Encode.string(required))}`)
342
381
  | None => ()
343
382
  }
383
+ switch retiredScope {
384
+ | Some(scope) => where->Array.push(notRetiredC(scope))
385
+ | None => ()
386
+ }
344
387
  fstr("prefix")->Option.forEach(p =>
345
388
  where->Array.push(`starts_with(sub_key, ${b->param(JSON.Encode.string(p))})`)
346
389
  )
@@ -169,7 +169,23 @@ function Make(P) {
169
169
  };
170
170
  let idExprC = `COALESCE(item->>'id', partition_key) COLLATE "C"`;
171
171
  let jsonTextC = field => col(field) + ` COLLATE "C"`;
172
- let listPage = async (readModelName, argsDict, capability, param$1, ownerScope) => {
172
+ let notRetiredC = scope => {
173
+ let key = scope.field.replaceAll("'", "''");
174
+ let states = scope.values;
175
+ if (states !== undefined) {
176
+ if (states.length !== 0) {
177
+ return "(" + states.map(state => {
178
+ let lit = `'"` + state.replaceAll("'", "''") + `"'::jsonb`;
179
+ return `(item->'` + key + `') IS DISTINCT FROM ` + lit;
180
+ }).join(" AND ") + ")";
181
+ } else {
182
+ return "TRUE";
183
+ }
184
+ } else {
185
+ return `(item->'` + key + `') IS DISTINCT FROM ` + "'true'::jsonb";
186
+ }
187
+ };
188
+ let listPage = async (readModelName, argsDict, capability, param$1, ownerScope, retiredScope) => {
173
189
  let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
174
190
  let filterDict = Stdlib_Option.getOr(Stdlib_Option.flatMap(argsDict["filter"], Stdlib_JSON.Decode.object), {});
175
191
  let strNonEmpty = k => Stdlib_Option.mapOr(Stdlib_Option.flatMap(filterDict[k], Stdlib_JSON.Decode.string), false, s => s.length > 0);
@@ -186,6 +202,9 @@ function Make(P) {
186
202
  if (ownerScope !== undefined) {
187
203
  whereParts.push(jsonTextC(ownerScope[0]) + ` = ` + param(b, ownerScope[1]));
188
204
  }
205
+ if (retiredScope !== undefined) {
206
+ whereParts.push(notRetiredC(retiredScope));
207
+ }
189
208
  let valString = v => {
190
209
  let s = Stdlib_JSON.Decode.string(v);
191
210
  if (s !== undefined) {
@@ -234,7 +253,7 @@ function Make(P) {
234
253
  let cursorValueOf = item => Stdlib_Option.getOr(QueryDbListQuery$ReventlessCore.getFieldString(item, cursorField), QueryDbListQuery$ReventlessCore.getId(item));
235
254
  return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore, false, cursorValueOf);
236
255
  };
237
- let itemsPage = async (readModelName, param$1, id, argsDict, ownerScope) => {
256
+ let itemsPage = async (readModelName, param$1, id, argsDict, ownerScope, retiredScope) => {
238
257
  let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
239
258
  let filterDict = Stdlib_Option.getOr(Stdlib_Option.flatMap(argsDict["filter"], Stdlib_JSON.Decode.object), {});
240
259
  let fstr = k => Stdlib_Option.flatMap(filterDict[k], Stdlib_JSON.Decode.string);
@@ -255,6 +274,9 @@ function Make(P) {
255
274
  if (ownerScope !== undefined) {
256
275
  where.push(jsonTextC(ownerScope[0]) + ` = ` + param(b, ownerScope[1]));
257
276
  }
277
+ if (retiredScope !== undefined) {
278
+ where.push(notRetiredC(retiredScope));
279
+ }
258
280
  Stdlib_Option.forEach(fstr("prefix"), p => {
259
281
  where.push(`starts_with(sub_key, ` + param(b, p) + `)`);
260
282
  });
@@ -316,6 +338,7 @@ function Make(P) {
316
338
  byIds: byIds,
317
339
  idExprC: idExprC,
318
340
  jsonTextC: jsonTextC,
341
+ notRetiredC: notRetiredC,
319
342
  listPage: listPage,
320
343
  itemsPage: itemsPage
321
344
  };