@afora/core 0.0.8 → 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 (74) hide show
  1. package/README.md +127 -23
  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/types/application.interface.d.ts +37 -0
  8. package/dist/core/auth/jwt.d.ts +30 -0
  9. package/dist/core/auth/types/jwt.interface.d.ts +66 -0
  10. package/dist/core/blueprint/exceptions.d.ts +5 -0
  11. package/dist/core/blueprint/optional.d.ts +3 -1
  12. package/dist/core/blueprint/types/blueprint.interface.d.ts +79 -5
  13. package/dist/core/blueprint/variant.d.ts +32 -0
  14. package/dist/core/entity/abstract-entity.d.ts +5 -0
  15. package/dist/core/entity/entity.d.ts +1 -1
  16. package/dist/core/entity/exceptions.d.ts +9 -0
  17. package/dist/core/entity/trust.d.ts +24 -0
  18. package/dist/core/entity/types/entity.interface.d.ts +30 -0
  19. package/dist/core/output/access.d.ts +16 -0
  20. package/dist/core/output/exceptions.d.ts +16 -0
  21. package/dist/core/output/http.d.ts +36 -0
  22. package/dist/core/output/openapi-sink.d.ts +23 -2
  23. package/dist/core/output/stage.d.ts +7 -2
  24. package/dist/core/output/status.d.ts +1 -1
  25. package/dist/core/output/types/http.interface.d.ts +130 -0
  26. package/dist/core/output/types/output.interface.d.ts +25 -5
  27. package/dist/core/parse/issue.d.ts +28 -3
  28. package/dist/core/parse/parse.d.ts +9 -0
  29. package/dist/core/property/abstract-property.d.ts +9 -0
  30. package/dist/core/property/define-property.d.ts +34 -6
  31. package/dist/core/property/types/property-options.interface.d.ts +10 -0
  32. package/dist/core/property/types/property-type.interface.d.ts +4 -0
  33. package/dist/core/record/abstract-record.d.ts +7 -0
  34. package/dist/core/record/types/record.interface.d.ts +12 -1
  35. package/dist/core/repository/exceptions.d.ts +13 -0
  36. package/dist/core/repository/in-memory.d.ts +2 -2
  37. package/dist/core/repository/migrate.d.ts +20 -0
  38. package/dist/core/repository/state.d.ts +2 -0
  39. package/dist/core/repository/types/repository.interface.d.ts +33 -0
  40. package/dist/core/repository/types/schema.interface.d.ts +126 -0
  41. package/dist/core/repository/upcast.d.ts +64 -0
  42. package/dist/core/schema/schema.d.ts +28 -0
  43. package/dist/core/symbols/kind.d.ts +1 -1
  44. package/dist/core/use-case/access.d.ts +12 -0
  45. package/dist/core/use-case/decorators.d.ts +40 -0
  46. package/dist/core/use-case/route.d.ts +18 -0
  47. package/dist/core/use-case/steps/types/check.interface.d.ts +214 -0
  48. package/dist/core/use-case/types/access.interface.d.ts +39 -0
  49. package/dist/core/use-case/types/route.interface.d.ts +66 -0
  50. package/dist/core/use-case/use-case.d.ts +52 -2
  51. package/dist/experimental.d.ts +13 -1
  52. package/dist/experimental.js +63 -25
  53. package/dist/index-3aae5jfq.js +2997 -0
  54. package/dist/index-hg1z2s2v.js +1055 -0
  55. package/dist/index-ny3as79r.js +4567 -0
  56. package/dist/index-xegv6a1p.js +4291 -0
  57. package/dist/index-y280ew0k.js +396 -0
  58. package/dist/index.d.ts +45 -49
  59. package/dist/index.js +110 -186
  60. package/dist/std/properties/password.d.ts +0 -2
  61. package/dist/std/record-plugins.d.ts +2 -2
  62. package/dist/std/resource.d.ts +31 -0
  63. package/dist/std/runtime.d.ts +6 -0
  64. package/dist/std/use-cases/auth.d.ts +5 -1
  65. package/dist/std/use-cases/index.d.ts +1 -1
  66. package/dist/std/use-cases/shared.d.ts +2 -2
  67. package/dist/std/use-cases/types.d.ts +97 -1
  68. package/dist/testing/http-provider-contract.d.ts +32 -0
  69. package/dist/testing/index.d.ts +2 -0
  70. package/dist/testing/index.js +495 -4
  71. package/dist/testing/provider-contract.d.ts +17 -1
  72. package/package.json +16 -3
  73. package/dist/index-qxfkyd6k.js +0 -1721
  74. package/dist/index-t5qwtyj5.js +0 -8194
@@ -1,6 +1,6 @@
1
1
  import type { OptionalProperty } from "../property/types/optional-property.type.js";
2
2
  import type { AnyPropertyConstructor } from "../property/types/property-collection.type.js";
3
- import type { AnyBlueprint, AnyBlueprintList, AnyRecordClass, BlueprintField, OptionalBlueprint, OptionalBlueprintList, OptionalRecord } from "./types/blueprint.interface.js";
3
+ import type { AnyBlueprint, AnyBlueprintList, AnyBlueprintVariant, AnyRecordClass, BlueprintField, OptionalBlueprint, OptionalBlueprintList, OptionalBlueprintVariant, OptionalRecord } from "./types/blueprint.interface.js";
4
4
  /**
5
5
  * The version of `Property` that accepts `undefined`. Returns `Property` itself
6
6
  * when it is already optional, and the same class for the same `Property`.
@@ -13,6 +13,8 @@ export declare function optionalOf<Property extends AnyPropertyConstructor>(Prop
13
13
  export declare function optionalOf<Blueprint extends AnyBlueprint>(blueprint: Blueprint): OptionalBlueprint<Blueprint>;
14
14
  /** The version of `list` that, as a field of a blueprint, accepts `undefined`. */
15
15
  export declare function optionalOf<List extends AnyBlueprintList>(list: List): OptionalBlueprintList<List>;
16
+ /** The version of `variant` that, as a field of a blueprint, accepts `undefined`. */
17
+ export declare function optionalOf<Variant extends AnyBlueprintVariant>(variant: Variant): OptionalBlueprintVariant<Variant>;
16
18
  /**
17
19
  * The version of `record` that, as a field of a blueprint, accepts `undefined`
18
20
  * or an instance of `record`. Returns `record` itself when it is already optional.
@@ -10,9 +10,22 @@ import type { StandardSchemaV1 } from "../../standard/standard-schema.js";
10
10
  import type { Kind } from "../../symbols/kind.js";
11
11
  import type { listOf } from "../list.js";
12
12
  import type { optionalOf } from "../optional.js";
13
+ import type { variantOf } from "../variant.js";
13
14
  import type { IBlueprintFactories, IBlueprintPlugin } from "./blueprint-plugin.interface.js";
14
15
  /** Any blueprint, whatever its properties, optional or not. */
15
16
  export type AnyBlueprint = IBlueprint<any, boolean>;
17
+ /**
18
+ * Any variant field, whatever its key and its variants, optional or not.
19
+ * Written out, not as `IBlueprintVariant` of anything: every field of every
20
+ * blueprint is compared with it, and this way nothing is worked out for that.
21
+ */
22
+ export interface AnyBlueprintVariant {
23
+ readonly [Kind]: "variant";
24
+ readonly key: string;
25
+ readonly variants: any;
26
+ readonly optional: boolean;
27
+ readonly "~standard": StandardSchemaV1.Props<any, any>;
28
+ }
16
29
  /** Any list, whatever its item, optional or not. */
17
30
  export type AnyBlueprintList = IBlueprintList<any, boolean>;
18
31
  /**
@@ -40,9 +53,10 @@ export interface OptionalRecord<Record extends AnyRecordClass> {
40
53
  export type AnyOptionalRecord = OptionalRecord<AnyRecordClass>;
41
54
  /**
42
55
  * What a blueprint key holds: a property, a blueprint for a nested object, a
43
- * record, or a list of any of them.
56
+ * record, a list of any of them, or one of several blueprints told apart by a
57
+ * key.
44
58
  */
45
- export type BlueprintField = AnyPropertyConstructor | AnyBlueprint | AnyBlueprintList | AnyRecordClass | AnyOptionalRecord;
59
+ export type BlueprintField = AnyPropertyConstructor | AnyBlueprint | AnyBlueprintList | AnyRecordClass | AnyOptionalRecord | AnyBlueprintVariant;
46
60
  /** Fields by key, e.g. `{ name: Name, address: Address }`. */
47
61
  export type IBlueprintCollection = Record<string, BlueprintField>;
48
62
  /** What a blueprint picks its fields from: a plugin, or another blueprint. */
@@ -66,7 +80,17 @@ type FieldSchema<Field, Mode extends FieldMode> = 0 extends 1 & Field ? TSchema
66
80
  } ? Optional extends true ? TOptional<CollectionSchema<Properties, Mode>> : CollectionSchema<Properties, Mode> : Field extends {
67
81
  readonly item: infer Item;
68
82
  readonly optional: infer Optional;
69
- } ? Optional extends true ? TOptional<TArray<FieldSchema<Item, Mode>>> : TArray<FieldSchema<Item, Mode>> : never;
83
+ } ? Optional extends true ? TOptional<TArray<FieldSchema<Item, Mode>>> : TArray<FieldSchema<Item, Mode>> : VariantSchema<Field, Mode>;
84
+ /**
85
+ * The schema of a variant field: its value as it is, since a union of objects
86
+ * is only read for whether the key is optional. Apart from `FieldSchema`, so
87
+ * that a field that is not a variant costs one comparison there.
88
+ */
89
+ type VariantSchema<Field, Mode extends FieldMode> = Field extends {
90
+ readonly key: infer Key extends string;
91
+ readonly variants: infer Variants;
92
+ readonly optional: infer Optional;
93
+ } ? Optional extends true ? TOptional<TUnsafe<VariantValue<Key, Variants, Mode>>> : TUnsafe<VariantValue<Key, Variants, Mode>> : never;
70
94
  /** Declares the records a field of a record class takes besides its own. */
71
95
  export declare const RecordAccepts: unique symbol;
72
96
  /** The values besides its instances and plain input that a field of `Record` takes. */
@@ -102,7 +126,31 @@ type FieldValue<Field, Mode extends FieldMode> = 0 extends 1 & Field ? Static<TS
102
126
  readonly properties: infer Properties extends IBlueprintCollection;
103
127
  } ? CollectionValue<Properties, Mode> : Field extends {
104
128
  readonly item: infer Item;
105
- } ? FieldValue<Item, Mode>[] : never;
129
+ } ? FieldValue<Item, Mode>[] : Field extends {
130
+ readonly key: infer Key extends string;
131
+ readonly variants: infer Variants;
132
+ } ? VariantValue<Key, Variants, Mode> : never;
133
+ /**
134
+ * The value of a variant field: for each variant, its own value with `Key`
135
+ * set to its name, so that `value[Key]` tells which one it is. Anything for a
136
+ * field of any variants (as `AnyBlueprintVariant`'s).
137
+ */
138
+ type VariantValue<Key extends string, Variants, Mode extends FieldMode> = 0 extends 1 & Variants ? any : {
139
+ [Tag in keyof Variants & string]: VariantMember<Key, Tag, Variants[Tag], Mode>;
140
+ }[keyof Variants & string];
141
+ /**
142
+ * The value of one variant named `Tag`. A blueprint gives its value with the
143
+ * key. A record class gives an instance, which has no key and is told apart
144
+ * by its class; what it takes is an instance or its plain input with the key,
145
+ * and its plain data has the key.
146
+ */
147
+ type VariantMember<Key extends string, Tag, Variant, Mode extends FieldMode> = Variant extends AnyRecordClass ? Mode extends "value" ? InstanceType<Variant> : Mode extends "input" ? InstanceType<Variant> | Evaluate<{
148
+ readonly [Name in Key]: Tag;
149
+ } & RecordInput<Variant>> : Evaluate<{
150
+ readonly [Name in Key]: Tag;
151
+ } & RecordValueOf<Variant, "data">> : Evaluate<{
152
+ readonly [Name in Key]: Tag;
153
+ } & FieldValue<Variant, Mode>>;
106
154
  /** The value of a record field, as `RecordSchema` describes it. */
107
155
  type RecordValueOf<Record extends AnyRecordClass, Mode extends FieldMode> = Mode extends "value" ? InstanceType<Record> : Mode extends "input" ? InstanceType<Record> | RecordInput<Record> | AcceptedBy<Record> : Record["blueprint"] extends IBlueprint<infer Properties, boolean> ? CollectionValue<Properties, "data"> : never;
108
156
  type ReadonlyOptionalKeys<Schemas> = {
@@ -196,6 +244,7 @@ export interface BlueprintHelpers {
196
244
  readonly listOf: typeof listOf;
197
245
  readonly optionalOf: typeof optionalOf;
198
246
  readonly propertyOf: typeof propertyOf;
247
+ readonly variantOf: typeof variantOf;
199
248
  }
200
249
  /**
201
250
  * What a blueprint's definition picks from: every source field, the field
@@ -223,7 +272,10 @@ export type CallableProperties<Fields> = {
223
272
  * @internal
224
273
  */
225
274
  export type OptionalProperties<Properties> = {
226
- readonly [Key in keyof Properties]: Properties[Key] extends AnyPropertyConstructor ? OptionalProperty<Properties[Key]> : Properties[Key] extends AnyBlueprint ? OptionalBlueprint<Properties[Key]> : Properties[Key] extends AnyBlueprintList ? OptionalBlueprintList<Properties[Key]> : Properties[Key] extends AnyRecordClass ? OptionalRecord<Properties[Key]> : Properties[Key] extends AnyOptionalRecord ? Properties[Key] : never;
275
+ readonly [Key in keyof Properties]: Properties[Key] extends AnyPropertyConstructor ? OptionalProperty<Properties[Key]> : Properties[Key] extends AnyBlueprint ? OptionalBlueprint<Properties[Key]> : Properties[Key] extends AnyBlueprintList ? OptionalBlueprintList<Properties[Key]> : Properties[Key] extends AnyRecordClass ? OptionalRecord<Properties[Key]> : Properties[Key] extends AnyOptionalRecord ? Properties[Key] : Properties[Key] extends {
276
+ readonly key: infer Name extends string;
277
+ readonly variants: infer Variants extends BlueprintVariants;
278
+ } ? IBlueprintVariant<Name, Variants, true> : never;
227
279
  };
228
280
  export type BlueprintDefinition<Sources extends readonly BlueprintSource[], Properties extends IBlueprintCollection> = (ctx: BlueprintContext<Sources>) => Properties;
229
281
  export interface IBlueprint<Properties extends IBlueprintCollection, Optional extends boolean = false> {
@@ -288,6 +340,28 @@ export interface IBlueprintList<Item extends BlueprintField, Optional extends bo
288
340
  readonly "~standard": StandardSchemaV1.Props<ListShape<Item, "input", Optional>, ListShape<Item, "value", Optional>>;
289
341
  }
290
342
  type ListShape<Item, Mode extends FieldMode, Optional extends boolean> = Optional extends true ? FieldValue<Item, Mode>[] | undefined : FieldValue<Item, Mode>[];
343
+ /** The variants of a variant field, by the value its key has for each: blueprints, or record classes. */
344
+ export type BlueprintVariants = Readonly<Record<string, AnyBlueprint | AnyRecordClass>>;
345
+ /**
346
+ * A field holding one of the objects of `Variants`, told apart by the value
347
+ * of `Key`, which is the name of the variant.
348
+ */
349
+ export interface IBlueprintVariant<Key extends string, Variants extends BlueprintVariants, Optional extends boolean = false> {
350
+ readonly [Kind]: "variant";
351
+ /** The key that tells the variants apart; no variant declares it. */
352
+ readonly key: Key;
353
+ readonly variants: Variants;
354
+ /** Whether, as a field of a blueprint, it accepts `undefined`. */
355
+ readonly optional: Optional;
356
+ /**
357
+ * The Standard Schema of the field: `validate` gives the frozen value of
358
+ * the variant the key names, or the issues that reject the value.
359
+ */
360
+ readonly "~standard": StandardSchemaV1.Props<VariantShape<Key, Variants, "input", Optional>, VariantShape<Key, Variants, "value", Optional>>;
361
+ }
362
+ type VariantShape<Key extends string, Variants, Mode extends FieldMode, Optional extends boolean> = Optional extends true ? VariantValue<Key, Variants, Mode> | undefined : VariantValue<Key, Variants, Mode>;
363
+ /** `Variant` as a field that accepts `undefined`. */
364
+ export type OptionalBlueprintVariant<Variant extends AnyBlueprintVariant> = Variant extends IBlueprintVariant<infer Key, infer Variants, boolean> ? IBlueprintVariant<Key, Variants, true> : never;
291
365
  /** `List` as a field that accepts `undefined`. */
292
366
  export type OptionalBlueprintList<List extends AnyBlueprintList> = List extends IBlueprintList<infer Item, boolean> ? IBlueprintList<Item, true> : never;
293
367
  export {};
@@ -0,0 +1,32 @@
1
+ import type { AnyBlueprint, AnyBlueprintVariant, BlueprintVariants, IBlueprintVariant } from "./types/blueprint.interface.js";
2
+ /**
3
+ * A field holding one of several objects, told apart by the value of `key`:
4
+ * `variantOf("type", { click: Click, signup: Signup })` takes
5
+ * `{ type: "click", ...a Click }` or `{ type: "signup", ...a Signup }`. Each
6
+ * variant is a blueprint, which does not declare `key`: the field adds it,
7
+ * with the name of the variant for its value. A variant can also be a record
8
+ * class (an entity too): the value is then an instance of it, told apart by
9
+ * its class, and the key is only in what is given, in `toJSON` and in what is
10
+ * stored.
11
+ */
12
+ export declare function variantOf<const Key extends string, const Variants extends BlueprintVariants>(key: Key, variants: Variants): IBlueprintVariant<Key, Variants>;
13
+ /** `variant`, frozen, with the `~standard` that handles a value as `variant` does. */
14
+ export declare function standardVariantOf<Variant extends object>(variant: Variant): Variant;
15
+ type Variant = BlueprintVariants[string];
16
+ /** The blueprint of a variant: itself, or that of the record class it is. */
17
+ export declare function blueprintOfVariant(variant: Variant): AnyBlueprint;
18
+ /**
19
+ * The variant that the plain `value` names with the key of `field`, if it
20
+ * names one: a blueprint or a record class.
21
+ */
22
+ export declare function namedVariantOf(field: AnyBlueprintVariant, value: unknown): Variant | undefined;
23
+ /** The blueprint of the variant that the plain `value` names with the key of `field`, if it names one. */
24
+ export declare function variantOfValue(field: AnyBlueprintVariant, value: unknown): AnyBlueprint | undefined;
25
+ /**
26
+ * The name of the variant `instance` is, a record of one of the classes of
27
+ * `field`; `undefined` when it is of none.
28
+ */
29
+ export declare function variantNameOf(field: AnyBlueprintVariant, instance: unknown): string | undefined;
30
+ /** Whether `value` is a field made by `variantOf`. */
31
+ export declare function isBlueprintVariant(value: unknown): value is AnyBlueprintVariant;
32
+ export {};
@@ -25,6 +25,11 @@ export declare const EntityLifetime: unique symbol;
25
25
  export declare const EXPIRES_AT = "expiresAt";
26
26
  /** The key a versioned entity adds to its identity. */
27
27
  export declare const ENTITY_VERSION_KEY = "version";
28
+ /**
29
+ * The key of each stored row holding its schema version, for entities
30
+ * declared with `schemaVersion`; it is never a key of the entity.
31
+ */
32
+ export declare const SCHEMA_VERSION_KEY = "$v";
28
33
  /** Whether `entityClass` was declared with `versioned: true`. */
29
34
  export declare function isVersionedEntityClass(entityClass: unknown): boolean;
30
35
  /**
@@ -9,7 +9,7 @@ import type { EntityBuilder, EntityBuilderSettings, IEntityIdentity } from "./ty
9
9
  * `options.plugins` picks from the plugins of `plugins`, catalogs made by
10
10
  * `recordPluginCatalogOf`.
11
11
  */
12
- export declare function entityBuilderOf<Identity extends IEntityIdentity, const Catalogs extends readonly IRecordPluginCatalog[] = []>({ identity, generateId, now, expiresAt, plugins: catalogs, }: EntityBuilderSettings<Identity, Catalogs>): EntityBuilder<Identity, RecordPluginContext<Catalogs>>;
12
+ export declare function entityBuilderOf<Identity extends IEntityIdentity, const Catalogs extends readonly IRecordPluginCatalog[] = []>(settings: EntityBuilderSettings<Identity, Catalogs>): EntityBuilder<Identity, RecordPluginContext<Catalogs>>;
13
13
  /** Whether `value` is a class made by `entityOf`, or one extending it. */
14
14
  export declare function isEntityClass(value: unknown): value is AnyRecordClass & AnyEntityClass;
15
15
  /** Whether `value` is an entity, an instance of a class made by `entityOf`. */
@@ -25,3 +25,12 @@ export declare const DeletedEntity: import("../../index.js").DeclaredExceptionCl
25
25
  export declare const InvalidEntityExpiry: import("../../index.js").DeclaredExceptionClass<{
26
26
  readonly reason: string;
27
27
  }>;
28
+ /**
29
+ * The schema evolution options of an entity are invalid: `schemaVersion` is
30
+ * not a positive integer, a step of `upgrade` is not a function or reaches a
31
+ * version outside `2..schemaVersion`, or `previousNames` holds something
32
+ * that cannot name a record, or its own name; `source` is the entity name.
33
+ */
34
+ export declare const InvalidEntitySchemaVersion: import("../../index.js").DeclaredExceptionClass<{
35
+ readonly reason: string;
36
+ }>;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Under this key, the settings of an entity builder tell when what their
3
+ * `generateId` and `now` make is known to be valid, so that an entity does
4
+ * not check again the id and the date it has just made. Only for a builder
5
+ * that knows its own makers, as `std` does of its defaults: a generator an
6
+ * application configures is never vouched for.
7
+ * @internal
8
+ */
9
+ export declare const MakesValid: unique symbol;
10
+ /** @internal */
11
+ export interface ValidMakers {
12
+ /** Whether the id `generateId` is about to make is one `id` accepts as it is. */
13
+ readonly id: () => boolean;
14
+ /** Whether the date `now` is about to make is one `createdAt` accepts as it is. */
15
+ readonly createdAt: () => boolean;
16
+ }
17
+ /**
18
+ * `settings` of an entity builder with `makers` vouching for its
19
+ * `generateId` and `now`.
20
+ * @internal
21
+ */
22
+ export declare function vouching<const Settings extends object>(settings: Settings, makers: ValidMakers): Settings;
23
+ /** The makers `settings` vouch for, if any. @internal */
24
+ export declare function validMakersOf(settings: object): ValidMakers | undefined;
@@ -75,6 +75,30 @@ export interface EntityStorageOptions<Key extends string = string, Expires exten
75
75
  * blueprint. A record with no date never expires.
76
76
  */
77
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;
78
102
  }
79
103
  /** A duration, e.g. `{ days: 400 }`. */
80
104
  export interface EntityDuration {
@@ -107,6 +131,12 @@ export type ExpiringIdentity<Identity extends IEntityIdentity, Expires> = Expire
107
131
  export interface EntityStorageSettings {
108
132
  readonly versioned: boolean;
109
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[];
110
140
  }
111
141
  /**
112
142
  * The unique keys of an entity: `id`, those listed in `options.unique`, and
@@ -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 {};
@@ -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,4 +1,5 @@
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. */
@@ -146,10 +147,30 @@ export interface OperationRoute {
146
147
  readonly headers: {
147
148
  readonly [name: string]: string;
148
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
+ };
149
169
  }
150
170
  /**
151
171
  * The route of every operation, by id: `POST {basePath}/{kebab(command)}/{kebab(operation)}`
152
- * with `200`, changed per operation id with `route` and `responses`. Throws
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
153
174
  * `InvalidOutputStage` (with `source`) when `route`, `responses` or
154
175
  * `security.operations` name an unknown operation, a path parameter is not a
155
176
  * scalar key of the input, or two operations share a method and path. The
@@ -179,5 +200,5 @@ export declare function operationRoutes(operations: readonly OperationDescriptor
179
200
  * transport-specific and not part of the document; sinks that serve HTTP map
180
201
  * it (headers, middleware).
181
202
  */
182
- 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>;
183
204
  export {};
@@ -1,8 +1,13 @@
1
1
  import type { IStageCatalog, SinkFactory, SinkSettings, Stage, StageDescription, StageFactory, StageOptions, TransformFactory, TransformSettings } from "./types/output.interface.js";
2
2
  /** A factory of transform stages: `select({ tags: ["public"] })`. */
3
3
  export declare function transformOf<Options extends object = StageOptions>(settings: TransformSettings<Options>): TransformFactory<Options>;
4
- /** A factory of sink stages: `openapi({ info })`. */
5
- export declare function sinkOf<Result, Options extends object = StageOptions>(settings: SinkSettings<Result, Options>): SinkFactory<Result, Options>;
4
+ /**
5
+ * A factory of sink stages: `openapi({ info })`. A sink that needs what JSON
6
+ * cannot hold (middlewares, an authenticator) takes it as a second argument,
7
+ * its `hooks`, which `build` receives and the description of the stage
8
+ * leaves out.
9
+ */
10
+ export declare function sinkOf<Result, Options extends object = StageOptions, Hooks = never>(settings: SinkSettings<Result, Options, Hooks>): SinkFactory<Result, Options, Hooks>;
6
11
  /**
7
12
  * What a stage is: its kind, name and JSON options when it came from a
8
13
  * factory, `{ kind: "opaque" }` otherwise (a hand-written stage needs an
@@ -11,7 +11,7 @@ export declare function raisedOf(error: Exception): Exception;
11
11
  * name; 400 for invalid input or context (and any exception whose
12
12
  * `details.issues` is a list, except `InvalidCommandEnvironment`, a 500); 404 for a missing entity or operation; 409 for
13
13
  * a unique violation, a concurrency conflict or an entity that already exists; 401 for
14
- * `UseCaseInvalidCredentials`; 422 for a class in `operation.errors`; 500 for the
14
+ * `UseCaseInvalidCredentials` and `Unauthenticated`; 403 for `Forbidden`; 422 for a class in `operation.errors`; 500 for the
15
15
  * rest, errors that are not exceptions included. Exceptions match by name,
16
16
  * or by class for `operation.errors`.
17
17
  */
@@ -0,0 +1,130 @@
1
+ import type { DescribedJwtOptions } from "../../auth/types/jwt.interface.js";
2
+ import type { Principal } from "../../use-case/types/access.interface.js";
3
+ import type { OpenAPIMethod, OpenAPISinkOptions, RouteOptions } from "../openapi-sink.js";
4
+ import type { OperationDescriptor } from "./output.interface.js";
5
+ /**
6
+ * The options every sink that serves HTTP takes. JSON, so the stage can live
7
+ * in an `afora.json`. The routing options (`basePath`, `route`, `responses`,
8
+ * `requestId`, `security`) are those of the `openapi` sink, so the served
9
+ * routes and the document agree.
10
+ */
11
+ export interface HttpOptions extends RouteOptions {
12
+ /** Serves the OpenAPI document of the same operations at `path`. */
13
+ readonly docs?: {
14
+ readonly path: string;
15
+ readonly info: OpenAPISinkOptions["info"];
16
+ readonly servers?: OpenAPISinkOptions["servers"];
17
+ };
18
+ /** Adds the `trace` to problems and the real `detail` to 5xx ones. Off by default. */
19
+ readonly debug?: boolean;
20
+ /**
21
+ * The context of an operation, by context key: from request headers
22
+ * (`{ headers: { locale: "accept-language" } }`) and from values a
23
+ * middleware of the server set for the request
24
+ * (`{ variables: { caller: "caller" } }`).
25
+ */
26
+ readonly context?: {
27
+ readonly headers?: {
28
+ readonly [contextKey: string]: string;
29
+ };
30
+ readonly variables?: {
31
+ readonly [contextKey: string]: string;
32
+ };
33
+ };
34
+ /**
35
+ * Who calls, from a JSON Web Token (see `jwtOf`; the secret is named by
36
+ * its environment variable, `{ env: "JWT_SECRET" }`): read from the
37
+ * `authorization` header (`Bearer <token>`; another header with `header`)
38
+ * and, with `cookie`, from that cookie when the header is absent. The
39
+ * document gets a matching security scheme unless `security` declares
40
+ * its own. An `authenticator` given in the hooks of the sink replaces it.
41
+ */
42
+ readonly auth?: {
43
+ readonly jwt: DescribedJwtOptions & {
44
+ readonly header?: string;
45
+ readonly cookie?: string;
46
+ };
47
+ };
48
+ }
49
+ /** A request, as the server of a sink gives it to the route table. */
50
+ export interface HttpRequest {
51
+ /** The value of the `{name}` segment of the path, decoded. */
52
+ param(name: string): string | undefined;
53
+ /** The query string, the first value of each name. */
54
+ query(): Readonly<Record<string, string | undefined>>;
55
+ /** The value of a header, by its name in lower case. */
56
+ header(name: string): string | undefined;
57
+ /** The body, as text. Called at most once, and only by routes with a body. */
58
+ text(): string | Promise<string>;
59
+ /** A value a middleware of the server set for this request, by name. */
60
+ variable?(name: string): unknown;
61
+ /** The request of the server itself, for an authenticator that needs it. */
62
+ readonly native?: unknown;
63
+ }
64
+ /** The answer to a request: what the server of a sink has to send, as is. */
65
+ export interface HttpResult {
66
+ readonly status: number;
67
+ /** `content-type` included, when there is a body. */
68
+ readonly headers: Readonly<Record<string, string>>;
69
+ /** One `set-cookie` header each. */
70
+ readonly cookies: readonly string[];
71
+ /** The body, already serialized (sensitive values masked), or `null` for none. */
72
+ readonly body: string | null;
73
+ }
74
+ /** What an authenticator reads of a request. */
75
+ export interface AuthRequest {
76
+ /** The value of a header, by its name in lower case. */
77
+ header(name: string): string | undefined;
78
+ /** The value of a cookie of the request. */
79
+ cookie(name: string): string | undefined;
80
+ /** The operation being called. */
81
+ readonly operation: OperationDescriptor;
82
+ /** The request of the server itself. */
83
+ readonly native?: unknown;
84
+ }
85
+ /**
86
+ * Who is calling, from the credentials of a request: a `Principal`, or
87
+ * `null` for an anonymous caller. Called for each request to an operation
88
+ * with an `access` other than `public`. It may throw `Unauthenticated` to
89
+ * refuse credentials it does not accept.
90
+ */
91
+ export type Authenticator = (request: AuthRequest) => Principal | null | Promise<Principal | null>;
92
+ /** One operation, ready to serve: the server routes `method` and `path` to `handle`. */
93
+ export interface CompiledRoute {
94
+ readonly operation: OperationDescriptor;
95
+ readonly method: OpenAPIMethod;
96
+ /** `basePath` included, parameters as `{name}`. */
97
+ readonly path: string;
98
+ /** The names of the `{name}` segments of `path`, in order. */
99
+ readonly parameters: readonly string[];
100
+ /** Never rejects: an error is a problem result. */
101
+ handle(request: HttpRequest): Promise<HttpResult>;
102
+ }
103
+ /** The routes of an output, compiled once (see `compileRoutes`). */
104
+ export interface RouteTable {
105
+ readonly routes: readonly CompiledRoute[];
106
+ /** The route that serves the OpenAPI document, with `docs`. */
107
+ readonly docs?: {
108
+ readonly path: string;
109
+ handle(request?: Pick<HttpRequest, "header">): HttpResult;
110
+ };
111
+ /** The 404 problem for a request no route serves. */
112
+ notFound(request?: Pick<HttpRequest, "header">): HttpResult;
113
+ /**
114
+ * The problem for an error raised outside `handle`: its status is the one
115
+ * the output `statuses` give its name, else `status`, else `statusOf`.
116
+ */
117
+ failure(error: unknown, options?: {
118
+ readonly operation?: OperationDescriptor;
119
+ readonly status?: number;
120
+ readonly request?: Pick<HttpRequest, "header">;
121
+ }): HttpResult;
122
+ }
123
+ /** What `compileRoutes` takes besides the operations and the options. */
124
+ export interface CompileRoutesSettings {
125
+ /** The name of the sink, for the messages of `InvalidOutputStage`. */
126
+ readonly source: string;
127
+ readonly authenticator?: Authenticator;
128
+ /** Changes the OpenAPI document before it is served (a sink with routes of its own kind). */
129
+ readonly document?: (document: unknown) => unknown;
130
+ }
@@ -2,6 +2,8 @@ import type { AnyBlueprint } from "../../blueprint/types/blueprint.interface.js"
2
2
  import type { AnyCommand } from "../../command/types/command.interface.js";
3
3
  import type { JsonValue } from "../../json.js";
4
4
  import type { Kind } from "../../symbols/kind.js";
5
+ import type { AccessDeclaration, Principal } from "../../use-case/types/access.interface.js";
6
+ import type { RouteDeclaration } from "../../use-case/types/route.interface.js";
5
7
  import type { AnyExceptionClass } from "../../use-case/use-case.js";
6
8
  /** The options of a stage: a JSON object. */
7
9
  export type StageOptions = {
@@ -9,9 +11,15 @@ export type StageOptions = {
9
11
  };
10
12
  /** An HTTP-style status by exception name. */
11
13
  export type OutputStatuses = Readonly<Record<string, number>>;
12
- /** The call of an operation: `context` is what the command (or use case) declares. */
14
+ /**
15
+ * The call of an operation: `context` is what the command (or use case)
16
+ * declares, and `principal` is who calls, as the sink authenticated it
17
+ * (`null` or absent for an anonymous caller). An operation with an `access`
18
+ * checks the principal before it runs.
19
+ */
13
20
  export interface OperationCallOptions {
14
21
  readonly context?: unknown;
22
+ readonly principal?: Principal | null;
15
23
  }
16
24
  /**
17
25
  * One operation of a command, as an output sees it. Frozen; transforms change
@@ -39,9 +47,16 @@ export interface OperationDescriptor {
39
47
  readonly context?: AnyBlueprint;
40
48
  /** The `errors` of the use case. */
41
49
  readonly errors: readonly AnyExceptionClass[];
50
+ /** The route the use case declares (`route` or `@Route`), if any. */
51
+ readonly route?: RouteDeclaration;
52
+ /** Who may call it, as the use case declares (`access` or `@Access`), if it does. */
53
+ readonly access?: AccessDeclaration;
42
54
  /** Set by transforms; `{}` initially. */
43
55
  readonly meta: Readonly<Record<string, unknown>>;
44
- /** `command.run(name, input, options)`. */
56
+ /**
57
+ * `command.run(name, input, { context })`, after the check of `access`:
58
+ * it rejects with `Unauthenticated` or `Forbidden` before anything runs.
59
+ */
45
60
  invoke(input: unknown, options?: OperationCallOptions): Promise<unknown>;
46
61
  }
47
62
  /** What `operationOf` can change. */
@@ -92,15 +107,20 @@ export interface TransformSettings<Options extends object> {
92
107
  /** Why `options` cannot be used, or `undefined`. Checked when the stage is made. */
93
108
  readonly validate?: (options: Options) => string | undefined;
94
109
  }
95
- export interface SinkSettings<Result, Options extends object> {
110
+ export interface SinkSettings<Result, Options extends object, Hooks = never> {
96
111
  readonly name: string;
97
- readonly build: (operations: readonly OperationDescriptor[], options: Options, context: StageContext) => Result;
112
+ /**
113
+ * `hooks` is the second argument the stage was made with: what cannot be
114
+ * JSON (functions, an instance of a server). It is not part of the
115
+ * description of the stage.
116
+ */
117
+ readonly build: (operations: readonly OperationDescriptor[], options: Options, context: StageContext, hooks: Hooks | undefined) => Result;
98
118
  /** Why `options` cannot be used, or `undefined`. Checked when the stage is made. */
99
119
  readonly validate?: (options: Options) => string | undefined;
100
120
  }
101
121
  /** Makes a stage from JSON options. */
102
122
  export type TransformFactory<Options extends object = StageOptions> = (options?: Options) => TransformStage<Options>;
103
- export type SinkFactory<Result = unknown, Options extends object = StageOptions> = (options?: Options) => SinkStage<Result, Options>;
123
+ export type SinkFactory<Result = unknown, Options extends object = StageOptions, Hooks = never> = (options?: Options, hooks?: Hooks) => SinkStage<Result, Options>;
104
124
  /** Any stage factory. */
105
125
  export type StageFactory = TransformFactory<any> | SinkFactory<any, any>;
106
126
  /** Stage factories by name. */