@reventlessdev/reventless-postgres 3.0.0-alpha.6 → 3.0.0-alpha.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/CHANGELOG.md +434 -0
  2. package/README.md +83 -0
  3. package/lib/bs/.compiler.log +2 -2
  4. package/lib/bs/ReventlessPostgres.cmi +0 -0
  5. package/lib/bs/ReventlessPostgres.cmt +0 -0
  6. package/lib/bs/ReventlessPostgres.mlmap +3 -2
  7. package/lib/bs/compiler-info.json +2 -2
  8. package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  9. package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmj +0 -0
  10. package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  11. package/lib/bs/src/DcbEventLogStorage_Postgres.ast +0 -0
  12. package/lib/bs/src/DcbEventLogStorage_Postgres.res +25 -340
  13. package/lib/bs/src/DcbEventLogStorage_Postgres.res.mjs +32 -348
  14. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  15. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmj +0 -0
  16. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  17. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.ast +0 -0
  18. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.res +350 -0
  19. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.res.mjs +380 -0
  20. package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
  21. package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
  22. package/lib/bs/src/EventLogChangeFeed.ast +0 -0
  23. package/lib/bs/src/EventLogChangeFeed.res +5 -2
  24. package/lib/bs/src/EventLogChangeFeed.res.mjs +3 -3
  25. package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  26. package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmj +0 -0
  27. package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  28. package/lib/bs/src/EventLogStorage_Postgres.ast +0 -0
  29. package/lib/bs/src/EventLogStorage_Postgres.res +12 -170
  30. package/lib/bs/src/EventLogStorage_Postgres.res.mjs +20 -186
  31. package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  32. package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmj +0 -0
  33. package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  34. package/lib/bs/src/EventLogStorage_Postgres_Ops.ast +0 -0
  35. package/lib/bs/src/EventLogStorage_Postgres_Ops.res +180 -0
  36. package/lib/bs/src/EventLogStorage_Postgres_Ops.res.mjs +202 -0
  37. package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmi +0 -0
  38. package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmt +0 -0
  39. package/lib/bs/src/PgChangeFeed.ast +0 -0
  40. package/lib/bs/src/PgChangeFeed.res +6 -1
  41. package/lib/bs/src/PgChangeFeed.res.mjs +6 -6
  42. package/lib/bs/src/PgDriver-ReventlessPostgres.cmi +0 -0
  43. package/lib/bs/src/PgDriver-ReventlessPostgres.cmt +0 -0
  44. package/lib/bs/src/PgDriver-ReventlessPostgres.cmti +0 -0
  45. package/lib/bs/src/PgDriver.ast +0 -0
  46. package/lib/bs/src/PgDriver.iast +0 -0
  47. package/lib/bs/src/PgSchema-ReventlessPostgres.cmi +0 -0
  48. package/lib/bs/src/PgSchema-ReventlessPostgres.cmt +0 -0
  49. package/lib/bs/src/PgSchema.ast +0 -0
  50. package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
  51. package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
  52. package/lib/bs/src/QueryDbStorage_Postgres.ast +0 -0
  53. package/lib/bs/src/QueryDbStorage_Postgres.res +10 -233
  54. package/lib/bs/src/QueryDbStorage_Postgres.res.mjs +25 -204
  55. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  56. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmj +0 -0
  57. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  58. package/lib/bs/src/QueryDbStorage_Postgres_Ops.ast +0 -0
  59. package/lib/bs/src/QueryDbStorage_Postgres_Ops.res +229 -0
  60. package/lib/bs/src/QueryDbStorage_Postgres_Ops.res.mjs +222 -0
  61. package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
  62. package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
  63. package/lib/bs/src/QueryEnginePostgres.ast +0 -0
  64. package/lib/bs/src/QueryEnginePostgres.res +9 -12
  65. package/lib/bs/src/QueryEnginePostgres.res.mjs +18 -24
  66. package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
  67. package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
  68. package/lib/bs/tests/PostgresIntegrationTest.ast +0 -0
  69. package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
  70. package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
  71. package/lib/bs/tests/QueryEnginePostgresListPageTest.ast +0 -0
  72. package/lib/ocaml/.compiler.log +2 -2
  73. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  74. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmj +0 -0
  75. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  76. package/lib/ocaml/DcbEventLogStorage_Postgres.ast +0 -0
  77. package/lib/ocaml/DcbEventLogStorage_Postgres.res +25 -340
  78. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  79. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmj +0 -0
  80. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  81. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.ast +0 -0
  82. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.res +350 -0
  83. package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
  84. package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
  85. package/lib/ocaml/EventLogChangeFeed.ast +0 -0
  86. package/lib/ocaml/EventLogChangeFeed.res +5 -2
  87. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  88. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmj +0 -0
  89. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  90. package/lib/ocaml/EventLogStorage_Postgres.ast +0 -0
  91. package/lib/ocaml/EventLogStorage_Postgres.res +12 -170
  92. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  93. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmj +0 -0
  94. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  95. package/lib/ocaml/EventLogStorage_Postgres_Ops.ast +0 -0
  96. package/lib/ocaml/EventLogStorage_Postgres_Ops.res +180 -0
  97. package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmi +0 -0
  98. package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmt +0 -0
  99. package/lib/ocaml/PgChangeFeed.ast +0 -0
  100. package/lib/ocaml/PgChangeFeed.res +6 -1
  101. package/lib/ocaml/PgDriver-ReventlessPostgres.cmi +0 -0
  102. package/lib/ocaml/PgDriver-ReventlessPostgres.cmt +0 -0
  103. package/lib/ocaml/PgDriver-ReventlessPostgres.cmti +0 -0
  104. package/lib/ocaml/PgDriver.ast +0 -0
  105. package/lib/ocaml/PgDriver.iast +0 -0
  106. package/lib/ocaml/PgSchema-ReventlessPostgres.cmi +0 -0
  107. package/lib/ocaml/PgSchema-ReventlessPostgres.cmt +0 -0
  108. package/lib/ocaml/PgSchema.ast +0 -0
  109. package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
  110. package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
  111. package/lib/ocaml/QueryDbStorage_Postgres.ast +0 -0
  112. package/lib/ocaml/QueryDbStorage_Postgres.res +10 -233
  113. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  114. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmj +0 -0
  115. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  116. package/lib/ocaml/QueryDbStorage_Postgres_Ops.ast +0 -0
  117. package/lib/ocaml/QueryDbStorage_Postgres_Ops.res +229 -0
  118. package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
  119. package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
  120. package/lib/ocaml/QueryEnginePostgres.ast +0 -0
  121. package/lib/ocaml/QueryEnginePostgres.res +9 -12
  122. package/lib/ocaml/ReventlessPostgres.cmi +0 -0
  123. package/lib/ocaml/ReventlessPostgres.cmt +0 -0
  124. package/lib/ocaml/ReventlessPostgres.mlmap +3 -0
  125. package/package.json +11 -11
  126. package/rescript.json +2 -1
  127. package/src/DcbEventLogStorage_Postgres.res +25 -340
  128. package/src/DcbEventLogStorage_Postgres.res.mjs +32 -348
  129. package/src/DcbEventLogStorage_Postgres_Ops.res +350 -0
  130. package/src/DcbEventLogStorage_Postgres_Ops.res.mjs +380 -0
  131. package/src/EventLogChangeFeed.res +5 -2
  132. package/src/EventLogChangeFeed.res.mjs +3 -3
  133. package/src/EventLogStorage_Postgres.res +12 -170
  134. package/src/EventLogStorage_Postgres.res.mjs +20 -186
  135. package/src/EventLogStorage_Postgres_Ops.res +180 -0
  136. package/src/EventLogStorage_Postgres_Ops.res.mjs +202 -0
  137. package/src/PgChangeFeed.res +6 -1
  138. package/src/PgChangeFeed.res.mjs +6 -6
  139. package/src/QueryDbStorage_Postgres.res +10 -233
  140. package/src/QueryDbStorage_Postgres.res.mjs +25 -204
  141. package/src/QueryDbStorage_Postgres_Ops.res +229 -0
  142. package/src/QueryDbStorage_Postgres_Ops.res.mjs +222 -0
  143. package/src/QueryEnginePostgres.res +9 -12
  144. package/src/QueryEnginePostgres.res.mjs +18 -24
  145. package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
  146. package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmj +0 -0
  147. package/lib/ocaml/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
  148. package/lib/ocaml/PostgresIntegrationTest.ast +0 -0
  149. package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
  150. package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmj +0 -0
  151. package/lib/ocaml/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
  152. package/lib/ocaml/QueryEnginePostgresListPageTest.ast +0 -0
  153. package/tests/PostgresIntegrationTest.res.mjs +0 -377
  154. package/tests/QueryEnginePostgresListPageTest.res.mjs +0 -461
@@ -0,0 +1,229 @@
1
+ // Runtime-pure QueryDb operations for the Postgres backend — JSONB document
2
+ // tables, one per read model.
3
+ //
4
+ // Holds the operation set + all SQL helpers and imports NO `@pulumi/pulumi`
5
+ // (deploy-time only), so the deployed-Lambda runtime can bind ops without dragging
6
+ // a deploy-time dep into its ESM import graph (which fails resolution on real
7
+ // Lambda). The thin `QueryDbStorage_Postgres` wrapper `include`s this and adds the
8
+ // deploy-time `Pulumi.Output`-shaped `storage` record + the `Make` functor.
9
+ // See docs/plans/deployed-lambda-esm-self-containment.md (Rung-3 finding).
10
+
11
+ open ReventlessCore
12
+ open Reventless.ReadModel
13
+
14
+ let tableName = (name: string): string => "qdb_" ++ name->String.replaceAll("-", "_")
15
+
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"), "_")
19
+
20
+ let notExpiredClause = "(expires_at IS NULL OR expires_at > extract(epoch from now()))"
21
+
22
+ // JSONB text extraction for a single top-level field.
23
+ let jsonField = (field: string): string =>
24
+ `(item->>'${field->String.replaceAll("'", "''")}')`
25
+
26
+ let compositeExpr = (fields: array<string>, sep: string): string => {
27
+ let escapedSep = sep->String.replaceAll("'", "''")
28
+ fields->Array.map(jsonField)->Array.join(` || '${escapedSep}' || `)
29
+ }
30
+
31
+ let indexExpressions = (idx: indexConfig): (string, option<string>) => {
32
+ let pkExpr = switch idx.pkFields {
33
+ | Some(fields) if fields->Array.length > 0 => compositeExpr(fields, idx.pkSep->Option.getOr("/"))
34
+ | _ =>
35
+ switch idx.idField {
36
+ | Some(field) => jsonField(field)
37
+ | None => jsonField(idx.index)
38
+ }
39
+ }
40
+ let skExpr = switch idx.skFields {
41
+ | Some(fields) if fields->Array.length > 0 =>
42
+ Some(compositeExpr(fields, idx.skSep->Option.getOr("/")))
43
+ | _ =>
44
+ switch idx.subIdField {
45
+ | Some(field) => Some(jsonField(field))
46
+ | None => None
47
+ }
48
+ }
49
+ (pkExpr, skExpr)
50
+ }
51
+
52
+ let ensureTable = async (~pool, ~table) => {
53
+ await pool->PgDriver.exec(
54
+ `CREATE TABLE IF NOT EXISTS ${table} (partition_key text NOT NULL, sub_key text NOT NULL DEFAULT '', item jsonb NOT NULL, expires_at double precision, PRIMARY KEY (partition_key, sub_key))`,
55
+ )
56
+ }
57
+
58
+ let ensureIndexes = async (~pool, ~table, ~indexes: array<indexConfig>) => {
59
+ for i in 0 to indexes->Array.length - 1 {
60
+ let idx = indexes->Array.getUnsafe(i)
61
+ let (pkExpr, skExprOpt) = indexExpressions(idx)
62
+ let indexName = `idx_${table}_${sanitizeIdent(idx.index)}`
63
+ let cols = switch skExprOpt {
64
+ | Some(skExpr) => `${pkExpr}, ${skExpr}`
65
+ | None => pkExpr
66
+ }
67
+ await pool->PgDriver.exec(`CREATE INDEX IF NOT EXISTS ${indexName} ON ${table} (${cols})`)
68
+ }
69
+ }
70
+
71
+ let decodeItem = (row: dict<JSON.t>): JSON.t =>
72
+ row->Dict.get("item")->Option.getOr(JSON.Encode.null)
73
+
74
+ let computeSubKey = (item: JSON.t, subIdField: option<string>): string =>
75
+ switch subIdField {
76
+ | None => ""
77
+ | Some(field) =>
78
+ switch item->JSON.Decode.object->Option.flatMap(o => o->Dict.get(field)) {
79
+ | Some(JSON.String(s)) => s
80
+ | _ => ""
81
+ }
82
+ }
83
+
84
+ let withId = (item: JSON.t, partitionKey: string): JSON.t =>
85
+ switch item->JSON.Decode.object {
86
+ | Some(obj) if obj->Dict.get("id")->Option.isNone =>
87
+ let copy = Dict.copy(obj)
88
+ copy->Dict.set("id", JSON.Encode.string(partitionKey))
89
+ JSON.Encode.object(copy)
90
+ | _ => item
91
+ }
92
+
93
+ // Plain operation set for a read model's qdb table. Exposed separately from
94
+ // `makeStorage` so the deployed-Lambda runtime (reventless-aws) can bind ops
95
+ // without touching the Pulumi-shaped `storage` record.
96
+ let makeOperations = (
97
+ ~pool: PgDriver.pool,
98
+ ~name: string,
99
+ ~indexes: array<indexConfig>,
100
+ ~subIdField: option<string>,
101
+ ): QueryDb_Adapter.operations => {
102
+ let table = tableName(name)
103
+ // Schema setup is async and LAZY: kicked off by the first operation, not at
104
+ // construction. A deploy-time caller may build the ops on a machine that
105
+ // cannot reach the database (VPC-private RDS) — only the runtime, which
106
+ // actually calls an operation, may open a connection. Subsequent operations
107
+ // await the same promise (table + index creation runs exactly once).
108
+ let readyRef: ref<option<promise<unit>>> = ref(None)
109
+ let ready = () =>
110
+ switch readyRef.contents {
111
+ | Some(p) => p
112
+ | None =>
113
+ let p = {
114
+ open Promise
115
+ ensureTable(~pool, ~table)->then(() => ensureIndexes(~pool, ~table, ~indexes))
116
+ }
117
+ readyRef := Some(p)
118
+ p
119
+ }
120
+
121
+ let rowsFor = async (id: string): array<JSON.t> => {
122
+ await ready()
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
+ ))->Array.map(decodeItem)
127
+ }
128
+
129
+ let saveOne = async (id: string, state: JSON.t, ttl: option<int>) => {
130
+ await ready()
131
+ let subKey = computeSubKey(state, subIdField)
132
+ let expiresAt = switch ttl {
133
+ | Some(t) => JSON.Encode.int(t)
134
+ | None => JSON.Encode.null
135
+ }
136
+ let _ = await pool->PgDriver.query(
137
+ `INSERT INTO ${table}(partition_key, sub_key, item, expires_at)
138
+ VALUES ($1, $2, $3::jsonb, $4)
139
+ ON CONFLICT (partition_key, sub_key)
140
+ DO UPDATE SET item = EXCLUDED.item, expires_at = EXCLUDED.expires_at`,
141
+ [
142
+ JSON.Encode.string(id),
143
+ JSON.Encode.string(subKey),
144
+ JSON.Encode.string(JSON.stringify(state)),
145
+ expiresAt,
146
+ ],
147
+ )
148
+ }
149
+
150
+ let load: QueryDb.load<string, JSON.t> = async id => Ok(await rowsFor(id))
151
+
152
+ let loadStream: QueryDb.loadStream<string, JSON.t> =
153
+ id =>
154
+ Effect.promise(() => rowsFor(id))
155
+ ->Stream.fromEffect
156
+ ->Stream.flatMap(arr => Stream.fromIterable(arr))
157
+
158
+ let save: QueryDb.save<string, JSON.t> = async (id, state, _saveMode, ttl) => {
159
+ await saveOne(id, state, ttl)
160
+ Ok()
161
+ }
162
+
163
+ let saveBatch: QueryDb.saveBatch<string, JSON.t> = async batch => {
164
+ // pg has no synchronous multi-statement transaction helper here; the upserts
165
+ // are independent (distinct keys) so a sequential apply is equivalent.
166
+ for i in 0 to batch->Array.length - 1 {
167
+ let (id, state, ttl) = batch->Array.getUnsafe(i)
168
+ await saveOne(id, state, ttl)
169
+ }
170
+ Ok()
171
+ }
172
+
173
+ // Mirror DynamoDB's `ADD #field :inc`: read the counter on the partition item
174
+ // (counters are single-state, sub_key=""), add inc, upsert, return NEW total.
175
+ let count: QueryDb.count<string> = async (id, fieldName, inc) => {
176
+ await ready()
177
+ let existing = (await rowsFor(id))->Array.get(0)->Option.flatMap(JSON.Decode.object)
178
+ let current =
179
+ existing
180
+ ->Option.flatMap(o => o->Dict.get(fieldName))
181
+ ->Option.flatMap(JSON.Decode.float)
182
+ ->Option.mapOr(0, Float.toInt)
183
+ let next = current + inc
184
+ let obj = switch existing {
185
+ | Some(o) => Dict.copy(o)
186
+ | None => Dict.make()
187
+ }
188
+ obj->Dict.set("id", JSON.Encode.string(id))
189
+ obj->Dict.set(fieldName, JSON.Encode.int(next))
190
+ await saveOne(id, JSON.Encode.object(obj), None)
191
+ Ok(next)
192
+ }
193
+
194
+ let delete: QueryDb.delete<string> = async (id, subIdOpt) => {
195
+ await ready()
196
+ let _ = switch subIdOpt {
197
+ | None =>
198
+ await pool->PgDriver.query(
199
+ `DELETE FROM ${table} WHERE partition_key = $1`,
200
+ [JSON.Encode.string(id)],
201
+ )
202
+ | Some((_, subValue)) =>
203
+ await pool->PgDriver.query(
204
+ `DELETE FROM ${table} WHERE partition_key = $1 AND sub_key = $2`,
205
+ [JSON.Encode.string(id), JSON.Encode.string(subValue)],
206
+ )
207
+ }
208
+ Ok()
209
+ }
210
+
211
+ let deleteBatch: QueryDb.deleteBatch<string> = async ids => {
212
+ for i in 0 to ids->Array.length - 1 {
213
+ let (id, subIdOpt) = ids->Array.getUnsafe(i)
214
+ let _ = await delete(id, subIdOpt)
215
+ }
216
+ Ok()
217
+ }
218
+
219
+ let ops: QueryDb_Adapter.operations = {
220
+ load,
221
+ loadStream,
222
+ save,
223
+ saveBatch,
224
+ count,
225
+ delete,
226
+ deleteBatch,
227
+ }
228
+ ops
229
+ }
@@ -0,0 +1,222 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
4
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
+ import * as Effect from "effect/Effect";
6
+ import * as Stream from "effect/Stream";
7
+ import * as PgDriver$ReventlessPostgres from "./PgDriver.res.mjs";
8
+
9
+ function tableName(name) {
10
+ return "qdb_" + name.replaceAll("-", "_");
11
+ }
12
+
13
+ function sanitizeIdent(s) {
14
+ return s.replace(/[^A-Za-z0-9_]/g, "_");
15
+ }
16
+
17
+ let notExpiredClause = "(expires_at IS NULL OR expires_at > extract(epoch from now()))";
18
+
19
+ function jsonField(field) {
20
+ return `(item->>'` + field.replaceAll("'", "''") + `')`;
21
+ }
22
+
23
+ function compositeExpr(fields, sep) {
24
+ let escapedSep = sep.replaceAll("'", "''");
25
+ return fields.map(jsonField).join(` || '` + escapedSep + `' || `);
26
+ }
27
+
28
+ function indexExpressions(idx) {
29
+ let fields = idx.pkFields;
30
+ let pkExpr;
31
+ let exit = 0;
32
+ if (fields !== undefined && fields.length !== 0) {
33
+ pkExpr = compositeExpr(fields, Stdlib_Option.getOr(idx.pkSep, "/"));
34
+ } else {
35
+ exit = 1;
36
+ }
37
+ if (exit === 1) {
38
+ let field = idx.idField;
39
+ pkExpr = field !== undefined ? jsonField(field) : jsonField(idx.index);
40
+ }
41
+ let fields$1 = idx.skFields;
42
+ let skExpr;
43
+ let exit$1 = 0;
44
+ if (fields$1 !== undefined && fields$1.length !== 0) {
45
+ skExpr = compositeExpr(fields$1, Stdlib_Option.getOr(idx.skSep, "/"));
46
+ } else {
47
+ exit$1 = 1;
48
+ }
49
+ if (exit$1 === 1) {
50
+ let field$1 = idx.subIdField;
51
+ skExpr = field$1 !== undefined ? jsonField(field$1) : undefined;
52
+ }
53
+ return [
54
+ pkExpr,
55
+ skExpr
56
+ ];
57
+ }
58
+
59
+ async function ensureTable(pool, table) {
60
+ return await PgDriver$ReventlessPostgres.exec(pool, `CREATE TABLE IF NOT EXISTS ` + table + ` (partition_key text NOT NULL, sub_key text NOT NULL DEFAULT '', item jsonb NOT NULL, expires_at double precision, PRIMARY KEY (partition_key, sub_key))`);
61
+ }
62
+
63
+ async function ensureIndexes(pool, table, indexes) {
64
+ for (let i = 0, i_finish = indexes.length; i < i_finish; ++i) {
65
+ let idx = indexes[i];
66
+ let match = indexExpressions(idx);
67
+ let skExprOpt = match[1];
68
+ let pkExpr = match[0];
69
+ let indexName = `idx_` + table + `_` + sanitizeIdent(idx.index);
70
+ let cols = skExprOpt !== undefined ? pkExpr + `, ` + skExprOpt : pkExpr;
71
+ await PgDriver$ReventlessPostgres.exec(pool, `CREATE INDEX IF NOT EXISTS ` + indexName + ` ON ` + table + ` (` + cols + `)`);
72
+ }
73
+ }
74
+
75
+ function decodeItem(row) {
76
+ return Stdlib_Option.getOr(row["item"], null);
77
+ }
78
+
79
+ function computeSubKey(item, subIdField) {
80
+ if (subIdField === undefined) {
81
+ return "";
82
+ }
83
+ let match = Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(item), o => o[subIdField]);
84
+ if (typeof match === "string") {
85
+ return match;
86
+ } else {
87
+ return "";
88
+ }
89
+ }
90
+
91
+ function withId(item, partitionKey) {
92
+ let obj = Stdlib_JSON.Decode.object(item);
93
+ if (obj === undefined) {
94
+ return item;
95
+ }
96
+ if (!Stdlib_Option.isNone(obj["id"])) {
97
+ return item;
98
+ }
99
+ let copy = Object.assign({}, obj);
100
+ copy["id"] = partitionKey;
101
+ return copy;
102
+ }
103
+
104
+ function makeOperations(pool, name, indexes, subIdField) {
105
+ let table = tableName(name);
106
+ let readyRef = {
107
+ contents: undefined
108
+ };
109
+ let ready = () => {
110
+ let p = readyRef.contents;
111
+ if (p !== undefined) {
112
+ return p;
113
+ }
114
+ let p$1 = ensureTable(pool, table).then(() => ensureIndexes(pool, table, indexes));
115
+ readyRef.contents = p$1;
116
+ return p$1;
117
+ };
118
+ let rowsFor = async id => {
119
+ await ready();
120
+ return (await PgDriver$ReventlessPostgres.query(pool, `SELECT item FROM ` + table + ` WHERE partition_key = $1 AND ` + notExpiredClause + ` ORDER BY sub_key ASC`, [id])).map(decodeItem);
121
+ };
122
+ let saveOne = async (id, state, ttl) => {
123
+ await ready();
124
+ let subKey = computeSubKey(state, subIdField);
125
+ let expiresAt = ttl !== undefined ? ttl : null;
126
+ await PgDriver$ReventlessPostgres.query(pool, `INSERT INTO ` + table + `(partition_key, sub_key, item, expires_at)
127
+ VALUES ($1, $2, $3::jsonb, $4)
128
+ ON CONFLICT (partition_key, sub_key)
129
+ DO UPDATE SET item = EXCLUDED.item, expires_at = EXCLUDED.expires_at`, [
130
+ id,
131
+ subKey,
132
+ JSON.stringify(state),
133
+ expiresAt
134
+ ]);
135
+ };
136
+ let load = async id => ({
137
+ TAG: "Ok",
138
+ _0: await rowsFor(id)
139
+ });
140
+ let loadStream = id => Stream.flatMap(Stream.fromEffect(Effect.promise(() => rowsFor(id))), arr => Stream.fromIterable(arr));
141
+ let save = async (id, state, _saveMode, ttl) => {
142
+ await saveOne(id, state, ttl);
143
+ return {
144
+ TAG: "Ok",
145
+ _0: undefined
146
+ };
147
+ };
148
+ let saveBatch = async batch => {
149
+ for (let i = 0, i_finish = batch.length; i < i_finish; ++i) {
150
+ let match = batch[i];
151
+ await saveOne(match[0], match[1], match[2]);
152
+ }
153
+ return {
154
+ TAG: "Ok",
155
+ _0: undefined
156
+ };
157
+ };
158
+ let count = async (id, fieldName, inc) => {
159
+ await ready();
160
+ let existing = Stdlib_Option.flatMap((await rowsFor(id))[0], Stdlib_JSON.Decode.object);
161
+ let current = Stdlib_Option.mapOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(existing, o => o[fieldName]), Stdlib_JSON.Decode.float), 0, prim => prim | 0);
162
+ let next = current + inc | 0;
163
+ let obj = existing !== undefined ? Object.assign({}, existing) : ({});
164
+ obj["id"] = id;
165
+ obj[fieldName] = next;
166
+ await saveOne(id, obj, undefined);
167
+ return {
168
+ TAG: "Ok",
169
+ _0: next
170
+ };
171
+ };
172
+ let $$delete = async (id, subIdOpt) => {
173
+ await ready();
174
+ if (subIdOpt !== undefined) {
175
+ await PgDriver$ReventlessPostgres.query(pool, `DELETE FROM ` + table + ` WHERE partition_key = $1 AND sub_key = $2`, [
176
+ id,
177
+ subIdOpt[1]
178
+ ]);
179
+ } else {
180
+ await PgDriver$ReventlessPostgres.query(pool, `DELETE FROM ` + table + ` WHERE partition_key = $1`, [id]);
181
+ }
182
+ return {
183
+ TAG: "Ok",
184
+ _0: undefined
185
+ };
186
+ };
187
+ let deleteBatch = async ids => {
188
+ for (let i = 0, i_finish = ids.length; i < i_finish; ++i) {
189
+ let match = ids[i];
190
+ await $$delete(match[0], match[1]);
191
+ }
192
+ return {
193
+ TAG: "Ok",
194
+ _0: undefined
195
+ };
196
+ };
197
+ return {
198
+ load: load,
199
+ loadStream: loadStream,
200
+ save: save,
201
+ saveBatch: saveBatch,
202
+ count: count,
203
+ delete: $$delete,
204
+ deleteBatch: deleteBatch
205
+ };
206
+ }
207
+
208
+ export {
209
+ tableName,
210
+ sanitizeIdent,
211
+ notExpiredClause,
212
+ jsonField,
213
+ compositeExpr,
214
+ indexExpressions,
215
+ ensureTable,
216
+ ensureIndexes,
217
+ decodeItem,
218
+ computeSubKey,
219
+ withId,
220
+ makeOperations,
221
+ }
222
+ /* effect/Effect Not a pure module */
Binary file
@@ -63,18 +63,18 @@ let subIdSql = (b: paramBuilder, field: string, cmp: QueryEngine.SubId.comparato
63
63
  }
64
64
  }
65
65
 
66
- let notExpired = QueryDbStorage_Postgres.notExpiredClause
66
+ let notExpired = QueryDbStorage_Postgres_Ops.notExpiredClause
67
67
 
68
68
  // Add `id` (from partition_key) when absent — matches allRows/scan in the SQLite
69
69
  // engine; partition-key queries return items as stored.
70
70
  let decodeRow = (~withId, row: dict<JSON.t>): JSON.t => {
71
- let item = QueryDbStorage_Postgres.decodeItem(row)
71
+ let item = QueryDbStorage_Postgres_Ops.decodeItem(row)
72
72
  if withId {
73
73
  let pk = switch row->Dict.get("partition_key") {
74
74
  | Some(JSON.String(s)) => s
75
75
  | _ => ""
76
76
  }
77
- QueryDbStorage_Postgres.withId(item, pk)
77
+ QueryDbStorage_Postgres_Ops.withId(item, pk)
78
78
  } else {
79
79
  item
80
80
  }
@@ -90,7 +90,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
90
90
  ~ascending=?,
91
91
  ~limit=?,
92
92
  ): array<JSON.t> => {
93
- let table = QueryDbStorage_Postgres.tableName(readModelName)
93
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
94
94
  let keyStr = switch key {
95
95
  | Some(k) => k
96
96
  | None => valueToText(id)
@@ -114,7 +114,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
114
114
  }
115
115
 
116
116
  let scan = async (~readModelName, ~filterConfigs, ~limit): array<JSON.t> => {
117
- let table = QueryDbStorage_Postgres.tableName(readModelName)
117
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
118
118
  let b = {params: []}
119
119
  let where = [notExpired]
120
120
  filterConfigs->Array.forEach(((field, cmp, v)) => where->Array.push(filterSql(b, field, cmp, v)))
@@ -138,7 +138,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
138
138
  // (`ensureIndexes`) instead of scanning. String comparison mirrors the
139
139
  // resolver's JS path (which only matched string-typed fields).
140
140
  let indexLookup = async (~readModelName, field: string, value: string): array<JSON.t> => {
141
- let table = QueryDbStorage_Postgres.tableName(readModelName)
141
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
142
142
  let b = {params: []}
143
143
  let sql = `SELECT partition_key, item FROM ${table} WHERE ${col(field)} = ${b->param(
144
144
  JSON.Encode.string(value),
@@ -154,7 +154,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
154
154
  if ids->Array.length == 0 {
155
155
  []
156
156
  } else {
157
- let table = QueryDbStorage_Postgres.tableName(readModelName)
157
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
158
158
  let b = {params: []}
159
159
  let idsJson = JSON.Encode.array(ids->Array.map(JSON.Encode.string))
160
160
  let sql = `SELECT partition_key, item FROM ${table} WHERE partition_key = ANY(${b->param(
@@ -184,7 +184,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
184
184
  ~capability: GraphQL_FragmentGenerator.serverCapability,
185
185
  ~labelField as _,
186
186
  ): option<JSON.t> => {
187
- let table = QueryDbStorage_Postgres.tableName(readModelName)
187
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
188
188
  let filterDict =
189
189
  argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
190
190
  let strNonEmpty = k =>
@@ -310,7 +310,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
310
310
  ~id: string,
311
311
  ~argsDict: dict<JSON.t>,
312
312
  ): JSON.t => {
313
- let table = QueryDbStorage_Postgres.tableName(readModelName)
313
+ let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
314
314
  let filterDict =
315
315
  argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
316
316
  let fstr = k => filterDict->Dict.get(k)->Option.flatMap(JSON.Decode.string)
@@ -380,7 +380,4 @@ module Make = (P: {let pool: PgDriver.pool}) => {
380
380
  },
381
381
  })
382
382
  }
383
-
384
- let make: QueryDb_Adapter.queryEngineMaker = _allQueryDbs =>
385
- Pulumi.Output.make({QueryEngine.scan, query})
386
383
  }
@@ -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 QueryDbStorage_Postgres$ReventlessPostgres from "./QueryDbStorage_Postgres.res.mjs";
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 = QueryDbStorage_Postgres$ReventlessPostgres.decodeItem(row);
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 QueryDbStorage_Postgres$ReventlessPostgres.withId(item, pk);
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 = QueryDbStorage_Postgres$ReventlessPostgres.tableName(readModelName);
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
- QueryDbStorage_Postgres$ReventlessPostgres.notExpiredClause
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 = QueryDbStorage_Postgres$ReventlessPostgres.tableName(readModelName);
139
+ let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
141
140
  let b = {
142
141
  params: []
143
142
  };
144
- let where = [QueryDbStorage_Postgres$ReventlessPostgres.notExpiredClause];
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 = QueryDbStorage_Postgres$ReventlessPostgres.tableName(readModelName);
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 ` + QueryDbStorage_Postgres$ReventlessPostgres.notExpiredClause;
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 = QueryDbStorage_Postgres$ReventlessPostgres.tableName(readModelName);
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 ` + QueryDbStorage_Postgres$ReventlessPostgres.notExpiredClause;
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
172
  let listPage = async (readModelName, argsDict, capability, param$1) => {
174
- let table = QueryDbStorage_Postgres$ReventlessPostgres.tableName(readModelName);
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,7 @@ function Make(P) {
183
182
  let b = {
184
183
  params: []
185
184
  };
186
- let whereParts = [QueryDbStorage_Postgres$ReventlessPostgres.notExpiredClause];
185
+ let whereParts = [QueryDbStorage_Postgres_Ops$ReventlessPostgres.notExpiredClause];
187
186
  let valString = v => {
188
187
  let s = Stdlib_JSON.Decode.string(v);
189
188
  if (s !== undefined) {
@@ -233,7 +232,7 @@ function Make(P) {
233
232
  return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore, false, cursorValueOf);
234
233
  };
235
234
  let itemsPage = async (readModelName, param$1, id, argsDict) => {
236
- let table = QueryDbStorage_Postgres$ReventlessPostgres.tableName(readModelName);
235
+ let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);
237
236
  let filterDict = Stdlib_Option.getOr(Stdlib_Option.flatMap(argsDict["filter"], Stdlib_JSON.Decode.object), {});
238
237
  let fstr = k => Stdlib_Option.flatMap(filterDict[k], Stdlib_JSON.Decode.string);
239
238
  let orderDesc = Stdlib_Option.mapOr(fstr("order"), false, o => o === "DESC");
@@ -248,7 +247,7 @@ function Make(P) {
248
247
  };
249
248
  let where = [
250
249
  `partition_key = ` + param(b, id),
251
- QueryDbStorage_Postgres$ReventlessPostgres.notExpiredClause
250
+ QueryDbStorage_Postgres_Ops$ReventlessPostgres.notExpiredClause
252
251
  ];
253
252
  Stdlib_Option.forEach(fstr("prefix"), p => {
254
253
  where.push(`starts_with(sub_key, ` + param(b, p) + `)`);
@@ -304,10 +303,6 @@ function Make(P) {
304
303
  }
305
304
  };
306
305
  };
307
- let make = _allQueryDbs => Pulumi.output({
308
- scan: scan,
309
- query: query
310
- });
311
306
  return {
312
307
  query: query,
313
308
  scan: scan,
@@ -316,12 +311,11 @@ function Make(P) {
316
311
  idExprC: idExprC,
317
312
  jsonTextC: jsonTextC,
318
313
  listPage: listPage,
319
- itemsPage: itemsPage,
320
- make: make
314
+ itemsPage: itemsPage
321
315
  };
322
316
  }
323
317
 
324
- let notExpired = QueryDbStorage_Postgres$ReventlessPostgres.notExpiredClause;
318
+ let notExpired = QueryDbStorage_Postgres_Ops$ReventlessPostgres.notExpiredClause;
325
319
 
326
320
  export {
327
321
  valueToText,
@@ -334,4 +328,4 @@ export {
334
328
  decodeRow,
335
329
  Make,
336
330
  }
337
- /* @pulumi/pulumi Not a pure module */
331
+ /* PgDriver-ReventlessPostgres Not a pure module */