@reventlessdev/reventless-postgres 3.0.0-alpha.14 → 3.0.0-alpha.140
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1013 -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 +4 -4
- 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 +43 -42
- 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 +15 -6
- 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 +24 -14
- 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 +21 -9
- 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/PgDriver.res +1 -2
- 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 +7 -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 +14 -14
- 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 +141 -42
- package/lib/bs/src/QueryEnginePostgres.res.mjs +53 -30
- 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/PostgresIntegrationTest.res +157 -104
- 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 +29 -6
- 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 +43 -42
- 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 +15 -6
- 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 +24 -14
- 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 +21 -9
- 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/PgDriver.res +1 -2
- 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/PostgresIntegrationTest.res +157 -104
- 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 +7 -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 +14 -14
- 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 +141 -42
- package/lib/ocaml/QueryEnginePostgresListPageTest.res +29 -6
- 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 +43 -42
- package/src/DcbEventLogStorage_Postgres_Ops.res.mjs +4 -4
- package/src/EventLogChangeFeed.res +15 -6
- package/src/EventLogChangeFeed.res.mjs +3 -3
- package/src/EventLogStorage_Postgres.res +1 -1
- package/src/EventLogStorage_Postgres_Ops.res +24 -14
- package/src/PgChangeFeed.res +21 -9
- package/src/PgChangeFeed.res.mjs +6 -6
- package/src/PgDriver.res +1 -2
- package/src/QueryDbStorage_Postgres.res +7 -2
- package/src/QueryDbStorage_Postgres.res.mjs +1 -1
- package/src/QueryDbStorage_Postgres_Ops.res +14 -14
- package/src/QueryEnginePostgres.res +141 -42
- package/src/QueryEnginePostgres.res.mjs +53 -30
- package/tests/PostgresIntegrationTest.res +157 -104
- package/tests/QueryEnginePostgresListPageTest.res +29 -6
- 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
|
@@ -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 */
|
|
@@ -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
|
|
@@ -87,11 +90,12 @@ let readBatch = async (
|
|
|
87
90
|
params->Array.push(JSON.Encode.string(tx))
|
|
88
91
|
params->Array.push(JSON.Encode.string(pos))
|
|
89
92
|
where :=
|
|
90
|
-
where.contents ++
|
|
91
|
-
" AND (transaction_id > $2::xid8 OR (transaction_id = $2::xid8 AND global_seq > $3::bigint))"
|
|
93
|
+
where.contents ++ " AND (transaction_id > $2::xid8 OR (transaction_id = $2::xid8 AND global_seq > $3::bigint))"
|
|
92
94
|
| None => ()
|
|
93
95
|
}
|
|
94
|
-
let sql = `SELECT ${selectColumns} FROM event_log WHERE ${where.contents} ORDER BY transaction_id ASC, global_seq ASC LIMIT ${Int.toString(
|
|
96
|
+
let sql = `SELECT ${selectColumns} FROM event_log WHERE ${where.contents} ORDER BY transaction_id ASC, global_seq ASC LIMIT ${Int.toString(
|
|
97
|
+
limit,
|
|
98
|
+
)}`
|
|
95
99
|
let rows = await pool->PgDriver.query(sql, params)
|
|
96
100
|
let events = rows->Array.map(rowToEvent)
|
|
97
101
|
let cursor = switch events->Array.get(events->Array.length - 1) {
|
|
@@ -163,7 +167,12 @@ let drain = async (
|
|
|
163
167
|
let cursor = ref(await loadCheckpoint(pool, ~subscriber))
|
|
164
168
|
let continue = ref(true)
|
|
165
169
|
while continue.contents {
|
|
166
|
-
let {events, cursor: newCursor} = await readBatch(
|
|
170
|
+
let {events, cursor: newCursor} = await readBatch(
|
|
171
|
+
pool,
|
|
172
|
+
~logName,
|
|
173
|
+
~after=?cursor.contents,
|
|
174
|
+
~limit,
|
|
175
|
+
)
|
|
167
176
|
if events->Array.length == 0 {
|
|
168
177
|
continue := false
|
|
169
178
|
} else {
|
|
@@ -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,
|
|
@@ -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
|
|
|
@@ -44,12 +44,10 @@ let isUniqueViolation = (exn: exn): bool => {
|
|
|
44
44
|
|
|
45
45
|
// Builds the classic runtime op set bound to `pool`. Returns `(name, ops)`; the
|
|
46
46
|
// deploy-time `EventLog_Adapter` wrapper lives in `EventLogStorage_Postgres`.
|
|
47
|
-
let makeOps = (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
~onAppended=noTracking,
|
|
52
|
-
): (string, EventLog_Adapter.operations) => {
|
|
47
|
+
let makeOps = (~pool: PgDriver.pool, ~name: string, ~opts as _, ~onAppended=noTracking): (
|
|
48
|
+
string,
|
|
49
|
+
EventLog_Adapter.operations,
|
|
50
|
+
) => {
|
|
53
51
|
let appendTracked = async (~track, seqNr: int, id: string, jsons: array<JSON.t>) => {
|
|
54
52
|
// Empty append is a legitimate no-op (at-least-once retries, idempotent
|
|
55
53
|
// commands), NOT a conflict — return before the guarded insert whose 0-row
|
|
@@ -95,7 +93,10 @@ let makeOps = (
|
|
|
95
93
|
? Error(EventLog.Conflict)
|
|
96
94
|
: Error(
|
|
97
95
|
EventLog.StorageFailure(
|
|
98
|
-
exn
|
|
96
|
+
exn
|
|
97
|
+
->JsExn.fromException
|
|
98
|
+
->Option.flatMap(JsExn.message)
|
|
99
|
+
->Option.getOr("storage error"),
|
|
99
100
|
),
|
|
100
101
|
)
|
|
101
102
|
}
|
|
@@ -106,12 +107,14 @@ let makeOps = (
|
|
|
106
107
|
appendTracked(~track=true, seqNr, id, jsons)
|
|
107
108
|
|
|
108
109
|
let replayArray = async (id: string, ~fromSeq=0): array<JSON.t> =>
|
|
109
|
-
(
|
|
110
|
-
|
|
110
|
+
(
|
|
111
|
+
await pool->PgDriver.query(
|
|
112
|
+
"SELECT payload FROM event_log
|
|
111
113
|
WHERE log_name = $1 AND aggregate_id = $2 AND seq_nr >= $3::bigint
|
|
112
114
|
ORDER BY seq_nr ASC",
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
[JSON.Encode.string(name), JSON.Encode.string(id), JSON.Encode.int(fromSeq)],
|
|
116
|
+
)
|
|
117
|
+
)->Array.map(decodePayload)
|
|
115
118
|
|
|
116
119
|
let replay: EventLog.replay<string, JSON.t> = id => replayArray(id)
|
|
117
120
|
|
|
@@ -152,14 +155,21 @@ let makeOps = (
|
|
|
152
155
|
Ok()
|
|
153
156
|
} catch {
|
|
154
157
|
| exn =>
|
|
155
|
-
Error(
|
|
158
|
+
Error(
|
|
159
|
+
exn
|
|
160
|
+
->JsExn.fromException
|
|
161
|
+
->Option.flatMap(JsExn.message)
|
|
162
|
+
->Option.getOr("snapshot write error"),
|
|
163
|
+
)
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
let appendStream: EventLog.appendStream<string, JSON.t> = (startingSeqNr, id, stream) =>
|
|
159
167
|
stream
|
|
160
168
|
->Stream.runCollect
|
|
161
169
|
->Effect.flatMap(jsons =>
|
|
162
|
-
Effect.promise(() =>
|
|
170
|
+
Effect.promise(() =>
|
|
171
|
+
appendTracked(~track=false, startingSeqNr, id, jsons)
|
|
172
|
+
)->Effect.flatMap(result =>
|
|
163
173
|
switch result {
|
|
164
174
|
| Ok() => Effect.succeed()
|
|
165
175
|
| Error(EventLog.Conflict) => Effect.fail("conflict")
|
package/src/PgChangeFeed.res
CHANGED
|
@@ -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
|
|
@@ -44,7 +49,9 @@ let readBatch = async (
|
|
|
44
49
|
~after=after->Option.flatMap(Dcb.decodeCursor),
|
|
45
50
|
~applyFence=true,
|
|
46
51
|
)
|
|
47
|
-
let sql = `SELECT ${Dcb.selectColumns} FROM dcb_event WHERE ${where} ORDER BY transaction_id ASC, position ASC LIMIT ${Int.toString(
|
|
52
|
+
let sql = `SELECT ${Dcb.selectColumns} FROM dcb_event WHERE ${where} ORDER BY transaction_id ASC, position ASC LIMIT ${Int.toString(
|
|
53
|
+
limit,
|
|
54
|
+
)}`
|
|
48
55
|
let rows = await pool->PgDriver.query(sql, b.params)
|
|
49
56
|
let events = rows->Array.map(Dcb.rowToEvent)
|
|
50
57
|
let cursor = switch events->Array.get(events->Array.length - 1) {
|
|
@@ -58,10 +65,9 @@ let readBatch = async (
|
|
|
58
65
|
|
|
59
66
|
// The persisted checkpoint for `subscriber`, encoded as a cursor, or None if the
|
|
60
67
|
// subscriber has never committed (→ replay from the beginning).
|
|
61
|
-
let loadCheckpoint = async (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
): option<DcbTag.sequencePosition> =>
|
|
68
|
+
let loadCheckpoint = async (pool: PgDriver.pool, ~subscriber: string): option<
|
|
69
|
+
DcbTag.sequencePosition,
|
|
70
|
+
> =>
|
|
65
71
|
switch await pool->PgDriver.queryOne(
|
|
66
72
|
"SELECT lpad(last_tx::text, 20, '0') || ':' || lpad(last_position::text, 20, '0') AS cursor
|
|
67
73
|
FROM dcb_subscription WHERE subscriber = $1 AND last_position > 0",
|
|
@@ -100,8 +106,9 @@ let saveCheckpoint = async (
|
|
|
100
106
|
// dedicated client (caller owns it — release via PgDriver.unlisten). Pair with a
|
|
101
107
|
// low-frequency fallback tick in the consumer loop so a missed NOTIFY only adds
|
|
102
108
|
// latency, never a permanent stall.
|
|
103
|
-
let listen = (pool: PgDriver.pool, ~logName: string, ~onWake: unit => unit): promise<
|
|
104
|
-
|
|
109
|
+
let listen = (pool: PgDriver.pool, ~logName: string, ~onWake: unit => unit): promise<
|
|
110
|
+
PgDriver.client,
|
|
111
|
+
> => pool->PgDriver.listen(~channel="dcb_" ++ logName, ~onNotify=_ => onWake())
|
|
105
112
|
|
|
106
113
|
let unlisten = (client: PgDriver.client, ~logName: string): promise<unit> =>
|
|
107
114
|
client->PgDriver.unlisten(~channel="dcb_" ++ logName)
|
|
@@ -124,7 +131,12 @@ let drain = async (
|
|
|
124
131
|
let cursor = ref(await loadCheckpoint(pool, ~subscriber))
|
|
125
132
|
let continue = ref(true)
|
|
126
133
|
while continue.contents {
|
|
127
|
-
let {events, cursor: newCursor} = await readBatch(
|
|
134
|
+
let {events, cursor: newCursor} = await readBatch(
|
|
135
|
+
pool,
|
|
136
|
+
~logName,
|
|
137
|
+
~after=?cursor.contents,
|
|
138
|
+
~limit,
|
|
139
|
+
)
|
|
128
140
|
if events->Array.length == 0 {
|
|
129
141
|
continue := false
|
|
130
142
|
} else {
|
package/src/PgChangeFeed.res.mjs
CHANGED
|
@@ -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,
|
package/src/PgDriver.res
CHANGED
|
@@ -47,8 +47,7 @@ type queryResult = {rows: array<dict<JSON.t>>}
|
|
|
47
47
|
|
|
48
48
|
let query = async (pool, sql, params) => (await _query(pool, sql, params)).rows
|
|
49
49
|
|
|
50
|
-
let queryOne = async (pool, sql, params) =>
|
|
51
|
-
(await _query(pool, sql, params)).rows->Array.get(0)
|
|
50
|
+
let queryOne = async (pool, sql, params) => (await _query(pool, sql, params)).rows->Array.get(0)
|
|
52
51
|
|
|
53
52
|
let exec = async (pool, sql) => {
|
|
54
53
|
let _ = await _queryText(pool, sql)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// that pure module (so every existing `QueryDbStorage_Postgres.*` reference keeps
|
|
7
7
|
// working) and adds only the deploy-time `Pulumi.Output`-shaped `storage` record
|
|
8
8
|
// and the `Make` provider functor.
|
|
9
|
-
// See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
|
|
9
|
+
// See docs/plans/done/deployed-lambda-esm-self-containment.md (Rung-3 finding).
|
|
10
10
|
|
|
11
11
|
open ReventlessCore
|
|
12
12
|
open Reventless.ReadModel
|
|
@@ -26,7 +26,11 @@ let makeStorage = (
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Standalone/deploy storage: inject the pool via a provider module.
|
|
29
|
-
module Make = (
|
|
29
|
+
module Make = (
|
|
30
|
+
P: {
|
|
31
|
+
let pool: PgDriver.pool
|
|
32
|
+
},
|
|
33
|
+
) => {
|
|
30
34
|
type api = unit
|
|
31
35
|
type role = unit
|
|
32
36
|
|
|
@@ -37,6 +41,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
|
|
|
37
41
|
~ttl as _=?,
|
|
38
42
|
~api as _,
|
|
39
43
|
~apiRole as _,
|
|
44
|
+
~owner as _,
|
|
40
45
|
~opts as _,
|
|
41
46
|
) => makeStorage(~pool=P.pool, ~name, ~indexes, ~subIdField)
|
|
42
47
|
}
|
|
@@ -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
|
|
@@ -14,14 +14,12 @@ open Reventless.ReadModel
|
|
|
14
14
|
let tableName = (name: string): string => "qdb_" ++ name->String.replaceAll("-", "_")
|
|
15
15
|
|
|
16
16
|
// Collapse anything outside [A-Za-z0-9_] to _ — predictable, injection-safe idents.
|
|
17
|
-
let sanitizeIdent = (s: string): string =>
|
|
18
|
-
s->String.replaceRegExp(%re("/[^A-Za-z0-9_]/g"), "_")
|
|
17
|
+
let sanitizeIdent = (s: string): string => s->String.replaceRegExp(/[^A-Za-z0-9_]/g, "_")
|
|
19
18
|
|
|
20
19
|
let notExpiredClause = "(expires_at IS NULL OR expires_at > extract(epoch from now()))"
|
|
21
20
|
|
|
22
21
|
// JSONB text extraction for a single top-level field.
|
|
23
|
-
let jsonField = (field: string): string =>
|
|
24
|
-
`(item->>'${field->String.replaceAll("'", "''")}')`
|
|
22
|
+
let jsonField = (field: string): string => `(item->>'${field->String.replaceAll("'", "''")}')`
|
|
25
23
|
|
|
26
24
|
let compositeExpr = (fields: array<string>, sep: string): string => {
|
|
27
25
|
let escapedSep = sep->String.replaceAll("'", "''")
|
|
@@ -120,10 +118,13 @@ let makeOperations = (
|
|
|
120
118
|
|
|
121
119
|
let rowsFor = async (id: string): array<JSON.t> => {
|
|
122
120
|
await ready()
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
|
|
122
|
+
(
|
|
123
|
+
await pool->PgDriver.query(
|
|
124
|
+
`SELECT item FROM ${table} WHERE partition_key = $1 AND ${notExpiredClause} ORDER BY sub_key ASC`,
|
|
125
|
+
[JSON.Encode.string(id)],
|
|
126
|
+
)
|
|
127
|
+
)->Array.map(decodeItem)
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
let saveOne = async (id: string, state: JSON.t, ttl: option<int>) => {
|
|
@@ -149,11 +150,10 @@ let makeOperations = (
|
|
|
149
150
|
|
|
150
151
|
let load: QueryDb.load<string, JSON.t> = async id => Ok(await rowsFor(id))
|
|
151
152
|
|
|
152
|
-
let loadStream: QueryDb.loadStream<string, JSON.t> =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
->Stream.flatMap(arr => Stream.fromIterable(arr))
|
|
153
|
+
let loadStream: QueryDb.loadStream<string, JSON.t> = id =>
|
|
154
|
+
Effect.promise(() => rowsFor(id))
|
|
155
|
+
->Stream.fromEffect
|
|
156
|
+
->Stream.flatMap(arr => Stream.fromIterable(arr))
|
|
157
157
|
|
|
158
158
|
let save: QueryDb.save<string, JSON.t> = async (id, state, _saveMode, ttl) => {
|
|
159
159
|
await saveOne(id, state, ttl)
|