@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.
- package/LICENSE +21 -0
- package/README.md +225 -17
- package/dist/cache/cache.memory.d.ts +167 -0
- package/dist/cache/cache.memory.js +186 -40
- package/dist/cache/index.d.ts +2 -2
- package/dist/cache/index.js +2 -2
- package/dist/database/database.core.d.ts +91 -0
- package/dist/database/database.core.js +21 -4
- package/dist/databaseClient/databaseClient.core.d.ts +171 -14
- package/dist/databaseClient/databaseClient.core.js +350 -70
- package/dist/databaseClient/databaseClient.errors.d.ts +112 -0
- package/dist/databaseClient/databaseClient.errors.js +425 -0
- package/dist/databaseClient/index.d.ts +2 -1
- package/dist/databaseClient/index.js +2 -1
- package/dist/databaseConnection/databaseConnection.manager.d.ts +163 -0
- package/dist/databaseConnection/databaseConnection.manager.js +138 -76
- package/dist/databaseConnection/index.d.ts +1 -1
- package/dist/databaseType/databaseType.type.d.ts +224 -0
- package/dist/databaseType/databaseType.type.js +0 -35
- package/dist/health/health.check.d.ts +74 -0
- package/dist/health/health.check.js +78 -47
- package/dist/health/index.d.ts +1 -1
- package/dist/health/index.js +1 -1
- package/dist/index.d.ts +14 -14
- package/dist/index.js +11 -11
- package/dist/locks/index.d.ts +2 -2
- package/dist/locks/index.js +2 -2
- package/dist/locks/locks.core.d.ts +132 -0
- package/dist/locks/locks.core.js +150 -85
- package/dist/migration/index.d.ts +3 -1
- package/dist/migration/index.js +2 -0
- package/dist/migration/migration.dialect.d.ts +65 -0
- package/dist/migration/migration.dialect.js +63 -0
- package/dist/migration/migration.helpers.d.ts +65 -0
- package/dist/migration/migration.helpers.js +60 -18
- package/dist/migration/migration.runner.d.ts +75 -0
- package/dist/migration/migration.runner.js +195 -107
- package/dist/migration/migration.types.d.ts +85 -0
- package/dist/pagination/index.d.ts +2 -1
- package/dist/pagination/index.js +2 -1
- package/dist/pagination/pagination.core.d.ts +163 -0
- package/dist/pagination/pagination.core.js +78 -9
- package/dist/pagination/pagination.keyset.d.ts +55 -0
- package/dist/pagination/pagination.keyset.js +108 -0
- package/dist/queryBuilder/index.d.ts +4 -3
- package/dist/queryBuilder/index.js +3 -2
- package/dist/queryBuilder/queryBuilder.core.d.ts +178 -0
- package/dist/queryBuilder/queryBuilder.core.js +92 -14
- package/dist/queryBuilder/queryBuilder.factory.d.ts +6 -0
- package/dist/queryBuilder/queryBuilder.factory.js +0 -25
- package/dist/queryBuilder/queryBuilder.filter.d.ts +170 -0
- package/dist/queryBuilder/queryBuilder.filter.js +191 -30
- package/dist/queryBuilder/queryBuilder.prisma.d.ts +60 -0
- package/dist/queryBuilder/queryBuilder.prisma.js +233 -0
- package/dist/queryBuilder/queryBuilder.type.d.ts +48 -0
- package/dist/relations/index.d.ts +1 -1
- package/dist/relations/index.js +1 -1
- package/dist/relations/relations.definition.d.ts +184 -0
- package/dist/relations/relations.definition.js +186 -19
- package/dist/repository/index.d.ts +2 -1
- package/dist/repository/index.js +1 -0
- package/dist/repository/repository.base.d.ts +283 -0
- package/dist/repository/repository.base.js +370 -89
- package/dist/repository/repository.errors.d.ts +60 -0
- package/dist/repository/repository.errors.js +235 -0
- package/dist/seed/index.d.ts +2 -2
- package/dist/seed/index.js +1 -1
- package/dist/seed/seed.runner.d.ts +160 -0
- package/dist/seed/seed.runner.js +194 -225
- package/dist/transaction/index.d.ts +1 -1
- package/dist/transaction/index.js +1 -1
- package/dist/transaction/transaction.core.d.ts +117 -0
- package/dist/transaction/transaction.core.js +83 -56
- package/dist/unitOfWork/unitOfWork.core.d.ts +44 -0
- package/dist/unitOfWork/unitOfWork.core.js +9 -20
- package/package.json +44 -24
- package/dist/cache/cache.memory.d.ts.map +0 -1
- package/dist/cache/cache.memory.js.map +0 -1
- package/dist/cache/index.d.ts.map +0 -1
- package/dist/cache/index.js.map +0 -1
- package/dist/database/database.core.d.ts.map +0 -1
- package/dist/database/database.core.js.map +0 -1
- package/dist/database/index.d.ts.map +0 -1
- package/dist/database/index.js.map +0 -1
- package/dist/databaseClient/databaseClient.core.d.ts.map +0 -1
- package/dist/databaseClient/databaseClient.core.js.map +0 -1
- package/dist/databaseClient/databaseClient.logger.d.ts.map +0 -1
- package/dist/databaseClient/databaseClient.logger.js.map +0 -1
- package/dist/databaseClient/index.d.ts.map +0 -1
- package/dist/databaseClient/index.js.map +0 -1
- package/dist/databaseConnection/databaseConnection.manager.d.ts.map +0 -1
- package/dist/databaseConnection/databaseConnection.manager.js.map +0 -1
- package/dist/databaseConnection/index.d.ts.map +0 -1
- package/dist/databaseConnection/index.js.map +0 -1
- package/dist/databaseType/databaseType.type.d.ts.map +0 -1
- package/dist/databaseType/databaseType.type.js.map +0 -1
- package/dist/databaseType/index.d.ts.map +0 -1
- package/dist/databaseType/index.js.map +0 -1
- package/dist/health/health.check.d.ts.map +0 -1
- package/dist/health/health.check.js.map +0 -1
- package/dist/health/index.d.ts.map +0 -1
- package/dist/health/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/locks/index.d.ts.map +0 -1
- package/dist/locks/index.js.map +0 -1
- package/dist/locks/locks.core.d.ts.map +0 -1
- package/dist/locks/locks.core.js.map +0 -1
- package/dist/migration/index.d.ts.map +0 -1
- package/dist/migration/index.js.map +0 -1
- package/dist/migration/migration.helpers.d.ts.map +0 -1
- package/dist/migration/migration.helpers.js.map +0 -1
- package/dist/migration/migration.runner.d.ts.map +0 -1
- package/dist/migration/migration.runner.js.map +0 -1
- package/dist/migration/migration.types.d.ts.map +0 -1
- package/dist/migration/migration.types.js.map +0 -1
- package/dist/pagination/index.d.ts.map +0 -1
- package/dist/pagination/index.js.map +0 -1
- package/dist/pagination/pagination.core.d.ts.map +0 -1
- package/dist/pagination/pagination.core.js.map +0 -1
- package/dist/queryBuilder/index.d.ts.map +0 -1
- package/dist/queryBuilder/index.js.map +0 -1
- package/dist/queryBuilder/queryBuilder.core.d.ts.map +0 -1
- package/dist/queryBuilder/queryBuilder.core.js.map +0 -1
- package/dist/queryBuilder/queryBuilder.factory.d.ts.map +0 -1
- package/dist/queryBuilder/queryBuilder.factory.js.map +0 -1
- package/dist/queryBuilder/queryBuilder.filter.d.ts.map +0 -1
- package/dist/queryBuilder/queryBuilder.filter.js.map +0 -1
- package/dist/queryBuilder/queryBuilder.type.d.ts.map +0 -1
- package/dist/queryBuilder/queryBuilder.type.js.map +0 -1
- package/dist/relations/index.d.ts.map +0 -1
- package/dist/relations/index.js.map +0 -1
- package/dist/relations/relations.definition.d.ts.map +0 -1
- package/dist/relations/relations.definition.js.map +0 -1
- package/dist/repository/index.d.ts.map +0 -1
- package/dist/repository/index.js.map +0 -1
- package/dist/repository/repository.base.d.ts.map +0 -1
- package/dist/repository/repository.base.js.map +0 -1
- package/dist/seed/index.d.ts.map +0 -1
- package/dist/seed/index.js.map +0 -1
- package/dist/seed/seed.runner.d.ts.map +0 -1
- package/dist/seed/seed.runner.js.map +0 -1
- package/dist/transaction/index.d.ts.map +0 -1
- package/dist/transaction/index.js.map +0 -1
- package/dist/transaction/transaction.core.d.ts.map +0 -1
- package/dist/transaction/transaction.core.js.map +0 -1
- package/dist/unitOfWork/index.d.ts.map +0 -1
- package/dist/unitOfWork/index.js.map +0 -1
- package/dist/unitOfWork/unitOfWork.core.d.ts.map +0 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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
|
package/dist/repository/index.js
CHANGED
|
@@ -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
|