@afora/core 0.0.8 → 0.0.11
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 +149 -23
- package/dist/adapters.d.ts +25 -0
- package/dist/adapters.js +32 -0
- package/dist/core/application/application.d.ts +30 -0
- package/dist/core/application/application.js +1291 -0
- package/dist/core/application/exceptions.d.ts +17 -0
- package/dist/core/application/exceptions.js +85 -0
- package/dist/core/application/migrate.d.ts +19 -0
- package/dist/core/application/migrate.js +67 -0
- package/dist/core/application/providers.js +108 -0
- package/dist/core/application/registry.js +23 -0
- package/dist/core/application/schema.d.ts +30 -0
- package/dist/core/application/schema.js +398 -0
- package/dist/core/application/start.js +162 -0
- package/dist/core/application/types/application.interface.d.ts +60 -3
- package/dist/core/auth/jwt.d.ts +30 -0
- package/dist/core/auth/jwt.js +200 -0
- package/dist/core/auth/types/jwt.interface.d.ts +74 -0
- package/dist/core/blueprint/blueprint-plugin.js +7 -0
- package/dist/core/blueprint/blueprint.js +146 -0
- package/dist/core/blueprint/callable.js +13 -0
- package/dist/core/blueprint/exceptions.d.ts +5 -0
- package/dist/core/blueprint/exceptions.js +20 -0
- package/dist/core/blueprint/is-blueprint.js +4 -0
- package/dist/core/blueprint/list.js +19 -0
- package/dist/core/blueprint/optional.d.ts +3 -1
- package/dist/core/blueprint/optional.js +69 -0
- package/dist/core/blueprint/record-field.js +14 -0
- package/dist/core/blueprint/slice.js +127 -0
- package/dist/core/blueprint/types/blueprint.interface.d.ts +95 -5
- package/dist/core/blueprint/variant.d.ts +32 -0
- package/dist/core/blueprint/variant.js +72 -0
- package/dist/core/command/command.js +428 -0
- package/dist/core/command/exceptions.js +30 -0
- package/dist/core/command/types/command.interface.d.ts +10 -1
- package/dist/core/entity/abstract-entity.d.ts +24 -0
- package/dist/core/entity/abstract-entity.js +181 -0
- package/dist/core/entity/entity.d.ts +1 -1
- package/dist/core/entity/entity.js +209 -0
- package/dist/core/entity/event.d.ts +39 -0
- package/dist/core/entity/event.js +80 -0
- package/dist/core/entity/exceptions.d.ts +9 -0
- package/dist/core/entity/exceptions.js +27 -0
- package/dist/core/entity/trust.d.ts +24 -0
- package/dist/core/entity/trust.js +7 -0
- package/dist/core/entity/types/entity.interface.d.ts +30 -0
- package/dist/core/entity/version.js +13 -0
- package/dist/core/environment/environment.d.ts +26 -3
- package/dist/core/environment/environment.js +71 -0
- package/dist/core/event-policy/event-policy.js +76 -0
- package/dist/core/event-policy/exceptions.js +12 -0
- package/dist/core/event-policy/publish.js +98 -0
- package/dist/core/event-policy/publishers.js +18 -0
- package/dist/core/event-policy/types/event-policy.interface.d.ts +8 -2
- package/dist/core/exception/enums/exception-layers.js +15 -0
- package/dist/core/exception/exception.d.ts +15 -0
- package/dist/core/exception/exception.js +128 -0
- package/dist/core/exception/exported.js +49 -0
- package/dist/core/exception/guard.d.ts +20 -1
- package/dist/core/exception/guard.js +16 -0
- package/dist/core/exception/types/exception.interface.d.ts +13 -0
- package/dist/core/json.js +19 -0
- package/dist/core/logger/exceptions.js +7 -0
- package/dist/core/logger/logger.d.ts +14 -1
- package/dist/core/logger/logger.js +178 -0
- package/dist/core/openapi/exceptions.js +7 -0
- package/dist/core/openapi/openapi.js +132 -0
- package/dist/core/openapi/types/openapi.interface.d.ts +7 -1
- package/dist/core/output/access.d.ts +16 -0
- package/dist/core/output/access.js +66 -0
- package/dist/core/output/coerce.js +38 -0
- package/dist/core/output/exceptions.d.ts +16 -0
- package/dist/core/output/exceptions.js +22 -0
- package/dist/core/output/http.d.ts +36 -0
- package/dist/core/output/http.js +351 -0
- package/dist/core/output/openapi-sink.d.ts +33 -4
- package/dist/core/output/openapi-sink.js +571 -0
- package/dist/core/output/output.js +192 -0
- package/dist/core/output/problem.js +30 -0
- package/dist/core/output/select.js +28 -0
- package/dist/core/output/stage.d.ts +7 -2
- package/dist/core/output/stage.js +111 -0
- package/dist/core/output/status.d.ts +1 -1
- package/dist/core/output/status.js +52 -0
- package/dist/core/output/types/http.interface.d.ts +134 -0
- package/dist/core/output/types/output.interface.d.ts +49 -5
- package/dist/core/parse/issue.d.ts +28 -3
- package/dist/core/parse/issue.js +31 -0
- package/dist/core/parse/parse.d.ts +16 -0
- package/dist/core/parse/parse.js +323 -0
- package/dist/core/parse/strip.js +48 -0
- package/dist/core/property/abstract-property.d.ts +9 -0
- package/dist/core/property/abstract-property.js +37 -0
- package/dist/core/property/define-property.d.ts +34 -6
- package/dist/core/property/define-property.js +209 -0
- package/dist/core/property/enums/property-states.js +8 -0
- package/dist/core/property/exceptions.js +12 -0
- package/dist/core/property/input.js +10 -0
- package/dist/core/property/is-property.js +3 -0
- package/dist/core/property/lineage.js +8 -0
- package/dist/core/property/property.d.ts +27 -0
- package/dist/core/property/property.js +46 -0
- package/dist/core/property/refine.js +103 -0
- package/dist/core/property/types/property-options.interface.d.ts +10 -0
- package/dist/core/property/types/property-type.interface.d.ts +4 -0
- package/dist/core/record/abstract-record.d.ts +8 -0
- package/dist/core/record/abstract-record.js +307 -0
- package/dist/core/record/exceptions.js +49 -0
- package/dist/core/record/marks.js +7 -0
- package/dist/core/record/mask.js +60 -0
- package/dist/core/record/name.js +23 -0
- package/dist/core/record/plugin-catalog.js +115 -0
- package/dist/core/record/plugin.js +198 -0
- package/dist/core/record/record.js +117 -0
- package/dist/core/record/types/record.interface.d.ts +17 -1
- package/dist/core/repository/cursor.js +83 -0
- package/dist/core/repository/exceptions.d.ts +13 -0
- package/dist/core/repository/exceptions.js +72 -0
- package/dist/core/repository/in-memory.d.ts +2 -2
- package/dist/core/repository/in-memory.js +196 -0
- package/dist/core/repository/migrate.d.ts +20 -0
- package/dist/core/repository/migrate.js +254 -0
- package/dist/core/repository/plan.js +27 -0
- package/dist/core/repository/provider.js +29 -0
- package/dist/core/repository/query.js +153 -0
- package/dist/core/repository/references.js +196 -0
- package/dist/core/repository/repository.d.ts +22 -2
- package/dist/core/repository/repository.js +720 -0
- package/dist/core/repository/state.d.ts +2 -0
- package/dist/core/repository/state.js +138 -0
- package/dist/core/repository/types/query.interface.d.ts +13 -0
- package/dist/core/repository/types/repository.interface.d.ts +50 -5
- package/dist/core/repository/types/schema.interface.d.ts +132 -0
- package/dist/core/repository/upcast.d.ts +64 -0
- package/dist/core/repository/upcast.js +179 -0
- package/dist/core/schema/schema.d.ts +37 -0
- package/dist/core/schema/schema.js +542 -0
- package/dist/core/standard/standard-schema.js +1 -0
- package/dist/core/standard/standard.js +23 -0
- package/dist/core/symbols/demo.d.ts +4 -0
- package/dist/core/symbols/demo.js +1 -0
- package/dist/core/symbols/kind.d.ts +1 -1
- package/dist/core/symbols/kind.js +1 -0
- package/dist/core/symbols/meta.js +1 -0
- package/dist/core/symbols/state.d.ts +5 -0
- package/dist/core/symbols/state.js +1 -0
- package/dist/core/tracing/exceptions.d.ts +4 -0
- package/dist/core/tracing/exceptions.js +7 -0
- package/dist/core/tracing/tracing.d.ts +72 -0
- package/dist/core/tracing/tracing.js +101 -0
- package/dist/core/use-case/access.d.ts +12 -0
- package/dist/core/use-case/access.js +56 -0
- package/dist/core/use-case/decorators.d.ts +40 -0
- package/dist/core/use-case/decorators.js +65 -0
- package/dist/core/use-case/exceptions.d.ts +5 -1
- package/dist/core/use-case/exceptions.js +48 -0
- package/dist/core/use-case/plugin-catalog.js +68 -0
- package/dist/core/use-case/plugin.js +133 -0
- package/dist/core/use-case/route.d.ts +18 -0
- package/dist/core/use-case/route.js +200 -0
- package/dist/core/use-case/scope.d.ts +5 -0
- package/dist/core/use-case/scope.js +4 -0
- package/dist/core/use-case/steps/describe.d.ts +5 -1
- package/dist/core/use-case/steps/describe.js +24 -0
- package/dist/core/use-case/steps/references.js +105 -0
- package/dist/core/use-case/steps/run.js +139 -0
- package/dist/core/use-case/steps/types/check.interface.d.ts +233 -0
- package/dist/core/use-case/steps/types/steps.interface.d.ts +8 -1
- package/dist/core/use-case/steps/validate.js +368 -0
- package/dist/core/use-case/target.js +100 -0
- package/dist/core/use-case/types/access.interface.d.ts +39 -0
- package/dist/core/use-case/types/route.interface.d.ts +86 -0
- package/dist/core/use-case/use-case.d.ts +127 -2
- package/dist/core/use-case/use-case.js +493 -0
- package/dist/experimental.d.ts +18 -1
- package/dist/experimental.js +18 -123
- package/dist/index.d.ts +49 -51
- package/dist/index.js +213 -443
- package/dist/std/blueprint-plugin.js +42 -0
- package/dist/std/blueprint.d.ts +21 -0
- package/dist/std/blueprint.js +3 -0
- package/dist/std/command.js +3 -0
- package/dist/std/entity.d.ts +20 -1
- package/dist/std/entity.js +30 -0
- package/dist/std/event-policies.js +3 -0
- package/dist/std/properties/contact.d.ts +3 -0
- package/dist/std/properties/contact.js +29 -0
- package/dist/std/properties/enum.js +26 -0
- package/dist/std/properties/exceptions.js +14 -0
- package/dist/std/properties/headless.js +45 -0
- package/dist/std/properties/identity.js +28 -0
- package/dist/std/properties/json.js +161 -0
- package/dist/std/properties/password.d.ts +0 -2
- package/dist/std/properties/password.js +79 -0
- package/dist/std/properties/primitives.d.ts +4 -0
- package/dist/std/properties/primitives.js +28 -0
- package/dist/std/properties/time.js +55 -0
- package/dist/std/record-plugins.d.ts +2 -2
- package/dist/std/record-plugins.js +73 -0
- package/dist/std/record.d.ts +20 -1
- package/dist/std/record.js +3 -0
- package/dist/std/resource.d.ts +31 -0
- package/dist/std/resource.js +39 -0
- package/dist/std/runtime.d.ts +6 -0
- package/dist/std/runtime.js +193 -0
- package/dist/std/stages.js +4 -0
- package/dist/std/use-cases/auth.d.ts +5 -1
- package/dist/std/use-cases/auth.js +193 -0
- package/dist/std/use-cases/crud.js +261 -0
- package/dist/std/use-cases/index.d.ts +1 -1
- package/dist/std/use-cases/index.js +12 -0
- package/dist/std/use-cases/lifecycle.js +97 -0
- package/dist/std/use-cases/shared.d.ts +2 -2
- package/dist/std/use-cases/shared.js +232 -0
- package/dist/std/use-cases/types.d.ts +102 -2
- package/dist/std/use-cases/types.js +0 -0
- package/dist/testing/http-provider-contract.d.ts +32 -0
- package/dist/testing/http-provider-contract.js +344 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +3 -944
- package/dist/testing/memory-publisher.js +12 -0
- package/dist/testing/provider-contract.d.ts +26 -1
- package/dist/testing/provider-contract.js +1071 -0
- package/package.json +23 -3
- package/dist/index-qxfkyd6k.js +0 -1721
- package/dist/index-t5qwtyj5.js +0 -8194
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { fieldOf } from "../blueprint/callable.js";
|
|
2
|
+
import { isBlueprint } from "../blueprint/is-blueprint.js";
|
|
3
|
+
import { isBlueprintList } from "../blueprint/list.js";
|
|
4
|
+
import { optionalOf } from "../blueprint/optional.js";
|
|
5
|
+
import { recordFieldOf } from "../blueprint/record-field.js";
|
|
6
|
+
import { isBlueprintVariant } from "../blueprint/variant.js";
|
|
7
|
+
import { identityKeysOf } from "../entity/abstract-entity.js";
|
|
8
|
+
import { isEntityClass } from "../entity/entity.js";
|
|
9
|
+
import { propertyInputOf } from "../property/input.js";
|
|
10
|
+
import { isProperty } from "../property/is-property.js";
|
|
11
|
+
import { recordNameOf } from "../record/name.js";
|
|
12
|
+
import { pluginsOf } from "../record/plugin.js";
|
|
13
|
+
import { findState, relatedRepositoryOf } from "../repository/state.js";
|
|
14
|
+
import { UseCaseUnavailable } from "./exceptions.js";
|
|
15
|
+
function isOptionalField(field) {
|
|
16
|
+
if (isProperty(field))
|
|
17
|
+
return field.type.optional === true;
|
|
18
|
+
const record = recordFieldOf(field);
|
|
19
|
+
if (record)
|
|
20
|
+
return record.optional;
|
|
21
|
+
return field.optional === true;
|
|
22
|
+
}
|
|
23
|
+
function planOf(entity, key, owners) {
|
|
24
|
+
const field = fieldOf(entity.blueprint.properties[key]);
|
|
25
|
+
const optional = isOptionalField(field);
|
|
26
|
+
const record = recordFieldOf(field);
|
|
27
|
+
const item = isBlueprintList(field) ? recordFieldOf(field.item) : undefined;
|
|
28
|
+
const related = [record?.record, item?.record].find(isEntityClass);
|
|
29
|
+
const kind = record ? related ? "entity" : "record" : isBlueprintList(field) ? related ? "entity-list" : "list" : isBlueprint(field) || isBlueprintVariant(field) ? "blueprint" : "property";
|
|
30
|
+
let input = { key, field };
|
|
31
|
+
if (kind === "entity" && related) {
|
|
32
|
+
const id = related.blueprint.properties.id;
|
|
33
|
+
input = { key: `${key}Id`, field: optional ? optionalOf(id) : id };
|
|
34
|
+
}
|
|
35
|
+
const declared = kind === "property" ? propertyInputOf(field) : undefined;
|
|
36
|
+
if (declared) {
|
|
37
|
+
input = {
|
|
38
|
+
key,
|
|
39
|
+
field: optional ? optionalOf(declared.accepts) : declared.accepts
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
key,
|
|
44
|
+
field,
|
|
45
|
+
identity: identityKeysOf(entity).includes(key),
|
|
46
|
+
plugin: owners.get(key),
|
|
47
|
+
sensitive: entity.options.sensitive.includes(key),
|
|
48
|
+
unique: entity.options.unique.includes(key),
|
|
49
|
+
optional,
|
|
50
|
+
change: entity.options.change.includes(key),
|
|
51
|
+
kind,
|
|
52
|
+
related,
|
|
53
|
+
input: Object.freeze(input),
|
|
54
|
+
convert: declared ? (value) => value === undefined ? undefined : declared.convert(value) : undefined
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
export function useCaseTargetOf(repository, source = "useCaseTargetOf") {
|
|
58
|
+
const state = findState(repository);
|
|
59
|
+
if (!state)
|
|
60
|
+
throw new TypeError("Expected a repository made by repositoryOf");
|
|
61
|
+
const { entity } = state;
|
|
62
|
+
const unavailable = (reason) => {
|
|
63
|
+
throw new UseCaseUnavailable({
|
|
64
|
+
source,
|
|
65
|
+
details: { repository: state.name, reason }
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const owners = new Map;
|
|
69
|
+
for (const plugin of pluginsOf(entity)) {
|
|
70
|
+
for (const key of plugin.keys)
|
|
71
|
+
owners.set(key, plugin.name);
|
|
72
|
+
}
|
|
73
|
+
const fields = Object.freeze(Object.keys(entity.blueprint.properties).map((key) => planOf(entity, key, owners)));
|
|
74
|
+
const field = (key) => fields.find((plan) => plan.key === key) ?? unavailable(`the entity has no key "${key}"`);
|
|
75
|
+
return Object.freeze({
|
|
76
|
+
repository,
|
|
77
|
+
repositoryName: state.name,
|
|
78
|
+
entity,
|
|
79
|
+
entityName: recordNameOf(entity),
|
|
80
|
+
actions: state.actions,
|
|
81
|
+
deletion: state.deletion,
|
|
82
|
+
fields,
|
|
83
|
+
changeKeys: entity.options.change,
|
|
84
|
+
uniqueKeys: entity.options.unique,
|
|
85
|
+
field,
|
|
86
|
+
relation(key, actions = []) {
|
|
87
|
+
const { related } = field(key);
|
|
88
|
+
const found = related ? relatedRepositoryOf(repository, related) : undefined;
|
|
89
|
+
if (!found) {
|
|
90
|
+
return unavailable(`the repository has no relation for the entity of "${key}"`);
|
|
91
|
+
}
|
|
92
|
+
const missing = actions.filter((action) => !found.actions.includes(action));
|
|
93
|
+
if (missing.length > 0) {
|
|
94
|
+
unavailable(`the repository of "${key}" lacks the action ${missing.map((action) => `"${action}"`).join(", ")}`);
|
|
95
|
+
}
|
|
96
|
+
return found;
|
|
97
|
+
},
|
|
98
|
+
unavailable
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { JsonValue } from "../../json.js";
|
|
2
|
+
/**
|
|
3
|
+
* Who is calling: what an authenticator made of the credentials of a request.
|
|
4
|
+
* `subject` identifies the caller (a user id), `scopes` is what it may do.
|
|
5
|
+
*/
|
|
6
|
+
export interface Principal {
|
|
7
|
+
readonly subject: string;
|
|
8
|
+
readonly scopes: readonly string[];
|
|
9
|
+
/** Anything else the credentials say (a tenant, a role). */
|
|
10
|
+
readonly claims?: Readonly<Record<string, JsonValue>>;
|
|
11
|
+
}
|
|
12
|
+
/** A value of the principal: its subject, its scopes or one of its claims. */
|
|
13
|
+
type PrincipalValue = "subject" | "scopes" | `claims.${string}`;
|
|
14
|
+
/**
|
|
15
|
+
* Who may call a use case, as data: declared with the `access` setting of
|
|
16
|
+
* `useCaseOf` or with `@Access`, and enforced by every output before the use
|
|
17
|
+
* case runs. `{ public: true }` lets anyone in. Any other declaration needs
|
|
18
|
+
* an authenticated caller (`{}` asks for nothing more), and may add:
|
|
19
|
+
*
|
|
20
|
+
* - `scopes`: the caller has all of them;
|
|
21
|
+
* - `owner`: the input key `input` equals the subject of the caller, or its
|
|
22
|
+
* claim `claim`;
|
|
23
|
+
* - `context`: keys of the context of the call filled from the caller, over
|
|
24
|
+
* whatever the request sent.
|
|
25
|
+
*/
|
|
26
|
+
export type AccessDeclaration = {
|
|
27
|
+
readonly public: true;
|
|
28
|
+
} | {
|
|
29
|
+
readonly public?: false;
|
|
30
|
+
readonly scopes?: readonly string[];
|
|
31
|
+
readonly owner?: {
|
|
32
|
+
readonly input: string;
|
|
33
|
+
readonly claim?: string;
|
|
34
|
+
};
|
|
35
|
+
readonly context?: {
|
|
36
|
+
readonly [contextKey: string]: PrincipalValue;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** An HTTP method an operation can be routed to. */
|
|
2
|
+
export type RouteMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
3
|
+
/** The status of a successful call of a route; `204` has no body. */
|
|
4
|
+
export type RouteSuccessStatus = 200 | 201 | 202 | 204;
|
|
5
|
+
/** How a cookie a route sets is sent. The defaults are the safe ones for a session. */
|
|
6
|
+
export interface RouteCookie {
|
|
7
|
+
/** The name of the cookie. */
|
|
8
|
+
readonly name: string;
|
|
9
|
+
/**
|
|
10
|
+
* Not readable by scripts of the page.
|
|
11
|
+
*
|
|
12
|
+
* @default true
|
|
13
|
+
*/
|
|
14
|
+
readonly httpOnly?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Sent over HTTPS only.
|
|
17
|
+
*
|
|
18
|
+
* @default true
|
|
19
|
+
*/
|
|
20
|
+
readonly secure?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The `SameSite` attribute of the cookie.
|
|
23
|
+
*
|
|
24
|
+
* @default "lax"
|
|
25
|
+
*/
|
|
26
|
+
readonly sameSite?: "strict" | "lax" | "none";
|
|
27
|
+
/**
|
|
28
|
+
* The `Path` attribute of the cookie.
|
|
29
|
+
*
|
|
30
|
+
* @default "/"
|
|
31
|
+
*/
|
|
32
|
+
readonly path?: string;
|
|
33
|
+
readonly domain?: string;
|
|
34
|
+
/** The seconds the cookie lives; without it, until the browser closes. */
|
|
35
|
+
readonly maxAge?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Where a use case is served over HTTP, as data: declared with the `route`
|
|
39
|
+
* setting of `useCaseOf` or with `@Route`, and read by the sinks that serve
|
|
40
|
+
* or document HTTP. The blueprint of the use case stays the only description
|
|
41
|
+
* of its input: a route only says where each key comes from.
|
|
42
|
+
*/
|
|
43
|
+
export interface RouteDeclaration {
|
|
44
|
+
readonly method: RouteMethod;
|
|
45
|
+
/**
|
|
46
|
+
* The path, after the `basePath` of the sink, e.g. `"/posts/{slug}"`. A
|
|
47
|
+
* `{name}` segment fills the scalar input key `name`. Without it, the path
|
|
48
|
+
* is the default of the sink: `/{kebab(command)}/{kebab(operation)}`.
|
|
49
|
+
*/
|
|
50
|
+
readonly path?: string;
|
|
51
|
+
/**
|
|
52
|
+
* The status of a successful response.
|
|
53
|
+
*
|
|
54
|
+
* @default 200
|
|
55
|
+
*/
|
|
56
|
+
readonly status?: RouteSuccessStatus;
|
|
57
|
+
/**
|
|
58
|
+
* Scalar input keys read from the query string. A `get` or `delete` route
|
|
59
|
+
* reads from the query every key that has no other place, so this is for
|
|
60
|
+
* the routes with a body.
|
|
61
|
+
*/
|
|
62
|
+
readonly query?: readonly string[];
|
|
63
|
+
/** Scalar input keys read from request headers: `{ locale: "accept-language" }`. */
|
|
64
|
+
readonly headers?: {
|
|
65
|
+
readonly [inputKey: string]: string;
|
|
66
|
+
};
|
|
67
|
+
/** Scalar input keys read from cookies: `{ session: "sid" }`. */
|
|
68
|
+
readonly cookie?: {
|
|
69
|
+
readonly [inputKey: string]: string;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Scalar keys of the response sent as cookies instead of in the body:
|
|
73
|
+
* `{ token: { name: "sid" } }`. An empty text removes the cookie, and an
|
|
74
|
+
* absent value sets none.
|
|
75
|
+
*/
|
|
76
|
+
readonly setCookie?: {
|
|
77
|
+
readonly [responseKey: string]: RouteCookie;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* What the `route` setting and `@Route` take: a {@link RouteDeclaration}
|
|
82
|
+
* whose method may be written in upper case.
|
|
83
|
+
*/
|
|
84
|
+
export interface RouteSettings extends Omit<RouteDeclaration, "method"> {
|
|
85
|
+
readonly method: RouteMethod | Uppercase<RouteMethod>;
|
|
86
|
+
}
|
|
@@ -5,7 +5,10 @@ import { type DeclaredExceptionClass } from "../exception/exception.js";
|
|
|
5
5
|
import { type ILogger, type LoggerDeclaration } from "../logger/logger.js";
|
|
6
6
|
import type { AnyRepository } from "../repository/types/repository.interface.js";
|
|
7
7
|
import { Kind } from "../symbols/kind.js";
|
|
8
|
+
import type { CheckedSteps } from "./steps/types/check.interface.js";
|
|
8
9
|
import type { UseCaseStep } from "./steps/types/steps.interface.js";
|
|
10
|
+
import type { AccessDeclaration } from "./types/access.interface.js";
|
|
11
|
+
import type { RouteDeclaration, RouteSettings } from "./types/route.interface.js";
|
|
9
12
|
/** A concrete UseCase declaration, usable as a dependency. */
|
|
10
13
|
export interface AnyUseCaseClass {
|
|
11
14
|
readonly [Kind]: "use-case";
|
|
@@ -18,6 +21,10 @@ export interface AnyUseCaseClass {
|
|
|
18
21
|
readonly tags: readonly string[];
|
|
19
22
|
readonly deprecated: boolean;
|
|
20
23
|
readonly errors: readonly AnyExceptionClass[];
|
|
24
|
+
/** Where it is served over HTTP, if it says so (see `RouteDeclaration`). */
|
|
25
|
+
readonly route: RouteDeclaration | undefined;
|
|
26
|
+
/** Who may call it, if it says so (see `AccessDeclaration`). */
|
|
27
|
+
readonly access: AccessDeclaration | undefined;
|
|
21
28
|
new (input: any, ...options: any[]): {
|
|
22
29
|
execute(): Promise<unknown>;
|
|
23
30
|
pullEvents(): EntityEvent[];
|
|
@@ -30,14 +37,31 @@ export type AnyExceptionClass = DeclaredExceptionClass<any>;
|
|
|
30
37
|
* `repositoryOf`, an environment made by `environmentOf`, or `Logger`.
|
|
31
38
|
*/
|
|
32
39
|
export type UseCaseDependency = AnyUseCaseClass | AnyRepository | AnyEnvironment | LoggerDeclaration;
|
|
40
|
+
/**
|
|
41
|
+
* The `dependencies` of a use case, by name: use case classes, repositories,
|
|
42
|
+
* environments or the {@link Logger}.
|
|
43
|
+
*/
|
|
33
44
|
export type UseCaseCollection = Readonly<Record<string, UseCaseDependency>>;
|
|
34
45
|
type Declaration = {
|
|
35
46
|
readonly blueprint: AnyBlueprint;
|
|
36
47
|
readonly response: AnyBlueprint;
|
|
37
48
|
readonly dependencies: UseCaseCollection;
|
|
38
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* The input of a use case class, as its constructor takes it: the input of its
|
|
52
|
+
* `blueprint`.
|
|
53
|
+
*/
|
|
39
54
|
export type UseCaseInput<Class extends Declaration> = BlueprintInput<Class["blueprint"]["properties"]>;
|
|
55
|
+
/**
|
|
56
|
+
* The value `execute` of a use case class resolves with: the value of its
|
|
57
|
+
* `response` blueprint.
|
|
58
|
+
*/
|
|
40
59
|
export type UseCaseResponse<Class extends Declaration> = BlueprintValue<Class["response"]["properties"]>;
|
|
60
|
+
/**
|
|
61
|
+
* The dependencies of a use case class as `this.dependencies` gives them: a
|
|
62
|
+
* function for a use case, the repository itself, the variables of an
|
|
63
|
+
* environment or an {@link ILogger} (see {@link DependencyOf}).
|
|
64
|
+
*/
|
|
41
65
|
export type UseCaseDependencies<Class extends Declaration> = DependencyFunctions<Class["dependencies"]>;
|
|
42
66
|
/** The context of a use case class, as `this.context` gives it; `undefined` without one. */
|
|
43
67
|
export type UseCaseContext<Class extends {
|
|
@@ -59,6 +83,10 @@ type OptionsArgs<Context extends IBlueprintCollection | undefined> = Context ext
|
|
|
59
83
|
* repository itself, the variables of an environment, or an `ILogger`.
|
|
60
84
|
*/
|
|
61
85
|
export type DependencyOf<Dependency> = Dependency extends AnyUseCaseClass ? (input: UseCaseInput<Dependency>) => Promise<UseCaseResponse<Dependency>> : Dependency extends AnyEnvironment ? EnvironmentValue<Dependency> : Dependency extends LoggerDeclaration ? ILogger : Dependency;
|
|
86
|
+
/**
|
|
87
|
+
* A collection of dependencies as a use case receives them: each one as {@link
|
|
88
|
+
* DependencyOf} gives it.
|
|
89
|
+
*/
|
|
62
90
|
export type DependencyFunctions<Dependencies extends UseCaseCollection> = {
|
|
63
91
|
readonly [Key in keyof Dependencies]: DependencyOf<Dependencies[Key]>;
|
|
64
92
|
};
|
|
@@ -69,6 +97,12 @@ export type DependencyFunctions<Dependencies extends UseCaseCollection> = {
|
|
|
69
97
|
export type DependencyOverrides<Dependencies extends UseCaseCollection> = {
|
|
70
98
|
readonly [Key in keyof Dependencies]?: Dependencies[Key] extends AnyRepository ? Partial<Dependencies[Key]> : DependencyOf<Dependencies[Key]>;
|
|
71
99
|
};
|
|
100
|
+
/**
|
|
101
|
+
* What {@link useCaseOf} takes: the `name`, the `blueprint` of the input, the
|
|
102
|
+
* `response` blueprint, the `dependencies` and the `context`, with the
|
|
103
|
+
* documentation of the operation (`description`, `tags`, `deprecated`,
|
|
104
|
+
* `errors`) and where it is served and who may call it (`route`, `access`).
|
|
105
|
+
*/
|
|
72
106
|
export interface UseCaseSettings<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> {
|
|
73
107
|
readonly name: string;
|
|
74
108
|
readonly blueprint: IBlueprint<Input>;
|
|
@@ -81,6 +115,22 @@ export interface UseCaseSettings<Input extends IBlueprintCollection, Output exte
|
|
|
81
115
|
readonly deprecated?: boolean;
|
|
82
116
|
/** The exceptions (made by `exceptionOf`) a caller should expect; documentation only. */
|
|
83
117
|
readonly errors?: readonly AnyExceptionClass[];
|
|
118
|
+
/**
|
|
119
|
+
* Where the use case is served over HTTP: method, path and where each
|
|
120
|
+
* input key is read from (see `RouteDeclaration`). A decorated class says
|
|
121
|
+
* the same with `@Route`. Read by the sinks that serve or document HTTP;
|
|
122
|
+
* the `route` option of a sink overrides the method, path and status.
|
|
123
|
+
*/
|
|
124
|
+
readonly route?: RouteSettings;
|
|
125
|
+
/**
|
|
126
|
+
* Who may call the use case through an output: anyone (`{ public: true }`),
|
|
127
|
+
* or an authenticated caller with these `scopes`, that is the `owner`
|
|
128
|
+
* named by an input key, and whose values fill the `context` (see
|
|
129
|
+
* `AccessDeclaration`). A decorated class says the same with `@Access`.
|
|
130
|
+
* Enforced before the use case runs; a call made in process, with
|
|
131
|
+
* `execute` or `command.run`, is trusted and not checked.
|
|
132
|
+
*/
|
|
133
|
+
readonly access?: AccessDeclaration;
|
|
84
134
|
}
|
|
85
135
|
/** `UseCaseSettings` with the `steps` that are its `execute`. */
|
|
86
136
|
export interface UseCaseStepsSettings<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> extends UseCaseSettings<Input, Output, Dependencies, Context> {
|
|
@@ -89,11 +139,46 @@ export interface UseCaseStepsSettings<Input extends IBlueprintCollection, Output
|
|
|
89
139
|
* interpreted by its `execute`, and described by `describe()`.
|
|
90
140
|
*/
|
|
91
141
|
readonly steps: readonly UseCaseStep<NoInfer<Dependencies>>[];
|
|
142
|
+
/** Checks the references and values of the steps at compile time (see `CheckedSteps`). */
|
|
143
|
+
readonly strictSteps?: boolean;
|
|
92
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* What `useCaseOf` takes: `UseCaseSettings`, and the `steps` that make the
|
|
147
|
+
* class concrete, if any.
|
|
148
|
+
*/
|
|
149
|
+
export interface UseCaseDeclaration<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined, Steps extends readonly UseCaseStep<Dependencies>[] | undefined = undefined, Strict extends boolean = false> extends UseCaseSettings<Input, Output, Dependencies, Context> {
|
|
150
|
+
/**
|
|
151
|
+
* What the use case does, as data (see `UseCaseStep`), which makes the
|
|
152
|
+
* class concrete: validated here, interpreted by its `execute`, and
|
|
153
|
+
* described by `describe()`. TypeScript checks the kinds of the steps and
|
|
154
|
+
* the names of dependencies and actions; with `strictSteps`, also their
|
|
155
|
+
* references and values, and the `return` against the response (see
|
|
156
|
+
* `CheckedSteps`).
|
|
157
|
+
*/
|
|
158
|
+
readonly steps?: [Strict] extends [true] ? CheckedSteps<Steps, NoInfer<Input>, NoInfer<Context>, NoInfer<Dependencies>, NoInfer<Output>> : Steps;
|
|
159
|
+
/**
|
|
160
|
+
* `true` checks the steps, written inline, at compile time: each reference
|
|
161
|
+
* must name a value in scope (the input, the context, an environment or the
|
|
162
|
+
* result of an earlier step, at any depth), and the `args`, `values`,
|
|
163
|
+
* `value` and `input` of a step must fit what it calls or builds. It costs
|
|
164
|
+
* the editor more than the default checks. Without effect at run time.
|
|
165
|
+
*/
|
|
166
|
+
readonly strictSteps?: Strict;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* A use case, or a decorator of one, is declared with what it cannot use;
|
|
170
|
+
* `reason` says what.
|
|
171
|
+
*/
|
|
93
172
|
export declare const InvalidUseCaseDeclaration: DeclaredExceptionClass<{
|
|
94
173
|
reason: string;
|
|
95
174
|
}>;
|
|
175
|
+
/** `execute` was called twice on one instance of a use case, which runs only once. */
|
|
96
176
|
export declare const UseCaseAlreadyExecuted: DeclaredExceptionClass<undefined>;
|
|
177
|
+
/**
|
|
178
|
+
* The overrides given to `execute` name a dependency the use case does not
|
|
179
|
+
* declare, or give the wrong kind of value for one; or a dependency was called
|
|
180
|
+
* after `execute` ended.
|
|
181
|
+
*/
|
|
97
182
|
export declare const InvalidUseCaseDependencies: DeclaredExceptionClass<undefined>;
|
|
98
183
|
/** An operation with validated input, dependency functions and a drainable event queue. */
|
|
99
184
|
export declare abstract class AbstractUseCase<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection> {
|
|
@@ -101,6 +186,14 @@ export declare abstract class AbstractUseCase<Input extends IBlueprintCollection
|
|
|
101
186
|
constructor(input: BlueprintInput<Input>, ...args: unknown[]);
|
|
102
187
|
/** Default parameter for implementations: `execute(deps = this.dependencies)`. */
|
|
103
188
|
get dependencies(): DependencyFunctions<Dependencies>;
|
|
189
|
+
/**
|
|
190
|
+
* Runs the operation and gives its response. `dependencies` replace the
|
|
191
|
+
* declared ones for this call: a function for a use case, some methods of a
|
|
192
|
+
* repository, the variables of an environment.
|
|
193
|
+
*
|
|
194
|
+
* @throws {@link UseCaseAlreadyExecuted} when this instance already executed.
|
|
195
|
+
* @throws {@link InvalidUseCaseDependencies} when an override names an undeclared dependency or is of the wrong kind.
|
|
196
|
+
*/
|
|
104
197
|
abstract execute(dependencies?: DependencyOverrides<Dependencies>): Promise<BlueprintValue<Output>>;
|
|
105
198
|
/** Immediately drains collected events; never executes their handlers. */
|
|
106
199
|
pullEvents(): EntityEvent[];
|
|
@@ -119,12 +212,21 @@ type UseCaseStatics<Input extends IBlueprintCollection, Output extends IBlueprin
|
|
|
119
212
|
readonly deprecated: boolean;
|
|
120
213
|
/** The exceptions a caller should expect. */
|
|
121
214
|
readonly errors: readonly AnyExceptionClass[];
|
|
215
|
+
/** Where it is served over HTTP, from the `route` setting or `@Route`. */
|
|
216
|
+
readonly route: RouteDeclaration | undefined;
|
|
217
|
+
/** Who may call it, from the `access` setting or `@Access`. */
|
|
218
|
+
readonly access: AccessDeclaration | undefined;
|
|
122
219
|
};
|
|
123
220
|
/** What a use case instance carries beyond `AbstractUseCase`. */
|
|
124
221
|
type UseCaseInstanceContext<Context extends IBlueprintCollection | undefined> = {
|
|
125
222
|
/** What the caller supplied with the call, parsed and frozen; `undefined` without a declared context. */
|
|
126
223
|
readonly context: Context extends IBlueprintCollection ? Readonly<BlueprintValue<Context>> : undefined;
|
|
127
224
|
};
|
|
225
|
+
/**
|
|
226
|
+
* What {@link useCaseOf} gives without `steps`: an abstract class to extend,
|
|
227
|
+
* implementing an async `execute`. Its constructor takes the input and, when
|
|
228
|
+
* the use case declares a context, `{ context }`.
|
|
229
|
+
*/
|
|
128
230
|
export type UseCaseConstructor<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> = (abstract new (input: BlueprintInput<Input>, ...options: OptionsArgs<Context>) => AbstractUseCase<Input, Output, Dependencies> & UseCaseInstanceContext<Context>) & UseCaseStatics<Input, Output, Dependencies, Context>;
|
|
129
231
|
/**
|
|
130
232
|
* What `useCaseOf` gives with `steps`: concrete, its `execute` interprets the
|
|
@@ -137,9 +239,32 @@ export type StepsUseCaseConstructor<Input extends IBlueprintCollection, Output e
|
|
|
137
239
|
* Declares an operation. With `steps` the class is concrete: its `execute`
|
|
138
240
|
* interprets them. Without, it is abstract: extend it and implement an async
|
|
139
241
|
* execute method.
|
|
242
|
+
*
|
|
243
|
+
* @throws {@link InvalidUseCaseDeclaration} when the name, the blueprints or the dependencies are missing or of the wrong kind, or a setting is not valid.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```ts
|
|
247
|
+
* import { blueprintOf, useCaseOf } from "@afora/core";
|
|
248
|
+
*
|
|
249
|
+
* class Greet extends useCaseOf({
|
|
250
|
+
* name: "Greet",
|
|
251
|
+
* blueprint: blueprintOf((ctx) => ({ name: ctx.text })),
|
|
252
|
+
* response: blueprintOf((ctx) => ({ message: ctx.string })),
|
|
253
|
+
* }) {
|
|
254
|
+
* override async execute() {
|
|
255
|
+
* return { message: `Hello, ${this.input.name}!` };
|
|
256
|
+
* }
|
|
257
|
+
* }
|
|
258
|
+
*
|
|
259
|
+
* const { message } = await new Greet({ name: "Ana" }).execute();
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
export declare function useCaseOf<Input extends IBlueprintCollection, Output extends IBlueprintCollection, const Dependencies extends UseCaseCollection = Record<never, never>, Context extends IBlueprintCollection | undefined = undefined, const Steps extends readonly UseCaseStep<NoInfer<Dependencies>>[] | undefined = undefined, Strict extends boolean = false>(settings: UseCaseDeclaration<Input, Output, Dependencies, Context, Steps, Strict>): [Steps] extends [undefined] ? UseCaseConstructor<Input, Output, Dependencies, Context> : StepsUseCaseConstructor<Input, Output, Dependencies, Context>;
|
|
263
|
+
/**
|
|
264
|
+
* Whether `value` is a use case class made by {@link useCaseOf}, abstract or
|
|
265
|
+
* concrete, or a subclass of one.
|
|
140
266
|
*/
|
|
141
|
-
export declare function useCaseOf<Input extends IBlueprintCollection, Output extends IBlueprintCollection, const Dependencies extends UseCaseCollection = Record<never, never>, Context extends IBlueprintCollection | undefined = undefined>(settings: UseCaseStepsSettings<Input, Output, Dependencies, Context>): StepsUseCaseConstructor<Input, Output, Dependencies, Context>;
|
|
142
|
-
export declare function useCaseOf<Input extends IBlueprintCollection, Output extends IBlueprintCollection, const Dependencies extends UseCaseCollection = Record<never, never>, Context extends IBlueprintCollection | undefined = undefined>(settings: UseCaseSettings<Input, Output, Dependencies, Context>): UseCaseConstructor<Input, Output, Dependencies, Context>;
|
|
143
267
|
export declare function isUseCaseClass(value: unknown): value is AnyUseCaseClass;
|
|
268
|
+
/** Whether `value` is an instance of a use case class. */
|
|
144
269
|
export declare function isUseCase(value: unknown): value is AbstractUseCase<IBlueprintCollection, IBlueprintCollection, UseCaseCollection>;
|
|
145
270
|
export {};
|