@zudojs/database 0.0.1 → 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.
- package/README.md +223 -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 +150 -0
- package/dist/databaseClient/databaseClient.core.d.ts +165 -14
- package/dist/databaseClient/databaseClient.core.js +304 -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 +10 -0
- 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 +128 -0
- package/dist/locks/locks.core.js +144 -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 +141 -0
- 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/migration/migration.types.js +2 -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 +279 -0
- 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 +420 -0
- package/dist/queryBuilder/queryBuilder.factory.d.ts +6 -0
- package/dist/queryBuilder/queryBuilder.factory.js +8 -0
- package/dist/queryBuilder/queryBuilder.filter.d.ts +170 -0
- package/dist/queryBuilder/queryBuilder.filter.js +497 -0
- 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/queryBuilder/queryBuilder.type.js +2 -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 +362 -0
- package/dist/repository/index.d.ts +2 -1
- package/dist/repository/index.js +1 -0
- package/dist/repository/repository.base.d.ts +270 -0
- package/dist/repository/repository.base.js +350 -84
- 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 +35 -19
- 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/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/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.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/pagination/index.d.ts.map +0 -1
- package/dist/pagination/index.js.map +0 -1
- package/dist/queryBuilder/index.d.ts.map +0 -1
- package/dist/queryBuilder/index.js.map +0 -1
- package/dist/relations/index.d.ts.map +0 -1
- package/dist/relations/index.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
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default maximum include depth.
|
|
3
|
+
*/
|
|
4
|
+
export const DEFAULT_INCLUDE_DEPTH = 5;
|
|
5
|
+
/**
|
|
6
|
+
* Creates a one-to-one relation definition.
|
|
7
|
+
*/
|
|
8
|
+
export function oneToOne(definition) {
|
|
9
|
+
const relation = Object.freeze({
|
|
10
|
+
...definition,
|
|
11
|
+
type: "one-to-one",
|
|
12
|
+
});
|
|
13
|
+
validateRelation(relation);
|
|
14
|
+
return relation;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Creates a one-to-many relation definition.
|
|
18
|
+
*/
|
|
19
|
+
export function oneToMany(definition) {
|
|
20
|
+
const relation = Object.freeze({
|
|
21
|
+
...definition,
|
|
22
|
+
type: "one-to-many",
|
|
23
|
+
});
|
|
24
|
+
validateRelation(relation);
|
|
25
|
+
return relation;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Creates a many-to-one relation definition.
|
|
29
|
+
*/
|
|
30
|
+
export function manyToOne(definition) {
|
|
31
|
+
const relation = Object.freeze({
|
|
32
|
+
...definition,
|
|
33
|
+
type: "many-to-one",
|
|
34
|
+
});
|
|
35
|
+
validateRelation(relation);
|
|
36
|
+
return relation;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates a many-to-many relation definition.
|
|
40
|
+
*/
|
|
41
|
+
export function manyToMany(definition) {
|
|
42
|
+
const relation = Object.freeze({
|
|
43
|
+
...definition,
|
|
44
|
+
type: "many-to-many",
|
|
45
|
+
});
|
|
46
|
+
validateRelation(relation);
|
|
47
|
+
return relation;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Creates a relation include definition.
|
|
51
|
+
*/
|
|
52
|
+
export function includeRelation(relation, options = {}) {
|
|
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
|
+
}
|
|
64
|
+
return Object.freeze({
|
|
65
|
+
relation,
|
|
66
|
+
select: options.select
|
|
67
|
+
? Object.freeze([...new Set(options.select)])
|
|
68
|
+
: undefined,
|
|
69
|
+
include: options.include ? Object.freeze([...options.include]) : undefined,
|
|
70
|
+
});
|
|
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
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Creates a nested relation include.
|
|
154
|
+
*/
|
|
155
|
+
export function includeRelations(...includes) {
|
|
156
|
+
return Object.freeze([...includes]);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
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.
|
|
163
|
+
*/
|
|
164
|
+
export class RelationRegistry {
|
|
165
|
+
relations = new Map();
|
|
166
|
+
/**
|
|
167
|
+
* Registers a relation.
|
|
168
|
+
*/
|
|
169
|
+
register(relation) {
|
|
170
|
+
validateRelation(relation);
|
|
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.`);
|
|
174
|
+
}
|
|
175
|
+
const frozen = Object.freeze({
|
|
176
|
+
...relation,
|
|
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
|
+
}
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Registers multiple relations.
|
|
188
|
+
*/
|
|
189
|
+
registerMany(relations) {
|
|
190
|
+
for (const relation of relations) {
|
|
191
|
+
this.register(relation);
|
|
192
|
+
}
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Gets a relation by name.
|
|
197
|
+
*
|
|
198
|
+
* Without `parent` the name must be unique across all parents; an
|
|
199
|
+
* ambiguous lookup throws.
|
|
200
|
+
*/
|
|
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];
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Checks whether a relation exists.
|
|
213
|
+
*/
|
|
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;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Removes a relation. Without `parent`, every relation with that name is
|
|
222
|
+
* removed.
|
|
223
|
+
*/
|
|
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;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Returns all registered relations.
|
|
246
|
+
*/
|
|
247
|
+
all() {
|
|
248
|
+
return Object.freeze([...this.relations.values()].flatMap((byName) => [...byName.values()]));
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Returns relations for a specific parent entity.
|
|
252
|
+
*/
|
|
253
|
+
forParent(parent) {
|
|
254
|
+
return Object.freeze([...(this.relations.get(parent)?.values() ?? [])]);
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Returns relations for a specific child entity.
|
|
258
|
+
*/
|
|
259
|
+
forChild(child) {
|
|
260
|
+
return Object.freeze(this.all().filter((relation) => relation.child === child));
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Clears all registered relations.
|
|
264
|
+
*/
|
|
265
|
+
clear() {
|
|
266
|
+
this.relations.clear();
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Returns the number of registered relations.
|
|
270
|
+
*/
|
|
271
|
+
get 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;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Creates a relation registry.
|
|
291
|
+
*/
|
|
292
|
+
export function createRelationRegistry(relations = []) {
|
|
293
|
+
const registry = new RelationRegistry();
|
|
294
|
+
registry.registerMany(relations);
|
|
295
|
+
return registry;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Validates a relation definition.
|
|
299
|
+
*/
|
|
300
|
+
export function validateRelation(relation) {
|
|
301
|
+
if (!relation || typeof relation !== "object") {
|
|
302
|
+
throw new TypeError("A relation definition is required.");
|
|
303
|
+
}
|
|
304
|
+
validateRelationName(relation.name);
|
|
305
|
+
if (relation.parent === undefined || relation.parent === null) {
|
|
306
|
+
throw new TypeError(`Relation "${relation.name}" requires a parent entity.`);
|
|
307
|
+
}
|
|
308
|
+
if (relation.child === undefined || relation.child === null) {
|
|
309
|
+
throw new TypeError(`Relation "${relation.name}" requires a child entity.`);
|
|
310
|
+
}
|
|
311
|
+
if (!isRelationType(relation.type)) {
|
|
312
|
+
throw new TypeError(`Relation "${relation.name}" has an invalid relation type.`);
|
|
313
|
+
}
|
|
314
|
+
if (typeof relation.foreignKey !== "string" ||
|
|
315
|
+
relation.foreignKey.trim().length === 0) {
|
|
316
|
+
throw new TypeError(`Relation "${relation.name}" requires a foreign key.`);
|
|
317
|
+
}
|
|
318
|
+
if (typeof relation.referencedKey !== "string" ||
|
|
319
|
+
relation.referencedKey.trim().length === 0) {
|
|
320
|
+
throw new TypeError(`Relation "${relation.name}" requires a referenced key.`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Checks whether a value is a supported relation type.
|
|
325
|
+
*/
|
|
326
|
+
export function isRelationType(value) {
|
|
327
|
+
return (value === "one-to-one" ||
|
|
328
|
+
value === "one-to-many" ||
|
|
329
|
+
value === "many-to-one" ||
|
|
330
|
+
value === "many-to-many");
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Returns whether the relation represents a collection.
|
|
334
|
+
*/
|
|
335
|
+
export function isCollectionRelation(relation) {
|
|
336
|
+
return relation.type === "one-to-many" || relation.type === "many-to-many";
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Returns whether the relation represents a single entity.
|
|
340
|
+
*/
|
|
341
|
+
export function isSingleRelation(relation) {
|
|
342
|
+
return relation.type === "one-to-one" || relation.type === "many-to-one";
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Validates a relation name.
|
|
346
|
+
*/
|
|
347
|
+
function validateRelationName(name) {
|
|
348
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
349
|
+
throw new TypeError("A relation name is required.");
|
|
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
|
+
}
|
|
361
|
+
}
|
|
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,270 @@
|
|
|
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
|
+
* Applies the soft-delete scope to a filter when enabled.
|
|
259
|
+
*/
|
|
260
|
+
protected scope(filter?: TWhereInput): TWhereInput | undefined;
|
|
261
|
+
/**
|
|
262
|
+
* Converts generic sort definitions into Prisma-compatible orderBy.
|
|
263
|
+
*/
|
|
264
|
+
protected buildOrderBy<TField extends string>(sort?: readonly SortInput<TField>[]): ReadonlyArray<Record<string, string>> | undefined;
|
|
265
|
+
private isScoped;
|
|
266
|
+
private requireSoftDelete;
|
|
267
|
+
private buildCursorSort;
|
|
268
|
+
private rebind;
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=repository.base.d.ts.map
|