@reventlessdev/reventless-local 3.0.0-alpha.121 → 3.0.0-alpha.122
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 +7 -0
- package/package.json +11 -11
- package/src/adapter/QueryDb/QueryDbResolvers_GraphQL.res.mjs +3 -3
- package/src/adapter/QueryDb/QueryDbStorage_Sqlite.res.mjs +5 -5
- package/tests/adapter/QueryDbListPushdownParityTest.res +2 -2
- package/tests/adapter/QueryDbListPushdownParityTest.res.mjs +6 -6
- package/src/adapter/QueryDb/QueryDbListQuery.res +0 -261
- package/src/adapter/QueryDb/QueryDbListQuery.res.mjs +0 -212
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.122 (2026-07-06)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **reventless-aws:** classic EventLog Postgres deploy-time wiring + relay (B1 vertical) ([8235ba4](https://github.com/ReventlessDev/reventless-core/commit/8235ba44e506f7094d17251405c6a05c39789805))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 3.0.0-alpha.121 (2026-07-05)
|
|
7
14
|
|
|
8
15
|
**Note:** Version bump only for package @reventlessdev/reventless-local
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-local",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.122",
|
|
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
|
"jest": {
|
|
@@ -31,16 +31,16 @@
|
|
|
31
31
|
"graphql-yoga": "^5.21.0",
|
|
32
32
|
"sury": "11.0.0-alpha.4",
|
|
33
33
|
"ws": "^8.18.0",
|
|
34
|
-
"@reventlessdev/rescript-
|
|
35
|
-
"@reventlessdev/rescript-
|
|
36
|
-
"@reventlessdev/rescript-
|
|
37
|
-
"@reventlessdev/rescript-
|
|
34
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.25",
|
|
35
|
+
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.18",
|
|
36
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.6",
|
|
37
|
+
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.17",
|
|
38
38
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.14",
|
|
39
|
-
"@reventlessdev/reventless-
|
|
40
|
-
"@reventlessdev/reventless-
|
|
41
|
-
"@reventlessdev/reventless-
|
|
42
|
-
"@reventlessdev/reventless-
|
|
43
|
-
"@reventlessdev/reventless-
|
|
39
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.143",
|
|
40
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.89",
|
|
41
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.67",
|
|
42
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.5",
|
|
43
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.83"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"rescript": "^12.3.0",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"build": "rescript build",
|
|
63
63
|
"start": "rescript watch",
|
|
64
64
|
"clean": "rescript clean",
|
|
65
|
-
"rebuild": "pnpm run clean && pnpm run build
|
|
65
|
+
"rebuild": "pnpm run clean && pnpm run build",
|
|
66
66
|
"test": "LOG_LEVEL=${LOG_LEVEL:-silent} NODE_OPTIONS='--experimental-vm-modules' npx jest"
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -10,8 +10,8 @@ import * as Identity$Reventless from "@reventlessdev/reventless-spec/src/types/I
|
|
|
10
10
|
import * as Authorization$Reventless from "@reventlessdev/reventless-spec/src/types/Authorization.res.mjs";
|
|
11
11
|
import * as Plugin_Helpers$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin_Helpers.res.mjs";
|
|
12
12
|
import * as SortKey_Filter$ReventlessLocal from "./SortKey_Filter.res.mjs";
|
|
13
|
+
import * as QueryDbListQuery$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/QueryDbListQuery.res.mjs";
|
|
13
14
|
import * as QueryDb_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/QueryDb/QueryDb_Callback.res.mjs";
|
|
14
|
-
import * as QueryDbListQuery$ReventlessLocal from "./QueryDbListQuery.res.mjs";
|
|
15
15
|
import * as DomainGraphQL_Server$ReventlessLocal from "../DomainGraphQL_Server.res.mjs";
|
|
16
16
|
import * as GraphQL_FragmentGenerator$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/GraphQL_FragmentGenerator.res.mjs";
|
|
17
17
|
|
|
@@ -199,10 +199,10 @@ function Make(Bus) {
|
|
|
199
199
|
return conn;
|
|
200
200
|
}
|
|
201
201
|
let items = await fetchAllItems();
|
|
202
|
-
return QueryDbListQuery$
|
|
202
|
+
return QueryDbListQuery$ReventlessCore.run(items, argsDict, capability, labelField, decodeLocalId);
|
|
203
203
|
}
|
|
204
204
|
let items$1 = await fetchAllItems();
|
|
205
|
-
return QueryDbListQuery$
|
|
205
|
+
return QueryDbListQuery$ReventlessCore.run(items$1, argsDict, capability, labelField, decodeLocalId);
|
|
206
206
|
};
|
|
207
207
|
match = [
|
|
208
208
|
sdl,
|
|
@@ -7,7 +7,7 @@ import * as Pulumi from "@pulumi/pulumi";
|
|
|
7
7
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
8
8
|
import * as LocalBus$ReventlessLocal from "../LocalBus.res.mjs";
|
|
9
9
|
import * as SqliteDriver$ReventlessLocal from "../SqliteDriver.res.mjs";
|
|
10
|
-
import * as QueryDbListQuery$
|
|
10
|
+
import * as QueryDbListQuery$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/QueryDbListQuery.res.mjs";
|
|
11
11
|
|
|
12
12
|
function tableName(name) {
|
|
13
13
|
return "qdb_" + name.replaceAll("-", "_");
|
|
@@ -366,19 +366,19 @@ function makeStorage(db, bus, name, indexes, subIdField) {
|
|
|
366
366
|
whereParts.push(cursorExpr + ` ` + (
|
|
367
367
|
isDesc ? "<" : ">"
|
|
368
368
|
) + ` ?`);
|
|
369
|
-
params.push(QueryDbListQuery$
|
|
369
|
+
params.push(QueryDbListQuery$ReventlessCore.decodeCursor(c));
|
|
370
370
|
}
|
|
371
371
|
let orderClause = orderByField !== undefined ? jsonText(orderByField) + ` ` + (
|
|
372
372
|
isDesc ? "DESC" : "ASC"
|
|
373
373
|
) + `, ` + idExpr + ` ASC` : idExpr + ` ASC`;
|
|
374
|
-
let pageSize = Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_Option.flatMap(argsDict["first"], Stdlib_JSON.Decode.float), prim => prim | 0), QueryDbListQuery$
|
|
374
|
+
let pageSize = Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_Option.flatMap(argsDict["first"], Stdlib_JSON.Decode.float), prim => prim | 0), QueryDbListQuery$ReventlessCore.defaultListPageSize);
|
|
375
375
|
let sql = `SELECT partition_key, item FROM ` + table + ` WHERE ` + whereParts.join(" AND ") + ` ORDER BY ` + orderClause + ` LIMIT ?`;
|
|
376
376
|
let rows = SqliteDriver$ReventlessLocal.all(preparedList(sql), params.concat([pageSize + 1 | 0])).map(rowToItem);
|
|
377
377
|
let hasMore = rows.length > pageSize;
|
|
378
378
|
let pageItems = rows.slice(0, pageSize);
|
|
379
379
|
let cursorField = Stdlib_Option.getOr(orderByField, "id");
|
|
380
|
-
let cursorValueOf = item => Stdlib_Option.getOr(QueryDbListQuery$
|
|
381
|
-
return QueryDbListQuery$
|
|
380
|
+
let cursorValueOf = item => Stdlib_Option.getOr(QueryDbListQuery$ReventlessCore.getFieldString(item, cursorField), QueryDbListQuery$ReventlessCore.getId(item));
|
|
381
|
+
return QueryDbListQuery$ReventlessCore.buildConnection(pageItems, hasMore, false, cursorValueOf);
|
|
382
382
|
};
|
|
383
383
|
bus.registerQueryDbListPage(name, listPage);
|
|
384
384
|
return {
|
|
@@ -107,14 +107,14 @@ let orderBy = (f, dir) =>
|
|
|
107
107
|
JSON.Encode.object(
|
|
108
108
|
Dict.fromArray([("field", JSON.Encode.string(f)), ("direction", JSON.Encode.string(dir))]),
|
|
109
109
|
)
|
|
110
|
-
let cur = QueryDbListQuery.encodeCursor
|
|
110
|
+
let cur = ReventlessCore.QueryDbListQuery.encodeCursor
|
|
111
111
|
|
|
112
112
|
// Assert the push-down serves this shape AND matches the spec exactly.
|
|
113
113
|
let checkPushed = async (~label, args) => {
|
|
114
114
|
let s = await build()
|
|
115
115
|
let argsDict = argsOf(args)
|
|
116
116
|
let expected =
|
|
117
|
-
QueryDbListQuery.run(
|
|
117
|
+
ReventlessCore.QueryDbListQuery.run(
|
|
118
118
|
~items=s.fullScan(),
|
|
119
119
|
~argsDict,
|
|
120
120
|
~capability,
|
|
@@ -5,7 +5,7 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
5
5
|
import * as LocalBus$ReventlessLocal from "../../src/adapter/LocalBus.res.mjs";
|
|
6
6
|
import * as TestRunner$ReventlessLocal from "../../src/test/TestRunner.res.mjs";
|
|
7
7
|
import * as SqliteDriver$ReventlessLocal from "../../src/adapter/SqliteDriver.res.mjs";
|
|
8
|
-
import * as QueryDbListQuery$
|
|
8
|
+
import * as QueryDbListQuery$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/QueryDbListQuery.res.mjs";
|
|
9
9
|
import * as QueryDbStorage_Sqlite$ReventlessLocal from "../../src/adapter/QueryDb/QueryDbStorage_Sqlite.res.mjs";
|
|
10
10
|
|
|
11
11
|
TestRunner$ReventlessLocal.setup();
|
|
@@ -145,7 +145,7 @@ function orderBy(f, dir) {
|
|
|
145
145
|
async function checkPushed(label, args) {
|
|
146
146
|
let s = await build();
|
|
147
147
|
let argsDict = Object.fromEntries(args);
|
|
148
|
-
let expected = norm(QueryDbListQuery$
|
|
148
|
+
let expected = norm(QueryDbListQuery$ReventlessCore.run(s.fullScan(), argsDict, capability, "name", param => {}));
|
|
149
149
|
let actual = s.listPage(argsDict, capability, "name");
|
|
150
150
|
if (actual !== undefined) {
|
|
151
151
|
globalThis.expect(norm(actual)).toEqual(expected);
|
|
@@ -172,7 +172,7 @@ globalThis.describe("QueryDb list push-down parity (SQLite ≡ QueryDbListQuery
|
|
|
172
172
|
],
|
|
173
173
|
[
|
|
174
174
|
"after",
|
|
175
|
-
QueryDbListQuery$
|
|
175
|
+
QueryDbListQuery$ReventlessCore.encodeCursor("p-2")
|
|
176
176
|
]
|
|
177
177
|
]));
|
|
178
178
|
globalThis.test("statusEq active", () => checkPushed("eq", [[
|
|
@@ -214,7 +214,7 @@ globalThis.describe("QueryDb list push-down parity (SQLite ≡ QueryDbListQuery
|
|
|
214
214
|
],
|
|
215
215
|
[
|
|
216
216
|
"after",
|
|
217
|
-
QueryDbListQuery$
|
|
217
|
+
QueryDbListQuery$ReventlessCore.encodeCursor("Delta")
|
|
218
218
|
]
|
|
219
219
|
]));
|
|
220
220
|
globalThis.test("orderBy status ASC (tiebreak by id)", () => checkPushed("order-tiebreak", [[
|
|
@@ -266,12 +266,12 @@ globalThis.describe("QueryDb list push-down parity (SQLite ≡ QueryDbListQuery
|
|
|
266
266
|
],
|
|
267
267
|
[
|
|
268
268
|
"before",
|
|
269
|
-
QueryDbListQuery$
|
|
269
|
+
QueryDbListQuery$ReventlessCore.encodeCursor("p-4")
|
|
270
270
|
]
|
|
271
271
|
]));
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
-
let cur = QueryDbListQuery$
|
|
274
|
+
let cur = QueryDbListQuery$ReventlessCore.encodeCursor;
|
|
275
275
|
|
|
276
276
|
export {
|
|
277
277
|
opts,
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
// The connection-list filter / sort / keyset-paginate logic, extracted verbatim
|
|
2
|
-
// from QueryDbResolvers_GraphQL so there is ONE source of truth for the
|
|
3
|
-
// semantics. The resolver calls `run` over the materialised items (in-memory
|
|
4
|
-
// path); the SQLite backend's list push-down must reproduce these exact
|
|
5
|
-
// semantics, and the parity harness asserts it does. Keeping this pure (no
|
|
6
|
-
// server / storage dependencies — the one server helper it needs, global-id
|
|
7
|
-
// decoding for the `ids` filter, is passed in) is what lets it be the shared
|
|
8
|
-
// spec both paths are tested against.
|
|
9
|
-
|
|
10
|
-
open ReventlessCore
|
|
11
|
-
|
|
12
|
-
// base64 cursor of the active sort field's value. Shared with the resolver's
|
|
13
|
-
// items (sub-id) connection so cursor encoding stays in lockstep.
|
|
14
|
-
@val external btoa: string => string = "btoa"
|
|
15
|
-
@val external atob: string => string = "atob"
|
|
16
|
-
let encodeCursor = (value: string): string => btoa(value)
|
|
17
|
-
let decodeCursor = (cursor: string): string => atob(cursor)
|
|
18
|
-
|
|
19
|
-
// Default page size when neither `first` nor `last` is supplied — a bound is
|
|
20
|
-
// required for keyset `pageInfo` to be reported correctly.
|
|
21
|
-
let defaultListPageSize = 50
|
|
22
|
-
|
|
23
|
-
// The effective id of an item (its "id" field). Shared with the SQLite
|
|
24
|
-
// push-down so both compute the tiebreak / default-cursor value identically.
|
|
25
|
-
let getId = (item: JSON.t): string =>
|
|
26
|
-
item
|
|
27
|
-
->JSON.Decode.object
|
|
28
|
-
->Option.flatMap(d => d->Dict.get("id"))
|
|
29
|
-
->Option.flatMap(JSON.Decode.string)
|
|
30
|
-
->Option.getOr("")
|
|
31
|
-
|
|
32
|
-
// String form of a field for comparison / cursor purposes: the string itself,
|
|
33
|
-
// or a number rendered via Float.toString, else None. The push-down must render
|
|
34
|
-
// the same string in SQL (CAST … AS TEXT) for cursor/order parity.
|
|
35
|
-
let getFieldString = (item: JSON.t, field: string): option<string> =>
|
|
36
|
-
item
|
|
37
|
-
->JSON.Decode.object
|
|
38
|
-
->Option.flatMap(d => d->Dict.get(field))
|
|
39
|
-
->Option.flatMap(v =>
|
|
40
|
-
switch v->JSON.Decode.string {
|
|
41
|
-
| Some(s) => Some(s)
|
|
42
|
-
| None => v->JSON.Decode.float->Option.map(f => Float.toString(f))
|
|
43
|
-
}
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
// Build the Relay connection object from an already-ordered page. Shared by
|
|
47
|
-
// `run` and the SQLite push-down so edges / cursors / pageInfo have one shape.
|
|
48
|
-
let buildConnection = (
|
|
49
|
-
~pageItems: array<JSON.t>,
|
|
50
|
-
~hasNextPage: bool,
|
|
51
|
-
~hasPreviousPage: bool,
|
|
52
|
-
~cursorValueOf: JSON.t => string,
|
|
53
|
-
): JSON.t => {
|
|
54
|
-
let edges =
|
|
55
|
-
pageItems->Array.map(item => Obj.magic({"node": item, "cursor": encodeCursor(cursorValueOf(item))}))
|
|
56
|
-
let startCursor = pageItems->Array.get(0)->Option.map(item => encodeCursor(cursorValueOf(item)))
|
|
57
|
-
let endCursor =
|
|
58
|
-
pageItems->Array.get(pageItems->Array.length - 1)->Option.map(item => encodeCursor(cursorValueOf(item)))
|
|
59
|
-
Obj.magic({
|
|
60
|
-
"edges": edges,
|
|
61
|
-
"pageInfo": {
|
|
62
|
-
"hasNextPage": hasNextPage,
|
|
63
|
-
"hasPreviousPage": hasPreviousPage,
|
|
64
|
-
"startCursor": startCursor->Nullable.fromOption,
|
|
65
|
-
"endCursor": endCursor->Nullable.fromOption,
|
|
66
|
-
},
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Compute the Relay connection response (edges + pageInfo) for `items` under the
|
|
71
|
-
// GraphQL args. `decodeLocalId` maps a possibly-Relay-encoded id to its local id
|
|
72
|
-
// (the resolver passes DomainGraphQL_Server.decodeGlobalId); everything else is
|
|
73
|
-
// pure over the item array.
|
|
74
|
-
let run = (
|
|
75
|
-
~items: array<JSON.t>,
|
|
76
|
-
~argsDict: Dict.t<JSON.t>,
|
|
77
|
-
~capability: GraphQL_FragmentGenerator.serverCapability,
|
|
78
|
-
~labelField: string,
|
|
79
|
-
~decodeLocalId: string => option<string>,
|
|
80
|
-
): JSON.t => {
|
|
81
|
-
let filterDict =
|
|
82
|
-
argsDict->Dict.get("filter")->Option.flatMap(JSON.Decode.object)->Option.getOr(Dict.make())
|
|
83
|
-
let search = filterDict->Dict.get("search")->Option.flatMap(JSON.Decode.string)
|
|
84
|
-
let searchPrefix = filterDict->Dict.get("searchPrefix")->Option.flatMap(JSON.Decode.string)
|
|
85
|
-
let ids =
|
|
86
|
-
filterDict
|
|
87
|
-
->Dict.get("ids")
|
|
88
|
-
->Option.flatMap(JSON.Decode.array)
|
|
89
|
-
->Option.map(arr => arr->Array.filterMap(JSON.Decode.string))
|
|
90
|
-
let getLabel = item =>
|
|
91
|
-
item
|
|
92
|
-
->JSON.Decode.object
|
|
93
|
-
->Option.flatMap(d => d->Dict.get(labelField))
|
|
94
|
-
->Option.flatMap(JSON.Decode.string)
|
|
95
|
-
->Option.getOr("")
|
|
96
|
-
// Per-field eq / from / to filters derived from capability — applied alongside
|
|
97
|
-
// the legacy search/searchPrefix/ids block.
|
|
98
|
-
let perFieldChecks: array<JSON.t => bool> = capability.filterFields->Array.flatMap(f => {
|
|
99
|
-
let checks: array<JSON.t => bool> = []
|
|
100
|
-
switch filterDict->Dict.get(f.name ++ "Eq") {
|
|
101
|
-
| Some(v) when v != JSON.Encode.null =>
|
|
102
|
-
let expected = switch v->JSON.Decode.string {
|
|
103
|
-
| Some(s) => s
|
|
104
|
-
| None => v->JSON.Decode.float->Option.map(f => Float.toString(f))->Option.getOr("")
|
|
105
|
-
}
|
|
106
|
-
checks->Array.push(item =>
|
|
107
|
-
getFieldString(item, f.name)->Option.mapOr(false, v => v == expected)
|
|
108
|
-
)
|
|
109
|
-
| _ => ()
|
|
110
|
-
}
|
|
111
|
-
if f.range {
|
|
112
|
-
switch filterDict->Dict.get(f.name ++ "From") {
|
|
113
|
-
| Some(v) when v != JSON.Encode.null =>
|
|
114
|
-
let from =
|
|
115
|
-
v
|
|
116
|
-
->JSON.Decode.string
|
|
117
|
-
->Option.getOr(v->JSON.Decode.float->Option.map(f => Float.toString(f))->Option.getOr(""))
|
|
118
|
-
checks->Array.push(item => getFieldString(item, f.name)->Option.mapOr(false, v => v >= from))
|
|
119
|
-
| _ => ()
|
|
120
|
-
}
|
|
121
|
-
switch filterDict->Dict.get(f.name ++ "To") {
|
|
122
|
-
| Some(v) when v != JSON.Encode.null =>
|
|
123
|
-
let to_ =
|
|
124
|
-
v
|
|
125
|
-
->JSON.Decode.string
|
|
126
|
-
->Option.getOr(v->JSON.Decode.float->Option.map(f => Float.toString(f))->Option.getOr(""))
|
|
127
|
-
checks->Array.push(item => getFieldString(item, f.name)->Option.mapOr(false, v => v <= to_))
|
|
128
|
-
| _ => ()
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
checks
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
let filtered = items->Array.filter(item => {
|
|
135
|
-
let passSearch = switch search {
|
|
136
|
-
| Some(s) if s->String.length > 0 =>
|
|
137
|
-
getLabel(item)->String.toLowerCase->String.includes(s->String.toLowerCase)
|
|
138
|
-
| _ => true
|
|
139
|
-
}
|
|
140
|
-
let passPrefix = switch searchPrefix {
|
|
141
|
-
| Some(p) if p->String.length > 0 =>
|
|
142
|
-
getLabel(item)->String.toLowerCase->String.startsWith(p->String.toLowerCase)
|
|
143
|
-
| _ => true
|
|
144
|
-
}
|
|
145
|
-
let passIds = switch ids {
|
|
146
|
-
| Some(idList) if idList->Array.length > 0 =>
|
|
147
|
-
let itemId = getId(item)
|
|
148
|
-
let itemLocalId = decodeLocalId(itemId)
|
|
149
|
-
idList->Array.some(i => i == itemId || itemLocalId == Some(i))
|
|
150
|
-
| _ => true
|
|
151
|
-
}
|
|
152
|
-
let passPerField = perFieldChecks->Array.every(check => check(item))
|
|
153
|
-
passSearch && passPrefix && passIds && passPerField
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
let orderByDict = argsDict->Dict.get("orderBy")->Option.flatMap(JSON.Decode.object)
|
|
157
|
-
let orderByField =
|
|
158
|
-
orderByDict->Option.flatMap(ob => ob->Dict.get("field"))->Option.flatMap(JSON.Decode.string)
|
|
159
|
-
let direction =
|
|
160
|
-
orderByDict
|
|
161
|
-
->Option.flatMap(ob => ob->Dict.get("direction"))
|
|
162
|
-
->Option.flatMap(JSON.Decode.string)
|
|
163
|
-
->Option.getOr("ASC")
|
|
164
|
-
let isDesc = direction == "DESC"
|
|
165
|
-
let sorted = switch orderByField {
|
|
166
|
-
| Some(f) =>
|
|
167
|
-
let cmp = (a, b) => {
|
|
168
|
-
let av = getFieldString(a, f)->Option.getOr("")
|
|
169
|
-
let bv = getFieldString(b, f)->Option.getOr("")
|
|
170
|
-
let primary = if av < bv {
|
|
171
|
-
-1
|
|
172
|
-
} else if av > bv {
|
|
173
|
-
1
|
|
174
|
-
} else {
|
|
175
|
-
0
|
|
176
|
-
}
|
|
177
|
-
let primary = isDesc ? -primary : primary
|
|
178
|
-
if primary != 0 {
|
|
179
|
-
primary
|
|
180
|
-
} else {
|
|
181
|
-
let aid = getId(a)
|
|
182
|
-
let bid = getId(b)
|
|
183
|
-
if aid < bid {
|
|
184
|
-
-1
|
|
185
|
-
} else if aid > bid {
|
|
186
|
-
1
|
|
187
|
-
} else {
|
|
188
|
-
0
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
filtered->Array.toSorted((a, b) => cmp(a, b)->Int.toFloat)
|
|
193
|
-
| None =>
|
|
194
|
-
filtered->Array.toSorted((a, b) => {
|
|
195
|
-
let aid = getId(a)
|
|
196
|
-
let bid = getId(b)
|
|
197
|
-
if aid < bid {
|
|
198
|
-
-1.
|
|
199
|
-
} else if aid > bid {
|
|
200
|
-
1.
|
|
201
|
-
} else {
|
|
202
|
-
0.
|
|
203
|
-
}
|
|
204
|
-
})
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
let first =
|
|
208
|
-
argsDict->Dict.get("first")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
|
|
209
|
-
let after = argsDict->Dict.get("after")->Option.flatMap(JSON.Decode.string)
|
|
210
|
-
let last = argsDict->Dict.get("last")->Option.flatMap(JSON.Decode.float)->Option.map(Float.toInt)
|
|
211
|
-
let before = argsDict->Dict.get("before")->Option.flatMap(JSON.Decode.string)
|
|
212
|
-
let isBackward = last->Option.isSome
|
|
213
|
-
let cursorField = orderByField->Option.getOr("id")
|
|
214
|
-
let getCursorValue = item => getFieldString(item, cursorField)->Option.getOr(getId(item))
|
|
215
|
-
|
|
216
|
-
let cursorBounded = switch (isBackward, after, before) {
|
|
217
|
-
| (false, Some(c), _) =>
|
|
218
|
-
let cv = decodeCursor(c)
|
|
219
|
-
sorted->Array.filter(item => {
|
|
220
|
-
let v = getCursorValue(item)
|
|
221
|
-
isDesc ? v < cv : v > cv
|
|
222
|
-
})
|
|
223
|
-
| (true, _, Some(c)) =>
|
|
224
|
-
let cv = decodeCursor(c)
|
|
225
|
-
sorted->Array.filter(item => {
|
|
226
|
-
let v = getCursorValue(item)
|
|
227
|
-
isDesc ? v > cv : v < cv
|
|
228
|
-
})
|
|
229
|
-
| _ => sorted
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
let pageSize = if isBackward {
|
|
233
|
-
last->Option.getOr(defaultListPageSize)
|
|
234
|
-
} else {
|
|
235
|
-
first->Option.getOr(defaultListPageSize)
|
|
236
|
-
}
|
|
237
|
-
let take = pageSize + 1
|
|
238
|
-
let (pageItems, hasMore) = if isBackward {
|
|
239
|
-
let len = cursorBounded->Array.length
|
|
240
|
-
let startIdx = len > take ? len - take : 0
|
|
241
|
-
let arr = cursorBounded->Array.slice(~start=startIdx, ~end=len)
|
|
242
|
-
let hasMore = arr->Array.length > pageSize
|
|
243
|
-
let result = if hasMore {
|
|
244
|
-
arr->Array.slice(~start=1, ~end=arr->Array.length)
|
|
245
|
-
} else {
|
|
246
|
-
arr
|
|
247
|
-
}
|
|
248
|
-
(result, hasMore)
|
|
249
|
-
} else {
|
|
250
|
-
let arr = cursorBounded->Array.slice(~start=0, ~end=take)
|
|
251
|
-
let hasMore = arr->Array.length > pageSize
|
|
252
|
-
(arr->Array.slice(~start=0, ~end=pageSize), hasMore)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
buildConnection(
|
|
256
|
-
~pageItems,
|
|
257
|
-
~hasNextPage=!isBackward && hasMore,
|
|
258
|
-
~hasPreviousPage=isBackward && hasMore,
|
|
259
|
-
~cursorValueOf=getCursorValue,
|
|
260
|
-
)
|
|
261
|
-
}
|
|
@@ -1,212 +0,0 @@
|
|
|
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_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
5
|
-
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
|
-
import * as Stdlib_Nullable from "@rescript/runtime/lib/es6/Stdlib_Nullable.js";
|
|
7
|
-
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
8
|
-
|
|
9
|
-
function encodeCursor(value) {
|
|
10
|
-
return btoa(value);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function decodeCursor(cursor) {
|
|
14
|
-
return atob(cursor);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function getId(item) {
|
|
18
|
-
return Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(item), d => d["id"]), Stdlib_JSON.Decode.string), "");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getFieldString(item, field) {
|
|
22
|
-
return Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(item), d => d[field]), v => {
|
|
23
|
-
let s = Stdlib_JSON.Decode.string(v);
|
|
24
|
-
if (s !== undefined) {
|
|
25
|
-
return s;
|
|
26
|
-
} else {
|
|
27
|
-
return Stdlib_Option.map(Stdlib_JSON.Decode.float(v), f => f.toString());
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function buildConnection(pageItems, hasNextPage, hasPreviousPage, cursorValueOf) {
|
|
33
|
-
let edges = pageItems.map(item => ({
|
|
34
|
-
node: item,
|
|
35
|
-
cursor: btoa(cursorValueOf(item))
|
|
36
|
-
}));
|
|
37
|
-
let startCursor = Stdlib_Option.map(pageItems[0], item => btoa(cursorValueOf(item)));
|
|
38
|
-
let endCursor = Stdlib_Option.map(pageItems[pageItems.length - 1 | 0], item => btoa(cursorValueOf(item)));
|
|
39
|
-
return {
|
|
40
|
-
edges: edges,
|
|
41
|
-
pageInfo: {
|
|
42
|
-
hasNextPage: hasNextPage,
|
|
43
|
-
hasPreviousPage: hasPreviousPage,
|
|
44
|
-
startCursor: Stdlib_Nullable.fromOption(startCursor),
|
|
45
|
-
endCursor: Stdlib_Nullable.fromOption(endCursor)
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function run(items, argsDict, capability, labelField, decodeLocalId) {
|
|
51
|
-
let filterDict = Stdlib_Option.getOr(Stdlib_Option.flatMap(argsDict["filter"], Stdlib_JSON.Decode.object), {});
|
|
52
|
-
let search = Stdlib_Option.flatMap(filterDict["search"], Stdlib_JSON.Decode.string);
|
|
53
|
-
let searchPrefix = Stdlib_Option.flatMap(filterDict["searchPrefix"], Stdlib_JSON.Decode.string);
|
|
54
|
-
let ids = Stdlib_Option.map(Stdlib_Option.flatMap(filterDict["ids"], Stdlib_JSON.Decode.array), arr => Stdlib_Array.filterMap(arr, Stdlib_JSON.Decode.string));
|
|
55
|
-
let getLabel = item => Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(item), d => d[labelField]), Stdlib_JSON.Decode.string), "");
|
|
56
|
-
let perFieldChecks = capability.filterFields.flatMap(f => {
|
|
57
|
-
let checks = [];
|
|
58
|
-
let v = filterDict[f.name + "Eq"];
|
|
59
|
-
if (v !== undefined && v !== null) {
|
|
60
|
-
let s = Stdlib_JSON.Decode.string(v);
|
|
61
|
-
let expected = s !== undefined ? s : Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_JSON.Decode.float(v), f => f.toString()), "");
|
|
62
|
-
checks.push(item => Stdlib_Option.mapOr(getFieldString(item, f.name), false, v => v === expected));
|
|
63
|
-
}
|
|
64
|
-
if (f.range) {
|
|
65
|
-
let v$1 = filterDict[f.name + "From"];
|
|
66
|
-
if (v$1 !== undefined && v$1 !== null) {
|
|
67
|
-
let from = Stdlib_Option.getOr(Stdlib_JSON.Decode.string(v$1), Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_JSON.Decode.float(v$1), f => f.toString()), ""));
|
|
68
|
-
checks.push(item => Stdlib_Option.mapOr(getFieldString(item, f.name), false, v => v >= from));
|
|
69
|
-
}
|
|
70
|
-
let v$2 = filterDict[f.name + "To"];
|
|
71
|
-
if (v$2 !== undefined && v$2 !== null) {
|
|
72
|
-
let to_ = Stdlib_Option.getOr(Stdlib_JSON.Decode.string(v$2), Stdlib_Option.getOr(Stdlib_Option.map(Stdlib_JSON.Decode.float(v$2), f => f.toString()), ""));
|
|
73
|
-
checks.push(item => Stdlib_Option.mapOr(getFieldString(item, f.name), false, v => v <= to_));
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return checks;
|
|
77
|
-
});
|
|
78
|
-
let filtered = items.filter(item => {
|
|
79
|
-
let passSearch = search !== undefined && search.length > 0 ? getLabel(item).toLowerCase().includes(search.toLowerCase()) : true;
|
|
80
|
-
let passPrefix = searchPrefix !== undefined && searchPrefix.length > 0 ? getLabel(item).toLowerCase().startsWith(searchPrefix.toLowerCase()) : true;
|
|
81
|
-
let passIds;
|
|
82
|
-
if (ids !== undefined && ids.length !== 0) {
|
|
83
|
-
let itemId = getId(item);
|
|
84
|
-
let itemLocalId = decodeLocalId(itemId);
|
|
85
|
-
passIds = ids.some(i => {
|
|
86
|
-
if (i === itemId) {
|
|
87
|
-
return true;
|
|
88
|
-
} else {
|
|
89
|
-
return Primitive_object.equal(itemLocalId, i);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
} else {
|
|
93
|
-
passIds = true;
|
|
94
|
-
}
|
|
95
|
-
let passPerField = perFieldChecks.every(check => check(item));
|
|
96
|
-
if (passSearch && passPrefix && passIds) {
|
|
97
|
-
return passPerField;
|
|
98
|
-
} else {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
let orderByDict = Stdlib_Option.flatMap(argsDict["orderBy"], Stdlib_JSON.Decode.object);
|
|
103
|
-
let orderByField = Stdlib_Option.flatMap(Stdlib_Option.flatMap(orderByDict, ob => ob["field"]), Stdlib_JSON.Decode.string);
|
|
104
|
-
let direction = Stdlib_Option.getOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(orderByDict, ob => ob["direction"]), Stdlib_JSON.Decode.string), "ASC");
|
|
105
|
-
let isDesc = direction === "DESC";
|
|
106
|
-
let sorted;
|
|
107
|
-
if (orderByField !== undefined) {
|
|
108
|
-
sorted = filtered.toSorted((a, b) => {
|
|
109
|
-
let av = Stdlib_Option.getOr(getFieldString(a, orderByField), "");
|
|
110
|
-
let bv = Stdlib_Option.getOr(getFieldString(b, orderByField), "");
|
|
111
|
-
let primary = av < bv ? -1 : (
|
|
112
|
-
av > bv ? 1 : 0
|
|
113
|
-
);
|
|
114
|
-
let primary$1 = isDesc ? -primary | 0 : primary;
|
|
115
|
-
if (primary$1 !== 0) {
|
|
116
|
-
return primary$1;
|
|
117
|
-
}
|
|
118
|
-
let aid = getId(a);
|
|
119
|
-
let bid = getId(b);
|
|
120
|
-
if (aid < bid) {
|
|
121
|
-
return -1;
|
|
122
|
-
} else if (aid > bid) {
|
|
123
|
-
return 1;
|
|
124
|
-
} else {
|
|
125
|
-
return 0;
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
} else {
|
|
129
|
-
sorted = filtered.toSorted((a, b) => {
|
|
130
|
-
let aid = getId(a);
|
|
131
|
-
let bid = getId(b);
|
|
132
|
-
if (aid < bid) {
|
|
133
|
-
return -1;
|
|
134
|
-
} else if (aid > bid) {
|
|
135
|
-
return 1;
|
|
136
|
-
} else {
|
|
137
|
-
return 0;
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
let first = Stdlib_Option.map(Stdlib_Option.flatMap(argsDict["first"], Stdlib_JSON.Decode.float), prim => prim | 0);
|
|
142
|
-
let after = Stdlib_Option.flatMap(argsDict["after"], Stdlib_JSON.Decode.string);
|
|
143
|
-
let last = Stdlib_Option.map(Stdlib_Option.flatMap(argsDict["last"], Stdlib_JSON.Decode.float), prim => prim | 0);
|
|
144
|
-
let before = Stdlib_Option.flatMap(argsDict["before"], Stdlib_JSON.Decode.string);
|
|
145
|
-
let isBackward = Stdlib_Option.isSome(last);
|
|
146
|
-
let cursorField = Stdlib_Option.getOr(orderByField, "id");
|
|
147
|
-
let getCursorValue = item => Stdlib_Option.getOr(getFieldString(item, cursorField), getId(item));
|
|
148
|
-
let cursorBounded;
|
|
149
|
-
if (isBackward) {
|
|
150
|
-
if (before !== undefined) {
|
|
151
|
-
let cv = atob(before);
|
|
152
|
-
cursorBounded = sorted.filter(item => {
|
|
153
|
-
let v = getCursorValue(item);
|
|
154
|
-
if (isDesc) {
|
|
155
|
-
return v > cv;
|
|
156
|
-
} else {
|
|
157
|
-
return v < cv;
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
} else {
|
|
161
|
-
cursorBounded = sorted;
|
|
162
|
-
}
|
|
163
|
-
} else if (after !== undefined) {
|
|
164
|
-
let cv$1 = atob(after);
|
|
165
|
-
cursorBounded = sorted.filter(item => {
|
|
166
|
-
let v = getCursorValue(item);
|
|
167
|
-
if (isDesc) {
|
|
168
|
-
return v < cv$1;
|
|
169
|
-
} else {
|
|
170
|
-
return v > cv$1;
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
} else {
|
|
174
|
-
cursorBounded = sorted;
|
|
175
|
-
}
|
|
176
|
-
let pageSize = isBackward ? Stdlib_Option.getOr(last, 50) : Stdlib_Option.getOr(first, 50);
|
|
177
|
-
let take = pageSize + 1 | 0;
|
|
178
|
-
let match;
|
|
179
|
-
if (isBackward) {
|
|
180
|
-
let len = cursorBounded.length;
|
|
181
|
-
let startIdx = len > take ? len - take | 0 : 0;
|
|
182
|
-
let arr = cursorBounded.slice(startIdx, len);
|
|
183
|
-
let hasMore = arr.length > pageSize;
|
|
184
|
-
let result = hasMore ? arr.slice(1, arr.length) : arr;
|
|
185
|
-
match = [
|
|
186
|
-
result,
|
|
187
|
-
hasMore
|
|
188
|
-
];
|
|
189
|
-
} else {
|
|
190
|
-
let arr$1 = cursorBounded.slice(0, take);
|
|
191
|
-
let hasMore$1 = arr$1.length > pageSize;
|
|
192
|
-
match = [
|
|
193
|
-
arr$1.slice(0, pageSize),
|
|
194
|
-
hasMore$1
|
|
195
|
-
];
|
|
196
|
-
}
|
|
197
|
-
let hasMore$2 = match[1];
|
|
198
|
-
return buildConnection(match[0], !isBackward && hasMore$2, isBackward && hasMore$2, getCursorValue);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
let defaultListPageSize = 50;
|
|
202
|
-
|
|
203
|
-
export {
|
|
204
|
-
encodeCursor,
|
|
205
|
-
decodeCursor,
|
|
206
|
-
defaultListPageSize,
|
|
207
|
-
getId,
|
|
208
|
-
getFieldString,
|
|
209
|
-
buildConnection,
|
|
210
|
-
run,
|
|
211
|
-
}
|
|
212
|
-
/* No side effect */
|