vsrepo 2.4.0 → 2.5.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/CHANGELOG.md +531 -0
- package/README.md +136 -51
- package/README.pt-BR.md +129 -44
- package/dist/VSRepoAdapter.d.ts +2 -0
- package/dist/VSRepoAdapter.js.map +1 -1
- package/dist/VSRepository.js +9 -1
- package/dist/VSRepository.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/validators/vsrepo.validator.js +1 -1
- package/dist/internal/validators/vsrepo.validator.js.map +1 -1
- package/dist/types/utils/infer-method-return.type.d.ts +108 -0
- package/dist/types/utils/infer-method-return.type.js +3 -0
- package/dist/types/utils/infer-method-return.type.js.map +1 -0
- package/dist/types/utils/infer-method-type.type.d.ts +66 -0
- package/dist/types/utils/infer-method-type.type.js +3 -0
- package/dist/types/utils/infer-method-type.type.js.map +1 -0
- package/dist/types/utils/ordering.type.d.ts +1 -5
- package/dist/types/vsrepo/vsrepo-options.type.d.ts +1 -1
- package/package.json +9 -3
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { RelationKeys } from "../vsrepo/vsrepo-relations.type";
|
|
2
|
+
import { SeeMode } from "./see-mode.type";
|
|
3
|
+
/**
|
|
4
|
+
* Loose structural constraint for the `Options` accepted by {@link InferMethodReturn}.
|
|
5
|
+
* Mirrors the four keys of `MethodOptions` without depending on the entity type.
|
|
6
|
+
*/
|
|
7
|
+
type InferMethodReturnOptions = {
|
|
8
|
+
select?: object | undefined;
|
|
9
|
+
relations?: object | undefined;
|
|
10
|
+
see?: SeeMode | undefined;
|
|
11
|
+
db?: unknown;
|
|
12
|
+
};
|
|
13
|
+
/** Flattens an intersection into a single object type (keeps modifiers). */
|
|
14
|
+
type Simplify<X> = {
|
|
15
|
+
[K in keyof X]: X[K];
|
|
16
|
+
} & {};
|
|
17
|
+
/** Safe `O[K]`: `undefined` when `K` is not a key of `O`. */
|
|
18
|
+
type Get<O, K extends string> = K extends keyof O ? O[K] : undefined;
|
|
19
|
+
/**
|
|
20
|
+
* How a given option (`select`/`relations`) was typed:
|
|
21
|
+
* - `"absent"` — not provided (or explicitly `undefined`);
|
|
22
|
+
* - `"unknown"` — declared but not narrowed (optional/wide, e.g. a variable annotated as
|
|
23
|
+
* plain `MethodOptions<T>`), so its contents can't be known at compile time;
|
|
24
|
+
* - `"known"` — a narrowed object type (e.g. `{ id: true }` obtained via `satisfies`).
|
|
25
|
+
*/
|
|
26
|
+
type OptionState<O, K extends string> = [Get<O, K>] extends [undefined] ? "absent" : undefined extends Get<O, K> ? "unknown" : "known";
|
|
27
|
+
/** Keys present in a `select`/`relations` spec (`undefined` spec has none). */
|
|
28
|
+
type SpecKeys<S> = S extends object ? keyof S : never;
|
|
29
|
+
/**
|
|
30
|
+
* Nested spec of key `K` inside `S`: the object itself when it's a nested
|
|
31
|
+
* `select`/`relations`, or `undefined` when the value is just `true`.
|
|
32
|
+
*/
|
|
33
|
+
type SubSpec<S, K> = S extends object ? K extends keyof S ? NonNullable<S[K]> extends object ? NonNullable<S[K]> : undefined : undefined : undefined;
|
|
34
|
+
/** Keys of `T` that are scalar fields (i.e. not relations). */
|
|
35
|
+
type ScalarKeys<T> = Exclude<keyof T, RelationKeys<T>>;
|
|
36
|
+
/** Which option is currently driving the shape. */
|
|
37
|
+
type SpecMode = "select" | "relations";
|
|
38
|
+
/** Scalar keys kept in the result: only the ones in a `select` spec, all of them otherwise. */
|
|
39
|
+
type ResultScalarKeys<T, Spec, Mode extends SpecMode> = Mode extends "select" ? Spec extends object ? Extract<SpecKeys<Spec>, ScalarKeys<T>> : ScalarKeys<T> : ScalarKeys<T>;
|
|
40
|
+
/** Relation keys kept in the result: the relation keys present in the spec. */
|
|
41
|
+
type ResultRelationKeys<T, Spec> = Extract<SpecKeys<Spec>, RelationKeys<T>>;
|
|
42
|
+
/** Applies the shape to a relation field, preserving its array-ness and nullability. */
|
|
43
|
+
type MapRelation<F, Spec, Mode extends SpecMode> = F extends readonly (infer U)[] ? Shape<U, Spec, Mode>[] : F extends null | undefined ? F : Shape<F, Spec, Mode>;
|
|
44
|
+
/**
|
|
45
|
+
* Shape of a single entity `T` given a `select` spec (`Mode = "select"`) or
|
|
46
|
+
* a `relations` spec (`Mode = "relations"`).
|
|
47
|
+
*
|
|
48
|
+
* - `select` mode: only the fields present in the spec; a relation set to `true` brings all of
|
|
49
|
+
* its scalar fields (no nested relations), a nested object restricts it further.
|
|
50
|
+
* - `relations` mode: every scalar field plus the relations present in the spec (recursively).
|
|
51
|
+
* - No spec (`undefined`): every scalar field, no relations.
|
|
52
|
+
*/
|
|
53
|
+
type Shape<T, Spec, Mode extends SpecMode> = Simplify<{
|
|
54
|
+
[K in keyof T as K extends ResultScalarKeys<T, Spec, Mode> ? K : never]: T[K];
|
|
55
|
+
} & {
|
|
56
|
+
[K in keyof T as K extends ResultRelationKeys<T, Spec> ? K : never]: MapRelation<T[K], SubSpec<Spec, K>, Mode>;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Resolves the shape of a single entity `T` (not an array, not nullable) from the method options.
|
|
60
|
+
* `select` takes precedence: when present, `relations` is ignored. If the option that drives
|
|
61
|
+
* the shape is not narrowed, its contents are unknown and `T` is returned unchanged.
|
|
62
|
+
*/
|
|
63
|
+
type InferEntity<T, O> = OptionState<O, "select"> extends "known" ? Shape<T, NonNullable<Get<O, "select">>, "select"> : OptionState<O, "select"> extends "unknown" ? T : OptionState<O, "relations"> extends "known" ? Shape<T, NonNullable<Get<O, "relations">>, "relations"> : OptionState<O, "relations"> extends "unknown" ? T : Shape<T, undefined, "relations">;
|
|
64
|
+
/**
|
|
65
|
+
* Opt-in **strict** return typing: narrows the type returned by a repository method
|
|
66
|
+
* according to the `select` / `relations` that were actually passed to it.
|
|
67
|
+
*
|
|
68
|
+
* By default, `VSRepository` methods type their return as the whole entity, ignoring
|
|
69
|
+
* `select` and `relations` (the same approach TypeORM takes). Use this utility when you
|
|
70
|
+
* prefer a tighter type:
|
|
71
|
+
*
|
|
72
|
+
* - **no `select`/`relations`** — only the scalar fields of the entity (no relations);
|
|
73
|
+
* - **`relations`** — the scalar fields plus the requested relations (nested ones included);
|
|
74
|
+
* - **`select`** — only the selected fields. A relation selected with `true` brings all of its
|
|
75
|
+
* scalar fields; a nested `select` restricts it further. Relations selected this way are
|
|
76
|
+
* loaded even without `relations`.
|
|
77
|
+
*
|
|
78
|
+
* `select` takes precedence over `relations`: when `select` is present, `relations` is
|
|
79
|
+
* ignored by the type. This mirrors the Prisma 7 adapter and never promises fields an
|
|
80
|
+
* adapter might not return.
|
|
81
|
+
*
|
|
82
|
+
* `see` and `db` don't affect the result type.
|
|
83
|
+
*
|
|
84
|
+
* The first type argument is what the method returns: `Entity`, `Entity | null` or
|
|
85
|
+
* `Entity[]` — `null`/array-ness are preserved (also on relation fields, e.g.
|
|
86
|
+
* `address: Address | null`).
|
|
87
|
+
*
|
|
88
|
+
* The options must keep their literal type, so declare them with `satisfies MethodOptions<T>`
|
|
89
|
+
* (or inline). If the options are typed as plain `MethodOptions<T>`, nothing is known
|
|
90
|
+
* at compile time and the whole entity `T` is returned unchanged (the default typing).
|
|
91
|
+
*
|
|
92
|
+
* @template T What the method returns: `Entity`, `Entity | null` or `Entity[]`.
|
|
93
|
+
* @template Options The options object passed to the method (`typeof options`).
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* const options = {
|
|
98
|
+
* select: { id: true, name: true, products: { id: true } },
|
|
99
|
+
* } satisfies MethodOptions<User>;
|
|
100
|
+
*
|
|
101
|
+
* const users: InferMethodReturn<User[], typeof options> = await userRepository.getAll(options);
|
|
102
|
+
* // { id: string; name: string; products: { id: string }[] }[]
|
|
103
|
+
* ```
|
|
104
|
+
*
|
|
105
|
+
* @publicApi
|
|
106
|
+
*/
|
|
107
|
+
export type InferMethodReturn<T, Options extends InferMethodReturnOptions = {}> = T extends readonly (infer U)[] ? InferEntity<U, Options>[] : T extends null | undefined ? T : InferEntity<T, Options>;
|
|
108
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infer-method-return.type.js","sourceRoot":"","sources":["../../../src/types/utils/infer-method-return.type.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { VSRepoOrmTypes } from "../vsrepo/vsrepo-orm-types.type";
|
|
2
|
+
import { VSRepoRelations } from "../vsrepo/vsrepo-relations.type";
|
|
3
|
+
import { VSRepoSelect } from "../vsrepo/vsrepo-select.type";
|
|
4
|
+
import { InferMethodReturn } from "./infer-method-return.type";
|
|
5
|
+
import { MethodOptions } from "./methods-options.type";
|
|
6
|
+
/** Entity type behind a method's return: `E`, `E | null`, `E[]` or `E[] | null` all give `E`. */
|
|
7
|
+
type EntityOf<R> = NonNullable<R> extends readonly (infer U)[] ? U : NonNullable<R>;
|
|
8
|
+
/**
|
|
9
|
+
* Deeply checks that a `select`/`relations` object only has keys that exist in the reference
|
|
10
|
+
* spec (`VSRepoSelect<E>`/`VSRepoRelations<E>`), turning unknown keys into `never`.
|
|
11
|
+
*
|
|
12
|
+
* Needed because a generic parameter (`Options` in {@link InferMethodType}) loses TypeScript's
|
|
13
|
+
* excess property check, so a typo like `select: { id: true, idd: true }` would otherwise be
|
|
14
|
+
* silently accepted.
|
|
15
|
+
*
|
|
16
|
+
* Non-object values (e.g. `true`) resolve to `unknown` (the identity of `&`): they are already
|
|
17
|
+
* validated by the `Options` constraint, and contributing nothing here keeps stray members of
|
|
18
|
+
* `boolean` (like `valueOf`) out of the editor's suggestions.
|
|
19
|
+
*/
|
|
20
|
+
type ExactSpec<S, Ref> = S extends object ? [Extract<Ref, object>] extends [never] ? never : {
|
|
21
|
+
[K in keyof S]: K extends keyof Extract<Ref, object> ? ExactSpec<S[K], NonNullable<Extract<Ref, object>[K]>> : never;
|
|
22
|
+
} : unknown;
|
|
23
|
+
/** Applies {@link ExactSpec} to `select` and `relations`; `see`/`db` pass through, unknown keys become `never`. */
|
|
24
|
+
type ExactOptions<O, E> = {
|
|
25
|
+
[K in keyof O]: K extends "select" ? ExactSpec<O[K], VSRepoSelect<E>> : K extends "relations" ? ExactSpec<O[K], VSRepoRelations<E>> : K extends "see" | "db" ? O[K] : never;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Type for declaring a dynamic method whose return type is **strictly inferred** from the
|
|
29
|
+
* `select` / `relations` passed on each call (see {@link InferMethodReturn} for the exact rules).
|
|
30
|
+
*
|
|
31
|
+
* It has two call signatures:
|
|
32
|
+
* - `(...args) => Promise<...>` — no `options`: only the scalar fields of the entity;
|
|
33
|
+
* - `(...args, options?: MethodOptions<Entity, OrmTypes>) => Promise<...>` — the return is
|
|
34
|
+
* inferred from the `options` passed.
|
|
35
|
+
*
|
|
36
|
+
* `Entity` is taken from the return type (`Entity`, `Entity | null`, `Entity[]`, ...).
|
|
37
|
+
*
|
|
38
|
+
* The `options` parameter is always the last one, after every argument in `Args` (as in a
|
|
39
|
+
* regular dynamic method signature). Unknown keys in `select`/`relations` are rejected, just like
|
|
40
|
+
* with a plain `MethodOptions<Entity>` parameter, and the editor autocompletes them.
|
|
41
|
+
*
|
|
42
|
+
* Meant for dynamic methods that return entities. Methods that don't (`existsBy...`,
|
|
43
|
+
* `countBy...`, etc.) can keep their regular signature.
|
|
44
|
+
*
|
|
45
|
+
* @template Args Positional arguments of the method, without `options` (e.g. `[name: string]`).
|
|
46
|
+
* @template R What the method resolves to: `Entity`, `Entity | null` or `Entity[]`.
|
|
47
|
+
* @template K ORM type map (`dbClient`/`dbTransaction`) used to type the `db` option. Optional.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* class UserRepository extends VSRepository<User, string, MyOrmTypes> {
|
|
52
|
+
* @DynamicMethod()
|
|
53
|
+
* declare findByName: InferMethodType<[name: string], User[], MyOrmTypes>;
|
|
54
|
+
* }
|
|
55
|
+
*
|
|
56
|
+
* const users = await userRepository.findByName("John", { select: { id: true, name: true } });
|
|
57
|
+
* // { id: string; name: string }[]
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @publicApi
|
|
61
|
+
*/
|
|
62
|
+
export type InferMethodType<Args extends unknown[], R, K extends VSRepoOrmTypes = VSRepoOrmTypes> = {
|
|
63
|
+
(...args: Args): Promise<InferMethodReturn<R, {}>>;
|
|
64
|
+
<Options extends MethodOptions<EntityOf<R>, K>>(...args: [...Args, options?: Options & ExactOptions<Options, EntityOf<R>>]): Promise<InferMethodReturn<R, Options>>;
|
|
65
|
+
};
|
|
66
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"infer-method-type.type.js","sourceRoot":"","sources":["../../../src/types/utils/infer-method-type.type.ts"],"names":[],"mappings":""}
|
|
@@ -9,16 +9,12 @@ export type SortDirection = "asc" | "desc" | "ASC" | "DESC";
|
|
|
9
9
|
/**
|
|
10
10
|
* Ordering shape for a single level of an entity's fields.
|
|
11
11
|
*
|
|
12
|
-
* Scalar fields accept a `SortDirection` directly; nested object (to-one
|
|
13
|
-
* relation) fields accept a nested `Ordering`. Array (to-many relation)
|
|
14
|
-
* fields are not orderable and are excluded.
|
|
15
|
-
*
|
|
16
12
|
* @template T Entity type being ordered.
|
|
17
13
|
*
|
|
18
14
|
* @publicApi
|
|
19
15
|
*/
|
|
20
16
|
export type OrderByField<T> = {
|
|
21
|
-
[P in keyof T
|
|
17
|
+
[P in keyof T as NonNullable<T[P]> extends Primitive ? P : never]?: SortDirection;
|
|
22
18
|
};
|
|
23
19
|
/**
|
|
24
20
|
* Ordering accepted by repository methods that support `order`, such as `getAll` or a dynamic method with `Ordered`.
|
|
@@ -14,7 +14,7 @@ export type VSRepoOptions<T, K> = {
|
|
|
14
14
|
/** Adapter that translates the repository's operations into calls against the underlying ORM/database. */
|
|
15
15
|
adapter: VSRepoAdapter<T>;
|
|
16
16
|
/** Name of the field that represents the entity's primary key (PK). */
|
|
17
|
-
pkName
|
|
17
|
+
pkName?: KeysOfType<T, K>;
|
|
18
18
|
/**
|
|
19
19
|
* Name of the field used for soft-delete.
|
|
20
20
|
*
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vsrepo",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "ORM-agnostic repository pattern library
|
|
3
|
+
"version": "2.5.0",
|
|
4
|
+
"description": "ORM-agnostic repository pattern library",
|
|
5
5
|
"homepage": "https://github.com/jaobrabo123/VSRepository#readme",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/jaobrabo123/VSRepository.git"
|
|
9
9
|
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"email": "joaodev.azevedo@outlook.com",
|
|
12
|
+
"url": "https://github.com/jaobrabo123/VSRepoDrizzleAdapter/issues"
|
|
13
|
+
},
|
|
10
14
|
"main": "./dist/index.js",
|
|
11
15
|
"types": "./dist/index.d.ts",
|
|
12
16
|
"exports": {
|
|
@@ -20,7 +24,8 @@
|
|
|
20
24
|
"files": [
|
|
21
25
|
"dist",
|
|
22
26
|
"README.md",
|
|
23
|
-
"README.pt-BR.md"
|
|
27
|
+
"README.pt-BR.md",
|
|
28
|
+
"CHANGELOG.md"
|
|
24
29
|
],
|
|
25
30
|
"engines": {
|
|
26
31
|
"node": ">=18"
|
|
@@ -38,6 +43,7 @@
|
|
|
38
43
|
"keywords": [
|
|
39
44
|
"prisma",
|
|
40
45
|
"typeorm",
|
|
46
|
+
"drizzle",
|
|
41
47
|
"repository",
|
|
42
48
|
"orm",
|
|
43
49
|
"typescript",
|