xantiagoma 0.1.2 → 0.2.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 +84 -24
- package/dist/entry-pagination-drizzle.cjs +61 -0
- package/dist/entry-pagination-drizzle.cjs.map +1 -0
- package/dist/entry-pagination-drizzle.d.cts +38 -0
- package/dist/entry-pagination-drizzle.d.mts +38 -0
- package/dist/entry-pagination-drizzle.mjs +59 -0
- package/dist/entry-pagination-drizzle.mjs.map +1 -0
- package/dist/entry-pagination-knex.cjs +50 -0
- package/dist/entry-pagination-knex.cjs.map +1 -0
- package/dist/entry-pagination-knex.d.cts +57 -0
- package/dist/entry-pagination-knex.d.mts +57 -0
- package/dist/entry-pagination-knex.mjs +49 -0
- package/dist/entry-pagination-knex.mjs.map +1 -0
- package/dist/entry-pagination-kysely.cjs +64 -0
- package/dist/entry-pagination-kysely.cjs.map +1 -0
- package/dist/entry-pagination-kysely.d.cts +40 -0
- package/dist/entry-pagination-kysely.d.mts +40 -0
- package/dist/entry-pagination-kysely.mjs +62 -0
- package/dist/entry-pagination-kysely.mjs.map +1 -0
- package/dist/entry-pagination-mongo.cjs +68 -0
- package/dist/entry-pagination-mongo.cjs.map +1 -0
- package/dist/entry-pagination-mongo.d.cts +51 -0
- package/dist/entry-pagination-mongo.d.mts +51 -0
- package/dist/entry-pagination-mongo.mjs +66 -0
- package/dist/entry-pagination-mongo.mjs.map +1 -0
- package/dist/entry-pagination-prisma.cjs +64 -0
- package/dist/entry-pagination-prisma.cjs.map +1 -0
- package/dist/entry-pagination-prisma.d.cts +49 -0
- package/dist/entry-pagination-prisma.d.mts +49 -0
- package/dist/entry-pagination-prisma.mjs +62 -0
- package/dist/entry-pagination-prisma.mjs.map +1 -0
- package/dist/entry-pagination.cjs +401 -0
- package/dist/entry-pagination.cjs.map +1 -0
- package/dist/entry-pagination.d.cts +120 -0
- package/dist/entry-pagination.d.mts +120 -0
- package/dist/entry-pagination.mjs +384 -0
- package/dist/entry-pagination.mjs.map +1 -0
- package/dist/entry-react.cjs +1 -1
- package/dist/entry-react.d.cts +1 -1
- package/dist/entry-react.d.mts +1 -1
- package/dist/entry-react.mjs +1 -1
- package/dist/entry-sonner.cjs +1 -1
- package/dist/entry-sonner.d.cts +1 -1
- package/dist/entry-sonner.d.mts +1 -1
- package/dist/entry-sonner.mjs +1 -1
- package/dist/entry-temporal.cjs +1 -1
- package/dist/entry-temporal.mjs +1 -1
- package/dist/entry-ulid.cjs +1 -1
- package/dist/entry-ulid.mjs +1 -1
- package/dist/{error-CQ6E1DMv.cjs → error-B1xdFIet.cjs} +1 -1
- package/dist/{error-CQ6E1DMv.cjs.map → error-B1xdFIet.cjs.map} +1 -1
- package/dist/{error-b0cYulof.mjs → error-B5yBe5I0.mjs} +1 -1
- package/dist/{error-b0cYulof.mjs.map → error-B5yBe5I0.mjs.map} +1 -1
- package/dist/{index-CECp5YtT.d.cts → index-BfArSGL_.d.mts} +140 -32
- package/dist/{index-kWrWj6TD.d.mts → index-k0rTrMol.d.cts} +140 -32
- package/dist/index.cjs +8 -5
- package/dist/index.d.cts +3 -2
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +6 -4
- package/dist/is-promise-BQwyCVAy.mjs +21 -0
- package/dist/is-promise-BQwyCVAy.mjs.map +1 -0
- package/dist/is-promise-Dle75dtS.cjs +26 -0
- package/dist/is-promise-Dle75dtS.cjs.map +1 -0
- package/dist/keyset-BiguUVI7.cjs +280 -0
- package/dist/keyset-BiguUVI7.cjs.map +1 -0
- package/dist/keyset-Cd8leiME.d.mts +512 -0
- package/dist/keyset-ChU9XDqc.mjs +245 -0
- package/dist/keyset-ChU9XDqc.mjs.map +1 -0
- package/dist/keyset-DqmiNJog.d.cts +512 -0
- package/dist/resolve-maybe-promise-4f-L5bxX.mjs +30 -0
- package/dist/resolve-maybe-promise-4f-L5bxX.mjs.map +1 -0
- package/dist/resolve-maybe-promise-DfTYDaO9.cjs +41 -0
- package/dist/resolve-maybe-promise-DfTYDaO9.cjs.map +1 -0
- package/dist/{src-D6TCFUzZ.mjs → src-CdaMpw6F.mjs} +46 -64
- package/dist/src-CdaMpw6F.mjs.map +1 -0
- package/dist/{src-BaG3gXLj.cjs → src-DD2TTZvg.cjs} +46 -76
- package/dist/src-DD2TTZvg.cjs.map +1 -0
- package/dist/try-catch-B9NlJqqx.mjs +35 -0
- package/dist/try-catch-B9NlJqqx.mjs.map +1 -0
- package/dist/try-catch-BDA5OPzr.cjs +46 -0
- package/dist/try-catch-BDA5OPzr.cjs.map +1 -0
- package/dist/types-DDbcxhql.d.mts +8 -0
- package/dist/types-i-wXHPp4.d.cts +8 -0
- package/docs/PAGINATION.md +1788 -0
- package/docs/sync-async-adaptive.md +283 -0
- package/package.json +72 -1
- package/dist/src-BaG3gXLj.cjs.map +0 -1
- package/dist/src-D6TCFUzZ.mjs.map +0 -1
- package/dist/try-catch-D2fA1iDo.mjs +0 -66
- package/dist/try-catch-D2fA1iDo.mjs.map +0 -1
- package/dist/try-catch-lfQywqx3.cjs +0 -77
- package/dist/try-catch-lfQywqx3.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -24,17 +24,23 @@ npm install xantiagoma
|
|
|
24
24
|
|
|
25
25
|
## Entry Points
|
|
26
26
|
|
|
27
|
-
| Import
|
|
28
|
-
|
|
|
29
|
-
| `xantiagoma`
|
|
30
|
-
| `xantiagoma/
|
|
31
|
-
| `xantiagoma/
|
|
32
|
-
| `xantiagoma/
|
|
33
|
-
| `xantiagoma/
|
|
34
|
-
| `xantiagoma/
|
|
35
|
-
| `xantiagoma/
|
|
36
|
-
| `xantiagoma/
|
|
37
|
-
| `xantiagoma/
|
|
27
|
+
| Import | Description | Dependencies |
|
|
28
|
+
| ------------------------------- | -------------------------------------- | -------------------------------- |
|
|
29
|
+
| `xantiagoma` | Core utilities (isomorphic, zero deps) | none |
|
|
30
|
+
| `xantiagoma/pagination` | Pagination + keyset helpers | none |
|
|
31
|
+
| `xantiagoma/pagination/drizzle` | Drizzle adapter for pagination keysets | `drizzle-orm` |
|
|
32
|
+
| `xantiagoma/pagination/kysely` | Kysely adapter for pagination keysets | `kysely` |
|
|
33
|
+
| `xantiagoma/pagination/knex` | Knex adapter for pagination keysets | none |
|
|
34
|
+
| `xantiagoma/pagination/mongo` | Mongo/Mongoose adapter for keysets | none |
|
|
35
|
+
| `xantiagoma/pagination/prisma` | Prisma adapter for pagination keysets | none |
|
|
36
|
+
| `xantiagoma/web` | Browser/FormData utilities | none |
|
|
37
|
+
| `xantiagoma/ulid` | Prefixed ULID generation + helpers | `ulid` |
|
|
38
|
+
| `xantiagoma/temporal` | Date/time/duration with Temporal API | `temporal-polyfill`, `itty-time` |
|
|
39
|
+
| `xantiagoma/dataloader` | DataLoader factory | `dataloader` |
|
|
40
|
+
| `xantiagoma/unstorage` | Cache helpers with unstorage | `unstorage`, `ohash` |
|
|
41
|
+
| `xantiagoma/valibot` | TimeZone validation schema | `valibot` |
|
|
42
|
+
| `xantiagoma/sonner` | Toast streaming for iterables | `sonner`, `react` |
|
|
43
|
+
| `xantiagoma/react` | React hooks + components | `react`, `@tanstack/react-query` |
|
|
38
44
|
|
|
39
45
|
Sub-entry dependencies are **optional peer deps** — only install what you use.
|
|
40
46
|
|
|
@@ -42,12 +48,13 @@ Sub-entry dependencies are **optional peer deps** — only install what you use.
|
|
|
42
48
|
|
|
43
49
|
### Error Handling
|
|
44
50
|
|
|
45
|
-
| Export | Description
|
|
46
|
-
| --------------------------- |
|
|
47
|
-
| `tryCatch`
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
+
| Export | Description | Source | Tests |
|
|
52
|
+
| --------------------------- | -------------------------------------------- | ------------------------------- | --------------------------------------- |
|
|
53
|
+
| `tryCatch` | `[data, error]` tuples — sync/async adaptive | [src](./src/try-catch.ts) | [tests](./test/try-catch.test.ts) |
|
|
54
|
+
| `tryCatchSync` (deprecated) | Use `tryCatch` — kept for backwards compat | [src](./src/try-catch.ts) | [tests](./test/try-catch.test.ts) |
|
|
55
|
+
| `assertNotNull` | Throws if null/undefined, narrows type | [src](./src/assert-not-null.ts) | [tests](./test/assert-not-null.test.ts) |
|
|
56
|
+
| `valueOrThrow` | Returns value or throws | [src](./src/error.ts) | [tests](./test/error.test.ts) |
|
|
57
|
+
| `AssertError` | Custom error class | [src](./src/errors.ts) | [tests](./test/errors.test.ts) |
|
|
51
58
|
|
|
52
59
|
### Async
|
|
53
60
|
|
|
@@ -55,19 +62,20 @@ Sub-entry dependencies are **optional peer deps** — only install what you use.
|
|
|
55
62
|
| --------------------- | ---------------------------------------- | ------------------------------------- | --------------------------------------------- |
|
|
56
63
|
| `wait` | Typed setTimeout delay | [src](./src/wait.ts) | [tests](./test/wait.test.ts) |
|
|
57
64
|
| `Completer` | Externally resolvable Promise | [src](./src/completer.ts) | [tests](./test/completer.test.ts) |
|
|
58
|
-
| `collect` | Drain
|
|
65
|
+
| `collect` | Drain any iterable into `T[]` (adaptive) | [src](./src/collect.ts) | [tests](./test/collect.test.ts) |
|
|
59
66
|
| `asyncOf` | Create `AsyncGenerator` from values | [src](./src/async-of.ts) | [tests](./test/async-of.test.ts) |
|
|
60
67
|
| `AsyncChannel` | Push-based `AsyncIterable` with modes | [src](./src/async-channel.ts) | [tests](./test/async-channel.test.ts) |
|
|
61
68
|
| `resolveMaybePromise` | Resolve `T \| Promise<T>` → `Promise<T>` | [src](./src/resolve-maybe-promise.ts) | [tests](./test/resolve-maybe-promise.test.ts) |
|
|
62
69
|
|
|
63
70
|
### Iterables & Generators
|
|
64
71
|
|
|
65
|
-
| Export
|
|
66
|
-
|
|
|
67
|
-
| `range` / `rangeLazy`
|
|
68
|
-
| `enumerate`
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
72
|
+
| Export | Description | Source | Tests |
|
|
73
|
+
| --------------------- | --------------------------------------------------- | --------------------------------- | ----------------------------------------- |
|
|
74
|
+
| `range` / `rangeLazy` | Numeric range (array / generator) | [src](./src/range.ts) | [tests](./test/range.test.ts) |
|
|
75
|
+
| `enumerate` | `[index, value]` tuples — sync/async adaptive | [src](./src/enumerate.ts) | [tests](./test/enumerate.test.ts) |
|
|
76
|
+
| `enumerateAsync` | Always-async; awaits Promise values in sync sources | [src](./src/enumerate.ts) | [tests](./test/enumerate.test.ts) |
|
|
77
|
+
| `toIterator` | Normalize to `Iterator` | [src](./src/to-iterator.ts) | [tests](./test/to-iterator.test.ts) |
|
|
78
|
+
| `toAsyncIterable` | Normalize to `AsyncGenerator` | [src](./src/to-async-iterable.ts) | [tests](./test/to-async-iterable.test.ts) |
|
|
71
79
|
|
|
72
80
|
### Type Guards
|
|
73
81
|
|
|
@@ -104,6 +112,58 @@ Sub-entry dependencies are **optional peer deps** — only install what you use.
|
|
|
104
112
|
| `resolveStreamSource` | Resolve `StreamSource<T>` | [src](./src/stream-source.ts) | [tests](./test/stream-source.test.ts) |
|
|
105
113
|
| `secondsToMs` / `minutesToMs` / `hoursToMs` | Time unit converters | [src](./src/time-convert.ts) | [tests](./test/time-convert.test.ts) |
|
|
106
114
|
|
|
115
|
+
## Pagination Utilities (`xantiagoma/pagination`)
|
|
116
|
+
|
|
117
|
+
Source-agnostic pagination: import from `xantiagoma/pagination`, provide fetchers for your data source (SQL, Drizzle, Prisma, Mongoose, HTTP...), and the paginator handles input styles (`page`/`pageSize`, `limit`/`offset`, cursor), `hasNextPage` lookahead, backward (scroll-up) pagination, and a uniform result envelope. **Full guide: [docs/PAGINATION.md](./docs/PAGINATION.md)** — backend recipes for SQL drivers, ORMs/query builders, Mongo, Firestore, DynamoDB, Redis sorted sets, Elasticsearch/OpenSearch, analytics warehouses, HTTP APIs, REST/GraphQL endpoints, cursor codecs, and frontend integration.
|
|
118
|
+
|
|
119
|
+
Sync/async adaptive: pass all-sync fetchers and codec (e.g. an in-memory array) and `paginate()` returns plain values — no `await` needed; any async piece (even just an async cursor encoder) makes results Promises, reflected in the types. The pattern is documented in [docs/sync-async-adaptive.md](./docs/sync-async-adaptive.md).
|
|
120
|
+
|
|
121
|
+
| Import | Export | Description | Source | Tests |
|
|
122
|
+
| ------------------------------- | --------------------------- | -------------------------------------------------------------------------- | ---------------------------------- | ------------------------------------------ |
|
|
123
|
+
| `xantiagoma/pagination` | `createPaginator` | Paginator over user-provided fetchers; `pages()`/`items()` async iteration | [src](./src/pagination.ts) | [tests](./test/pagination.test.ts) |
|
|
124
|
+
| `xantiagoma/pagination` | `toOffsetWindow` | Page/offset input → `{ limit, offset }` | [src](./src/pagination.ts) | [tests](./test/pagination.test.ts) |
|
|
125
|
+
| `xantiagoma/pagination` | `createCursorCodec` | Pluggable opaque-cursor codec (JSON + base64url) | [src](./src/cursor-codec.ts) | [tests](./test/cursor-codec.test.ts) |
|
|
126
|
+
| `xantiagoma/pagination` | `createKeysetSpec` | Portable keyset `where()`/`orderBy()` AST | [src](./src/keyset.ts) | [tests](./test/keyset.test.ts) |
|
|
127
|
+
| `xantiagoma/pagination` | `keysetSqlExpression` | Mark server-owned computed SQL expressions for raw keyset helpers | [src](./src/keyset.ts) | [tests](./test/keyset.test.ts) |
|
|
128
|
+
| `xantiagoma/pagination` | `toKeysetWhereSql` | Keyset `WHERE` → parameterized SQL + params | [src](./src/keyset.ts) | [tests](./test/keyset.test.ts) |
|
|
129
|
+
| `xantiagoma/pagination` | `toKeysetOrderBySql` | Keyset order → SQL `ORDER BY` fragment | [src](./src/keyset.ts) | [tests](./test/keyset.test.ts) |
|
|
130
|
+
| `xantiagoma/pagination/drizzle` | `toDrizzleKeyset` | Keyset AST → Drizzle `where`/`orderBy` helpers | [src](./src/pagination-drizzle.ts) | [tests](./test/pagination-drizzle.test.ts) |
|
|
131
|
+
| `xantiagoma/pagination/kysely` | `toKyselyKeyset` | Keyset AST → Kysely `where`/`orderBy` helpers | [src](./src/pagination-kysely.ts) | [tests](./test/pagination-kysely.test.ts) |
|
|
132
|
+
| `xantiagoma/pagination/knex` | `applyKeysetToKnex` | Apply keyset AST to Knex `whereRaw`/`orderByRaw` | [src](./src/pagination-knex.ts) | [tests](./test/pagination-knex.test.ts) |
|
|
133
|
+
| `xantiagoma/pagination/mongo` | `toMongoKeyset` | Keyset AST → Mongo/Mongoose `filter`/`sort` objects | [src](./src/pagination-mongo.ts) | [tests](./test/pagination-mongo.test.ts) |
|
|
134
|
+
| `xantiagoma/pagination/prisma` | `toPrismaKeyset` | Keyset AST → Prisma `where`/`orderBy` objects | [src](./src/pagination-prisma.ts) | [tests](./test/pagination-prisma.test.ts) |
|
|
135
|
+
| `xantiagoma/pagination` | `parsePaginationParams` | Query params → `PaginationInput` (with clamping) | [src](./src/pagination-params.ts) | [tests](./test/pagination-params.test.ts) |
|
|
136
|
+
| `xantiagoma/pagination` | `fromRelayArgs` | Relay `first`/`after`/`last`/`before` → input | [src](./src/pagination-params.ts) | [tests](./test/pagination-params.test.ts) |
|
|
137
|
+
| `xantiagoma/pagination` | `toRelayConnection` | Result → Relay connection (`edges`, `pageInfo`) | [src](./src/pagination-output.ts) | [tests](./test/pagination-output.test.ts) |
|
|
138
|
+
| `xantiagoma/pagination` | `toRestEnvelope` | Result → `{ data, meta }` REST envelope | [src](./src/pagination-output.ts) | [tests](./test/pagination-output.test.ts) |
|
|
139
|
+
| `xantiagoma/pagination` | `infinitePaginationOptions` | TanStack `useInfiniteQuery` config (dep-free) | [src](./src/pagination-output.ts) | [tests](./test/pagination-output.test.ts) |
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import {
|
|
143
|
+
createPaginator,
|
|
144
|
+
fromRelayArgs,
|
|
145
|
+
parsePaginationParams,
|
|
146
|
+
toRestEnvelope,
|
|
147
|
+
} from "xantiagoma/pagination";
|
|
148
|
+
|
|
149
|
+
const paginator = createPaginator({
|
|
150
|
+
fetchOffset: ({ limit, offset }) => ({ items: db.query(/* LIMIT/OFFSET */) }),
|
|
151
|
+
fetchCursor: ({ limit, cursor, direction }) => ({ items: db.query(/* keyset */) }),
|
|
152
|
+
cursor: { fromItem: (u) => ({ id: u.id }) }, // codec optional — defaults to JSON + base64url
|
|
153
|
+
maxLimit: 100,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// REST route
|
|
157
|
+
const result = await paginator.paginate(parsePaginationParams(url.searchParams));
|
|
158
|
+
return Response.json(toRestEnvelope(result));
|
|
159
|
+
|
|
160
|
+
// GraphQL/Relay resolver
|
|
161
|
+
const result = await paginator.paginate(fromRelayArgs(args));
|
|
162
|
+
return toRelayConnection(result, paginator.cursorFor);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Cursor tokens are produced by a two-stage codec (`serializer` + `encoder`, both replaceable) signature-compatible with [drizzle-cursor](https://github.com/xantiagoma/drizzle-cursor), so the same custom encoder/decoder (encryption, signing...) can be shared between both.
|
|
166
|
+
|
|
107
167
|
## Web Utilities (`xantiagoma/web`)
|
|
108
168
|
|
|
109
169
|
| Export | Description | Source | Tests |
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("./chunk-CKQMccvm.cjs");
|
|
3
|
+
let drizzle_orm = require("drizzle-orm");
|
|
4
|
+
//#region src/pagination-drizzle.ts
|
|
5
|
+
const resolveColumn = (columns, key) => {
|
|
6
|
+
const col = columns[key];
|
|
7
|
+
if (col === void 0) throw new Error(`toDrizzleKeyset: missing column mapping for key "${key}"`);
|
|
8
|
+
return col;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Create a Drizzle adapter for portable keyset predicates and ordering.
|
|
12
|
+
*
|
|
13
|
+
* The mapping values can be Drizzle columns or SQL expressions. Keys are the
|
|
14
|
+
* logical cursor keys used by `createKeysetSpec`; they do not need to match
|
|
15
|
+
* database column names.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import { toDrizzleKeyset } from "xantiagoma/pagination/drizzle";
|
|
20
|
+
*
|
|
21
|
+
* const drizzleKeyset = toDrizzleKeyset({
|
|
22
|
+
* createdAt: posts.createdAt,
|
|
23
|
+
* id: posts.id,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* await db
|
|
27
|
+
* .select()
|
|
28
|
+
* .from(posts)
|
|
29
|
+
* .where(drizzleKeyset.where(keyset.where(cursor, direction)))
|
|
30
|
+
* .orderBy(...drizzleKeyset.orderBy(keyset.orderBy(direction)))
|
|
31
|
+
* .limit(limit);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function toDrizzleKeyset(columns) {
|
|
35
|
+
const where = (keysetWhere) => {
|
|
36
|
+
if (keysetWhere == null) return;
|
|
37
|
+
return (0, drizzle_orm.or)(...keysetWhere.or.flatMap((branch) => {
|
|
38
|
+
const combined = (0, drizzle_orm.and)(...branch.and.map((pred) => {
|
|
39
|
+
const col = resolveColumn(columns, pred.key);
|
|
40
|
+
switch (pred.op) {
|
|
41
|
+
case "eq": return (0, drizzle_orm.eq)(col, pred.value);
|
|
42
|
+
case "gt": return (0, drizzle_orm.gt)(col, pred.value);
|
|
43
|
+
case "lt": return (0, drizzle_orm.lt)(col, pred.value);
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
return combined === void 0 ? [] : [combined];
|
|
47
|
+
}));
|
|
48
|
+
};
|
|
49
|
+
const orderBy = (order) => order.map((col) => {
|
|
50
|
+
const target = resolveColumn(columns, col.key);
|
|
51
|
+
return col.order === "asc" ? (0, drizzle_orm.asc)(target) : (0, drizzle_orm.desc)(target);
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
where,
|
|
55
|
+
orderBy
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
exports.toDrizzleKeyset = toDrizzleKeyset;
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=entry-pagination-drizzle.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-pagination-drizzle.cjs","names":[],"sources":["../src/pagination-drizzle.ts"],"sourcesContent":["import { and, asc, desc, eq, gt, lt, or, type SQL } from \"drizzle-orm\";\nimport type { KeysetSortKey, KeysetWhere } from \"./keyset.ts\";\n\nexport type DrizzleKeysetTarget = Parameters<typeof asc>[0];\n\nexport type DrizzleKeysetColumns = Record<string, DrizzleKeysetTarget>;\n\nexport type DrizzleKeyset = {\n /** Render a portable keyset where AST to Drizzle SQL. `null` means first page. */\n where(where: KeysetWhere | null): SQL | undefined;\n /** Render portable keyset order keys to Drizzle `orderBy(...values)`. */\n orderBy(order: KeysetSortKey[]): SQL[];\n};\n\nconst resolveColumn = (columns: DrizzleKeysetColumns, key: string): DrizzleKeysetTarget => {\n const col = columns[key];\n if (col === undefined) {\n throw new Error(`toDrizzleKeyset: missing column mapping for key \"${key}\"`);\n }\n return col;\n};\n\n/**\n * Create a Drizzle adapter for portable keyset predicates and ordering.\n *\n * The mapping values can be Drizzle columns or SQL expressions. Keys are the\n * logical cursor keys used by `createKeysetSpec`; they do not need to match\n * database column names.\n *\n * @example\n * ```ts\n * import { toDrizzleKeyset } from \"xantiagoma/pagination/drizzle\";\n *\n * const drizzleKeyset = toDrizzleKeyset({\n * createdAt: posts.createdAt,\n * id: posts.id,\n * });\n *\n * await db\n * .select()\n * .from(posts)\n * .where(drizzleKeyset.where(keyset.where(cursor, direction)))\n * .orderBy(...drizzleKeyset.orderBy(keyset.orderBy(direction)))\n * .limit(limit);\n * ```\n */\nexport function toDrizzleKeyset(columns: DrizzleKeysetColumns): DrizzleKeyset {\n const where = (keysetWhere: KeysetWhere | null): SQL | undefined => {\n if (keysetWhere == null) {\n return undefined;\n }\n\n const branches = keysetWhere.or.flatMap((branch) => {\n const predicates = branch.and.map((pred) => {\n const col = resolveColumn(columns, pred.key);\n switch (pred.op) {\n case \"eq\":\n return eq(col, pred.value);\n case \"gt\":\n return gt(col, pred.value);\n case \"lt\":\n return lt(col, pred.value);\n }\n });\n const combined = and(...predicates);\n return combined === undefined ? [] : [combined];\n });\n\n return or(...branches);\n };\n\n const orderBy = (order: KeysetSortKey[]): SQL[] =>\n order.map((col) => {\n const target = resolveColumn(columns, col.key);\n return col.order === \"asc\" ? asc(target) : desc(target);\n });\n\n return { where, orderBy };\n}\n"],"mappings":";;;;AAcA,MAAM,iBAAiB,SAA+B,QAAqC;CACzF,MAAM,MAAM,QAAQ;CACpB,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,MAAM,oDAAoD,IAAI,GAAG;CAE7E,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BT,SAAgB,gBAAgB,SAA8C;CAC5E,MAAM,SAAS,gBAAqD;EAClE,IAAI,eAAe,MACjB;EAmBF,QAAA,GAAA,YAAA,IAAU,GAhBO,YAAY,GAAG,SAAS,WAAW;GAYlD,MAAM,YAAA,GAAA,YAAA,KAAe,GAXF,OAAO,IAAI,KAAK,SAAS;IAC1C,MAAM,MAAM,cAAc,SAAS,KAAK,IAAI;IAC5C,QAAQ,KAAK,IAAb;KACE,KAAK,MACH,QAAA,GAAA,YAAA,IAAU,KAAK,KAAK,MAAM;KAC5B,KAAK,MACH,QAAA,GAAA,YAAA,IAAU,KAAK,KAAK,MAAM;KAC5B,KAAK,MACH,QAAA,GAAA,YAAA,IAAU,KAAK,KAAK,MAAM;;KAGE,CAAC;GACnC,OAAO,aAAa,KAAA,IAAY,EAAE,GAAG,CAAC,SAAS;IAG5B,CAAC;;CAGxB,MAAM,WAAW,UACf,MAAM,KAAK,QAAQ;EACjB,MAAM,SAAS,cAAc,SAAS,IAAI,IAAI;EAC9C,OAAO,IAAI,UAAU,SAAA,GAAA,YAAA,KAAY,OAAO,IAAA,GAAA,YAAA,MAAQ,OAAO;GACvD;CAEJ,OAAO;EAAE;EAAO;EAAS"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { d as KeysetWhere, o as KeysetSortKey } from "./keyset-DqmiNJog.cjs";
|
|
2
|
+
import { SQL, asc } from "drizzle-orm";
|
|
3
|
+
|
|
4
|
+
//#region src/pagination-drizzle.d.ts
|
|
5
|
+
type DrizzleKeysetTarget = Parameters<typeof asc>[0];
|
|
6
|
+
type DrizzleKeysetColumns = Record<string, DrizzleKeysetTarget>;
|
|
7
|
+
type DrizzleKeyset = {
|
|
8
|
+
/** Render a portable keyset where AST to Drizzle SQL. `null` means first page. */where(where: KeysetWhere | null): SQL | undefined; /** Render portable keyset order keys to Drizzle `orderBy(...values)`. */
|
|
9
|
+
orderBy(order: KeysetSortKey[]): SQL[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Create a Drizzle adapter for portable keyset predicates and ordering.
|
|
13
|
+
*
|
|
14
|
+
* The mapping values can be Drizzle columns or SQL expressions. Keys are the
|
|
15
|
+
* logical cursor keys used by `createKeysetSpec`; they do not need to match
|
|
16
|
+
* database column names.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { toDrizzleKeyset } from "xantiagoma/pagination/drizzle";
|
|
21
|
+
*
|
|
22
|
+
* const drizzleKeyset = toDrizzleKeyset({
|
|
23
|
+
* createdAt: posts.createdAt,
|
|
24
|
+
* id: posts.id,
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* await db
|
|
28
|
+
* .select()
|
|
29
|
+
* .from(posts)
|
|
30
|
+
* .where(drizzleKeyset.where(keyset.where(cursor, direction)))
|
|
31
|
+
* .orderBy(...drizzleKeyset.orderBy(keyset.orderBy(direction)))
|
|
32
|
+
* .limit(limit);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function toDrizzleKeyset(columns: DrizzleKeysetColumns): DrizzleKeyset;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { type DrizzleKeyset, type DrizzleKeysetColumns, type DrizzleKeysetTarget, toDrizzleKeyset };
|
|
38
|
+
//# sourceMappingURL=entry-pagination-drizzle.d.cts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { d as KeysetWhere, o as KeysetSortKey } from "./keyset-Cd8leiME.mjs";
|
|
2
|
+
import { SQL, asc } from "drizzle-orm";
|
|
3
|
+
|
|
4
|
+
//#region src/pagination-drizzle.d.ts
|
|
5
|
+
type DrizzleKeysetTarget = Parameters<typeof asc>[0];
|
|
6
|
+
type DrizzleKeysetColumns = Record<string, DrizzleKeysetTarget>;
|
|
7
|
+
type DrizzleKeyset = {
|
|
8
|
+
/** Render a portable keyset where AST to Drizzle SQL. `null` means first page. */where(where: KeysetWhere | null): SQL | undefined; /** Render portable keyset order keys to Drizzle `orderBy(...values)`. */
|
|
9
|
+
orderBy(order: KeysetSortKey[]): SQL[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Create a Drizzle adapter for portable keyset predicates and ordering.
|
|
13
|
+
*
|
|
14
|
+
* The mapping values can be Drizzle columns or SQL expressions. Keys are the
|
|
15
|
+
* logical cursor keys used by `createKeysetSpec`; they do not need to match
|
|
16
|
+
* database column names.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { toDrizzleKeyset } from "xantiagoma/pagination/drizzle";
|
|
21
|
+
*
|
|
22
|
+
* const drizzleKeyset = toDrizzleKeyset({
|
|
23
|
+
* createdAt: posts.createdAt,
|
|
24
|
+
* id: posts.id,
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* await db
|
|
28
|
+
* .select()
|
|
29
|
+
* .from(posts)
|
|
30
|
+
* .where(drizzleKeyset.where(keyset.where(cursor, direction)))
|
|
31
|
+
* .orderBy(...drizzleKeyset.orderBy(keyset.orderBy(direction)))
|
|
32
|
+
* .limit(limit);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function toDrizzleKeyset(columns: DrizzleKeysetColumns): DrizzleKeyset;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { type DrizzleKeyset, type DrizzleKeysetColumns, type DrizzleKeysetTarget, toDrizzleKeyset };
|
|
38
|
+
//# sourceMappingURL=entry-pagination-drizzle.d.mts.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { and, asc, desc, eq, gt, lt, or } from "drizzle-orm";
|
|
2
|
+
//#region src/pagination-drizzle.ts
|
|
3
|
+
const resolveColumn = (columns, key) => {
|
|
4
|
+
const col = columns[key];
|
|
5
|
+
if (col === void 0) throw new Error(`toDrizzleKeyset: missing column mapping for key "${key}"`);
|
|
6
|
+
return col;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Create a Drizzle adapter for portable keyset predicates and ordering.
|
|
10
|
+
*
|
|
11
|
+
* The mapping values can be Drizzle columns or SQL expressions. Keys are the
|
|
12
|
+
* logical cursor keys used by `createKeysetSpec`; they do not need to match
|
|
13
|
+
* database column names.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { toDrizzleKeyset } from "xantiagoma/pagination/drizzle";
|
|
18
|
+
*
|
|
19
|
+
* const drizzleKeyset = toDrizzleKeyset({
|
|
20
|
+
* createdAt: posts.createdAt,
|
|
21
|
+
* id: posts.id,
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* await db
|
|
25
|
+
* .select()
|
|
26
|
+
* .from(posts)
|
|
27
|
+
* .where(drizzleKeyset.where(keyset.where(cursor, direction)))
|
|
28
|
+
* .orderBy(...drizzleKeyset.orderBy(keyset.orderBy(direction)))
|
|
29
|
+
* .limit(limit);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
function toDrizzleKeyset(columns) {
|
|
33
|
+
const where = (keysetWhere) => {
|
|
34
|
+
if (keysetWhere == null) return;
|
|
35
|
+
return or(...keysetWhere.or.flatMap((branch) => {
|
|
36
|
+
const combined = and(...branch.and.map((pred) => {
|
|
37
|
+
const col = resolveColumn(columns, pred.key);
|
|
38
|
+
switch (pred.op) {
|
|
39
|
+
case "eq": return eq(col, pred.value);
|
|
40
|
+
case "gt": return gt(col, pred.value);
|
|
41
|
+
case "lt": return lt(col, pred.value);
|
|
42
|
+
}
|
|
43
|
+
}));
|
|
44
|
+
return combined === void 0 ? [] : [combined];
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
47
|
+
const orderBy = (order) => order.map((col) => {
|
|
48
|
+
const target = resolveColumn(columns, col.key);
|
|
49
|
+
return col.order === "asc" ? asc(target) : desc(target);
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
where,
|
|
53
|
+
orderBy
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { toDrizzleKeyset };
|
|
58
|
+
|
|
59
|
+
//# sourceMappingURL=entry-pagination-drizzle.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-pagination-drizzle.mjs","names":[],"sources":["../src/pagination-drizzle.ts"],"sourcesContent":["import { and, asc, desc, eq, gt, lt, or, type SQL } from \"drizzle-orm\";\nimport type { KeysetSortKey, KeysetWhere } from \"./keyset.ts\";\n\nexport type DrizzleKeysetTarget = Parameters<typeof asc>[0];\n\nexport type DrizzleKeysetColumns = Record<string, DrizzleKeysetTarget>;\n\nexport type DrizzleKeyset = {\n /** Render a portable keyset where AST to Drizzle SQL. `null` means first page. */\n where(where: KeysetWhere | null): SQL | undefined;\n /** Render portable keyset order keys to Drizzle `orderBy(...values)`. */\n orderBy(order: KeysetSortKey[]): SQL[];\n};\n\nconst resolveColumn = (columns: DrizzleKeysetColumns, key: string): DrizzleKeysetTarget => {\n const col = columns[key];\n if (col === undefined) {\n throw new Error(`toDrizzleKeyset: missing column mapping for key \"${key}\"`);\n }\n return col;\n};\n\n/**\n * Create a Drizzle adapter for portable keyset predicates and ordering.\n *\n * The mapping values can be Drizzle columns or SQL expressions. Keys are the\n * logical cursor keys used by `createKeysetSpec`; they do not need to match\n * database column names.\n *\n * @example\n * ```ts\n * import { toDrizzleKeyset } from \"xantiagoma/pagination/drizzle\";\n *\n * const drizzleKeyset = toDrizzleKeyset({\n * createdAt: posts.createdAt,\n * id: posts.id,\n * });\n *\n * await db\n * .select()\n * .from(posts)\n * .where(drizzleKeyset.where(keyset.where(cursor, direction)))\n * .orderBy(...drizzleKeyset.orderBy(keyset.orderBy(direction)))\n * .limit(limit);\n * ```\n */\nexport function toDrizzleKeyset(columns: DrizzleKeysetColumns): DrizzleKeyset {\n const where = (keysetWhere: KeysetWhere | null): SQL | undefined => {\n if (keysetWhere == null) {\n return undefined;\n }\n\n const branches = keysetWhere.or.flatMap((branch) => {\n const predicates = branch.and.map((pred) => {\n const col = resolveColumn(columns, pred.key);\n switch (pred.op) {\n case \"eq\":\n return eq(col, pred.value);\n case \"gt\":\n return gt(col, pred.value);\n case \"lt\":\n return lt(col, pred.value);\n }\n });\n const combined = and(...predicates);\n return combined === undefined ? [] : [combined];\n });\n\n return or(...branches);\n };\n\n const orderBy = (order: KeysetSortKey[]): SQL[] =>\n order.map((col) => {\n const target = resolveColumn(columns, col.key);\n return col.order === \"asc\" ? asc(target) : desc(target);\n });\n\n return { where, orderBy };\n}\n"],"mappings":";;AAcA,MAAM,iBAAiB,SAA+B,QAAqC;CACzF,MAAM,MAAM,QAAQ;CACpB,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,MAAM,oDAAoD,IAAI,GAAG;CAE7E,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BT,SAAgB,gBAAgB,SAA8C;CAC5E,MAAM,SAAS,gBAAqD;EAClE,IAAI,eAAe,MACjB;EAmBF,OAAO,GAAG,GAhBO,YAAY,GAAG,SAAS,WAAW;GAYlD,MAAM,WAAW,IAAI,GAXF,OAAO,IAAI,KAAK,SAAS;IAC1C,MAAM,MAAM,cAAc,SAAS,KAAK,IAAI;IAC5C,QAAQ,KAAK,IAAb;KACE,KAAK,MACH,OAAO,GAAG,KAAK,KAAK,MAAM;KAC5B,KAAK,MACH,OAAO,GAAG,KAAK,KAAK,MAAM;KAC5B,KAAK,MACH,OAAO,GAAG,KAAK,KAAK,MAAM;;KAGE,CAAC;GACnC,OAAO,aAAa,KAAA,IAAY,EAAE,GAAG,CAAC,SAAS;IAG5B,CAAC;;CAGxB,MAAM,WAAW,UACf,MAAM,KAAK,QAAQ;EACjB,MAAM,SAAS,cAAc,SAAS,IAAI,IAAI;EAC9C,OAAO,IAAI,UAAU,QAAQ,IAAI,OAAO,GAAG,KAAK,OAAO;GACvD;CAEJ,OAAO;EAAE;EAAO;EAAS"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_keyset = require("./keyset-BiguUVI7.cjs");
|
|
3
|
+
//#region src/pagination-knex.ts
|
|
4
|
+
/**
|
|
5
|
+
* Apply a portable keyset `WHERE` + `ORDER BY` to a Knex query builder.
|
|
6
|
+
* Values are passed as bindings; column identifiers are validated by the raw
|
|
7
|
+
* SQL helpers before being rendered.
|
|
8
|
+
*
|
|
9
|
+
* This mutates and returns the provided Knex query builder, matching Knex's
|
|
10
|
+
* chaining style. For first-page cursor requests (`where === null`), it skips
|
|
11
|
+
* `whereRaw` and only applies `orderByRaw`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { createKeysetSpec } from "xantiagoma/pagination";
|
|
16
|
+
* import { applyKeysetToKnex } from "xantiagoma/pagination/knex";
|
|
17
|
+
*
|
|
18
|
+
* const keyset = createKeysetSpec({
|
|
19
|
+
* sort: [
|
|
20
|
+
* { key: "createdAt", order: "asc" },
|
|
21
|
+
* { key: "id", order: "asc" },
|
|
22
|
+
* ],
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* const q = applyKeysetToKnex(
|
|
26
|
+
* knex("posts").select("*"),
|
|
27
|
+
* keyset.where(cursor, direction),
|
|
28
|
+
* keyset.orderBy(direction),
|
|
29
|
+
* { columns: { createdAt: "created_at", id: "id" } },
|
|
30
|
+
* );
|
|
31
|
+
*
|
|
32
|
+
* const rows = await q.limit(limit);
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @throws If `options.columns` is missing a key or contains an unsafe SQL
|
|
36
|
+
* identifier.
|
|
37
|
+
*/
|
|
38
|
+
function applyKeysetToKnex(query, where, order, options) {
|
|
39
|
+
const whereSql = require_keyset.toKeysetWhereSql(where, options.columns, {
|
|
40
|
+
paramStart: options.paramStart,
|
|
41
|
+
placeholder: options.placeholder ?? (() => "?")
|
|
42
|
+
});
|
|
43
|
+
if (whereSql.sql) query.whereRaw(whereSql.sql, whereSql.params);
|
|
44
|
+
query.orderByRaw(require_keyset.toKeysetOrderBySql(order, options.columns));
|
|
45
|
+
return query;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
exports.applyKeysetToKnex = applyKeysetToKnex;
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=entry-pagination-knex.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-pagination-knex.cjs","names":["toKeysetWhereSql","toKeysetOrderBySql"],"sources":["../src/pagination-knex.ts"],"sourcesContent":["import type { KeysetSortKey, KeysetSqlColumns, KeysetWhere, ToKeysetSqlOptions } from \"./keyset.ts\";\nimport { toKeysetOrderBySql, toKeysetWhereSql } from \"./keyset.ts\";\n\n/**\n * Minimal structural subset of a Knex query builder used by\n * {@link applyKeysetToKnex}.\n *\n * `xantiagoma/pagination/knex` does not import `knex`; any object with these\n * methods works, which keeps Knex as your application's dependency rather than\n * a peer dependency of this package.\n */\nexport type KnexKeysetQuery = {\n /** Apply a raw `WHERE` clause with bind parameters. */\n whereRaw(sql: string, bindings?: unknown[]): unknown;\n /** Apply a raw `ORDER BY` clause. */\n orderByRaw(sql: string): unknown;\n};\n\n/** Options for {@link applyKeysetToKnex}. */\nexport type ApplyKeysetToKnexOptions = ToKeysetSqlOptions & {\n /** Logical key → SQL identifier/expression. Must be server-owned, not request data. */\n columns: KeysetSqlColumns;\n};\n\n/**\n * Apply a portable keyset `WHERE` + `ORDER BY` to a Knex query builder.\n * Values are passed as bindings; column identifiers are validated by the raw\n * SQL helpers before being rendered.\n *\n * This mutates and returns the provided Knex query builder, matching Knex's\n * chaining style. For first-page cursor requests (`where === null`), it skips\n * `whereRaw` and only applies `orderByRaw`.\n *\n * @example\n * ```ts\n * import { createKeysetSpec } from \"xantiagoma/pagination\";\n * import { applyKeysetToKnex } from \"xantiagoma/pagination/knex\";\n *\n * const keyset = createKeysetSpec({\n * sort: [\n * { key: \"createdAt\", order: \"asc\" },\n * { key: \"id\", order: \"asc\" },\n * ],\n * });\n *\n * const q = applyKeysetToKnex(\n * knex(\"posts\").select(\"*\"),\n * keyset.where(cursor, direction),\n * keyset.orderBy(direction),\n * { columns: { createdAt: \"created_at\", id: \"id\" } },\n * );\n *\n * const rows = await q.limit(limit);\n * ```\n *\n * @throws If `options.columns` is missing a key or contains an unsafe SQL\n * identifier.\n */\nexport function applyKeysetToKnex<TQuery extends KnexKeysetQuery>(\n query: TQuery,\n where: KeysetWhere | null,\n order: KeysetSortKey[],\n options: ApplyKeysetToKnexOptions,\n): TQuery {\n const whereSql = toKeysetWhereSql(where, options.columns, {\n paramStart: options.paramStart,\n placeholder: options.placeholder ?? (() => \"?\"),\n });\n if (whereSql.sql) {\n query.whereRaw(whereSql.sql, whereSql.params);\n }\n query.orderByRaw(toKeysetOrderBySql(order, options.columns));\n return query;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,SAAgB,kBACd,OACA,OACA,OACA,SACQ;CACR,MAAM,WAAWA,eAAAA,iBAAiB,OAAO,QAAQ,SAAS;EACxD,YAAY,QAAQ;EACpB,aAAa,QAAQ,sBAAsB;EAC5C,CAAC;CACF,IAAI,SAAS,KACX,MAAM,SAAS,SAAS,KAAK,SAAS,OAAO;CAE/C,MAAM,WAAWC,eAAAA,mBAAmB,OAAO,QAAQ,QAAQ,CAAC;CAC5D,OAAO"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { c as KeysetSqlColumns, d as KeysetWhere, o as KeysetSortKey, p as ToKeysetSqlOptions } from "./keyset-DqmiNJog.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/pagination-knex.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Minimal structural subset of a Knex query builder used by
|
|
6
|
+
* {@link applyKeysetToKnex}.
|
|
7
|
+
*
|
|
8
|
+
* `xantiagoma/pagination/knex` does not import `knex`; any object with these
|
|
9
|
+
* methods works, which keeps Knex as your application's dependency rather than
|
|
10
|
+
* a peer dependency of this package.
|
|
11
|
+
*/
|
|
12
|
+
type KnexKeysetQuery = {
|
|
13
|
+
/** Apply a raw `WHERE` clause with bind parameters. */whereRaw(sql: string, bindings?: unknown[]): unknown; /** Apply a raw `ORDER BY` clause. */
|
|
14
|
+
orderByRaw(sql: string): unknown;
|
|
15
|
+
};
|
|
16
|
+
/** Options for {@link applyKeysetToKnex}. */
|
|
17
|
+
type ApplyKeysetToKnexOptions = ToKeysetSqlOptions & {
|
|
18
|
+
/** Logical key → SQL identifier/expression. Must be server-owned, not request data. */columns: KeysetSqlColumns;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Apply a portable keyset `WHERE` + `ORDER BY` to a Knex query builder.
|
|
22
|
+
* Values are passed as bindings; column identifiers are validated by the raw
|
|
23
|
+
* SQL helpers before being rendered.
|
|
24
|
+
*
|
|
25
|
+
* This mutates and returns the provided Knex query builder, matching Knex's
|
|
26
|
+
* chaining style. For first-page cursor requests (`where === null`), it skips
|
|
27
|
+
* `whereRaw` and only applies `orderByRaw`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { createKeysetSpec } from "xantiagoma/pagination";
|
|
32
|
+
* import { applyKeysetToKnex } from "xantiagoma/pagination/knex";
|
|
33
|
+
*
|
|
34
|
+
* const keyset = createKeysetSpec({
|
|
35
|
+
* sort: [
|
|
36
|
+
* { key: "createdAt", order: "asc" },
|
|
37
|
+
* { key: "id", order: "asc" },
|
|
38
|
+
* ],
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* const q = applyKeysetToKnex(
|
|
42
|
+
* knex("posts").select("*"),
|
|
43
|
+
* keyset.where(cursor, direction),
|
|
44
|
+
* keyset.orderBy(direction),
|
|
45
|
+
* { columns: { createdAt: "created_at", id: "id" } },
|
|
46
|
+
* );
|
|
47
|
+
*
|
|
48
|
+
* const rows = await q.limit(limit);
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @throws If `options.columns` is missing a key or contains an unsafe SQL
|
|
52
|
+
* identifier.
|
|
53
|
+
*/
|
|
54
|
+
declare function applyKeysetToKnex<TQuery extends KnexKeysetQuery>(query: TQuery, where: KeysetWhere | null, order: KeysetSortKey[], options: ApplyKeysetToKnexOptions): TQuery;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { type ApplyKeysetToKnexOptions, type KnexKeysetQuery, applyKeysetToKnex };
|
|
57
|
+
//# sourceMappingURL=entry-pagination-knex.d.cts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { c as KeysetSqlColumns, d as KeysetWhere, o as KeysetSortKey, p as ToKeysetSqlOptions } from "./keyset-Cd8leiME.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/pagination-knex.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Minimal structural subset of a Knex query builder used by
|
|
6
|
+
* {@link applyKeysetToKnex}.
|
|
7
|
+
*
|
|
8
|
+
* `xantiagoma/pagination/knex` does not import `knex`; any object with these
|
|
9
|
+
* methods works, which keeps Knex as your application's dependency rather than
|
|
10
|
+
* a peer dependency of this package.
|
|
11
|
+
*/
|
|
12
|
+
type KnexKeysetQuery = {
|
|
13
|
+
/** Apply a raw `WHERE` clause with bind parameters. */whereRaw(sql: string, bindings?: unknown[]): unknown; /** Apply a raw `ORDER BY` clause. */
|
|
14
|
+
orderByRaw(sql: string): unknown;
|
|
15
|
+
};
|
|
16
|
+
/** Options for {@link applyKeysetToKnex}. */
|
|
17
|
+
type ApplyKeysetToKnexOptions = ToKeysetSqlOptions & {
|
|
18
|
+
/** Logical key → SQL identifier/expression. Must be server-owned, not request data. */columns: KeysetSqlColumns;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Apply a portable keyset `WHERE` + `ORDER BY` to a Knex query builder.
|
|
22
|
+
* Values are passed as bindings; column identifiers are validated by the raw
|
|
23
|
+
* SQL helpers before being rendered.
|
|
24
|
+
*
|
|
25
|
+
* This mutates and returns the provided Knex query builder, matching Knex's
|
|
26
|
+
* chaining style. For first-page cursor requests (`where === null`), it skips
|
|
27
|
+
* `whereRaw` and only applies `orderByRaw`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { createKeysetSpec } from "xantiagoma/pagination";
|
|
32
|
+
* import { applyKeysetToKnex } from "xantiagoma/pagination/knex";
|
|
33
|
+
*
|
|
34
|
+
* const keyset = createKeysetSpec({
|
|
35
|
+
* sort: [
|
|
36
|
+
* { key: "createdAt", order: "asc" },
|
|
37
|
+
* { key: "id", order: "asc" },
|
|
38
|
+
* ],
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* const q = applyKeysetToKnex(
|
|
42
|
+
* knex("posts").select("*"),
|
|
43
|
+
* keyset.where(cursor, direction),
|
|
44
|
+
* keyset.orderBy(direction),
|
|
45
|
+
* { columns: { createdAt: "created_at", id: "id" } },
|
|
46
|
+
* );
|
|
47
|
+
*
|
|
48
|
+
* const rows = await q.limit(limit);
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @throws If `options.columns` is missing a key or contains an unsafe SQL
|
|
52
|
+
* identifier.
|
|
53
|
+
*/
|
|
54
|
+
declare function applyKeysetToKnex<TQuery extends KnexKeysetQuery>(query: TQuery, where: KeysetWhere | null, order: KeysetSortKey[], options: ApplyKeysetToKnexOptions): TQuery;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { type ApplyKeysetToKnexOptions, type KnexKeysetQuery, applyKeysetToKnex };
|
|
57
|
+
//# sourceMappingURL=entry-pagination-knex.d.mts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { a as toKeysetOrderBySql, o as toKeysetWhereSql } from "./keyset-ChU9XDqc.mjs";
|
|
2
|
+
//#region src/pagination-knex.ts
|
|
3
|
+
/**
|
|
4
|
+
* Apply a portable keyset `WHERE` + `ORDER BY` to a Knex query builder.
|
|
5
|
+
* Values are passed as bindings; column identifiers are validated by the raw
|
|
6
|
+
* SQL helpers before being rendered.
|
|
7
|
+
*
|
|
8
|
+
* This mutates and returns the provided Knex query builder, matching Knex's
|
|
9
|
+
* chaining style. For first-page cursor requests (`where === null`), it skips
|
|
10
|
+
* `whereRaw` and only applies `orderByRaw`.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createKeysetSpec } from "xantiagoma/pagination";
|
|
15
|
+
* import { applyKeysetToKnex } from "xantiagoma/pagination/knex";
|
|
16
|
+
*
|
|
17
|
+
* const keyset = createKeysetSpec({
|
|
18
|
+
* sort: [
|
|
19
|
+
* { key: "createdAt", order: "asc" },
|
|
20
|
+
* { key: "id", order: "asc" },
|
|
21
|
+
* ],
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* const q = applyKeysetToKnex(
|
|
25
|
+
* knex("posts").select("*"),
|
|
26
|
+
* keyset.where(cursor, direction),
|
|
27
|
+
* keyset.orderBy(direction),
|
|
28
|
+
* { columns: { createdAt: "created_at", id: "id" } },
|
|
29
|
+
* );
|
|
30
|
+
*
|
|
31
|
+
* const rows = await q.limit(limit);
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @throws If `options.columns` is missing a key or contains an unsafe SQL
|
|
35
|
+
* identifier.
|
|
36
|
+
*/
|
|
37
|
+
function applyKeysetToKnex(query, where, order, options) {
|
|
38
|
+
const whereSql = toKeysetWhereSql(where, options.columns, {
|
|
39
|
+
paramStart: options.paramStart,
|
|
40
|
+
placeholder: options.placeholder ?? (() => "?")
|
|
41
|
+
});
|
|
42
|
+
if (whereSql.sql) query.whereRaw(whereSql.sql, whereSql.params);
|
|
43
|
+
query.orderByRaw(toKeysetOrderBySql(order, options.columns));
|
|
44
|
+
return query;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { applyKeysetToKnex };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=entry-pagination-knex.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry-pagination-knex.mjs","names":[],"sources":["../src/pagination-knex.ts"],"sourcesContent":["import type { KeysetSortKey, KeysetSqlColumns, KeysetWhere, ToKeysetSqlOptions } from \"./keyset.ts\";\nimport { toKeysetOrderBySql, toKeysetWhereSql } from \"./keyset.ts\";\n\n/**\n * Minimal structural subset of a Knex query builder used by\n * {@link applyKeysetToKnex}.\n *\n * `xantiagoma/pagination/knex` does not import `knex`; any object with these\n * methods works, which keeps Knex as your application's dependency rather than\n * a peer dependency of this package.\n */\nexport type KnexKeysetQuery = {\n /** Apply a raw `WHERE` clause with bind parameters. */\n whereRaw(sql: string, bindings?: unknown[]): unknown;\n /** Apply a raw `ORDER BY` clause. */\n orderByRaw(sql: string): unknown;\n};\n\n/** Options for {@link applyKeysetToKnex}. */\nexport type ApplyKeysetToKnexOptions = ToKeysetSqlOptions & {\n /** Logical key → SQL identifier/expression. Must be server-owned, not request data. */\n columns: KeysetSqlColumns;\n};\n\n/**\n * Apply a portable keyset `WHERE` + `ORDER BY` to a Knex query builder.\n * Values are passed as bindings; column identifiers are validated by the raw\n * SQL helpers before being rendered.\n *\n * This mutates and returns the provided Knex query builder, matching Knex's\n * chaining style. For first-page cursor requests (`where === null`), it skips\n * `whereRaw` and only applies `orderByRaw`.\n *\n * @example\n * ```ts\n * import { createKeysetSpec } from \"xantiagoma/pagination\";\n * import { applyKeysetToKnex } from \"xantiagoma/pagination/knex\";\n *\n * const keyset = createKeysetSpec({\n * sort: [\n * { key: \"createdAt\", order: \"asc\" },\n * { key: \"id\", order: \"asc\" },\n * ],\n * });\n *\n * const q = applyKeysetToKnex(\n * knex(\"posts\").select(\"*\"),\n * keyset.where(cursor, direction),\n * keyset.orderBy(direction),\n * { columns: { createdAt: \"created_at\", id: \"id\" } },\n * );\n *\n * const rows = await q.limit(limit);\n * ```\n *\n * @throws If `options.columns` is missing a key or contains an unsafe SQL\n * identifier.\n */\nexport function applyKeysetToKnex<TQuery extends KnexKeysetQuery>(\n query: TQuery,\n where: KeysetWhere | null,\n order: KeysetSortKey[],\n options: ApplyKeysetToKnexOptions,\n): TQuery {\n const whereSql = toKeysetWhereSql(where, options.columns, {\n paramStart: options.paramStart,\n placeholder: options.placeholder ?? (() => \"?\"),\n });\n if (whereSql.sql) {\n query.whereRaw(whereSql.sql, whereSql.params);\n }\n query.orderByRaw(toKeysetOrderBySql(order, options.columns));\n return query;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DA,SAAgB,kBACd,OACA,OACA,OACA,SACQ;CACR,MAAM,WAAW,iBAAiB,OAAO,QAAQ,SAAS;EACxD,YAAY,QAAQ;EACpB,aAAa,QAAQ,sBAAsB;EAC5C,CAAC;CACF,IAAI,SAAS,KACX,MAAM,SAAS,SAAS,KAAK,SAAS,OAAO;CAE/C,MAAM,WAAW,mBAAmB,OAAO,QAAQ,QAAQ,CAAC;CAC5D,OAAO"}
|