@reventlessdev/reventless-postgres 3.0.0-alpha.10 → 3.0.0-alpha.100
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 +722 -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_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/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_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/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 +1 -1
- 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/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 +26 -12
- package/lib/bs/src/QueryEnginePostgres.res.mjs +26 -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_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/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_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/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 +1 -1
- 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/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 +26 -12
- package/lib/ocaml/ReventlessPostgres.cmt +0 -0
- package/package.json +11 -11
- package/rescript.json +2 -1
- package/src/EventLogChangeFeed.res +5 -2
- package/src/EventLogChangeFeed.res.mjs +3 -3
- package/src/PgChangeFeed.res +6 -1
- package/src/PgChangeFeed.res.mjs +6 -6
- package/src/QueryDbStorage_Postgres.res +1 -1
- package/src/QueryDbStorage_Postgres.res.mjs +1 -1
- package/src/QueryEnginePostgres.res +26 -12
- package/src/QueryEnginePostgres.res.mjs +26 -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(1786782398524)
|
|
2
|
+
#Done(1786782404708)
|
|
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": "1786782404713"
|
|
8
8
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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
|
|
@@ -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
|
|
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(
|
|
@@ -183,8 +183,9 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
183
183
|
~argsDict: dict<JSON.t>,
|
|
184
184
|
~capability: GraphQL_FragmentGenerator.serverCapability,
|
|
185
185
|
~labelField as _,
|
|
186
|
+
~ownerScope: option<(string, string)>=?,
|
|
186
187
|
): option<JSON.t> => {
|
|
187
|
-
let table =
|
|
188
|
+
let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
|
|
188
189
|
let filterDict =
|
|
189
190
|
argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
|
|
190
191
|
let strNonEmpty = k =>
|
|
@@ -210,6 +211,15 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
210
211
|
} else {
|
|
211
212
|
let b = {params: []}
|
|
212
213
|
let whereParts = [notExpired]
|
|
214
|
+
// Into the SQL, before the LIMIT — narrowing the returned page instead
|
|
215
|
+
// would hand back fewer rows than asked for while still claiming a next
|
|
216
|
+
// page. Mirrors the SQLite push-down; the parity harness holds the two
|
|
217
|
+
// and the shared spec to the same answer.
|
|
218
|
+
switch ownerScope {
|
|
219
|
+
| Some((field, required)) =>
|
|
220
|
+
whereParts->Array.push(`${jsonTextC(field)} = ${b->param(JSON.Encode.string(required))}`)
|
|
221
|
+
| None => ()
|
|
222
|
+
}
|
|
213
223
|
let valString = v =>
|
|
214
224
|
switch v->JSON.Decode.string {
|
|
215
225
|
| Some(s) => Some(s)
|
|
@@ -309,8 +319,9 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
309
319
|
~subIdField as _: string,
|
|
310
320
|
~id: string,
|
|
311
321
|
~argsDict: dict<JSON.t>,
|
|
322
|
+
~ownerScope: option<(string, string)>=?,
|
|
312
323
|
): JSON.t => {
|
|
313
|
-
let table =
|
|
324
|
+
let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
|
|
314
325
|
let filterDict =
|
|
315
326
|
argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
|
|
316
327
|
let fstr = k => filterDict->Dict.get(k)->Option.flatMap(JSON.Decode.string)
|
|
@@ -324,6 +335,12 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
324
335
|
let sk = `sub_key COLLATE "C"`
|
|
325
336
|
let b = {params: []}
|
|
326
337
|
let where = [`partition_key = ${b->param(JSON.Encode.string(id))}`, notExpired]
|
|
338
|
+
// Before the keyset window and the LIMIT, for the same reason as `listPage`.
|
|
339
|
+
switch ownerScope {
|
|
340
|
+
| Some((field, required)) =>
|
|
341
|
+
where->Array.push(`${jsonTextC(field)} = ${b->param(JSON.Encode.string(required))}`)
|
|
342
|
+
| None => ()
|
|
343
|
+
}
|
|
327
344
|
fstr("prefix")->Option.forEach(p =>
|
|
328
345
|
where->Array.push(`starts_with(sub_key, ${b->param(JSON.Encode.string(p))})`)
|
|
329
346
|
)
|
|
@@ -380,7 +397,4 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
380
397
|
},
|
|
381
398
|
})
|
|
382
399
|
}
|
|
383
|
-
|
|
384
|
-
let make: QueryDb_Adapter.queryEngineMaker = _allQueryDbs =>
|
|
385
|
-
Pulumi.Output.make({QueryEngine.scan, query})
|
|
386
400
|
}
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
|
|
4
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
|
-
import * as Pulumi from "@pulumi/pulumi";
|
|
6
5
|
import * as Stdlib_Nullable from "@rescript/runtime/lib/es6/Stdlib_Nullable.js";
|
|
7
6
|
import * as PgDriver$ReventlessPostgres from "./PgDriver.res.mjs";
|
|
8
7
|
import * as QueryDbListQuery$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/QueryDbListQuery.res.mjs";
|
|
9
|
-
import * as
|
|
8
|
+
import * as QueryDbStorage_Postgres_Ops$ReventlessPostgres from "./QueryDbStorage_Postgres_Ops.res.mjs";
|
|
10
9
|
|
|
11
10
|
function valueToText(v) {
|
|
12
11
|
switch (v.TAG) {
|
|
@@ -105,25 +104,25 @@ function subIdSql(b, field, cmp, v) {
|
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
function decodeRow(withId, row) {
|
|
108
|
-
let item =
|
|
107
|
+
let item = QueryDbStorage_Postgres_Ops$ReventlessPostgres.decodeItem(row);
|
|
109
108
|
if (!withId) {
|
|
110
109
|
return item;
|
|
111
110
|
}
|
|
112
111
|
let match = row["partition_key"];
|
|
113
112
|
let pk = typeof match === "string" ? match : "";
|
|
114
|
-
return
|
|
113
|
+
return QueryDbStorage_Postgres_Ops$ReventlessPostgres.withId(item, pk);
|
|
115
114
|
}
|
|
116
115
|
|
|
117
116
|
function Make(P) {
|
|
118
117
|
let query = async (readModelName, key, id, subIdConfig, filterConfigs, ascending, limit) => {
|
|
119
|
-
let table =
|
|
118
|
+
let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
|
|
120
119
|
let keyStr = key !== undefined ? key : valueToText(id);
|
|
121
120
|
let b = {
|
|
122
121
|
params: []
|
|
123
122
|
};
|
|
124
123
|
let where = [
|
|
125
124
|
`partition_key = ` + param(b, keyStr),
|
|
126
|
-
|
|
125
|
+
QueryDbStorage_Postgres_Ops$ReventlessPostgres.notExpiredClause
|
|
127
126
|
];
|
|
128
127
|
if (subIdConfig !== undefined) {
|
|
129
128
|
where.push(subIdSql(b, subIdConfig[0], subIdConfig[1], subIdConfig[2]));
|
|
@@ -137,11 +136,11 @@ function Make(P) {
|
|
|
137
136
|
return (await PgDriver$ReventlessPostgres.query(P.pool, sql, b.params)).map(extra => decodeRow(false, extra));
|
|
138
137
|
};
|
|
139
138
|
let scan = async (readModelName, filterConfigs, limit) => {
|
|
140
|
-
let table =
|
|
139
|
+
let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
|
|
141
140
|
let b = {
|
|
142
141
|
params: []
|
|
143
142
|
};
|
|
144
|
-
let where = [
|
|
143
|
+
let where = [QueryDbStorage_Postgres_Ops$ReventlessPostgres.notExpiredClause];
|
|
145
144
|
filterConfigs.forEach(param => {
|
|
146
145
|
where.push(filterSql(b, param[0], param[1], param[2]));
|
|
147
146
|
});
|
|
@@ -149,29 +148,29 @@ function Make(P) {
|
|
|
149
148
|
return (await PgDriver$ReventlessPostgres.query(P.pool, sql, b.params)).map(extra => decodeRow(true, extra));
|
|
150
149
|
};
|
|
151
150
|
let indexLookup = async (readModelName, field, value) => {
|
|
152
|
-
let table =
|
|
151
|
+
let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
|
|
153
152
|
let b = {
|
|
154
153
|
params: []
|
|
155
154
|
};
|
|
156
|
-
let sql = `SELECT partition_key, item FROM ` + table + ` WHERE ` + col(field) + ` = ` + param(b, value) + ` AND ` +
|
|
155
|
+
let sql = `SELECT partition_key, item FROM ` + table + ` WHERE ` + col(field) + ` = ` + param(b, value) + ` AND ` + QueryDbStorage_Postgres_Ops$ReventlessPostgres.notExpiredClause;
|
|
157
156
|
return (await PgDriver$ReventlessPostgres.query(P.pool, sql, b.params)).map(extra => decodeRow(true, extra));
|
|
158
157
|
};
|
|
159
158
|
let byIds = async (readModelName, ids) => {
|
|
160
159
|
if (ids.length === 0) {
|
|
161
160
|
return [];
|
|
162
161
|
}
|
|
163
|
-
let table =
|
|
162
|
+
let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
|
|
164
163
|
let b = {
|
|
165
164
|
params: []
|
|
166
165
|
};
|
|
167
166
|
let idsJson = ids.map(prim => prim);
|
|
168
|
-
let sql = `SELECT partition_key, item FROM ` + table + ` WHERE partition_key = ANY(` + param(b, idsJson) + `::text[]) AND ` +
|
|
167
|
+
let sql = `SELECT partition_key, item FROM ` + table + ` WHERE partition_key = ANY(` + param(b, idsJson) + `::text[]) AND ` + QueryDbStorage_Postgres_Ops$ReventlessPostgres.notExpiredClause;
|
|
169
168
|
return (await PgDriver$ReventlessPostgres.query(P.pool, sql, b.params)).map(extra => decodeRow(true, extra));
|
|
170
169
|
};
|
|
171
170
|
let idExprC = `COALESCE(item->>'id', partition_key) COLLATE "C"`;
|
|
172
171
|
let jsonTextC = field => col(field) + ` COLLATE "C"`;
|
|
173
|
-
let listPage = async (readModelName, argsDict, capability, param$1) => {
|
|
174
|
-
let table =
|
|
172
|
+
let listPage = async (readModelName, argsDict, capability, param$1, ownerScope) => {
|
|
173
|
+
let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
|
|
175
174
|
let filterDict = Stdlib_Option.getOr(Stdlib_Option.flatMap(argsDict["filter"], Stdlib_JSON.Decode.object), {});
|
|
176
175
|
let strNonEmpty = k => Stdlib_Option.mapOr(Stdlib_Option.flatMap(filterDict[k], Stdlib_JSON.Decode.string), false, s => s.length > 0);
|
|
177
176
|
let hasIds = Stdlib_Option.mapOr(Stdlib_Option.flatMap(filterDict["ids"], Stdlib_JSON.Decode.array), false, a => a.length !== 0);
|
|
@@ -183,7 +182,10 @@ function Make(P) {
|
|
|
183
182
|
let b = {
|
|
184
183
|
params: []
|
|
185
184
|
};
|
|
186
|
-
let whereParts = [
|
|
185
|
+
let whereParts = [QueryDbStorage_Postgres_Ops$ReventlessPostgres.notExpiredClause];
|
|
186
|
+
if (ownerScope !== undefined) {
|
|
187
|
+
whereParts.push(jsonTextC(ownerScope[0]) + ` = ` + param(b, ownerScope[1]));
|
|
188
|
+
}
|
|
187
189
|
let valString = v => {
|
|
188
190
|
let s = Stdlib_JSON.Decode.string(v);
|
|
189
191
|
if (s !== undefined) {
|
|
@@ -232,8 +234,8 @@ function Make(P) {
|
|
|
232
234
|
let cursorValueOf = item => Stdlib_Option.getOr(QueryDbListQuery$ReventlessCore.getFieldString(item, cursorField), QueryDbListQuery$ReventlessCore.getId(item));
|
|
233
235
|
return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore, false, cursorValueOf);
|
|
234
236
|
};
|
|
235
|
-
let itemsPage = async (readModelName, param$1, id, argsDict) => {
|
|
236
|
-
let table =
|
|
237
|
+
let itemsPage = async (readModelName, param$1, id, argsDict, ownerScope) => {
|
|
238
|
+
let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
|
|
237
239
|
let filterDict = Stdlib_Option.getOr(Stdlib_Option.flatMap(argsDict["filter"], Stdlib_JSON.Decode.object), {});
|
|
238
240
|
let fstr = k => Stdlib_Option.flatMap(filterDict[k], Stdlib_JSON.Decode.string);
|
|
239
241
|
let orderDesc = Stdlib_Option.mapOr(fstr("order"), false, o => o === "DESC");
|
|
@@ -248,8 +250,11 @@ function Make(P) {
|
|
|
248
250
|
};
|
|
249
251
|
let where = [
|
|
250
252
|
`partition_key = ` + param(b, id),
|
|
251
|
-
|
|
253
|
+
QueryDbStorage_Postgres_Ops$ReventlessPostgres.notExpiredClause
|
|
252
254
|
];
|
|
255
|
+
if (ownerScope !== undefined) {
|
|
256
|
+
where.push(jsonTextC(ownerScope[0]) + ` = ` + param(b, ownerScope[1]));
|
|
257
|
+
}
|
|
253
258
|
Stdlib_Option.forEach(fstr("prefix"), p => {
|
|
254
259
|
where.push(`starts_with(sub_key, ` + param(b, p) + `)`);
|
|
255
260
|
});
|
|
@@ -304,10 +309,6 @@ function Make(P) {
|
|
|
304
309
|
}
|
|
305
310
|
};
|
|
306
311
|
};
|
|
307
|
-
let make = _allQueryDbs => Pulumi.output({
|
|
308
|
-
scan: scan,
|
|
309
|
-
query: query
|
|
310
|
-
});
|
|
311
312
|
return {
|
|
312
313
|
query: query,
|
|
313
314
|
scan: scan,
|
|
@@ -316,12 +317,11 @@ function Make(P) {
|
|
|
316
317
|
idExprC: idExprC,
|
|
317
318
|
jsonTextC: jsonTextC,
|
|
318
319
|
listPage: listPage,
|
|
319
|
-
itemsPage: itemsPage
|
|
320
|
-
make: make
|
|
320
|
+
itemsPage: itemsPage
|
|
321
321
|
};
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
-
let notExpired =
|
|
324
|
+
let notExpired = QueryDbStorage_Postgres_Ops$ReventlessPostgres.notExpiredClause;
|
|
325
325
|
|
|
326
326
|
export {
|
|
327
327
|
valueToText,
|
|
@@ -334,4 +334,4 @@ export {
|
|
|
334
334
|
decodeRow,
|
|
335
335
|
Make,
|
|
336
336
|
}
|
|
337
|
-
/*
|
|
337
|
+
/* PgDriver-ReventlessPostgres Not a pure module */
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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 => {}));
|
|
125
|
+
return norm(QueryDbListQuery$ReventlessCore.run(seededItems, argsDict, capability, "name", param => {}, 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");
|
|
151
|
+
let actual = await Eng.listPage(name, argsDict, capability, "name", 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");
|
|
159
|
+
let r = await Eng.listPage(name, Object.fromEntries(args), capability, "name", 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));
|
|
325
|
+
let itemsPage = args => Eng.itemsPage(itemsName, "seq", "order-1", Object.fromEntries(args), 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([]);
|