xantiagoma 0.1.1 → 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.
Files changed (100) hide show
  1. package/README.md +131 -62
  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 +98 -1
  39. package/dist/entry-react.cjs.map +1 -1
  40. package/dist/entry-react.d.cts +94 -3
  41. package/dist/entry-react.d.mts +94 -3
  42. package/dist/entry-react.mjs +98 -3
  43. package/dist/entry-react.mjs.map +1 -1
  44. package/dist/entry-sonner.cjs +1 -1
  45. package/dist/entry-sonner.d.cts +1 -1
  46. package/dist/entry-sonner.d.mts +1 -1
  47. package/dist/entry-sonner.mjs +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/entry-web.cjs +256 -0
  53. package/dist/entry-web.cjs.map +1 -1
  54. package/dist/entry-web.d.cts +139 -1
  55. package/dist/entry-web.d.mts +139 -1
  56. package/dist/entry-web.mjs +256 -1
  57. package/dist/entry-web.mjs.map +1 -1
  58. package/dist/{error-CQ6E1DMv.cjs → error-B1xdFIet.cjs} +1 -1
  59. package/dist/{error-CQ6E1DMv.cjs.map → error-B1xdFIet.cjs.map} +1 -1
  60. package/dist/{error-b0cYulof.mjs → error-B5yBe5I0.mjs} +1 -1
  61. package/dist/{error-b0cYulof.mjs.map → error-B5yBe5I0.mjs.map} +1 -1
  62. package/dist/{index-CECp5YtT.d.cts → index-BfArSGL_.d.mts} +140 -32
  63. package/dist/{index-kWrWj6TD.d.mts → index-k0rTrMol.d.cts} +140 -32
  64. package/dist/index.cjs +8 -5
  65. package/dist/index.d.cts +3 -2
  66. package/dist/index.d.mts +3 -2
  67. package/dist/index.mjs +6 -4
  68. package/dist/is-promise-BQwyCVAy.mjs +21 -0
  69. package/dist/is-promise-BQwyCVAy.mjs.map +1 -0
  70. package/dist/is-promise-Dle75dtS.cjs +26 -0
  71. package/dist/is-promise-Dle75dtS.cjs.map +1 -0
  72. package/dist/keyset-BiguUVI7.cjs +280 -0
  73. package/dist/keyset-BiguUVI7.cjs.map +1 -0
  74. package/dist/keyset-Cd8leiME.d.mts +512 -0
  75. package/dist/keyset-ChU9XDqc.mjs +245 -0
  76. package/dist/keyset-ChU9XDqc.mjs.map +1 -0
  77. package/dist/keyset-DqmiNJog.d.cts +512 -0
  78. package/dist/resolve-maybe-promise-4f-L5bxX.mjs +30 -0
  79. package/dist/resolve-maybe-promise-4f-L5bxX.mjs.map +1 -0
  80. package/dist/resolve-maybe-promise-DfTYDaO9.cjs +41 -0
  81. package/dist/resolve-maybe-promise-DfTYDaO9.cjs.map +1 -0
  82. package/dist/{src-D6TCFUzZ.mjs → src-CdaMpw6F.mjs} +46 -64
  83. package/dist/src-CdaMpw6F.mjs.map +1 -0
  84. package/dist/{src-BaG3gXLj.cjs → src-DD2TTZvg.cjs} +46 -76
  85. package/dist/src-DD2TTZvg.cjs.map +1 -0
  86. package/dist/try-catch-B9NlJqqx.mjs +35 -0
  87. package/dist/try-catch-B9NlJqqx.mjs.map +1 -0
  88. package/dist/try-catch-BDA5OPzr.cjs +46 -0
  89. package/dist/try-catch-BDA5OPzr.cjs.map +1 -0
  90. package/dist/types-DDbcxhql.d.mts +8 -0
  91. package/dist/types-i-wXHPp4.d.cts +8 -0
  92. package/docs/PAGINATION.md +1788 -0
  93. package/docs/sync-async-adaptive.md +283 -0
  94. package/package.json +73 -2
  95. package/dist/src-BaG3gXLj.cjs.map +0 -1
  96. package/dist/src-D6TCFUzZ.mjs.map +0 -1
  97. package/dist/try-catch-D2fA1iDo.mjs +0 -66
  98. package/dist/try-catch-D2fA1iDo.mjs.map +0 -1
  99. package/dist/try-catch-lfQywqx3.cjs +0 -77
  100. 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 | 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` | StreamRenderer + useStream hook | `react`, `@tanstack/react-query` |
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,75 +48,138 @@ 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(promise)` | Async `[data, error]` tupleno try/catch needed |
48
- | `tryCatchSync(fn)` | Sync `[data, error]` tuple |
49
- | `assertNotNull(value)` | Throws if null/undefined, narrows type |
50
- | `valueOrThrow(value, error)` | Returns value or throws |
51
- | `AssertError` | Custom error class for assertions |
51
+ | Export | Description | Source | Tests |
52
+ | --------------------------- | -------------------------------------------- | ------------------------------- | --------------------------------------- |
53
+ | `tryCatch` | `[data, error]` tuplessync/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) |
52
58
 
53
59
  ### Async
54
60
 
55
- | Export | Description |
56
- | ---------------------------- | ----------------------------------------- |
57
- | `wait(ms)` | Typed setTimeout delay |
58
- | `Completer` | Externally resolvable Promise (like Dart) |
59
- | `collect(iterable)` | Drain `AsyncIterable<T>` into `T[]` |
60
- | `asyncOf(...values)` | Create `AsyncGenerator` from values |
61
- | `AsyncChannel` | Push-based `AsyncIterable` with modes |
62
- | `resolveMaybePromise(value)` | Resolve `T \| Promise<T>` → `Promise<T>` |
61
+ | Export | Description | Source | Tests |
62
+ | --------------------- | ---------------------------------------- | ------------------------------------- | --------------------------------------------- |
63
+ | `wait` | Typed setTimeout delay | [src](./src/wait.ts) | [tests](./test/wait.test.ts) |
64
+ | `Completer` | Externally resolvable Promise | [src](./src/completer.ts) | [tests](./test/completer.test.ts) |
65
+ | `collect` | Drain any iterable into `T[]` (adaptive) | [src](./src/collect.ts) | [tests](./test/collect.test.ts) |
66
+ | `asyncOf` | Create `AsyncGenerator` from values | [src](./src/async-of.ts) | [tests](./test/async-of.test.ts) |
67
+ | `AsyncChannel` | Push-based `AsyncIterable` with modes | [src](./src/async-channel.ts) | [tests](./test/async-channel.test.ts) |
68
+ | `resolveMaybePromise` | Resolve `T \| Promise<T>` → `Promise<T>` | [src](./src/resolve-maybe-promise.ts) | [tests](./test/resolve-maybe-promise.test.ts) |
63
69
 
64
70
  ### Iterables & Generators
65
71
 
66
- | Export | Description |
67
- | ------------------------------ | ------------------------------------------ |
68
- | `range(start, end, step?)` | Numeric range as array |
69
- | `rangeLazy(start, end, step?)` | Numeric range as generator |
70
- | `enumerate(iterable)` | `[index, value]` tuples (sync) |
71
- | `enumerateAsync(iterable)` | `[index, value]` tuples (async) |
72
- | `toIterator(iterable)` | Normalize to `Iterator` |
73
- | `toAsyncIterable(source)` | Normalize any iterable to `AsyncGenerator` |
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) |
74
79
 
75
80
  ### Type Guards
76
81
 
77
- | Export | Description |
78
- | -------------------------- | ------------------------------------------- |
79
- | `isPromise(value)` | Check for promise-like |
80
- | `isIterable(value)` | Check for `Iterable` |
81
- | `isAsyncIterable(value)` | Check for `AsyncIterable` |
82
- | `isIterator(value)` | Check for `Iterator` |
83
- | `isGenerator(value)` | Check for `Generator` |
84
- | `isAsyncGenerator(value)` | Check for `AsyncGenerator` |
85
- | `isDisposable(value)` | Check for `Disposable` or `AsyncDisposable` |
86
- | `isAsyncDisposable(value)` | Check for `AsyncDisposable` |
87
- | `isSyncDisposable(value)` | Check for `Disposable` |
82
+ | Export | Description | Source | Tests |
83
+ | --------------------------------------------------------- | ---------------------- | ---------------------------------- | ------------------------------------------ |
84
+ | `isPromise` | Promise-like check | [src](./src/is-promise.ts) | [tests](./test/is-promise.test.ts) |
85
+ | `isIterable` | `Iterable` check | [src](./src/is-iterable.ts) | [tests](./test/is-iterable.test.ts) |
86
+ | `isAsyncIterable` | `AsyncIterable` check | [src](./src/is-async-iterable.ts) | [tests](./test/is-async-iterable.test.ts) |
87
+ | `isIterator` | `Iterator` check | [src](./src/is-iterator.ts) | [tests](./test/is-iterator.test.ts) |
88
+ | `isGenerator` | `Generator` check | [src](./src/is-generator.ts) | [tests](./test/is-generator.test.ts) |
89
+ | `isAsyncGenerator` | `AsyncGenerator` check | [src](./src/is-async-generator.ts) | [tests](./test/is-async-generator.test.ts) |
90
+ | `isDisposable` / `isAsyncDisposable` / `isSyncDisposable` | Disposable checks | [src](./src/is-disposable.ts) | [tests](./test/is-disposable.test.ts) |
88
91
 
89
92
  ### Disposable Utilities
90
93
 
91
- | Export | Description |
92
- | --------------------- | --------------------------------------- |
93
- | `defer(fn)` | Cancellable `await using` disposable |
94
- | `deferSync(fn)` | Cancellable `using` disposable |
95
- | `makeDisposable(obj)` | Add `Symbol.asyncDispose` to any object |
94
+ | Export | Description | Source | Tests |
95
+ | --------------------- | --------------------------------------- | ------------------------------- | --------------------------------------- |
96
+ | `defer` / `deferSync` | Cancellable `using`/`await using` | [src](./src/defer.ts) | [tests](./test/defer.test.ts) |
97
+ | `makeDisposable` | Add `Symbol.asyncDispose` to any object | [src](./src/make-disposable.ts) | [tests](./test/make-disposable.test.ts) |
96
98
 
97
99
  ### Strings
98
100
 
99
- | Export | Description |
100
- | --------------------------- | ----------------------------- |
101
- | `ensureString(value)` | Coerce to string |
102
- | `naturalSortCompare(a, b)` | Natural sort comparator |
103
- | `jaroWinklerDistance(a, b)` | Fuzzy string similarity (0-1) |
101
+ | Export | Description | Source | Tests |
102
+ | ------------------------------------------------------------- | ---------------- | ---------------------- | ------------------------------ |
103
+ | `ensureString` / `naturalSortCompare` / `jaroWinklerDistance` | String utilities | [src](./src/string.ts) | [tests](./test/string.test.ts) |
104
104
 
105
105
  ### Misc
106
106
 
107
- | Export | Description |
108
- | ------------------------------------------- | --------------------------------------- |
109
- | `cast<T>(value)` | Unsafe `as T` type cast |
110
- | `log(value)` | `console.log` that returns its argument |
111
- | `prepareLoaderResult(rows, keys)` | Map DB rows to DataLoader key order |
112
- | `resolveStreamSource(source)` | Resolve `StreamSource<T>` |
113
- | `secondsToMs` / `minutesToMs` / `hoursToMs` | Time unit converters |
107
+ | Export | Description | Source | Tests |
108
+ | ------------------------------------------- | --------------------------------------- | ------------------------------------- | --------------------------------------------- |
109
+ | `cast<T>` | Unsafe `as T` type cast | [src](./src/cast.ts) | [tests](./test/cast.test.ts) |
110
+ | `log` | `console.log` that returns its argument | [src](./src/log.ts) | [tests](./test/log.test.ts) |
111
+ | `prepareLoaderResult` | Map DB rows to DataLoader key order | [src](./src/prepare-loader-result.ts) | [tests](./test/prepare-loader-result.test.ts) |
112
+ | `resolveStreamSource` | Resolve `StreamSource<T>` | [src](./src/stream-source.ts) | [tests](./test/stream-source.test.ts) |
113
+ | `secondsToMs` / `minutesToMs` / `hoursToMs` | Time unit converters | [src](./src/time-convert.ts) | [tests](./test/time-convert.test.ts) |
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
+
167
+ ## Web Utilities (`xantiagoma/web`)
168
+
169
+ | Export | Description | Source | Tests |
170
+ | ----------------------- | ----------------------------------- | ----------------------------------------- | ------------------------------------------- |
171
+ | `formDataToObject` | `FormData` → plain object | [src](./src/form-data-to-object-utils.ts) | [tests](./test/form-data-to-object.test.ts) |
172
+ | `fetchWithProgress` | Fetch with upload/download progress | [src](./src/fetch-with-progress.ts) | [tests](./test/fetch-with-progress.test.ts) |
173
+ | `createHttpInterceptor` | Intercept fetch + XHR with rules | [src](./src/intercept-http.ts) | [tests](./test/intercept-http.test.tsx) |
174
+
175
+ ## React Utilities (`xantiagoma/react`)
176
+
177
+ | Export | Description | Source | Tests |
178
+ | ------------------------------ | ----------------------------------- | -------------------------------------- | ----------------------------------------------- |
179
+ | `Providers` / `provider` | Compose providers without nesting | [src](./src/providers.tsx) | [tests](./test/providers.test.tsx) |
180
+ | `usePreventAutoFocus` | Prevent auto-focus in modals | [src](./src/use-prevent-auto-focus.ts) | [tests](./test/use-prevent-auto-focus.test.tsx) |
181
+ | `useDynamicRefs` | Dynamic ref registry by key | [src](./src/use-dynamic-refs.ts) | [tests](./test/use-dynamic-refs.test.tsx) |
182
+ | `useStream` / `StreamRenderer` | Stream consumption hook + component | [src](./src/stream-renderer.tsx) | [tests](./test/stream-renderer.test.tsx) |
114
183
 
115
184
  ## Recommended Libraries
116
185
 
@@ -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