@reventlessdev/reventless-postgres 3.0.0-alpha.124 → 3.0.0-alpha.125

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 (97) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/lib/bs/.compiler.log +2 -2
  3. package/lib/bs/compiler-info.json +1 -1
  4. package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  5. package/lib/bs/src/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  6. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  7. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  8. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.ast +0 -0
  9. package/lib/bs/src/DcbEventLogStorage_Postgres_Ops.res +41 -40
  10. package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
  11. package/lib/bs/src/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
  12. package/lib/bs/src/EventLogChangeFeed.ast +0 -0
  13. package/lib/bs/src/EventLogChangeFeed.res +10 -4
  14. package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  15. package/lib/bs/src/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  16. package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  17. package/lib/bs/src/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  18. package/lib/bs/src/EventLogStorage_Postgres_Ops.ast +0 -0
  19. package/lib/bs/src/EventLogStorage_Postgres_Ops.res +23 -13
  20. package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmi +0 -0
  21. package/lib/bs/src/PgChangeFeed-ReventlessPostgres.cmt +0 -0
  22. package/lib/bs/src/PgChangeFeed.ast +0 -0
  23. package/lib/bs/src/PgChangeFeed.res +15 -8
  24. package/lib/bs/src/PgDriver-ReventlessPostgres.cmt +0 -0
  25. package/lib/bs/src/PgDriver.ast +0 -0
  26. package/lib/bs/src/PgDriver.res +1 -2
  27. package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
  28. package/lib/bs/src/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
  29. package/lib/bs/src/QueryDbStorage_Postgres.ast +0 -0
  30. package/lib/bs/src/QueryDbStorage_Postgres.res +7 -2
  31. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  32. package/lib/bs/src/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  33. package/lib/bs/src/QueryDbStorage_Postgres_Ops.ast +0 -0
  34. package/lib/bs/src/QueryDbStorage_Postgres_Ops.res +13 -13
  35. package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
  36. package/lib/bs/src/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
  37. package/lib/bs/src/QueryEnginePostgres.ast +0 -0
  38. package/lib/bs/src/QueryEnginePostgres.res +72 -30
  39. package/lib/bs/src/QueryEnginePostgres.res.mjs +4 -4
  40. package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmi +0 -0
  41. package/lib/bs/tests/PostgresIntegrationTest-ReventlessPostgres.cmt +0 -0
  42. package/lib/bs/tests/PostgresIntegrationTest.ast +0 -0
  43. package/lib/bs/tests/PostgresIntegrationTest.res +157 -104
  44. package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmi +0 -0
  45. package/lib/bs/tests/QueryEnginePostgresListPageTest-ReventlessPostgres.cmt +0 -0
  46. package/lib/bs/tests/QueryEnginePostgresListPageTest.ast +0 -0
  47. package/lib/bs/tests/QueryEnginePostgresListPageTest.res +29 -6
  48. package/lib/ocaml/.compiler.log +2 -2
  49. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  50. package/lib/ocaml/DcbEventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  51. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  52. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  53. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.ast +0 -0
  54. package/lib/ocaml/DcbEventLogStorage_Postgres_Ops.res +41 -40
  55. package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmi +0 -0
  56. package/lib/ocaml/EventLogChangeFeed-ReventlessPostgres.cmt +0 -0
  57. package/lib/ocaml/EventLogChangeFeed.ast +0 -0
  58. package/lib/ocaml/EventLogChangeFeed.res +10 -4
  59. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmi +0 -0
  60. package/lib/ocaml/EventLogStorage_Postgres-ReventlessPostgres.cmt +0 -0
  61. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  62. package/lib/ocaml/EventLogStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  63. package/lib/ocaml/EventLogStorage_Postgres_Ops.ast +0 -0
  64. package/lib/ocaml/EventLogStorage_Postgres_Ops.res +23 -13
  65. package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmi +0 -0
  66. package/lib/ocaml/PgChangeFeed-ReventlessPostgres.cmt +0 -0
  67. package/lib/ocaml/PgChangeFeed.ast +0 -0
  68. package/lib/ocaml/PgChangeFeed.res +15 -8
  69. package/lib/ocaml/PgDriver-ReventlessPostgres.cmt +0 -0
  70. package/lib/ocaml/PgDriver.ast +0 -0
  71. package/lib/ocaml/PgDriver.res +1 -2
  72. package/lib/ocaml/PostgresIntegrationTest.res +157 -104
  73. package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmi +0 -0
  74. package/lib/ocaml/QueryDbStorage_Postgres-ReventlessPostgres.cmt +0 -0
  75. package/lib/ocaml/QueryDbStorage_Postgres.ast +0 -0
  76. package/lib/ocaml/QueryDbStorage_Postgres.res +7 -2
  77. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmi +0 -0
  78. package/lib/ocaml/QueryDbStorage_Postgres_Ops-ReventlessPostgres.cmt +0 -0
  79. package/lib/ocaml/QueryDbStorage_Postgres_Ops.ast +0 -0
  80. package/lib/ocaml/QueryDbStorage_Postgres_Ops.res +13 -13
  81. package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmi +0 -0
  82. package/lib/ocaml/QueryEnginePostgres-ReventlessPostgres.cmt +0 -0
  83. package/lib/ocaml/QueryEnginePostgres.ast +0 -0
  84. package/lib/ocaml/QueryEnginePostgres.res +72 -30
  85. package/lib/ocaml/QueryEnginePostgresListPageTest.res +29 -6
  86. package/package.json +6 -6
  87. package/src/DcbEventLogStorage_Postgres_Ops.res +41 -40
  88. package/src/EventLogChangeFeed.res +10 -4
  89. package/src/EventLogStorage_Postgres_Ops.res +23 -13
  90. package/src/PgChangeFeed.res +15 -8
  91. package/src/PgDriver.res +1 -2
  92. package/src/QueryDbStorage_Postgres.res +7 -2
  93. package/src/QueryDbStorage_Postgres_Ops.res +13 -13
  94. package/src/QueryEnginePostgres.res +72 -30
  95. package/src/QueryEnginePostgres.res.mjs +4 -4
  96. package/tests/PostgresIntegrationTest.res +157 -104
  97. package/tests/QueryEnginePostgresListPageTest.res +29 -6
@@ -49,7 +49,9 @@ let readBatch = async (
49
49
  ~after=after->Option.flatMap(Dcb.decodeCursor),
50
50
  ~applyFence=true,
51
51
  )
52
- let sql = `SELECT ${Dcb.selectColumns} FROM dcb_event WHERE ${where} ORDER BY transaction_id ASC, position ASC LIMIT ${Int.toString(limit)}`
52
+ let sql = `SELECT ${Dcb.selectColumns} FROM dcb_event WHERE ${where} ORDER BY transaction_id ASC, position ASC LIMIT ${Int.toString(
53
+ limit,
54
+ )}`
53
55
  let rows = await pool->PgDriver.query(sql, b.params)
54
56
  let events = rows->Array.map(Dcb.rowToEvent)
55
57
  let cursor = switch events->Array.get(events->Array.length - 1) {
@@ -63,10 +65,9 @@ let readBatch = async (
63
65
 
64
66
  // The persisted checkpoint for `subscriber`, encoded as a cursor, or None if the
65
67
  // subscriber has never committed (→ replay from the beginning).
66
- let loadCheckpoint = async (
67
- pool: PgDriver.pool,
68
- ~subscriber: string,
69
- ): option<DcbTag.sequencePosition> =>
68
+ let loadCheckpoint = async (pool: PgDriver.pool, ~subscriber: string): option<
69
+ DcbTag.sequencePosition,
70
+ > =>
70
71
  switch await pool->PgDriver.queryOne(
71
72
  "SELECT lpad(last_tx::text, 20, '0') || ':' || lpad(last_position::text, 20, '0') AS cursor
72
73
  FROM dcb_subscription WHERE subscriber = $1 AND last_position > 0",
@@ -105,8 +106,9 @@ let saveCheckpoint = async (
105
106
  // dedicated client (caller owns it — release via PgDriver.unlisten). Pair with a
106
107
  // low-frequency fallback tick in the consumer loop so a missed NOTIFY only adds
107
108
  // latency, never a permanent stall.
108
- let listen = (pool: PgDriver.pool, ~logName: string, ~onWake: unit => unit): promise<PgDriver.client> =>
109
- pool->PgDriver.listen(~channel="dcb_" ++ logName, ~onNotify=_ => onWake())
109
+ let listen = (pool: PgDriver.pool, ~logName: string, ~onWake: unit => unit): promise<
110
+ PgDriver.client,
111
+ > => pool->PgDriver.listen(~channel="dcb_" ++ logName, ~onNotify=_ => onWake())
110
112
 
111
113
  let unlisten = (client: PgDriver.client, ~logName: string): promise<unit> =>
112
114
  client->PgDriver.unlisten(~channel="dcb_" ++ logName)
@@ -129,7 +131,12 @@ let drain = async (
129
131
  let cursor = ref(await loadCheckpoint(pool, ~subscriber))
130
132
  let continue = ref(true)
131
133
  while continue.contents {
132
- let {events, cursor: newCursor} = await readBatch(pool, ~logName, ~after=?cursor.contents, ~limit)
134
+ let {events, cursor: newCursor} = await readBatch(
135
+ pool,
136
+ ~logName,
137
+ ~after=?cursor.contents,
138
+ ~limit,
139
+ )
133
140
  if events->Array.length == 0 {
134
141
  continue := false
135
142
  } else {
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)
@@ -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 = (P: {let pool: PgDriver.pool}) => {
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 _,
40
- ~owner as _, ~opts as _,
44
+ ~owner as _,
45
+ ~opts as _,
41
46
  ) => makeStorage(~pool=P.pool, ~name, ~indexes, ~subIdField)
42
47
  }
@@ -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
- (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)
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
- id =>
154
- Effect.promise(() => rowsFor(id))
155
- ->Stream.fromEffect
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)
@@ -28,7 +28,12 @@ let param = (b: paramBuilder, v: JSON.t): string => {
28
28
  let col = (field: string): string => `(item->>'${field->String.replaceAll("'", "''")}')`
29
29
 
30
30
  // One Filter.config → SQL predicate, appending any bound params.
31
- let filterSql = (b: paramBuilder, field: string, cmp: QueryEngine.Filter.comparator, v: QueryEngine.value): string => {
31
+ let filterSql = (
32
+ b: paramBuilder,
33
+ field: string,
34
+ cmp: QueryEngine.Filter.comparator,
35
+ v: QueryEngine.value,
36
+ ): string => {
32
37
  let c = col(field)
33
38
  let bind = () => b->param(JSON.Encode.string(valueToText(v)))
34
39
  let cmpSql = op =>
@@ -48,10 +53,16 @@ let filterSql = (b: paramBuilder, field: string, cmp: QueryEngine.Filter.compara
48
53
  }
49
54
  }
50
55
 
51
- let subIdSql = (b: paramBuilder, field: string, cmp: QueryEngine.SubId.comparator, v: QueryEngine.value): string => {
56
+ let subIdSql = (
57
+ b: paramBuilder,
58
+ field: string,
59
+ cmp: QueryEngine.SubId.comparator,
60
+ v: QueryEngine.value,
61
+ ): string => {
52
62
  let c = col(field)
53
63
  let bind = () => b->param(JSON.Encode.string(valueToText(v)))
54
- let cmpSql = op => isNumeric(v) ? `${c}::numeric ${op} ${bind()}::numeric` : `${c} ${op} ${bind()}`
64
+ let cmpSql = op =>
65
+ isNumeric(v) ? `${c}::numeric ${op} ${bind()}::numeric` : `${c} ${op} ${bind()}`
55
66
  switch cmp {
56
67
  | QueryEngine.SubId.Equal => cmpSql("=")
57
68
  | Unequal => cmpSql("<>")
@@ -80,7 +91,11 @@ let decodeRow = (~withId, row: dict<JSON.t>): JSON.t => {
80
91
  }
81
92
  }
82
93
 
83
- module Make = (P: {let pool: PgDriver.pool}) => {
94
+ module Make = (
95
+ P: {
96
+ let pool: PgDriver.pool
97
+ },
98
+ ) => {
84
99
  let query = async (
85
100
  ~readModelName,
86
101
  ~key=?,
@@ -109,7 +124,9 @@ module Make = (P: {let pool: PgDriver.pool}) => {
109
124
  | Some(n) => ` LIMIT ${b->param(JSON.Encode.int(n))}`
110
125
  | None => ""
111
126
  }
112
- let sql = `SELECT partition_key, item FROM ${table} WHERE ${where->Array.join(" AND ")} ORDER BY sub_key ${dir}${limitClause}`
127
+ let sql = `SELECT partition_key, item FROM ${table} WHERE ${where->Array.join(
128
+ " AND ",
129
+ )} ORDER BY sub_key ${dir}${limitClause}`
113
130
  (await P.pool->PgDriver.query(sql, b.params))->Array.map(decodeRow(~withId=false, ...))
114
131
  }
115
132
 
@@ -117,8 +134,12 @@ module Make = (P: {let pool: PgDriver.pool}) => {
117
134
  let table = QueryDbStorage_Postgres_Ops.tableName(readModelName)
118
135
  let b = {params: []}
119
136
  let where = [notExpired]
120
- filterConfigs->Array.forEach(((field, cmp, v)) => where->Array.push(filterSql(b, field, cmp, v)))
121
- let sql = `SELECT partition_key, item FROM ${table} WHERE ${where->Array.join(" AND ")} ORDER BY partition_key, sub_key LIMIT ${b->param(JSON.Encode.int(limit))}`
137
+ filterConfigs->Array.forEach(((field, cmp, v)) =>
138
+ where->Array.push(filterSql(b, field, cmp, v))
139
+ )
140
+ let sql = `SELECT partition_key, item FROM ${table} WHERE ${where->Array.join(
141
+ " AND ",
142
+ )} ORDER BY partition_key, sub_key LIMIT ${b->param(JSON.Encode.int(limit))}`
122
143
  (await P.pool->PgDriver.query(sql, b.params))->Array.map(decodeRow(~withId=true, ...))
123
144
  }
124
145
 
@@ -265,15 +286,17 @@ module Make = (P: {let pool: PgDriver.pool}) => {
265
286
  }
266
287
  capability.filterFields->Array.forEach(f => {
267
288
  switch filterDict->Dict.get(f.name ++ "Eq") {
268
- | Some(v) when v != JSON.Encode.null =>
289
+ | Some(v) if v != JSON.Encode.null =>
269
290
  whereParts->Array.push(
270
- `${jsonTextC(f.name)} = ${b->param(JSON.Encode.string(valString(v)->Option.getOr("")))}`,
291
+ `${jsonTextC(f.name)} = ${b->param(
292
+ JSON.Encode.string(valString(v)->Option.getOr("")),
293
+ )}`,
271
294
  )
272
295
  | _ => ()
273
296
  }
274
297
  if f.range {
275
298
  switch filterDict->Dict.get(f.name ++ "From") {
276
- | Some(v) when v != JSON.Encode.null =>
299
+ | Some(v) if v != JSON.Encode.null =>
277
300
  whereParts->Array.push(
278
301
  `${jsonTextC(f.name)} >= ${b->param(
279
302
  JSON.Encode.string(valString(v)->Option.getOr("")),
@@ -282,7 +305,7 @@ module Make = (P: {let pool: PgDriver.pool}) => {
282
305
  | _ => ()
283
306
  }
284
307
  switch filterDict->Dict.get(f.name ++ "To") {
285
- | Some(v) when v != JSON.Encode.null =>
308
+ | Some(v) if v != JSON.Encode.null =>
286
309
  whereParts->Array.push(
287
310
  `${jsonTextC(f.name)} <= ${b->param(
288
311
  JSON.Encode.string(valString(v)->Option.getOr("")),
@@ -304,7 +327,8 @@ module Make = (P: {let pool: PgDriver.pool}) => {
304
327
  | Some(f) => jsonTextC(f)
305
328
  | None => idExprC
306
329
  }
307
- switch argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string) {
330
+ let afterCursor = argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string)
331
+ switch afterCursor {
308
332
  | Some(c) =>
309
333
  whereParts->Array.push(
310
334
  `${cursorExpr} ${isDesc ? "<" : ">"} ${b->param(
@@ -326,7 +350,8 @@ module Make = (P: {let pool: PgDriver.pool}) => {
326
350
  let sql = `SELECT partition_key, item FROM ${table} WHERE ${whereParts->Array.join(
327
351
  " AND ",
328
352
  )} ORDER BY ${orderClause} LIMIT ${b->param(JSON.Encode.int(pageSize + 1))}`
329
- let rows = (await P.pool->PgDriver.query(sql, b.params))->Array.map(decodeRow(~withId=true, ...))
353
+ let rows =
354
+ (await P.pool->PgDriver.query(sql, b.params))->Array.map(decodeRow(~withId=true, ...))
330
355
  let hasMore = rows->Array.length > pageSize
331
356
  let pageItems = rows->Array.slice(~start=0, ~end=pageSize)
332
357
  let cursorField = orderByField->Option.getOr("id")
@@ -338,7 +363,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
338
363
  ReventlessCore.QueryDbListQuery.buildConnection(
339
364
  ~pageItems,
340
365
  ~hasNextPage=hasMore,
341
- ~hasPreviousPage=false,
366
+ // Same rule the spec uses, because the parity suite compares them: a
367
+ // forward page reached by a cursor has one behind it. Hardcoding false
368
+ // made Prev disappear on exactly the pages that have a previous one.
369
+ ~hasPreviousPage=afterCursor->Option.isSome,
342
370
  ~cursorValueOf,
343
371
  ),
344
372
  )
@@ -365,8 +393,10 @@ module Make = (P: {let pool: PgDriver.pool}) => {
365
393
  argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
366
394
  let fstr = k => filterDict->Dict.get(k)->Option.flatMap(JSON.Decode.string)
367
395
  let orderDesc = fstr("order")->Option.mapOr(false, o => o == "DESC")
368
- let last = argsDict->Dict.get("last")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
369
- let first = argsDict->Dict.get("first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
396
+ let last =
397
+ argsDict->Dict.get("last")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
398
+ let first =
399
+ argsDict->Dict.get("first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
370
400
  let after = argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string)
371
401
  let before = argsDict->Dict.get("before")->Option.flatMap(JSON.Decode.string)
372
402
  let isBackward = last->Option.isSome
@@ -387,25 +417,36 @@ module Make = (P: {let pool: PgDriver.pool}) => {
387
417
  fstr("prefix")->Option.forEach(p =>
388
418
  where->Array.push(`starts_with(sub_key, ${b->param(JSON.Encode.string(p))})`)
389
419
  )
390
- fstr("from")->Option.forEach(f => where->Array.push(`${sk} >= ${b->param(JSON.Encode.string(f))}`))
391
- fstr("to")->Option.forEach(t => where->Array.push(`${sk} <= ${b->param(JSON.Encode.string(t))}`))
420
+ fstr("from")->Option.forEach(f =>
421
+ where->Array.push(`${sk} >= ${b->param(JSON.Encode.string(f))}`)
422
+ )
423
+ fstr("to")->Option.forEach(t =>
424
+ where->Array.push(`${sk} <= ${b->param(JSON.Encode.string(t))}`)
425
+ )
392
426
  fstr("eq")->Option.forEach(e => where->Array.push(`${sk} = ${b->param(JSON.Encode.string(e))}`))
393
427
  // Keyset: forward excludes ≤ after; backward excludes ≥ before.
394
428
  switch (isBackward, after, before) {
395
429
  | (false, Some(c), _) =>
396
- where->Array.push(`${sk} > ${b->param(JSON.Encode.string(ReventlessCore.QueryDbListQuery.decodeCursor(c)))}`)
430
+ where->Array.push(
431
+ `${sk} > ${b->param(JSON.Encode.string(ReventlessCore.QueryDbListQuery.decodeCursor(c)))}`,
432
+ )
397
433
  | (true, _, Some(c)) =>
398
- where->Array.push(`${sk} < ${b->param(JSON.Encode.string(ReventlessCore.QueryDbListQuery.decodeCursor(c)))}`)
434
+ where->Array.push(
435
+ `${sk} < ${b->param(JSON.Encode.string(ReventlessCore.QueryDbListQuery.decodeCursor(c)))}`,
436
+ )
399
437
  | _ => ()
400
438
  }
401
439
  // Page-fetch direction is the LOGICAL order forward, and its OPPOSITE
402
440
  // backward (take the N nearest `before`, then reverse to logical) — mirrors
403
441
  // the resolver's `reverse = isBackward ? !orderDesc : orderDesc`.
404
442
  let fetchDesc = isBackward ? !orderDesc : orderDesc
405
- let pageSize = (isBackward ? last : first)->Option.getOr(ReventlessCore.QueryDbListQuery.defaultListPageSize)
443
+ let pageSize =
444
+ (isBackward ? last : first)->Option.getOr(ReventlessCore.QueryDbListQuery.defaultListPageSize)
406
445
  let sql = `SELECT partition_key, sub_key, item FROM ${table} WHERE ${where->Array.join(
407
446
  " AND ",
408
- )} ORDER BY ${sk} ${fetchDesc ? "DESC" : "ASC"} LIMIT ${b->param(JSON.Encode.int(pageSize + 1))}`
447
+ )} ORDER BY ${sk} ${fetchDesc ? "DESC" : "ASC"} LIMIT ${b->param(
448
+ JSON.Encode.int(pageSize + 1),
449
+ )}`
409
450
  let rows = await P.pool->PgDriver.query(sql, b.params)
410
451
  let hasMore = rows->Array.length > pageSize
411
452
  let taken = rows->Array.slice(~start=0, ~end=pageSize)
@@ -419,17 +460,18 @@ module Make = (P: {let pool: PgDriver.pool}) => {
419
460
  | _ => ""
420
461
  }
421
462
  let cursors = logical->Array.map(subKeyOf)
422
- let edges =
423
- pageItems->Array.mapWithIndex((item, i) =>
424
- Obj.magic({
425
- "node": item,
426
- "cursor": ReventlessCore.QueryDbListQuery.encodeCursor(cursors->Array.getUnsafe(i)),
427
- })
428
- )
463
+ let edges = pageItems->Array.mapWithIndex((item, i) =>
464
+ Obj.magic({
465
+ "node": item,
466
+ "cursor": ReventlessCore.QueryDbListQuery.encodeCursor(cursors->Array.getUnsafe(i)),
467
+ })
468
+ )
429
469
  let startCursor =
430
470
  cursors->Array.get(0)->Option.map(ReventlessCore.QueryDbListQuery.encodeCursor)
431
471
  let endCursor =
432
- cursors->Array.get(cursors->Array.length - 1)->Option.map(ReventlessCore.QueryDbListQuery.encodeCursor)
472
+ cursors
473
+ ->Array.get(cursors->Array.length - 1)
474
+ ->Option.map(ReventlessCore.QueryDbListQuery.encodeCursor)
433
475
  Obj.magic({
434
476
  "edges": edges,
435
477
  "pageInfo": {
@@ -235,11 +235,11 @@ function Make(P) {
235
235
  let orderByField = Stdlib_Option.flatMap(Stdlib_Option.flatMap(orderByDict, ob => ob["field"]), Stdlib_JSON.Decode.string);
236
236
  let isDesc = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(orderByDict, ob => ob["direction"]), Stdlib_JSON.Decode.string), "ASC") === "DESC";
237
237
  let cursorExpr = orderByField !== undefined ? jsonTextC(orderByField) : idExprC;
238
- let c = Stdlib_Option.flatMap(argsDict["after"], Stdlib_JSON.Decode.string);
239
- if (c !== undefined) {
238
+ let afterCursor = Stdlib_Option.flatMap(argsDict["after"], Stdlib_JSON.Decode.string);
239
+ if (afterCursor !== undefined) {
240
240
  whereParts.push(cursorExpr + ` ` + (
241
241
  isDesc ? "<" : ">"
242
- ) + ` ` + param(b, QueryDbListQuery$ReventlessCore.decodeCursor(c)));
242
+ ) + ` ` + param(b, QueryDbListQuery$ReventlessCore.decodeCursor(afterCursor)));
243
243
  }
244
244
  let orderClause = orderByField !== undefined ? jsonTextC(orderByField) + ` ` + (
245
245
  isDesc ? "DESC" : "ASC"
@@ -251,7 +251,7 @@ function Make(P) {
251
251
  let pageItems = rows.slice(0, pageSize);
252
252
  let cursorField = Stdlib_Option.getOr(orderByField, "id");
253
253
  let cursorValueOf = item => Stdlib_Option.getOr(QueryDbListQuery$ReventlessCore.getFieldString(item, cursorField), QueryDbListQuery$ReventlessCore.getId(item));
254
- return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore, false, cursorValueOf);
254
+ return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore, Stdlib_Option.isSome(afterCursor), cursorValueOf);
255
255
  };
256
256
  let itemsPage = async (readModelName, param$1, id, argsDict, ownerScope, retiredScope) => {
257
257
  let table = QueryDbStorage_Postgres_Ops$ReventlessPostgres.tableName(readModelName);