@zudojs/database 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +223 -17
  3. package/dist/cache/cache.memory.d.ts +167 -0
  4. package/dist/cache/cache.memory.js +186 -40
  5. package/dist/cache/index.d.ts +2 -2
  6. package/dist/cache/index.js +2 -2
  7. package/dist/database/database.core.d.ts +91 -0
  8. package/dist/database/database.core.js +21 -4
  9. package/dist/databaseClient/databaseClient.core.d.ts +165 -14
  10. package/dist/databaseClient/databaseClient.core.js +304 -70
  11. package/dist/databaseClient/databaseClient.errors.d.ts +112 -0
  12. package/dist/databaseClient/databaseClient.errors.js +425 -0
  13. package/dist/databaseClient/index.d.ts +2 -1
  14. package/dist/databaseClient/index.js +2 -1
  15. package/dist/databaseConnection/databaseConnection.manager.d.ts +163 -0
  16. package/dist/databaseConnection/databaseConnection.manager.js +138 -76
  17. package/dist/databaseConnection/index.d.ts +1 -1
  18. package/dist/databaseType/databaseType.type.d.ts +224 -0
  19. package/dist/databaseType/databaseType.type.js +0 -35
  20. package/dist/health/health.check.d.ts +74 -0
  21. package/dist/health/health.check.js +78 -47
  22. package/dist/health/index.d.ts +1 -1
  23. package/dist/health/index.js +1 -1
  24. package/dist/index.d.ts +14 -14
  25. package/dist/index.js +11 -11
  26. package/dist/locks/index.d.ts +2 -2
  27. package/dist/locks/index.js +2 -2
  28. package/dist/locks/locks.core.d.ts +128 -0
  29. package/dist/locks/locks.core.js +144 -85
  30. package/dist/migration/index.d.ts +3 -1
  31. package/dist/migration/index.js +2 -0
  32. package/dist/migration/migration.dialect.d.ts +65 -0
  33. package/dist/migration/migration.dialect.js +63 -0
  34. package/dist/migration/migration.helpers.d.ts +65 -0
  35. package/dist/migration/migration.helpers.js +60 -18
  36. package/dist/migration/migration.runner.d.ts +75 -0
  37. package/dist/migration/migration.runner.js +195 -107
  38. package/dist/migration/migration.types.d.ts +85 -0
  39. package/dist/pagination/index.d.ts +2 -1
  40. package/dist/pagination/index.js +2 -1
  41. package/dist/pagination/pagination.core.d.ts +163 -0
  42. package/dist/pagination/pagination.core.js +78 -9
  43. package/dist/pagination/pagination.keyset.d.ts +55 -0
  44. package/dist/pagination/pagination.keyset.js +108 -0
  45. package/dist/queryBuilder/index.d.ts +4 -3
  46. package/dist/queryBuilder/index.js +3 -2
  47. package/dist/queryBuilder/queryBuilder.core.d.ts +178 -0
  48. package/dist/queryBuilder/queryBuilder.core.js +92 -14
  49. package/dist/queryBuilder/queryBuilder.factory.d.ts +6 -0
  50. package/dist/queryBuilder/queryBuilder.factory.js +0 -25
  51. package/dist/queryBuilder/queryBuilder.filter.d.ts +170 -0
  52. package/dist/queryBuilder/queryBuilder.filter.js +191 -30
  53. package/dist/queryBuilder/queryBuilder.prisma.d.ts +60 -0
  54. package/dist/queryBuilder/queryBuilder.prisma.js +233 -0
  55. package/dist/queryBuilder/queryBuilder.type.d.ts +48 -0
  56. package/dist/relations/index.d.ts +1 -1
  57. package/dist/relations/index.js +1 -1
  58. package/dist/relations/relations.definition.d.ts +184 -0
  59. package/dist/relations/relations.definition.js +186 -19
  60. package/dist/repository/index.d.ts +2 -1
  61. package/dist/repository/index.js +1 -0
  62. package/dist/repository/repository.base.d.ts +270 -0
  63. package/dist/repository/repository.base.js +350 -84
  64. package/dist/repository/repository.errors.d.ts +60 -0
  65. package/dist/repository/repository.errors.js +235 -0
  66. package/dist/seed/index.d.ts +2 -2
  67. package/dist/seed/index.js +1 -1
  68. package/dist/seed/seed.runner.d.ts +160 -0
  69. package/dist/seed/seed.runner.js +194 -225
  70. package/dist/transaction/index.d.ts +1 -1
  71. package/dist/transaction/index.js +1 -1
  72. package/dist/transaction/transaction.core.d.ts +117 -0
  73. package/dist/transaction/transaction.core.js +83 -56
  74. package/dist/unitOfWork/unitOfWork.core.d.ts +44 -0
  75. package/dist/unitOfWork/unitOfWork.core.js +9 -20
  76. package/package.json +40 -24
  77. package/dist/cache/cache.memory.d.ts.map +0 -1
  78. package/dist/cache/cache.memory.js.map +0 -1
  79. package/dist/cache/index.d.ts.map +0 -1
  80. package/dist/cache/index.js.map +0 -1
  81. package/dist/database/database.core.d.ts.map +0 -1
  82. package/dist/database/database.core.js.map +0 -1
  83. package/dist/database/index.d.ts.map +0 -1
  84. package/dist/database/index.js.map +0 -1
  85. package/dist/databaseClient/databaseClient.core.d.ts.map +0 -1
  86. package/dist/databaseClient/databaseClient.core.js.map +0 -1
  87. package/dist/databaseClient/databaseClient.logger.d.ts.map +0 -1
  88. package/dist/databaseClient/databaseClient.logger.js.map +0 -1
  89. package/dist/databaseClient/index.d.ts.map +0 -1
  90. package/dist/databaseClient/index.js.map +0 -1
  91. package/dist/databaseConnection/databaseConnection.manager.d.ts.map +0 -1
  92. package/dist/databaseConnection/databaseConnection.manager.js.map +0 -1
  93. package/dist/databaseConnection/index.d.ts.map +0 -1
  94. package/dist/databaseConnection/index.js.map +0 -1
  95. package/dist/databaseType/databaseType.type.d.ts.map +0 -1
  96. package/dist/databaseType/databaseType.type.js.map +0 -1
  97. package/dist/databaseType/index.d.ts.map +0 -1
  98. package/dist/databaseType/index.js.map +0 -1
  99. package/dist/health/health.check.d.ts.map +0 -1
  100. package/dist/health/health.check.js.map +0 -1
  101. package/dist/health/index.d.ts.map +0 -1
  102. package/dist/health/index.js.map +0 -1
  103. package/dist/index.d.ts.map +0 -1
  104. package/dist/index.js.map +0 -1
  105. package/dist/locks/index.d.ts.map +0 -1
  106. package/dist/locks/index.js.map +0 -1
  107. package/dist/locks/locks.core.d.ts.map +0 -1
  108. package/dist/locks/locks.core.js.map +0 -1
  109. package/dist/migration/index.d.ts.map +0 -1
  110. package/dist/migration/index.js.map +0 -1
  111. package/dist/migration/migration.helpers.d.ts.map +0 -1
  112. package/dist/migration/migration.helpers.js.map +0 -1
  113. package/dist/migration/migration.runner.d.ts.map +0 -1
  114. package/dist/migration/migration.runner.js.map +0 -1
  115. package/dist/migration/migration.types.d.ts.map +0 -1
  116. package/dist/migration/migration.types.js.map +0 -1
  117. package/dist/pagination/index.d.ts.map +0 -1
  118. package/dist/pagination/index.js.map +0 -1
  119. package/dist/pagination/pagination.core.d.ts.map +0 -1
  120. package/dist/pagination/pagination.core.js.map +0 -1
  121. package/dist/queryBuilder/index.d.ts.map +0 -1
  122. package/dist/queryBuilder/index.js.map +0 -1
  123. package/dist/queryBuilder/queryBuilder.core.d.ts.map +0 -1
  124. package/dist/queryBuilder/queryBuilder.core.js.map +0 -1
  125. package/dist/queryBuilder/queryBuilder.factory.d.ts.map +0 -1
  126. package/dist/queryBuilder/queryBuilder.factory.js.map +0 -1
  127. package/dist/queryBuilder/queryBuilder.filter.d.ts.map +0 -1
  128. package/dist/queryBuilder/queryBuilder.filter.js.map +0 -1
  129. package/dist/queryBuilder/queryBuilder.type.d.ts.map +0 -1
  130. package/dist/queryBuilder/queryBuilder.type.js.map +0 -1
  131. package/dist/relations/index.d.ts.map +0 -1
  132. package/dist/relations/index.js.map +0 -1
  133. package/dist/relations/relations.definition.d.ts.map +0 -1
  134. package/dist/relations/relations.definition.js.map +0 -1
  135. package/dist/repository/index.d.ts.map +0 -1
  136. package/dist/repository/index.js.map +0 -1
  137. package/dist/repository/repository.base.d.ts.map +0 -1
  138. package/dist/repository/repository.base.js.map +0 -1
  139. package/dist/seed/index.d.ts.map +0 -1
  140. package/dist/seed/index.js.map +0 -1
  141. package/dist/seed/seed.runner.d.ts.map +0 -1
  142. package/dist/seed/seed.runner.js.map +0 -1
  143. package/dist/transaction/index.d.ts.map +0 -1
  144. package/dist/transaction/index.js.map +0 -1
  145. package/dist/transaction/transaction.core.d.ts.map +0 -1
  146. package/dist/transaction/transaction.core.js.map +0 -1
  147. package/dist/unitOfWork/index.d.ts.map +0 -1
  148. package/dist/unitOfWork/index.js.map +0 -1
  149. package/dist/unitOfWork/unitOfWork.core.d.ts.map +0 -1
  150. package/dist/unitOfWork/unitOfWork.core.js.map +0 -1
@@ -0,0 +1,163 @@
1
+ import type { PaginationInput, PaginationMeta, PaginatedResult } from "../databaseType/databaseType.type.js";
2
+ /**
3
+ * Default pagination values.
4
+ */
5
+ export declare const DEFAULT_PAGE = 1;
6
+ export declare const DEFAULT_LIMIT = 20;
7
+ export declare const MAX_LIMIT = 100;
8
+ /**
9
+ * Normalized pagination configuration.
10
+ */
11
+ export interface NormalizedPagination {
12
+ readonly page: number;
13
+ readonly limit: number;
14
+ readonly offset: number;
15
+ }
16
+ /**
17
+ * Cursor pagination request.
18
+ */
19
+ export interface CursorPaginationInput {
20
+ readonly cursor?: string | null;
21
+ readonly limit?: number;
22
+ }
23
+ /**
24
+ * Cursor pagination metadata.
25
+ */
26
+ export interface CursorPaginationMeta {
27
+ readonly limit: number;
28
+ readonly hasNextPage: boolean;
29
+ readonly hasPreviousPage: boolean;
30
+ readonly nextCursor?: string;
31
+ readonly previousCursor?: string;
32
+ }
33
+ /**
34
+ * Cursor paginated result.
35
+ */
36
+ export interface CursorPaginatedResult<TEntity> {
37
+ readonly data: readonly TEntity[];
38
+ readonly meta: CursorPaginationMeta;
39
+ }
40
+ /**
41
+ * Normalizes page and limit values.
42
+ */
43
+ export declare function normalizePagination(input?: PaginationInput): NormalizedPagination;
44
+ /**
45
+ * Normalizes a page number.
46
+ */
47
+ export declare function normalizePage(page?: number): number;
48
+ /**
49
+ * Normalizes a page size.
50
+ */
51
+ export declare function normalizeLimit(limit?: number): number;
52
+ /**
53
+ * Calculates the database offset for a page.
54
+ */
55
+ export declare function calculateOffset(page: number, limit: number): number;
56
+ /**
57
+ * Calculates the total number of pages.
58
+ */
59
+ export declare function calculateTotalPages(total: number, limit: number): number;
60
+ /**
61
+ * Creates pagination metadata.
62
+ */
63
+ export declare function createPaginationMeta(page: number, limit: number, total: number): PaginationMeta;
64
+ /**
65
+ * Creates a paginated result.
66
+ */
67
+ export declare function createPaginatedResult<TEntity>(data: readonly TEntity[], page: number, limit: number, total: number): PaginatedResult<TEntity>;
68
+ /**
69
+ * Gets the next page number.
70
+ */
71
+ export declare function getNextPage(meta: PaginationMeta): number | null;
72
+ /**
73
+ * Gets the previous page number.
74
+ */
75
+ export declare function getPreviousPage(meta: PaginationMeta): number | null;
76
+ /**
77
+ * Checks whether a page number is valid for the result set.
78
+ */
79
+ export declare function isValidPage(page: number, totalPages: number): boolean;
80
+ /**
81
+ * Calculates the item range represented by a page.
82
+ *
83
+ * For example, page 2 with a limit of 20 and total of 55 returns
84
+ * `{ start: 21, end: 40 }`.
85
+ */
86
+ export declare function getItemRange(page: number, limit: number, total: number): {
87
+ readonly start: number;
88
+ readonly end: number;
89
+ };
90
+ /**
91
+ * Applies offset pagination to an in-memory collection.
92
+ *
93
+ * This is useful for adapters and tests that need the same pagination
94
+ * semantics without querying the database directly.
95
+ */
96
+ export declare function paginateCollection<TEntity>(items: readonly TEntity[], input?: PaginationInput): PaginatedResult<TEntity>;
97
+ /**
98
+ * Normalizes cursor pagination input.
99
+ */
100
+ export declare function normalizeCursorPagination(input?: CursorPaginationInput): Required<Pick<CursorPaginationInput, "limit">> & Pick<CursorPaginationInput, "cursor">;
101
+ /**
102
+ * Creates cursor pagination metadata.
103
+ */
104
+ export declare function createCursorPaginationMeta(limit: number, options: {
105
+ readonly hasNextPage: boolean;
106
+ readonly hasPreviousPage?: boolean;
107
+ readonly nextCursor?: string | null;
108
+ readonly previousCursor?: string | null;
109
+ }): CursorPaginationMeta;
110
+ /**
111
+ * Creates a cursor paginated result.
112
+ */
113
+ export declare function createCursorPaginatedResult<TEntity>(data: readonly TEntity[], limit: number, options: {
114
+ readonly hasNextPage: boolean;
115
+ readonly hasPreviousPage?: boolean;
116
+ readonly nextCursor?: string | null;
117
+ readonly previousCursor?: string | null;
118
+ }): CursorPaginatedResult<TEntity>;
119
+ /**
120
+ * Options for encoding a cursor.
121
+ */
122
+ export interface EncodeCursorOptions {
123
+ /**
124
+ * When supplied the cursor is signed with HMAC-SHA256 so clients cannot
125
+ * forge or tamper with its payload.
126
+ */
127
+ readonly secret?: string;
128
+ }
129
+ /**
130
+ * Options for decoding a cursor.
131
+ */
132
+ export interface DecodeCursorOptions extends EncodeCursorOptions {
133
+ /**
134
+ * Keys the decoded payload may contain. Any other key is rejected.
135
+ */
136
+ readonly allowedFields?: readonly string[];
137
+ }
138
+ /**
139
+ * A validated cursor payload: a flat object of primitive values.
140
+ */
141
+ export type CursorPayload = Readonly<Record<string, string | number | boolean | null>>;
142
+ /**
143
+ * Encodes a cursor value.
144
+ *
145
+ * Without a `secret` the cursor is plain base64url JSON and must be treated
146
+ * as client-controlled input; pass `allowedFields` to `decodeCursor` (or
147
+ * use a secret) before feeding it to a query.
148
+ */
149
+ export declare function encodeCursor(value: unknown, options?: EncodeCursorOptions): string;
150
+ /**
151
+ * Decodes a cursor value.
152
+ *
153
+ * When `secret` is supplied the signature is verified; when
154
+ * `allowedFields` is supplied the payload must be a flat object whose keys
155
+ * are all allowed and whose values are primitives.
156
+ */
157
+ export declare function decodeCursor<T = unknown>(cursor: string, options?: DecodeCursorOptions): T;
158
+ /**
159
+ * Validates that a decoded cursor is a flat object of primitive values
160
+ * restricted to the allowed fields.
161
+ */
162
+ export declare function validateCursorPayload(value: unknown, allowedFields: readonly string[]): asserts value is CursorPayload;
163
+ //# sourceMappingURL=pagination.core.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { createHmac, timingSafeEqual } from "node:crypto";
1
2
  /**
2
3
  * Default pagination values.
3
4
  */
@@ -59,13 +60,17 @@ export function createPaginationMeta(page, limit, total) {
59
60
  const normalizedLimit = normalizeLimit(limit);
60
61
  const normalizedTotal = Math.max(0, Math.floor(total));
61
62
  const totalPages = calculateTotalPages(normalizedTotal, normalizedLimit);
63
+ const hasNextPage = totalPages > 0 && normalizedPage < totalPages;
64
+ const hasPreviousPage = normalizedPage > 1 && totalPages > 0;
62
65
  return {
63
66
  page: normalizedPage,
64
67
  limit: normalizedLimit,
65
68
  total: normalizedTotal,
66
69
  totalPages,
67
- hasNextPage: totalPages > 0 && normalizedPage < totalPages,
68
- hasPreviousPage: normalizedPage > 1 && totalPages > 0,
70
+ hasNextPage,
71
+ hasPreviousPage,
72
+ hasNext: hasNextPage,
73
+ hasPrev: hasPreviousPage,
69
74
  };
70
75
  }
71
76
  /**
@@ -128,9 +133,10 @@ export function getItemRange(page, limit, total) {
128
133
  const start = calculateOffset(normalizedPage, normalizedLimit) + 1;
129
134
  const end = Math.min(start + normalizedLimit - 1, normalizedTotal);
130
135
  if (start > normalizedTotal) {
136
+ // The page lies past the end of the data set: there is no item range.
131
137
  return {
132
- start: normalizedTotal,
133
- end: normalizedTotal,
138
+ start: 0,
139
+ end: 0,
134
140
  };
135
141
  }
136
142
  return {
@@ -180,31 +186,94 @@ export function createCursorPaginatedResult(data, limit, options) {
180
186
  meta: createCursorPaginationMeta(limit, options),
181
187
  };
182
188
  }
189
+ const CURSOR_SIGNATURE_SEPARATOR = ".";
190
+ const FORBIDDEN_CURSOR_KEYS = new Set(["__proto__", "constructor", "prototype"]);
183
191
  /**
184
192
  * Encodes a cursor value.
193
+ *
194
+ * Without a `secret` the cursor is plain base64url JSON and must be treated
195
+ * as client-controlled input; pass `allowedFields` to `decodeCursor` (or
196
+ * use a secret) before feeding it to a query.
185
197
  */
186
- export function encodeCursor(value) {
198
+ export function encodeCursor(value, options = {}) {
187
199
  const serialized = JSON.stringify(value);
188
200
  if (typeof serialized !== "string") {
189
201
  throw new TypeError("Cursor value could not be serialized.");
190
202
  }
191
- return Buffer.from(serialized, "utf8").toString("base64url");
203
+ const payload = Buffer.from(serialized, "utf8").toString("base64url");
204
+ if (options.secret === undefined) {
205
+ return payload;
206
+ }
207
+ validateSecret(options.secret);
208
+ return `${payload}${CURSOR_SIGNATURE_SEPARATOR}${signCursor(payload, options.secret)}`;
192
209
  }
193
210
  /**
194
211
  * Decodes a cursor value.
212
+ *
213
+ * When `secret` is supplied the signature is verified; when
214
+ * `allowedFields` is supplied the payload must be a flat object whose keys
215
+ * are all allowed and whose values are primitives.
195
216
  */
196
- export function decodeCursor(cursor) {
217
+ export function decodeCursor(cursor, options = {}) {
197
218
  if (typeof cursor !== "string" || cursor.trim().length === 0) {
198
219
  throw new TypeError("A cursor value is required.");
199
220
  }
221
+ let payload = cursor;
222
+ if (options.secret !== undefined) {
223
+ validateSecret(options.secret);
224
+ const separator = cursor.lastIndexOf(CURSOR_SIGNATURE_SEPARATOR);
225
+ if (separator <= 0) {
226
+ throw new TypeError("Invalid pagination cursor signature.");
227
+ }
228
+ payload = cursor.slice(0, separator);
229
+ const signature = cursor.slice(separator + 1);
230
+ const expected = signCursor(payload, options.secret);
231
+ if (signature.length !== expected.length ||
232
+ !timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
233
+ throw new TypeError("Invalid pagination cursor signature.");
234
+ }
235
+ }
236
+ let decoded;
200
237
  try {
201
- const decoded = Buffer.from(cursor, "base64url").toString("utf8");
202
- return JSON.parse(decoded);
238
+ decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
203
239
  }
204
240
  catch (error) {
205
241
  throw new TypeError("Invalid pagination cursor.", {
206
242
  cause: error,
207
243
  });
208
244
  }
245
+ if (options.allowedFields !== undefined) {
246
+ validateCursorPayload(decoded, options.allowedFields);
247
+ }
248
+ return decoded;
249
+ }
250
+ /**
251
+ * Validates that a decoded cursor is a flat object of primitive values
252
+ * restricted to the allowed fields.
253
+ */
254
+ export function validateCursorPayload(value, allowedFields) {
255
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
256
+ throw new TypeError("Pagination cursor payload must be an object.");
257
+ }
258
+ const allowed = new Set(allowedFields);
259
+ for (const [key, entry] of Object.entries(value)) {
260
+ if (FORBIDDEN_CURSOR_KEYS.has(key) || !allowed.has(key)) {
261
+ throw new TypeError(`Pagination cursor contains an unexpected field "${key}".`);
262
+ }
263
+ if (entry !== null &&
264
+ typeof entry !== "string" &&
265
+ typeof entry !== "number" &&
266
+ typeof entry !== "boolean") {
267
+ throw new TypeError(`Pagination cursor field "${key}" must be a primitive value.`);
268
+ }
269
+ }
270
+ }
271
+ function signCursor(payload, secret) {
272
+ return createHmac("sha256", secret).update(payload).digest("base64url");
273
+ }
274
+ function validateSecret(secret) {
275
+ if (typeof secret !== "string" || secret.length === 0) {
276
+ throw new TypeError("A cursor secret must be a non-empty string.");
277
+ }
209
278
  }
210
279
  //# sourceMappingURL=pagination.core.js.map
@@ -0,0 +1,55 @@
1
+ import type { SortInput } from "../databaseType/databaseType.type.js";
2
+ import { type CursorPaginatedResult, type CursorPayload } from "./pagination.core.js";
3
+ /**
4
+ * Options for building a keyset page.
5
+ */
6
+ export interface KeysetPageOptions<TField extends string = string> {
7
+ /**
8
+ * The sort definition the rows were fetched with. The cursor is derived
9
+ * from these fields, in order.
10
+ */
11
+ readonly sort: readonly SortInput<TField>[];
12
+ /**
13
+ * The requested page size. Rows beyond this size indicate a next page.
14
+ */
15
+ readonly limit: number;
16
+ /**
17
+ * The cursor the page was requested with, if any.
18
+ */
19
+ readonly cursor?: string | null;
20
+ /**
21
+ * Secret used to sign cursors. Strongly recommended for public APIs.
22
+ */
23
+ readonly secret?: string;
24
+ }
25
+ /**
26
+ * A Prisma-style filter object produced by the keyset helpers.
27
+ */
28
+ export type KeysetWhere = Readonly<Record<string, unknown>>;
29
+ /**
30
+ * Decodes and validates a keyset cursor against the sort definition.
31
+ *
32
+ * The payload may only contain the sort fields and primitive values.
33
+ */
34
+ export declare function decodeKeysetCursor<TField extends string = string>(cursor: string, sort: readonly SortInput<TField>[], secret?: string): CursorPayload;
35
+ /**
36
+ * Builds a Prisma-compatible `where` fragment that selects the rows that
37
+ * come strictly after the cursor position in the given sort order.
38
+ *
39
+ * For a sort of `[a asc, b desc]` the result is
40
+ * `OR: [{ a: { gt: A } }, { AND: [{ a: A }, { b: { lt: B } }] }]`.
41
+ */
42
+ export declare function buildKeysetWhere<TField extends string = string>(cursor: CursorPayload, sort: readonly SortInput<TField>[]): KeysetWhere;
43
+ /**
44
+ * Derives the cursor payload for a row from the sort fields.
45
+ */
46
+ export declare function createKeysetCursor<TField extends string = string>(row: Readonly<Record<string, unknown>>, sort: readonly SortInput<TField>[], secret?: string): string;
47
+ /**
48
+ * Turns `limit + 1` fetched rows into a cursor-paginated result.
49
+ *
50
+ * Fetch `limit + 1` rows ordered by `sort`, then pass them here: the extra
51
+ * row signals a next page and is dropped, and `nextCursor` is derived from
52
+ * the last returned row.
53
+ */
54
+ export declare function createKeysetPage<TEntity extends Readonly<Record<string, unknown>>, TField extends string = string>(rows: readonly TEntity[], options: KeysetPageOptions<TField>): CursorPaginatedResult<TEntity>;
55
+ //# sourceMappingURL=pagination.keyset.d.ts.map
@@ -0,0 +1,108 @@
1
+ import { createCursorPaginatedResult, decodeCursor, encodeCursor, normalizeLimit, } from "./pagination.core.js";
2
+ /**
3
+ * Decodes and validates a keyset cursor against the sort definition.
4
+ *
5
+ * The payload may only contain the sort fields and primitive values.
6
+ */
7
+ export function decodeKeysetCursor(cursor, sort, secret) {
8
+ validateSort(sort);
9
+ const payload = decodeCursor(cursor, {
10
+ secret,
11
+ allowedFields: sort.map((entry) => entry.field),
12
+ });
13
+ for (const entry of sort) {
14
+ if (!(entry.field in payload)) {
15
+ throw new TypeError(`Pagination cursor is missing sort field "${entry.field}".`);
16
+ }
17
+ }
18
+ return payload;
19
+ }
20
+ /**
21
+ * Builds a Prisma-compatible `where` fragment that selects the rows that
22
+ * come strictly after the cursor position in the given sort order.
23
+ *
24
+ * For a sort of `[a asc, b desc]` the result is
25
+ * `OR: [{ a: { gt: A } }, { AND: [{ a: A }, { b: { lt: B } }] }]`.
26
+ */
27
+ export function buildKeysetWhere(cursor, sort) {
28
+ validateSort(sort);
29
+ const branches = [];
30
+ sort.forEach((entry, index) => {
31
+ const comparison = entry.direction === "desc" ? "lt" : "gt";
32
+ const conditions = sort
33
+ .slice(0, index)
34
+ .map((previous) => ({
35
+ [previous.field]: { equals: cursor[previous.field] },
36
+ }));
37
+ conditions.push({
38
+ [entry.field]: { [comparison]: cursor[entry.field] },
39
+ });
40
+ branches.push(conditions.length === 1 ? conditions[0] : { AND: conditions });
41
+ });
42
+ return branches.length === 1 ? branches[0] : { OR: branches };
43
+ }
44
+ /**
45
+ * Derives the cursor payload for a row from the sort fields.
46
+ */
47
+ export function createKeysetCursor(row, sort, secret) {
48
+ validateSort(sort);
49
+ const payload = {};
50
+ for (const entry of sort) {
51
+ payload[entry.field] = toCursorValue(row[entry.field], entry.field);
52
+ }
53
+ return encodeCursor(payload, { secret });
54
+ }
55
+ /**
56
+ * Turns `limit + 1` fetched rows into a cursor-paginated result.
57
+ *
58
+ * Fetch `limit + 1` rows ordered by `sort`, then pass them here: the extra
59
+ * row signals a next page and is dropped, and `nextCursor` is derived from
60
+ * the last returned row.
61
+ */
62
+ export function createKeysetPage(rows, options) {
63
+ const limit = normalizeLimit(options.limit);
64
+ const hasNextPage = rows.length > limit;
65
+ const data = rows.slice(0, limit);
66
+ const last = data[data.length - 1];
67
+ return createCursorPaginatedResult(data, limit, {
68
+ hasNextPage,
69
+ hasPreviousPage: options.cursor !== undefined && options.cursor !== null,
70
+ nextCursor: hasNextPage && last
71
+ ? createKeysetCursor(last, options.sort, options.secret)
72
+ : null,
73
+ });
74
+ }
75
+ function toCursorValue(value, field) {
76
+ if (value instanceof Date) {
77
+ return value.toISOString();
78
+ }
79
+ if (typeof value === "bigint") {
80
+ return value.toString();
81
+ }
82
+ if (value === null ||
83
+ typeof value === "string" ||
84
+ typeof value === "number" ||
85
+ typeof value === "boolean") {
86
+ return value;
87
+ }
88
+ throw new TypeError(`Cannot build a cursor from field "${field}": unsupported value type.`);
89
+ }
90
+ function validateSort(sort) {
91
+ if (!Array.isArray(sort) || sort.length === 0) {
92
+ throw new TypeError("Keyset pagination requires at least one sort field.");
93
+ }
94
+ const seen = new Set();
95
+ for (const entry of sort) {
96
+ if (typeof entry.field !== "string" || entry.field.trim().length === 0) {
97
+ throw new TypeError("Keyset sort fields must be non-empty strings.");
98
+ }
99
+ if (entry.direction !== "asc" && entry.direction !== "desc") {
100
+ throw new TypeError(`Keyset sort field "${entry.field}" has an invalid direction.`);
101
+ }
102
+ if (seen.has(entry.field)) {
103
+ throw new TypeError(`Keyset sort field "${entry.field}" is duplicated.`);
104
+ }
105
+ seen.add(entry.field);
106
+ }
107
+ }
108
+ //# sourceMappingURL=pagination.keyset.js.map
@@ -4,7 +4,8 @@
4
4
  * Database-neutral query construction and filter helpers.
5
5
  */
6
6
  export { QueryBuilder } from "./queryBuilder.core.js";
7
- export { createQueryBuilder, cloneFilter } from "./queryBuilder.factory.js";
8
- export type { QueryCondition, QueryFilter, QueryOperator, QueryBuilderState, } from "./queryBuilder.type.js";
9
- export { equals, notEquals, inList, notInList, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, contains, startsWith, endsWith, isNull, isNotNull, and, or, not, condition, allOf, anyOf, fromObject, dateRange, oneOf, noneOf, optionalEquals, optionalContains, hasConditions, flattenAnd, } from "./queryBuilder.filter.js";
7
+ export { createQueryBuilder } from "./queryBuilder.factory.js";
8
+ export { toPrismaWhere, toPrismaArgs, toPrismaOrderBy, toPrismaSelect, toPrismaSkipTake, type PrismaWhere, type PrismaQueryArgs, type ToPrismaArgsOptions, } from "./queryBuilder.prisma.js";
9
+ export type { QueryCondition, QueryFilter, QueryOperator, RelationOperator, QueryBuilderState, } from "./queryBuilder.type.js";
10
+ export { equals, notEquals, inList, notInList, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, contains, startsWith, endsWith, isNull, isNotNull, and, or, not, condition, allOf, anyOf, fromObject, dateRange, oneOf, noneOf, optionalEquals, optionalContains, hasConditions, flattenAnd, cloneFilter, between, matchesPattern, isEmpty, isNotEmpty, dateOnly, isBefore, isAfter, isBetween, notCondition, relational, } from "./queryBuilder.filter.js";
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -4,6 +4,7 @@
4
4
  * Database-neutral query construction and filter helpers.
5
5
  */
6
6
  export { QueryBuilder } from "./queryBuilder.core.js";
7
- export { createQueryBuilder, cloneFilter } from "./queryBuilder.factory.js";
8
- export { equals, notEquals, inList, notInList, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, contains, startsWith, endsWith, isNull, isNotNull, and, or, not, condition, allOf, anyOf, fromObject, dateRange, oneOf, noneOf, optionalEquals, optionalContains, hasConditions, flattenAnd, } from "./queryBuilder.filter.js";
7
+ export { createQueryBuilder } from "./queryBuilder.factory.js";
8
+ export { toPrismaWhere, toPrismaArgs, toPrismaOrderBy, toPrismaSelect, toPrismaSkipTake, } from "./queryBuilder.prisma.js";
9
+ export { equals, notEquals, inList, notInList, lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual, contains, startsWith, endsWith, isNull, isNotNull, and, or, not, condition, allOf, anyOf, fromObject, dateRange, oneOf, noneOf, optionalEquals, optionalContains, hasConditions, flattenAnd, cloneFilter, between, matchesPattern, isEmpty, isNotEmpty, dateOnly, isBefore, isAfter, isBetween, notCondition, relational, } from "./queryBuilder.filter.js";
9
10
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,178 @@
1
+ import type { QueryOperator, QueryFilter, QueryBuilderState, PaginationInput, SortDirection, SortInput, QueryOptions } from "./queryBuilder.type.js";
2
+ import { type PrismaQueryArgs, type ToPrismaArgsOptions } from "./queryBuilder.prisma.js";
3
+ import { type RelationInclude } from "../relations/relations.definition.js";
4
+ /**
5
+ * Query builder used to construct database-neutral query definitions.
6
+ *
7
+ * The builder does not execute queries. It produces a plain query
8
+ * definition that repositories or adapters can translate into their
9
+ * ORM-specific representation.
10
+ */
11
+ export declare class QueryBuilder<TField extends string = string> {
12
+ private filterState?;
13
+ private paginationState?;
14
+ private sortState;
15
+ private selectState;
16
+ private offsetState?;
17
+ private includeState;
18
+ private _cachedBuild?;
19
+ private invalidateCache;
20
+ /**
21
+ * Adds an equality condition.
22
+ */
23
+ where(field: TField, value: unknown): this;
24
+ /**
25
+ * Adds a condition using a specific operator.
26
+ */
27
+ whereOperator(field: TField, operator: QueryOperator, value?: unknown): this;
28
+ /**
29
+ * Adds a not-equal condition.
30
+ */
31
+ whereNot(field: TField, value: unknown): this;
32
+ /**
33
+ * Adds an IN condition.
34
+ */
35
+ whereIn(field: TField, values: readonly unknown[]): this;
36
+ /**
37
+ * Adds a NOT IN condition.
38
+ */
39
+ whereNotIn(field: TField, values: readonly unknown[]): this;
40
+ /**
41
+ * Adds a less-than condition.
42
+ */
43
+ whereLessThan(field: TField, value: unknown): this;
44
+ /**
45
+ * Adds a less-than-or-equal condition.
46
+ */
47
+ whereLessThanOrEqual(field: TField, value: unknown): this;
48
+ /**
49
+ * Adds a greater-than condition.
50
+ */
51
+ whereGreaterThan(field: TField, value: unknown): this;
52
+ /**
53
+ * Adds a greater-than-or-equal condition.
54
+ */
55
+ whereGreaterThanOrEqual(field: TField, value: unknown): this;
56
+ /**
57
+ * Adds a contains condition.
58
+ */
59
+ whereContains(field: TField, value: string): this;
60
+ /**
61
+ * Adds a starts-with condition.
62
+ */
63
+ whereStartsWith(field: TField, value: string): this;
64
+ /**
65
+ * Adds an ends-with condition.
66
+ */
67
+ whereEndsWith(field: TField, value: string): this;
68
+ /**
69
+ * Adds an IS NULL condition.
70
+ */
71
+ whereNull(field: TField): this;
72
+ /**
73
+ * Adds an IS NOT NULL condition.
74
+ */
75
+ whereNotNull(field: TField): this;
76
+ /**
77
+ * Adds an AND group.
78
+ */
79
+ and(...filters: QueryFilter[]): this;
80
+ /**
81
+ * Adds an OR group.
82
+ */
83
+ or(...filters: QueryFilter[]): this;
84
+ /**
85
+ * Adds a NOT group.
86
+ */
87
+ not(filter: QueryFilter): this;
88
+ /**
89
+ * Sets the requested page.
90
+ */
91
+ page(page: number): this;
92
+ /**
93
+ * Sets the requested page size.
94
+ */
95
+ limit(limit: number): this;
96
+ /**
97
+ * Sets pagination.
98
+ */
99
+ paginate(pagination: PaginationInput): this;
100
+ /**
101
+ * Sets an explicit row offset. Takes precedence over `page()`.
102
+ */
103
+ offset(offset: number): this;
104
+ /**
105
+ * Adds relations to include. Accepts relation names or include
106
+ * definitions created with `includeRelation`.
107
+ */
108
+ include(...relations: readonly (string | RelationInclude)[]): this;
109
+ /**
110
+ * Sorts ascending by a field.
111
+ */
112
+ orderByAsc(field: TField): this;
113
+ /**
114
+ * Sorts descending by a field.
115
+ */
116
+ orderByDesc(field: TField): this;
117
+ /**
118
+ * Adds a sort definition.
119
+ */
120
+ orderBy(field: TField, direction?: SortDirection): this;
121
+ /**
122
+ * Replaces all sort definitions.
123
+ */
124
+ sort(sort: readonly SortInput<TField>[]): this;
125
+ /**
126
+ * Selects specific fields.
127
+ */
128
+ select(...fields: TField[]): this;
129
+ /**
130
+ * Clears all filters.
131
+ */
132
+ clearFilters(): this;
133
+ /**
134
+ * Clears pagination.
135
+ */
136
+ clearPagination(): this;
137
+ /**
138
+ * Clears sorting.
139
+ */
140
+ clearSort(): this;
141
+ /**
142
+ * Clears selected fields.
143
+ */
144
+ clearSelect(): this;
145
+ /**
146
+ * Clears included relations.
147
+ */
148
+ clearInclude(): this;
149
+ /**
150
+ * Resets the builder to its initial empty state.
151
+ */
152
+ reset(): this;
153
+ /**
154
+ * Returns the immutable query definition.
155
+ */
156
+ build(): QueryBuilderState<TField>;
157
+ /**
158
+ * Converts the builder into generic query options (pagination, sort and
159
+ * the built filter).
160
+ */
161
+ toQueryOptions(): QueryOptions<TField> & {
162
+ readonly filter?: QueryFilter;
163
+ readonly offset?: number;
164
+ };
165
+ /**
166
+ * Translates the builder into Prisma `findMany`-style arguments.
167
+ */
168
+ toPrismaArgs(options?: ToPrismaArgsOptions): PrismaQueryArgs;
169
+ /**
170
+ * Creates an independent copy of the builder.
171
+ */
172
+ clone(): QueryBuilder<TField>;
173
+ /**
174
+ * Adds a condition to the current filter.
175
+ */
176
+ private addCondition;
177
+ }
178
+ //# sourceMappingURL=queryBuilder.core.d.ts.map