@afora/core 0.0.2 → 0.0.3

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 (79) 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 +51 -0
  6. package/dist/core/application/types/application.interface.d.ts +186 -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 +102 -0
  12. package/dist/core/environment/environment.d.ts +4 -1
  13. package/dist/core/exception/exception.d.ts +4 -0
  14. package/dist/core/exception/exported.d.ts +4 -0
  15. package/dist/core/json.d.ts +10 -0
  16. package/dist/core/output/exceptions.d.ts +8 -0
  17. package/dist/core/output/openapi-sink.d.ts +83 -0
  18. package/dist/core/output/output.d.ts +19 -0
  19. package/dist/core/output/problem.d.ts +15 -0
  20. package/dist/core/output/select.d.ts +14 -0
  21. package/dist/core/output/stage.d.ts +24 -0
  22. package/dist/core/output/status.d.ts +17 -0
  23. package/dist/core/output/types/output.interface.d.ts +171 -0
  24. package/dist/core/property/abstract-property.d.ts +8 -7
  25. package/dist/core/property/define-property.d.ts +8 -7
  26. package/dist/core/property/property.d.ts +26 -15
  27. package/dist/core/property/types/abstract-property-constructor.type.d.ts +8 -7
  28. package/dist/core/property/types/define-property.interface.d.ts +5 -4
  29. package/dist/core/property/types/optional-property.type.d.ts +5 -5
  30. package/dist/core/property/types/property-collection.type.d.ts +2 -2
  31. package/dist/core/property/types/property-constructor.type.d.ts +5 -1
  32. package/dist/core/property/types/property-options.interface.d.ts +17 -8
  33. package/dist/core/property/types/property-type.interface.d.ts +7 -3
  34. package/dist/core/property/types/property.interface.d.ts +5 -4
  35. package/dist/core/record/abstract-record.d.ts +12 -4
  36. package/dist/core/record/types/plugin.interface.d.ts +13 -3
  37. package/dist/core/record/types/record.interface.d.ts +20 -8
  38. package/dist/core/repository/exceptions.d.ts +4 -0
  39. package/dist/core/repository/in-memory.d.ts +3 -0
  40. package/dist/core/repository/provider.d.ts +6 -0
  41. package/dist/core/repository/query.d.ts +9 -0
  42. package/dist/core/repository/references.d.ts +4 -2
  43. package/dist/core/repository/repository.d.ts +4 -2
  44. package/dist/core/repository/state.d.ts +4 -0
  45. package/dist/core/repository/types/query.interface.d.ts +67 -0
  46. package/dist/core/repository/types/repository.interface.d.ts +13 -13
  47. package/dist/core/schema/types/schema.interface.d.ts +11 -4
  48. package/dist/core/symbols/meta.d.ts +2 -0
  49. package/dist/core/use-case/exceptions.d.ts +10 -0
  50. package/dist/core/use-case/scope.d.ts +21 -0
  51. package/dist/core/use-case/steps/describe.d.ts +13 -0
  52. package/dist/core/use-case/steps/references.d.ts +27 -0
  53. package/dist/core/use-case/steps/run.d.ts +20 -0
  54. package/dist/core/use-case/steps/types/steps.interface.d.ts +153 -0
  55. package/dist/core/use-case/steps/validate.d.ts +17 -0
  56. package/dist/core/use-case/use-case.d.ts +74 -9
  57. package/dist/experimental.d.ts +6 -0
  58. package/dist/experimental.js +123 -0
  59. package/dist/index-hn82hm31.js +1651 -0
  60. package/dist/index-sw2b9ehx.js +5019 -0
  61. package/dist/index.d.ts +30 -11
  62. package/dist/index.js +325 -4142
  63. package/dist/std/blueprint-plugin.d.ts +10 -10
  64. package/dist/std/blueprint.d.ts +10 -10
  65. package/dist/std/entity.d.ts +2 -2
  66. package/dist/std/properties/contact.d.ts +4 -4
  67. package/dist/std/properties/enum.d.ts +1 -1
  68. package/dist/std/properties/headless.d.ts +1 -1
  69. package/dist/std/properties/identity.d.ts +2 -2
  70. package/dist/std/properties/password.d.ts +1 -1
  71. package/dist/std/properties/primitives.d.ts +5 -5
  72. package/dist/std/properties/time.d.ts +2 -2
  73. package/dist/std/record-plugins.d.ts +12 -12
  74. package/dist/std/record.d.ts +1 -1
  75. package/dist/std/stages.d.ts +2 -0
  76. package/dist/testing/index.d.ts +2 -0
  77. package/dist/testing/index.js +502 -0
  78. package/dist/testing/provider-contract.d.ts +22 -0
  79. package/package.json +28 -8
@@ -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,21 @@
1
+ import type { IRepositoryProvider } from "../repository/types/repository.interface.js";
2
+ /**
3
+ * What a call carries to its repositories, internal: the providers that
4
+ * replace the ones the repositories declared (e.g. one inside a transaction).
5
+ */
6
+ export interface ExecutionScope {
7
+ /** Provider used by the call for each provider the repositories declared. */
8
+ readonly providers: ReadonlyMap<IRepositoryProvider, IRepositoryProvider>;
9
+ /**
10
+ * The variables a command gives the call in place of those an environment
11
+ * loads, by environment: a use case that declares it gets these.
12
+ */
13
+ readonly environments?: ReadonlyMap<object, Readonly<Record<string, unknown>>>;
14
+ }
15
+ /**
16
+ * Key of the constructor options (and of the repository view) that carries
17
+ * the `ExecutionScope`. Not exported by the package: only the core passes it.
18
+ */
19
+ export declare const ScopeKey: unique symbol;
20
+ /** The provider `scope` gives the call in place of `provider`. */
21
+ 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;
@@ -1,8 +1,10 @@
1
1
  import type { AnyBlueprint, BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection } from "../blueprint/types/blueprint.interface.js";
2
2
  import type { EntityEvent } from "../entity/event.js";
3
3
  import { type AnyEnvironment, type EnvironmentValue } from "../environment/environment.js";
4
+ import { type DeclaredExceptionClass } from "../exception/exception.js";
4
5
  import type { AnyRepository } from "../repository/types/repository.interface.js";
5
6
  import { Kind } from "../symbols/kind.js";
7
+ import type { UseCaseStep } from "./steps/types/steps.interface.js";
6
8
  /** A concrete UseCase declaration, usable as a dependency. */
7
9
  export interface AnyUseCaseClass {
8
10
  readonly [Kind]: "use-case";
@@ -10,11 +12,18 @@ export interface AnyUseCaseClass {
10
12
  readonly blueprint: AnyBlueprint;
11
13
  readonly response: AnyBlueprint;
12
14
  readonly dependencies: UseCaseCollection;
13
- new (input: any): {
15
+ readonly context: AnyBlueprint | undefined;
16
+ readonly description: string | undefined;
17
+ readonly tags: readonly string[];
18
+ readonly deprecated: boolean;
19
+ readonly errors: readonly AnyExceptionClass[];
20
+ new (input: any, ...options: any[]): {
14
21
  execute(): Promise<unknown>;
15
22
  pullEvents(): EntityEvent[];
16
23
  };
17
24
  }
25
+ /** Any class made by `exceptionOf`. */
26
+ export type AnyExceptionClass = DeclaredExceptionClass<any>;
18
27
  /**
19
28
  * What a use case can depend on: another use case, a repository made by
20
29
  * `repositoryOf`, or an environment made by `environmentOf`.
@@ -29,6 +38,21 @@ type Declaration = {
29
38
  export type UseCaseInput<Class extends Declaration> = BlueprintInput<Class["blueprint"]["properties"]>;
30
39
  export type UseCaseResponse<Class extends Declaration> = BlueprintValue<Class["response"]["properties"]>;
31
40
  export type UseCaseDependencies<Class extends Declaration> = DependencyFunctions<Class["dependencies"]>;
41
+ /** The context of a use case class, as `this.context` gives it; `undefined` without one. */
42
+ export type UseCaseContext<Class extends {
43
+ readonly context: unknown;
44
+ }> = Class["context"] extends IBlueprint<infer Properties> ? Readonly<BlueprintValue<Properties>> : undefined;
45
+ /**
46
+ * The second argument of the constructor of a use case: `{ context }`,
47
+ * required when the use case declares a context, not accepted otherwise.
48
+ */
49
+ export type UseCaseOptions<Context extends IBlueprintCollection | undefined = undefined> = Context extends IBlueprintCollection ? {
50
+ readonly context: BlueprintInput<Context>;
51
+ } : {
52
+ readonly context?: undefined;
53
+ };
54
+ /** @internal */
55
+ type OptionsArgs<Context extends IBlueprintCollection | undefined> = Context extends IBlueprintCollection ? [options: UseCaseOptions<Context>] : [];
32
56
  /**
33
57
  * What a use case gets for a dependency: a function for a use case, the
34
58
  * repository itself, or the variables of an environment.
@@ -44,36 +68,77 @@ export type DependencyFunctions<Dependencies extends UseCaseCollection> = {
44
68
  export type DependencyOverrides<Dependencies extends UseCaseCollection> = {
45
69
  readonly [Key in keyof Dependencies]?: Dependencies[Key] extends AnyRepository ? Partial<Dependencies[Key]> : DependencyOf<Dependencies[Key]>;
46
70
  };
47
- export interface UseCaseSettings<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection> {
71
+ export interface UseCaseSettings<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> {
48
72
  readonly name: string;
49
73
  readonly blueprint: IBlueprint<Input>;
50
74
  readonly response: IBlueprint<Output>;
51
75
  readonly dependencies?: Dependencies;
76
+ /** What the caller supplies with each call, e.g. the user and the request; validated by this blueprint. */
77
+ readonly context?: IBlueprint<Exclude<Context, undefined>>;
78
+ readonly description?: string;
79
+ readonly tags?: readonly string[];
80
+ readonly deprecated?: boolean;
81
+ /** The exceptions (made by `exceptionOf`) a caller should expect; documentation only. */
82
+ readonly errors?: readonly AnyExceptionClass[];
52
83
  }
53
- export declare const InvalidUseCaseDeclaration: import("../../index.js").DeclaredExceptionClass<{
84
+ /** `UseCaseSettings` with the `steps` that are its `execute`. */
85
+ export interface UseCaseStepsSettings<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> extends UseCaseSettings<Input, Output, Dependencies, Context> {
86
+ /**
87
+ * What the use case does, as data (see `UseCaseStep`): validated here,
88
+ * interpreted by its `execute`, and described by `describe()`.
89
+ */
90
+ readonly steps: readonly UseCaseStep<NoInfer<Dependencies>>[];
91
+ }
92
+ export declare const InvalidUseCaseDeclaration: DeclaredExceptionClass<{
54
93
  reason: string;
55
94
  }>;
56
- export declare const UseCaseAlreadyExecuted: import("../../index.js").DeclaredExceptionClass<undefined>;
57
- export declare const InvalidUseCaseDependencies: import("../../index.js").DeclaredExceptionClass<undefined>;
95
+ export declare const UseCaseAlreadyExecuted: DeclaredExceptionClass<undefined>;
96
+ export declare const InvalidUseCaseDependencies: DeclaredExceptionClass<undefined>;
58
97
  /** An operation with validated input, dependency functions and a drainable event queue. */
59
98
  export declare abstract class AbstractUseCase<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection> {
60
99
  readonly input: Readonly<BlueprintValue<Input>>;
61
- constructor(input: BlueprintInput<Input>);
100
+ constructor(input: BlueprintInput<Input>, ...args: unknown[]);
62
101
  /** Default parameter for implementations: `execute(deps = this.dependencies)`. */
63
102
  get dependencies(): DependencyFunctions<Dependencies>;
64
103
  abstract execute(dependencies?: DependencyOverrides<Dependencies>): Promise<BlueprintValue<Output>>;
65
104
  /** Immediately drains collected events; never executes their handlers. */
66
105
  pullEvents(): EntityEvent[];
67
106
  }
68
- export type UseCaseConstructor<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection> = (abstract new (input: BlueprintInput<Input>) => AbstractUseCase<Input, Output, Dependencies>) & {
107
+ /** What every use case class carries, abstract or not. */
108
+ type UseCaseStatics<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined> = {
69
109
  readonly [Kind]: "use-case";
70
110
  readonly useCaseName: string;
71
111
  readonly blueprint: IBlueprint<Input>;
72
112
  readonly response: IBlueprint<Output>;
73
113
  readonly dependencies: Dependencies;
114
+ /** The blueprint of the context of each call, or `undefined`. */
115
+ readonly context: Context extends IBlueprintCollection ? IBlueprint<Context> : undefined;
116
+ readonly description: string | undefined;
117
+ readonly tags: readonly string[];
118
+ readonly deprecated: boolean;
119
+ /** The exceptions a caller should expect. */
120
+ readonly errors: readonly AnyExceptionClass[];
121
+ };
122
+ /** What a use case instance carries beyond `AbstractUseCase`. */
123
+ type UseCaseInstanceContext<Context extends IBlueprintCollection | undefined> = {
124
+ /** What the caller supplied with the call, parsed and frozen; `undefined` without a declared context. */
125
+ readonly context: Context extends IBlueprintCollection ? Readonly<BlueprintValue<Context>> : undefined;
74
126
  };
75
- /** Declares an abstract operation. Extend it and implement an async execute method. */
76
- export declare function useCaseOf<Input extends IBlueprintCollection, Output extends IBlueprintCollection, const Dependencies extends UseCaseCollection = Record<never, never>>(settings: UseCaseSettings<Input, Output, Dependencies>): UseCaseConstructor<Input, Output, Dependencies>;
127
+ 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>;
128
+ /**
129
+ * What `useCaseOf` gives with `steps`: concrete, its `execute` interprets the
130
+ * steps. A subclass may still override `execute`.
131
+ */
132
+ export type StepsUseCaseConstructor<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> = (new (input: BlueprintInput<Input>, ...options: OptionsArgs<Context>) => Omit<AbstractUseCase<Input, Output, Dependencies>, "execute"> & UseCaseInstanceContext<Context> & {
133
+ execute(dependencies?: DependencyOverrides<Dependencies>): Promise<BlueprintValue<Output>>;
134
+ }) & UseCaseStatics<Input, Output, Dependencies, Context>;
135
+ /**
136
+ * Declares an operation. With `steps` the class is concrete: its `execute`
137
+ * interprets them. Without, it is abstract: extend it and implement an async
138
+ * execute method.
139
+ */
140
+ 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>;
141
+ 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>;
77
142
  export declare function isUseCaseClass(value: unknown): value is AnyUseCaseClass;
78
143
  export declare function isUseCase(value: unknown): value is AbstractUseCase<IBlueprintCollection, IBlueprintCollection, UseCaseCollection>;
79
144
  export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `@afora/core/experimental`: features that may change in any release. Not
3
+ * covered by the compatibility policy in `VERSIONING.md`.
4
+ */
5
+ export { sliceOf, sliceValue } from "./core/blueprint/slice.js";
6
+ export type { SlicedBlueprint, SlicedValue, SliceMask, ValueMask, } from "./core/blueprint/types/slice.interface.js";
@@ -0,0 +1,123 @@
1
+ import {
2
+ isBlueprint2,
3
+ isRecordClass2,
4
+ recordFieldOf2,
5
+ listOf2,
6
+ isBlueprintList2,
7
+ AbstractRecord2,
8
+ declareRecord,
9
+ UndeclaredBlueprintProperty2,
10
+ optionalOf2,
11
+ blueprintFromProperties
12
+ } from "./index-hn82hm31.js";
13
+
14
+ // src/core/blueprint/slice.ts
15
+ function propertiesOfField(field) {
16
+ if (field === undefined)
17
+ return;
18
+ if (isBlueprintList2(field))
19
+ return propertiesOfField(field.item);
20
+ const record = recordFieldOf2(field);
21
+ if (record)
22
+ return record.record.blueprint.properties;
23
+ return isBlueprint2(field) ? field.properties : undefined;
24
+ }
25
+ function maskOf(selector, against) {
26
+ if (isRecordClass2(selector))
27
+ return maskOf(selector.blueprint, against);
28
+ if (isBlueprint2(selector)) {
29
+ return Object.fromEntries(Object.entries(selector.properties).map(([key, field]) => [key, fieldMaskOf(field, against?.[key])]));
30
+ }
31
+ return selector;
32
+ }
33
+ function fieldMaskOf(field, other) {
34
+ if (isBlueprintList2(field)) {
35
+ return fieldMaskOf(field.item, isBlueprintList2(other) ? other.item : other);
36
+ }
37
+ const record = recordFieldOf2(field);
38
+ if (record) {
39
+ if (record.record === recordFieldOf2(other)?.record)
40
+ return true;
41
+ return maskOf(record.record, propertiesOfField(other));
42
+ }
43
+ if (isBlueprint2(field)) {
44
+ return field === other ? true : maskOf(field, propertiesOfField(other));
45
+ }
46
+ return true;
47
+ }
48
+ function sliceCollection(properties, mask, path) {
49
+ const sliced = {};
50
+ for (const [key, selected] of Object.entries(mask)) {
51
+ if (!selected)
52
+ continue;
53
+ if (!Object.hasOwn(properties, key)) {
54
+ throw new UndeclaredBlueprintProperty2({
55
+ source: "blueprint",
56
+ details: { key: path + key }
57
+ });
58
+ }
59
+ sliced[key] = sliceField(properties[key], selected, path + key);
60
+ }
61
+ return sliced;
62
+ }
63
+ function slicedRecordOf(Original, mask, path) {
64
+ const source = Original.blueprint;
65
+ const blueprint = blueprintFromProperties(source.plugins, sliceCollection(source.properties, mask, `${path}.`));
66
+ const kept = (keys) => keys.filter((key) => Object.hasOwn(blueprint.properties, key));
67
+ const { sensitive, unique, change } = Original.options;
68
+ const Sliced = declareRecord(AbstractRecord2, blueprint, {
69
+ sensitive: kept(sensitive),
70
+ unique: kept(unique),
71
+ change: kept(change)
72
+ }, Original.name);
73
+ Object.defineProperty(Sliced, Symbol.hasInstance, {
74
+ value(value) {
75
+ return Function.prototype[Symbol.hasInstance].call(this, value) || value instanceof Original;
76
+ }
77
+ });
78
+ return Sliced;
79
+ }
80
+ function sliceField(field, selected, path) {
81
+ if (selected === true)
82
+ return field;
83
+ if (isBlueprintList2(field)) {
84
+ const list = listOf2(sliceField(field.item, selected, path), {
85
+ minItems: field.minItems,
86
+ maxItems: field.maxItems
87
+ });
88
+ return field.optional ? optionalOf2(list) : list;
89
+ }
90
+ const record = recordFieldOf2(field);
91
+ if (record) {
92
+ const sliced = slicedRecordOf(record.record, selected, path);
93
+ return record.optional ? optionalOf2(sliced) : sliced;
94
+ }
95
+ if (isBlueprint2(field)) {
96
+ const sliced = blueprintFromProperties(field.plugins, sliceCollection(field.properties, selected, `${path}.`));
97
+ return field.optional ? optionalOf2(sliced) : sliced;
98
+ }
99
+ throw new UndeclaredBlueprintProperty2({
100
+ source: "blueprint",
101
+ details: { key: `${path}.${Object.keys(selected)[0] ?? ""}` }
102
+ });
103
+ }
104
+ var sliceOf = (blueprint, selector) => blueprintFromProperties(blueprint.plugins, sliceCollection(blueprint.properties, maskOf(selector, blueprint.properties), ""));
105
+ var sliceValue = (value, selector) => pick(value, maskOf(selector));
106
+ function pick(value, mask) {
107
+ if (Array.isArray(value))
108
+ return value.map((item) => pick(item, mask));
109
+ if (typeof value !== "object" || value === null)
110
+ return value;
111
+ const source = isRecordClass2(value.constructor) ? value.toJSON() : value;
112
+ const picked = {};
113
+ for (const [key, selected] of Object.entries(mask)) {
114
+ if (!selected || !Object.hasOwn(source, key))
115
+ continue;
116
+ picked[key] = selected === true ? source[key] : pick(source[key], selected);
117
+ }
118
+ return picked;
119
+ }
120
+ export {
121
+ sliceOf,
122
+ sliceValue
123
+ };