vsrepo 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +180 -16
- package/README.pt-BR.md +180 -16
- package/dist/VSRepoAdapter.d.ts +38 -0
- package/dist/VSRepository.d.ts +45 -5
- package/dist/VSRepository.js +77 -11
- package/dist/decorators/query-method.decorator.d.ts +26 -3
- package/dist/decorators/query-method.decorator.js +25 -2
- package/dist/index.d.ts +8 -1
- package/dist/index.js +6 -1
- package/dist/internal/enums/vsrepo-error-type.enum.d.ts +1 -1
- package/dist/internal/enums/vsrepo-error-type.enum.js +1 -1
- package/dist/internal/resolvers/dynamic-methods.resolver.js +32 -6
- package/dist/internal/utils/db-arg.util.d.ts +15 -0
- package/dist/internal/utils/db-arg.util.js +22 -0
- package/dist/internal/utils/with-db.util.d.ts +19 -0
- package/dist/internal/utils/with-db.util.js +23 -0
- package/dist/internal/validators/decorators.validator.js +2 -0
- package/dist/internal/validators/vsrepo.validator.d.ts +8 -1
- package/dist/internal/validators/vsrepo.validator.js +23 -0
- package/dist/types/decorators/query-method-options.type.d.ts +35 -1
- package/dist/types/utils/decimal-like.type.d.ts +23 -0
- package/dist/types/utils/decimal-like.type.js +2 -0
- package/dist/types/utils/numeric-keys.type.d.ts +24 -0
- package/dist/types/utils/numeric-keys.type.js +2 -0
- package/dist/types/utils/numeric-like.type.d.ts +10 -0
- package/dist/types/utils/numeric-like.type.js +2 -0
- package/dist/types/utils/primitive.type.d.ts +2 -1
- package/dist/types/utils/query-args.type.d.ts +30 -0
- package/dist/types/utils/query-args.type.js +2 -0
- package/dist/types/utils/query-method-arg.type.d.ts +3 -2
- package/dist/types/utils/restrict-method-options.type.d.ts +14 -0
- package/dist/types/utils/restrict-method-options.type.js +2 -0
- package/dist/types/vsrepo/vsrepo-query-options.type.d.ts +14 -0
- package/package.json +1 -1
package/dist/VSRepository.js
CHANGED
|
@@ -102,10 +102,12 @@ class VSRepository {
|
|
|
102
102
|
wherePkIn(pks) {
|
|
103
103
|
return { [this.pkName]: { in: pks } };
|
|
104
104
|
}
|
|
105
|
-
async execBaseMethod(fn, methodName, optionsUnchecked) {
|
|
106
|
-
const optionsChecked =
|
|
107
|
-
? this.validator.
|
|
108
|
-
:
|
|
105
|
+
async execBaseMethod(fn, methodName, optionsUnchecked, opsType = "common") {
|
|
106
|
+
const optionsChecked = opsType === "common"
|
|
107
|
+
? this.validator.validateMethodOptions(optionsUnchecked)
|
|
108
|
+
: opsType === "restrict"
|
|
109
|
+
? this.validator.validateRestrictMethodOptions(optionsUnchecked)
|
|
110
|
+
: this.validator.validateGetAllMethodOptions(optionsUnchecked);
|
|
109
111
|
optionsChecked.db ??= this.getDbClient();
|
|
110
112
|
const start = this.logger.startPerformLog("run " + methodName);
|
|
111
113
|
try {
|
|
@@ -139,6 +141,8 @@ class VSRepository {
|
|
|
139
141
|
* Use `$1`, `$2`, ... placeholders for values passed via `options.args` —
|
|
140
142
|
* never interpolate values directly into `query`, to avoid SQL injection.
|
|
141
143
|
* Set `options.modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements.
|
|
144
|
+
* Set `options.singleResult: true` to collapse an array result into its
|
|
145
|
+
* first element (`null` if empty) — see {@link VSRepoQueryOptions.singleResult}.
|
|
142
146
|
*
|
|
143
147
|
* @example
|
|
144
148
|
* ```typescript
|
|
@@ -151,6 +155,13 @@ class VSRepository {
|
|
|
151
155
|
* 'UPDATE "user" SET active = true WHERE id = $1',
|
|
152
156
|
* { args: ["123"], modifying: true },
|
|
153
157
|
* );
|
|
158
|
+
*
|
|
159
|
+
* // Only one row is ever expected here, so `singleResult` collapses the
|
|
160
|
+
* // array into a single object (or `null` when no row matches).
|
|
161
|
+
* const user = await userRepository.query<User | null>(
|
|
162
|
+
* 'SELECT * FROM "user" WHERE id = $1 LIMIT 1',
|
|
163
|
+
* { args: ["123"], singleResult: true },
|
|
164
|
+
* );
|
|
154
165
|
* ```
|
|
155
166
|
*/
|
|
156
167
|
async query(query, options) {
|
|
@@ -166,8 +177,11 @@ class VSRepository {
|
|
|
166
177
|
db: optionsValidated.db,
|
|
167
178
|
modifying: optionsValidated.modifying ?? false,
|
|
168
179
|
});
|
|
180
|
+
const resolved = optionsValidated.singleResult && Array.isArray(result)
|
|
181
|
+
? (result[0] ?? null)
|
|
182
|
+
: result;
|
|
169
183
|
this.logger.endPerformLog(start);
|
|
170
|
-
return
|
|
184
|
+
return resolved;
|
|
171
185
|
}
|
|
172
186
|
catch (err) {
|
|
173
187
|
this.logger.endPerformLog(start);
|
|
@@ -195,7 +209,7 @@ class VSRepository {
|
|
|
195
209
|
return this.execBaseMethod((opt) => this.adapter.findMany(this.mergeWheresResolver.resolve(opt.see, {}), {
|
|
196
210
|
...opt,
|
|
197
211
|
order: opt.order ?? this.defaultOrdering,
|
|
198
|
-
}), "getAll", options);
|
|
212
|
+
}), "getAll", options, "getAll");
|
|
199
213
|
}
|
|
200
214
|
/** Creates or updates (upsert) a record. */
|
|
201
215
|
async save(obj, options) {
|
|
@@ -217,7 +231,7 @@ class VSRepository {
|
|
|
217
231
|
if (!Array.isArray(pks)) {
|
|
218
232
|
this.fail("'pks' must be a valid array", vsrepo_error_type_enum_1.VSRepoErrorType.BASE);
|
|
219
233
|
}
|
|
220
|
-
return this.execBaseMethod(opt => this.adapter.deleteMany(this.mergeWheresResolver.resolve(opt.see, this.wherePkIn(pks)), opt), "removeList", options);
|
|
234
|
+
return this.execBaseMethod(opt => this.adapter.deleteMany(this.mergeWheresResolver.resolve(opt.see, this.wherePkIn(pks)), opt), "removeList", options, "restrict");
|
|
221
235
|
}
|
|
222
236
|
/** Partially updates an existing record by its primary key (PK). */
|
|
223
237
|
async patch(pk, obj, options) {
|
|
@@ -233,11 +247,11 @@ class VSRepository {
|
|
|
233
247
|
}
|
|
234
248
|
/** Returns the total number of records. */
|
|
235
249
|
async total(options) {
|
|
236
|
-
return this.execBaseMethod(opt => this.adapter.count(this.mergeWheresResolver.resolve(opt.see, {}), opt), "total", options);
|
|
250
|
+
return this.execBaseMethod(opt => this.adapter.count(this.mergeWheresResolver.resolve(opt.see, {}), opt), "total", options, "restrict");
|
|
237
251
|
}
|
|
238
252
|
/** Checks whether a record exists by its primary key (PK). */
|
|
239
253
|
async has(pk, options) {
|
|
240
|
-
return this.execBaseMethod(opt => this.adapter.exists(this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "has", options);
|
|
254
|
+
return this.execBaseMethod(opt => this.adapter.exists(this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "has", options, "restrict");
|
|
241
255
|
}
|
|
242
256
|
/** Marks a record as deleted (soft-delete). Requires `softRemoveKey` to be configured on the repository. */
|
|
243
257
|
async softRemove(pk, options) {
|
|
@@ -256,7 +270,7 @@ class VSRepository {
|
|
|
256
270
|
this.fail("'pks' must be a valid array", vsrepo_error_type_enum_1.VSRepoErrorType.BASE);
|
|
257
271
|
}
|
|
258
272
|
const key = this.softRemoveKey;
|
|
259
|
-
return this.execBaseMethod(opt => this.adapter.updateMany(this.mergeWheresResolver.resolve(opt.see ?? "all", this.wherePkIn(pks)), { [key]: new Date() }, opt), "softRemoveList", options);
|
|
273
|
+
return this.execBaseMethod(opt => this.adapter.updateMany(this.mergeWheresResolver.resolve(opt.see ?? "all", this.wherePkIn(pks)), { [key]: new Date() }, opt), "softRemoveList", options, "restrict");
|
|
260
274
|
}
|
|
261
275
|
/** Restores a record previously marked as deleted (soft-delete). Requires `softRemoveKey` to be configured on the repository. */
|
|
262
276
|
async restore(pk, options) {
|
|
@@ -275,7 +289,59 @@ class VSRepository {
|
|
|
275
289
|
this.fail("'pks' must be a valid array", vsrepo_error_type_enum_1.VSRepoErrorType.BASE);
|
|
276
290
|
}
|
|
277
291
|
const key = this.softRemoveKey;
|
|
278
|
-
return this.execBaseMethod(opt => this.adapter.updateMany(this.mergeWheresResolver.resolve(opt.see ?? "all", this.wherePkIn(pks)), { [key]: null }, opt), "restoreList", options);
|
|
292
|
+
return this.execBaseMethod(opt => this.adapter.updateMany(this.mergeWheresResolver.resolve(opt.see ?? "all", this.wherePkIn(pks)), { [key]: null }, opt), "restoreList", options, "restrict");
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Atomically adds `value` to a numeric field of the record identified
|
|
296
|
+
* by `pk`, evaluated server-side against the row's current value (e.g.
|
|
297
|
+
* `saldo = saldo + value`) — not a fetch-then-save round trip.
|
|
298
|
+
*/
|
|
299
|
+
async increment(pk, field, value, options) {
|
|
300
|
+
this.validator.assertIsNumericLike(value);
|
|
301
|
+
return this.execBaseMethod(opt => this.adapter.incrementOne(field, value, this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "increment", options);
|
|
302
|
+
}
|
|
303
|
+
/** Same as {@link VSRepository.increment}, subtracting `value` instead of adding it. */
|
|
304
|
+
async decrement(pk, field, value, options) {
|
|
305
|
+
this.validator.assertIsNumericLike(value);
|
|
306
|
+
return this.execBaseMethod(opt => this.adapter.decrementOne(field, value, this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "decrement", options);
|
|
307
|
+
}
|
|
308
|
+
/** Same as {@link VSRepository.increment}, multiplying the field's current value by `value`. */
|
|
309
|
+
async multiply(pk, field, value, options) {
|
|
310
|
+
this.validator.assertIsNumericLike(value);
|
|
311
|
+
return this.execBaseMethod(opt => this.adapter.multiplyOne(field, value, this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "multiply", options);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Same as {@link VSRepository.increment}, dividing the field's current
|
|
315
|
+
* value by `value`. Division-by-zero behavior depends on the adapter/
|
|
316
|
+
* underlying database (see {@link VSRepoAdapter.divideOne}).
|
|
317
|
+
*/
|
|
318
|
+
async divide(pk, field, value, options) {
|
|
319
|
+
this.validator.assertIsNumericLike(value);
|
|
320
|
+
return this.execBaseMethod(opt => this.adapter.divideOne(field, value, this.mergeWheresResolver.resolve(opt.see, this.wherePk(pk)), opt), "divide", options);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Returns the sum of a numeric field across every record matching
|
|
324
|
+
* `where` (all records if omitted), or `null` if none match — mirrors
|
|
325
|
+
* SQL's `SUM()`, which returns `NULL` (not `0`) over an empty set.
|
|
326
|
+
*/
|
|
327
|
+
async sum(field, where, options) {
|
|
328
|
+
const validatedWhere = this.validator.validateWhere(where ?? {});
|
|
329
|
+
return this.execBaseMethod(opt => this.adapter.sum(field, this.mergeWheresResolver.resolve(opt.see, validatedWhere), opt), "sum", options, "restrict");
|
|
330
|
+
}
|
|
331
|
+
/** Same as {@link VSRepository.sum}, but the arithmetic mean instead of the total. */
|
|
332
|
+
async average(field, where, options) {
|
|
333
|
+
const validatedWhere = this.validator.validateWhere(where ?? {});
|
|
334
|
+
return this.execBaseMethod(opt => this.adapter.average(field, this.mergeWheresResolver.resolve(opt.see, validatedWhere), opt), "average", options, "restrict");
|
|
335
|
+
}
|
|
336
|
+
/** Same as {@link VSRepository.sum}, but the minimum value instead of the total. */
|
|
337
|
+
async min(field, where, options) {
|
|
338
|
+
const validatedWhere = this.validator.validateWhere(where ?? {});
|
|
339
|
+
return this.execBaseMethod(opt => this.adapter.min(field, this.mergeWheresResolver.resolve(opt.see, validatedWhere), opt), "min", options, "restrict");
|
|
340
|
+
}
|
|
341
|
+
/** Same as {@link VSRepository.sum}, but the maximum value instead of the total. */
|
|
342
|
+
async max(field, where, options) {
|
|
343
|
+
const validatedWhere = this.validator.validateWhere(where ?? {});
|
|
344
|
+
return this.execBaseMethod(opt => this.adapter.max(field, this.mergeWheresResolver.resolve(opt.see, validatedWhere), opt), "max", options, "restrict");
|
|
279
345
|
}
|
|
280
346
|
}
|
|
281
347
|
exports.VSRepository = VSRepository;
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import { QueryMethodOptions } from "../types/decorators/query-method-options.type";
|
|
1
|
+
import type { QueryMethodOptions } from "../types/decorators/query-method-options.type";
|
|
2
2
|
/**
|
|
3
3
|
* Property decorator used to declare a raw SQL query method on a `VSRepository`
|
|
4
4
|
* subclass, bypassing name-based method parsing entirely.
|
|
5
5
|
*
|
|
6
6
|
* Applied to a `declare` class field, it executes `value` directly through the
|
|
7
7
|
* adapter's `query()` method, with parameters injected positionally via the
|
|
8
|
-
* `args` array passed at the call site (`$1`, `$2`, ... placeholders)
|
|
8
|
+
* `args` array passed at the call site (`$1`, `$2`, ... placeholders) — or,
|
|
9
|
+
* with `spreadArgs: true`, via separate positional arguments instead.
|
|
9
10
|
*
|
|
10
11
|
* @param value Raw SQL statement to execute. Use `$1`, `$2`, ... placeholders for
|
|
11
12
|
* the values that will be passed via `args` — never interpolate values directly into `value`.
|
|
12
|
-
* @param options Optional configuration; set `modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements
|
|
13
|
+
* @param options Optional configuration; set `modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements,
|
|
14
|
+
* `singleResult: true` to collapse an array result into its first element, and
|
|
15
|
+
* `spreadArgs: true` to receive placeholder values as separate arguments instead of a
|
|
16
|
+
* single `QueryMethodArg` object — see {@link QueryMethodOptions.singleResult} and
|
|
17
|
+
* {@link QueryMethodOptions.spreadArgs}.
|
|
13
18
|
*
|
|
14
19
|
* @example
|
|
15
20
|
* ```typescript
|
|
@@ -19,7 +24,25 @@ import { QueryMethodOptions } from "../types/decorators/query-method-options.typ
|
|
|
19
24
|
*
|
|
20
25
|
* @QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
|
|
21
26
|
* declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
|
|
27
|
+
*
|
|
28
|
+
* // Only one row is ever expected here, so `singleResult` collapses the
|
|
29
|
+
* // array into a single object (or `null` when no row matches).
|
|
30
|
+
* @QueryMethod('SELECT * FROM "user" WHERE id = $1 LIMIT 1', { singleResult: true })
|
|
31
|
+
* declare findByIdRaw: (arg: QueryMethodArg<[id: string]>) => Promise<User | null>;
|
|
32
|
+
*
|
|
33
|
+
* // `spreadArgs: true` takes placeholder values as separate arguments,
|
|
34
|
+
* // JpaRepository style, instead of a single `{ args: [...] }` object.
|
|
35
|
+
* // An optional trailing `withDb(tx)` runs the query in a transaction.
|
|
36
|
+
* @QueryMethod('SELECT * FROM "user" WHERE email = $1 AND "userType" = $2', {
|
|
37
|
+
* spreadArgs: true,
|
|
38
|
+
* })
|
|
39
|
+
* declare findByEmailAndType: (
|
|
40
|
+
* ...args: QueryArgs<[email: string, userType: string]>
|
|
41
|
+
* ) => Promise<User[]>;
|
|
22
42
|
* }
|
|
43
|
+
*
|
|
44
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin");
|
|
45
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
23
46
|
* ```
|
|
24
47
|
*
|
|
25
48
|
* @publicApi
|
|
@@ -11,11 +11,16 @@ const decorators_validator_1 = require("../internal/validators/decorators.valida
|
|
|
11
11
|
*
|
|
12
12
|
* Applied to a `declare` class field, it executes `value` directly through the
|
|
13
13
|
* adapter's `query()` method, with parameters injected positionally via the
|
|
14
|
-
* `args` array passed at the call site (`$1`, `$2`, ... placeholders)
|
|
14
|
+
* `args` array passed at the call site (`$1`, `$2`, ... placeholders) — or,
|
|
15
|
+
* with `spreadArgs: true`, via separate positional arguments instead.
|
|
15
16
|
*
|
|
16
17
|
* @param value Raw SQL statement to execute. Use `$1`, `$2`, ... placeholders for
|
|
17
18
|
* the values that will be passed via `args` — never interpolate values directly into `value`.
|
|
18
|
-
* @param options Optional configuration; set `modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements
|
|
19
|
+
* @param options Optional configuration; set `modifying: true` for `INSERT`/`UPDATE`/`DELETE` statements,
|
|
20
|
+
* `singleResult: true` to collapse an array result into its first element, and
|
|
21
|
+
* `spreadArgs: true` to receive placeholder values as separate arguments instead of a
|
|
22
|
+
* single `QueryMethodArg` object — see {@link QueryMethodOptions.singleResult} and
|
|
23
|
+
* {@link QueryMethodOptions.spreadArgs}.
|
|
19
24
|
*
|
|
20
25
|
* @example
|
|
21
26
|
* ```typescript
|
|
@@ -25,7 +30,25 @@ const decorators_validator_1 = require("../internal/validators/decorators.valida
|
|
|
25
30
|
*
|
|
26
31
|
* @QueryMethod('UPDATE "user" SET active = true WHERE id = $1', { modifying: true })
|
|
27
32
|
* declare activateUser: (arg: QueryMethodArg<[id: string]>) => Promise<number>;
|
|
33
|
+
*
|
|
34
|
+
* // Only one row is ever expected here, so `singleResult` collapses the
|
|
35
|
+
* // array into a single object (or `null` when no row matches).
|
|
36
|
+
* @QueryMethod('SELECT * FROM "user" WHERE id = $1 LIMIT 1', { singleResult: true })
|
|
37
|
+
* declare findByIdRaw: (arg: QueryMethodArg<[id: string]>) => Promise<User | null>;
|
|
38
|
+
*
|
|
39
|
+
* // `spreadArgs: true` takes placeholder values as separate arguments,
|
|
40
|
+
* // JpaRepository style, instead of a single `{ args: [...] }` object.
|
|
41
|
+
* // An optional trailing `withDb(tx)` runs the query in a transaction.
|
|
42
|
+
* @QueryMethod('SELECT * FROM "user" WHERE email = $1 AND "userType" = $2', {
|
|
43
|
+
* spreadArgs: true,
|
|
44
|
+
* })
|
|
45
|
+
* declare findByEmailAndType: (
|
|
46
|
+
* ...args: QueryArgs<[email: string, userType: string]>
|
|
47
|
+
* ) => Promise<User[]>;
|
|
28
48
|
* }
|
|
49
|
+
*
|
|
50
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin");
|
|
51
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
29
52
|
* ```
|
|
30
53
|
*
|
|
31
54
|
* @publicApi
|
package/dist/index.d.ts
CHANGED
|
@@ -3,17 +3,19 @@ export { VSRepository } from "./VSRepository.js";
|
|
|
3
3
|
export { VSRepoAdapter } from "./VSRepoAdapter.js";
|
|
4
4
|
export { VSRepoError } from "./errors/VSRepoError.js";
|
|
5
5
|
export { VSRepoAdapterError } from "./errors/VSRepoAdapterError.js";
|
|
6
|
+
export { DbArg } from "./internal/utils/db-arg.util.js";
|
|
6
7
|
export { DynamicMethod } from "./decorators/dynamic-method.decorator.js";
|
|
7
8
|
export { QueryMethod } from "./decorators/query-method.decorator.js";
|
|
8
9
|
export { VSRepoErrorType } from "./internal/enums/vsrepo-error-type.enum.js";
|
|
9
10
|
export { VSLogLevel } from "./internal/enums/vs-log-level.enum.js";
|
|
10
11
|
export { TransactionIsolationLevel } from "./internal/enums/transaction-isolation-level.enum.js";
|
|
11
12
|
export { AdapterErrorCode } from "./internal/enums/adapter-error-code.enum.js";
|
|
13
|
+
export { withDb } from "./internal/utils/with-db.util.js";
|
|
12
14
|
export type { VSRepoOptions } from "./types/vsrepo/vsrepo-options.type.js";
|
|
13
15
|
export type { VSRepoOrmTypes } from "./types/vsrepo/vsrepo-orm-types.type.js";
|
|
14
16
|
export type { VSRepoArgs } from "./types/vsrepo/vsrepo-args.type.js";
|
|
15
17
|
export type { VSRepoMethod } from "./types/vsrepo/vsrepo-method.type.js";
|
|
16
|
-
export type { VSRepoWhere, VSRepoWherePlain, VSRepoFieldWhere, VSRepoFieldOperators } from "./types/vsrepo/vsrepo-where.type.js";
|
|
18
|
+
export type { VSRepoWhere, VSRepoWherePlain, VSRepoFieldWhere, VSRepoFieldOperators, } from "./types/vsrepo/vsrepo-where.type.js";
|
|
17
19
|
export type { VSRepoRelations, RelationKeys } from "./types/vsrepo/vsrepo-relations.type.js";
|
|
18
20
|
export type { VSRepoSelect } from "./types/vsrepo/vsrepo-select.type.js";
|
|
19
21
|
export type { VSRepoTransactionOptions } from "./types/vsrepo/vsrepo-transaction-options.type.js";
|
|
@@ -31,4 +33,9 @@ export type { Pagination } from "./types/utils/pagination.type.js";
|
|
|
31
33
|
export type { Primitive } from "./types/utils/primitive.type.js";
|
|
32
34
|
export type { SeeMode } from "./types/utils/see-mode.type.js";
|
|
33
35
|
export type { VSRepoQueryOptions } from "./types/vsrepo/vsrepo-query-options.type";
|
|
36
|
+
export type { DecimalLike } from "./types/utils/decimal-like.type.js";
|
|
37
|
+
export type { NumericKeys } from "./types/utils/numeric-keys.type.js";
|
|
38
|
+
export type { NumericLike } from "./types/utils/numeric-like.type.js";
|
|
39
|
+
export type { RestrictMethodOptions } from "./types/utils/restrict-method-options.type.js";
|
|
40
|
+
export type { QueryArgs } from "./types/utils/query-args.type.js";
|
|
34
41
|
export { VSLogger } from "./internal/utils/vs-logger.util.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VSLogger = exports.AdapterErrorCode = exports.TransactionIsolationLevel = exports.VSLogLevel = exports.VSRepoErrorType = exports.QueryMethod = exports.DynamicMethod = exports.VSRepoAdapterError = exports.VSRepoError = exports.VSRepoAdapter = exports.VSRepository = void 0;
|
|
3
|
+
exports.VSLogger = exports.withDb = exports.AdapterErrorCode = exports.TransactionIsolationLevel = exports.VSLogLevel = exports.VSRepoErrorType = exports.QueryMethod = exports.DynamicMethod = exports.DbArg = exports.VSRepoAdapterError = exports.VSRepoError = exports.VSRepoAdapter = exports.VSRepository = void 0;
|
|
4
4
|
require("reflect-metadata");
|
|
5
5
|
// Public classes / constructors
|
|
6
6
|
var VSRepository_js_1 = require("./VSRepository.js");
|
|
@@ -11,6 +11,8 @@ var VSRepoError_js_1 = require("./errors/VSRepoError.js");
|
|
|
11
11
|
Object.defineProperty(exports, "VSRepoError", { enumerable: true, get: function () { return VSRepoError_js_1.VSRepoError; } });
|
|
12
12
|
var VSRepoAdapterError_js_1 = require("./errors/VSRepoAdapterError.js");
|
|
13
13
|
Object.defineProperty(exports, "VSRepoAdapterError", { enumerable: true, get: function () { return VSRepoAdapterError_js_1.VSRepoAdapterError; } });
|
|
14
|
+
var db_arg_util_js_1 = require("./internal/utils/db-arg.util.js");
|
|
15
|
+
Object.defineProperty(exports, "DbArg", { enumerable: true, get: function () { return db_arg_util_js_1.DbArg; } });
|
|
14
16
|
// Decorators
|
|
15
17
|
var dynamic_method_decorator_js_1 = require("./decorators/dynamic-method.decorator.js");
|
|
16
18
|
Object.defineProperty(exports, "DynamicMethod", { enumerable: true, get: function () { return dynamic_method_decorator_js_1.DynamicMethod; } });
|
|
@@ -25,6 +27,9 @@ var transaction_isolation_level_enum_js_1 = require("./internal/enums/transactio
|
|
|
25
27
|
Object.defineProperty(exports, "TransactionIsolationLevel", { enumerable: true, get: function () { return transaction_isolation_level_enum_js_1.TransactionIsolationLevel; } });
|
|
26
28
|
var adapter_error_code_enum_js_1 = require("./internal/enums/adapter-error-code.enum.js");
|
|
27
29
|
Object.defineProperty(exports, "AdapterErrorCode", { enumerable: true, get: function () { return adapter_error_code_enum_js_1.AdapterErrorCode; } });
|
|
30
|
+
// Public functions
|
|
31
|
+
var with_db_util_js_1 = require("./internal/utils/with-db.util.js");
|
|
32
|
+
Object.defineProperty(exports, "withDb", { enumerable: true, get: function () { return with_db_util_js_1.withDb; } });
|
|
28
33
|
// Internal features
|
|
29
34
|
var vs_logger_util_js_1 = require("./internal/utils/vs-logger.util.js");
|
|
30
35
|
Object.defineProperty(exports, "VSLogger", { enumerable: true, get: function () { return vs_logger_util_js_1.VSLogger; } });
|
|
@@ -8,7 +8,7 @@ export declare enum VSRepoErrorType {
|
|
|
8
8
|
DECORATOR = "DECORATOR",
|
|
9
9
|
/** Failure while resolving a dynamic or query method's configuration into a callable method. */
|
|
10
10
|
RESOLVER = "RESOLVER",
|
|
11
|
-
/** Failure while executing a resolved dynamic method at runtime. */
|
|
11
|
+
/** Failure while executing a resolved dynamic/query method at runtime. */
|
|
12
12
|
DYNAMIC = "DYNAMIC",
|
|
13
13
|
/** Invalid method options or arguments detected during validation. */
|
|
14
14
|
VALIDATOR = "VALIDATOR",
|
|
@@ -12,7 +12,7 @@ var VSRepoErrorType;
|
|
|
12
12
|
VSRepoErrorType["DECORATOR"] = "DECORATOR";
|
|
13
13
|
/** Failure while resolving a dynamic or query method's configuration into a callable method. */
|
|
14
14
|
VSRepoErrorType["RESOLVER"] = "RESOLVER";
|
|
15
|
-
/** Failure while executing a resolved dynamic method at runtime. */
|
|
15
|
+
/** Failure while executing a resolved dynamic/query method at runtime. */
|
|
16
16
|
VSRepoErrorType["DYNAMIC"] = "DYNAMIC";
|
|
17
17
|
/** Invalid method options or arguments detected during validation. */
|
|
18
18
|
VSRepoErrorType["VALIDATOR"] = "VALIDATOR";
|
|
@@ -12,6 +12,7 @@ const uncapitalize_util_1 = require("../utils/uncapitalize.util");
|
|
|
12
12
|
const deepmerge_1 = __importDefault(require("deepmerge"));
|
|
13
13
|
const vsrepo_error_type_enum_1 = require("../enums/vsrepo-error-type.enum");
|
|
14
14
|
const debug_arg_symbol_constant_1 = require("../constants/debug-arg-symbol.constant");
|
|
15
|
+
const db_arg_util_1 = require("../utils/db-arg.util");
|
|
15
16
|
class DynamicMethodsResolver {
|
|
16
17
|
logger;
|
|
17
18
|
adapter;
|
|
@@ -880,19 +881,44 @@ class DynamicMethodsResolver {
|
|
|
880
881
|
this.logger.logDebug(`Resolving ${queryMethods.length} query method(s):`, queryMethods);
|
|
881
882
|
for (const method of queryMethods) {
|
|
882
883
|
const originalKey = method.propertyKey;
|
|
883
|
-
const modifyingQueryMethod = method.modifying;
|
|
884
|
+
const modifyingQueryMethod = method.modifying ?? false;
|
|
884
885
|
const valueQueryMethod = method.value;
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
886
|
+
const singleResult = method.singleResult;
|
|
887
|
+
const spreadArgsMode = method.spreadArgs;
|
|
888
|
+
instance[originalKey] = async (...args) => {
|
|
889
|
+
let db;
|
|
890
|
+
let queryArgs;
|
|
891
|
+
if (spreadArgsMode) {
|
|
892
|
+
const dbPos = args.at(-1);
|
|
893
|
+
if (dbPos instanceof db_arg_util_1.DbArg) {
|
|
894
|
+
db = dbPos.getDb();
|
|
895
|
+
queryArgs = args.slice(0, -1);
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
queryArgs = args;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
if (args.length > 1) {
|
|
903
|
+
const errorMessage = `This query method was declared without spreadArgs = true, use a single QueryMethodArg instead`;
|
|
904
|
+
this.logger.logError(`Cannot run '${String(originalKey)}': ${errorMessage}`);
|
|
905
|
+
throw new VSRepoError_1.VSRepoError(errorMessage, vsrepo_error_type_enum_1.VSRepoErrorType.DYNAMIC);
|
|
906
|
+
}
|
|
907
|
+
const queryArgValidated = this.validator.validateQueryMethodArg(args[0]);
|
|
908
|
+
db = queryArgValidated.db;
|
|
909
|
+
queryArgs = queryArgValidated.args;
|
|
910
|
+
}
|
|
911
|
+
db ??= this.adapter.getDbClient();
|
|
888
912
|
const start = this.logger.startPerformLog(`run ${String(originalKey)} (Modifying: ${modifyingQueryMethod})`);
|
|
889
913
|
try {
|
|
890
914
|
const result = await this.adapter.query(valueQueryMethod, {
|
|
891
|
-
|
|
915
|
+
db,
|
|
916
|
+
args: queryArgs,
|
|
892
917
|
modifying: modifyingQueryMethod,
|
|
893
918
|
});
|
|
919
|
+
const resolved = singleResult && Array.isArray(result) ? (result[0] ?? null) : result;
|
|
894
920
|
this.logger.endPerformLog(start);
|
|
895
|
-
return
|
|
921
|
+
return resolved;
|
|
896
922
|
}
|
|
897
923
|
catch (err) {
|
|
898
924
|
this.logger.endPerformLog(start);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { VSRepoOrmTypes } from "../../types/vsrepo/vsrepo-orm-types.type";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps a database client or transaction so it can be recognized, at
|
|
4
|
+
* runtime, as the trailing `db` override in a {@link QueryArgs} spread call —
|
|
5
|
+
* as opposed to a regular positional query argument. Build one with
|
|
6
|
+
* {@link withDb} rather than constructing it directly.
|
|
7
|
+
*
|
|
8
|
+
* @publicApi
|
|
9
|
+
*/
|
|
10
|
+
export declare class DbArg<T extends VSRepoOrmTypes = VSRepoOrmTypes> {
|
|
11
|
+
private readonly db;
|
|
12
|
+
constructor(db: T["dbClient"] | T["dbTransaction"]);
|
|
13
|
+
/** Returns the wrapped database client or transaction. */
|
|
14
|
+
getDb(): T["dbClient"] | T["dbTransaction"];
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DbArg = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Wraps a database client or transaction so it can be recognized, at
|
|
6
|
+
* runtime, as the trailing `db` override in a {@link QueryArgs} spread call —
|
|
7
|
+
* as opposed to a regular positional query argument. Build one with
|
|
8
|
+
* {@link withDb} rather than constructing it directly.
|
|
9
|
+
*
|
|
10
|
+
* @publicApi
|
|
11
|
+
*/
|
|
12
|
+
class DbArg {
|
|
13
|
+
db;
|
|
14
|
+
constructor(db) {
|
|
15
|
+
this.db = db;
|
|
16
|
+
}
|
|
17
|
+
/** Returns the wrapped database client or transaction. */
|
|
18
|
+
getDb() {
|
|
19
|
+
return this.db;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.DbArg = DbArg;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { VSRepoOrmTypes } from "../../types/vsrepo/vsrepo-orm-types.type";
|
|
2
|
+
import { DbArg } from "./db-arg.util";
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a database client or transaction so it can be passed as the
|
|
5
|
+
* trailing argument of a `@QueryMethod` declared with `{ spreadArgs: true }`,
|
|
6
|
+
* running that call against `db` instead of the repository's default
|
|
7
|
+
* client — the same role `{ db }` plays in the single-object
|
|
8
|
+
* `QueryMethodArg` call style.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* await userRepository.transaction(async (tx) => {
|
|
13
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @publicApi
|
|
18
|
+
*/
|
|
19
|
+
export declare function withDb<T extends VSRepoOrmTypes = VSRepoOrmTypes>(db: T["dbClient"] | T["dbTransaction"]): DbArg<T>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withDb = withDb;
|
|
4
|
+
const db_arg_util_1 = require("./db-arg.util");
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a database client or transaction so it can be passed as the
|
|
7
|
+
* trailing argument of a `@QueryMethod` declared with `{ spreadArgs: true }`,
|
|
8
|
+
* running that call against `db` instead of the repository's default
|
|
9
|
+
* client — the same role `{ db }` plays in the single-object
|
|
10
|
+
* `QueryMethodArg` call style.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* await userRepository.transaction(async (tx) => {
|
|
15
|
+
* await userRepository.findByEmailAndType("joao@email.com", "admin", withDb(tx));
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @publicApi
|
|
20
|
+
*/
|
|
21
|
+
function withDb(db) {
|
|
22
|
+
return new db_arg_util_1.DbArg(db);
|
|
23
|
+
}
|
|
@@ -59,6 +59,8 @@ class DecoratorsValidator {
|
|
|
59
59
|
}
|
|
60
60
|
static queryMethodOptionsSchema = v.object({
|
|
61
61
|
modifying: v.optional(v.boolean(), false),
|
|
62
|
+
singleResult: v.optional(v.boolean()),
|
|
63
|
+
spreadArgs: v.optional(v.boolean()),
|
|
62
64
|
});
|
|
63
65
|
static validateQueryMethodOptions(options) {
|
|
64
66
|
const parsed = v.safeParse(this.queryMethodOptionsSchema, options);
|
|
@@ -8,6 +8,8 @@ import { Ordering } from "../../types/utils/ordering.type";
|
|
|
8
8
|
import { VSLogger } from "../utils/vs-logger.util";
|
|
9
9
|
import { VSRepoWhere } from "../../types/vsrepo/vsrepo-where.type";
|
|
10
10
|
import { VSRepoQueryOptions } from "../../types/vsrepo/vsrepo-query-options.type";
|
|
11
|
+
import { NumericLike } from "../../types/utils/numeric-like.type";
|
|
12
|
+
import { RestrictMethodOptions } from "../../types/utils/restrict-method-options.type";
|
|
11
13
|
export declare class VSRepoValidator<T, K, O extends VSRepoOrmTypes = VSRepoOrmTypes> {
|
|
12
14
|
private logger?;
|
|
13
15
|
setLogger(logger: VSLogger): void;
|
|
@@ -16,13 +18,15 @@ export declare class VSRepoValidator<T, K, O extends VSRepoOrmTypes = VSRepoOrmT
|
|
|
16
18
|
validateConstructorOptions(options: unknown): VSRepoOptions<T, K>;
|
|
17
19
|
private readonly methodOptionsSchema;
|
|
18
20
|
validateMethodOptions(options?: unknown): MethodOptions<T, O>;
|
|
21
|
+
private readonly restrictMethodOptionsSchema;
|
|
22
|
+
validateRestrictMethodOptions(options?: unknown): RestrictMethodOptions<T, O>;
|
|
19
23
|
private readonly getAllMethodOptionsSchema;
|
|
20
24
|
validateGetAllMethodOptions(options?: unknown): MethodOptions<T, O> & {
|
|
21
25
|
pagination?: Pagination;
|
|
22
26
|
order?: Ordering<T>;
|
|
23
27
|
};
|
|
24
28
|
private queryArgSchema;
|
|
25
|
-
validateQueryMethodArg(arg?: unknown): QueryMethodArg<any>;
|
|
29
|
+
validateQueryMethodArg(arg?: unknown): QueryMethodArg<any[]>;
|
|
26
30
|
private queryOptionsSchema;
|
|
27
31
|
validateQueryOptions(options?: unknown): VSRepoQueryOptions;
|
|
28
32
|
private transactionOptionsSchema;
|
|
@@ -30,4 +34,7 @@ export declare class VSRepoValidator<T, K, O extends VSRepoOrmTypes = VSRepoOrmT
|
|
|
30
34
|
validateOrdering(value: unknown): Ordering<T>;
|
|
31
35
|
validatePagination(value: unknown): Pagination;
|
|
32
36
|
validateWhere(value: unknown): VSRepoWhere<T>;
|
|
37
|
+
private decimalLikeSchema;
|
|
38
|
+
private numericLikeSchema;
|
|
39
|
+
assertIsNumericLike(value: unknown): asserts value is NumericLike;
|
|
33
40
|
}
|
|
@@ -89,6 +89,17 @@ class VSRepoValidator {
|
|
|
89
89
|
}
|
|
90
90
|
return parsed.output;
|
|
91
91
|
}
|
|
92
|
+
restrictMethodOptionsSchema = v.object({
|
|
93
|
+
db: v.optional(v.any()),
|
|
94
|
+
see: v.optional(v.picklist(["active", "removed", "all"])),
|
|
95
|
+
});
|
|
96
|
+
validateRestrictMethodOptions(options) {
|
|
97
|
+
const parsed = v.safeParse(this.restrictMethodOptionsSchema, options ?? {});
|
|
98
|
+
if (!parsed.success) {
|
|
99
|
+
this.failValidation(parsed.issues[0], vsrepo_error_type_enum_1.VSRepoErrorType.VALIDATOR);
|
|
100
|
+
}
|
|
101
|
+
return parsed.output;
|
|
102
|
+
}
|
|
92
103
|
getAllMethodOptionsSchema = v.object({
|
|
93
104
|
...this.methodOptionsSchema.entries,
|
|
94
105
|
order: v.optional(ordering_schema_1.default),
|
|
@@ -116,6 +127,7 @@ class VSRepoValidator {
|
|
|
116
127
|
args: v.optional(v.array(v.any())),
|
|
117
128
|
db: v.optional(v.any()),
|
|
118
129
|
modifying: v.optional(v.boolean()),
|
|
130
|
+
singleResult: v.optional(v.boolean()),
|
|
119
131
|
});
|
|
120
132
|
validateQueryOptions(options) {
|
|
121
133
|
const parsed = v.safeParse(this.queryOptionsSchema, options ?? {});
|
|
@@ -156,5 +168,16 @@ class VSRepoValidator {
|
|
|
156
168
|
}
|
|
157
169
|
return parsed.output;
|
|
158
170
|
}
|
|
171
|
+
decimalLikeSchema = v.looseObject({
|
|
172
|
+
toNumber: v.function(),
|
|
173
|
+
decimalPlaces: v.function(),
|
|
174
|
+
});
|
|
175
|
+
numericLikeSchema = v.union([v.number(), v.bigint(), this.decimalLikeSchema]);
|
|
176
|
+
assertIsNumericLike(value) {
|
|
177
|
+
const parsed = v.safeParse(this.numericLikeSchema, value);
|
|
178
|
+
if (!parsed.success) {
|
|
179
|
+
this.failValidation(parsed.issues[0], vsrepo_error_type_enum_1.VSRepoErrorType.VALIDATOR);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
159
182
|
}
|
|
160
183
|
exports.VSRepoValidator = VSRepoValidator;
|
|
@@ -11,5 +11,39 @@ export type QueryMethodOptions = {
|
|
|
11
11
|
* to whatever return type is declared on the field.
|
|
12
12
|
* @default false
|
|
13
13
|
*/
|
|
14
|
-
modifying
|
|
14
|
+
modifying?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* When `true`, the array returned by the underlying query is collapsed
|
|
17
|
+
* into its first element (`null` if the array is empty) before
|
|
18
|
+
* being resolved to the caller. Has no effect when the query resolves
|
|
19
|
+
* to something other than an array (e.g. a `modifying` query's
|
|
20
|
+
* affected-row count).
|
|
21
|
+
*
|
|
22
|
+
* Useful for queries you already know return at most one row (e.g. a
|
|
23
|
+
* `SELECT ... LIMIT 1` or a lookup by a unique column), where declaring
|
|
24
|
+
* the return type as an array would be misleading.
|
|
25
|
+
*
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
singleResult?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* When `true`, the decorated method receives its SQL placeholder values
|
|
31
|
+
* as separate positional arguments (`method(a, b, c)`) instead of a
|
|
32
|
+
* single {@link QueryMethodArg} object (`method({ args: [a, b, c] })`).
|
|
33
|
+
* Type the declared field's parameters with {@link QueryArgs} to get
|
|
34
|
+
* autocompletion and arity checking for this call style.
|
|
35
|
+
*
|
|
36
|
+
* To run the query against a specific client or transaction — instead
|
|
37
|
+
* of the repository's default one — pass {@link DbArg} (built via
|
|
38
|
+
* {@link withDb}) as the trailing argument: `method(a, b, withDb(tx))`.
|
|
39
|
+
* It's recognized by `instanceof`, so it never collides with a regular
|
|
40
|
+
* positional argument, even one that happens to be an object.
|
|
41
|
+
*
|
|
42
|
+
* When `false` (the default), calling the method with more than one
|
|
43
|
+
* argument throws, since it expects the single-object `QueryMethodArg`
|
|
44
|
+
* call style instead.
|
|
45
|
+
*
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
spreadArgs?: boolean;
|
|
15
49
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural shape of an arbitrary-precision "Decimal" value, as commonly
|
|
3
|
+
* returned by ORMs for `decimal`/`numeric` columns (e.g. Prisma's
|
|
4
|
+
* `Prisma.Decimal`, built on top of `decimal.js`).
|
|
5
|
+
*
|
|
6
|
+
* Matched structurally (duck-typed) instead of importing a concrete class,
|
|
7
|
+
* so the core stays ORM-agnostic — any object exposing both `toNumber()`
|
|
8
|
+
* and `decimalPlaces()` is treated as Decimal-like by {@link NumericLike}
|
|
9
|
+
* and, transitively, by {@link NumericKeys}.
|
|
10
|
+
*
|
|
11
|
+
* Note that several ORMs (e.g. Drizzle, MikroORM, TypeORM) represent
|
|
12
|
+
* `decimal`/`numeric` columns as plain `string` by default, to avoid
|
|
13
|
+
* floating-point precision loss — a `string` value does **not** satisfy
|
|
14
|
+
* `DecimalLike`. Configure the column in a numeric mode (or provide a
|
|
15
|
+
* transformer) on those ORMs if you want the field to be eligible for
|
|
16
|
+
* `increment`/`decrement`/`multiply`/`divide`/`sum`/`average`/`min`/`max`.
|
|
17
|
+
*
|
|
18
|
+
* @publicApi
|
|
19
|
+
*/
|
|
20
|
+
export type DecimalLike = {
|
|
21
|
+
toNumber(): number;
|
|
22
|
+
decimalPlaces(): number;
|
|
23
|
+
};
|