@reventlessdev/reventless-postgres 3.0.0-alpha.11 → 3.0.0-alpha.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +798 -0
- package/README.md +83 -0
- package/lib/bs/.compiler.log +2 -2
- package/lib/bs/ReventlessPostgres.cmt +0 -0
- package/lib/bs/ReventlessPostgres.mlmap +0 -2
- package/lib/bs/compiler-info.json +2 -2
- package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres.ast +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres.res +1 -1
- package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.ast +0 -0
- package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.res +3 -3
- package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.res.mjs +4 -4
- package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/EventLogChangeFeed.ast +0 -0
- package/lib/bs/src/EventLogChangeFeed.res +5 -2
- package/lib/bs/src/EventLogChangeFeed.res.mjs +3 -3
- package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/EventLogStorage_Postgres.ast +0 -0
- package/lib/bs/src/EventLogStorage_Postgres.res +1 -1
- package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/EventLogStorage_Postgres_Ops.ast +0 -0
- package/lib/bs/src/EventLogStorage_Postgres_Ops.res +1 -1
- package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/PgChangeFeed.ast +0 -0
- package/lib/bs/src/PgChangeFeed.res +6 -1
- package/lib/bs/src/PgChangeFeed.res.mjs +6 -6
- package/lib/bs/src/PgDriver-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/PgDriver-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/PgDriver-ReventlessPostgres.cmti +0 -0
- package/lib/bs/src/PgDriver.ast +0 -0
- package/lib/bs/src/PgDriver.iast +0 -0
- package/lib/bs/src/PgSchema-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/PgSchema-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/PgSchema.ast +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres.ast +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres.res +2 -2
- package/lib/bs/src/QueryDbStorage_Postgres.res.mjs +1 -1
- package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres_Ops.ast +0 -0
- package/lib/bs/src/QueryDbStorage_Postgres_Ops.res +1 -1
- package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
- package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
- package/lib/bs/src/QueryEnginePostgres.ast +0 -0
- package/lib/bs/src/QueryEnginePostgres.res +69 -12
- package/lib/bs/src/QueryEnginePostgres.res.mjs +49 -26
- package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
- package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
- package/lib/bs/tests/PostgresIntegrationTest.ast +0 -0
- package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
- package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
- package/lib/bs/tests/QueryEnginePostgresListPageTest.ast +0 -0
- package/lib/bs/tests/QueryEnginePostgresListPageTest.res.mjs +4 -4
- package/lib/ocaml/.compiler.log +2 -2
- package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres.ast +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres.res +1 -1
- package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.ast +0 -0
- package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.res +3 -3
- package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/EventLogChangeFeed.ast +0 -0
- package/lib/ocaml/EventLogChangeFeed.res +5 -2
- package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/EventLogStorage_Postgres.ast +0 -0
- package/lib/ocaml/EventLogStorage_Postgres.res +1 -1
- package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/EventLogStorage_Postgres_Ops.ast +0 -0
- package/lib/ocaml/EventLogStorage_Postgres_Ops.res +1 -1
- package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/PgChangeFeed.ast +0 -0
- package/lib/ocaml/PgChangeFeed.res +6 -1
- package/lib/ocaml/PgDriver-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/PgDriver-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/PgDriver-ReventlessPostgres.cmti +0 -0
- package/lib/ocaml/PgDriver.ast +0 -0
- package/lib/ocaml/PgDriver.iast +0 -0
- package/lib/ocaml/PgSchema-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/PgSchema-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/PgSchema.ast +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres.ast +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres.res +2 -2
- package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres_Ops.ast +0 -0
- package/lib/ocaml/QueryDbStorage_Postgres_Ops.res +1 -1
- package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/QueryEnginePostgres.ast +0 -0
- package/lib/ocaml/QueryEnginePostgres.res +69 -12
- package/lib/ocaml/ReventlessPostgres.cmt +0 -0
- package/package.json +13 -13
- package/rescript.json +2 -1
- package/src/DcbEventLogStorage_Postgres.res +1 -1
- package/src/DcbEventLogStorage_Postgres_Ops.res +3 -3
- package/src/DcbEventLogStorage_Postgres_Ops.res.mjs +4 -4
- package/src/EventLogChangeFeed.res +5 -2
- package/src/EventLogChangeFeed.res.mjs +3 -3
- package/src/EventLogStorage_Postgres.res +1 -1
- package/src/EventLogStorage_Postgres_Ops.res +1 -1
- package/src/PgChangeFeed.res +6 -1
- package/src/PgChangeFeed.res.mjs +6 -6
- package/src/QueryDbStorage_Postgres.res +2 -2
- package/src/QueryDbStorage_Postgres.res.mjs +1 -1
- package/src/QueryDbStorage_Postgres_Ops.res +1 -1
- package/src/QueryEnginePostgres.res +69 -12
- package/src/QueryEnginePostgres.res.mjs +49 -26
- package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmj +0 -0
- package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/PostgresIntegrationTest.ast +0 -0
- package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
- package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmj +0 -0
- package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
- package/lib/ocaml/QueryEnginePostgresListPageTest.ast +0 -0
- package/tests/PostgresIntegrationTest.res.mjs +0 -377
- package/tests/QueryEnginePostgresListPageTest.res.mjs +0 -461
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@reventlessdev/reventless-postgres)
|
|
2
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
3
|
+
[](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)
|
package/lib/bs/.compiler.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#Start(
|
|
2
|
-
#Done(
|
|
1
|
+
#Start(1787354433221)
|
|
2
|
+
#Done(1787354439157)
|
|
Binary file
|
|
@@ -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": "
|
|
5
|
+
"rescript_config_hash": "af2af60a0fa33b8249a96ff2758f36482843126dc4e07b9302842d011e0ad8c6",
|
|
6
6
|
"runtime_path": "/home/runner/work/reventless-core/reventless-core/node_modules/@rescript/runtime",
|
|
7
|
-
"generated_at": "
|
|
7
|
+
"generated_at": "1787354439163"
|
|
8
8
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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->
|
|
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->
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
/*
|
|
380
|
+
/* Stream Not a pure module */
|
|
Binary file
|
|
Binary file
|
|
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
|
|
19
|
-
module
|
|
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
|
|
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,
|
|
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 =
|
|
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,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
@@ -18,7 +18,12 @@
|
|
|
18
18
|
open ReventlessCore
|
|
19
19
|
open Reventless
|
|
20
20
|
|
|
21
|
-
module
|
|
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
|
|
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 =
|
|
10
|
-
let where =
|
|
11
|
-
let sql = `SELECT ` +
|
|
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(
|
|
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 =
|
|
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
|
package/lib/bs/src/PgDriver.ast
CHANGED
|
Binary file
|
package/lib/bs/src/PgDriver.iast
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/bs/src/PgSchema.ast
CHANGED
|
Binary file
|
|
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
|
};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
|
Binary file
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
}
|