@reventlessdev/reventless-local 3.0.0-alpha.237 → 3.0.0-alpha.238

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 CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.238 (2026-08-23)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **querydb:** serve the previous page instead of refusing it ([48341c9](https://github.com/ReventlessDev/reventless-core/commit/48341c9f61b5155b441deeb0edc9abf461948346))
11
+
12
+
6
13
  # 3.0.0-alpha.237 (2026-08-23)
7
14
 
8
15
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-local",
3
- "version": "3.0.0-alpha.237",
3
+ "version": "3.0.0-alpha.238",
4
4
  "description": "Local platform for Reventless (in-memory or SQLite backend, for development and testing without AWS)",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -35,17 +35,17 @@
35
35
  "sury": "11.0.0-rc.2",
36
36
  "ws": "^8.18.0",
37
37
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
38
- "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.27",
39
38
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
40
- "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
39
+ "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.27",
41
40
  "@reventlessdev/rescript-node": "2.0.0-alpha.8",
41
+ "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
42
42
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
43
- "@reventlessdev/reventless-core": "3.0.0-alpha.249",
44
- "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.97",
45
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.196",
43
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.197",
46
44
  "@reventlessdev/reventless-infra": "3.0.0-alpha.151",
47
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.113",
45
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.114",
48
46
  "@reventlessdev/reventless-seed": "1.0.0-alpha.17",
47
+ "@reventlessdev/reventless-core": "3.0.0-alpha.250",
48
+ "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.98",
49
49
  "@reventlessdev/reventless-spec": "3.0.0-alpha.123"
50
50
  },
51
51
  "devDependencies": {
@@ -527,7 +527,15 @@ let makeStorage = (
527
527
  | Some(f) => jsonText(f)
528
528
  | None => idExpr
529
529
  }
530
- switch argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string) {
530
+ // Empty is absent, as it is in the spec: the host shell clears the opposite
531
+ // cursor by setting it to "" rather than dropping it, and an empty cursor
532
+ // pushed into SQL bounds the read on a decoded empty string.
533
+ let afterCursor =
534
+ argsDict
535
+ ->Dict.get("after")
536
+ ->Option.flatMap(JSON.Decode.string)
537
+ ->Option.flatMap(c => c == "" ? None : Some(c))
538
+ switch afterCursor {
531
539
  | Some(c) =>
532
540
  whereParts->Array.push(`${cursorExpr} ${isDesc ? "<" : ">"} ?`)
533
541
  params->Array.push(JSON.Encode.string(QueryDbListQuery.decodeCursor(c)))
@@ -557,7 +565,10 @@ let makeStorage = (
557
565
  QueryDbListQuery.buildConnection(
558
566
  ~pageItems,
559
567
  ~hasNextPage=hasMore,
560
- ~hasPreviousPage=false,
568
+ // Same rule the spec uses, because the parity suite compares them: a
569
+ // forward page reached by a cursor has one behind it. Hardcoding false
570
+ // made Prev disappear on exactly the pages that have a previous one.
571
+ ~hasPreviousPage=afterCursor->Option.isSome,
561
572
  ~cursorValueOf,
562
573
  ),
563
574
  )
@@ -396,12 +396,18 @@ function makeStorage(db, bus, name, indexes, subIdField) {
396
396
  let orderByField = Stdlib_Option.flatMap(Stdlib_Option.flatMap(orderByDict, ob => ob["field"]), Stdlib_JSON.Decode.string);
397
397
  let isDesc = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(orderByDict, ob => ob["direction"]), Stdlib_JSON.Decode.string), "ASC") === "DESC";
398
398
  let cursorExpr = orderByField !== undefined ? jsonText(orderByField) : idExpr;
399
- let c = Stdlib_Option.flatMap(argsDict["after"], Stdlib_JSON.Decode.string);
400
- if (c !== undefined) {
399
+ let afterCursor = Stdlib_Option.flatMap(Stdlib_Option.flatMap(argsDict["after"], Stdlib_JSON.Decode.string), c => {
400
+ if (c === "") {
401
+ return;
402
+ } else {
403
+ return c;
404
+ }
405
+ });
406
+ if (afterCursor !== undefined) {
401
407
  whereParts.push(cursorExpr + ` ` + (
402
408
  isDesc ? "<" : ">"
403
409
  ) + ` ?`);
404
- params.push(QueryDbListQuery$ReventlessCore.decodeCursor(c));
410
+ params.push(QueryDbListQuery$ReventlessCore.decodeCursor(afterCursor));
405
411
  }
406
412
  let orderClause = orderByField !== undefined ? jsonText(orderByField) + ` ` + (
407
413
  isDesc ? "DESC" : "ASC"
@@ -413,7 +419,7 @@ function makeStorage(db, bus, name, indexes, subIdField) {
413
419
  let pageItems = rows.slice(0, pageSize);
414
420
  let cursorField = Stdlib_Option.getOr(orderByField, "id");
415
421
  let cursorValueOf = item => Stdlib_Option.getOr(QueryDbListQuery$ReventlessCore.getFieldString(item, cursorField), QueryDbListQuery$ReventlessCore.getId(item));
416
- return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore, false, cursorValueOf);
422
+ return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore, Stdlib_Option.isSome(afterCursor), cursorValueOf);
417
423
  };
418
424
  bus.registerQueryDbListPage(name, listPage);
419
425
  return {
@@ -291,7 +291,10 @@ describe("QueryDb list resolver — keyset pagination", () => {
291
291
  expect(edgeNodeField(backEdges->Array.getUnsafe(0), "productId"))->toBe("p-3")
292
292
  expect(edgeNodeField(backEdges->Array.getUnsafe(1), "productId"))->toBe("p-4")
293
293
  expect(pageInfoBool(backPage, "hasPreviousPage"))->toBe(true)
294
- expect(pageInfoBool(backPage, "hasNextPage"))->toBe(false)
294
+ // A backward page was cut from ahead, so the page it came from is a next
295
+ // page. Reporting false here is what made Prev a one-way door: step back
296
+ // once and the connection claimed to have ended.
297
+ expect(pageInfoBool(backPage, "hasNextPage"))->toBe(true)
295
298
 
296
299
  // Walk one more step back — should land on p-1, p-2 with hasPreviousPage = false.
297
300
  let backStart = pageInfoString(backPage, "startCursor")->Option.getOr("")
@@ -334,7 +334,7 @@ globalThis.describe("QueryDb list resolver — keyset pagination", () => {
334
334
  globalThis.expect(edgeNodeField(backEdges[0], "productId")).toBe("p-3");
335
335
  globalThis.expect(edgeNodeField(backEdges[1], "productId")).toBe("p-4");
336
336
  globalThis.expect(pageInfoBool(backPage, "hasPreviousPage")).toBe(true);
337
- globalThis.expect(pageInfoBool(backPage, "hasNextPage")).toBe(false);
337
+ globalThis.expect(pageInfoBool(backPage, "hasNextPage")).toBe(true);
338
338
  let backStart = Stdlib_Option.getOr(getString(pageInfo(backPage), "startCursor"), "");
339
339
  let firstPage = await resolver(null, Object.fromEntries([
340
340
  [