@afora/core 0.0.6 → 0.0.7
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 +2 -1
- package/dist/core/application/types/application.interface.d.ts +11 -0
- package/dist/core/command/command.d.ts +11 -4
- package/dist/core/command/types/command.interface.d.ts +19 -0
- package/dist/core/output/status.d.ts +2 -1
- package/dist/core/property/input.d.ts +20 -0
- package/dist/core/record/plugin.d.ts +6 -2
- package/dist/core/repository/repository.d.ts +1 -1
- package/dist/core/repository/state.d.ts +9 -0
- package/dist/core/repository/types/repository.interface.d.ts +9 -2
- package/dist/core/use-case/exceptions.d.ts +36 -0
- package/dist/core/use-case/plugin-catalog.d.ts +11 -0
- package/dist/core/use-case/plugin.d.ts +25 -0
- package/dist/core/use-case/steps/types/steps.interface.d.ts +23 -6
- package/dist/core/use-case/target.d.ts +9 -0
- package/dist/core/use-case/types/plugin.interface.d.ts +185 -0
- package/dist/experimental.js +2 -2
- package/dist/{index-gbgc40xe.js → index-q6hggpzw.js} +1024 -48
- package/dist/{index-3nd9bjnc.js → index-r78dsvtc.js} +68 -57
- package/dist/index.d.ts +15 -6
- package/dist/index.js +41 -5
- package/dist/std/command.d.ts +11 -0
- package/dist/std/properties/password.d.ts +16 -1
- package/dist/std/use-cases/auth.d.ts +5 -0
- package/dist/std/use-cases/crud.d.ts +23 -0
- package/dist/std/use-cases/index.d.ts +11 -0
- package/dist/std/use-cases/lifecycle.d.ts +13 -0
- package/dist/std/use-cases/shared.d.ts +60 -0
- package/dist/std/use-cases/types.d.ts +212 -0
- package/dist/testing/index.js +10 -2
- package/package.json +1 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { AnyBlueprint, AnyBlueprintList, AnyRecordClass, BlueprintField, IBlueprint, IBlueprintCollection, IBlueprintList, OptionalBlueprint, OptionalBlueprintList, OptionalRecord } from "../../core/blueprint/types/blueprint.interface.js";
|
|
2
|
+
import type { EntityIdentityKey } from "../../core/entity/types/entity.interface.js";
|
|
3
|
+
import type { OptionalProperty } from "../../core/property/types/optional-property.type.js";
|
|
4
|
+
import type { AnyPropertyConstructor } from "../../core/property/types/property-collection.type.js";
|
|
5
|
+
import type { AnyRepository, AnyRepositoryEntity, EntityInstanceOf } from "../../core/repository/types/repository.interface.js";
|
|
6
|
+
import type { IUseCasePlugin, UseCaseTarget } from "../../core/use-case/types/plugin.interface.js";
|
|
7
|
+
import type { AnyExceptionClass, StepsUseCaseConstructor } from "../../core/use-case/use-case.js";
|
|
8
|
+
import type { Integer, StringProperty } from "../properties/primitives.js";
|
|
9
|
+
type EntityOf<Repository> = Repository extends {
|
|
10
|
+
readonly entity: infer Entity extends AnyRepositoryEntity;
|
|
11
|
+
} ? Entity : never;
|
|
12
|
+
type PropertiesOf<Entity> = Entity extends {
|
|
13
|
+
readonly blueprint: IBlueprint<infer Properties extends IBlueprintCollection>;
|
|
14
|
+
} ? Properties : never;
|
|
15
|
+
type Props<Repository> = PropertiesOf<EntityOf<Repository>>;
|
|
16
|
+
type FieldAt<Properties, Key extends string> = Properties extends {
|
|
17
|
+
readonly [Name in Key]: infer Field extends BlueprintField;
|
|
18
|
+
} ? Field : never;
|
|
19
|
+
type IdField<Repository> = FieldAt<Props<Repository>, "id">;
|
|
20
|
+
type KeyOf<Repository> = keyof Props<Repository> & string;
|
|
21
|
+
/** Whether a field holds an entity, one that may be absent, or a list of them. */
|
|
22
|
+
type HoldsEntity<Field> = Field extends AnyRepositoryEntity ? true : Field extends {
|
|
23
|
+
readonly record: AnyRepositoryEntity;
|
|
24
|
+
} ? true : Field extends IBlueprintList<infer Item> ? Item extends AnyRepositoryEntity ? true : false : false;
|
|
25
|
+
/** The field that accepts `undefined` too, as `optionalOf` gives it. */
|
|
26
|
+
type Optional<Field> = Field extends AnyPropertyConstructor ? OptionalProperty<Field> : Field extends AnyBlueprintList ? OptionalBlueprintList<Field> : Field extends AnyBlueprint ? OptionalBlueprint<Field> : Field extends AnyRecordClass ? OptionalRecord<Field> : never;
|
|
27
|
+
/** The errors a standard use case throws, in place of its defaults. */
|
|
28
|
+
export interface StdUseCaseErrors {
|
|
29
|
+
/** For an entity that is not found, or is not the caller's. `UseCaseEntityNotFound` by default. */
|
|
30
|
+
readonly notFound?: AnyExceptionClass;
|
|
31
|
+
/** For an entity that already exists (`register`). `UseCaseEntityConflict` by default. */
|
|
32
|
+
readonly conflict?: AnyExceptionClass;
|
|
33
|
+
/** For credentials that do not match (`login`). `UseCaseInvalidCredentials` by default. */
|
|
34
|
+
readonly invalid?: AnyExceptionClass;
|
|
35
|
+
/** For a caller that is not found, when it owns what `create` makes. `notFound` by default. */
|
|
36
|
+
readonly owner?: AnyExceptionClass;
|
|
37
|
+
}
|
|
38
|
+
/** The options every standard use case takes. */
|
|
39
|
+
export interface StdUseCaseOptions {
|
|
40
|
+
/** The `useCaseName`; the verb and the name of the entity by default, as `CreatePost`. */
|
|
41
|
+
readonly name?: string;
|
|
42
|
+
readonly description?: string;
|
|
43
|
+
readonly tags?: readonly string[];
|
|
44
|
+
readonly deprecated?: boolean;
|
|
45
|
+
readonly errors?: StdUseCaseErrors;
|
|
46
|
+
}
|
|
47
|
+
/** Who owns the entities: the caller, whose id the context gives. */
|
|
48
|
+
export interface StdOwnerOption<Repository> {
|
|
49
|
+
/**
|
|
50
|
+
* `key` holds the owner, as an id or as an entity (`ownerId` by default,
|
|
51
|
+
* the key of the `ownable` plugin), and `context` names the context key
|
|
52
|
+
* with the id of the caller: `create` fills the key from it, a use case
|
|
53
|
+
* over one entity throws `notFound` for another owner's, and `list` gives
|
|
54
|
+
* only the caller's.
|
|
55
|
+
*/
|
|
56
|
+
readonly owner?: {
|
|
57
|
+
readonly key?: KeyOf<Repository>;
|
|
58
|
+
readonly context: string;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** The options of a standard use case that gives one entity. */
|
|
62
|
+
export interface StdEntityOptions<Repository> extends StdUseCaseOptions, StdOwnerOption<Repository> {
|
|
63
|
+
/** The key of the response that holds the entity; `item` by default. */
|
|
64
|
+
readonly as?: string;
|
|
65
|
+
}
|
|
66
|
+
/** The options of `create` and `register`. */
|
|
67
|
+
export interface StdCreateOptions<Repository> extends StdEntityOptions<Repository> {
|
|
68
|
+
/** Optional keys the input does not take. */
|
|
69
|
+
readonly omit?: readonly KeyOf<Repository>[];
|
|
70
|
+
/** Values the entity is built with, in place of taking them from the input. */
|
|
71
|
+
readonly values?: {
|
|
72
|
+
readonly [Key in KeyOf<Repository>]?: unknown;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** The options of `publish`, `unpublish`, `archive` and `unarchive`. */
|
|
76
|
+
export interface StdLifecycleOptions<Repository> extends StdEntityOptions<Repository> {
|
|
77
|
+
/** Change keys set before the entity is stored, e.g. `{ status: "published" }`. */
|
|
78
|
+
readonly set?: {
|
|
79
|
+
readonly [Key in KeyOf<Repository>]?: unknown;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** The options of `list`. */
|
|
83
|
+
export interface StdListOptions<Repository> extends StdUseCaseOptions, StdOwnerOption<Repository> {
|
|
84
|
+
/** The order of the page; newest first (`createdAt`, descending) by default. */
|
|
85
|
+
readonly orderBy?: readonly {
|
|
86
|
+
readonly key: KeyOf<Repository>;
|
|
87
|
+
readonly direction?: "asc" | "desc";
|
|
88
|
+
}[];
|
|
89
|
+
}
|
|
90
|
+
type None = Record<never, never>;
|
|
91
|
+
type AsOf<Options> = Options extends {
|
|
92
|
+
readonly as: infer Key extends string;
|
|
93
|
+
} ? Key : "item";
|
|
94
|
+
type EntityResponse<Repository, Options> = {
|
|
95
|
+
readonly [Key in AsOf<Options>]: EntityOf<Repository>;
|
|
96
|
+
};
|
|
97
|
+
type OwnerContext<Repository, Options> = Options extends {
|
|
98
|
+
readonly owner: {
|
|
99
|
+
readonly context: infer Key extends string;
|
|
100
|
+
};
|
|
101
|
+
} ? {
|
|
102
|
+
readonly [Name in Key]: IdField<Repository>;
|
|
103
|
+
} : undefined;
|
|
104
|
+
type OwnerKey<Options> = Options extends {
|
|
105
|
+
readonly owner: infer Owner;
|
|
106
|
+
} ? Owner extends {
|
|
107
|
+
readonly key: infer Key;
|
|
108
|
+
} ? Key : "ownerId" : never;
|
|
109
|
+
type LeftOut<Options> = EntityIdentityKey | OwnerKey<Options> | (Options extends {
|
|
110
|
+
readonly omit: readonly (infer Key)[];
|
|
111
|
+
} ? Key : never) | (Options extends {
|
|
112
|
+
readonly values: infer Values;
|
|
113
|
+
} ? keyof Values : never);
|
|
114
|
+
/** The input of `create`: the keys of the entity, an entity it holds by `<key>Id`. */
|
|
115
|
+
type CreateInput<Repository, Options> = {
|
|
116
|
+
readonly [Key in KeyOf<Repository> as Key extends LeftOut<Options> ? never : Props<Repository>[Key] extends AnyRepositoryEntity ? `${Key}Id` : HoldsEntity<Props<Repository>[Key]> extends true ? never : Key]: Props<Repository>[Key] extends AnyRepositoryEntity ? FieldAt<PropertiesOf<Props<Repository>[Key]>, "id"> : Props<Repository>[Key];
|
|
117
|
+
};
|
|
118
|
+
type ChangeKey<Repository> = EntityInstanceOf<EntityOf<Repository>> extends {
|
|
119
|
+
readonly change: infer Changes;
|
|
120
|
+
} ? keyof Changes & KeyOf<Repository> : never;
|
|
121
|
+
/** The input of `update`: the id, and each change key, optional. */
|
|
122
|
+
type UpdateInput<Repository> = {
|
|
123
|
+
readonly [Key in "id" | ChangeKey<Repository> as Key extends "id" ? Key : HoldsEntity<Props<Repository>[Key]> extends true ? never : Key]: Key extends "id" ? IdField<Repository> : Optional<Props<Repository>[Key]>;
|
|
124
|
+
};
|
|
125
|
+
type EntityUseCase<Repository extends AnyRepository, Options, Input extends IBlueprintCollection> = StepsUseCaseConstructor<Input, EntityResponse<Repository, Options>, {
|
|
126
|
+
readonly repository: Repository;
|
|
127
|
+
}, OwnerContext<Repository, Options>>;
|
|
128
|
+
type ById<Repository> = {
|
|
129
|
+
readonly id: IdField<Repository>;
|
|
130
|
+
};
|
|
131
|
+
/** `create`: for a repository with the `create` action. */
|
|
132
|
+
export type CreateMaker = <Repository extends AnyRepository, const Options extends StdCreateOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "create">, options?: Options) => EntityUseCase<Repository, Options, CreateInput<Repository, Options>>;
|
|
133
|
+
/** `get`: for a repository with `findById`. */
|
|
134
|
+
export type GetMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById">, options?: Options) => EntityUseCase<Repository, Options, ById<Repository>>;
|
|
135
|
+
/** `list`: for a repository with `findPage`. */
|
|
136
|
+
export type ListMaker = <Repository extends AnyRepository, const Options extends StdListOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findPage">, options?: Options) => StepsUseCaseConstructor<{
|
|
137
|
+
readonly limit: OptionalProperty<typeof Integer>;
|
|
138
|
+
readonly offset: OptionalProperty<typeof Integer>;
|
|
139
|
+
}, {
|
|
140
|
+
readonly items: IBlueprintList<EntityOf<Repository>>;
|
|
141
|
+
readonly total: typeof Integer;
|
|
142
|
+
readonly limit: typeof Integer;
|
|
143
|
+
readonly offset: typeof Integer;
|
|
144
|
+
}, {
|
|
145
|
+
readonly repository: Repository;
|
|
146
|
+
}, OwnerContext<Repository, Options>>;
|
|
147
|
+
/** `update`: for a repository with `findById` and `update`. */
|
|
148
|
+
export type UpdateMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update">, options?: Options) => EntityUseCase<Repository, Options, UpdateInput<Repository>>;
|
|
149
|
+
/** `delete`: for a repository with `findById` and `delete`. */
|
|
150
|
+
export type DeleteMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "delete">, options?: Options) => EntityUseCase<Repository, Options, ById<Repository>>;
|
|
151
|
+
/** `restore`: for a repository with soft deletion and the `restore` action. */
|
|
152
|
+
export type RestoreMaker = <Repository extends AnyRepository, const Options extends StdUseCaseOptions & {
|
|
153
|
+
readonly as?: string;
|
|
154
|
+
} = None>(repository: Repository & UseCaseTarget<Repository, "restore">, options?: Options) => StepsUseCaseConstructor<ById<Repository>, EntityResponse<Repository, Options>, {
|
|
155
|
+
readonly repository: Repository;
|
|
156
|
+
}>;
|
|
157
|
+
/** `publish` and `unpublish`: for a publishable entity. */
|
|
158
|
+
export type PublishMaker = <Repository extends AnyRepository, const Options extends StdLifecycleOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update", {
|
|
159
|
+
publish(at?: string): unknown;
|
|
160
|
+
unpublish(): unknown;
|
|
161
|
+
}, "publishable">, options?: Options) => EntityUseCase<Repository, Options, ById<Repository>>;
|
|
162
|
+
/** `archive` and `unarchive`: for an archivable entity. */
|
|
163
|
+
export type ArchiveMaker = <Repository extends AnyRepository, const Options extends StdLifecycleOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update", {
|
|
164
|
+
archive(at?: string): unknown;
|
|
165
|
+
readonly isArchived: boolean;
|
|
166
|
+
}, "archivable">, options?: Options) => EntityUseCase<Repository, Options, ById<Repository>>;
|
|
167
|
+
/** `tag` and `untag`: for a taggable entity. */
|
|
168
|
+
export type TagMaker = <Repository extends AnyRepository, const Options extends StdEntityOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findById" | "update", {
|
|
169
|
+
tag(...tags: string[]): unknown;
|
|
170
|
+
untag(...tags: string[]): unknown;
|
|
171
|
+
}, "taggable">, options?: Options) => EntityUseCase<Repository, Options, ById<Repository> & {
|
|
172
|
+
readonly tags: IBlueprintList<typeof StringProperty>;
|
|
173
|
+
}>;
|
|
174
|
+
type Authenticatable = {
|
|
175
|
+
verifyPassword(plain: string): Promise<boolean>;
|
|
176
|
+
};
|
|
177
|
+
/** `register`: for an authenticatable entity, with `findByEmail` and `create`. */
|
|
178
|
+
export type RegisterMaker = <Repository extends AnyRepository, const Options extends StdCreateOptions<Repository> = None>(repository: Repository & UseCaseTarget<Repository, "findByEmail" | "create", Authenticatable, "authenticatable">, options?: Options) => EntityUseCase<Repository, Options, CreateInput<Repository, Options>>;
|
|
179
|
+
/** `login`: for an authenticatable entity, with `findByEmail`. */
|
|
180
|
+
export type LoginMaker = <Repository extends AnyRepository, const Options extends StdUseCaseOptions & {
|
|
181
|
+
readonly as?: string;
|
|
182
|
+
} = None>(repository: Repository & UseCaseTarget<Repository, "findByEmail", Authenticatable, "authenticatable">, options?: Options) => StepsUseCaseConstructor<{
|
|
183
|
+
readonly email: FieldAt<Props<Repository>, "email">;
|
|
184
|
+
readonly password: typeof StringProperty;
|
|
185
|
+
}, EntityResponse<Repository, Options>, {
|
|
186
|
+
readonly repository: Repository;
|
|
187
|
+
}>;
|
|
188
|
+
/**
|
|
189
|
+
* The standard use cases, by name: what the `useCases` of `commandOf` gets
|
|
190
|
+
* to pick from, each called with a repository and its options.
|
|
191
|
+
*/
|
|
192
|
+
export interface StdUseCaseContext {
|
|
193
|
+
readonly create: CreateMaker;
|
|
194
|
+
readonly get: GetMaker;
|
|
195
|
+
readonly list: ListMaker;
|
|
196
|
+
readonly update: UpdateMaker;
|
|
197
|
+
readonly delete: DeleteMaker;
|
|
198
|
+
readonly restore: RestoreMaker;
|
|
199
|
+
readonly publish: PublishMaker;
|
|
200
|
+
readonly unpublish: PublishMaker;
|
|
201
|
+
readonly archive: ArchiveMaker;
|
|
202
|
+
readonly unarchive: ArchiveMaker;
|
|
203
|
+
readonly tag: TagMaker;
|
|
204
|
+
readonly untag: TagMaker;
|
|
205
|
+
readonly register: RegisterMaker;
|
|
206
|
+
readonly login: LoginMaker;
|
|
207
|
+
}
|
|
208
|
+
/** The plugins of `StdUseCases`, by name. */
|
|
209
|
+
export type StdUseCasePlugins = {
|
|
210
|
+
readonly [Name in keyof StdUseCaseContext]: IUseCasePlugin<Name, StdUseCaseContext[Name]>;
|
|
211
|
+
};
|
|
212
|
+
export {};
|
package/dist/testing/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import"../index-
|
|
1
|
+
import"../index-r78dsvtc.js";
|
|
2
2
|
import {
|
|
3
3
|
RepositoryUniqueViolation2,
|
|
4
4
|
RepositoryEntityNotFound2,
|
|
5
5
|
blueprintOf2,
|
|
6
6
|
entityOf2
|
|
7
|
-
} from "../index-
|
|
7
|
+
} from "../index-q6hggpzw.js";
|
|
8
8
|
|
|
9
9
|
// src/testing/provider-contract.ts
|
|
10
10
|
function contextOf(name = "Accounts") {
|
|
@@ -129,6 +129,14 @@ function providerContract(runner, makeProvider, options = {}) {
|
|
|
129
129
|
await provider.update(context, { ...data, name: "Other" });
|
|
130
130
|
expect(await provider.findOne(context, { where: { id: data.id } })).toEqual({ ...data, name: "Other" });
|
|
131
131
|
});
|
|
132
|
+
it("update clears a value that is left out", async () => {
|
|
133
|
+
const { provider, context, seed } = await fixture();
|
|
134
|
+
const data = row({ deletedAt: "2024-01-01T00:00:00.000Z" });
|
|
135
|
+
await seed(data);
|
|
136
|
+
const { deletedAt: _deletedAt, ...alive } = data;
|
|
137
|
+
await provider.update(context, alive);
|
|
138
|
+
expect(await provider.findOne(context, { where: { id: data.id } })).toEqual(alive);
|
|
139
|
+
});
|
|
132
140
|
it("soft-deleted rows keep occupying unique values", async () => {
|
|
133
141
|
const { provider, context, seed } = await fixture();
|
|
134
142
|
await seed(row({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@afora/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Domain modeling on TypeBox: entities with invariants, repositories, use cases, sensitive data masking and layered exceptions, usable by any Standard Schema consumer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"domain-driven-design",
|