@afora/core 0.0.7 → 0.0.9

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 (99) hide show
  1. package/README.md +157 -25
  2. package/dist/adapters.d.ts +25 -0
  3. package/dist/adapters.js +56 -0
  4. package/dist/core/application/exceptions.d.ts +17 -0
  5. package/dist/core/application/migrate.d.ts +19 -0
  6. package/dist/core/application/schema.d.ts +30 -0
  7. package/dist/core/application/start.d.ts +7 -0
  8. package/dist/core/application/types/application.interface.d.ts +73 -2
  9. package/dist/core/auth/jwt.d.ts +30 -0
  10. package/dist/core/auth/types/jwt.interface.d.ts +66 -0
  11. package/dist/core/blueprint/exceptions.d.ts +5 -0
  12. package/dist/core/blueprint/optional.d.ts +3 -1
  13. package/dist/core/blueprint/types/blueprint.interface.d.ts +79 -5
  14. package/dist/core/blueprint/variant.d.ts +32 -0
  15. package/dist/core/command/types/command.interface.d.ts +7 -0
  16. package/dist/core/entity/abstract-entity.d.ts +33 -1
  17. package/dist/core/entity/entity.d.ts +1 -1
  18. package/dist/core/entity/event.d.ts +13 -2
  19. package/dist/core/entity/exceptions.d.ts +17 -0
  20. package/dist/core/entity/trust.d.ts +24 -0
  21. package/dist/core/entity/types/entity.interface.d.ts +96 -2
  22. package/dist/core/entity/version.d.ts +13 -0
  23. package/dist/core/event-policy/event-policy.d.ts +25 -0
  24. package/dist/core/event-policy/exceptions.d.ts +11 -0
  25. package/dist/core/event-policy/publish.d.ts +17 -0
  26. package/dist/core/event-policy/publishers.d.ts +18 -0
  27. package/dist/core/event-policy/types/event-policy.interface.d.ts +72 -0
  28. package/dist/core/output/access.d.ts +16 -0
  29. package/dist/core/output/coerce.d.ts +9 -0
  30. package/dist/core/output/exceptions.d.ts +16 -0
  31. package/dist/core/output/http.d.ts +36 -0
  32. package/dist/core/output/openapi-sink.d.ts +160 -39
  33. package/dist/core/output/stage.d.ts +7 -2
  34. package/dist/core/output/status.d.ts +2 -2
  35. package/dist/core/output/types/http.interface.d.ts +130 -0
  36. package/dist/core/output/types/output.interface.d.ts +25 -5
  37. package/dist/core/parse/issue.d.ts +28 -3
  38. package/dist/core/parse/parse.d.ts +19 -0
  39. package/dist/core/parse/strip.d.ts +13 -0
  40. package/dist/core/property/abstract-property.d.ts +9 -0
  41. package/dist/core/property/define-property.d.ts +34 -6
  42. package/dist/core/property/types/property-options.interface.d.ts +10 -0
  43. package/dist/core/property/types/property-type.interface.d.ts +4 -0
  44. package/dist/core/record/abstract-record.d.ts +7 -0
  45. package/dist/core/record/types/record.interface.d.ts +12 -1
  46. package/dist/core/repository/exceptions.d.ts +43 -0
  47. package/dist/core/repository/in-memory.d.ts +16 -3
  48. package/dist/core/repository/migrate.d.ts +20 -0
  49. package/dist/core/repository/plan.d.ts +20 -0
  50. package/dist/core/repository/provider.d.ts +1 -1
  51. package/dist/core/repository/query.d.ts +9 -2
  52. package/dist/core/repository/repository.d.ts +40 -8
  53. package/dist/core/repository/state.d.ts +32 -2
  54. package/dist/core/repository/types/query.interface.d.ts +106 -5
  55. package/dist/core/repository/types/repository.interface.d.ts +139 -7
  56. package/dist/core/repository/types/schema.interface.d.ts +126 -0
  57. package/dist/core/repository/upcast.d.ts +64 -0
  58. package/dist/core/schema/schema.d.ts +28 -0
  59. package/dist/core/symbols/kind.d.ts +1 -1
  60. package/dist/core/use-case/access.d.ts +12 -0
  61. package/dist/core/use-case/decorators.d.ts +40 -0
  62. package/dist/core/use-case/route.d.ts +18 -0
  63. package/dist/core/use-case/steps/types/check.interface.d.ts +214 -0
  64. package/dist/core/use-case/types/access.interface.d.ts +39 -0
  65. package/dist/core/use-case/types/route.interface.d.ts +66 -0
  66. package/dist/core/use-case/use-case.d.ts +52 -2
  67. package/dist/experimental.d.ts +13 -1
  68. package/dist/experimental.js +64 -26
  69. package/dist/index-3aae5jfq.js +2997 -0
  70. package/dist/index-hg1z2s2v.js +1055 -0
  71. package/dist/index-ny3as79r.js +4567 -0
  72. package/dist/index-xegv6a1p.js +4291 -0
  73. package/dist/index-y280ew0k.js +396 -0
  74. package/dist/index.d.ts +55 -48
  75. package/dist/index.js +141 -169
  76. package/dist/std/blueprint-plugin.d.ts +6 -2
  77. package/dist/std/blueprint.d.ts +3 -0
  78. package/dist/std/entity.d.ts +2 -1
  79. package/dist/std/event-policies.d.ts +2 -0
  80. package/dist/std/properties/exceptions.d.ts +11 -0
  81. package/dist/std/properties/json.d.ts +54 -0
  82. package/dist/std/properties/password.d.ts +0 -2
  83. package/dist/std/properties/time.d.ts +4 -0
  84. package/dist/std/record-plugins.d.ts +2 -2
  85. package/dist/std/resource.d.ts +31 -0
  86. package/dist/std/runtime.d.ts +6 -0
  87. package/dist/std/use-cases/auth.d.ts +5 -1
  88. package/dist/std/use-cases/crud.d.ts +6 -4
  89. package/dist/std/use-cases/index.d.ts +1 -1
  90. package/dist/std/use-cases/shared.d.ts +34 -6
  91. package/dist/std/use-cases/types.d.ts +197 -22
  92. package/dist/testing/http-provider-contract.d.ts +32 -0
  93. package/dist/testing/index.d.ts +4 -0
  94. package/dist/testing/index.js +839 -3
  95. package/dist/testing/memory-publisher.d.ts +16 -0
  96. package/dist/testing/provider-contract.d.ts +31 -1
  97. package/package.json +17 -3
  98. package/dist/index-q6hggpzw.js +0 -6667
  99. package/dist/index-r78dsvtc.js +0 -1721
@@ -0,0 +1,64 @@
1
+ import type { EntityUpgrades } from "../entity/types/entity.interface.js";
2
+ import type { RepositoryState } from "./state.js";
3
+ import type { RepositoryData } from "./types/repository.interface.js";
4
+ /** What happens to the value of one key of a collection. */
5
+ type FieldStep = {
6
+ readonly kind: "property";
7
+ readonly fill?: unknown;
8
+ readonly renamedFrom?: string;
9
+ } | {
10
+ readonly kind: "collection";
11
+ readonly fields: CollectionSteps;
12
+ } | {
13
+ readonly kind: "list";
14
+ readonly item: FieldStep;
15
+ } | {
16
+ readonly kind: "opaque";
17
+ };
18
+ /** The steps of the keys of a blueprint, and whether any of them does something. */
19
+ interface CollectionSteps {
20
+ readonly fields: ReadonlyMap<string, FieldStep>;
21
+ }
22
+ /** How the rows of an entity are brought up to its current shape. */
23
+ export interface UpcastPlan {
24
+ readonly versioned: boolean;
25
+ readonly version: number;
26
+ /** Whether the entity lists `previousNames`: its moved rows may hold old keys. */
27
+ readonly renamed: boolean;
28
+ readonly upgrade: EntityUpgrades;
29
+ readonly fields: CollectionSteps;
30
+ }
31
+ /** What `upcast` did to a row, for `app.migrate`. */
32
+ export interface UpcastReport {
33
+ /** The version the row was stored at (1 without the version key). */
34
+ from: number;
35
+ /** Paths of the keys moved from their `renamedFrom`. */
36
+ readonly renamed: string[];
37
+ /** Paths of the keys given their `fill`. */
38
+ readonly filled: string[];
39
+ /** Paths of the keys the blueprint no longer declares. */
40
+ readonly removed: string[];
41
+ }
42
+ /** The plan of `entity`, even when it changes nothing but the keys it no longer declares. */
43
+ export declare function fullUpcastPlanOf(entity: object): UpcastPlan;
44
+ /**
45
+ * `data`, a row of an entity of `plan` as a provider gave it, in the current
46
+ * shape: without the version key, through each `upgrade` step from the
47
+ * version after the stored one up to `schemaVersion`, then the automatic
48
+ * steps of the current blueprint (`renamedFrom`, `fill`, and, with `drop`,
49
+ * leaving out keys it no longer declares). The steps run first because each
50
+ * is written against the shape of the version before it, which the
51
+ * automatic steps would already have changed. Throws
52
+ * `InvalidStoredSchemaVersion` for a version that is not a positive integer
53
+ * or is above `schemaVersion`.
54
+ */
55
+ export declare function upcast(source: string, plan: UpcastPlan, data: RepositoryData, drop?: boolean, report?: UpcastReport): RepositoryData;
56
+ /**
57
+ * `rows` of `state`'s entity in its current shape (see `upcast`); `rows`
58
+ * itself when the entity needs nothing. Throws `InvalidRepositoryData`
59
+ * caused by `InvalidStoredSchemaVersion` for a row it cannot bring up.
60
+ */
61
+ export declare function upcastRows(state: RepositoryState, rows: readonly RepositoryData[]): readonly RepositoryData[];
62
+ /** `data` with the version key of `state`'s entity, when it declares `schemaVersion`. */
63
+ export declare function withSchemaVersion(entity: object, data: RepositoryData): RepositoryData;
64
+ export {};
@@ -5,6 +5,34 @@ export declare function describeError(error: {
5
5
  instancePath: string;
6
6
  message: string;
7
7
  }): string;
8
+ /** One TypeBox error, as `Errors` gives it. */
9
+ export interface SchemaError {
10
+ readonly keyword: string;
11
+ readonly schemaPath: string;
12
+ readonly instancePath: string;
13
+ readonly params: object;
14
+ readonly message: string;
15
+ }
16
+ /**
17
+ * The errors of `value`, which `schema` rejects. For every schema but one
18
+ * they are what `schema.type.Errors(value)` gives: same errors, same order,
19
+ * same messages. The one is a discriminated union given an object, told by
20
+ * the variant its key names (see `errorsOfVariant`). TypeBox is only asked
21
+ * about a schema with a keyword that has no explanation here (`explainerOf`).
22
+ * @internal
23
+ */
24
+ export declare function errorsOfSchema(schema: ISchema<TSchema>, value: unknown): readonly SchemaError[];
25
+ /** The two checks of a schema, as functions. */
26
+ export interface SchemaChecks {
27
+ match(value: unknown): boolean;
28
+ matchShape(value: unknown): boolean;
29
+ }
30
+ /**
31
+ * `schema.match` and `schema.matchShape`, compiled now, for who checks many
32
+ * values with one schema; those of a schema not made by `schemaOf` call it.
33
+ * @internal
34
+ */
35
+ export declare function checksOfSchema(schema: ISchema<TSchema>): SchemaChecks;
8
36
  /** A schema class declared with `schemaOf`. */
9
37
  export interface SchemaClass<SchemaType extends TSchema> {
10
38
  new (type?: SchemaType): ISchema<SchemaType>;
@@ -1,2 +1,2 @@
1
- /** Brands blueprints, lists and records, so none is told apart by its shape. */
1
+ /** Brands blueprints, lists, variants and records, so none is told apart by its shape. */
2
2
  export declare const Kind: unique symbol;
@@ -0,0 +1,12 @@
1
+ import type { AnyBlueprint } from "../blueprint/types/blueprint.interface.js";
2
+ import type { AccessDeclaration } from "./types/access.interface.js";
3
+ /** Whether `access` lets anyone in. */
4
+ export declare function isPublicAccess(access: AccessDeclaration): access is {
5
+ readonly public: true;
6
+ };
7
+ /**
8
+ * The frozen declaration of `access` for a use case whose input is
9
+ * `blueprint`, or the reason it is not valid. The keys of `context` are
10
+ * checked where the context is known: when the use case becomes an operation.
11
+ */
12
+ export declare function accessDeclarationOf(access: unknown, blueprint: AnyBlueprint): AccessDeclaration | string;
@@ -0,0 +1,40 @@
1
+ import type { AccessDeclaration } from "./types/access.interface.js";
2
+ import type { RouteSettings } from "./types/route.interface.js";
3
+ /** What a decorator of this module is given besides the class: nothing it reads. */
4
+ type ClassContext = {
5
+ readonly kind: string;
6
+ };
7
+ /** A class decorator for use cases, in the standard form and in the legacy one of TypeScript. */
8
+ export type UseCaseDecorator = <UseCase extends abstract new (...args: any[]) => unknown>(value: UseCase, context?: ClassContext) => void;
9
+ /**
10
+ * Says where a use case is served over HTTP, on the class itself:
11
+ *
12
+ * ```ts
13
+ * @Route("GET", "/posts/{slug}", { query: ["page"], cookie: { session: "sid" } })
14
+ * class ListPosts extends useCaseOf({ name: "ListPosts", blueprint, response }) {}
15
+ * ```
16
+ *
17
+ * The same as the `route` setting of `useCaseOf` (see `RouteDeclaration`), so
18
+ * `describe()`, the sinks and `afora.json` see one declaration. It names no
19
+ * server: the sink of an output does. Throws `InvalidUseCaseDeclaration` on
20
+ * a class that is not a use case, on one that already has another route
21
+ * (the same one again is accepted), and for a route that does not fit the
22
+ * input of the use case.
23
+ */
24
+ export declare function Route(method: RouteSettings["method"], path?: string, options?: Omit<RouteSettings, "method" | "path">): UseCaseDecorator;
25
+ export declare function Route(method: RouteSettings["method"], options?: Omit<RouteSettings, "method" | "path">): UseCaseDecorator;
26
+ /**
27
+ * Says who may call a use case, on the class itself:
28
+ *
29
+ * ```ts
30
+ * @Access({ scopes: ["posts:write"], context: { userId: "subject" } })
31
+ * class PublishPost extends useCaseOf({ name: "PublishPost", blueprint, response, context }) {}
32
+ * ```
33
+ *
34
+ * The same as the `access` setting of `useCaseOf` (see `AccessDeclaration`).
35
+ * Throws `InvalidUseCaseDeclaration` on a class that is not a use case, on
36
+ * one that already declares another access (the same one again is accepted),
37
+ * and for a declaration that does not fit the input of the use case.
38
+ */
39
+ export declare function Access(access: AccessDeclaration): UseCaseDecorator;
40
+ export {};
@@ -0,0 +1,18 @@
1
+ import type { AnyBlueprint } from "../blueprint/types/blueprint.interface.js";
2
+ import type { OpenAPISchema } from "../openapi/types/openapi.interface.js";
3
+ import type { RouteDeclaration } from "./types/route.interface.js";
4
+ /** A header name (an HTTP token), which a cookie name is too. */
5
+ export declare const HEADER_NAME: RegExp;
6
+ /** A path such as `/entries/{slug}`: `{name}` only as a whole segment, no `:`, `*`, query or spaces. */
7
+ export declare function isRoutePath(value: unknown, parameters?: boolean): value is string;
8
+ /** The names of the `{name}` segments of `path`, in order. */
9
+ export declare function parametersOfPath(path: string): string[];
10
+ /** Whether a JSON Schema describes a value that is neither an object nor a list. */
11
+ export declare function isScalarSchema(schema: OpenAPISchema): boolean;
12
+ /** Whether `name` is a key of `blueprint` that is a scalar property (no object, no list). */
13
+ export declare function isScalarKey(blueprint: AnyBlueprint, name: string): boolean;
14
+ /**
15
+ * The frozen, normalized declaration of `route` for a use case whose input is
16
+ * `blueprint` (the method in lower case), or the reason it is not valid.
17
+ */
18
+ export declare function routeDeclarationOf(route: unknown, blueprint: AnyBlueprint, response: AnyBlueprint): RouteDeclaration | string;
@@ -0,0 +1,214 @@
1
+ /**
2
+ * The compile-time checking of `useCaseOf({ steps })`: the steps are inferred
3
+ * as a tuple and folded in order. Each step sees the values in scope (the
4
+ * input, the context, the environments and the names bound by the earlier
5
+ * steps with `as`); its references must name a path of one of them, and its
6
+ * `args`, `values`, `value` or `input` must fit what the called function,
7
+ * record, repository or use case takes. A reference that does not fit is
8
+ * replaced by the references that would, which is the error TypeScript shows
9
+ * and what the editor offers inside a `"$…"` string.
10
+ *
11
+ * The list of the paths of the scope is computed only for a reference that
12
+ * does not fit, so that right steps cost little to check. The conditionals on
13
+ * the steps are not distributive (`[Step] extends [...]`): TypeScript then
14
+ * never evaluates them with the constraint of the steps while it infers them.
15
+ */
16
+ import type { BlueprintValue, IBlueprint, IBlueprintCollection } from "../../../blueprint/types/blueprint.interface.js";
17
+ import type { AnyEnvironment, EnvironmentValue } from "../../../environment/environment.js";
18
+ import type { Meta } from "../../../symbols/meta.js";
19
+ import type { AnyUseCaseClass, UseCaseInput, UseCaseResponse } from "../../use-case.js";
20
+ /** The values the first step can reference: the input, the context (if declared) and the environments. */
21
+ export type StepsInitialScope<Input extends IBlueprintCollection, Context extends IBlueprintCollection | undefined, Dependencies> = {
22
+ readonly input: BlueprintValue<Input>;
23
+ readonly env: {
24
+ readonly [Key in keyof Dependencies as Dependencies[Key] extends AnyEnvironment ? Key : never]: Dependencies[Key] extends AnyEnvironment ? EnvironmentValue<Dependencies[Key]> : never;
25
+ };
26
+ } & (Context extends IBlueprintCollection ? {
27
+ readonly context: BlueprintValue<Context>;
28
+ } : unknown);
29
+ type Split<Path extends string> = Path extends `${infer Head}.${infer Tail}` ? [Head, ...Split<Tail>] : [Path];
30
+ /** `Value[Key]` for a value that is not `null` or `undefined`: by index (or `length`) in a list; `never` when absent. */
31
+ type GetDefined<Value, Key extends string> = Value extends readonly unknown[] ? Key extends `${number}` ? Key extends keyof Value ? Value[Key] : Value[number] : Key extends "length" ? number : never : Key extends keyof Value ? Value[Key] : never;
32
+ /**
33
+ * `Value[Key]` as `followPath` reads it: `undefined` through a value that
34
+ * may be `undefined` or `null`, and `never` when no value of the type has
35
+ * the key; `unknown` stays `unknown`.
36
+ */
37
+ type Get<Value, Key extends string> = unknown extends Value ? unknown : [GetDefined<NonNullable<Value>, Key>] extends [infer Found] ? [Found] extends [never] ? never : Found | (Value extends null | undefined ? undefined : never) : never;
38
+ type Walk<Value, Path extends readonly string[]> = Path extends readonly [
39
+ infer Key extends string,
40
+ ...infer Rest extends string[]
41
+ ] ? Walk<Get<Value, Key>, Rest> : Value;
42
+ /** The type of the value the reference `$root.a.b` stands for in `Scope`; `never` when it names nothing. */
43
+ export type StepReferenceType<Scope, Reference> = [Reference] extends [
44
+ `$${infer Path}`
45
+ ] ? Split<Path> extends [
46
+ infer Root extends string,
47
+ ...infer Rest extends string[]
48
+ ] ? Root extends "env" ? Rest extends [string, ...string[]] ? Walk<Get<Scope, Root>, Rest> : never : Walk<Get<Scope, Root>, Rest> : never : never;
49
+ /** What a path walks into: the value of a record, not its methods. */
50
+ type DataOf<Value> = Value extends {
51
+ readonly [Meta]: {
52
+ readonly blueprint: IBlueprint<infer Properties>;
53
+ };
54
+ } ? BlueprintValue<Properties> : Value;
55
+ type Leaf = string | number | boolean | bigint | symbol | null | undefined | Date | readonly unknown[] | ((...args: any[]) => unknown);
56
+ type Below<Value, Prefix extends string, Expected, Depth extends unknown[]> = Depth["length"] extends 3 ? never : DataOf<NonNullable<Value>> extends infer Data ? Data extends Leaf ? never : {
57
+ [Key in keyof Data & string]-?: PathsFrom<Data[Key] | (Value extends null | undefined ? undefined : never), `${Prefix}.${Key}`, Expected, [
58
+ ...Depth,
59
+ 0
60
+ ]>;
61
+ }[keyof Data & string] : never;
62
+ type PathsFrom<Value, Path extends string, Expected, Depth extends unknown[]> = Path extends unknown ? ([Value] extends [Expected] ? Path : never) | Below<Value, Path, Expected, Depth> : never;
63
+ /** Every reference of `Scope` whose value fits `Expected` (three keys deep at most). */
64
+ export type StepReferencesOf<Scope, Expected = unknown> = Scope extends unknown ? {
65
+ [Root in keyof Scope & string]-?: Root extends "env" ? Below<Scope[Root], `$${Root}`, Expected, [0]> : PathsFrom<Scope[Root], `$${Root}`, Expected, []>;
66
+ }[keyof Scope & string] : never;
67
+ /** `Reference` when it names a value of `Scope` that fits `Expected`; else the references that would. */
68
+ type CheckReference<Reference, Expected, Scope> = [
69
+ StepReferenceType<Scope, Reference>
70
+ ] extends [infer Type] ? [Type] extends [never] ? StepReferencesOf<Scope, Expected> : [Type] extends [Expected] ? Reference : StepReferencesOf<Scope, Expected> : never;
71
+ type Primitive = string | number | boolean | null;
72
+ /** The type of the key `Key` in what `Expected` takes, over each object of it. */
73
+ type ExpectedAt<Expected, Key> = Expected extends object ? Key extends keyof Expected ? Expected[Key] : never : never;
74
+ /** The keys `Expected` takes in a mapping. */
75
+ type KnownKeys<Expected> = Expected extends object ? keyof Expected : never;
76
+ /** The keys the plain objects of `Expected` require (a record instance is not built from a mapping). */
77
+ type RequiredKeys<Expected> = Expected extends {
78
+ readonly [Meta]: unknown;
79
+ } ? never : Expected extends object ? {
80
+ [Key in keyof Expected]-?: undefined extends Expected[Key] ? never : Key;
81
+ }[keyof Expected] : never;
82
+ /** The keys of `Value` that `Expected` takes, each checked; the others are excess properties. */
83
+ type CheckKeys<Value, Expected, Scope> = Value extends unknown ? {
84
+ readonly [Key in keyof Value as Key extends KnownKeys<Expected> ? Key : never]: StepValueCheck<Value[Key], ExpectedAt<Expected, Key>, Scope>;
85
+ } : never;
86
+ type CheckMapping<Value, Expected, Scope> = unknown extends Expected ? {
87
+ readonly [Key in keyof Value]: StepValueCheck<Value[Key], unknown, Scope>;
88
+ } : [Exclude<RequiredKeys<Expected>, keyof Value>] extends [never] ? CheckKeys<Value, Expected, Scope> : CheckKeys<Value, Expected, Scope> & {
89
+ readonly [Key in Exclude<RequiredKeys<Expected>, keyof Value>]: ExpectedAt<Expected, Key>;
90
+ };
91
+ type ElementAt<Expected, Index> = unknown extends Expected ? unknown : Expected extends readonly unknown[] ? Index extends keyof Expected ? Expected[Index] : Expected[number] : never;
92
+ /**
93
+ * What the step value `Value` must be where `Expected` is taken: itself when
94
+ * it fits; else, for a reference, the references that would fit, and for a
95
+ * literal, `Expected`. A mapping is checked key by key, a list item by item.
96
+ */
97
+ export type StepValueCheck<Value, Expected, Scope> = [Value] extends [
98
+ `$${string}`
99
+ ] ? CheckReference<Value, Expected, Scope> : [Value] extends [Primitive] ? [Value] extends [Expected] ? Value : Expected : [Value] extends [{
100
+ readonly literal: unknown;
101
+ }] ? Value : [Value] extends [readonly unknown[]] ? {
102
+ readonly [Index in keyof Value]: StepValueCheck<Value[Index], ElementAt<Expected, Index>, Scope>;
103
+ } : [Value] extends [object] ? CheckMapping<Value, Expected, Scope> : Value;
104
+ /** The parameter `Index` of a function whose parameters are `Parameters`. */
105
+ type ParameterAt<Parameters extends readonly unknown[], Index> = Index extends keyof Parameters ? Parameters[Index] : number extends Parameters["length"] ? Parameters[number] : undefined;
106
+ /** What the `args` of a call must be: each against its parameter, or one reference to a list. */
107
+ type CheckArgs<Args, Parameters extends readonly unknown[], Scope> = [
108
+ Args
109
+ ] extends [`$${string}`] ? CheckReference<Args, readonly unknown[], Scope> : {
110
+ readonly [Index in keyof Args]: StepValueCheck<Args[Index], ParameterAt<Parameters, Index>, Scope>;
111
+ };
112
+ type Fn = (...args: any[]) => unknown;
113
+ type ParametersOf<Value> = [Value] extends [Fn] ? Parameters<Value> : unknown[];
114
+ type FirstParameter<Value> = [Value] extends [never] ? unknown : [Value] extends [Fn] ? Parameters<Value>[0] : unknown;
115
+ type ResultOf<Value> = [Value] extends [Fn] ? Awaited<ReturnType<Value>> : unknown;
116
+ /** `Dependencies[Key][Member]`, or `unknown`. */
117
+ type MemberOf<Dependencies, Key, Member> = Key extends keyof Dependencies ? Member extends keyof Dependencies[Key] ? Dependencies[Key][Member] : unknown : unknown;
118
+ /** The method `"users.findByEmail"` names among `Dependencies`. */
119
+ type FindMethod<Dependencies, Target> = Target extends `${infer Repository}.${infer Action}` ? MemberOf<Dependencies, Repository, Action> : unknown;
120
+ /** The members of a record no `invoke` may call. */
121
+ type ForbiddenMethod = "set" | "emit" | "constructor" | "toLocaleString" | "valueOf" | "hasOwnProperty" | "isPrototypeOf" | "propertyIsEnumerable";
122
+ /** The public methods of the record `Record`: what `invoke.method` names. */
123
+ type StepInvokeMethod<Record> = Record extends unknown ? {
124
+ [Key in keyof Record & string]-?: Record[Key] extends Fn ? Key : never;
125
+ }[Exclude<keyof Record & string, ForbiddenMethod>] : never;
126
+ type ChangeOf<Record> = Record extends {
127
+ readonly change: infer Change;
128
+ } ? Change : never;
129
+ type UseCaseOf<Dependencies, Key> = Key extends keyof Dependencies ? Dependencies[Key] extends AnyUseCaseClass ? Dependencies[Key] : never : never;
130
+ /** A step that calls `Method` with its `args`: `[checked step, result]`. */
131
+ type CallStep<Step, Method, Scope> = [
132
+ {
133
+ readonly [Key in keyof Step]: Key extends "args" ? CheckArgs<Step[Key], ParametersOf<Method>, Scope> : Step[Key];
134
+ },
135
+ ResultOf<Method>
136
+ ];
137
+ /** A step whose `Field` is checked against `Expected`: `[checked step, result]`. */
138
+ type ValueStep<Step, Field, Expected, Result, Scope> = [
139
+ {
140
+ readonly [Key in keyof Step]: Key extends Field ? StepValueCheck<Step[Key], Expected, Scope> : Step[Key];
141
+ },
142
+ Result
143
+ ];
144
+ type InvokeStep<Step, Record, Scope> = [Record] extends [never] ? ValueStep<Step, "invoke", object, unknown, Scope> : [Step] extends [{
145
+ readonly method: infer Method;
146
+ }] ? Method extends keyof Record ? CallStep<Step, Record[Method], Scope> : [
147
+ {
148
+ readonly [Key in keyof Step]: Key extends "method" ? StepInvokeMethod<Record> : Step[Key];
149
+ },
150
+ unknown
151
+ ] : [Step, unknown];
152
+ type ChangeStep<Step, Record, Scope> = [Record] extends [never] ? ValueStep<Step, "change", object, unknown, Scope> : [Step] extends [{
153
+ readonly key: infer Name;
154
+ }] ? Name extends keyof ChangeOf<Record> ? ValueStep<Step, "value", FirstParameter<ChangeOf<Record>[Name]>, Record, Scope> : [
155
+ {
156
+ readonly [Key in keyof Step]: Key extends "key" ? keyof ChangeOf<Record> & string : Step[Key];
157
+ },
158
+ Record
159
+ ] : [Step, Record];
160
+ /** `[what Step must be, what it gives to its as]`, read against `Scope`. */
161
+ type CheckStep<Step, Dependencies, Scope> = [Step] extends [
162
+ {
163
+ readonly find: infer Target;
164
+ }
165
+ ] ? CallStep<Step, FindMethod<Dependencies, Target>, Scope> : [Step] extends [{
166
+ readonly run: unknown;
167
+ readonly fn: infer Run;
168
+ }] ? CallStep<Step, Run, Scope> : [Step] extends [{
169
+ readonly invoke: infer Reference;
170
+ }] ? InvokeStep<Step, NonNullable<StepReferenceType<Scope, Reference>>, Scope> : [Step] extends [{
171
+ readonly change: infer Reference;
172
+ }] ? ChangeStep<Step, NonNullable<StepReferenceType<Scope, Reference>>, Scope> : [Step] extends [
173
+ {
174
+ readonly build: abstract new (value: infer Input) => infer Built;
175
+ }
176
+ ] ? ValueStep<Step, "values", Input, Built, Scope> : [Step] extends [{
177
+ readonly call: infer Name;
178
+ }] ? ValueStep<Step, "input", UseCaseInput<UseCaseOf<Dependencies, Name>>, UseCaseResponse<UseCaseOf<Dependencies, Name>>, Scope> : [Step] extends [{
179
+ readonly create: infer Repository;
180
+ }] ? WriteStep<Step, MemberOf<Dependencies, Repository, "create">, Scope> : [Step] extends [{
181
+ readonly update: infer Repository;
182
+ }] ? WriteStep<Step, MemberOf<Dependencies, Repository, "update">, Scope> : [Step] extends [{
183
+ readonly delete: infer Repository;
184
+ }] ? WriteStep<Step, MemberOf<Dependencies, Repository, "delete">, Scope> : [Step] extends [{
185
+ readonly restore: infer Repository;
186
+ }] ? WriteStep<Step, MemberOf<Dependencies, Repository, "restore">, Scope> : ValueStep<Step, "value" | "other" | "details" | "return", unknown, unknown, Scope>;
187
+ type WriteStep<Step, Method, Scope> = ValueStep<Step, "value", FirstParameter<Method>, ResultOf<Method>, Scope>;
188
+ /** The scope after `Step`: its result bound to its `as`, or a value it ensures exists. */
189
+ type ScopeAfter<Step, Result, Scope> = [Step] extends [
190
+ {
191
+ readonly as: infer Name extends string;
192
+ }
193
+ ] ? Scope & {
194
+ readonly [Key in Name]: Result;
195
+ } : [Step] extends [
196
+ {
197
+ readonly ensure: "exists";
198
+ readonly value: `$${infer Name}`;
199
+ }
200
+ ] ? Name extends keyof Scope ? Scope & {
201
+ readonly [Key in Name]: NonNullable<Scope[Name]>;
202
+ } : Scope : Scope;
203
+ /** The steps folded in order, each checked against the scope the earlier ones built. */
204
+ type CheckSteps<Steps extends readonly unknown[], Dependencies, Scope, Checked extends readonly unknown[] = []> = [Steps] extends [readonly [infer Step, ...infer Rest]] ? CheckStep<Step, Dependencies, Scope> extends [infer Current, infer Result] ? CheckSteps<Rest, Dependencies, ScopeAfter<Step, Result, Scope>, [
205
+ ...Checked,
206
+ Current
207
+ ]> : never : readonly [...Checked];
208
+ /**
209
+ * What `useCaseOf` takes as `steps`: the steps, inferred as a tuple, each
210
+ * checked against the values in scope. Steps that are not a tuple (built at
211
+ * run time) are checked only when the use case is declared.
212
+ */
213
+ export type CheckedSteps<Steps, Input extends IBlueprintCollection, Context extends IBlueprintCollection | undefined, Dependencies> = [Steps] extends [readonly [unknown, ...unknown[]]] ? CheckSteps<Steps, Dependencies, StepsInitialScope<Input, Context, Dependencies>> : Steps;
214
+ export {};
@@ -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,66 @@
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
+ /** Not readable by scripts of the page. `true` by default. */
10
+ readonly httpOnly?: boolean;
11
+ /** Sent over HTTPS only. `true` by default. */
12
+ readonly secure?: boolean;
13
+ /** `"lax"` by default. */
14
+ readonly sameSite?: "strict" | "lax" | "none";
15
+ /** `"/"` by default. */
16
+ readonly path?: string;
17
+ readonly domain?: string;
18
+ /** The seconds the cookie lives; without it, until the browser closes. */
19
+ readonly maxAge?: number;
20
+ }
21
+ /**
22
+ * Where a use case is served over HTTP, as data: declared with the `route`
23
+ * setting of `useCaseOf` or with `@Route`, and read by the sinks that serve
24
+ * or document HTTP. The blueprint of the use case stays the only description
25
+ * of its input: a route only says where each key comes from.
26
+ */
27
+ export interface RouteDeclaration {
28
+ readonly method: RouteMethod;
29
+ /**
30
+ * The path, after the `basePath` of the sink, e.g. `"/posts/{slug}"`. A
31
+ * `{name}` segment fills the scalar input key `name`. Without it, the path
32
+ * is the default of the sink: `/{kebab(command)}/{kebab(operation)}`.
33
+ */
34
+ readonly path?: string;
35
+ /** `200` by default. */
36
+ readonly status?: RouteSuccessStatus;
37
+ /**
38
+ * Scalar input keys read from the query string. A `get` or `delete` route
39
+ * reads from the query every key that has no other place, so this is for
40
+ * the routes with a body.
41
+ */
42
+ readonly query?: readonly string[];
43
+ /** Scalar input keys read from request headers: `{ locale: "accept-language" }`. */
44
+ readonly headers?: {
45
+ readonly [inputKey: string]: string;
46
+ };
47
+ /** Scalar input keys read from cookies: `{ session: "sid" }`. */
48
+ readonly cookie?: {
49
+ readonly [inputKey: string]: string;
50
+ };
51
+ /**
52
+ * Scalar keys of the response sent as cookies instead of in the body:
53
+ * `{ token: { name: "sid" } }`. An empty text removes the cookie, and an
54
+ * absent value sets none.
55
+ */
56
+ readonly setCookie?: {
57
+ readonly [responseKey: string]: RouteCookie;
58
+ };
59
+ }
60
+ /**
61
+ * What the `route` setting and `@Route` take: a {@link RouteDeclaration}
62
+ * whose method may be written in upper case.
63
+ */
64
+ export interface RouteSettings extends Omit<RouteDeclaration, "method"> {
65
+ readonly method: RouteMethod | Uppercase<RouteMethod>;
66
+ }
@@ -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[];
@@ -81,6 +88,22 @@ export interface UseCaseSettings<Input extends IBlueprintCollection, Output exte
81
88
  readonly deprecated?: boolean;
82
89
  /** The exceptions (made by `exceptionOf`) a caller should expect; documentation only. */
83
90
  readonly errors?: readonly AnyExceptionClass[];
91
+ /**
92
+ * Where the use case is served over HTTP: method, path and where each
93
+ * input key is read from (see `RouteDeclaration`). A decorated class says
94
+ * the same with `@Route`. Read by the sinks that serve or document HTTP;
95
+ * the `route` option of a sink overrides the method, path and status.
96
+ */
97
+ readonly route?: RouteSettings;
98
+ /**
99
+ * Who may call the use case through an output: anyone (`{ public: true }`),
100
+ * or an authenticated caller with these `scopes`, that is the `owner`
101
+ * named by an input key, and whose values fill the `context` (see
102
+ * `AccessDeclaration`). A decorated class says the same with `@Access`.
103
+ * Enforced before the use case runs; a call made in process, with
104
+ * `execute` or `command.run`, is trusted and not checked.
105
+ */
106
+ readonly access?: AccessDeclaration;
84
107
  }
85
108
  /** `UseCaseSettings` with the `steps` that are its `execute`. */
86
109
  export interface UseCaseStepsSettings<Input extends IBlueprintCollection, Output extends IBlueprintCollection, Dependencies extends UseCaseCollection, Context extends IBlueprintCollection | undefined = undefined> extends UseCaseSettings<Input, Output, Dependencies, Context> {
@@ -89,6 +112,30 @@ export interface UseCaseStepsSettings<Input extends IBlueprintCollection, Output
89
112
  * interpreted by its `execute`, and described by `describe()`.
90
113
  */
91
114
  readonly steps: readonly UseCaseStep<NoInfer<Dependencies>>[];
115
+ /** Checks the references and values of the steps at compile time (see `CheckedSteps`). */
116
+ readonly strictSteps?: boolean;
117
+ }
118
+ /**
119
+ * What `useCaseOf` takes: `UseCaseSettings`, and the `steps` that make the
120
+ * class concrete, if any.
121
+ */
122
+ 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> {
123
+ /**
124
+ * What the use case does, as data (see `UseCaseStep`), which makes the
125
+ * class concrete: validated here, interpreted by its `execute`, and
126
+ * described by `describe()`. TypeScript checks the kinds of the steps and
127
+ * the names of dependencies and actions; with `strictSteps`, also their
128
+ * references and values (see `CheckedSteps`).
129
+ */
130
+ readonly steps?: [Strict] extends [true] ? CheckedSteps<Steps, NoInfer<Input>, NoInfer<Context>, NoInfer<Dependencies>> : Steps;
131
+ /**
132
+ * `true` checks the steps, written inline, at compile time: each reference
133
+ * must name a value in scope (the input, the context, an environment or the
134
+ * result of an earlier step, at any depth), and the `args`, `values`,
135
+ * `value` and `input` of a step must fit what it calls or builds. It costs
136
+ * the editor more than the default checks. Without effect at run time.
137
+ */
138
+ readonly strictSteps?: Strict;
92
139
  }
93
140
  export declare const InvalidUseCaseDeclaration: DeclaredExceptionClass<{
94
141
  reason: string;
@@ -119,6 +166,10 @@ type UseCaseStatics<Input extends IBlueprintCollection, Output extends IBlueprin
119
166
  readonly deprecated: boolean;
120
167
  /** The exceptions a caller should expect. */
121
168
  readonly errors: readonly AnyExceptionClass[];
169
+ /** Where it is served over HTTP, from the `route` setting or `@Route`. */
170
+ readonly route: RouteDeclaration | undefined;
171
+ /** Who may call it, from the `access` setting or `@Access`. */
172
+ readonly access: AccessDeclaration | undefined;
122
173
  };
123
174
  /** What a use case instance carries beyond `AbstractUseCase`. */
124
175
  type UseCaseInstanceContext<Context extends IBlueprintCollection | undefined> = {
@@ -138,8 +189,7 @@ export type StepsUseCaseConstructor<Input extends IBlueprintCollection, Output e
138
189
  * interprets them. Without, it is abstract: extend it and implement an async
139
190
  * execute method.
140
191
  */
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>;
192
+ 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>;
143
193
  export declare function isUseCaseClass(value: unknown): value is AnyUseCaseClass;
144
194
  export declare function isUseCase(value: unknown): value is AbstractUseCase<IBlueprintCollection, IBlueprintCollection, UseCaseCollection>;
145
195
  export {};
@@ -1,6 +1,18 @@
1
1
  /**
2
2
  * `@afora/core/experimental`: features that may change in any release. Not
3
- * covered by the compatibility policy in `VERSIONING.md`.
3
+ * covered by the compatibility policy in `VERSIONING.md`. See
4
+ * `docs/api-tiers.md`.
4
5
  */
6
+ export { InvalidJwtOptions, jwtOf } from "./core/auth/jwt.js";
7
+ export type { DescribedJwtOptions, Jwt, JwtAlgorithm, JwtOptions, JwtSubject, JwtTokenOptions, } from "./core/auth/types/jwt.interface.js";
5
8
  export { sliceOf, sliceValue } from "./core/blueprint/slice.js";
6
9
  export type { SlicedBlueprint, SlicedValue, SliceMask, ValueMask, } from "./core/blueprint/types/slice.interface.js";
10
+ export { eventPolicyCatalogOf, eventPolicyDescriptionOf, } from "./core/event-policy/event-policy.js";
11
+ export { stageDescriptionOf } from "./core/output/stage.js";
12
+ export { type PropertyLineage, propertyLineageOf, } from "./core/property/lineage.js";
13
+ export { providerDescriptionOf } from "./core/repository/provider.js";
14
+ export { matchesQuery, prepareQuery, sameValue, selectRows, } from "./core/repository/query.js";
15
+ export { useCaseOriginOf } from "./core/use-case/plugin.js";
16
+ export { useCaseTargetOf } from "./core/use-case/target.js";
17
+ export type { UseCaseOriginInfo } from "./core/use-case/types/plugin.interface.js";
18
+ export type { StdTokenOptions } from "./std/use-cases/types.js";