@zudojs/database 0.1.0 → 1.1.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 +225 -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 +171 -14
  10. package/dist/databaseClient/databaseClient.core.js +350 -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 +132 -0
  29. package/dist/locks/locks.core.js +150 -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 +283 -0
  63. package/dist/repository/repository.base.js +370 -89
  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 +44 -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
@@ -1,44 +1,66 @@
1
+ /**
2
+ * Default maximum include depth.
3
+ */
4
+ export const DEFAULT_INCLUDE_DEPTH = 5;
1
5
  /**
2
6
  * Creates a one-to-one relation definition.
3
7
  */
4
8
  export function oneToOne(definition) {
5
- return Object.freeze({
9
+ const relation = Object.freeze({
6
10
  ...definition,
7
11
  type: "one-to-one",
8
12
  });
13
+ validateRelation(relation);
14
+ return relation;
9
15
  }
10
16
  /**
11
17
  * Creates a one-to-many relation definition.
12
18
  */
13
19
  export function oneToMany(definition) {
14
- return Object.freeze({
20
+ const relation = Object.freeze({
15
21
  ...definition,
16
22
  type: "one-to-many",
17
23
  });
24
+ validateRelation(relation);
25
+ return relation;
18
26
  }
19
27
  /**
20
28
  * Creates a many-to-one relation definition.
21
29
  */
22
30
  export function manyToOne(definition) {
23
- return Object.freeze({
31
+ const relation = Object.freeze({
24
32
  ...definition,
25
33
  type: "many-to-one",
26
34
  });
35
+ validateRelation(relation);
36
+ return relation;
27
37
  }
28
38
  /**
29
39
  * Creates a many-to-many relation definition.
30
40
  */
31
41
  export function manyToMany(definition) {
32
- return Object.freeze({
42
+ const relation = Object.freeze({
33
43
  ...definition,
34
44
  type: "many-to-many",
35
45
  });
46
+ validateRelation(relation);
47
+ return relation;
36
48
  }
37
49
  /**
38
50
  * Creates a relation include definition.
39
51
  */
40
52
  export function includeRelation(relation, options = {}) {
41
53
  validateRelationName(relation);
54
+ if (options.select !== undefined) {
55
+ for (const field of options.select) {
56
+ validateFieldName(field);
57
+ }
58
+ }
59
+ if (options.include !== undefined) {
60
+ for (const nested of options.include) {
61
+ validateInclude(nested);
62
+ }
63
+ }
42
64
  return Object.freeze({
43
65
  relation,
44
66
  select: options.select
@@ -47,6 +69,86 @@ export function includeRelation(relation, options = {}) {
47
69
  include: options.include ? Object.freeze([...options.include]) : undefined,
48
70
  });
49
71
  }
72
+ /**
73
+ * Validates a relation include (recursively).
74
+ */
75
+ export function validateInclude(include) {
76
+ if (!include || typeof include !== "object") {
77
+ throw new TypeError("A relation include is required.");
78
+ }
79
+ validateRelationName(include.relation);
80
+ for (const field of include.select ?? []) {
81
+ validateFieldName(field);
82
+ }
83
+ for (const nested of include.include ?? []) {
84
+ validateInclude(nested);
85
+ }
86
+ }
87
+ /**
88
+ * Translates relation includes into a Prisma `include` object.
89
+ *
90
+ * When a registry is supplied every relation is validated against the
91
+ * parent model (descending through child models for nested includes) and
92
+ * cyclic includes are rejected. Depth is bounded in all cases.
93
+ */
94
+ export function toPrismaInclude(includes, options = {}) {
95
+ const maxDepth = options.depth ?? DEFAULT_INCLUDE_DEPTH;
96
+ if (!Number.isInteger(maxDepth) || maxDepth < 1) {
97
+ throw new TypeError("Include depth must be a positive integer.");
98
+ }
99
+ if (options.registry && options.parent === undefined) {
100
+ throw new TypeError("toPrismaInclude requires `parent` when a registry is supplied.");
101
+ }
102
+ return buildInclude(includes, options, options.parent, 1, maxDepth, []);
103
+ }
104
+ function buildInclude(includes, options, parent, depth, maxDepth, path) {
105
+ if (depth > maxDepth) {
106
+ throw new RangeError(`Relation include depth exceeds the maximum of ${maxDepth}.`);
107
+ }
108
+ const result = {};
109
+ for (const include of includes) {
110
+ validateInclude(include);
111
+ if (include.relation in result) {
112
+ throw new TypeError(`Relation "${include.relation}" is included more than once.`);
113
+ }
114
+ let definition;
115
+ if (options.registry) {
116
+ definition = options.registry.get(include.relation, parent);
117
+ if (!definition) {
118
+ throw new TypeError(`Relation "${include.relation}" is not registered for the parent model.`);
119
+ }
120
+ if (path.includes(definition)) {
121
+ throw new TypeError(`Relation include "${include.relation}" is cyclic.`);
122
+ }
123
+ }
124
+ const entry = {};
125
+ if (include.select && include.select.length > 0) {
126
+ const select = {};
127
+ for (const field of include.select) {
128
+ select[field] = true;
129
+ }
130
+ entry["select"] = select;
131
+ }
132
+ if (include.include && include.include.length > 0) {
133
+ const nested = buildInclude(include.include, options, definition ? definition.child : undefined, depth + 1, maxDepth, definition ? [...path, definition] : path);
134
+ if (entry["select"]) {
135
+ entry["select"] = { ...entry["select"], ...nested };
136
+ }
137
+ else {
138
+ entry["include"] = nested;
139
+ }
140
+ }
141
+ if (options.includeDeleted === false &&
142
+ definition &&
143
+ isCollectionRelation(definition)) {
144
+ const field = options.softDeleteField ?? "deletedAt";
145
+ validateFieldName(field);
146
+ entry["where"] = { [field]: null };
147
+ }
148
+ result[include.relation] = Object.keys(entry).length === 0 ? true : entry;
149
+ }
150
+ return result;
151
+ }
50
152
  /**
51
153
  * Creates a nested relation include.
52
154
  */
@@ -55,6 +157,9 @@ export function includeRelations(...includes) {
55
157
  }
56
158
  /**
57
159
  * Relation registry used by database infrastructure.
160
+ *
161
+ * Relations are keyed by parent model and name, so two models may each
162
+ * define a relation with the same name.
58
163
  */
59
164
  export class RelationRegistry {
60
165
  relations = new Map();
@@ -63,12 +168,19 @@ export class RelationRegistry {
63
168
  */
64
169
  register(relation) {
65
170
  validateRelation(relation);
66
- if (this.relations.has(relation.name)) {
67
- throw new Error(`Relation "${relation.name}" is already registered.`);
171
+ const byName = this.relations.get(relation.parent);
172
+ if (byName?.has(relation.name)) {
173
+ throw new Error(`Relation "${relation.name}" is already registered for this parent.`);
68
174
  }
69
- this.relations.set(relation.name, Object.freeze({
175
+ const frozen = Object.freeze({
70
176
  ...relation,
71
- }));
177
+ });
178
+ if (byName) {
179
+ byName.set(relation.name, frozen);
180
+ }
181
+ else {
182
+ this.relations.set(relation.parent, new Map([[relation.name, frozen]]));
183
+ }
72
184
  return this;
73
185
  }
74
186
  /**
@@ -82,39 +194,70 @@ export class RelationRegistry {
82
194
  }
83
195
  /**
84
196
  * Gets a relation by name.
197
+ *
198
+ * Without `parent` the name must be unique across all parents; an
199
+ * ambiguous lookup throws.
85
200
  */
86
- get(name) {
87
- return this.relations.get(name);
201
+ get(name, parent) {
202
+ if (parent !== undefined) {
203
+ return this.relations.get(parent)?.get(name);
204
+ }
205
+ const matches = this.findByName(name);
206
+ if (matches.length > 1) {
207
+ throw new Error(`Relation "${name}" is registered for multiple parents; specify the parent.`);
208
+ }
209
+ return matches[0];
88
210
  }
89
211
  /**
90
212
  * Checks whether a relation exists.
91
213
  */
92
- has(name) {
93
- return this.relations.has(name);
214
+ has(name, parent) {
215
+ if (parent !== undefined) {
216
+ return this.relations.get(parent)?.has(name) ?? false;
217
+ }
218
+ return this.findByName(name).length > 0;
94
219
  }
95
220
  /**
96
- * Removes a relation.
221
+ * Removes a relation. Without `parent`, every relation with that name is
222
+ * removed.
97
223
  */
98
- remove(name) {
99
- return this.relations.delete(name);
224
+ remove(name, parent) {
225
+ if (parent !== undefined) {
226
+ const byName = this.relations.get(parent);
227
+ const removed = byName?.delete(name) ?? false;
228
+ if (byName && byName.size === 0) {
229
+ this.relations.delete(parent);
230
+ }
231
+ return removed;
232
+ }
233
+ let removed = false;
234
+ for (const [key, byName] of this.relations) {
235
+ if (byName.delete(name)) {
236
+ removed = true;
237
+ }
238
+ if (byName.size === 0) {
239
+ this.relations.delete(key);
240
+ }
241
+ }
242
+ return removed;
100
243
  }
101
244
  /**
102
245
  * Returns all registered relations.
103
246
  */
104
247
  all() {
105
- return Object.freeze([...this.relations.values()]);
248
+ return Object.freeze([...this.relations.values()].flatMap((byName) => [...byName.values()]));
106
249
  }
107
250
  /**
108
251
  * Returns relations for a specific parent entity.
109
252
  */
110
253
  forParent(parent) {
111
- return Object.freeze([...this.relations.values()].filter((relation) => relation.parent === parent));
254
+ return Object.freeze([...(this.relations.get(parent)?.values() ?? [])]);
112
255
  }
113
256
  /**
114
257
  * Returns relations for a specific child entity.
115
258
  */
116
259
  forChild(child) {
117
- return Object.freeze([...this.relations.values()].filter((relation) => relation.child === child));
260
+ return Object.freeze(this.all().filter((relation) => relation.child === child));
118
261
  }
119
262
  /**
120
263
  * Clears all registered relations.
@@ -126,7 +269,21 @@ export class RelationRegistry {
126
269
  * Returns the number of registered relations.
127
270
  */
128
271
  get size() {
129
- return this.relations.size;
272
+ let size = 0;
273
+ for (const byName of this.relations.values()) {
274
+ size += byName.size;
275
+ }
276
+ return size;
277
+ }
278
+ findByName(name) {
279
+ const matches = [];
280
+ for (const byName of this.relations.values()) {
281
+ const relation = byName.get(name);
282
+ if (relation) {
283
+ matches.push(relation);
284
+ }
285
+ }
286
+ return matches;
130
287
  }
131
288
  }
132
289
  /**
@@ -191,5 +348,15 @@ function validateRelationName(name) {
191
348
  if (typeof name !== "string" || name.trim().length === 0) {
192
349
  throw new TypeError("A relation name is required.");
193
350
  }
351
+ validateFieldName(name);
352
+ }
353
+ const FIELD_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
354
+ /**
355
+ * Validates a relation or select field name.
356
+ */
357
+ function validateFieldName(field) {
358
+ if (typeof field !== "string" || !FIELD_PATTERN.test(field)) {
359
+ throw new TypeError(`Invalid relation field name "${String(field)}".`);
360
+ }
194
361
  }
195
362
  //# sourceMappingURL=relations.definition.js.map
@@ -3,5 +3,6 @@
3
3
  *
4
4
  * Generic repository pattern with Prisma delegate support.
5
5
  */
6
- export { BaseRepository, type RepositoryDelegate, type BaseRepositoryOptions, } from "./repository.base.js";
6
+ export { BaseRepository, type RepositoryDelegate, type BaseRepositoryOptions, type SoftDeleteOptions, type CursorQueryOptions, type TransactionClientLike, } from "./repository.base.js";
7
+ export { mapRepositoryError, isPrismaErrorLike, toDatabaseOperation, toErrorMetadata, createAbortError, createTimeoutError, type RepositoryOperation, type RepositoryErrorContext, type PrismaErrorLike, } from "./repository.errors.js";
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -4,4 +4,5 @@
4
4
  * Generic repository pattern with Prisma delegate support.
5
5
  */
6
6
  export { BaseRepository, } from "./repository.base.js";
7
+ export { mapRepositoryError, isPrismaErrorLike, toDatabaseOperation, toErrorMetadata, createAbortError, createTimeoutError, } from "./repository.errors.js";
7
8
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,283 @@
1
+ import type { DatabaseOperationOptions, PaginatedResult, QueryOptions, Repository, SoftDeletableRepository, SortInput } from "../databaseType/databaseType.type.js";
2
+ import { type CursorPaginatedResult } from "../pagination/pagination.core.js";
3
+ import type { QueryBuilder } from "../queryBuilder/queryBuilder.core.js";
4
+ import type { QueryBuilderState } from "../queryBuilder/queryBuilder.type.js";
5
+ import type { RelationLoadOptions, RelationRegistry } from "../relations/relations.definition.js";
6
+ import { type RepositoryOperation } from "./repository.errors.js";
7
+ /**
8
+ * Generic Prisma-style delegate contract.
9
+ *
10
+ * This keeps the repository base class independent from generated
11
+ * Prisma model types while still supporting standard CRUD operations.
12
+ */
13
+ export interface RepositoryDelegate<TEntity, TId = string, TCreateInput = Partial<TEntity>, TUpdateInput = Partial<TEntity>, TWhereInput = unknown> {
14
+ findUnique(args: {
15
+ where: unknown;
16
+ }): Promise<TEntity | null>;
17
+ findFirst(args: {
18
+ where?: TWhereInput;
19
+ orderBy?: unknown;
20
+ select?: unknown;
21
+ }): Promise<TEntity | null>;
22
+ findMany(args?: {
23
+ where?: TWhereInput;
24
+ skip?: number;
25
+ take?: number;
26
+ orderBy?: unknown;
27
+ select?: unknown;
28
+ include?: unknown;
29
+ }): Promise<readonly TEntity[]>;
30
+ create(args: {
31
+ data: TCreateInput;
32
+ }): Promise<TEntity>;
33
+ update(args: {
34
+ where: unknown;
35
+ data: TUpdateInput;
36
+ }): Promise<TEntity>;
37
+ delete(args: {
38
+ where: unknown;
39
+ }): Promise<TEntity>;
40
+ count(args?: {
41
+ where?: TWhereInput;
42
+ }): Promise<number>;
43
+ upsert?(args: {
44
+ where: unknown;
45
+ create: TCreateInput;
46
+ update: TUpdateInput;
47
+ }): Promise<TEntity>;
48
+ createMany?(args: {
49
+ data: readonly TCreateInput[];
50
+ }): Promise<{
51
+ count: number;
52
+ }>;
53
+ deleteMany?(args: {
54
+ where?: TWhereInput;
55
+ }): Promise<{
56
+ count: number;
57
+ }>;
58
+ }
59
+ /**
60
+ * Soft-delete configuration.
61
+ */
62
+ export interface SoftDeleteOptions {
63
+ /**
64
+ * Nullable timestamp column marking deleted rows (default `deletedAt`).
65
+ */
66
+ readonly field?: string;
67
+ }
68
+ /**
69
+ * Options for constructing a repository.
70
+ */
71
+ export interface BaseRepositoryOptions {
72
+ readonly modelName?: string;
73
+ /**
74
+ * Primary key field used by `findById`, `update`, `delete` and friends
75
+ * (default `id`).
76
+ */
77
+ readonly idField?: string;
78
+ /**
79
+ * Enables soft deletion. Every read, count, exists, update and paginate
80
+ * path then excludes rows whose soft-delete field is set.
81
+ */
82
+ readonly softDelete?: boolean | SoftDeleteOptions;
83
+ /**
84
+ * Secret used to sign keyset cursors produced by `paginateCursor`.
85
+ */
86
+ readonly cursorSecret?: string;
87
+ /**
88
+ * Property on a transaction client that yields this model's delegate
89
+ * (default: `modelName` with a lower-cased first letter). Used by
90
+ * `withTransaction`.
91
+ */
92
+ readonly delegateKey?: string;
93
+ /**
94
+ * Relation registry used to validate `include` definitions passed to
95
+ * `findByQuery`.
96
+ */
97
+ readonly relations?: RelationRegistry;
98
+ /**
99
+ * Model identifier registered in `relations` for this repository's
100
+ * entity (default `modelName`).
101
+ */
102
+ readonly relationParent?: unknown;
103
+ }
104
+ /**
105
+ * Options for cursor pagination.
106
+ */
107
+ export interface CursorQueryOptions<TField extends string = string> extends DatabaseOperationOptions {
108
+ readonly cursor?: string | null;
109
+ readonly limit?: number;
110
+ /**
111
+ * Sort order. The id field is appended as a tiebreaker when absent.
112
+ */
113
+ readonly sort?: readonly SortInput<TField>[];
114
+ }
115
+ /**
116
+ * A Prisma transaction client (or any object exposing model delegates).
117
+ */
118
+ export type TransactionClientLike = Readonly<Record<string, unknown>>;
119
+ /**
120
+ * Generic base repository implementation.
121
+ *
122
+ * Concrete repositories should extend this class and provide the
123
+ * appropriate Prisma delegate plus any domain-specific behavior.
124
+ */
125
+ export declare abstract class BaseRepository<TEntity, TId = string, TCreateInput = Partial<TEntity>, TUpdateInput = Partial<TEntity>, TWhereInput = Record<string, unknown>> implements Repository<TEntity, TId, TCreateInput, TUpdateInput, TWhereInput>, SoftDeletableRepository<TEntity, TId, TCreateInput, TUpdateInput, TWhereInput> {
126
+ protected readonly delegate: RepositoryDelegate<TEntity, TId, TCreateInput, TUpdateInput, TWhereInput>;
127
+ protected readonly modelName: string;
128
+ protected readonly idField: string;
129
+ protected readonly softDeleteField?: string;
130
+ protected readonly cursorSecret?: string;
131
+ protected readonly delegateKey: string;
132
+ protected readonly relations?: RelationRegistry;
133
+ protected readonly relationParent: unknown;
134
+ /**
135
+ * When true, soft-deleted rows are visible to reads (see `withDeleted`).
136
+ */
137
+ protected readonly includeDeleted: boolean;
138
+ constructor(delegate: RepositoryDelegate<TEntity, TId, TCreateInput, TUpdateInput, TWhereInput>, options?: BaseRepositoryOptions);
139
+ /**
140
+ * Returns a copy of this repository bound to a transaction client's
141
+ * delegate, so operations run inside the transaction.
142
+ */
143
+ withTransaction(transaction: TransactionClientLike): this;
144
+ /**
145
+ * Returns a copy of this repository bound to a different delegate.
146
+ */
147
+ withDelegate(delegate: RepositoryDelegate<TEntity, TId, TCreateInput, TUpdateInput, TWhereInput>): this;
148
+ /**
149
+ * Returns a copy of this repository whose reads include soft-deleted
150
+ * rows.
151
+ */
152
+ withDeleted(): this;
153
+ /**
154
+ * Finds an entity by its primary identifier.
155
+ */
156
+ findById(id: TId, options?: DatabaseOperationOptions): Promise<TEntity | null>;
157
+ /**
158
+ * Finds the first entity matching a filter.
159
+ */
160
+ findOne(filter: TWhereInput, options?: DatabaseOperationOptions): Promise<TEntity | null>;
161
+ /**
162
+ * Finds all entities matching a filter.
163
+ */
164
+ findMany(filter?: TWhereInput, options?: DatabaseOperationOptions): Promise<readonly TEntity[]>;
165
+ /**
166
+ * Finds entities using pagination and sorting.
167
+ */
168
+ findPaginated<TField extends string = string>(filter?: TWhereInput, options?: QueryOptions<TField>): Promise<PaginatedResult<TEntity>>;
169
+ /**
170
+ * Alias of {@link findPaginated}.
171
+ */
172
+ paginate<TField extends string = string>(filter?: TWhereInput, options?: QueryOptions<TField>): Promise<PaginatedResult<TEntity>>;
173
+ /**
174
+ * Finds entities using keyset (cursor) pagination.
175
+ *
176
+ * Rows are ordered by `options.sort` (the id field is appended as a
177
+ * tiebreaker), `limit + 1` rows are fetched and the extra row decides
178
+ * `hasNextPage`. Cursors are validated against the sort fields and, when
179
+ * `cursorSecret` is configured, signed.
180
+ */
181
+ paginateCursor<TField extends string = string>(filter?: TWhereInput, options?: CursorQueryOptions<TField>): Promise<CursorPaginatedResult<TEntity>>;
182
+ /**
183
+ * Finds entities from a query builder (or its built state), applying the
184
+ * filter, sort, select, include and pagination it carries.
185
+ *
186
+ * `options.includeDeleted` and `options.depth` control how relation
187
+ * includes are resolved (see `toPrismaInclude`); by default soft-deleted
188
+ * rows of collection relations are filtered whenever this repository
189
+ * filters its own rows.
190
+ */
191
+ findByQuery<TField extends string = string>(query: QueryBuilder<TField> | QueryBuilderState<TField>, options?: RelationLoadOptions): Promise<readonly TEntity[]>;
192
+ /**
193
+ * Creates a new entity.
194
+ */
195
+ create(input: TCreateInput, options?: DatabaseOperationOptions): Promise<TEntity>;
196
+ /**
197
+ * Creates many entities and returns the number created.
198
+ */
199
+ createMany(inputs: readonly TCreateInput[], options?: DatabaseOperationOptions): Promise<number>;
200
+ /**
201
+ * Updates an entity by its identifier.
202
+ */
203
+ update(id: TId, input: TUpdateInput, options?: DatabaseOperationOptions): Promise<TEntity>;
204
+ /**
205
+ * Deletes an entity by its identifier (hard delete).
206
+ */
207
+ delete(id: TId, options?: DatabaseOperationOptions): Promise<void>;
208
+ /**
209
+ * Deletes every entity matching a filter (hard delete, including
210
+ * soft-deleted rows) and returns the number removed.
211
+ */
212
+ deleteMany(filter: TWhereInput, options?: DatabaseOperationOptions): Promise<number>;
213
+ /**
214
+ * Marks an entity as deleted by setting its soft-delete field.
215
+ */
216
+ softDelete(id: TId, options?: DatabaseOperationOptions): Promise<TEntity>;
217
+ /**
218
+ * Restores a soft-deleted entity.
219
+ */
220
+ restore(id: TId, options?: DatabaseOperationOptions): Promise<TEntity>;
221
+ /**
222
+ * Finds soft-deleted entities matching a filter.
223
+ */
224
+ findDeleted(filter?: TWhereInput, options?: DatabaseOperationOptions): Promise<readonly TEntity[]>;
225
+ /**
226
+ * Checks whether an entity exists.
227
+ */
228
+ exists(filter: TWhereInput, options?: DatabaseOperationOptions): Promise<boolean>;
229
+ /**
230
+ * Counts entities matching a filter.
231
+ */
232
+ count(filter?: TWhereInput, options?: DatabaseOperationOptions): Promise<number>;
233
+ /**
234
+ * Updates an entity if it exists, otherwise creates it.
235
+ */
236
+ upsert(where: TWhereInput, create: TCreateInput, update: TUpdateInput, options?: DatabaseOperationOptions): Promise<TEntity>;
237
+ /**
238
+ * Executes a repository operation and normalizes database failures.
239
+ *
240
+ * Honours `options.signal` for the whole duration of the call and
241
+ * `options.timeoutMs` as a client-side deadline (the underlying query is
242
+ * not cancelled server-side).
243
+ */
244
+ protected execute<TResult>(operation: RepositoryOperation | string, callback: () => Promise<TResult>, options?: DatabaseOperationOptions): Promise<TResult>;
245
+ /**
246
+ * Validates an entity identifier.
247
+ */
248
+ protected validateId(id: TId): void;
249
+ /**
250
+ * Validates a repository filter.
251
+ */
252
+ protected validateFilter(filter: TWhereInput): void;
253
+ /**
254
+ * Builds the primary-key `where` for an identifier.
255
+ */
256
+ protected whereId(id: TId): Record<string, unknown>;
257
+ /**
258
+ * Builds the primary-key `where` for a *unique* operation (`update`),
259
+ * folding the soft-delete scope in as a sibling of the id rather than
260
+ * wrapping it in `AND`. Prisma's `WhereUniqueInput` requires the unique
261
+ * field at the top level, so `{ AND: [{ id }, { deletedAt: null }] }` is
262
+ * rejected with a validation error.
263
+ */
264
+ protected whereUniqueId(id: TId): Record<string, unknown>;
265
+ /**
266
+ * Applies the soft-delete scope to a filter when enabled.
267
+ */
268
+ protected scope(filter?: TWhereInput): TWhereInput | undefined;
269
+ /**
270
+ * Converts generic sort definitions into Prisma-compatible orderBy.
271
+ *
272
+ * Field names and directions are validated the same way the query
273
+ * builder validates them (`toPrismaOrderBy`), so a sort taken from
274
+ * request input cannot reach the delegate with an arbitrary key or an
275
+ * unsupported direction.
276
+ */
277
+ protected buildOrderBy<TField extends string>(sort?: readonly SortInput<TField>[]): ReadonlyArray<Record<string, string>> | undefined;
278
+ private isScoped;
279
+ private requireSoftDelete;
280
+ private buildCursorSort;
281
+ private rebind;
282
+ }
283
+ //# sourceMappingURL=repository.base.d.ts.map