@reventlessdev/reventless-postgres 3.0.0-alpha.12 → 3.0.0-alpha.120

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 (134) hide show
  1. package/CHANGELOG.md +870 -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 +2 -2
  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 +3 -3
  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 +5 -2
  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 +1 -1
  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 +6 -1
  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/PgSchema-ReventlessPostgres.cmi +0 -0
  40. package/lib/bs/src/PgSchema-ReventlessPostgres.cmt +0 -0
  41. package/lib/bs/src/PgSchema.ast +0 -0
  42. package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
  43. package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
  44. package/lib/bs/src/QueryDbStorage_Postgres.ast +0 -0
  45. package/lib/bs/src/QueryDbStorage_Postgres.res +2 -2
  46. package/lib/bs/src/QueryDbStorage_Postgres.res.mjs +1 -1
  47. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  48. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  49. package/lib/bs/src/QueryDbStorage_Postgres_Ops.ast +0 -0
  50. package/lib/bs/src/QueryDbStorage_Postgres_Ops.res +1 -1
  51. package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
  52. package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
  53. package/lib/bs/src/QueryEnginePostgres.ast +0 -0
  54. package/lib/bs/src/QueryEnginePostgres.res +69 -12
  55. package/lib/bs/src/QueryEnginePostgres.res.mjs +49 -26
  56. package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
  57. package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
  58. package/lib/bs/tests/PostgresIntegrationTest.ast +0 -0
  59. package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
  60. package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
  61. package/lib/bs/tests/QueryEnginePostgresListPageTest.ast +0 -0
  62. package/lib/bs/tests/QueryEnginePostgresListPageTest.res.mjs +4 -4
  63. package/lib/ocaml/.compiler.log +2 -2
  64. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  65. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  66. package/lib/ocaml/DcbEventLogStorage_Postgres.ast +0 -0
  67. package/lib/ocaml/DcbEventLogStorage_Postgres.res +1 -1
  68. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  69. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  70. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.ast +0 -0
  71. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.res +3 -3
  72. package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
  73. package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
  74. package/lib/ocaml/EventLogChangeFeed.ast +0 -0
  75. package/lib/ocaml/EventLogChangeFeed.res +5 -2
  76. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  77. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  78. package/lib/ocaml/EventLogStorage_Postgres.ast +0 -0
  79. package/lib/ocaml/EventLogStorage_Postgres.res +1 -1
  80. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  81. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  82. package/lib/ocaml/EventLogStorage_Postgres_Ops.ast +0 -0
  83. package/lib/ocaml/EventLogStorage_Postgres_Ops.res +1 -1
  84. package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmi +0 -0
  85. package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmt +0 -0
  86. package/lib/ocaml/PgChangeFeed.ast +0 -0
  87. package/lib/ocaml/PgChangeFeed.res +6 -1
  88. package/lib/ocaml/PgDriver-ReventlessPostgres.cmi +0 -0
  89. package/lib/ocaml/PgDriver-ReventlessPostgres.cmt +0 -0
  90. package/lib/ocaml/PgDriver-ReventlessPostgres.cmti +0 -0
  91. package/lib/ocaml/PgDriver.ast +0 -0
  92. package/lib/ocaml/PgDriver.iast +0 -0
  93. package/lib/ocaml/PgSchema-ReventlessPostgres.cmi +0 -0
  94. package/lib/ocaml/PgSchema-ReventlessPostgres.cmt +0 -0
  95. package/lib/ocaml/PgSchema.ast +0 -0
  96. package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
  97. package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
  98. package/lib/ocaml/QueryDbStorage_Postgres.ast +0 -0
  99. package/lib/ocaml/QueryDbStorage_Postgres.res +2 -2
  100. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  101. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  102. package/lib/ocaml/QueryDbStorage_Postgres_Ops.ast +0 -0
  103. package/lib/ocaml/QueryDbStorage_Postgres_Ops.res +1 -1
  104. package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
  105. package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
  106. package/lib/ocaml/QueryEnginePostgres.ast +0 -0
  107. package/lib/ocaml/QueryEnginePostgres.res +69 -12
  108. package/lib/ocaml/ReventlessPostgres.cmt +0 -0
  109. package/package.json +13 -13
  110. package/rescript.json +2 -1
  111. package/src/DcbEventLogStorage_Postgres.res +1 -1
  112. package/src/DcbEventLogStorage_Postgres_Ops.res +3 -3
  113. package/src/DcbEventLogStorage_Postgres_Ops.res.mjs +4 -4
  114. package/src/EventLogChangeFeed.res +5 -2
  115. package/src/EventLogChangeFeed.res.mjs +3 -3
  116. package/src/EventLogStorage_Postgres.res +1 -1
  117. package/src/EventLogStorage_Postgres_Ops.res +1 -1
  118. package/src/PgChangeFeed.res +6 -1
  119. package/src/PgChangeFeed.res.mjs +6 -6
  120. package/src/QueryDbStorage_Postgres.res +2 -2
  121. package/src/QueryDbStorage_Postgres.res.mjs +1 -1
  122. package/src/QueryDbStorage_Postgres_Ops.res +1 -1
  123. package/src/QueryEnginePostgres.res +69 -12
  124. package/src/QueryEnginePostgres.res.mjs +49 -26
  125. package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
  126. package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmj +0 -0
  127. package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
  128. package/lib/ocaml/PostgresIntegrationTest.ast +0 -0
  129. package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
  130. package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmj +0 -0
  131. package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
  132. package/lib/ocaml/QueryEnginePostgresListPageTest.ast +0 -0
  133. package/tests/PostgresIntegrationTest.res.mjs +0 -377
  134. package/tests/QueryEnginePostgresListPageTest.res.mjs +0 -461
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ [![npm](https://img.shields.io/npm/v/@reventlessdev/reventless-postgres.svg?label=npm)](https://www.npmjs.com/package/@reventlessdev/reventless-postgres)
2
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
3
+ [![Docs](https://img.shields.io/badge/docs-reventless.dev-blue)](https://docs.reventless.dev)
4
+
5
+ # @reventlessdev/reventless-postgres
6
+
7
+ > ⚠️ **Alpha.** APIs and on-disk formats can change without notice between releases.
8
+ > Pin exact versions and expect breaking changes.
9
+
10
+ The **Postgres storage backend** for [Reventless](https://docs.reventless.dev) — a
11
+ spec-driven, event-sourced CQRS framework written in [ReScript](https://rescript-lang.org).
12
+ It implements the core storage adapters (classic and DCB event logs, the query
13
+ database, and change feeds) over a plain Postgres connection. It carries **no**
14
+ IaC/provider SDK dependency: it connects by connection string, so it runs against
15
+ RDS/Aurora, any managed provider, or a local container.
16
+
17
+ ## What it provides
18
+
19
+ ReScript modules, consumed by adding the package to your `rescript.json` `dependencies`:
20
+
21
+ - **`PgDriver`** — the [`node-postgres`](https://node-postgres.com) (`pg`) binding
22
+ (pool/client). Accepts a full `connectionString` or discrete fields, with SSL
23
+ and rotating-secret (password-provider) support.
24
+ - **`PgSchema`** — an idempotent schema + migration runner (`ensureSchema(pool)`),
25
+ safe to run on every startup and concurrently across processes.
26
+ - **`EventLogStorage_Postgres`** — the classic optimistic-concurrency event log
27
+ (`event_log` / `snapshot`).
28
+ - **`DcbEventLogStorage_Postgres`** — the DCB (Dynamic Consistency Boundary) event
29
+ log with a global position sequence and a concurrency-critical `dcb_append`.
30
+ - **`QueryDbStorage_Postgres`** — read-side storage over JSONB `qdb_<name>` tables.
31
+ - **`QueryEnginePostgres`** — compiles the core QueryEngine query/scan AST down to
32
+ SQL, pushing filters into the database.
33
+ - **`EventLogChangeFeed`** / **`PgChangeFeed`** — checkpointed change feeds for the
34
+ classic and DCB logs, using an `xmin`-fenced read plus `LISTEN`/`NOTIFY`
35
+ (`pg_notify`) for near-real-time wakeup. A documented public consumer surface.
36
+
37
+ The `*_Ops` modules hold the runtime-pure operations (no Pulumi import) so a
38
+ deployed handler's ESM graph stays clean; the wrapper modules add the deploy-time
39
+ adapter shape.
40
+
41
+ ## Where it fits
42
+
43
+ `reventless-postgres` is a storage **adapter** for the Reventless framework.
44
+ [`reventless-core`](https://www.npmjs.com/package/@reventlessdev/reventless-core)
45
+ is provider-agnostic; this package supplies the Postgres implementation of its
46
+ event-log, query-db, and change-feed interfaces:
47
+
48
+ - [`@reventlessdev/reventless-aws`](https://www.npmjs.com/package/@reventlessdev/reventless-aws) — AWS (DynamoDB, Lambda, SQS, SNS, S3); can delegate event logs to this package for managed Postgres
49
+ - [`@reventlessdev/reventless-postgres`](https://www.npmjs.com/package/@reventlessdev/reventless-postgres) — **this package**
50
+ - [`@reventlessdev/reventless-local`](https://www.npmjs.com/package/@reventlessdev/reventless-local) — in-memory / SQLite platform that uses this storage against a local Postgres
51
+
52
+ Because it depends only on `pg` (plus
53
+ [`reventless-core`](https://www.npmjs.com/package/@reventlessdev/reventless-core),
54
+ [`reventless-infra`](https://www.npmjs.com/package/@reventlessdev/reventless-infra),
55
+ and [`reventless-spec`](https://www.npmjs.com/package/@reventlessdev/reventless-spec)),
56
+ the platform package that embeds it owns provisioning; this package only needs a
57
+ reachable Postgres.
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ pnpm add @reventlessdev/reventless-postgres
63
+ ```
64
+
65
+ Then register it as a ReScript dependency in `rescript.json`:
66
+
67
+ ```json
68
+ {
69
+ "dependencies": ["@reventlessdev/reventless-postgres"]
70
+ }
71
+ ```
72
+
73
+ Requires ReScript `^12.3.0` (peer dependency).
74
+
75
+ ## Links
76
+
77
+ - 📚 Documentation — [docs.reventless.dev](https://docs.reventless.dev)
78
+ - 📦 Repository — [ReventlessDev/reventless-core](https://github.com/ReventlessDev/reventless-core)
79
+ - 📋 [Changelog](./CHANGELOG.md)
80
+
81
+ ## License
82
+
83
+ [Apache-2.0](https://opensource.org/licenses/Apache-2.0)
@@ -1,2 +1,2 @@
1
- #Start(1783626440149)
2
- #Done(1783626442687)
1
+ #Start(1788547290814)
2
+ #Done(1788547296008)
Binary file
@@ -7,8 +7,6 @@ EventLogStorage_Postgres_Ops
7
7
  PgChangeFeed
8
8
  PgDriver
9
9
  PgSchema
10
- PostgresIntegrationTest
11
10
  QueryDbStorage_Postgres
12
11
  QueryDbStorage_Postgres_Ops
13
12
  QueryEnginePostgres
14
- QueryEnginePostgresListPageTest
@@ -2,7 +2,7 @@
2
2
  "version": "12.3.0",
3
3
  "bsc_path": "/home/runner/work/reventless-core/reventless-core/node_modules/@rescript/linux-x64/bin/bsc.exe",
4
4
  "bsc_hash": "75e3a59c95cc953608fd3dd6ea6ed68141bba4414a20a52f114261b8a48385fa",
5
- "rescript_config_hash": "48ea15db3d741cf15a2ff49dd0b47d86badf2b548ecb3859a896faf1e3a82a15",
5
+ "rescript_config_hash": "af2af60a0fa33b8249a96ff2758f36482843126dc4e07b9302842d011e0ad8c6",
6
6
  "runtime_path": "/home/runner/work/reventless-core/reventless-core/node_modules/@rescript/runtime",
7
- "generated_at": "1783626442705"
7
+ "generated_at": "1788547296009"
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
@@ -67,7 +67,7 @@ let rowToEvent = (row: dict<JSON.t>): DcbEventLog_Adapter.rawSequencedEvent => {
67
67
  | _ => ""
68
68
  }
69
69
  let meta = switch row->Dict.get("meta") {
70
- | Some(metaJson) => metaJson->S.parseJsonOrThrow(Reventless.Message.metaSchema)
70
+ | Some(metaJson) => metaJson->Reventless.Util_Sury.fromJson(Reventless.Message.metaSchema)
71
71
  | None => JsError.throwWithMessage("missing meta column in dcb_event row")
72
72
  }
73
73
  {
@@ -236,7 +236,7 @@ let makeOps = (
236
236
  JSON.Array(ev.tags->Array.map(t => JSON.Encode.string(t.key ++ "=" ++ t.value))),
237
237
  ),
238
238
  ("data", ev.data),
239
- ("meta", ev.meta->S.reverseConvertToJsonOrThrow(Reventless.Message.metaSchema)),
239
+ ("meta", ev.meta->Reventless.Util_Sury.toJson(Reventless.Message.metaSchema)),
240
240
  ]),
241
241
  )
242
242
  ),
@@ -1,6 +1,5 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
- import * as S from "sury/src/S.res.mjs";
4
3
  import * as Stream from "@reventlessdev/rescript-effect/src/Stream.res.mjs";
5
4
  import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
6
5
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
@@ -11,6 +10,7 @@ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
11
10
  import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
12
11
  import * as Message$Reventless from "@reventlessdev/reventless-spec/src/types/Message.res.mjs";
13
12
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
13
+ import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
14
14
  import * as PgDriver$ReventlessPostgres from "./PgDriver.res.mjs";
15
15
 
16
16
  function strategyParam(s) {
@@ -68,7 +68,7 @@ function rowToEvent(row) {
68
68
  }
69
69
  };
70
70
  let metaJson = row["meta"];
71
- let meta = metaJson !== undefined ? S.parseJsonOrThrow(metaJson, Message$Reventless.metaSchema) : Stdlib_JsError.throwWithMessage("missing meta column in dcb_event row");
71
+ let meta = metaJson !== undefined ? Util_Sury$Reventless.fromJson(metaJson, Message$Reventless.metaSchema) : Stdlib_JsError.throwWithMessage("missing meta column in dcb_event row");
72
72
  return {
73
73
  position: str("cursor"),
74
74
  eventType: str("event_type"),
@@ -226,7 +226,7 @@ function makeOps(pool, name, param, param$1, param$2, param$3, lockStrategyOpt,
226
226
  ],
227
227
  [
228
228
  "meta",
229
- S.reverseConvertToJsonOrThrow(ev.meta, Message$Reventless.metaSchema)
229
+ Util_Sury$Reventless.toJson(ev.meta, Message$Reventless.metaSchema)
230
230
  ]
231
231
  ])));
232
232
  let conditionParam;
@@ -377,4 +377,4 @@ export {
377
377
  countAll,
378
378
  makeOps,
379
379
  }
380
- /* S Not a pure module */
380
+ /* Stream Not a pure module */
Binary file
@@ -15,8 +15,11 @@
15
15
  // later commit) is never skipped by a checkpointing reader. `global_seq` alone,
16
16
  // being an IDENTITY assigned at INSERT, could NOT provide that guarantee.
17
17
 
18
- // Reuse the shared `<a>:<b>` cursor codec from the DCB storage — identical format.
19
- module Dcb = DcbEventLogStorage_Postgres
18
+ // Reuse the shared `<a>:<b>` cursor codec from the DCB storage — identical
19
+ // format. Point at the runtime-pure `_Ops` module (no `@pulumi/pulumi`): this
20
+ // feed is drained by the deployed change-feed relay Lambda, whose cold-start
21
+ // graph must stay Pulumi-free.
22
+ module Dcb = DcbEventLogStorage_Postgres_Ops
20
23
 
21
24
  // One classic event as the feed surfaces it. `payload` is the stored event JSON
22
25
  // verbatim; a bus/topic relay transforms it into its target shape (e.g. the AWS
@@ -3,7 +3,7 @@
3
3
  import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
4
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
5
  import * as PgDriver$ReventlessPostgres from "./PgDriver.res.mjs";
6
- import * as DcbEventLogStorage_Postgres$ReventlessPostgres from "./DcbEventLogStorage_Postgres.res.mjs";
6
+ import * as DcbEventLogStorage_Postgres_Ops$ReventlessPostgres from "./DcbEventLogStorage_Postgres_Ops.res.mjs";
7
7
 
8
8
  let selectColumns = "\n lpad(transaction_id::text, 20, '0') || ':' || lpad(global_seq::text, 20, '0') AS cursor,\n aggregate_id,\n seq_nr,\n payload,\n msg_id\n";
9
9
 
@@ -43,7 +43,7 @@ async function readBatch(pool, logName, after, limitOpt) {
43
43
  let limit = limitOpt !== undefined ? limitOpt : 500;
44
44
  let params = [logName];
45
45
  let where = "log_name = $1 AND transaction_id < pg_snapshot_xmin(pg_current_snapshot())";
46
- let match = Stdlib_Option.flatMap(after, DcbEventLogStorage_Postgres$ReventlessPostgres.decodeCursor);
46
+ let match = Stdlib_Option.flatMap(after, DcbEventLogStorage_Postgres_Ops$ReventlessPostgres.decodeCursor);
47
47
  if (match !== undefined) {
48
48
  params.push(match[0]);
49
49
  params.push(match[1]);
@@ -72,7 +72,7 @@ async function loadCheckpoint(pool, subscriber) {
72
72
  }
73
73
 
74
74
  async function saveCheckpoint(pool, subscriber, cursor) {
75
- let match = DcbEventLogStorage_Postgres$ReventlessPostgres.decodeCursor(cursor);
75
+ let match = DcbEventLogStorage_Postgres_Ops$ReventlessPostgres.decodeCursor(cursor);
76
76
  if (match !== undefined) {
77
77
  await PgDriver$ReventlessPostgres.query(pool, "INSERT INTO event_log_subscription(subscriber, last_tx, last_global_seq)\n VALUES ($1, $2::xid8, $3::bigint)\n ON CONFLICT (subscriber)\n DO UPDATE SET last_tx = EXCLUDED.last_tx, last_global_seq = EXCLUDED.last_global_seq", [
78
78
  subscriber,
@@ -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
 
Binary file
@@ -18,7 +18,12 @@
18
18
  open ReventlessCore
19
19
  open Reventless
20
20
 
21
- module Dcb = DcbEventLogStorage_Postgres
21
+ // Runtime-pure ops module (no `@pulumi/pulumi`) — this feed is drained by the
22
+ // deployed change-feed relay Lambda, so its cold-start graph must stay
23
+ // Pulumi-free. `_Ops` carries every helper used below (mkBuilder,
24
+ // buildReadWhere, decodeCursor, selectColumns, rowToEvent); the deploy-time
25
+ // DcbEventLogStorage_Postgres only `include`s it and adds the Pulumi adapter.
26
+ module Dcb = DcbEventLogStorage_Postgres_Ops
22
27
 
23
28
  // One page of the feed. `events` are in (transaction_id, position) order;
24
29
  // `cursor` is the checkpoint to persist and to pass as the next `after` (None
@@ -2,15 +2,15 @@
2
2
 
3
3
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
4
  import * as PgDriver$ReventlessPostgres from "./PgDriver.res.mjs";
5
- import * as DcbEventLogStorage_Postgres$ReventlessPostgres from "./DcbEventLogStorage_Postgres.res.mjs";
5
+ import * as DcbEventLogStorage_Postgres_Ops$ReventlessPostgres from "./DcbEventLogStorage_Postgres_Ops.res.mjs";
6
6
 
7
7
  async function readBatch(pool, logName, after, limitOpt) {
8
8
  let limit = limitOpt !== undefined ? limitOpt : 500;
9
- let b = DcbEventLogStorage_Postgres$ReventlessPostgres.mkBuilder();
10
- let where = DcbEventLogStorage_Postgres$ReventlessPostgres.buildReadWhere(b, logName, [], Stdlib_Option.flatMap(after, DcbEventLogStorage_Postgres$ReventlessPostgres.decodeCursor), true);
11
- let sql = `SELECT ` + DcbEventLogStorage_Postgres$ReventlessPostgres.selectColumns + ` FROM dcb_event WHERE ` + where + ` ORDER BY transaction_id ASC, position ASC LIMIT ` + limit.toString();
9
+ let b = DcbEventLogStorage_Postgres_Ops$ReventlessPostgres.mkBuilder();
10
+ let where = DcbEventLogStorage_Postgres_Ops$ReventlessPostgres.buildReadWhere(b, logName, [], Stdlib_Option.flatMap(after, DcbEventLogStorage_Postgres_Ops$ReventlessPostgres.decodeCursor), true);
11
+ let sql = `SELECT ` + DcbEventLogStorage_Postgres_Ops$ReventlessPostgres.selectColumns + ` FROM dcb_event WHERE ` + where + ` ORDER BY transaction_id ASC, position ASC LIMIT ` + limit.toString();
12
12
  let rows = await PgDriver$ReventlessPostgres.query(pool, sql, b.params);
13
- let events = rows.map(DcbEventLogStorage_Postgres$ReventlessPostgres.rowToEvent);
13
+ let events = rows.map(DcbEventLogStorage_Postgres_Ops$ReventlessPostgres.rowToEvent);
14
14
  let last = events[events.length - 1 | 0];
15
15
  let cursor = last !== undefined ? last.position : undefined;
16
16
  return {
@@ -31,7 +31,7 @@ async function loadCheckpoint(pool, subscriber) {
31
31
  }
32
32
 
33
33
  async function saveCheckpoint(pool, subscriber, cursor) {
34
- let match = DcbEventLogStorage_Postgres$ReventlessPostgres.decodeCursor(cursor);
34
+ let match = DcbEventLogStorage_Postgres_Ops$ReventlessPostgres.decodeCursor(cursor);
35
35
  if (match !== undefined) {
36
36
  await PgDriver$ReventlessPostgres.query(pool, "INSERT INTO dcb_subscription(subscriber, last_tx, last_position)\n VALUES ($1, $2::xid8, $3::bigint)\n ON CONFLICT (subscriber)\n DO UPDATE SET last_tx = EXCLUDED.last_tx, last_position = EXCLUDED.last_position", [
37
37
  subscriber,
Binary file
Binary file
Binary file
@@ -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
@@ -37,6 +37,6 @@ module Make = (P: {let pool: PgDriver.pool}) => {
37
37
  ~ttl as _=?,
38
38
  ~api as _,
39
39
  ~apiRole as _,
40
- ~opts as _,
40
+ ~owner as _, ~opts as _,
41
41
  ) => makeStorage(~pool=P.pool, ~name, ~indexes, ~subIdField)
42
42
  }
@@ -12,7 +12,7 @@ function makeStorage(pool, name, indexes, subIdField) {
12
12
  }
13
13
 
14
14
  function Make(P) {
15
- let make = (name, indexes, subIdField, param, param$1, param$2, param$3) => makeStorage(P.pool, name, indexes, subIdField);
15
+ let make = (name, indexes, subIdField, param, param$1, param$2, param$3, param$4) => makeStorage(P.pool, name, indexes, subIdField);
16
16
  return {
17
17
  make: make
18
18
  };
@@ -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
@@ -63,18 +63,18 @@ let subIdSql = (b: paramBuilder, field: string, cmp: QueryEngine.SubId.comparato
63
63
  }
64
64
  }
65
65
 
66
- let notExpired = QueryDbStorage_Postgres.notExpiredClause
66
+ let notExpired = QueryDbStorage_Postgres_Ops.notExpiredClause
67
67
 
68
68
  // Add `id` (from partition_key) when absent — matches allRows/scan in the SQLite
69
69
  // engine; partition-key queries return items as stored.
70
70
  let decodeRow = (~withId, row: dict<JSON.t>): JSON.t => {
71
- let item = QueryDbStorage_Postgres.decodeItem(row)
71
+ let item = QueryDbStorage_Postgres_Ops.decodeItem(row)
72
72
  if withId {
73
73
  let pk = switch row->Dict.get("partition_key") {
74
74
  | Some(JSON.String(s)) => s
75
75
  | _ => ""
76
76
  }
77
- QueryDbStorage_Postgres.withId(item, pk)
77
+ QueryDbStorage_Postgres_Ops.withId(item, pk)
78
78
  } else {
79
79
  item
80
80
  }
@@ -90,7 +90,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
90
90
  ~ascending=?,
91
91
  ~limit=?,
92
92
  ): array<JSON.t> => {
93
- let table = QueryDbStorage_Postgres.tableName(readModelName)
93
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
94
94
  let keyStr = switch key {
95
95
  | Some(k) => k
96
96
  | None => valueToText(id)
@@ -114,7 +114,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
114
114
  }
115
115
 
116
116
  let scan = async (~readModelName, ~filterConfigs, ~limit): array<JSON.t> => {
117
- let table = QueryDbStorage_Postgres.tableName(readModelName)
117
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
118
118
  let b = {params: []}
119
119
  let where = [notExpired]
120
120
  filterConfigs->Array.forEach(((field, cmp, v)) => where->Array.push(filterSql(b, field, cmp, v)))
@@ -138,7 +138,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
138
138
  // (`ensureIndexes`) instead of scanning. String comparison mirrors the
139
139
  // resolver's JS path (which only matched string-typed fields).
140
140
  let indexLookup = async (~readModelName, field: string, value: string): array<JSON.t> => {
141
- let table = QueryDbStorage_Postgres.tableName(readModelName)
141
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
142
142
  let b = {params: []}
143
143
  let sql = `SELECT partition_key, item FROM ${table} WHERE ${col(field)} = ${b->param(
144
144
  JSON.Encode.string(value),
@@ -154,7 +154,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
154
154
  if ids->Array.length == 0 {
155
155
  []
156
156
  } else {
157
- let table = QueryDbStorage_Postgres.tableName(readModelName)
157
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
158
158
  let b = {params: []}
159
159
  let idsJson = JSON.Encode.array(ids->Array.map(JSON.Encode.string))
160
160
  let sql = `SELECT partition_key, item FROM ${table} WHERE partition_key = ANY(${b->param(
@@ -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
@@ -183,8 +216,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
183
216
  ~argsDict: dict<JSON.t>,
184
217
  ~capability: GraphQL_FragmentGenerator.serverCapability,
185
218
  ~labelField as _,
219
+ ~ownerScope: option<(string, string)>=?,
220
+ ~retiredScope: option<Reventless.OwnerScope.retiredScope>=?,
186
221
  ): option<JSON.t> => {
187
- let table = QueryDbStorage_Postgres.tableName(readModelName)
222
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
188
223
  let filterDict =
189
224
  argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
190
225
  let strNonEmpty = k =>
@@ -210,6 +245,19 @@ module Make = (P: {let pool: PgDriver.pool}) => {
210
245
  } else {
211
246
  let b = {params: []}
212
247
  let whereParts = [notExpired]
248
+ // Into the SQL, before the LIMIT — narrowing the returned page instead
249
+ // would hand back fewer rows than asked for while still claiming a next
250
+ // page. Mirrors the SQLite push-down; the parity harness holds the two
251
+ // and the shared spec to the same answer.
252
+ switch ownerScope {
253
+ | Some((field, required)) =>
254
+ whereParts->Array.push(`${jsonTextC(field)} = ${b->param(JSON.Encode.string(required))}`)
255
+ | None => ()
256
+ }
257
+ switch retiredScope {
258
+ | Some(scope) => whereParts->Array.push(notRetiredC(scope))
259
+ | None => ()
260
+ }
213
261
  let valString = v =>
214
262
  switch v->JSON.Decode.string {
215
263
  | Some(s) => Some(s)
@@ -309,8 +357,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
309
357
  ~subIdField as _: string,
310
358
  ~id: string,
311
359
  ~argsDict: dict<JSON.t>,
360
+ ~ownerScope: option<(string, string)>=?,
361
+ ~retiredScope: option<Reventless.OwnerScope.retiredScope>=?,
312
362
  ): JSON.t => {
313
- let table = QueryDbStorage_Postgres.tableName(readModelName)
363
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
314
364
  let filterDict =
315
365
  argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
316
366
  let fstr = k => filterDict->Dict.get(k)->Option.flatMap(JSON.Decode.string)
@@ -324,6 +374,16 @@ module Make = (P: {let pool: PgDriver.pool}) => {
324
374
  let sk = `sub_key COLLATE "C"`
325
375
  let b = {params: []}
326
376
  let where = [`partition_key = ${b->param(JSON.Encode.string(id))}`, notExpired]
377
+ // Before the keyset window and the LIMIT, for the same reason as `listPage`.
378
+ switch ownerScope {
379
+ | Some((field, required)) =>
380
+ where->Array.push(`${jsonTextC(field)} = ${b->param(JSON.Encode.string(required))}`)
381
+ | None => ()
382
+ }
383
+ switch retiredScope {
384
+ | Some(scope) => where->Array.push(notRetiredC(scope))
385
+ | None => ()
386
+ }
327
387
  fstr("prefix")->Option.forEach(p =>
328
388
  where->Array.push(`starts_with(sub_key, ${b->param(JSON.Encode.string(p))})`)
329
389
  )
@@ -380,7 +440,4 @@ module Make = (P: {let pool: PgDriver.pool}) => {
380
440
  },
381
441
  })
382
442
  }
383
-
384
- let make: QueryDb_Adapter.queryEngineMaker = _allQueryDbs =>
385
- Pulumi.Output.make({QueryEngine.scan, query})
386
443
  }