@afora/core 0.0.5 → 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 +4 -1
- package/dist/core/application/types/application.interface.d.ts +56 -11
- package/dist/core/blueprint/types/slice.interface.d.ts +5 -2
- package/dist/core/command/command.d.ts +11 -4
- package/dist/core/command/types/command.interface.d.ts +19 -0
- package/dist/core/entity/abstract-entity.d.ts +1 -1
- package/dist/core/entity/types/entity.interface.d.ts +13 -7
- package/dist/core/exception/exception.d.ts +10 -1
- package/dist/core/output/status.d.ts +4 -3
- package/dist/core/property/input.d.ts +20 -0
- package/dist/core/property/property.d.ts +4 -4
- package/dist/core/property/types/optional-property.type.d.ts +2 -2
- package/dist/core/property/types/property-type.interface.d.ts +17 -0
- package/dist/core/record/abstract-record.d.ts +4 -4
- package/dist/core/record/exceptions.d.ts +2 -0
- package/dist/core/record/name.d.ts +16 -0
- package/dist/core/record/plugin.d.ts +6 -2
- package/dist/core/record/record.d.ts +4 -3
- package/dist/core/record/types/plugin.interface.d.ts +11 -0
- package/dist/core/record/types/record.interface.d.ts +71 -10
- package/dist/core/repository/cursor.d.ts +13 -0
- package/dist/core/repository/exceptions.d.ts +10 -0
- package/dist/core/repository/query.d.ts +4 -4
- package/dist/core/repository/repository.d.ts +2 -2
- package/dist/core/repository/state.d.ts +9 -0
- package/dist/core/repository/types/query.interface.d.ts +37 -0
- package/dist/core/repository/types/repository.interface.d.ts +17 -6
- 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 +24 -7
- 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 +4 -3
- package/dist/{index-8m80wjzf.js → index-q6hggpzw.js} +1210 -85
- package/dist/{index-hn82hm31.js → index-r78dsvtc.js} +136 -66
- package/dist/index.d.ts +22 -12
- package/dist/index.js +47 -5
- package/dist/std/command.d.ts +11 -0
- package/dist/std/properties/password.d.ts +21 -2
- package/dist/std/record-plugins.d.ts +5 -5
- 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 +99 -2
- package/dist/testing/provider-contract.d.ts +5 -0
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type { AnyBlueprint, BlueprintData, BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection, RecordAccepts } from "../../blueprint/types/blueprint.interface.js";
|
|
1
|
+
import type { AnyBlueprint, AnyRecordClass, BlueprintData, BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection, RecordAccepts } from "../../blueprint/types/blueprint.interface.js";
|
|
2
2
|
import type { ParseResult } from "../../parse/types/parse.interface.js";
|
|
3
3
|
import type { StandardSchemaV1 } from "../../standard/standard-schema.js";
|
|
4
4
|
import type { Kind } from "../../symbols/kind.js";
|
|
5
5
|
import type { Meta } from "../../symbols/meta.js";
|
|
6
6
|
import type { AbstractRecord } from "../abstract-record.js";
|
|
7
|
-
import type { AnyRecordPlugin, RecordPluginsOption, RecordUniqueKeys } from "./plugin.interface.js";
|
|
7
|
+
import type { AnyRecordPlugin, RecordPluginsOption, RecordSensitiveKeys, RecordUniqueKeys } from "./plugin.interface.js";
|
|
8
8
|
/** The value of a record, read from its blueprint. */
|
|
9
9
|
export type RecordValue<Record extends AnyRecordShape> = Record extends {
|
|
10
10
|
readonly [Meta]: {
|
|
@@ -41,13 +41,21 @@ export type RecordChange<Record extends AnyRecordShape> = {
|
|
|
41
41
|
/** A key of the blueprint of a record. */
|
|
42
42
|
export type RecordKey<Properties extends IBlueprintCollection> = keyof Properties & string;
|
|
43
43
|
/** What `recordOf` takes, after the blueprint, to describe its keys. */
|
|
44
|
-
export interface RecordOptions<Properties extends IBlueprintCollection, Change extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Unique extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Plugins extends readonly AnyRecordPlugin[] = readonly AnyRecordPlugin[], Context = unknown> {
|
|
44
|
+
export interface RecordOptions<Properties extends IBlueprintCollection, Change extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Unique extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Plugins extends readonly AnyRecordPlugin[] = readonly AnyRecordPlugin[], Context = unknown, Sensitive extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[]> {
|
|
45
|
+
/**
|
|
46
|
+
* The name of the record, read with `recordNameOf(Class)` (or
|
|
47
|
+
* `Class.recordName`): an identifier that stays the same when a bundler
|
|
48
|
+
* renames the class. It names the record in exceptions, in the context a
|
|
49
|
+
* repository gives its provider (`context.name` is `<name>Repository`) and
|
|
50
|
+
* in OpenAPI. Without it, the name of the class is used.
|
|
51
|
+
*/
|
|
52
|
+
readonly name?: string;
|
|
45
53
|
/**
|
|
46
54
|
* Keys whose values `toJSON` masks, every leaf as `MASK`, at any depth. A
|
|
47
55
|
* key holding a property declared `sensitive` (as `Password`), or a list of
|
|
48
56
|
* them, is always sensitive, and such a property is masked at any depth.
|
|
49
57
|
*/
|
|
50
|
-
readonly sensitive?:
|
|
58
|
+
readonly sensitive?: Sensitive;
|
|
51
59
|
/**
|
|
52
60
|
* Keys whose value no two records may share. Records only list them in
|
|
53
61
|
* `options.unique`: repositories (or the database) enforce it. A key
|
|
@@ -91,45 +99,98 @@ interface RecordChangeNamespace<Properties extends IBlueprintCollection, Change
|
|
|
91
99
|
type RecordChangeMethods<Self, Properties extends IBlueprintCollection, Key extends string> = {
|
|
92
100
|
readonly [K in Key]: (value: K extends keyof BlueprintValue<Properties> ? BlueprintValue<Properties>[K] : never) => Self;
|
|
93
101
|
};
|
|
94
|
-
|
|
102
|
+
/**
|
|
103
|
+
* A sensitive value as `toJSON` gives it: each leaf (string, number, boolean,
|
|
104
|
+
* bigint) is the string `MASK`; lists and objects keep their shape, and
|
|
105
|
+
* `null` and `undefined` stay.
|
|
106
|
+
*/
|
|
107
|
+
export type Masked<Value> = Value extends null | undefined ? Value : Value extends readonly (infer Item)[] ? Masked<Item>[] : Value extends object ? {
|
|
108
|
+
readonly [Key in keyof Value]: Masked<Value[Key]>;
|
|
109
|
+
} : string;
|
|
110
|
+
/** What `toJSON` gives for `Field`, whose plain value is `Data`. */
|
|
111
|
+
type JsonField<Field, Data> = Field extends {
|
|
112
|
+
readonly type: {
|
|
113
|
+
readonly sensitive: true;
|
|
114
|
+
};
|
|
115
|
+
} ? Masked<Data> : Field extends {
|
|
116
|
+
readonly type: unknown;
|
|
117
|
+
} ? Data : Field extends AnyRecordClass ? JsonOfRecord<Field, Data> : Field extends {
|
|
118
|
+
readonly record: infer Class extends AnyRecordClass;
|
|
119
|
+
} ? JsonOfRecord<Class, Data> : Field extends {
|
|
120
|
+
readonly properties: infer Properties extends IBlueprintCollection;
|
|
121
|
+
} ? Data extends object ? JsonKeys<Properties, Data, never> : Data : Field extends {
|
|
122
|
+
readonly item: infer Item;
|
|
123
|
+
} ? Data extends readonly (infer ItemData)[] ? JsonField<Item, ItemData>[] : Data : Data;
|
|
124
|
+
/** What the `toJSON` of the records of `Class` gives, in place of their plain value. */
|
|
125
|
+
type JsonOfRecord<Class extends AnyRecordClass, Data> = InstanceType<Class> extends {
|
|
126
|
+
toJSON(): infer Json;
|
|
127
|
+
} ? Data extends object ? Json : Data : Data;
|
|
128
|
+
/**
|
|
129
|
+
* `Data`, the plain value of `Properties`, key by key as `toJSON` gives it.
|
|
130
|
+
* `Result` is a parameter so that no alias names the object.
|
|
131
|
+
*/
|
|
132
|
+
type JsonKeys<Properties, Data, Sensitive, Result = {
|
|
133
|
+
readonly [Key in keyof Data]: Key extends Sensitive ? Masked<Data[Key]> : Key extends keyof Properties ? JsonField<Properties[Key], Data[Key]> : Data[Key];
|
|
134
|
+
}> = Result;
|
|
135
|
+
/**
|
|
136
|
+
* What `toJSON` of a record of `Properties` gives: every key of its plain
|
|
137
|
+
* value, with the keys of `Sensitive` and every property declared
|
|
138
|
+
* `sensitive`, at any depth, as `Masked`. `Result` is a parameter so that no
|
|
139
|
+
* alias names the object.
|
|
140
|
+
*/
|
|
141
|
+
type JsonValue<Properties extends IBlueprintCollection, Sensitive, Result = 0 extends 1 & Properties ? Readonly<Record<string, unknown>> : string extends keyof Properties ? Readonly<Record<string, unknown>> : JsonKeys<Properties, BlueprintData<Properties>, Sensitive>> = Result;
|
|
142
|
+
/**
|
|
143
|
+
* The type of `toJSON()` of a record of `Properties` whose `options.sensitive`
|
|
144
|
+
* lists `Sensitive`: the plain value, no key left out, sensitive values masked.
|
|
145
|
+
*/
|
|
146
|
+
export type RecordJson<Properties extends IBlueprintCollection, Sensitive extends string = never> = JsonValue<Properties, Sensitive>;
|
|
147
|
+
export interface IRecord<Properties extends IBlueprintCollection, Sensitive extends string = never> {
|
|
95
148
|
/** Declaration metadata, separate from domain keys. */
|
|
96
149
|
readonly [Meta]: {
|
|
97
150
|
readonly blueprint: IBlueprint<Properties>;
|
|
98
151
|
readonly options: RecordSettings<Properties>;
|
|
99
152
|
};
|
|
100
153
|
/** The value with the sensitive keys, and every `sensitive` property, masked. */
|
|
101
|
-
toJSON():
|
|
154
|
+
toJSON(): RecordJson<Properties, Sensitive>;
|
|
102
155
|
}
|
|
103
156
|
/**
|
|
104
157
|
* The records of the class `recordOf` returns, with what its plugins add,
|
|
105
158
|
* `Members`.
|
|
106
159
|
*/
|
|
107
|
-
export type DeclaredRecordInstance<Properties extends IBlueprintCollection, Change extends readonly string[], Members = unknown> = AbstractRecord<Properties> & Readonly<BlueprintValue<Properties>> & RecordChanges<Properties, Change> & Members;
|
|
160
|
+
export type DeclaredRecordInstance<Properties extends IBlueprintCollection, Change extends readonly string[], Members = unknown, Sensitive extends string = never> = AbstractRecord<Properties, Sensitive> & Readonly<BlueprintValue<Properties>> & RecordChanges<Properties, Change> & Members;
|
|
108
161
|
/**
|
|
109
162
|
* What `recordOf` returns: a class built from the value of the blueprint;
|
|
110
163
|
* `Unique` are the keys of `options.unique`, and `Members` what its plugins
|
|
111
164
|
* add to its records.
|
|
112
165
|
*/
|
|
113
|
-
export type RecordConstructor<Properties extends IBlueprintCollection, Change extends readonly string[], Unique extends string = never, Members = unknown, Accepts = never> = {
|
|
114
|
-
new (value: BlueprintInput<Properties>): DeclaredRecordInstance<Properties, Change, Members>;
|
|
166
|
+
export type RecordConstructor<Properties extends IBlueprintCollection, Change extends readonly string[], Unique extends string = never, Members = unknown, Accepts = never, Sensitive extends string = never> = {
|
|
167
|
+
new (value: BlueprintInput<Properties>): DeclaredRecordInstance<Properties, Change, Members, Sensitive>;
|
|
115
168
|
/**
|
|
116
169
|
* The Standard Schema of the class: `validate` gives an instance of it, or
|
|
117
170
|
* the issues that reject the value. Typed with the instance of the class
|
|
118
171
|
* `recordOf` returns, not of a subclass.
|
|
119
172
|
*/
|
|
120
|
-
readonly "~standard": StandardSchemaV1.Props<BlueprintInput<Properties>, DeclaredRecordInstance<Properties, Change, Members>>;
|
|
173
|
+
readonly "~standard": StandardSchemaV1.Props<BlueprintInput<Properties>, DeclaredRecordInstance<Properties, Change, Members, Sensitive>>;
|
|
121
174
|
/**
|
|
122
175
|
* An instance of the class (a subclass included) for `value`, or every
|
|
123
176
|
* issue that rejects it; never throws for an invalid value.
|
|
124
177
|
*/
|
|
125
178
|
safeParse<Self extends new (value: never) => object>(this: Self, value: unknown): ParseResult<InstanceType<Self>>;
|
|
126
179
|
readonly [Kind]: "record";
|
|
180
|
+
/**
|
|
181
|
+
* The name of the class as declared: the `name` option, which a bundler
|
|
182
|
+
* does not rename, or the name of the class without one. A subclass may
|
|
183
|
+
* give its own with `static override readonly recordName = "..."`.
|
|
184
|
+
*/
|
|
185
|
+
readonly recordName: string;
|
|
127
186
|
readonly blueprint: IBlueprint<Properties>;
|
|
128
187
|
readonly options: RecordSettings<Properties>;
|
|
129
188
|
/** Its plugins, those they require included, each once. */
|
|
130
189
|
readonly plugins: readonly AnyRecordPlugin[];
|
|
131
190
|
/** The keys of `options.unique`, for the type of a plugin of the class. */
|
|
132
191
|
readonly [RecordUniqueKeys]?: Unique;
|
|
192
|
+
/** The keys of `options.sensitive`, for the type of a plugin of the class. */
|
|
193
|
+
readonly [RecordSensitiveKeys]?: Sensitive;
|
|
133
194
|
/** More values a field of this class takes, as a sliced record takes the original's records. */
|
|
134
195
|
readonly [RecordAccepts]?: Accepts;
|
|
135
196
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RepositoryOrder } from "./types/query.interface.js";
|
|
2
|
+
import type { RepositoryData } from "./types/repository.interface.js";
|
|
3
|
+
/**
|
|
4
|
+
* The cursor of `row` in `orders`: an opaque text holding the values of its
|
|
5
|
+
* order keys. It is not encrypted or signed: whoever has it can read those
|
|
6
|
+
* values, and changing it only moves the position inside the same query.
|
|
7
|
+
*/
|
|
8
|
+
export declare function encodeCursor(orders: readonly RepositoryOrder[], row: RepositoryData): string;
|
|
9
|
+
/**
|
|
10
|
+
* The values a cursor holds, one for each of `orders`; throws
|
|
11
|
+
* `InvalidRepositoryCursor` when it is not a cursor or was made for another order.
|
|
12
|
+
*/
|
|
13
|
+
export declare function decodeCursor(source: string, orders: readonly RepositoryOrder[], cursor: unknown): readonly unknown[];
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
import type { Issue } from "../parse/types/parse.interface.js";
|
|
1
2
|
/** A query contains unsupported keys, ordering or pagination. */
|
|
2
3
|
export declare const InvalidRepositoryQuery: import("../../index.js").DeclaredExceptionClass<{
|
|
3
4
|
readonly reason: string;
|
|
4
5
|
}>;
|
|
6
|
+
/**
|
|
7
|
+
* `findCursor` got an `after` that is not a cursor it gave, or one made for
|
|
8
|
+
* another `orderBy`; `source` is the repository name. Its `issues` name the
|
|
9
|
+
* `after` of the query, so an output answers it as invalid input.
|
|
10
|
+
*/
|
|
11
|
+
export declare const InvalidRepositoryCursor: import("../../index.js").DeclaredExceptionClass<{
|
|
12
|
+
readonly reason: string;
|
|
13
|
+
readonly issues: readonly Issue[];
|
|
14
|
+
}>;
|
|
5
15
|
/** `repositoryOf` got something other than an entity, actions and a provider. */
|
|
6
16
|
export declare const InvalidRepositoryDeclaration: import("../../index.js").DeclaredExceptionClass<{
|
|
7
17
|
readonly reason: string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { RepositoryQuery } from "./types/query.interface.js";
|
|
1
|
+
import type { ProviderQuery, RepositoryQuery } from "./types/query.interface.js";
|
|
2
2
|
import type { RepositoryContext, RepositoryData } from "./types/repository.interface.js";
|
|
3
3
|
/** Validates query structure and optionally normalizes domain values for storage. */
|
|
4
|
-
export declare function prepareQuery(context: RepositoryContext, input?: unknown, map?: (key: string, value: unknown) => unknown): RepositoryQuery;
|
|
4
|
+
export declare function prepareQuery(context: RepositoryContext, input?: unknown, map?: (key: string, value: unknown) => unknown, options?: readonly string[]): RepositoryQuery;
|
|
5
5
|
/** Structural JSON/data equality without depending on object key insertion order. */
|
|
6
6
|
export declare function sameValue(a: unknown, b: unknown): boolean;
|
|
7
7
|
export declare function matchesQuery(data: RepositoryData, query: RepositoryQuery): boolean;
|
|
8
|
-
/** The rows of `query` after ordering, offset and limit. */
|
|
9
|
-
export declare function selectRows(rows: Iterable<RepositoryData>, query:
|
|
8
|
+
/** The rows of `query` after ordering, `after`, offset and limit. */
|
|
9
|
+
export declare function selectRows(rows: Iterable<RepositoryData>, query: ProviderQuery): RepositoryData[];
|
|
@@ -51,8 +51,8 @@ export declare abstract class AbstractRepository<Entity extends AnyRepositoryEnt
|
|
|
51
51
|
* A repository of `entity` with the methods of `actions`: `findBy<Key>` for a
|
|
52
52
|
* unique key (`id`, `options.unique`, a `unique` property), giving the entity
|
|
53
53
|
* or `undefined`; `findManyBy<Key>` for any other key, giving a list;
|
|
54
|
-
* `create`, `update`, `delete`, `count`, and the queries `findMany`,
|
|
55
|
-
* `exists` and `
|
|
54
|
+
* `create`, `update`, `delete`, `restore` (soft deletion only), `count`, and the queries `findMany`,
|
|
55
|
+
* `exists`, `findPage` and `findCursor`. Every method is async and reads and writes through `provider`
|
|
56
56
|
* (a new `inMemoryProvider()` by default), which gets the variables of
|
|
57
57
|
* `environmentDependencies` as `context.env`. The repository can be a
|
|
58
58
|
* dependency of a use case, and `RepositoryOf` extends it with a class.
|
|
@@ -37,6 +37,15 @@ export interface RepositoryInspection {
|
|
|
37
37
|
}
|
|
38
38
|
/** The declaration of `repository`; `undefined` when it is not one. */
|
|
39
39
|
export declare function inspectRepository(repository: unknown): RepositoryInspection | undefined;
|
|
40
|
+
/** Keeps `repository` as the one `repositoryOf` made for `state`. */
|
|
41
|
+
export declare function declareRepository(state: RepositoryState, repository: object): void;
|
|
42
|
+
/**
|
|
43
|
+
* The repository `repositoryOf` made for the entity class `Entity` that the
|
|
44
|
+
* entities of `repository` hold; `undefined` when it has no such relation.
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export declare function relatedRepositoryOf(repository: object, Entity: object): object | undefined;
|
|
40
49
|
/**
|
|
41
50
|
* A repository sharing the declaration of `repository` that reads and writes
|
|
42
51
|
* through `provider`, unless it declared a provider of its own; a declared
|
|
@@ -30,6 +30,41 @@ export interface RepositoryPage<Entity> {
|
|
|
30
30
|
readonly limit: number;
|
|
31
31
|
readonly offset: number;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* The query of `findCursor`: as `RepositoryQuery`, with the `next` of the
|
|
35
|
+
* previous page as `after` in place of `offset`. `where`, `orderBy` and
|
|
36
|
+
* `withDeleted` must stay the same from one page to the next.
|
|
37
|
+
*/
|
|
38
|
+
export interface RepositoryCursorQuery<Entity = unknown> extends Omit<RepositoryQuery<Entity>, "offset" | "limit"> {
|
|
39
|
+
/** Positive integer; 20 when left out. */
|
|
40
|
+
readonly limit?: number;
|
|
41
|
+
/** The `next` of the page before; the first page when left out. */
|
|
42
|
+
readonly after?: string;
|
|
43
|
+
}
|
|
44
|
+
/** A page read by position: its items and, when there are more, the cursor of the next. */
|
|
45
|
+
export interface RepositoryCursorPage<Entity> {
|
|
46
|
+
readonly items: readonly EntityInstanceOf<Entity>[];
|
|
47
|
+
readonly limit: number;
|
|
48
|
+
/** What to give as `after` for the next page; `undefined` on the last one. */
|
|
49
|
+
readonly next: string | undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* What a provider gets from `findCursor` on `findMany`: `after` holds one
|
|
53
|
+
* value for each order of `orderBy`, those of the last row already read, and
|
|
54
|
+
* the provider gives the rows strictly after it in that order. Only a
|
|
55
|
+
* provider with `capabilities.keyset` is given it.
|
|
56
|
+
*/
|
|
57
|
+
export interface ProviderQuery extends RepositoryQuery {
|
|
58
|
+
readonly after?: readonly unknown[];
|
|
59
|
+
}
|
|
60
|
+
export interface RepositoryFindCursor<Entity> {
|
|
61
|
+
/**
|
|
62
|
+
* A page of `limit` items (20 by default) after the cursor `after`. Unlike
|
|
63
|
+
* `findPage` it reads one page with one query, does not count, and stays
|
|
64
|
+
* correct when rows are added or removed between pages.
|
|
65
|
+
*/
|
|
66
|
+
findCursor(query?: RepositoryCursorQuery<Entity>): Promise<RepositoryCursorPage<Entity>>;
|
|
67
|
+
}
|
|
33
68
|
export interface RepositoryFindMany<Entity> {
|
|
34
69
|
findMany(query?: RepositoryQuery<Entity>): Promise<EntityInstanceOf<Entity>[]>;
|
|
35
70
|
}
|
|
@@ -57,6 +92,8 @@ export type ProviderDescription = {
|
|
|
57
92
|
export interface ProviderCapabilities {
|
|
58
93
|
readonly operators?: readonly string[];
|
|
59
94
|
readonly transactions?: boolean;
|
|
95
|
+
/** Whether `findMany` honors `after` (`ProviderQuery`), which `findCursor` needs. */
|
|
96
|
+
readonly keyset?: boolean;
|
|
60
97
|
}
|
|
61
98
|
/** Declaration input for a named provider, implemented by providerOf in T10. */
|
|
62
99
|
export interface ProviderSettings<Options extends ProviderOptions = ProviderOptions, Provider extends IRepositoryProvider = IRepositoryProvider> {
|
|
@@ -3,7 +3,7 @@ import type { AbstractEntity, EntityUniqueKeys } from "../../entity/abstract-ent
|
|
|
3
3
|
import type { AnyEntityClass } from "../../entity/event.js";
|
|
4
4
|
import type { RecordSettings } from "../../record/types/record.interface.js";
|
|
5
5
|
import type { AbstractRepository } from "../repository.js";
|
|
6
|
-
import type { ProviderCapabilities, RepositoryExists, RepositoryFindMany, RepositoryFindPage, RepositoryQuery } from "./query.interface.js";
|
|
6
|
+
import type { ProviderCapabilities, ProviderQuery, RepositoryExists, RepositoryFindCursor, RepositoryFindMany, RepositoryFindPage, RepositoryQuery } from "./query.interface.js";
|
|
7
7
|
/** Any class made by `entityOf`, or extending one, that a repository holds. */
|
|
8
8
|
export type AnyRepositoryEntity = AnyRecordClass & AnyEntityClass & {
|
|
9
9
|
readonly [EntityUniqueKeys]: readonly string[];
|
|
@@ -42,6 +42,13 @@ export interface RepositoryUpdate<Entity> {
|
|
|
42
42
|
export interface RepositoryDelete<Entity> {
|
|
43
43
|
delete(entity: EntityInstanceOf<Entity> | string): Promise<EntityInstanceOf<Entity>>;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* `restore`: brings back a soft-deleted entity (or the one of the id) and
|
|
47
|
+
* gives it, alive; only for a repository with `deletion: "soft"`.
|
|
48
|
+
*/
|
|
49
|
+
export interface RepositoryRestore<Entity> {
|
|
50
|
+
restore(entity: EntityInstanceOf<Entity> | string): Promise<EntityInstanceOf<Entity>>;
|
|
51
|
+
}
|
|
45
52
|
/** `count`: how many entities it holds. */
|
|
46
53
|
export interface RepositoryCount<Entity = unknown> {
|
|
47
54
|
count(query?: RepositoryQuery<Entity>): Promise<number>;
|
|
@@ -67,9 +74,9 @@ export type RepositoryFindManyBy<Entity, Actions extends string = string> = {
|
|
|
67
74
|
* so that a class extending a repository can override them.
|
|
68
75
|
* @internal
|
|
69
76
|
*/
|
|
70
|
-
export type RepositoryMethods<Entity, Actions extends string = string> = RepositoryFindBy<Entity, Actions> & RepositoryFindManyBy<Entity, Actions> & ("create" extends Actions ? RepositoryCreate<Entity> : unknown) & ("update" extends Actions ? RepositoryUpdate<Entity> : unknown) & ("delete" extends Actions ? RepositoryDelete<Entity> : unknown) & ("count" extends Actions ? RepositoryCount<Entity> : unknown) & ("findMany" extends Actions ? RepositoryFindMany<Entity> : unknown) & ("exists" extends Actions ? RepositoryExists<Entity> : unknown) & ("findPage" extends Actions ? RepositoryFindPage<Entity> : unknown);
|
|
77
|
+
export type RepositoryMethods<Entity, Actions extends string = string> = RepositoryFindBy<Entity, Actions> & RepositoryFindManyBy<Entity, Actions> & ("create" extends Actions ? RepositoryCreate<Entity> : unknown) & ("update" extends Actions ? RepositoryUpdate<Entity> : unknown) & ("delete" extends Actions ? RepositoryDelete<Entity> : unknown) & ("restore" extends Actions ? RepositoryRestore<Entity> : unknown) & ("count" extends Actions ? RepositoryCount<Entity> : unknown) & ("findMany" extends Actions ? RepositoryFindMany<Entity> : unknown) & ("exists" extends Actions ? RepositoryExists<Entity> : unknown) & ("findPage" extends Actions ? RepositoryFindPage<Entity> : unknown) & ("findCursor" extends Actions ? RepositoryFindCursor<Entity> : unknown);
|
|
71
78
|
/** A method a repository of `Entity` can have, e.g. `"findByEmail"`. */
|
|
72
|
-
export type RepositoryAction<Entity> = keyof RepositoryFindBy<Entity> | keyof RepositoryFindManyBy<Entity> | "create" | "update" | "delete" | "count" | "findMany" | "exists" | "findPage";
|
|
79
|
+
export type RepositoryAction<Entity> = keyof RepositoryFindBy<Entity> | keyof RepositoryFindManyBy<Entity> | "create" | "update" | "delete" | "restore" | "count" | "findMany" | "exists" | "findPage" | "findCursor";
|
|
73
80
|
/**
|
|
74
81
|
* A repository of `Entity` with the methods of `Actions`, whose provider gets
|
|
75
82
|
* `Environment` as `context.env`, e.g.
|
|
@@ -82,9 +89,12 @@ export type AnyRepository = AbstractRepository<any, any, any>;
|
|
|
82
89
|
export type RepositoryData = Readonly<Record<string, unknown>>;
|
|
83
90
|
/** What a provider gets with every call: whose entities, and the environment. */
|
|
84
91
|
export interface RepositoryContext<Environment = unknown> {
|
|
85
|
-
/** The entity class
|
|
92
|
+
/** The entity class; name a table after `recordNameOf(entity)`, not `entity.name`. */
|
|
86
93
|
readonly entity: AnyRepositoryEntity;
|
|
87
|
-
/**
|
|
94
|
+
/**
|
|
95
|
+
* The name of the repository, `<entity name>Repository`, for the source of
|
|
96
|
+
* its exceptions; stable under minification when the entity declares a `name`.
|
|
97
|
+
*/
|
|
88
98
|
readonly name: string;
|
|
89
99
|
/** The keys no two entities may share, `id` first. */
|
|
90
100
|
readonly unique: readonly string[];
|
|
@@ -99,7 +109,8 @@ export interface RepositoryContext<Environment = unknown> {
|
|
|
99
109
|
*/
|
|
100
110
|
export interface IRepositoryProvider<Environment = unknown> {
|
|
101
111
|
findOne(context: RepositoryContext<Environment>, query?: RepositoryQuery): Promise<RepositoryData | undefined>;
|
|
102
|
-
|
|
112
|
+
/** `query.after` is only given to a provider with `capabilities.keyset`. */
|
|
113
|
+
findMany(context: RepositoryContext<Environment>, query?: ProviderQuery): Promise<readonly RepositoryData[]>;
|
|
103
114
|
insert(context: RepositoryContext<Environment>, data: RepositoryData): Promise<void>;
|
|
104
115
|
update(context: RepositoryContext<Environment>, data: RepositoryData): Promise<void>;
|
|
105
116
|
remove(context: RepositoryContext<Environment>, id: string): Promise<void>;
|
|
@@ -8,3 +8,39 @@ export declare const InvalidUseCaseStep: import("../../index.js").DeclaredExcept
|
|
|
8
8
|
readonly reason: string;
|
|
9
9
|
readonly code?: string;
|
|
10
10
|
}>;
|
|
11
|
+
/**
|
|
12
|
+
* `useCasePluginOf` or `useCaseCatalogOf` was given what cannot be a plugin
|
|
13
|
+
* or a catalog: an invalid name or version, no `compile`, two plugins with
|
|
14
|
+
* one name; or a plugin was called with something that is not a repository
|
|
15
|
+
* or with options that are not a plain object. `source` is the plugin name.
|
|
16
|
+
*/
|
|
17
|
+
export declare const InvalidUseCasePlugin: import("../../index.js").DeclaredExceptionClass<{
|
|
18
|
+
readonly reason: string;
|
|
19
|
+
}>;
|
|
20
|
+
/** A catalog has no use case plugin of that name; `source` is `catalog`. */
|
|
21
|
+
export declare const UnknownUseCasePlugin: import("../../index.js").DeclaredExceptionClass<{
|
|
22
|
+
readonly name: string;
|
|
23
|
+
}>;
|
|
24
|
+
/** The use case plugin of a catalog does not satisfy the version asked for; `source` is the plugin name. */
|
|
25
|
+
export declare const IncompatibleUseCasePlugin: import("../../index.js").DeclaredExceptionClass<{
|
|
26
|
+
readonly reason: string;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* A use case plugin cannot make its use case for a repository: the
|
|
30
|
+
* repository lacks an action, the entity lacks a record plugin, or an option
|
|
31
|
+
* names what the entity does not have. `source` is the plugin name.
|
|
32
|
+
*/
|
|
33
|
+
export declare const UseCaseUnavailable: import("../../index.js").DeclaredExceptionClass<{
|
|
34
|
+
readonly repository: string;
|
|
35
|
+
readonly reason: string;
|
|
36
|
+
readonly missing?: {
|
|
37
|
+
readonly actions: readonly string[];
|
|
38
|
+
readonly plugins: readonly string[];
|
|
39
|
+
};
|
|
40
|
+
}>;
|
|
41
|
+
/** What a use case made by a plugin throws for an entity it did not find, unless given another error. */
|
|
42
|
+
export declare const UseCaseEntityNotFound: import("../../index.js").DeclaredExceptionClass<undefined>;
|
|
43
|
+
/** What a use case made by a plugin throws for an entity that already exists, unless given another error. */
|
|
44
|
+
export declare const UseCaseEntityConflict: import("../../index.js").DeclaredExceptionClass<undefined>;
|
|
45
|
+
/** What a use case made by a plugin throws for credentials that do not match, unless given another error. */
|
|
46
|
+
export declare const UseCaseInvalidCredentials: import("../../index.js").DeclaredExceptionClass<undefined>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AnyUseCasePlugin, IUseCaseCatalog, UseCasePluginsByName } from "./types/plugin.interface.js";
|
|
2
|
+
/** Whether `value` is a catalog made by `useCaseCatalogOf`. */
|
|
3
|
+
export declare function isUseCaseCatalog(value: unknown): value is IUseCaseCatalog;
|
|
4
|
+
/**
|
|
5
|
+
* A catalog of use case `plugins`, by name: what `commandBuilderOf` gives
|
|
6
|
+
* the `useCases` of a command to pick from, as
|
|
7
|
+
* `(ctx) => ({ create: ctx.create(Posts) })`; `catalog.context` is that
|
|
8
|
+
* `ctx`, for use outside a command. Throws `InvalidUseCasePlugin` for what
|
|
9
|
+
* is not a plugin and for two plugins with the same name.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useCaseCatalogOf<const Plugins extends readonly AnyUseCasePlugin[]>(...plugins: Plugins): IUseCaseCatalog<UseCasePluginsByName<Plugins>>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AnyUseCasePlugin, IUseCasePlugin, UseCaseMaker, UseCaseOriginInfo, UseCasePluginSettings } from "./types/plugin.interface.js";
|
|
2
|
+
/** Whether `value` is a plugin made by `useCasePluginOf`. */
|
|
3
|
+
export declare function isUseCasePlugin(value: unknown): value is AnyUseCasePlugin;
|
|
4
|
+
/**
|
|
5
|
+
* The plugin, the repository and the options a use case class was made
|
|
6
|
+
* from, by a plugin's `make`; `undefined` for a class declared by hand. A
|
|
7
|
+
* class extending one keeps its origin.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useCaseOriginOf(UseCase: unknown): UseCaseOriginInfo | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* A use case that is made for a repository. `settings.compile` gets what is
|
|
12
|
+
* known of the repository and its entity (`UseCaseTargetInfo`) and the
|
|
13
|
+
* options of the call, and gives the settings of `useCaseOf` with `steps`;
|
|
14
|
+
* `settings.requires` lists the actions and record plugins it cannot do
|
|
15
|
+
* without. The plugin's `make(repository, options)` gives the class, the
|
|
16
|
+
* same for the same repository and equal options.
|
|
17
|
+
*
|
|
18
|
+
* `Make` types that call: give it to say which repositories are accepted
|
|
19
|
+
* (`UseCaseTarget`) and what the use case takes and returns. Without it any
|
|
20
|
+
* repository is accepted and the class is `AnyUseCaseClass`.
|
|
21
|
+
*
|
|
22
|
+
* Throws `InvalidUseCasePlugin` for a name that is not an identifier, a
|
|
23
|
+
* version that is not semantic or a `compile` that is not a function.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useCasePluginOf<const Name extends string, Make extends UseCaseMaker = UseCaseMaker>(name: Name, settings: UseCasePluginSettings): IUseCasePlugin<Name, Make>;
|
|
@@ -10,8 +10,13 @@ export type StepReference = `$${string}`;
|
|
|
10
10
|
export type StepValue = StepReference | string | number | boolean | null | readonly StepValue[] | {
|
|
11
11
|
readonly [key: string]: StepValue;
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* The arguments of a `find`, `invoke` or `run`: a list of values, or one
|
|
15
|
+
* reference to a list, which is spread (`args: "$input.tags"`).
|
|
16
|
+
*/
|
|
17
|
+
export type StepArgs = readonly StepValue[] | StepReference;
|
|
13
18
|
/** The actions of a repository that only read. */
|
|
14
|
-
export type StepReadAction = `findBy${string}` | `findManyBy${string}` | "findMany" | "exists" | "findPage" | "count";
|
|
19
|
+
export type StepReadAction = `findBy${string}` | `findManyBy${string}` | "findMany" | "exists" | "findPage" | "findCursor" | "count";
|
|
15
20
|
type RepositoryKeys<Dependencies> = {
|
|
16
21
|
[Key in keyof Dependencies & string]: Dependencies[Key] extends AnyRepository ? Key : never;
|
|
17
22
|
}[keyof Dependencies & string];
|
|
@@ -40,7 +45,7 @@ export type StepEnsureKind = "exists" | "absent" | "equals" | "notEquals" | "tru
|
|
|
40
45
|
export type UseCaseStep<Dependencies extends UseCaseCollection = UseCaseCollection> = {
|
|
41
46
|
/** `"<repository>.<read action>"`. */
|
|
42
47
|
readonly find: StepFindTarget<Dependencies>;
|
|
43
|
-
readonly args?:
|
|
48
|
+
readonly args?: StepArgs;
|
|
44
49
|
readonly as?: string;
|
|
45
50
|
} | {
|
|
46
51
|
readonly ensure: "exists" | "absent" | "true" | "false";
|
|
@@ -64,12 +69,14 @@ export type UseCaseStep<Dependencies extends UseCaseCollection = UseCaseCollecti
|
|
|
64
69
|
readonly change: StepReference;
|
|
65
70
|
readonly key: string;
|
|
66
71
|
readonly value: StepValue;
|
|
72
|
+
/** Skips the step when `value` resolves to `undefined`: a partial update. */
|
|
73
|
+
readonly ifPresent?: boolean;
|
|
67
74
|
readonly as?: string;
|
|
68
75
|
} | {
|
|
69
76
|
/** A reference to a record or entity. */
|
|
70
77
|
readonly invoke: StepReference;
|
|
71
78
|
readonly method: string;
|
|
72
|
-
readonly args?:
|
|
79
|
+
readonly args?: StepArgs;
|
|
73
80
|
readonly as?: string;
|
|
74
81
|
} | {
|
|
75
82
|
readonly create: StepRepositoryWith<Dependencies, "create">;
|
|
@@ -83,6 +90,11 @@ export type UseCaseStep<Dependencies extends UseCaseCollection = UseCaseCollecti
|
|
|
83
90
|
readonly delete: StepRepositoryWith<Dependencies, "delete">;
|
|
84
91
|
readonly value: StepValue;
|
|
85
92
|
readonly as?: string;
|
|
93
|
+
} | {
|
|
94
|
+
/** A soft-deleted entity or its id; the repository gives it back alive. */
|
|
95
|
+
readonly restore: StepRepositoryWith<Dependencies, "restore">;
|
|
96
|
+
readonly value: StepValue;
|
|
97
|
+
readonly as?: string;
|
|
86
98
|
} | {
|
|
87
99
|
readonly call: UseCaseKeys<Dependencies>;
|
|
88
100
|
readonly input: StepValue;
|
|
@@ -90,7 +102,7 @@ export type UseCaseStep<Dependencies extends UseCaseCollection = UseCaseCollecti
|
|
|
90
102
|
} | {
|
|
91
103
|
readonly run: string;
|
|
92
104
|
readonly fn: (...args: any[]) => unknown;
|
|
93
|
-
readonly args?:
|
|
105
|
+
readonly args?: StepArgs;
|
|
94
106
|
readonly as?: string;
|
|
95
107
|
} | {
|
|
96
108
|
readonly return: StepValue;
|
|
@@ -102,7 +114,7 @@ export type StepErrorDescription = string | {
|
|
|
102
114
|
/** A `UseCaseStep` as `describe()` gives it: classes by name, functions as `opaque`. */
|
|
103
115
|
export type UseCaseStepDescription = {
|
|
104
116
|
readonly find: string;
|
|
105
|
-
readonly args?:
|
|
117
|
+
readonly args?: StepArgs;
|
|
106
118
|
readonly as?: string;
|
|
107
119
|
} | {
|
|
108
120
|
readonly ensure: StepEnsureKind;
|
|
@@ -118,11 +130,12 @@ export type UseCaseStepDescription = {
|
|
|
118
130
|
readonly change: string;
|
|
119
131
|
readonly key: string;
|
|
120
132
|
readonly value: StepValue;
|
|
133
|
+
readonly ifPresent?: boolean;
|
|
121
134
|
readonly as?: string;
|
|
122
135
|
} | {
|
|
123
136
|
readonly invoke: string;
|
|
124
137
|
readonly method: string;
|
|
125
|
-
readonly args?:
|
|
138
|
+
readonly args?: StepArgs;
|
|
126
139
|
readonly as?: string;
|
|
127
140
|
} | {
|
|
128
141
|
readonly create: string;
|
|
@@ -136,6 +149,10 @@ export type UseCaseStepDescription = {
|
|
|
136
149
|
readonly delete: string;
|
|
137
150
|
readonly value: StepValue;
|
|
138
151
|
readonly as?: string;
|
|
152
|
+
} | {
|
|
153
|
+
readonly restore: string;
|
|
154
|
+
readonly value: StepValue;
|
|
155
|
+
readonly as?: string;
|
|
139
156
|
} | {
|
|
140
157
|
readonly call: string;
|
|
141
158
|
readonly input: StepValue;
|
|
@@ -145,7 +162,7 @@ export type UseCaseStepDescription = {
|
|
|
145
162
|
readonly fn: {
|
|
146
163
|
readonly kind: "opaque";
|
|
147
164
|
};
|
|
148
|
-
readonly args?:
|
|
165
|
+
readonly args?: StepArgs;
|
|
149
166
|
readonly as?: string;
|
|
150
167
|
} | {
|
|
151
168
|
readonly return: StepValue;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AnyRepository } from "../repository/types/repository.interface.js";
|
|
2
|
+
import type { UseCaseTargetInfo } from "./types/plugin.interface.js";
|
|
3
|
+
/**
|
|
4
|
+
* What a use case plugin reads of `repository`: its entity, its actions and
|
|
5
|
+
* a plan of every key, to derive blueprints and steps from. `source` names
|
|
6
|
+
* who asks, in the `UseCaseUnavailable` it throws. Throws a `TypeError` when
|
|
7
|
+
* `repository` was not made by `repositoryOf`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useCaseTargetOf(repository: AnyRepository, source?: string): UseCaseTargetInfo;
|