orchid-pagination 1.0.0 → 2.0.0
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/README.md +36 -11
- package/dist/index.cjs +76 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -23
- package/dist/index.d.ts +32 -23
- package/dist/index.js +74 -51
- package/dist/index.js.map +1 -1
- package/package.json +20 -13
- package/src/index.ts +4 -3
- package/src/limit.test.ts +57 -0
- package/src/limit.ts +32 -0
- package/src/paginators/cursor/cursor.test.ts +40 -0
- package/src/paginators/cursor/cursor.ts +23 -0
- package/src/paginators/cursor/factory.test.ts +21 -0
- package/src/paginators/cursor/factory.ts +11 -0
- package/src/paginators/cursor/index.ts +2 -0
- package/src/paginators/cursor/order.test.ts +29 -0
- package/src/{cursor/utils.ts → paginators/cursor/order.ts} +7 -4
- package/src/paginators/cursor/paginator.test.ts +224 -0
- package/src/{cursor.ts → paginators/cursor/paginator.ts} +36 -27
- package/src/paginators/page/factory.test.ts +21 -0
- package/src/paginators/page/factory.ts +11 -0
- package/src/paginators/page/index.ts +2 -0
- package/src/paginators/page/paginator.test.ts +76 -0
- package/src/paginators/page/paginator.ts +45 -0
- package/src/types.ts +7 -0
- package/src/base.ts +0 -28
- package/src/page.ts +0 -46
package/README.md
CHANGED
|
@@ -18,8 +18,8 @@ import { paginateByPage } from "orchid-pagination"
|
|
|
18
18
|
|
|
19
19
|
defineEventHandler(async (ctx) => {
|
|
20
20
|
const query = db.user.where(conditions).order({ name: "ASC", id: "DESC" })
|
|
21
|
-
const params = getValidatedParams(ctx) // prepare object with { page?,
|
|
22
|
-
const page = await paginateByPage(query, {
|
|
21
|
+
const params = getValidatedParams(ctx) // prepare object with { page?, limit? }
|
|
22
|
+
const page = await paginateByPage(query, { limit: 10, maxLimit: 1000 }, params)
|
|
23
23
|
return page
|
|
24
24
|
})
|
|
25
25
|
```
|
|
@@ -29,17 +29,17 @@ Alternatively, pre-create the paginator:
|
|
|
29
29
|
```ts
|
|
30
30
|
import { createPagePaginator } from "orchid-pagination"
|
|
31
31
|
|
|
32
|
-
const paginate = createPagePaginator({
|
|
32
|
+
const paginate = createPagePaginator({ limit: 10, maxLimit: 1000 })
|
|
33
33
|
|
|
34
34
|
defineEventHandler(async (ctx) => {
|
|
35
35
|
const query = db.user.where(conditions).order({ name: "ASC", id: "DESC" })
|
|
36
|
-
const params = getValidatedParams(ctx) // prepare object with { page?,
|
|
36
|
+
const params = getValidatedParams(ctx) // prepare object with { page?, limit? }
|
|
37
37
|
const page = await paginate(query, params)
|
|
38
38
|
return page
|
|
39
39
|
})
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
The page
|
|
42
|
+
The page has `{ items, page, limit, offset, prevPage?, nextPage? }`.
|
|
43
43
|
|
|
44
44
|
## Cursor pagination
|
|
45
45
|
|
|
@@ -48,8 +48,8 @@ import { paginateByCursor } from "orchid-pagination"
|
|
|
48
48
|
|
|
49
49
|
defineEventHandler(async (ctx) => {
|
|
50
50
|
const query = db.user.where(conditions).order({ name: "ASC", id: "DESC" })
|
|
51
|
-
const params = getValidatedParams(ctx) // prepare object with { cursor?,
|
|
52
|
-
const page = await paginateByCursor(query, {
|
|
51
|
+
const params = getValidatedParams(ctx) // prepare object with { cursor?, limit? }
|
|
52
|
+
const page = await paginateByCursor(query, { limit: 10, maxLimit: 1000 }, params)
|
|
53
53
|
return page
|
|
54
54
|
})
|
|
55
55
|
```
|
|
@@ -57,16 +57,41 @@ defineEventHandler(async (ctx) => {
|
|
|
57
57
|
Alternatively, pre-create the paginator:
|
|
58
58
|
|
|
59
59
|
```ts
|
|
60
|
-
import {
|
|
60
|
+
import { createCursorPaginator } from "orchid-pagination"
|
|
61
61
|
|
|
62
|
-
const paginate =
|
|
62
|
+
const paginate = createCursorPaginator({ limit: 10, maxLimit: 1000 })
|
|
63
63
|
|
|
64
64
|
defineEventHandler(async (ctx) => {
|
|
65
65
|
const query = db.user.where(conditions).order({ name: "ASC", id: "DESC" })
|
|
66
|
-
const params = getValidatedParams(ctx) // prepare object with { cursor?,
|
|
66
|
+
const params = getValidatedParams(ctx) // prepare object with { cursor?, limit? }
|
|
67
67
|
const page = await paginate(query, params)
|
|
68
68
|
return page
|
|
69
69
|
})
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
The page
|
|
72
|
+
The page has `{ items, limit, prevCursor?, nextCursor? }`.
|
|
73
|
+
|
|
74
|
+
Cursor queries must be ordered.
|
|
75
|
+
Include a deterministic tie-breaker, usually `id`.
|
|
76
|
+
Treat cursors as opaque strings and pass them back unchanged.
|
|
77
|
+
|
|
78
|
+
You can order by selected relation fields and aliases as long as the ordered value is present in the query result:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
const page = await paginateByCursor(
|
|
82
|
+
db.post
|
|
83
|
+
.select("id", "text", {
|
|
84
|
+
authorName: q => q.author.get("name"),
|
|
85
|
+
})
|
|
86
|
+
.order("authorName", { id: "DESC" }),
|
|
87
|
+
{ limit: 10 },
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Pagination config
|
|
92
|
+
|
|
93
|
+
- `limit`: default limit.
|
|
94
|
+
- `maxLimit`: max accepted client `limit`.
|
|
95
|
+
- Client `limit` is ignored unless `maxLimit` is set.
|
|
96
|
+
- With only `maxLimit`, client `limit` is required.
|
|
97
|
+
- Without config, query `.limit(...)` is required.
|
package/dist/index.cjs
CHANGED
|
@@ -22,27 +22,47 @@ var src_exports = {};
|
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
createCursorPaginator: () => createCursorPaginator,
|
|
24
24
|
createPagePaginator: () => createPagePaginator,
|
|
25
|
-
|
|
25
|
+
getLimit: () => getLimit,
|
|
26
26
|
paginateByCursor: () => paginateByCursor,
|
|
27
27
|
paginateByPage: () => paginateByPage
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(src_exports);
|
|
30
30
|
|
|
31
|
-
// src/
|
|
32
|
-
function
|
|
31
|
+
// src/limit.ts
|
|
32
|
+
function getLimit(query, config, params) {
|
|
33
|
+
if (config) {
|
|
34
|
+
const limit = config.maxLimit !== void 0 ? params?.limit ?? config.limit : config.limit;
|
|
35
|
+
if (limit === void 0) {
|
|
36
|
+
throw new Error("Set config.limit or params.limit with config.maxLimit.");
|
|
37
|
+
}
|
|
38
|
+
return Math.max(1, Math.min(limit, config.maxLimit ?? limit));
|
|
39
|
+
}
|
|
33
40
|
const queryLimit = query.q.limit;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
throw new Error("Set query limit, config.maxPageSize, config.pageSize or params.limit.");
|
|
41
|
+
if (!queryLimit) {
|
|
42
|
+
throw new Error("Set query limit or config.limit.");
|
|
37
43
|
}
|
|
38
|
-
return Math.max(1,
|
|
44
|
+
return Math.max(1, queryLimit);
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
// src/cursor.ts
|
|
42
|
-
var import_orchid_orm = require("orchid-orm");
|
|
43
|
-
|
|
44
|
-
// src/cursor/utils.ts
|
|
47
|
+
// src/paginators/cursor/cursor.ts
|
|
45
48
|
var import_node_buffer = require("buffer");
|
|
49
|
+
function createCursor(parts) {
|
|
50
|
+
return import_node_buffer.Buffer.from(parts.map(String).join(String.fromCharCode(0))).toString("base64url");
|
|
51
|
+
}
|
|
52
|
+
function parseCursor(cursor) {
|
|
53
|
+
return import_node_buffer.Buffer.from(cursor, "base64url").toString().split(String.fromCharCode(0));
|
|
54
|
+
}
|
|
55
|
+
function createDirectedCursor(parts, reverse) {
|
|
56
|
+
const cursor = createCursor(parts);
|
|
57
|
+
return reverse ? "-" + cursor : cursor;
|
|
58
|
+
}
|
|
59
|
+
function parseDirectedCursor(directedCursor) {
|
|
60
|
+
const [cursor, reverse] = directedCursor.startsWith("-") ? [directedCursor.slice(1), true] : [directedCursor, false];
|
|
61
|
+
return { cursor, parts: parseCursor(cursor), reverse };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/paginators/cursor/order.ts
|
|
65
|
+
var import_node_buffer2 = require("buffer");
|
|
46
66
|
function getQueryOrderFields(query) {
|
|
47
67
|
const orderFields = query.q.order?.flatMap((orderItem) => {
|
|
48
68
|
if (typeof orderItem === "string") {
|
|
@@ -64,29 +84,10 @@ function getQueryOrderFields(query) {
|
|
|
64
84
|
}
|
|
65
85
|
return orderFields;
|
|
66
86
|
}
|
|
67
|
-
function createCursor(parts) {
|
|
68
|
-
return import_node_buffer.Buffer.from(parts.map(String).join(String.fromCharCode(0))).toString("base64url");
|
|
69
|
-
}
|
|
70
|
-
function parseCursor(cursor) {
|
|
71
|
-
return import_node_buffer.Buffer.from(cursor, "base64url").toString().split(String.fromCharCode(0));
|
|
72
|
-
}
|
|
73
|
-
function createDirectedCursor(parts, reverse) {
|
|
74
|
-
const cursor = createCursor(parts);
|
|
75
|
-
return reverse ? "-" + cursor : cursor;
|
|
76
|
-
}
|
|
77
|
-
function parseDirectedCursor(directedCursor) {
|
|
78
|
-
const [cursor, reverse] = directedCursor.startsWith("-") ? [directedCursor.slice(1), true] : [directedCursor, false];
|
|
79
|
-
return { cursor, parts: parseCursor(cursor), reverse };
|
|
80
|
-
}
|
|
81
87
|
|
|
82
|
-
// src/cursor.ts
|
|
83
|
-
async function createCursorPaginator(config) {
|
|
84
|
-
return function paginate(query, params) {
|
|
85
|
-
return paginateByCursor(query, config, params);
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
+
// src/paginators/cursor/paginator.ts
|
|
88
89
|
async function paginateByCursor(query, config, params) {
|
|
89
|
-
const
|
|
90
|
+
const limit = getLimit(query, config, params);
|
|
90
91
|
const orderFields = getQueryOrderFields(query);
|
|
91
92
|
const parsedCursorMaybeValid = params?.cursor ? parseDirectedCursor(params.cursor) : void 0;
|
|
92
93
|
const parsedCursor = parsedCursorMaybeValid && parsedCursorMaybeValid.parts.length >= orderFields.length ? parsedCursorMaybeValid : void 0;
|
|
@@ -99,57 +100,80 @@ async function paginateByCursor(query, config, params) {
|
|
|
99
100
|
query = query.clear("order").order(orderArg);
|
|
100
101
|
}
|
|
101
102
|
if (parsedCursor) {
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
|
|
103
|
+
const leftRawSql = orderFields.map(([field, asc], i) => asc ? query.ref(field).toSQL() : `$value${i}`).join(",");
|
|
104
|
+
const rightRawSql = orderFields.map(([field, asc], i) => !asc ? query.ref(field).toSQL() : `$value${i}`).join(",");
|
|
105
|
+
const rawSql = `(${leftRawSql}) > (${rightRawSql})`;
|
|
106
|
+
const rawSqlValues = Object.fromEntries(
|
|
107
|
+
orderFields.map((_field, i) => [`value${i}`, parsedCursor.parts[i]])
|
|
108
|
+
);
|
|
109
|
+
const sqlExpr = query.qb.sql({ raw: rawSql, values: rawSqlValues });
|
|
110
|
+
query = query.where(sqlExpr);
|
|
107
111
|
}
|
|
108
|
-
const items = await query.limit(
|
|
112
|
+
const items = await query.limit(limit + 1);
|
|
109
113
|
if (!Array.isArray(items)) {
|
|
110
114
|
throw new TypeError("Query must return an array.");
|
|
111
115
|
}
|
|
112
|
-
const hasContinuation = items.length >
|
|
116
|
+
const hasContinuation = items.length > limit;
|
|
113
117
|
if (hasContinuation) {
|
|
114
|
-
items.splice(
|
|
118
|
+
items.splice(limit);
|
|
115
119
|
}
|
|
116
120
|
if (reverse) {
|
|
117
121
|
items.reverse();
|
|
118
122
|
}
|
|
119
123
|
function createItemCursor(item, reverse2) {
|
|
120
124
|
return createDirectedCursor(orderFields.map(([field]) => {
|
|
121
|
-
return String(item
|
|
125
|
+
return String(getItemValue(item, field));
|
|
122
126
|
}), reverse2);
|
|
123
127
|
}
|
|
124
128
|
const prevCursor = parsedCursor && (parsedCursor.reverse === false || hasContinuation) ? createItemCursor(items[0], true) : void 0;
|
|
125
129
|
const nextCursor = parsedCursor?.reverse === true || hasContinuation ? createItemCursor(items.at(-1), false) : void 0;
|
|
126
|
-
return { items,
|
|
130
|
+
return { items, limit, prevCursor, nextCursor };
|
|
131
|
+
}
|
|
132
|
+
function getItemValue(item, field) {
|
|
133
|
+
if (!field.includes(".")) {
|
|
134
|
+
return item[field];
|
|
135
|
+
}
|
|
136
|
+
return field.split(".").reduce((obj, key) => {
|
|
137
|
+
return obj == null ? void 0 : obj[key];
|
|
138
|
+
}, item);
|
|
127
139
|
}
|
|
128
140
|
|
|
129
|
-
// src/
|
|
130
|
-
function
|
|
141
|
+
// src/paginators/cursor/factory.ts
|
|
142
|
+
function createCursorPaginator(config) {
|
|
131
143
|
return function paginate(query, params) {
|
|
132
|
-
return
|
|
144
|
+
return paginateByCursor(query, config, params);
|
|
133
145
|
};
|
|
134
146
|
}
|
|
147
|
+
|
|
148
|
+
// src/paginators/page/paginator.ts
|
|
135
149
|
async function paginateByPage(query, config, params) {
|
|
136
|
-
const
|
|
150
|
+
const limit = getLimit(query, config, params);
|
|
137
151
|
const page = Math.max(1, params?.page ?? 1);
|
|
138
|
-
const offset = (page - 1) *
|
|
139
|
-
const items = await query.offset(offset).limit(
|
|
140
|
-
|
|
152
|
+
const offset = (page - 1) * limit;
|
|
153
|
+
const items = await query.offset(offset).limit(limit + 1);
|
|
154
|
+
if (!Array.isArray(items)) {
|
|
155
|
+
throw new TypeError("Query must return an array.");
|
|
156
|
+
}
|
|
157
|
+
const hasContinuation = items.length > limit;
|
|
141
158
|
if (hasContinuation) {
|
|
142
|
-
items.splice(
|
|
159
|
+
items.splice(limit);
|
|
143
160
|
}
|
|
144
161
|
const prevPage = page > 1 ? page - 1 : void 0;
|
|
145
162
|
const nextPage = hasContinuation ? page + 1 : void 0;
|
|
146
|
-
return { items, page,
|
|
163
|
+
return { items, page, limit, offset, prevPage, nextPage };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/paginators/page/factory.ts
|
|
167
|
+
function createPagePaginator(config) {
|
|
168
|
+
return function paginate(query, params) {
|
|
169
|
+
return paginateByPage(query, config, params);
|
|
170
|
+
};
|
|
147
171
|
}
|
|
148
172
|
// Annotate the CommonJS export names for ESM import in node:
|
|
149
173
|
0 && (module.exports = {
|
|
150
174
|
createCursorPaginator,
|
|
151
175
|
createPagePaginator,
|
|
152
|
-
|
|
176
|
+
getLimit,
|
|
153
177
|
paginateByCursor,
|
|
154
178
|
paginateByPage
|
|
155
179
|
});
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/base.ts","../src/cursor.ts","../src/cursor/utils.ts","../src/page.ts"],"sourcesContent":["export * from \"./base\"\nexport * from \"./cursor\"\nexport * from \"./page\"\n","import type { Query, QueryThen, SelectQueryData } from \"orchid-orm\"\n\nexport interface ListQuery extends Query {\n then: QueryThen<unknown[]>\n}\n\nexport interface PaginationConfig {\n /** Default page size. If not set, `maxPageSize` is used. */\n pageSize?: number\n /** Max page size allowed to be passed in params. If not set, `pageSize` is used. */\n maxPageSize?: number\n}\n\nexport interface PaginationParams {\n /** Requested page size. */\n size?: number\n}\n\nexport function getPageSize(query: ListQuery, config?: PaginationConfig, params?: PaginationParams): number {\n const queryLimit = (query.q as SelectQueryData).limit\n\n const maxPageSize = config?.maxPageSize ?? config?.pageSize ?? queryLimit\n if (!maxPageSize) {\n throw new Error(\"Set query limit, config.maxPageSize, config.pageSize or params.limit.\")\n }\n\n return Math.max(1, Math.min(params?.size ?? config?.pageSize ?? maxPageSize, maxPageSize))\n}\n","import type { SortDir } from \"orchid-orm\"\nimport { raw } from \"orchid-orm\"\n\nimport type { ListQuery, PaginationConfig } from \"./base\"\nimport { getPageSize } from \"./base\"\nimport { createDirectedCursor, getQueryOrderFields, parseDirectedCursor } from \"./cursor/utils\"\n\nexport interface CursorPaginationParams {\n /** Page cursor, as returned by previous call in prevCursor / nextCursor. */\n cursor?: string\n /** Page size. */\n size?: number\n}\n\nexport type CursorPaginationPage<T extends ListQuery = ListQuery> = {\n items: Awaited<T>\n /** Effective page size. Number of items is guaranteed to be less or equal. */\n size: number\n /** Cursor pointing to previous page. */\n prevCursor?: string\n /** Cursor pointing to next page. */\n nextCursor?: string\n}\n\nexport async function createCursorPaginator(config?: PaginationConfig) {\n return function paginate<T extends ListQuery>(query: T, params?: CursorPaginationParams) {\n return paginateByCursor(query, config, params)\n }\n}\n\nexport async function paginateByCursor<T extends ListQuery>(query: T, config?: PaginationConfig, params?: CursorPaginationParams): Promise<CursorPaginationPage<T>> {\n const size = getPageSize(query, config, params)\n\n const orderFields = getQueryOrderFields(query)\n\n // poor man validation, TODO improve\n const parsedCursorMaybeValid = params?.cursor ? parseDirectedCursor(params.cursor) : undefined\n const parsedCursor = parsedCursorMaybeValid && parsedCursorMaybeValid.parts.length >= orderFields.length ? parsedCursorMaybeValid : undefined\n\n const reverse = parsedCursor?.reverse ?? false\n\n if (reverse) {\n // Reverse parsed order fields + reverse query ordering.\n orderFields.forEach((of) => {\n of[1] = !of[1]\n })\n const orderArg = Object.fromEntries(orderFields.map<[string, SortDir]>(([field, asc]) => [field, asc ? \"ASC\" : \"DESC\"]))\n query = query.clear(\"order\").order(orderArg as any)\n }\n\n if (parsedCursor) {\n // Prepare raw SQL.\n // For example, for (amount asc, id asc) order, that would be:\n // (amount, $id) >= ($amount, id)\n const leftSqlExpr = orderFields.map(([field, asc]) => asc ? field : `$${field}`).join(\",\")\n const rightSqlExp = orderFields.map(([field, asc]) => asc ? `$${field}` : field).join(\",\")\n const sqlExpr = `(${leftSqlExpr}) > (${rightSqlExp})`\n const values = Object.fromEntries(orderFields.map(([field], i) => [field, parsedCursor.parts[i]]))\n query = query.where(raw({ raw: sqlExpr, values }))\n }\n\n // Query 1 extra item to see if we can paginate farther in current direction.\n const items = await query.limit(size + 1)\n if (!Array.isArray(items)) {\n throw new TypeError(\"Query must return an array.\")\n }\n const hasContinuation = items.length > size\n if (hasContinuation) {\n items.splice(size)\n }\n if (reverse) {\n items.reverse()\n }\n\n function createItemCursor(item: any, reverse: boolean) {\n return createDirectedCursor(orderFields.map(([field]) => {\n // Can add custom serializer here if needed.\n return String(item[field])\n }), reverse)\n }\n\n // Prev cursor:\n // - for initial pagination, there is no prev page\n // - for forward pagination, prev page exists always\n // - for reverse pagination, prev page exists if we have a continuation\n const prevCursor = (parsedCursor && (parsedCursor.reverse === false || hasContinuation))\n ? createItemCursor(items[0], true)\n : undefined\n\n // Next cursor:\n // - for reverse pagination, next page exists always\n // - for initial or forward pagination, next page exists if we have a continuation\n const nextCursor = (parsedCursor?.reverse === true || hasContinuation)\n ? createItemCursor(items.at(-1), false)\n : undefined\n\n return { items, size, prevCursor, nextCursor }\n}\n","import { Buffer } from \"node:buffer\"\n\nimport type { SelectQueryData } from \"orchid-orm\"\n\nimport type { ListQuery } from \"../base\"\n\ntype OrderField = [field: string, asc: boolean]\n\nexport function getQueryOrderFields(query: ListQuery): OrderField[] {\n const orderFields = (query.q as SelectQueryData).order?.flatMap<[field: string, asc: boolean]>((orderItem) => {\n if (typeof orderItem === \"string\") {\n return [[orderItem, true]]\n } else if (typeof orderItem === \"object\") {\n return Object.entries(orderItem).map<[string, boolean]>(([field, order]) => {\n if (order === \"ASC\" || order === \"DESC\") {\n return [field, order === \"ASC\"]\n } else {\n throw new Error(\"Unsupported order: \" + order)\n }\n })\n } else {\n throw new TypeError(\"Unsupported order type: \" + orderItem)\n }\n })\n if (!orderFields?.length) {\n throw new Error(\"Query must be ordered.\")\n }\n return orderFields\n}\n\nexport function createCursor(parts: string[]) {\n return Buffer.from(parts.map(String).join(String.fromCharCode(0))).toString(\"base64url\")\n}\n\nexport function parseCursor(cursor: string) {\n return Buffer.from(cursor, \"base64url\").toString().split(String.fromCharCode(0))\n}\n\nexport function createDirectedCursor(parts: string[], reverse: boolean) {\n const cursor = createCursor(parts)\n return reverse ? \"-\" + cursor : cursor\n}\n\nexport function parseDirectedCursor(directedCursor: string) {\n const [cursor, reverse] = directedCursor.startsWith(\"-\") ? [directedCursor.slice(1), true] : [directedCursor, false]\n return { cursor, parts: parseCursor(cursor), reverse }\n}\n","import { getPageSize, type ListQuery, type PaginationConfig } from \"./base\"\n\nexport interface PagePaginationParams {\n /** Page, 1-based. */\n page?: number\n /** Page size. */\n size?: number\n}\n\nexport type PagePaginationPage<T extends ListQuery = ListQuery> = {\n items: Awaited<T>\n /** Effective page number. */\n page: number\n /** Effective page size. Number of items is guaranteed to be less or equal. */\n size: number\n /** Offset of the first item, 1-based. */\n offset: number\n /** Prev page numberm (if exists). */\n prevPage?: number\n /** Next page number (if exists). */\n nextPage?: number\n}\n\nexport function createPagePaginator(config?: PaginationConfig) {\n return function paginate<T extends ListQuery>(query: T, params?: PagePaginationParams) {\n return paginateByPage(query, config, params)\n }\n}\n\nexport async function paginateByPage<T extends ListQuery>(query: T, config?: PaginationConfig, params?: PagePaginationParams): Promise<PagePaginationPage<T>> {\n const size = getPageSize(query, config, params)\n\n const page = Math.max(1, params?.page ?? 1)\n const offset = (page - 1) * size\n\n const items = await query.offset(offset).limit(size + 1)\n const hasContinuation = items.length > size\n if (hasContinuation) {\n items.splice(size)\n }\n\n const prevPage = page > 1 ? page - 1 : undefined\n const nextPage = hasContinuation ? page + 1 : undefined\n\n return { items, page, size, offset, prevPage, nextPage }\n}\n"],"mappings":"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","names":["reverse"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/limit.ts","../src/paginators/cursor/cursor.ts","../src/paginators/cursor/order.ts","../src/paginators/cursor/paginator.ts","../src/paginators/cursor/factory.ts","../src/paginators/page/paginator.ts","../src/paginators/page/factory.ts"],"sourcesContent":["export * from \"./limit\"\nexport * from \"./paginators/cursor\"\nexport * from \"./paginators/page\"\nexport * from \"./types\"\n","import type { ListQuery } from \"./types\"\n\nexport interface PaginationConfig {\n /** Default limit. If `maxLimit` is not set, client params limit is ignored. */\n limit?: number\n /** Max limit allowed to be passed in params. */\n maxLimit?: number\n}\n\nexport interface PaginationParams {\n /** Requested limit. */\n limit?: number\n}\n\n/** getLimit returns the effective limit for a query and pagination parameters. */\nexport function getLimit(query: ListQuery, config?: PaginationConfig, params?: PaginationParams): number {\n if (config) {\n const limit = config.maxLimit !== undefined ? (params?.limit ?? config.limit) : config.limit\n if (limit === undefined) {\n throw new Error(\"Set config.limit or params.limit with config.maxLimit.\")\n }\n\n return Math.max(1, Math.min(limit, config.maxLimit ?? limit))\n }\n\n const queryLimit = query.q.limit\n if (!queryLimit) {\n throw new Error(\"Set query limit or config.limit.\")\n }\n\n return Math.max(1, queryLimit)\n}\n","import { Buffer } from \"node:buffer\"\n\n/** createCursor encodes cursor parts into an opaque cursor string. */\nexport function createCursor(parts: string[]) {\n return Buffer.from(parts.map(String).join(String.fromCharCode(0))).toString(\"base64url\")\n}\n\n/** parseCursor decodes an opaque cursor string into cursor parts. */\nexport function parseCursor(cursor: string) {\n return Buffer.from(cursor, \"base64url\").toString().split(String.fromCharCode(0))\n}\n\n/** createDirectedCursor encodes cursor parts and pagination direction into an opaque cursor string. */\nexport function createDirectedCursor(parts: string[], reverse: boolean) {\n const cursor = createCursor(parts)\n return reverse ? \"-\" + cursor : cursor\n}\n\n/** parseDirectedCursor decodes an opaque directed cursor string into cursor parts and pagination direction. */\nexport function parseDirectedCursor(directedCursor: string) {\n const [cursor, reverse] = directedCursor.startsWith(\"-\") ? [directedCursor.slice(1), true] : [directedCursor, false]\n return { cursor, parts: parseCursor(cursor), reverse }\n}\n","import { Buffer } from \"node:buffer\"\n\nimport type { ListQuery } from \"../../types\"\n\ntype OrderField = [field: string, asc: boolean]\n\n/** getQueryOrderFields returns ordered query fields as field and ascending-direction pairs. */\nexport function getQueryOrderFields(query: ListQuery): OrderField[] {\n const orderFields = query.q.order?.flatMap<[field: string, asc: boolean]>((orderItem) => {\n if (typeof orderItem === \"string\") {\n return [[orderItem, true]]\n } else if (typeof orderItem === \"object\") {\n return Object.entries(orderItem).map<[string, boolean]>(([field, order]) => {\n if (order === \"ASC\" || order === \"DESC\") {\n return [field, order === \"ASC\"]\n } else {\n throw new Error(\"Unsupported order: \" + order)\n }\n })\n } else {\n throw new TypeError(\"Unsupported order type: \" + orderItem)\n }\n })\n if (!orderFields?.length) {\n throw new Error(\"Query must be ordered.\")\n }\n return orderFields\n}\n\n/** createCursor encodes cursor parts into an opaque cursor string. */\nexport function createCursor(parts: string[]) {\n return Buffer.from(parts.map(String).join(String.fromCharCode(0))).toString(\"base64url\")\n}\n\n/** parseCursor decodes an opaque cursor string into cursor parts. */\nexport function parseCursor(cursor: string) {\n return Buffer.from(cursor, \"base64url\").toString().split(String.fromCharCode(0))\n}\n\n/** createDirectedCursor encodes cursor parts and pagination direction into an opaque cursor string. */\nexport function createDirectedCursor(parts: string[], reverse: boolean) {\n const cursor = createCursor(parts)\n return reverse ? \"-\" + cursor : cursor\n}\n\n/** parseDirectedCursor decodes an opaque directed cursor string into cursor parts and pagination direction. */\nexport function parseDirectedCursor(directedCursor: string) {\n const [cursor, reverse] = directedCursor.startsWith(\"-\") ? [directedCursor.slice(1), true] : [directedCursor, false]\n return { cursor, parts: parseCursor(cursor), reverse }\n}\n","import { getLimit, type PaginationConfig } from \"../../limit\"\nimport type { ListQuery, SortDir } from \"../../types\"\n\nimport { createDirectedCursor, parseDirectedCursor } from \"./cursor\"\nimport { getQueryOrderFields } from \"./order\"\n\nexport interface CursorPaginationParams {\n /** Page cursor, as returned by previous call in prevCursor / nextCursor. */\n cursor?: string\n /** Limit. */\n limit?: number\n}\n\nexport type CursorPaginationPage<T extends ListQuery = ListQuery> = {\n items: Awaited<T>\n /** Effective limit. Number of items is guaranteed to be less or equal. */\n limit: number\n /** Cursor pointing to previous page. */\n prevCursor?: string\n /** Cursor pointing to next page. */\n nextCursor?: string\n}\n\n/** paginateByCursor returns one page of results using cursor-based pagination. */\nexport async function paginateByCursor<T extends ListQuery>(query: T, config?: PaginationConfig, params?: CursorPaginationParams): Promise<CursorPaginationPage<T>> {\n const limit = getLimit(query, config, params)\n\n const orderFields = getQueryOrderFields(query)\n\n // poor man validation, TODO improve\n const parsedCursorMaybeValid = params?.cursor ? parseDirectedCursor(params.cursor) : undefined\n const parsedCursor = parsedCursorMaybeValid && parsedCursorMaybeValid.parts.length >= orderFields.length ? parsedCursorMaybeValid : undefined\n\n const reverse = parsedCursor?.reverse ?? false\n\n if (reverse) {\n // Reverse parsed order fields + reverse query ordering.\n orderFields.forEach((of) => {\n of[1] = !of[1]\n })\n const orderArg = Object.fromEntries(orderFields.map<[string, SortDir]>(([field, asc]) => [field, asc ? \"ASC\" : \"DESC\"]))\n query = query.clear(\"order\").order(orderArg as any)\n }\n\n if (parsedCursor) {\n // Prepare raw SQL.\n // For example, for (amount ASC, id DESC) ordering, that would be:\n // (amount, $id) >= ($amount, id)\n const leftRawSql = orderFields.map(([field, asc], i) => asc ? query.ref(field).toSQL() : `$value${i}`).join(\",\")\n const rightRawSql = orderFields.map(([field, asc], i) => !asc ? query.ref(field).toSQL() : `$value${i}`).join(\",\")\n const rawSql = `(${leftRawSql}) > (${rightRawSql})`\n const rawSqlValues = Object.fromEntries(\n orderFields.map((_field, i) => [`value${i}`, parsedCursor.parts[i]]),\n )\n const sqlExpr = query.qb.sql({ raw: rawSql, values: rawSqlValues })\n // query.where doesn't like low-level RawSql objects, cast to any to silence\n query = query.where(sqlExpr as any)\n }\n\n // Query 1 extra item to see if we can paginate farther in current direction.\n const items = await (query as ListQuery).limit(limit + 1) as Awaited<T>\n if (!Array.isArray(items)) {\n throw new TypeError(\"Query must return an array.\")\n }\n const hasContinuation = items.length > limit\n if (hasContinuation) {\n items.splice(limit)\n }\n if (reverse) {\n items.reverse()\n }\n\n function createItemCursor(item: any, reverse: boolean) {\n return createDirectedCursor(orderFields.map(([field]) => {\n // Can add custom serializer here if needed.\n return String(getItemValue(item, field))\n }), reverse)\n }\n\n // Prev cursor:\n // - for initial pagination, there is no prev page\n // - for forward pagination, prev page exists always\n // - for reverse pagination, prev page exists if we have a continuation\n const prevCursor = (parsedCursor && (parsedCursor.reverse === false || hasContinuation))\n ? createItemCursor(items[0], true)\n : undefined\n\n // Next cursor:\n // - for reverse pagination, next page exists always\n // - for initial or forward pagination, next page exists if we have a continuation\n const nextCursor = (parsedCursor?.reverse === true || hasContinuation)\n ? createItemCursor(items.at(-1), false)\n : undefined\n\n return { items, limit, prevCursor, nextCursor }\n}\n\n/** getItemValue returns an item's field value, resolving dot-notation paths against nested objects. */\nfunction getItemValue(item: unknown, field: string): unknown {\n if (!field.includes(\".\")) {\n return (item as Record<string, unknown>)[field]\n }\n\n return field.split(\".\").reduce<unknown>((obj, key) => {\n return obj == null ? undefined : (obj as Record<string, unknown>)[key]\n }, item)\n}\n","import type { PaginationConfig } from \"../../limit\"\nimport type { ListQuery } from \"../../types\"\n\nimport { type CursorPaginationParams, paginateByCursor } from \"./paginator\"\n\n/** createCursorPaginator creates a reusable cursor paginator with the given config. */\nexport function createCursorPaginator(config?: PaginationConfig) {\n return function paginate<T extends ListQuery>(query: T, params?: CursorPaginationParams) {\n return paginateByCursor(query, config, params)\n }\n}\n","import { getLimit, type PaginationConfig } from \"../../limit\"\nimport type { ListQuery } from \"../../types\"\n\nexport interface PagePaginationParams {\n /** Page, 1-based. */\n page?: number\n /** Limit. */\n limit?: number\n}\n\nexport type PagePaginationPage<T extends ListQuery = ListQuery> = {\n items: Awaited<T>\n /** Effective page number. */\n page: number\n /** Effective limit. Number of items is guaranteed to be less or equal. */\n limit: number\n /** Offset of the first item, 1-based. */\n offset: number\n /** Prev page number (if exists). */\n prevPage?: number\n /** Next page number (if exists). */\n nextPage?: number\n}\n\n/** paginateByPage returns one page of results using offset-based pagination. */\nexport async function paginateByPage<T extends ListQuery>(query: T, config?: PaginationConfig, params?: PagePaginationParams): Promise<PagePaginationPage<T>> {\n const limit = getLimit(query, config, params)\n\n const page = Math.max(1, params?.page ?? 1)\n const offset = (page - 1) * limit\n\n const items = await (query as ListQuery).offset(offset).limit(limit + 1) as Awaited<T>\n if (!Array.isArray(items)) {\n throw new TypeError(\"Query must return an array.\")\n }\n const hasContinuation = items.length > limit\n if (hasContinuation) {\n items.splice(limit)\n }\n\n const prevPage = page > 1 ? page - 1 : undefined\n const nextPage = hasContinuation ? page + 1 : undefined\n\n return { items, page, limit, offset, prevPage, nextPage }\n}\n","import type { PaginationConfig } from \"../../limit\"\nimport type { ListQuery } from \"../../types\"\n\nimport { type PagePaginationParams, paginateByPage } from \"./paginator\"\n\n/** createPagePaginator creates a reusable page paginator with the given config. */\nexport function createPagePaginator(config?: PaginationConfig) {\n return function paginate<T extends ListQuery>(query: T, params?: PagePaginationParams) {\n return paginateByPage(query, config, params)\n }\n}\n"],"mappings":"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","names":["import_node_buffer","reverse"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,58 +1,67 @@
|
|
|
1
|
-
import { Query
|
|
1
|
+
import { Query } from 'orchid-orm';
|
|
2
|
+
|
|
3
|
+
type ListQuery = Query & {
|
|
4
|
+
returnType: undefined | "all";
|
|
5
|
+
};
|
|
6
|
+
type SortDir = "ASC" | "DESC";
|
|
2
7
|
|
|
3
|
-
interface ListQuery extends Query {
|
|
4
|
-
then: QueryThen<unknown[]>;
|
|
5
|
-
}
|
|
6
8
|
interface PaginationConfig {
|
|
7
|
-
/** Default
|
|
8
|
-
|
|
9
|
-
/** Max
|
|
10
|
-
|
|
9
|
+
/** Default limit. If `maxLimit` is not set, client params limit is ignored. */
|
|
10
|
+
limit?: number;
|
|
11
|
+
/** Max limit allowed to be passed in params. */
|
|
12
|
+
maxLimit?: number;
|
|
11
13
|
}
|
|
12
14
|
interface PaginationParams {
|
|
13
|
-
/** Requested
|
|
14
|
-
|
|
15
|
+
/** Requested limit. */
|
|
16
|
+
limit?: number;
|
|
15
17
|
}
|
|
16
|
-
|
|
18
|
+
/** getLimit returns the effective limit for a query and pagination parameters. */
|
|
19
|
+
declare function getLimit(query: ListQuery, config?: PaginationConfig, params?: PaginationParams): number;
|
|
17
20
|
|
|
18
21
|
interface CursorPaginationParams {
|
|
19
22
|
/** Page cursor, as returned by previous call in prevCursor / nextCursor. */
|
|
20
23
|
cursor?: string;
|
|
21
|
-
/**
|
|
22
|
-
|
|
24
|
+
/** Limit. */
|
|
25
|
+
limit?: number;
|
|
23
26
|
}
|
|
24
27
|
type CursorPaginationPage<T extends ListQuery = ListQuery> = {
|
|
25
28
|
items: Awaited<T>;
|
|
26
|
-
/** Effective
|
|
27
|
-
|
|
29
|
+
/** Effective limit. Number of items is guaranteed to be less or equal. */
|
|
30
|
+
limit: number;
|
|
28
31
|
/** Cursor pointing to previous page. */
|
|
29
32
|
prevCursor?: string;
|
|
30
33
|
/** Cursor pointing to next page. */
|
|
31
34
|
nextCursor?: string;
|
|
32
35
|
};
|
|
33
|
-
|
|
36
|
+
/** paginateByCursor returns one page of results using cursor-based pagination. */
|
|
34
37
|
declare function paginateByCursor<T extends ListQuery>(query: T, config?: PaginationConfig, params?: CursorPaginationParams): Promise<CursorPaginationPage<T>>;
|
|
35
38
|
|
|
39
|
+
/** createCursorPaginator creates a reusable cursor paginator with the given config. */
|
|
40
|
+
declare function createCursorPaginator(config?: PaginationConfig): <T extends ListQuery>(query: T, params?: CursorPaginationParams) => Promise<CursorPaginationPage<T>>;
|
|
41
|
+
|
|
36
42
|
interface PagePaginationParams {
|
|
37
43
|
/** Page, 1-based. */
|
|
38
44
|
page?: number;
|
|
39
|
-
/**
|
|
40
|
-
|
|
45
|
+
/** Limit. */
|
|
46
|
+
limit?: number;
|
|
41
47
|
}
|
|
42
48
|
type PagePaginationPage<T extends ListQuery = ListQuery> = {
|
|
43
49
|
items: Awaited<T>;
|
|
44
50
|
/** Effective page number. */
|
|
45
51
|
page: number;
|
|
46
|
-
/** Effective
|
|
47
|
-
|
|
52
|
+
/** Effective limit. Number of items is guaranteed to be less or equal. */
|
|
53
|
+
limit: number;
|
|
48
54
|
/** Offset of the first item, 1-based. */
|
|
49
55
|
offset: number;
|
|
50
|
-
/** Prev page
|
|
56
|
+
/** Prev page number (if exists). */
|
|
51
57
|
prevPage?: number;
|
|
52
58
|
/** Next page number (if exists). */
|
|
53
59
|
nextPage?: number;
|
|
54
60
|
};
|
|
55
|
-
|
|
61
|
+
/** paginateByPage returns one page of results using offset-based pagination. */
|
|
56
62
|
declare function paginateByPage<T extends ListQuery>(query: T, config?: PaginationConfig, params?: PagePaginationParams): Promise<PagePaginationPage<T>>;
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
/** createPagePaginator creates a reusable page paginator with the given config. */
|
|
65
|
+
declare function createPagePaginator(config?: PaginationConfig): <T extends ListQuery>(query: T, params?: PagePaginationParams) => Promise<PagePaginationPage<T>>;
|
|
66
|
+
|
|
67
|
+
export { type CursorPaginationPage, type CursorPaginationParams, type ListQuery, type PagePaginationPage, type PagePaginationParams, type PaginationConfig, type PaginationParams, type SortDir, createCursorPaginator, createPagePaginator, getLimit, paginateByCursor, paginateByPage };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,58 +1,67 @@
|
|
|
1
|
-
import { Query
|
|
1
|
+
import { Query } from 'orchid-orm';
|
|
2
|
+
|
|
3
|
+
type ListQuery = Query & {
|
|
4
|
+
returnType: undefined | "all";
|
|
5
|
+
};
|
|
6
|
+
type SortDir = "ASC" | "DESC";
|
|
2
7
|
|
|
3
|
-
interface ListQuery extends Query {
|
|
4
|
-
then: QueryThen<unknown[]>;
|
|
5
|
-
}
|
|
6
8
|
interface PaginationConfig {
|
|
7
|
-
/** Default
|
|
8
|
-
|
|
9
|
-
/** Max
|
|
10
|
-
|
|
9
|
+
/** Default limit. If `maxLimit` is not set, client params limit is ignored. */
|
|
10
|
+
limit?: number;
|
|
11
|
+
/** Max limit allowed to be passed in params. */
|
|
12
|
+
maxLimit?: number;
|
|
11
13
|
}
|
|
12
14
|
interface PaginationParams {
|
|
13
|
-
/** Requested
|
|
14
|
-
|
|
15
|
+
/** Requested limit. */
|
|
16
|
+
limit?: number;
|
|
15
17
|
}
|
|
16
|
-
|
|
18
|
+
/** getLimit returns the effective limit for a query and pagination parameters. */
|
|
19
|
+
declare function getLimit(query: ListQuery, config?: PaginationConfig, params?: PaginationParams): number;
|
|
17
20
|
|
|
18
21
|
interface CursorPaginationParams {
|
|
19
22
|
/** Page cursor, as returned by previous call in prevCursor / nextCursor. */
|
|
20
23
|
cursor?: string;
|
|
21
|
-
/**
|
|
22
|
-
|
|
24
|
+
/** Limit. */
|
|
25
|
+
limit?: number;
|
|
23
26
|
}
|
|
24
27
|
type CursorPaginationPage<T extends ListQuery = ListQuery> = {
|
|
25
28
|
items: Awaited<T>;
|
|
26
|
-
/** Effective
|
|
27
|
-
|
|
29
|
+
/** Effective limit. Number of items is guaranteed to be less or equal. */
|
|
30
|
+
limit: number;
|
|
28
31
|
/** Cursor pointing to previous page. */
|
|
29
32
|
prevCursor?: string;
|
|
30
33
|
/** Cursor pointing to next page. */
|
|
31
34
|
nextCursor?: string;
|
|
32
35
|
};
|
|
33
|
-
|
|
36
|
+
/** paginateByCursor returns one page of results using cursor-based pagination. */
|
|
34
37
|
declare function paginateByCursor<T extends ListQuery>(query: T, config?: PaginationConfig, params?: CursorPaginationParams): Promise<CursorPaginationPage<T>>;
|
|
35
38
|
|
|
39
|
+
/** createCursorPaginator creates a reusable cursor paginator with the given config. */
|
|
40
|
+
declare function createCursorPaginator(config?: PaginationConfig): <T extends ListQuery>(query: T, params?: CursorPaginationParams) => Promise<CursorPaginationPage<T>>;
|
|
41
|
+
|
|
36
42
|
interface PagePaginationParams {
|
|
37
43
|
/** Page, 1-based. */
|
|
38
44
|
page?: number;
|
|
39
|
-
/**
|
|
40
|
-
|
|
45
|
+
/** Limit. */
|
|
46
|
+
limit?: number;
|
|
41
47
|
}
|
|
42
48
|
type PagePaginationPage<T extends ListQuery = ListQuery> = {
|
|
43
49
|
items: Awaited<T>;
|
|
44
50
|
/** Effective page number. */
|
|
45
51
|
page: number;
|
|
46
|
-
/** Effective
|
|
47
|
-
|
|
52
|
+
/** Effective limit. Number of items is guaranteed to be less or equal. */
|
|
53
|
+
limit: number;
|
|
48
54
|
/** Offset of the first item, 1-based. */
|
|
49
55
|
offset: number;
|
|
50
|
-
/** Prev page
|
|
56
|
+
/** Prev page number (if exists). */
|
|
51
57
|
prevPage?: number;
|
|
52
58
|
/** Next page number (if exists). */
|
|
53
59
|
nextPage?: number;
|
|
54
60
|
};
|
|
55
|
-
|
|
61
|
+
/** paginateByPage returns one page of results using offset-based pagination. */
|
|
56
62
|
declare function paginateByPage<T extends ListQuery>(query: T, config?: PaginationConfig, params?: PagePaginationParams): Promise<PagePaginationPage<T>>;
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
/** createPagePaginator creates a reusable page paginator with the given config. */
|
|
65
|
+
declare function createPagePaginator(config?: PaginationConfig): <T extends ListQuery>(query: T, params?: PagePaginationParams) => Promise<PagePaginationPage<T>>;
|
|
66
|
+
|
|
67
|
+
export { type CursorPaginationPage, type CursorPaginationParams, type ListQuery, type PagePaginationPage, type PagePaginationParams, type PaginationConfig, type PaginationParams, type SortDir, createCursorPaginator, createPagePaginator, getLimit, paginateByCursor, paginateByPage };
|