@afora/core 0.0.2 → 0.0.4

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.
Files changed (82) hide show
  1. package/README.md +220 -733
  2. package/dist/core/application/application.d.ts +9 -1
  3. package/dist/core/application/exceptions.d.ts +28 -0
  4. package/dist/core/application/registry.d.ts +33 -0
  5. package/dist/core/application/start.d.ts +75 -0
  6. package/dist/core/application/types/application.interface.d.ts +229 -7
  7. package/dist/core/blueprint/types/blueprint.interface.d.ts +66 -11
  8. package/dist/core/blueprint/types/slice.interface.d.ts +7 -2
  9. package/dist/core/command/command.d.ts +25 -0
  10. package/dist/core/command/exceptions.d.ts +33 -0
  11. package/dist/core/command/types/command.interface.d.ts +108 -0
  12. package/dist/core/entity/event.d.ts +12 -2
  13. package/dist/core/environment/environment.d.ts +4 -1
  14. package/dist/core/exception/exception.d.ts +4 -0
  15. package/dist/core/exception/exported.d.ts +4 -0
  16. package/dist/core/json.d.ts +10 -0
  17. package/dist/core/logger/exceptions.d.ts +4 -0
  18. package/dist/core/logger/logger.d.ts +106 -0
  19. package/dist/core/output/exceptions.d.ts +8 -0
  20. package/dist/core/output/openapi-sink.d.ts +83 -0
  21. package/dist/core/output/output.d.ts +19 -0
  22. package/dist/core/output/problem.d.ts +15 -0
  23. package/dist/core/output/select.d.ts +14 -0
  24. package/dist/core/output/stage.d.ts +24 -0
  25. package/dist/core/output/status.d.ts +17 -0
  26. package/dist/core/output/types/output.interface.d.ts +171 -0
  27. package/dist/core/property/abstract-property.d.ts +8 -7
  28. package/dist/core/property/define-property.d.ts +8 -7
  29. package/dist/core/property/property.d.ts +26 -15
  30. package/dist/core/property/types/abstract-property-constructor.type.d.ts +8 -7
  31. package/dist/core/property/types/define-property.interface.d.ts +5 -4
  32. package/dist/core/property/types/optional-property.type.d.ts +5 -5
  33. package/dist/core/property/types/property-collection.type.d.ts +2 -2
  34. package/dist/core/property/types/property-constructor.type.d.ts +5 -1
  35. package/dist/core/property/types/property-options.interface.d.ts +17 -8
  36. package/dist/core/property/types/property-type.interface.d.ts +7 -3
  37. package/dist/core/property/types/property.interface.d.ts +5 -4
  38. package/dist/core/record/abstract-record.d.ts +12 -4
  39. package/dist/core/record/types/plugin.interface.d.ts +13 -3
  40. package/dist/core/record/types/record.interface.d.ts +20 -8
  41. package/dist/core/repository/exceptions.d.ts +4 -0
  42. package/dist/core/repository/in-memory.d.ts +3 -0
  43. package/dist/core/repository/provider.d.ts +6 -0
  44. package/dist/core/repository/query.d.ts +9 -0
  45. package/dist/core/repository/references.d.ts +4 -2
  46. package/dist/core/repository/repository.d.ts +4 -2
  47. package/dist/core/repository/state.d.ts +4 -0
  48. package/dist/core/repository/types/query.interface.d.ts +67 -0
  49. package/dist/core/repository/types/repository.interface.d.ts +13 -13
  50. package/dist/core/schema/types/schema.interface.d.ts +11 -4
  51. package/dist/core/symbols/meta.d.ts +2 -0
  52. package/dist/core/use-case/exceptions.d.ts +10 -0
  53. package/dist/core/use-case/scope.d.ts +24 -0
  54. package/dist/core/use-case/steps/describe.d.ts +13 -0
  55. package/dist/core/use-case/steps/references.d.ts +27 -0
  56. package/dist/core/use-case/steps/run.d.ts +20 -0
  57. package/dist/core/use-case/steps/types/steps.interface.d.ts +153 -0
  58. package/dist/core/use-case/steps/validate.d.ts +17 -0
  59. package/dist/core/use-case/use-case.d.ts +79 -13
  60. package/dist/experimental.d.ts +6 -0
  61. package/dist/experimental.js +123 -0
  62. package/dist/index-ewm6r4nw.js +5335 -0
  63. package/dist/index-hn82hm31.js +1651 -0
  64. package/dist/index.d.ts +34 -12
  65. package/dist/index.js +343 -4142
  66. package/dist/std/blueprint-plugin.d.ts +10 -10
  67. package/dist/std/blueprint.d.ts +10 -10
  68. package/dist/std/entity.d.ts +2 -2
  69. package/dist/std/properties/contact.d.ts +4 -4
  70. package/dist/std/properties/enum.d.ts +1 -1
  71. package/dist/std/properties/headless.d.ts +1 -1
  72. package/dist/std/properties/identity.d.ts +2 -2
  73. package/dist/std/properties/password.d.ts +1 -1
  74. package/dist/std/properties/primitives.d.ts +5 -5
  75. package/dist/std/properties/time.d.ts +2 -2
  76. package/dist/std/record-plugins.d.ts +12 -12
  77. package/dist/std/record.d.ts +1 -1
  78. package/dist/std/stages.d.ts +2 -0
  79. package/dist/testing/index.d.ts +2 -0
  80. package/dist/testing/index.js +502 -0
  81. package/dist/testing/provider-contract.d.ts +22 -0
  82. package/package.json +28 -8
@@ -0,0 +1,9 @@
1
+ import type { RepositoryQuery } from "./types/query.interface.js";
2
+ import type { RepositoryContext, RepositoryData } from "./types/repository.interface.js";
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;
5
+ /** Structural JSON/data equality without depending on object key insertion order. */
6
+ export declare function sameValue(a: unknown, b: unknown): boolean;
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: RepositoryQuery): RepositoryData[];
@@ -1,3 +1,4 @@
1
+ import { type ExecutionScope } from "../use-case/scope.js";
1
2
  import { type RepositoryState } from "./state.js";
2
3
  import type { AnyRepositoryEntity, RepositoryData } from "./types/repository.interface.js";
3
4
  /** The relations of a repository of `entity` named `name`, one for each entity class it holds. */
@@ -11,6 +12,7 @@ export type ReferenceSession = Map<RepositoryState, Map<string, object>>;
11
12
  * entity they hold replaced by the entity, loaded in one batch for each
12
13
  * relation. An entity loaded twice in `session` is the same instance. Throws
13
14
  * `InvalidRepositoryData`, caused by `RepositoryEntityNotFound`, for an id
14
- * with no entity.
15
+ * with no entity. The providers of the `scope` of the call, if any, replace
16
+ * those of the relations.
15
17
  */
16
- export declare function resolveReferences(state: RepositoryState, datas: readonly RepositoryData[], session: ReferenceSession): Promise<readonly RepositoryData[]>;
18
+ export declare function resolveReferences(state: RepositoryState, datas: readonly RepositoryData[], session: ReferenceSession, scope?: ExecutionScope): Promise<readonly RepositoryData[]>;
@@ -7,6 +7,8 @@ export interface RepositorySettings<Entity extends AnyRepositoryEntity, Actions
7
7
  readonly entity: Entity;
8
8
  /** Its methods, e.g. `["findById", "findByEmail", "findManyByName", "create"]`. */
9
9
  readonly actions: Actions;
10
+ /** Physically remove rows (default), or retain them with `deletedAt`. */
11
+ readonly deletion?: "hard" | "soft";
10
12
  /** How it reads and writes; a new `inMemoryProvider()` by default. */
11
13
  readonly provider?: IRepositoryProvider<MergedEnvironment<Environment>>;
12
14
  /** The environments the provider gets, merged, as `context.env`. */
@@ -49,8 +51,8 @@ export declare abstract class AbstractRepository<Entity extends AnyRepositoryEnt
49
51
  * A repository of `entity` with the methods of `actions`: `findBy<Key>` for a
50
52
  * unique key (`id`, `options.unique`, a `unique` property), giving the entity
51
53
  * or `undefined`; `findManyBy<Key>` for any other key, giving a list;
52
- * `create`, `update`, `delete`
53
- * and `count`. Every method is async and reads and writes through `provider`
54
+ * `create`, `update`, `delete`, `count`, and the queries `findMany`,
55
+ * `exists` and `findPage`. Every method is async and reads and writes through `provider`
54
56
  * (a new `inMemoryProvider()` by default), which gets the variables of
55
57
  * `environmentDependencies` as `context.env`. The repository can be a
56
58
  * dependency of a use case, and `RepositoryOf` extends it with a class.
@@ -5,6 +5,7 @@ export interface RepositoryState {
5
5
  readonly name: string;
6
6
  readonly entity: AnyRepositoryEntity;
7
7
  readonly actions: readonly string[];
8
+ readonly deletion: "hard" | "soft";
8
9
  readonly provider: IRepositoryProvider;
9
10
  /** Whether `settings.provider` was given, and not the default one. */
10
11
  readonly explicitProvider: boolean;
@@ -24,7 +25,10 @@ export declare function contextOf(state: RepositoryState): RepositoryContext;
24
25
  export interface RepositoryInspection {
25
26
  readonly entity: AnyRepositoryEntity;
26
27
  readonly actions: readonly string[];
28
+ readonly deletion: "hard" | "soft";
27
29
  readonly explicitProvider: boolean;
30
+ /** The provider, only when the repository declared it. */
31
+ readonly provider: IRepositoryProvider | undefined;
28
32
  readonly environments: readonly object[];
29
33
  /** The repository state of each related entity class; compare with `inspect(...).state`. */
30
34
  readonly relations: readonly object[];
@@ -0,0 +1,67 @@
1
+ import type { EntityInstanceOf, EntityKeyOf, EntityValueOf, IRepositoryProvider, QueryValueOf } from "./repository.interface.js";
2
+ type QueryKey<Entity> = unknown extends Entity ? string : EntityKeyOf<Entity>;
3
+ type QueryValue<Entity, Key extends string> = unknown extends Entity ? unknown : Key extends keyof EntityValueOf<Entity> ? QueryValueOf<EntityValueOf<Entity>[Key]> : unknown;
4
+ /** Equality, or membership in a list. Conditions on different keys are ANDed. */
5
+ export type RepositoryWhere<Entity = unknown> = {
6
+ readonly [Key in QueryKey<Entity>]?: QueryValue<Entity, Key> | {
7
+ readonly in: readonly QueryValue<Entity, Key>[];
8
+ };
9
+ };
10
+ /** Ordered lexicographically; ties retain provider order. Missing values sort first. */
11
+ export interface RepositoryOrder<Key extends string = string> {
12
+ readonly key: Key;
13
+ readonly direction?: "asc" | "desc";
14
+ }
15
+ /** The v2 query shared by providers and typed repository reads. */
16
+ export interface RepositoryQuery<Entity = unknown> {
17
+ readonly where?: RepositoryWhere<Entity>;
18
+ readonly orderBy?: readonly RepositoryOrder<QueryKey<Entity>>[];
19
+ /** Nonnegative integer; zero returns an empty result. */
20
+ readonly limit?: number;
21
+ /** Nonnegative integer, applied before limit. */
22
+ readonly offset?: number;
23
+ /** Include destroyed rows; repository defaults depend on its deletion policy. */
24
+ readonly withDeleted?: boolean;
25
+ }
26
+ /** A page and the unpaginated number of matching live (or explicitly included) rows. */
27
+ export interface RepositoryPage<Entity> {
28
+ readonly items: readonly EntityInstanceOf<Entity>[];
29
+ readonly total: number;
30
+ readonly limit: number;
31
+ readonly offset: number;
32
+ }
33
+ export interface RepositoryFindMany<Entity> {
34
+ findMany(query?: RepositoryQuery<Entity>): Promise<EntityInstanceOf<Entity>[]>;
35
+ }
36
+ export interface RepositoryExists<Entity> {
37
+ exists(query?: RepositoryQuery<Entity>): Promise<boolean>;
38
+ }
39
+ export interface RepositoryFindPage<Entity> {
40
+ /** Defaults to limit 20 and offset 0. */
41
+ findPage(query?: RepositoryQuery<Entity>): Promise<RepositoryPage<Entity>>;
42
+ }
43
+ /** JSON values retained in provider descriptors, including explicit import references. */
44
+ export type ProviderOption = null | boolean | number | string | readonly ProviderOption[] | {
45
+ readonly [key: string]: ProviderOption;
46
+ };
47
+ export type ProviderOptions = Readonly<Record<string, ProviderOption>>;
48
+ /** A named provider can be reconstructed; opaque providers require an import mapping. */
49
+ export type ProviderDescription = {
50
+ readonly kind: "provider";
51
+ readonly name: string;
52
+ readonly options: ProviderOptions;
53
+ } | {
54
+ readonly kind: "opaque";
55
+ };
56
+ /** Equality and `in` are mandatory; additional operators are advertised here. */
57
+ export interface ProviderCapabilities {
58
+ readonly operators?: readonly string[];
59
+ readonly transactions?: boolean;
60
+ }
61
+ /** Declaration input for a named provider, implemented by providerOf in T10. */
62
+ export interface ProviderSettings<Options extends ProviderOptions = ProviderOptions, Provider extends IRepositoryProvider = IRepositoryProvider> {
63
+ readonly name: string;
64
+ readonly options?: Options;
65
+ readonly create: (options: Options) => Provider;
66
+ }
67
+ export {};
@@ -3,6 +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
7
  /** Any class made by `entityOf`, or extending one, that a repository holds. */
7
8
  export type AnyRepositoryEntity = AnyRecordClass & AnyEntityClass & {
8
9
  readonly [EntityUniqueKeys]: readonly string[];
@@ -42,8 +43,8 @@ export interface RepositoryDelete<Entity> {
42
43
  delete(entity: EntityInstanceOf<Entity> | string): Promise<EntityInstanceOf<Entity>>;
43
44
  }
44
45
  /** `count`: how many entities it holds. */
45
- export interface RepositoryCount {
46
- count(): Promise<number>;
46
+ export interface RepositoryCount<Entity = unknown> {
47
+ count(query?: RepositoryQuery<Entity>): Promise<number>;
47
48
  }
48
49
  /** The `findBy<Key>` of `Actions`, one for each unique key: the entity, or `undefined`. */
49
50
  export type RepositoryFindBy<Entity, Actions extends string = string> = {
@@ -64,10 +65,11 @@ export type RepositoryFindManyBy<Entity, Actions extends string = string> = {
64
65
  /**
65
66
  * The methods of `Actions` for `Entity`. Declared as methods, not properties,
66
67
  * so that a class extending a repository can override them.
68
+ * @internal
67
69
  */
68
- 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 : unknown);
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);
69
71
  /** A method a repository of `Entity` can have, e.g. `"findByEmail"`. */
70
- export type RepositoryAction<Entity> = keyof RepositoryFindBy<Entity> | keyof RepositoryFindManyBy<Entity> | "create" | "update" | "delete" | "count";
72
+ export type RepositoryAction<Entity> = keyof RepositoryFindBy<Entity> | keyof RepositoryFindManyBy<Entity> | "create" | "update" | "delete" | "count" | "findMany" | "exists" | "findPage";
71
73
  /**
72
74
  * A repository of `Entity` with the methods of `Actions`, whose provider gets
73
75
  * `Environment` as `context.env`, e.g.
@@ -96,17 +98,15 @@ export interface RepositoryContext<Environment = unknown> {
96
98
  * `RepositoryEntityNotFound` for an unknown id.
97
99
  */
98
100
  export interface IRepositoryProvider<Environment = unknown> {
99
- findOne(context: RepositoryContext<Environment>, key: string, value: unknown): Promise<RepositoryData | undefined>;
100
- findMany(context: RepositoryContext<Environment>, key: string, value: unknown): Promise<readonly RepositoryData[]>;
101
+ findOne(context: RepositoryContext<Environment>, query?: RepositoryQuery): Promise<RepositoryData | undefined>;
102
+ findMany(context: RepositoryContext<Environment>, query?: RepositoryQuery): Promise<readonly RepositoryData[]>;
101
103
  insert(context: RepositoryContext<Environment>, data: RepositoryData): Promise<void>;
102
104
  update(context: RepositoryContext<Environment>, data: RepositoryData): Promise<void>;
103
105
  remove(context: RepositoryContext<Environment>, id: string): Promise<void>;
104
- count(context: RepositoryContext<Environment>): Promise<number>;
105
- /**
106
- * Optional: every value whose `key` is one of `values`, in a single call.
107
- * A repository uses it to load the entities another one holds by id, and
108
- * calls `findOne` for each id when it is missing.
109
- */
110
- findIn?(context: RepositoryContext<Environment>, key: string, values: readonly unknown[]): Promise<readonly RepositoryData[]>;
106
+ count(context: RepositoryContext<Environment>, query?: RepositoryQuery): Promise<number>;
107
+ readonly capabilities?: ProviderCapabilities;
108
+ transaction?<Result>(run: (provider: IRepositoryProvider<Environment>) => Promise<Result>): Promise<Result>;
109
+ /** Releases what the provider holds (connections, files); `app.stop()` calls it. */
110
+ close?(): void | Promise<void>;
111
111
  }
112
112
  export {};
@@ -1,8 +1,15 @@
1
1
  import type { Static, TSchema } from "typebox";
2
2
  import type { Validator } from "typebox/compile";
3
- export type ISchemaType<SchemaType extends TSchema> = Validator<Record<string, TSchema>, SchemaType>;
4
- export interface ISchema<SchemaType extends TSchema> {
5
- match(value: unknown): value is Static<SchemaType>;
3
+ /**
4
+ * The compiled validator of a schema. `Value` is the schema's value, computed
5
+ * once where `SchemaType` is known: TypeScript then relates two of them by
6
+ * their values, without measuring `Static` itself, which costs about a second
7
+ * on every check.
8
+ */
9
+ export type ISchemaType<SchemaType extends TSchema, Value = Static<SchemaType>> = Validator<Record<string, TSchema>, SchemaType, Value, Value>;
10
+ /** A schema; `Value` is its value, as in `ISchemaType`. */
11
+ export interface ISchema<SchemaType extends TSchema, Value = Static<SchemaType>> {
12
+ match(value: unknown): value is Value;
6
13
  /**
7
14
  * Whether `value` has the structure of the schema (its types), ignoring
8
15
  * constraints such as `minLength`, `format`, `pattern` or `const`.
@@ -10,5 +17,5 @@ export interface ISchema<SchemaType extends TSchema> {
10
17
  matchShape(value: unknown): boolean;
11
18
  errors(value: unknown): string[];
12
19
  toString(): string;
13
- get type(): ISchemaType<SchemaType>;
20
+ get type(): ISchemaType<SchemaType, Value>;
14
21
  }
@@ -0,0 +1,2 @@
1
+ /** Declaration metadata, separate from the string keys of a record's domain. */
2
+ export declare const Meta: unique symbol;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A step of a use case is invalid: at declaration (`useCaseOf({ steps })`),
3
+ * or while the steps run (a `change` or `invoke` on something that is not a
4
+ * record or has no such member). `details.index` is the position of the step.
5
+ */
6
+ export declare const InvalidUseCaseStep: import("../../index.js").DeclaredExceptionClass<{
7
+ readonly index: number;
8
+ readonly reason: string;
9
+ readonly code?: string;
10
+ }>;
@@ -0,0 +1,24 @@
1
+ import type { ILogger } from "../logger/logger.js";
2
+ import type { IRepositoryProvider } from "../repository/types/repository.interface.js";
3
+ /**
4
+ * What a call carries to its repositories, internal: the providers that
5
+ * replace the ones the repositories declared (e.g. one inside a transaction).
6
+ */
7
+ export interface ExecutionScope {
8
+ /** Provider used by the call for each provider the repositories declared. */
9
+ readonly providers: ReadonlyMap<IRepositoryProvider, IRepositoryProvider>;
10
+ /**
11
+ * The variables a command gives the call in place of those an environment
12
+ * loads, by environment: a use case that declares it gets these.
13
+ */
14
+ readonly environments?: ReadonlyMap<object, Readonly<Record<string, unknown>>>;
15
+ /** The logger of the application running the call, given by its command. */
16
+ readonly logger?: ILogger;
17
+ }
18
+ /**
19
+ * Key of the constructor options (and of the repository view) that carries
20
+ * the `ExecutionScope`. Not exported by the package: only the core passes it.
21
+ */
22
+ export declare const ScopeKey: unique symbol;
23
+ /** The provider `scope` gives the call in place of `provider`. */
24
+ export declare function scopedProvider<Provider extends IRepositoryProvider>(scope: ExecutionScope | undefined, provider: Provider): Provider;
@@ -0,0 +1,13 @@
1
+ import type { StepErrorDescription, UseCaseStepDescription } from "./types/steps.interface.js";
2
+ /** How a class of a step is named in the descriptor. */
3
+ export interface StepNames {
4
+ /** The registered name of a record or entity class. */
5
+ readonly record: (Class: unknown) => string;
6
+ /** The registered name of an exception, or `{ core }` for one the package exports. */
7
+ readonly error: (Class: unknown) => StepErrorDescription;
8
+ }
9
+ /**
10
+ * The steps as `describe()` gives them: the same data, with the classes of
11
+ * `build` and `ensure.error` by name and the function of `run` as `opaque`.
12
+ */
13
+ export declare function describeSteps(steps: readonly Readonly<Record<string, unknown>>[], names: StepNames): UseCaseStepDescription[];
@@ -0,0 +1,27 @@
1
+ /** The names a step cannot bind with `as`: they are roots or escapes of values. */
2
+ export declare const RESERVED_NAMES: readonly string[];
3
+ /** Whether `value` is usable as the name of a step result or of a `run`. */
4
+ export declare function isStepName(value: unknown): value is string;
5
+ /** A reference split in its root (`input`, `context`, `env` or a bound name) and path. */
6
+ export interface ParsedReference {
7
+ readonly root: string;
8
+ readonly path: readonly string[];
9
+ }
10
+ /** Splits `$root.a.0.b`; `undefined` when `text` is not a well-formed reference. */
11
+ export declare function parseReference(text: string): ParsedReference | undefined;
12
+ /** A plain object or array, the shapes a value is made of. */
13
+ export declare function isPlain(value: unknown): value is Record<string, unknown>;
14
+ /** What a walk over a value finds wrong in it. */
15
+ export type ValueIssue = "invalid_value" | "invalid_reference";
16
+ /**
17
+ * Visits every reference in `value` (a reference, a literal or a mapping),
18
+ * and reports the first thing that cannot be a value: a function, a class
19
+ * instance, `undefined`, or a malformed reference.
20
+ */
21
+ export declare function walkValue(value: unknown, onReference: (reference: ParsedReference, text: string) => void, onIssue: (issue: ValueIssue, text: string) => never): void;
22
+ /** Follows `path` from `from`; a missing segment is `undefined`, never an error. */
23
+ export declare function followPath(from: unknown, path: readonly string[]): unknown;
24
+ /** The value `value` stands for, with `lookup` resolving the references. */
25
+ export declare function resolveValue(value: unknown, lookup: (reference: ParsedReference) => unknown): unknown;
26
+ /** A copy of the data in `value`, frozen; classes and functions are kept as they are. */
27
+ export declare function freezeData<Value>(value: Value): Value;
@@ -0,0 +1,20 @@
1
+ type Step = Readonly<Record<string, unknown>>;
2
+ /** What the interpreter reads of the use case it runs for. */
3
+ interface Host {
4
+ readonly input: unknown;
5
+ readonly context: unknown;
6
+ readonly constructor: {
7
+ readonly useCaseName: string;
8
+ readonly steps?: readonly Step[];
9
+ };
10
+ }
11
+ /**
12
+ * The `execute` of a use case declared with `steps`: runs them in order, each
13
+ * awaited, over the dependencies `execute` resolved, so that scope, events and
14
+ * trace frames work as in a hand-written `execute`. The wrapper of the
15
+ * constructor validates the response `return` gives.
16
+ */
17
+ export declare function executeSteps(this: Host, dependencies?: Readonly<Record<string, unknown>>): Promise<unknown>;
18
+ /** The steps a use case class interprets; `undefined` when its `execute` is hand-written. */
19
+ export declare function interpretedStepsOf(UseCase: unknown): readonly Readonly<Record<string, unknown>>[] | undefined;
20
+ export {};
@@ -0,0 +1,153 @@
1
+ import type { AnyRecordClass } from "../../../blueprint/types/blueprint.interface.js";
2
+ import type { AnyRepository } from "../../../repository/types/repository.interface.js";
3
+ import type { AnyExceptionClass, AnyUseCaseClass, UseCaseCollection } from "../../use-case.js";
4
+ /** A reference to a value: `$input.email`, `$context.userId`, `$env.mail`, `$saved.tags.0`. */
5
+ export type StepReference = `$${string}`;
6
+ /**
7
+ * A value of a step: a reference, a JSON literal (`{ literal: x }` for a
8
+ * string that starts with `$`) or a mapping of values. Checked at execution.
9
+ */
10
+ export type StepValue = StepReference | string | number | boolean | null | readonly StepValue[] | {
11
+ readonly [key: string]: StepValue;
12
+ };
13
+ /** The actions of a repository that only read. */
14
+ export type StepReadAction = `findBy${string}` | `findManyBy${string}` | "findMany" | "exists" | "findPage" | "count";
15
+ type RepositoryKeys<Dependencies> = {
16
+ [Key in keyof Dependencies & string]: Dependencies[Key] extends AnyRepository ? Key : never;
17
+ }[keyof Dependencies & string];
18
+ type UseCaseKeys<Dependencies> = {
19
+ [Key in keyof Dependencies & string]: Dependencies[Key] extends AnyUseCaseClass ? Key : never;
20
+ }[keyof Dependencies & string];
21
+ type ActionsOf<Repository> = Repository extends {
22
+ readonly actions: readonly (infer Action)[];
23
+ } ? Action : never;
24
+ /** `"users.findByEmail"`: a repository dependency and one of its read actions. */
25
+ export type StepFindTarget<Dependencies> = {
26
+ [Key in RepositoryKeys<Dependencies>]: `${Key}.${Extract<ActionsOf<Dependencies[Key]>, StepReadAction>}`;
27
+ }[RepositoryKeys<Dependencies>];
28
+ /** The repository dependencies that have the action `Action`. */
29
+ export type StepRepositoryWith<Dependencies, Action extends string> = {
30
+ [Key in RepositoryKeys<Dependencies>]: Action extends ActionsOf<Dependencies[Key]> ? Key : never;
31
+ }[RepositoryKeys<Dependencies>];
32
+ /** What an `ensure` checks. */
33
+ export type StepEnsureKind = "exists" | "absent" | "equals" | "notEquals" | "true" | "false";
34
+ /**
35
+ * One step of `useCaseOf({ steps })`: plain data, the same in TypeScript and
36
+ * in JSON but for the classes of `build` and `ensure.error` and the function
37
+ * of `run`. TypeScript checks the names of dependencies and actions; the
38
+ * values are checked by the blueprints at execution.
39
+ */
40
+ export type UseCaseStep<Dependencies extends UseCaseCollection = UseCaseCollection> = {
41
+ /** `"<repository>.<read action>"`. */
42
+ readonly find: StepFindTarget<Dependencies>;
43
+ readonly args?: readonly StepValue[];
44
+ readonly as?: string;
45
+ } | {
46
+ readonly ensure: "exists" | "absent" | "true" | "false";
47
+ readonly value: StepValue;
48
+ readonly other?: undefined;
49
+ readonly error: AnyExceptionClass;
50
+ readonly details?: StepValue;
51
+ } | {
52
+ readonly ensure: "equals" | "notEquals";
53
+ readonly value: StepValue;
54
+ readonly other: StepValue;
55
+ readonly error: AnyExceptionClass;
56
+ readonly details?: StepValue;
57
+ } | {
58
+ /** A record or entity class. */
59
+ readonly build: AnyRecordClass;
60
+ readonly values: StepValue;
61
+ readonly as: string;
62
+ } | {
63
+ /** A reference to a record or entity. */
64
+ readonly change: StepReference;
65
+ readonly key: string;
66
+ readonly value: StepValue;
67
+ readonly as?: string;
68
+ } | {
69
+ /** A reference to a record or entity. */
70
+ readonly invoke: StepReference;
71
+ readonly method: string;
72
+ readonly args?: readonly StepValue[];
73
+ readonly as?: string;
74
+ } | {
75
+ readonly create: StepRepositoryWith<Dependencies, "create">;
76
+ readonly value: StepValue;
77
+ readonly as?: string;
78
+ } | {
79
+ readonly update: StepRepositoryWith<Dependencies, "update">;
80
+ readonly value: StepValue;
81
+ readonly as?: string;
82
+ } | {
83
+ readonly delete: StepRepositoryWith<Dependencies, "delete">;
84
+ readonly value: StepValue;
85
+ readonly as?: string;
86
+ } | {
87
+ readonly call: UseCaseKeys<Dependencies>;
88
+ readonly input: StepValue;
89
+ readonly as?: string;
90
+ } | {
91
+ readonly run: string;
92
+ readonly fn: (...args: any[]) => unknown;
93
+ readonly args?: readonly StepValue[];
94
+ readonly as?: string;
95
+ } | {
96
+ readonly return: StepValue;
97
+ };
98
+ /** A core exception or one registered in the application, by name. */
99
+ export type StepErrorDescription = string | {
100
+ readonly core: string;
101
+ };
102
+ /** A `UseCaseStep` as `describe()` gives it: classes by name, functions as `opaque`. */
103
+ export type UseCaseStepDescription = {
104
+ readonly find: string;
105
+ readonly args?: readonly StepValue[];
106
+ readonly as?: string;
107
+ } | {
108
+ readonly ensure: StepEnsureKind;
109
+ readonly value: StepValue;
110
+ readonly other?: StepValue;
111
+ readonly error: StepErrorDescription;
112
+ readonly details?: StepValue;
113
+ } | {
114
+ readonly build: string;
115
+ readonly values: StepValue;
116
+ readonly as: string;
117
+ } | {
118
+ readonly change: string;
119
+ readonly key: string;
120
+ readonly value: StepValue;
121
+ readonly as?: string;
122
+ } | {
123
+ readonly invoke: string;
124
+ readonly method: string;
125
+ readonly args?: readonly StepValue[];
126
+ readonly as?: string;
127
+ } | {
128
+ readonly create: string;
129
+ readonly value: StepValue;
130
+ readonly as?: string;
131
+ } | {
132
+ readonly update: string;
133
+ readonly value: StepValue;
134
+ readonly as?: string;
135
+ } | {
136
+ readonly delete: string;
137
+ readonly value: StepValue;
138
+ readonly as?: string;
139
+ } | {
140
+ readonly call: string;
141
+ readonly input: StepValue;
142
+ readonly as?: string;
143
+ } | {
144
+ readonly run: string;
145
+ readonly fn: {
146
+ readonly kind: "opaque";
147
+ };
148
+ readonly args?: readonly StepValue[];
149
+ readonly as?: string;
150
+ } | {
151
+ readonly return: StepValue;
152
+ };
153
+ export {};
@@ -0,0 +1,17 @@
1
+ import type { AnyBlueprint } from "../../blueprint/types/blueprint.interface.js";
2
+ /** What `validateSteps` needs to know of the use case being declared. */
3
+ export interface StepsDeclaration {
4
+ readonly name: string;
5
+ readonly blueprint: AnyBlueprint;
6
+ readonly context: AnyBlueprint | undefined;
7
+ readonly dependencies: Readonly<Record<string, unknown>>;
8
+ readonly errors: readonly unknown[];
9
+ }
10
+ /**
11
+ * Checks `steps` against the declaration of a use case and throws
12
+ * `InvalidUseCaseStep` (`details: { index, reason, code }`) on the first
13
+ * thing wrong: an unknown kind, a missing or extra field, a bad name, a
14
+ * reference to what is not declared or bound yet, an action the repository
15
+ * lacks, an error not in `errors`, or a missing, repeated or misplaced `return`.
16
+ */
17
+ export declare function validateSteps(declaration: StepsDeclaration, steps: unknown): void;