xantiagoma 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +117 -24
  2. package/dist/entry-pagination-drizzle.cjs +61 -0
  3. package/dist/entry-pagination-drizzle.cjs.map +1 -0
  4. package/dist/entry-pagination-drizzle.d.cts +38 -0
  5. package/dist/entry-pagination-drizzle.d.mts +38 -0
  6. package/dist/entry-pagination-drizzle.mjs +59 -0
  7. package/dist/entry-pagination-drizzle.mjs.map +1 -0
  8. package/dist/entry-pagination-knex.cjs +50 -0
  9. package/dist/entry-pagination-knex.cjs.map +1 -0
  10. package/dist/entry-pagination-knex.d.cts +57 -0
  11. package/dist/entry-pagination-knex.d.mts +57 -0
  12. package/dist/entry-pagination-knex.mjs +49 -0
  13. package/dist/entry-pagination-knex.mjs.map +1 -0
  14. package/dist/entry-pagination-kysely.cjs +64 -0
  15. package/dist/entry-pagination-kysely.cjs.map +1 -0
  16. package/dist/entry-pagination-kysely.d.cts +40 -0
  17. package/dist/entry-pagination-kysely.d.mts +40 -0
  18. package/dist/entry-pagination-kysely.mjs +62 -0
  19. package/dist/entry-pagination-kysely.mjs.map +1 -0
  20. package/dist/entry-pagination-mongo.cjs +68 -0
  21. package/dist/entry-pagination-mongo.cjs.map +1 -0
  22. package/dist/entry-pagination-mongo.d.cts +51 -0
  23. package/dist/entry-pagination-mongo.d.mts +51 -0
  24. package/dist/entry-pagination-mongo.mjs +66 -0
  25. package/dist/entry-pagination-mongo.mjs.map +1 -0
  26. package/dist/entry-pagination-prisma.cjs +64 -0
  27. package/dist/entry-pagination-prisma.cjs.map +1 -0
  28. package/dist/entry-pagination-prisma.d.cts +49 -0
  29. package/dist/entry-pagination-prisma.d.mts +49 -0
  30. package/dist/entry-pagination-prisma.mjs +62 -0
  31. package/dist/entry-pagination-prisma.mjs.map +1 -0
  32. package/dist/entry-pagination.cjs +401 -0
  33. package/dist/entry-pagination.cjs.map +1 -0
  34. package/dist/entry-pagination.d.cts +120 -0
  35. package/dist/entry-pagination.d.mts +120 -0
  36. package/dist/entry-pagination.mjs +384 -0
  37. package/dist/entry-pagination.mjs.map +1 -0
  38. package/dist/entry-react.cjs +1 -1
  39. package/dist/entry-react.d.cts +1 -1
  40. package/dist/entry-react.d.mts +1 -1
  41. package/dist/entry-react.mjs +1 -1
  42. package/dist/entry-sonner.cjs +58 -1
  43. package/dist/entry-sonner.cjs.map +1 -1
  44. package/dist/entry-sonner.d.cts +87 -9
  45. package/dist/entry-sonner.d.mts +87 -9
  46. package/dist/entry-sonner.mjs +58 -2
  47. package/dist/entry-sonner.mjs.map +1 -1
  48. package/dist/entry-temporal.cjs +1 -1
  49. package/dist/entry-temporal.mjs +1 -1
  50. package/dist/entry-ulid.cjs +1 -1
  51. package/dist/entry-ulid.mjs +1 -1
  52. package/dist/{error-CQ6E1DMv.cjs → error-B1xdFIet.cjs} +1 -1
  53. package/dist/{error-CQ6E1DMv.cjs.map → error-B1xdFIet.cjs.map} +1 -1
  54. package/dist/{error-b0cYulof.mjs → error-B5yBe5I0.mjs} +1 -1
  55. package/dist/{error-b0cYulof.mjs.map → error-B5yBe5I0.mjs.map} +1 -1
  56. package/dist/{index-CECp5YtT.d.cts → index-BfArSGL_.d.mts} +140 -32
  57. package/dist/{index-kWrWj6TD.d.mts → index-k0rTrMol.d.cts} +140 -32
  58. package/dist/index.cjs +8 -5
  59. package/dist/index.d.cts +3 -2
  60. package/dist/index.d.mts +3 -2
  61. package/dist/index.mjs +6 -4
  62. package/dist/is-promise-BQwyCVAy.mjs +21 -0
  63. package/dist/is-promise-BQwyCVAy.mjs.map +1 -0
  64. package/dist/is-promise-Dle75dtS.cjs +26 -0
  65. package/dist/is-promise-Dle75dtS.cjs.map +1 -0
  66. package/dist/keyset-BiguUVI7.cjs +280 -0
  67. package/dist/keyset-BiguUVI7.cjs.map +1 -0
  68. package/dist/keyset-Cd8leiME.d.mts +512 -0
  69. package/dist/keyset-ChU9XDqc.mjs +245 -0
  70. package/dist/keyset-ChU9XDqc.mjs.map +1 -0
  71. package/dist/keyset-DqmiNJog.d.cts +512 -0
  72. package/dist/resolve-maybe-promise-4f-L5bxX.mjs +30 -0
  73. package/dist/resolve-maybe-promise-4f-L5bxX.mjs.map +1 -0
  74. package/dist/resolve-maybe-promise-DfTYDaO9.cjs +41 -0
  75. package/dist/resolve-maybe-promise-DfTYDaO9.cjs.map +1 -0
  76. package/dist/{src-D6TCFUzZ.mjs → src-CdaMpw6F.mjs} +46 -64
  77. package/dist/src-CdaMpw6F.mjs.map +1 -0
  78. package/dist/{src-BaG3gXLj.cjs → src-DD2TTZvg.cjs} +46 -76
  79. package/dist/src-DD2TTZvg.cjs.map +1 -0
  80. package/dist/try-catch-B9NlJqqx.mjs +35 -0
  81. package/dist/try-catch-B9NlJqqx.mjs.map +1 -0
  82. package/dist/try-catch-BDA5OPzr.cjs +46 -0
  83. package/dist/try-catch-BDA5OPzr.cjs.map +1 -0
  84. package/dist/types-DDbcxhql.d.mts +8 -0
  85. package/dist/types-i-wXHPp4.d.cts +8 -0
  86. package/docs/PAGINATION.md +1788 -0
  87. package/docs/RELEASING.md +109 -0
  88. package/docs/sync-async-adaptive.md +283 -0
  89. package/package.json +72 -1
  90. package/dist/src-BaG3gXLj.cjs.map +0 -1
  91. package/dist/src-D6TCFUzZ.mjs.map +0 -1
  92. package/dist/try-catch-D2fA1iDo.mjs +0 -66
  93. package/dist/try-catch-D2fA1iDo.mjs.map +0 -1
  94. package/dist/try-catch-lfQywqx3.cjs +0 -77
  95. package/dist/try-catch-lfQywqx3.cjs.map +0 -1
package/README.md CHANGED
@@ -22,19 +22,27 @@
22
22
  npm install xantiagoma
23
23
  ```
24
24
 
25
+ Release process notes live in [docs/RELEASING.md](./docs/RELEASING.md).
26
+
25
27
  ## Entry Points
26
28
 
27
- | Import | Description | Dependencies |
28
- | ----------------------- | -------------------------------------- | -------------------------------- |
29
- | `xantiagoma` | Core utilities (isomorphic, zero deps) | none |
30
- | `xantiagoma/web` | Browser/FormData utilities | none |
31
- | `xantiagoma/ulid` | Prefixed ULID generation + helpers | `ulid` |
32
- | `xantiagoma/temporal` | Date/time/duration with Temporal API | `temporal-polyfill`, `itty-time` |
33
- | `xantiagoma/dataloader` | DataLoader factory | `dataloader` |
34
- | `xantiagoma/unstorage` | Cache helpers with unstorage | `unstorage`, `ohash` |
35
- | `xantiagoma/valibot` | TimeZone validation schema | `valibot` |
36
- | `xantiagoma/sonner` | Toast streaming for iterables | `sonner`, `react` |
37
- | `xantiagoma/react` | React hooks + components | `react`, `@tanstack/react-query` |
29
+ | Import | Description | Dependencies |
30
+ | ------------------------------- | -------------------------------------- | -------------------------------- |
31
+ | `xantiagoma` | Core utilities (isomorphic, zero deps) | none |
32
+ | `xantiagoma/pagination` | Pagination + keyset helpers | none |
33
+ | `xantiagoma/pagination/drizzle` | Drizzle adapter for pagination keysets | `drizzle-orm` |
34
+ | `xantiagoma/pagination/kysely` | Kysely adapter for pagination keysets | `kysely` |
35
+ | `xantiagoma/pagination/knex` | Knex adapter for pagination keysets | none |
36
+ | `xantiagoma/pagination/mongo` | Mongo/Mongoose adapter for keysets | none |
37
+ | `xantiagoma/pagination/prisma` | Prisma adapter for pagination keysets | none |
38
+ | `xantiagoma/web` | Browser/FormData utilities | none |
39
+ | `xantiagoma/ulid` | Prefixed ULID generation + helpers | `ulid` |
40
+ | `xantiagoma/temporal` | Date/time/duration with Temporal API | `temporal-polyfill`, `itty-time` |
41
+ | `xantiagoma/dataloader` | DataLoader factory | `dataloader` |
42
+ | `xantiagoma/unstorage` | Cache helpers with unstorage | `unstorage`, `ohash` |
43
+ | `xantiagoma/valibot` | TimeZone validation schema | `valibot` |
44
+ | `xantiagoma/sonner` | Toast streaming for iterables | `sonner`, `react` |
45
+ | `xantiagoma/react` | React hooks + components | `react`, `@tanstack/react-query` |
38
46
 
39
47
  Sub-entry dependencies are **optional peer deps** — only install what you use.
40
48
 
@@ -42,12 +50,13 @@ Sub-entry dependencies are **optional peer deps** — only install what you use.
42
50
 
43
51
  ### Error Handling
44
52
 
45
- | Export | Description | Source | Tests |
46
- | --------------------------- | -------------------------------------- | ------------------------------- | --------------------------------------- |
47
- | `tryCatch` / `tryCatchSync` | `[data, error]` tuples — no try/catch | [src](./src/try-catch.ts) | [tests](./test/try-catch.test.ts) |
48
- | `assertNotNull` | Throws if null/undefined, narrows type | [src](./src/assert-not-null.ts) | [tests](./test/assert-not-null.test.ts) |
49
- | `valueOrThrow` | Returns value or throws | [src](./src/error.ts) | [tests](./test/error.test.ts) |
50
- | `AssertError` | Custom error class | [src](./src/errors.ts) | [tests](./test/errors.test.ts) |
53
+ | Export | Description | Source | Tests |
54
+ | --------------------------- | -------------------------------------------- | ------------------------------- | --------------------------------------- |
55
+ | `tryCatch` | `[data, error]` tuples — sync/async adaptive | [src](./src/try-catch.ts) | [tests](./test/try-catch.test.ts) |
56
+ | `tryCatchSync` (deprecated) | Use `tryCatch` kept for backwards compat | [src](./src/try-catch.ts) | [tests](./test/try-catch.test.ts) |
57
+ | `assertNotNull` | Throws if null/undefined, narrows type | [src](./src/assert-not-null.ts) | [tests](./test/assert-not-null.test.ts) |
58
+ | `valueOrThrow` | Returns value or throws | [src](./src/error.ts) | [tests](./test/error.test.ts) |
59
+ | `AssertError` | Custom error class | [src](./src/errors.ts) | [tests](./test/errors.test.ts) |
51
60
 
52
61
  ### Async
53
62
 
@@ -55,19 +64,20 @@ Sub-entry dependencies are **optional peer deps** — only install what you use.
55
64
  | --------------------- | ---------------------------------------- | ------------------------------------- | --------------------------------------------- |
56
65
  | `wait` | Typed setTimeout delay | [src](./src/wait.ts) | [tests](./test/wait.test.ts) |
57
66
  | `Completer` | Externally resolvable Promise | [src](./src/completer.ts) | [tests](./test/completer.test.ts) |
58
- | `collect` | Drain `AsyncIterable<T>` into `T[]` | [src](./src/collect.ts) | [tests](./test/collect.test.ts) |
67
+ | `collect` | Drain any iterable into `T[]` (adaptive) | [src](./src/collect.ts) | [tests](./test/collect.test.ts) |
59
68
  | `asyncOf` | Create `AsyncGenerator` from values | [src](./src/async-of.ts) | [tests](./test/async-of.test.ts) |
60
69
  | `AsyncChannel` | Push-based `AsyncIterable` with modes | [src](./src/async-channel.ts) | [tests](./test/async-channel.test.ts) |
61
70
  | `resolveMaybePromise` | Resolve `T \| Promise<T>` → `Promise<T>` | [src](./src/resolve-maybe-promise.ts) | [tests](./test/resolve-maybe-promise.test.ts) |
62
71
 
63
72
  ### Iterables & Generators
64
73
 
65
- | Export | Description | Source | Tests |
66
- | ------------------------------ | --------------------------------- | --------------------------------- | ----------------------------------------- |
67
- | `range` / `rangeLazy` | Numeric range (array / generator) | [src](./src/range.ts) | [tests](./test/range.test.ts) |
68
- | `enumerate` / `enumerateAsync` | `[index, value]` tuples | [src](./src/enumerate.ts) | [tests](./test/enumerate.test.ts) |
69
- | `toIterator` | Normalize to `Iterator` | [src](./src/to-iterator.ts) | [tests](./test/to-iterator.test.ts) |
70
- | `toAsyncIterable` | Normalize to `AsyncGenerator` | [src](./src/to-async-iterable.ts) | [tests](./test/to-async-iterable.test.ts) |
74
+ | Export | Description | Source | Tests |
75
+ | --------------------- | --------------------------------------------------- | --------------------------------- | ----------------------------------------- |
76
+ | `range` / `rangeLazy` | Numeric range (array / generator) | [src](./src/range.ts) | [tests](./test/range.test.ts) |
77
+ | `enumerate` | `[index, value]` tuples — sync/async adaptive | [src](./src/enumerate.ts) | [tests](./test/enumerate.test.ts) |
78
+ | `enumerateAsync` | Always-async; awaits Promise values in sync sources | [src](./src/enumerate.ts) | [tests](./test/enumerate.test.ts) |
79
+ | `toIterator` | Normalize to `Iterator` | [src](./src/to-iterator.ts) | [tests](./test/to-iterator.test.ts) |
80
+ | `toAsyncIterable` | Normalize to `AsyncGenerator` | [src](./src/to-async-iterable.ts) | [tests](./test/to-async-iterable.test.ts) |
71
81
 
72
82
  ### Type Guards
73
83
 
@@ -104,6 +114,58 @@ Sub-entry dependencies are **optional peer deps** — only install what you use.
104
114
  | `resolveStreamSource` | Resolve `StreamSource<T>` | [src](./src/stream-source.ts) | [tests](./test/stream-source.test.ts) |
105
115
  | `secondsToMs` / `minutesToMs` / `hoursToMs` | Time unit converters | [src](./src/time-convert.ts) | [tests](./test/time-convert.test.ts) |
106
116
 
117
+ ## Pagination Utilities (`xantiagoma/pagination`)
118
+
119
+ 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.
120
+
121
+ 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).
122
+
123
+ | Import | Export | Description | Source | Tests |
124
+ | ------------------------------- | --------------------------- | -------------------------------------------------------------------------- | ---------------------------------- | ------------------------------------------ |
125
+ | `xantiagoma/pagination` | `createPaginator` | Paginator over user-provided fetchers; `pages()`/`items()` async iteration | [src](./src/pagination.ts) | [tests](./test/pagination.test.ts) |
126
+ | `xantiagoma/pagination` | `toOffsetWindow` | Page/offset input → `{ limit, offset }` | [src](./src/pagination.ts) | [tests](./test/pagination.test.ts) |
127
+ | `xantiagoma/pagination` | `createCursorCodec` | Pluggable opaque-cursor codec (JSON + base64url) | [src](./src/cursor-codec.ts) | [tests](./test/cursor-codec.test.ts) |
128
+ | `xantiagoma/pagination` | `createKeysetSpec` | Portable keyset `where()`/`orderBy()` AST | [src](./src/keyset.ts) | [tests](./test/keyset.test.ts) |
129
+ | `xantiagoma/pagination` | `keysetSqlExpression` | Mark server-owned computed SQL expressions for raw keyset helpers | [src](./src/keyset.ts) | [tests](./test/keyset.test.ts) |
130
+ | `xantiagoma/pagination` | `toKeysetWhereSql` | Keyset `WHERE` → parameterized SQL + params | [src](./src/keyset.ts) | [tests](./test/keyset.test.ts) |
131
+ | `xantiagoma/pagination` | `toKeysetOrderBySql` | Keyset order → SQL `ORDER BY` fragment | [src](./src/keyset.ts) | [tests](./test/keyset.test.ts) |
132
+ | `xantiagoma/pagination/drizzle` | `toDrizzleKeyset` | Keyset AST → Drizzle `where`/`orderBy` helpers | [src](./src/pagination-drizzle.ts) | [tests](./test/pagination-drizzle.test.ts) |
133
+ | `xantiagoma/pagination/kysely` | `toKyselyKeyset` | Keyset AST → Kysely `where`/`orderBy` helpers | [src](./src/pagination-kysely.ts) | [tests](./test/pagination-kysely.test.ts) |
134
+ | `xantiagoma/pagination/knex` | `applyKeysetToKnex` | Apply keyset AST to Knex `whereRaw`/`orderByRaw` | [src](./src/pagination-knex.ts) | [tests](./test/pagination-knex.test.ts) |
135
+ | `xantiagoma/pagination/mongo` | `toMongoKeyset` | Keyset AST → Mongo/Mongoose `filter`/`sort` objects | [src](./src/pagination-mongo.ts) | [tests](./test/pagination-mongo.test.ts) |
136
+ | `xantiagoma/pagination/prisma` | `toPrismaKeyset` | Keyset AST → Prisma `where`/`orderBy` objects | [src](./src/pagination-prisma.ts) | [tests](./test/pagination-prisma.test.ts) |
137
+ | `xantiagoma/pagination` | `parsePaginationParams` | Query params → `PaginationInput` (with clamping) | [src](./src/pagination-params.ts) | [tests](./test/pagination-params.test.ts) |
138
+ | `xantiagoma/pagination` | `fromRelayArgs` | Relay `first`/`after`/`last`/`before` → input | [src](./src/pagination-params.ts) | [tests](./test/pagination-params.test.ts) |
139
+ | `xantiagoma/pagination` | `toRelayConnection` | Result → Relay connection (`edges`, `pageInfo`) | [src](./src/pagination-output.ts) | [tests](./test/pagination-output.test.ts) |
140
+ | `xantiagoma/pagination` | `toRestEnvelope` | Result → `{ data, meta }` REST envelope | [src](./src/pagination-output.ts) | [tests](./test/pagination-output.test.ts) |
141
+ | `xantiagoma/pagination` | `infinitePaginationOptions` | TanStack `useInfiniteQuery` config (dep-free) | [src](./src/pagination-output.ts) | [tests](./test/pagination-output.test.ts) |
142
+
143
+ ```ts
144
+ import {
145
+ createPaginator,
146
+ fromRelayArgs,
147
+ parsePaginationParams,
148
+ toRestEnvelope,
149
+ } from "xantiagoma/pagination";
150
+
151
+ const paginator = createPaginator({
152
+ fetchOffset: ({ limit, offset }) => ({ items: db.query(/* LIMIT/OFFSET */) }),
153
+ fetchCursor: ({ limit, cursor, direction }) => ({ items: db.query(/* keyset */) }),
154
+ cursor: { fromItem: (u) => ({ id: u.id }) }, // codec optional — defaults to JSON + base64url
155
+ maxLimit: 100,
156
+ });
157
+
158
+ // REST route
159
+ const result = await paginator.paginate(parsePaginationParams(url.searchParams));
160
+ return Response.json(toRestEnvelope(result));
161
+
162
+ // GraphQL/Relay resolver
163
+ const result = await paginator.paginate(fromRelayArgs(args));
164
+ return toRelayConnection(result, paginator.cursorFor);
165
+ ```
166
+
167
+ 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.
168
+
107
169
  ## Web Utilities (`xantiagoma/web`)
108
170
 
109
171
  | Export | Description | Source | Tests |
@@ -112,6 +174,37 @@ Sub-entry dependencies are **optional peer deps** — only install what you use.
112
174
  | `fetchWithProgress` | Fetch with upload/download progress | [src](./src/fetch-with-progress.ts) | [tests](./test/fetch-with-progress.test.ts) |
113
175
  | `createHttpInterceptor` | Intercept fetch + XHR with rules | [src](./src/intercept-http.ts) | [tests](./test/intercept-http.test.tsx) |
114
176
 
177
+ ## Sonner Utilities (`xantiagoma/sonner`)
178
+
179
+ Toast helpers for streaming iterables/generators through [Sonner](https://sonner.emilkowal.ski/).
180
+
181
+ | Export | Description | Source | Tests |
182
+ | ------------------ | ----------------------------------------------------------------------- | ---------------------------- | ------------------------------------ |
183
+ | `toastStream` | Blocking/awaitable stream toast; resolves to `{ items, returnValue }` | [src](./src/toast-stream.ts) | [tests](./test/toast-stream.test.ts) |
184
+ | `toastStreamAsync` | Non-blocking stream toast; returns toast id immediately with `unwrap()` | [src](./src/toast-stream.ts) | [tests](./test/toast-stream.test.ts) |
185
+
186
+ Use `toastStream` when the caller should wait for completion:
187
+
188
+ ```ts
189
+ import { toastStream } from "xantiagoma/sonner";
190
+
191
+ const { items, returnValue } = await toastStream(source, {
192
+ loading: "Loading...",
193
+ streaming: ({ count }) => `Received ${count}`,
194
+ success: ({ count }) => `Done: ${count} items`,
195
+ });
196
+ ```
197
+
198
+ Use `toastStreamAsync` when the caller should continue immediately, matching
199
+ Sonner's `toast.promise(...).unwrap()` style:
200
+
201
+ ```ts
202
+ import { toastStreamAsync } from "xantiagoma/sonner";
203
+
204
+ const toastId = toastStreamAsync(source, { loading: "Loading..." });
205
+ const { items, returnValue } = await toastId.unwrap();
206
+ ```
207
+
115
208
  ## React Utilities (`xantiagoma/react`)
116
209
 
117
210
  | 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