@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
@@ -1,11 +1,14 @@
1
+ import type { TString } from "typebox";
1
2
  import type { BlueprintField, BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection, JoinedProperties } from "../../blueprint/types/blueprint.interface.js";
2
3
  import type { ParseResult } from "../../parse/types/parse.interface.js";
4
+ import type { PropertyClass } from "../../property/property.js";
3
5
  import type { AnyRecordPlugin, IRecordPluginCatalog, PluginMembers, PluginSensitiveKey, PluginUniqueKey, WithPluginProperties } from "../../record/types/plugin.interface.js";
4
6
  import type { MarkedUniqueKey, RecordChanges, RecordKey, RecordOptions, RecordSettings } from "../../record/types/record.interface.js";
5
7
  import type { StandardSchemaV1 } from "../../standard/standard-schema.js";
6
8
  import type { Kind } from "../../symbols/kind.js";
7
9
  import type { AbstractEntity, EntityUniqueKeys } from "../abstract-entity.js";
8
10
  import type { EntityClassBrand, EntityEvents } from "../event.js";
11
+ import type { EntityVersion } from "../version.js";
9
12
  /** The keys every entity has, which its blueprint cannot declare. */
10
13
  export type EntityIdentityKey = "id" | "createdAt" | "updatedAt" | "deletedAt";
11
14
  /**
@@ -15,6 +18,12 @@ export type EntityIdentityKey = "id" | "createdAt" | "updatedAt" | "deletedAt";
15
18
  export type IEntityIdentity = {
16
19
  readonly [Key in EntityIdentityKey]: BlueprintField;
17
20
  };
21
+ /** The identity key a versioned entity adds: `version`, a positive integer. */
22
+ export type IEntityVersion = {
23
+ readonly version: typeof EntityVersion;
24
+ };
25
+ /** `Identity`, with `version` when `Versioned` is `true`. */
26
+ export type VersionedIdentity<Identity extends IEntityIdentity, Versioned extends boolean> = Versioned extends true ? Identity & IEntityVersion : Identity;
18
27
  /** What an entity builder needs: the identity fields, and how to make an id and a timestamp. */
19
28
  export interface EntityBuilderSettings<Identity extends IEntityIdentity, Catalogs extends readonly IRecordPluginCatalog[] = []> {
20
29
  /** The blueprint of `id`, `createdAt`, `updatedAt` and `deletedAt`. */
@@ -23,6 +32,11 @@ export interface EntityBuilderSettings<Identity extends IEntityIdentity, Catalog
23
32
  readonly generateId: () => string;
24
33
  /** The current date and time, as the timestamps accept it. */
25
34
  readonly now: () => string;
35
+ /**
36
+ * The field of `expiresAt`, a date and time, which entities declared with
37
+ * `expires: { after }` get; without it such a declaration throws.
38
+ */
39
+ readonly expiresAt?: ExpiresAtField;
26
40
  /**
27
41
  * Catalogs of plugins, made by `recordPluginCatalogOf`, that
28
42
  * `options.plugins` picks from, as `(ctx) => [ctx.publishable]`.
@@ -45,7 +59,85 @@ export type EntityProperties<Identity extends IEntityIdentity, Properties extend
45
59
  */
46
60
  export type EntityInput<Identity extends IEntityIdentity, Properties extends IBlueprintCollection> = BlueprintInput<Properties> & Partial<BlueprintInput<Identity>>;
47
61
  /** What `entityOf` takes, after the blueprint, to describe its keys. */
48
- export type EntityOptions<Properties extends IBlueprintCollection, Change extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Unique extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Plugins extends readonly AnyRecordPlugin[] = readonly AnyRecordPlugin[], Context = unknown, Sensitive extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[]> = RecordOptions<Properties, Change, Unique, Plugins, Context, Sensitive> & EntityEvents;
62
+ export type EntityOptions<Properties extends IBlueprintCollection, Change extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Unique extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Plugins extends readonly AnyRecordPlugin[] = readonly AnyRecordPlugin[], Context = unknown, Sensitive extends readonly RecordKey<Properties>[] = readonly RecordKey<Properties>[], Expires extends EntityExpires<RecordKey<Properties>> | undefined = EntityExpires<RecordKey<Properties>> | undefined> = RecordOptions<Properties, Change, Unique, Plugins, Context, Sensitive> & EntityEvents & EntityStorageOptions<RecordKey<Properties>, Expires>;
63
+ /** How an entity is stored: optimistic concurrency and expiry. */
64
+ export interface EntityStorageOptions<Key extends string = string, Expires extends EntityExpires<Key> | undefined = EntityExpires<Key> | undefined> {
65
+ /**
66
+ * Adds a `version` identity key (1 when created) that the `update` of its
67
+ * repository checks and increments, throwing `RepositoryConcurrencyConflict`
68
+ * when the stored one differs: optimistic concurrency.
69
+ */
70
+ readonly versioned?: boolean;
71
+ /**
72
+ * When records of the entity expire, which reads then leave out:
73
+ * `{ after: { days: 400 } }` from `createdAt`, kept in an `expiresAt`
74
+ * identity key, or `{ at: "until" }`, the date held by a key of the
75
+ * blueprint. A record with no date never expires.
76
+ */
77
+ readonly expires?: Expires;
78
+ /**
79
+ * The version of the stored shape of the entity, 1 when absent. When set,
80
+ * every write stores it in each row under `SCHEMA_VERSION_KEY`, and reads
81
+ * bring older rows up to it with `upgrade` before parsing them.
82
+ */
83
+ readonly schemaVersion?: number;
84
+ /**
85
+ * The steps that bring a stored row from the version before each key up
86
+ * to that key: `{ 2: (data) => ({ ...data, slug: data.path }) }`. Each
87
+ * gets the raw stored data, without the version key.
88
+ */
89
+ readonly upgrade?: EntityUpgrades;
90
+ /**
91
+ * The names the entity had before (its `name` option), so a backfill
92
+ * (`app.migrate`) moves their rows to the current name, and `start()`
93
+ * accepts the rename.
94
+ */
95
+ readonly previousNames?: readonly string[];
96
+ }
97
+ /** A step of `upgrade`: the stored data of a version, as the next one. */
98
+ export type EntityUpgrade = (data: Readonly<Record<string, unknown>>) => Record<string, unknown>;
99
+ /** The `upgrade` steps of an entity, keyed by the version each one reaches. */
100
+ export interface EntityUpgrades {
101
+ readonly [version: number]: EntityUpgrade;
102
+ }
103
+ /** A duration, e.g. `{ days: 400 }`. */
104
+ export interface EntityDuration {
105
+ readonly days?: number;
106
+ readonly hours?: number;
107
+ readonly minutes?: number;
108
+ readonly seconds?: number;
109
+ }
110
+ /**
111
+ * `after` a duration from `createdAt` (stored as `expiresAt`), or `at` the
112
+ * date held by a key of the blueprint, `Key`.
113
+ */
114
+ export type EntityExpires<Key extends string = string> = {
115
+ readonly after: EntityDuration;
116
+ } | {
117
+ readonly at: Key;
118
+ };
119
+ /**
120
+ * The field `expiresAt` of the entities that expire `after` a duration: a
121
+ * required date and time, as `createdAt`.
122
+ */
123
+ export type ExpiresAtField = PropertyClass<TString, string>;
124
+ /** `Identity`, with `expiresAt` when `Expires` is `{ after }`. */
125
+ export type ExpiringIdentity<Identity extends IEntityIdentity, Expires> = Expires extends {
126
+ readonly after: EntityDuration;
127
+ } ? Identity & {
128
+ readonly expiresAt: ExpiresAtField;
129
+ } : Identity;
130
+ /** What `entityOf` stores under `EntitySettings`. */
131
+ export interface EntityStorageSettings {
132
+ readonly versioned: boolean;
133
+ readonly expires?: EntityExpires;
134
+ /** Whether `schemaVersion` was declared, so rows carry the version key. */
135
+ readonly schemaVersioned: boolean;
136
+ /** `schemaVersion`, 1 when absent. */
137
+ readonly schemaVersion: number;
138
+ readonly upgrade: EntityUpgrades;
139
+ readonly previousNames: readonly string[];
140
+ }
49
141
  /**
50
142
  * The unique keys of an entity: `id`, those listed in `options.unique`, and
51
143
  * those holding a property declared `unique`, as `Slug`.
@@ -86,4 +178,6 @@ export type EntityConstructor<Identity extends IEntityIdentity, Properties exten
86
178
  * What `entityBuilderOf` returns: `entityOf`, declaring entities with the
87
179
  * identity it was given, whose `options.plugins` picks from `Context`.
88
180
  */
89
- export type EntityBuilder<Identity extends IEntityIdentity, Context = Record<never, never>> = <Properties extends IBlueprintCollection & Partial<Record<EntityIdentityKey, never>>, const Change extends readonly RecordKey<Properties>[] = [], const Unique extends readonly RecordKey<Properties>[] = [], const Plugins extends readonly AnyRecordPlugin[] = [], const Sensitive extends readonly RecordKey<Properties>[] = []>(blueprint: IBlueprint<Properties, boolean>, options?: EntityOptions<NoInfer<Properties>, Change, Unique, Plugins, Context, Sensitive>) => EntityConstructor<Identity, WithPluginProperties<Properties, Plugins>, Change, readonly (Unique[number] | PluginUniqueKey<Plugins>)[], PluginMembers<Plugins>, Sensitive[number] | PluginSensitiveKey<Plugins>>;
181
+ export type EntityBuilder<Identity extends IEntityIdentity, Context = Record<never, never>> = <Properties extends IBlueprintCollection & Partial<Record<EntityIdentityKey, never>>, const Change extends readonly RecordKey<Properties>[] = [], const Unique extends readonly RecordKey<Properties>[] = [], const Plugins extends readonly AnyRecordPlugin[] = [], const Sensitive extends readonly RecordKey<Properties>[] = [], const Versioned extends boolean = false, const Expires extends EntityExpires<RecordKey<Properties>> | undefined = undefined>(blueprint: IBlueprint<Properties, boolean>, options?: EntityOptions<NoInfer<Properties>, Change, Unique, Plugins, Context, Sensitive, Expires> & {
182
+ readonly versioned?: Versioned;
183
+ }) => EntityConstructor<VersionedIdentity<ExpiringIdentity<Identity, Expires>, Versioned>, WithPluginProperties<Properties, Plugins>, Change, readonly (Unique[number] | PluginUniqueKey<Plugins>)[], PluginMembers<Plugins>, Sensitive[number] | PluginSensitiveKey<Plugins>>;
@@ -0,0 +1,13 @@
1
+ import { Type } from "typebox";
2
+ declare const EntityVersion_base: import("../../index.js").PropertyClass<Type.TInteger, number>;
3
+ /**
4
+ * The `version` of a versioned entity: a positive integer, 1 when created,
5
+ * which each `update` of its repository checks and increments.
6
+ */
7
+ export declare class EntityVersion extends EntityVersion_base {
8
+ }
9
+ /** The identity key a versioned entity adds after the others. */
10
+ export declare const VersionIdentity: import("../../index.js").IBlueprint<import("../property/types/property-options.interface.js").StoredFields<{
11
+ version: typeof EntityVersion;
12
+ }>, false>;
13
+ export {};
@@ -0,0 +1,25 @@
1
+ import type { EventPolicyDescription, EventPolicyFactory, EventPolicyOptions, EventPolicySettings, IEventPolicyCatalog } from "./types/event-policy.interface.js";
2
+ /**
3
+ * A factory of event policies for the `events` setting of `commandOf`:
4
+ * `events: publishEvents({ publisher: "realtime", topic: "posts/{id}" })`.
5
+ * Its options are JSON, checked by `validate` when the policy is made (an
6
+ * `InvalidEventPolicy` otherwise), so the policy can be written in an
7
+ * `afora.json` and an application describes it by name and options instead
8
+ * of `{ kind: "opaque" }`. `handle` gets the events of a call that succeeded,
9
+ * what a handler policy gets, and the options.
10
+ */
11
+ export declare function eventPolicyOf<Options extends object = EventPolicyOptions>(settings: EventPolicySettings<Options>): EventPolicyFactory<Options>;
12
+ /**
13
+ * The name and options of a policy made by `eventPolicyOf`; `undefined` for
14
+ * any other value, such as a handler written by hand.
15
+ */
16
+ export declare function eventPolicyDescriptionOf(handler: unknown): EventPolicyDescription | undefined;
17
+ /** The keys of the application `publishers` a policy uses; none for a handler written by hand. @internal */
18
+ export declare function eventPolicyPublishersOf(handler: unknown): readonly string[];
19
+ /**
20
+ * A catalog of event policy factories, by name: what resolves
21
+ * `{ "policy": name, "options": {...} }` back to a policy. Throws
22
+ * `InvalidEventPolicy` for a function that is not a factory made by
23
+ * `eventPolicyOf` or for two with the same name.
24
+ */
25
+ export declare function eventPolicyCatalogOf(...policies: readonly EventPolicyFactory<never>[]): IEventPolicyCatalog;
@@ -0,0 +1,11 @@
1
+ /** `eventPolicyOf`, a policy's options or a catalog got something it cannot use; `details.reason` says what. */
2
+ export declare const InvalidEventPolicy: import("../../index.js").DeclaredExceptionClass<{
3
+ readonly reason: string;
4
+ }>;
5
+ /**
6
+ * An event policy asked for a publisher that neither the application that
7
+ * bound the command nor the started application declares.
8
+ */
9
+ export declare const PublisherNotBound: import("../../index.js").DeclaredExceptionClass<{
10
+ readonly publisher: string;
11
+ }>;
@@ -0,0 +1,17 @@
1
+ import type { PublishEventsOptions } from "./types/event-policy.interface.js";
2
+ /**
3
+ * An event policy that publishes the events of a call to a publisher of the
4
+ * application, once the call succeeded (and committed):
5
+ * `events: publishEvents({ publisher: "realtime", topic: "posts/{id}" })`.
6
+ *
7
+ * For each event, in order, it runs the event's own handler first (unless
8
+ * `handle: false`), then publishes `{ name, command, operation, entity }`
9
+ * to the topic, the entity as JSON with sensitive values masked; the context
10
+ * and the variables are never published. `events` limits it to some names.
11
+ * A handler that fails stops the call with `CommandEventFailed`, as with
12
+ * `"after-commit"`. A publish that fails is logged and skipped
13
+ * (`onPublishError: "log"`, the default) or fails the call (`"throw"`). A
14
+ * publisher the application does not declare fails the call, caused by
15
+ * `PublisherNotBound`.
16
+ */
17
+ export declare const publishEvents: import("./types/event-policy.interface.js").EventPolicyFactory<PublishEventsOptions>;
@@ -0,0 +1,18 @@
1
+ import type { IEventPublisher } from "./types/event-policy.interface.js";
2
+ /** The publishers of an application, by key. */
3
+ export type Publishers = Readonly<Record<string, IEventPublisher>>;
4
+ /**
5
+ * Carries the publishers of an application, when it declares any: on the
6
+ * application itself. @internal
7
+ */
8
+ export declare const ApplicationPublishersKey: unique symbol;
9
+ /** Gives `command` the publishers of the application that bound it. @internal */
10
+ export declare function bindPublishers(command: object, publishers: Publishers): void;
11
+ /** Whether `value` can publish: an object with a `publish` function. @internal */
12
+ export declare function isEventPublisher(value: unknown): value is IEventPublisher;
13
+ /**
14
+ * The publisher `name` for a call of `command`: one of the application that
15
+ * bound the command, else one of the started application. Throws
16
+ * `PublisherNotBound` when neither declares it. @internal
17
+ */
18
+ export declare function resolvePublisher(command: object, name: string, source: string): IEventPublisher;
@@ -0,0 +1,72 @@
1
+ import type { CommandEventHandler, CommandEventInfo } from "../../command/types/command.interface.js";
2
+ import type { JsonValue } from "../../json.js";
3
+ import type { Kind } from "../../symbols/kind.js";
4
+ /** The options of an event policy: JSON, so it can live in an `afora.json`. */
5
+ export type EventPolicyOptions = {
6
+ readonly [key: string]: JsonValue;
7
+ };
8
+ /** What `eventPolicyOf` takes. */
9
+ export interface EventPolicySettings<Options extends object = EventPolicyOptions> {
10
+ /** The name the policy is known by in a catalog and in `afora.json`. */
11
+ readonly name: string;
12
+ /** Why `options` cannot be used, or `undefined` when they can. */
13
+ readonly validate?: (options: Options) => string | undefined;
14
+ /** The handler a command awaits with the events of a call that succeeded. */
15
+ readonly handle: (events: Parameters<CommandEventHandler>[0], info: CommandEventInfo, options: Options) => void | Promise<void>;
16
+ /** The keys of the application `publishers` the policy uses, so an application checks them when declared. */
17
+ readonly publishers?: (options: Options) => readonly string[];
18
+ }
19
+ /**
20
+ * Makes an event policy for the `events` setting of `commandOf`: a
21
+ * `CommandEventHandler` that remembers its name and options.
22
+ */
23
+ export type EventPolicyFactory<Options extends object = EventPolicyOptions> = (options?: Options) => CommandEventHandler;
24
+ /** How an event policy made by a factory is described: by name and options. */
25
+ export interface EventPolicyDescription {
26
+ readonly kind: "policy";
27
+ readonly name: string;
28
+ readonly options: EventPolicyOptions;
29
+ }
30
+ /** Event policy factories by name: what resolves `{ "policy": name, "options": {...} }` of an `afora.json`. */
31
+ export interface IEventPolicyCatalog {
32
+ readonly [Kind]: "event-policy-catalog";
33
+ readonly policies: Readonly<Record<string, EventPolicyFactory<never>>>;
34
+ /** Makes the policy `name` with `options`; throws `InvalidEventPolicy` for an unknown name or invalid options. */
35
+ resolve(name: string, options?: EventPolicyOptions): CommandEventHandler;
36
+ }
37
+ /** An event as a publisher receives it: JSON, the entity masked by its `toJSON`. */
38
+ export interface PublishedEvent {
39
+ /** The name given to `eventOf`. */
40
+ readonly name: string;
41
+ readonly command: string;
42
+ readonly operation: string;
43
+ /** The entity as JSON: sensitive values are masked. */
44
+ readonly entity: JsonValue;
45
+ }
46
+ /**
47
+ * Where published events go: a WebSocket hub, a queue, a broker. Declared
48
+ * under a key in `applicationOf({ publishers })`.
49
+ */
50
+ export interface IEventPublisher {
51
+ publish(topic: string, event: PublishedEvent): void | Promise<void>;
52
+ }
53
+ /** The options of the `publishEvents` policy. */
54
+ export interface PublishEventsOptions {
55
+ /** The key of the publisher in `applicationOf({ publishers })`. */
56
+ readonly publisher: string;
57
+ /**
58
+ * The topic of each event. `{event}`, `{command}`, `{operation}` and
59
+ * `{id}` (the id of the entity) are replaced: `"posts/{id}"`.
60
+ */
61
+ readonly topic: string;
62
+ /** The names of the events to publish; all of them by default. */
63
+ readonly events?: readonly string[];
64
+ /** Whether each event runs its own handler first, as `"after-commit"` does. `true` by default. */
65
+ readonly handle?: boolean;
66
+ /**
67
+ * What a failed publish does: `"log"` (default) writes an error to the
68
+ * logger and goes on, since the call already committed; `"throw"` makes
69
+ * the call fail with `CommandEventFailed`.
70
+ */
71
+ readonly onPublishError?: "log" | "throw";
72
+ }
@@ -0,0 +1,16 @@
1
+ import type { AnyBlueprint } from "../blueprint/types/blueprint.interface.js";
2
+ import type { AccessDeclaration } from "../use-case/types/access.interface.js";
3
+ import type { OperationCallOptions } from "./types/output.interface.js";
4
+ /** What `command.run` takes: the call options without the caller. */
5
+ type RunOptions = {
6
+ readonly context?: unknown;
7
+ } | undefined;
8
+ /**
9
+ * The check of `access` for the operation `id`, made once: given the input
10
+ * and the options of a call, it throws `Unauthenticated` or `Forbidden`, or
11
+ * gives the options to run the command with (the context filled from the
12
+ * caller). Throws `InvalidOutputStage` when `access.context` names a key the
13
+ * `context` of the operation does not have.
14
+ */
15
+ export declare function accessGuardOf(id: string, access: AccessDeclaration, context: AnyBlueprint | undefined): (input: unknown, options: OperationCallOptions | undefined) => RunOptions;
16
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { AnyBlueprint } from "../blueprint/types/blueprint.interface.js";
2
+ /**
3
+ * Text values (a query string, headers, the values of a session) as the
4
+ * scalar fields of `blueprint` take them: a number or a boolean where the
5
+ * field is one rather than a string, else as is, so the blueprint rejects
6
+ * invalid text with its own issue. `undefined` values are left out. For
7
+ * sinks, which read text from their transport.
8
+ */
9
+ export declare function coerceTextValues(blueprint: AnyBlueprint, texts: Readonly<Record<string, string | undefined>>): Record<string, unknown>;
@@ -6,3 +6,19 @@ export declare const InvalidOutputStage: import("../../index.js").DeclaredExcept
6
6
  export declare const DuplicateOperation: import("../../index.js").DeclaredExceptionClass<{
7
7
  readonly id: string;
8
8
  }>;
9
+ /**
10
+ * An operation that declares its `access` was called without an
11
+ * authenticated caller: no credentials, or ones that were not accepted. A
12
+ * 401.
13
+ */
14
+ export declare const Unauthenticated: import("../../index.js").DeclaredExceptionClass<undefined>;
15
+ /**
16
+ * The caller is authenticated but may not call the operation: it lacks a
17
+ * scope the `access` of the operation asks for (`details.reason` is
18
+ * `"scope"`, with the missing `scopes`), or is not the owner (`"owner"`). A
19
+ * 403.
20
+ */
21
+ export declare const Forbidden: import("../../index.js").DeclaredExceptionClass<{
22
+ readonly reason: "scope" | "owner";
23
+ readonly scopes?: readonly string[];
24
+ }>;
@@ -0,0 +1,36 @@
1
+ import type { CompileRoutesSettings, HttpOptions, RouteTable } from "./types/http.interface.js";
2
+ import type { OperationDescriptor, StageContext } from "./types/output.interface.js";
3
+ /** The option names of {@link HttpOptions}, for sinks that list their options. */
4
+ export declare const HTTP_OPTION_KEYS: readonly ["basePath", "route", "responses", "requestId", "security", "docs", "debug", "context", "auth"];
5
+ /**
6
+ * Why the {@link HttpOptions} are not valid, naming them `where.<option>`;
7
+ * `undefined` when they are. Other keys of `options` are not looked at.
8
+ */
9
+ export declare function httpOptionsIssue(options: HttpOptions, where: string): string | undefined;
10
+ /**
11
+ * The routes of `operations`, compiled once, for a sink that serves HTTP on
12
+ * any server: the sink routes each `method` and `path` to `handle` and sends
13
+ * the result as is. Everything a request needs is decided here, so a call
14
+ * only reads, coerces and runs:
15
+ *
16
+ * - The input is the JSON body of a `post`, `put` or `patch` and the query
17
+ * of a `get` or `delete`, with the keys the route reads elsewhere over it:
18
+ * `{name}` segments of the path, and the query keys, headers and cookies
19
+ * the use case route declares. Text is coerced to the numbers and booleans
20
+ * the input takes; the operation validates it (a 400 problem with `issues`).
21
+ * - The context comes from `context.headers` (coerced) and
22
+ * `context.variables` (as is); a missing one leaves the key out.
23
+ * - An operation with an `access` other than `public` asks
24
+ * `settings.authenticator` who calls; without one, or without credentials,
25
+ * it is a 401.
26
+ * - Success is the route status with the response as JSON (sensitive values
27
+ * masked by `toJSON`), or no body for `204`, plus the `responses` headers.
28
+ * Errors are `application/problem+json`, with the status of `statusOf`
29
+ * (the output `statuses` first). `requestId` puts the request id on every
30
+ * result, `notFound` and `failure` included.
31
+ * - `docs.path` serves the `openapi` document of the same operations.
32
+ *
33
+ * Throws `InvalidOutputStage` (source `settings.source`) as `operationRoutes`
34
+ * does, and when `docs.path` is taken by a `get` route.
35
+ */
36
+ export declare function compileRoutes(operations: readonly OperationDescriptor[], options: HttpOptions, context: StageContext, settings: CompileRoutesSettings): RouteTable;
@@ -1,10 +1,96 @@
1
1
  import type { OpenAPISchema } from "../openapi/types/openapi.interface.js";
2
+ import type { RouteCookie } from "../use-case/types/route.interface.js";
2
3
  import type { OperationDescriptor } from "./types/output.interface.js";
3
4
  declare const METHODS: readonly ["get", "post", "put", "patch", "delete"];
4
5
  /** An HTTP method an operation can be routed to. */
5
6
  export type OpenAPIMethod = (typeof METHODS)[number];
7
+ /** The status of a successful call of a route; `204` has no body. */
8
+ export type RouteStatus = 200 | 201 | 202 | 204;
9
+ /**
10
+ * A security scheme of the document (`components.securitySchemes`), as
11
+ * OpenAPI 3.1 writes it.
12
+ */
13
+ export type OpenAPISecurityScheme = {
14
+ readonly type: "http";
15
+ /** `"bearer"`, `"basic"`... */
16
+ readonly scheme: string;
17
+ readonly bearerFormat?: string;
18
+ readonly description?: string;
19
+ } | {
20
+ readonly type: "apiKey";
21
+ readonly name: string;
22
+ readonly in: "header" | "query" | "cookie";
23
+ readonly description?: string;
24
+ } | {
25
+ readonly type: "oauth2";
26
+ readonly flows: {
27
+ readonly [flow: string]: unknown;
28
+ };
29
+ readonly description?: string;
30
+ } | {
31
+ readonly type: "openIdConnect";
32
+ readonly openIdConnectUrl: string;
33
+ readonly description?: string;
34
+ } | {
35
+ readonly type: "mutualTLS";
36
+ readonly description?: string;
37
+ };
38
+ /** The scheme an operation needs, and its scopes (none by default). */
39
+ export interface SecurityRequirement {
40
+ readonly scheme: string;
41
+ readonly scopes?: readonly string[];
42
+ }
43
+ /**
44
+ * The options that route operations: shared by the `openapi` sink and the
45
+ * sinks that serve HTTP, so the served routes and the document agree.
46
+ */
47
+ export interface RouteOptions {
48
+ /** Prefix of every path, e.g. `"/api"`. Empty by default. */
49
+ readonly basePath?: string;
50
+ /**
51
+ * Method, path and success status by operation id, e.g.
52
+ * `{ "Entries.get": { method: "get", path: "/entries/{slug}" } }`. A
53
+ * `{name}` segment of the path fills the scalar input key `name`.
54
+ */
55
+ readonly route?: {
56
+ readonly [operationId: string]: {
57
+ readonly method?: OpenAPIMethod;
58
+ readonly path?: string;
59
+ /** `200` by default. */
60
+ readonly status?: RouteStatus;
61
+ };
62
+ };
63
+ /** Static headers of the successful responses, by operation id. */
64
+ readonly responses?: {
65
+ readonly [operationId: string]: {
66
+ readonly headers?: {
67
+ readonly [name: string]: string;
68
+ };
69
+ };
70
+ };
71
+ /**
72
+ * Every response carries a request id: the one the request sent in this
73
+ * header, else a new UUID. `true` is `"x-request-id"`.
74
+ */
75
+ readonly requestId?: true | string;
76
+ /**
77
+ * Security schemes and the one each operation needs, for the document
78
+ * (`components.securitySchemes` and `security`). `default` applies to
79
+ * every operation not in `operations`; `false` there marks one public.
80
+ * Sinks that serve HTTP do not enforce it.
81
+ */
82
+ readonly security?: {
83
+ readonly schemes: {
84
+ readonly [name: string]: OpenAPISecurityScheme;
85
+ };
86
+ readonly operations?: {
87
+ readonly [operationId: string]: SecurityRequirement | false;
88
+ };
89
+ readonly default?: SecurityRequirement;
90
+ };
91
+ }
6
92
  /** The options of the `openapi` sink. */
7
- export interface OpenAPISinkOptions {
93
+ export interface OpenAPISinkOptions extends RouteOptions {
8
94
  readonly info: {
9
95
  readonly title: string;
10
96
  readonly version: string;
@@ -14,21 +100,15 @@ export interface OpenAPISinkOptions {
14
100
  readonly url: string;
15
101
  readonly description?: string;
16
102
  }[];
17
- /** Prefix of every path, e.g. `"/api"`. Empty by default. */
18
- readonly basePath?: string;
19
- /** Method and path by operation id, e.g. `{ "Users.login": { path: "/session" } }`. */
20
- readonly route?: {
21
- readonly [operationId: string]: {
22
- readonly method?: OpenAPIMethod;
23
- readonly path?: string;
24
- };
25
- };
26
103
  }
27
104
  /** An OpenAPI 3.1 document. */
28
105
  export interface OpenAPIDocument {
29
106
  readonly openapi: "3.1.0";
30
107
  readonly info: OpenAPISinkOptions["info"];
31
108
  readonly servers?: OpenAPISinkOptions["servers"];
109
+ readonly security?: readonly {
110
+ readonly [scheme: string]: string[];
111
+ }[];
32
112
  readonly paths: {
33
113
  readonly [path: string]: Record<string, unknown>;
34
114
  };
@@ -36,30 +116,65 @@ export interface OpenAPIDocument {
36
116
  readonly schemas: {
37
117
  readonly [name: string]: OpenAPISchema;
38
118
  };
119
+ readonly securitySchemes?: {
120
+ readonly [name: string]: OpenAPISecurityScheme;
121
+ };
39
122
  };
40
123
  }
41
- /** The options that route operations: shared by the sinks that serve HTTP. */
42
- export interface RouteOptions {
43
- readonly basePath?: string;
44
- readonly route?: OpenAPISinkOptions["route"];
45
- }
46
- /** Where an operation is served. */
124
+ /** The option names of {@link RouteOptions}, for sinks that list their options. */
125
+ export declare const ROUTE_OPTION_KEYS: readonly ["basePath", "route", "responses", "requestId", "security"];
126
+ /**
127
+ * Why the {@link RouteOptions} (`basePath`, `route`, `responses`,
128
+ * `requestId`, `security`) are not valid, naming them `where.<option>`;
129
+ * `undefined` when they are. Other keys of `options` are not looked at.
130
+ */
131
+ export declare function routeOptionsIssue(options: RouteOptions, where: string): string | undefined;
132
+ /**
133
+ * The header that carries the request id with these options (`"x-request-id"`
134
+ * for `requestId: true`), or `undefined` without `requestId`.
135
+ */
136
+ export declare function requestIdHeaderOf(options: RouteOptions): string | undefined;
137
+ /** Where an operation is served, and how it answers. */
47
138
  export interface OperationRoute {
48
139
  readonly method: OpenAPIMethod;
49
- /** `basePath` included. */
140
+ /** `basePath` included, parameters as `{name}`. */
50
141
  readonly path: string;
142
+ /** The input keys the `{name}` segments of `path` fill, in order. */
143
+ readonly parameters: readonly string[];
144
+ /** The status of a success: `200` unless `route` says otherwise. */
145
+ readonly status: RouteStatus;
146
+ /** The static headers of a success, from `responses`. */
147
+ readonly headers: {
148
+ readonly [name: string]: string;
149
+ };
150
+ /**
151
+ * Whether the input keys with no other place are a JSON body (`post`,
152
+ * `put`, `patch`); otherwise they are read from the query.
153
+ */
154
+ readonly body: boolean;
155
+ /** The input keys the use case route reads from the query, whatever the method. */
156
+ readonly query: readonly string[];
157
+ /** The input keys read from request headers, with the header of each (lower case). */
158
+ readonly requestHeaders: {
159
+ readonly [inputKey: string]: string;
160
+ };
161
+ /** The input keys read from cookies, with the cookie of each. */
162
+ readonly cookies: {
163
+ readonly [inputKey: string]: string;
164
+ };
165
+ /** The response keys sent as cookies instead of in the body, with the cookie of each. */
166
+ readonly setCookies: {
167
+ readonly [responseKey: string]: RouteCookie;
168
+ };
51
169
  }
52
170
  /**
53
- * Why `basePath` and `route` are not valid routing options, naming them
54
- * `where.basePath` and `where.route`; `undefined` when they are.
55
- */
56
- export declare function routeOptionsIssue(options: RouteOptions, where: string): string | undefined;
57
- /**
58
- * The route of every operation, by id: `POST {basePath}/{kebab(command)}/{kebab(operation)}`,
59
- * changed per operation id with `route`. Throws `InvalidOutputStage` (with
60
- * `source`) when `route` names an unknown operation or two operations share a
61
- * method and path. The routing of the `openapi` sink and of the sinks that
62
- * serve HTTP.
171
+ * The route of every operation, by id: `POST {basePath}/{kebab(command)}/{kebab(operation)}`
172
+ * with `200`, changed by the route the use case declares (`operation.route`)
173
+ * and, over it, per operation id with `route` and `responses`. Throws
174
+ * `InvalidOutputStage` (with `source`) when `route`, `responses` or
175
+ * `security.operations` name an unknown operation, a path parameter is not a
176
+ * scalar key of the input, or two operations share a method and path. The
177
+ * routing of the `openapi` sink and of the sinks that serve HTTP.
63
178
  */
64
179
  export declare function operationRoutes(operations: readonly OperationDescriptor[], options: RouteOptions, source?: string): ReadonlyMap<string, OperationRoute>;
65
180
  /**
@@ -67,17 +182,23 @@ export declare function operationRoutes(operations: readonly OperationDescriptor
67
182
  *
68
183
  * Routes: `POST {basePath}/{kebab(command)}/{kebab(operation)}`, changed per
69
184
  * operation id with `route`; two operations on one method and path throw
70
- * `InvalidOutputStage`. The `operationId` is the id with its dot as an
71
- * underscore (`Users.register` -> `Users_register`), unique or the build
72
- * throws. `get` and `delete` send the input as query parameters, which needs
73
- * every input field to be a scalar property. Responses: `200` with the
74
- * response schema, `400` and `500` always, and the status of each declared
75
- * error (`statusOf`: `statuses` of the output, else 422). Operations carry no
76
- * repository dependencies, so `404` and `409` appear only when an error that
77
- * maps to them is declared. Records and entities reachable from inputs and
78
- * responses are named by class name in `components.schemas`, beside `Problem`
79
- * (RFC 9457). The `context` of an operation is transport-specific and not
80
- * part of the document; sinks that serve HTTP map it (headers, middleware).
185
+ * `InvalidOutputStage`. A `{name}` segment of a path is a required path
186
+ * parameter, the input key `name` (a scalar), left out of the body or query.
187
+ * The `operationId` is the id with its dot as an underscore (`Users.register`
188
+ * -> `Users_register`), unique or the build throws. `get` and `delete` send
189
+ * the input as query parameters, which needs every input field to be a
190
+ * scalar property. Responses: the `route` status (`200` by default; `204`
191
+ * without content) with the response schema and the `responses` headers,
192
+ * `400` and `500` always, and the status of each declared error (`statusOf`:
193
+ * `statuses` of the output, else 422); `requestId` adds its header to all of
194
+ * them. Operations carry no repository dependencies, so `404` and `409`
195
+ * appear only when an error that maps to them is declared. Records and
196
+ * entities reachable from inputs and responses are named by class name in
197
+ * `components.schemas`, beside `Problem` (RFC 9457). `security` gives
198
+ * `components.securitySchemes`, the document `security` (`default`) and the
199
+ * `security` of each operation. The `context` of an operation is
200
+ * transport-specific and not part of the document; sinks that serve HTTP map
201
+ * it (headers, middleware).
81
202
  */
82
- export declare const openapi: import("./types/output.interface.js").SinkFactory<OpenAPIDocument, OpenAPISinkOptions>;
203
+ export declare const openapi: import("./types/output.interface.js").SinkFactory<OpenAPIDocument, OpenAPISinkOptions, never>;
83
204
  export {};