@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/lib/ocaml/.compiler.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#Start(
|
|
2
|
-
#Done(
|
|
1
|
+
#Start(1786782398524)
|
|
2
|
+
#Done(1786782404708)
|
|
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
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/ocaml/PgDriver.ast
CHANGED
|
Binary file
|
package/lib/ocaml/PgDriver.iast
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/ocaml/PgSchema.ast
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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
|
}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-postgres",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.100",
|
|
4
4
|
"description": "Postgres storage backend for Reventless (classic + DCB event logs, query DB, change feed). Connection-string only — RDS/Aurora, any managed provider, or a local container.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"jest": {
|
|
@@ -22,21 +22,21 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"pg": "^8.13.1",
|
|
24
24
|
"sury": "11.0.0-alpha.4",
|
|
25
|
-
"@reventlessdev/
|
|
26
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.
|
|
27
|
-
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.
|
|
28
|
-
"@reventlessdev/
|
|
29
|
-
"@reventlessdev/reventless-
|
|
30
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.
|
|
25
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.236",
|
|
26
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
27
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
|
|
28
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
29
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.142",
|
|
30
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.114"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/pg": "^8.11.10",
|
|
34
|
-
"rescript": "
|
|
34
|
+
"rescript": "12.3.0",
|
|
35
35
|
"sury-ppx": "11.0.0-alpha.2",
|
|
36
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
36
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.66"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"rescript": "
|
|
39
|
+
"rescript": "12.3.0"
|
|
40
40
|
},
|
|
41
41
|
"publishConfig": {
|
|
42
42
|
"registry": "https://registry.npmjs.org"
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
|
46
46
|
"url": "git+https://github.com/ReventlessDev/reventless-core.git",
|
|
47
|
-
"directory": "reventless/
|
|
47
|
+
"directory": "reventless/postgres"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"build": "rescript build",
|
package/rescript.json
CHANGED
|
@@ -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,
|
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
|
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,
|
|
@@ -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
|
};
|
|
@@ -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
|
}
|