@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,31 @@
1
1
  import type { Exception } from "../exception/exception.js";
2
- import type { Issue } from "./types/parse.interface.js";
3
- /** `issue` with a `cause` that is built the first time it is read, and kept. */
4
- export declare function withLazyCause(issue: Issue, build: () => Exception): Issue;
2
+ import type { Issue, IssueCode } from "./types/parse.interface.js";
3
+ /**
4
+ * What a step gives back, in place of a value, for a value it rejects; the
5
+ * issues are then found where the step says, so a valid value costs no result
6
+ * object.
7
+ * @internal
8
+ */
9
+ export declare const REJECTED: unique symbol;
10
+ /** Who knows the exception an issue stands for, and builds it only when asked. */
11
+ export interface IssueOrigin {
12
+ exception(): Exception;
13
+ }
14
+ /**
15
+ * An issue whose `cause` is built the first time it is read, by its origin,
16
+ * which keeps it: every issue of one origin has the same cause. Its own keys
17
+ * are those of any issue, `cause` is not one of them.
18
+ */
19
+ export declare class LazyIssue implements Issue {
20
+ #private;
21
+ readonly message: string;
22
+ readonly path: readonly (string | number)[];
23
+ readonly code: IssueCode;
24
+ readonly source: string;
25
+ constructor(message: string, path: readonly (string | number)[], code: IssueCode, source: string, origin: IssueOrigin);
26
+ get cause(): Exception;
27
+ /** A copy of the issue at `path`, with the same cause. */
28
+ at(path: readonly (string | number)[]): LazyIssue;
29
+ }
5
30
  /** A copy of `issue` at `path`, with the same `cause`, built lazily or not. */
6
31
  export declare function movedIssue(issue: Issue, path: readonly (string | number)[]): Issue;
@@ -18,6 +18,15 @@ export declare function isPlainObject(value: unknown): value is Record<string, u
18
18
  * is parsing it, e.g. a record name.
19
19
  */
20
20
  export declare function parseCollection(properties: IBlueprintCollection, value: unknown, path: Path, source: string): ParseResult<Readonly<Record<string, unknown>>>;
21
+ /**
22
+ * `parseCollection` at the root, as a function of the value alone: what it
23
+ * needs of `properties` is looked up on the first value and kept, instead of
24
+ * on every one. For what parses many values of one shape, a blueprint or a
25
+ * record class. The values of the keys in `vouched` are kept as they are,
26
+ * unchecked: for what the caller made itself and knows to be valid.
27
+ * @internal
28
+ */
29
+ export declare function collectionParserOf(properties: IBlueprintCollection, source: string): (value: unknown, vouched?: readonly string[]) => ParseResult<Readonly<Record<string, unknown>>>;
21
30
  /**
22
31
  * `value` handled by `field`, or every issue found: a property runs its
23
32
  * handlers, a blueprint each of its fields, a list each of its items, and a
@@ -9,6 +9,15 @@ import type { AnyPropertyConstructor } from "./types/property-collection.type.js
9
9
  import type { PropertyValue } from "./types/property-constructor.type.js";
10
10
  import type { PropertyOptions } from "./types/property-options.interface.js";
11
11
  import type { IPropertyType } from "./types/property-type.interface.js";
12
+ /**
13
+ * The definition `Class` parses with. It does not depend on any member the
14
+ * constructor sets, and `parse` leaves nothing on it that the next one reads,
15
+ * so every `safeParse` shares one.
16
+ * @internal
17
+ */
18
+ export declare function parsingDefinitionOf(Class: {
19
+ prototype: object;
20
+ }): AbstractDefineProperty<TSchema>;
12
21
  /** A property instance; `Value` is the value of its schema, as in `ISchema`. */
13
22
  export declare abstract class AbstractProperty<SchemaType extends TSchema, Value = Static<SchemaType>> implements IProperty<SchemaType, Value> {
14
23
  get [State](): PropertyStates;
@@ -1,9 +1,16 @@
1
1
  import type { Static, TSchema } from "typebox";
2
- import type { ParseResult } from "../parse/types/parse.interface.js";
2
+ import type { Issue, ParseResult } from "../parse/types/parse.interface.js";
3
3
  import { State } from "../symbols/state.js";
4
4
  import { PropertyStates } from "./enums/property-states.js";
5
5
  import type { IDefineProperty } from "./types/define-property.interface.js";
6
6
  import type { IPropertyType } from "./types/property-type.interface.js";
7
+ /**
8
+ * Adds to `issues` those of the value a pipeline has just rejected, each
9
+ * under `path`, where the value is. An issue of the value itself has `path`
10
+ * as it is, not a copy.
11
+ * @internal
12
+ */
13
+ export declare function takeRejectionIssues(path: readonly (string | number)[], issues: Issue[]): void;
7
14
  /** How a property handles its value; `Value` is the value of its schema, as in `ISchema`. */
8
15
  export declare abstract class AbstractDefineProperty<SchemaType extends TSchema, Value = Static<SchemaType>> implements IDefineProperty<SchemaType, Value> {
9
16
  [State]: PropertyStates;
@@ -27,9 +34,13 @@ export declare abstract class AbstractDefineProperty<SchemaType extends TSchema,
27
34
  protected afterHandle(value: Value): Value;
28
35
  /** The value, or `MISMATCH` when the schema rejects it. */
29
36
  protected handle(value: Value): Value;
30
- /** `attempt`, with the error of any step as a failure. */
37
+ /** `attempt`, with the error of any step as a rejection. */
31
38
  private attemptWithoutThrowing;
32
- /** Runs the pipeline, updating the state, and tells why it stopped if it did. */
39
+ /**
40
+ * Runs the pipeline, updating the state: the handled value, or `REJECTED`
41
+ * with the reason in `rejection`. A handler that is not declared is not
42
+ * run, and neither is the shape check that protects it.
43
+ */
33
44
  private attempt;
34
45
  /**
35
46
  * Runs a handler: an `Exception` it throws goes up as it is, any other
@@ -38,11 +49,28 @@ export declare abstract class AbstractDefineProperty<SchemaType extends TSchema,
38
49
  private runHandler;
39
50
  /** The `InvalidPropertyValue` of an error that is not an `Exception`, as its cause. */
40
51
  private invalidValue;
41
- /** The failure of an `exception` that a step threw. */
52
+ /** Rejects the value with an `exception` that a step threw. */
42
53
  private raise;
43
54
  /**
44
- * The failure of `value`, which the schema rejects: an issue for each of its
45
- * errors, found once and kept for the details of the exception too.
55
+ * Rejects `value`, which the schema rejects, with its errors, found once
56
+ * and kept for the details of the exception too.
46
57
  */
47
58
  private mismatch;
48
59
  }
60
+ /**
61
+ * Tells that the definitions of `prototype` are as `propertyOf` declares them.
62
+ * @internal
63
+ */
64
+ export declare function declarePipeline(prototype: object): void;
65
+ /**
66
+ * `definition.parse` without its result object: a function giving the handled
67
+ * value, or `REJECTED`, with the issues for `takeRejectionIssues` to take.
68
+ * `undefined` when `definition` has a `parse` of its own, which must be
69
+ * called instead.
70
+ *
71
+ * For a definition declared by `propertyOf`, the function is the pipeline of
72
+ * `attempt` with what never changes read once: which steps there are, the
73
+ * schema, and no state, which nobody can read.
74
+ * @internal
75
+ */
76
+ export declare function parserOf(definition: AbstractDefineProperty<TSchema>): ((value: unknown) => unknown) | undefined;
@@ -14,6 +14,16 @@ export interface PropertyMetadata<Value> {
14
14
  readonly writeOnly?: boolean;
15
15
  /** The demo value, as `default` in `propertyOf`; it is not written to the schema. */
16
16
  readonly default?: Value;
17
+ /**
18
+ * The value a repository gives the key when a stored row lacks it, on read
19
+ * and in a backfill (`app.migrate`); unlike `default`, it fills real data.
20
+ */
21
+ readonly fill?: Value;
22
+ /**
23
+ * The key that held the value in rows stored before the key was renamed:
24
+ * a repository moves it on read and in a backfill.
25
+ */
26
+ readonly renamedFrom?: string;
17
27
  }
18
28
  /**
19
29
  * Bounds of a number. They only narrow the property: a bound looser than one
@@ -15,6 +15,10 @@ export interface IPropertyType<SchemaType extends TSchema, Value = Static<Schema
15
15
  readonly sensitive?: boolean;
16
16
  /** Whether a record lists a key holding it in `options.unique`, for repositories to enforce. */
17
17
  readonly unique?: boolean;
18
+ /** `fill` of `.with()`: the value given to the key when a stored row lacks it. */
19
+ readonly fill?: Value;
20
+ /** `renamedFrom` of `.with()`: the key that held the value in older rows. */
21
+ readonly renamedFrom?: string;
18
22
  }
19
23
  /**
20
24
  * The type of a property declared `unique`, as `Slug`, so that the keys
@@ -15,6 +15,13 @@ interface RecordDeclaration<Properties extends IBlueprintCollection> {
15
15
  export declare const RESERVED_RECORD_KEYS: readonly string[];
16
16
  /** Makes each of `keys` a member of the records of `prototype`, e.g. `user.name`. */
17
17
  export declare function exposeRecordKeys(prototype: object, keys: readonly string[]): void;
18
+ /**
19
+ * Tells the record whose constructor runs next that the values of `keys` are
20
+ * valid as they are. For a class that makes values for its own record on the
21
+ * way to `super()`, as an entity does of its id.
22
+ * @internal
23
+ */
24
+ export declare function vouchFor(keys: readonly string[]): void;
18
25
  /** How `recordDataOf` stores the records a record holds. */
19
26
  export interface RecordDataOptions {
20
27
  /**
@@ -120,7 +120,18 @@ type JsonField<Field, Data> = Field extends {
120
120
  readonly properties: infer Properties extends IBlueprintCollection;
121
121
  } ? Data extends object ? JsonKeys<Properties, Data, never> : Data : Field extends {
122
122
  readonly item: infer Item;
123
- } ? Data extends readonly (infer ItemData)[] ? JsonField<Item, ItemData>[] : Data : Data;
123
+ } ? Data extends readonly (infer ItemData)[] ? JsonField<Item, ItemData>[] : Data : Field extends {
124
+ readonly key: infer Key extends string;
125
+ readonly variants: infer Variants;
126
+ } ? JsonVariant<Key, Variants, Data> : Data;
127
+ /** What `toJSON` gives for the value `Data` of a variant field: each variant by its own blueprint. */
128
+ type JsonVariant<Key extends string, Variants, Data> = Data extends {
129
+ readonly [Name in Key]: infer Tag extends keyof Variants;
130
+ } ? Variants[Tag] extends AnyRecordClass ? {
131
+ readonly [Name in Key]: Tag;
132
+ } & JsonOfRecord<Variants[Tag], Data> : Variants[Tag] extends {
133
+ readonly properties: infer Properties extends IBlueprintCollection;
134
+ } ? JsonKeys<Properties, Data, never> : Data : Data;
124
135
  /** What the `toJSON` of the records of `Class` gives, in place of their plain value. */
125
136
  type JsonOfRecord<Class extends AnyRecordClass, Data> = InstanceType<Class> extends {
126
137
  toJSON(): infer Json;
@@ -18,6 +18,19 @@ export declare const InvalidRepositoryDeclaration: import("../../index.js").Decl
18
18
  }>;
19
19
  /** A method of a repository got a value that is not an entity of it. */
20
20
  export declare const InvalidRepositoryEntity: import("../../index.js").DeclaredExceptionClass<undefined>;
21
+ /**
22
+ * A stored row holds a schema version (`SCHEMA_VERSION_KEY`) that is not a
23
+ * positive integer, or that is above the `schemaVersion` of its entity (it
24
+ * was written by newer code), or an `upgrade` step did not give an object.
25
+ * `source` is the repository name; reads throw it as the cause of
26
+ * `InvalidRepositoryData`.
27
+ */
28
+ export declare const InvalidStoredSchemaVersion: import("../../index.js").DeclaredExceptionClass<{
29
+ readonly id: string;
30
+ readonly stored: unknown;
31
+ readonly current: number;
32
+ readonly reason?: string;
33
+ }>;
21
34
  /** A provider gave a value its entity rejects; `source` is the repository name. */
22
35
  export declare const InvalidRepositoryData: import("../../index.js").DeclaredExceptionClass<undefined>;
23
36
  /** Another entity holds the value of a unique key; `source` is the repository name. */
@@ -13,8 +13,8 @@ export type InMemoryProvider = IRepositoryProvider & {
13
13
  };
14
14
  };
15
15
  /**
16
- * A provider that keeps the values in memory, one table per entity class,
17
- * enforcing its unique keys. Each `repositoryOf` without a provider gets its
16
+ * A provider that keeps the values in memory, one table per entity name it
17
+ * declares (or per entity class, without one), enforcing its unique keys. Each `repositoryOf` without a provider gets its
18
18
  * own; repositories given the same one share the tables of their entities.
19
19
  * Its `transaction` works on isolated snapshots: commits on success, discards
20
20
  * on rejection, runs one at a time and makes other operations wait; nested
@@ -0,0 +1,20 @@
1
+ import { type RepositoryState } from "./state.js";
2
+ import type { EntityMigrationReport, SchemaPlan } from "./types/schema.interface.js";
3
+ /** How `migrateRepository` walks one repository. */
4
+ export interface RepositoryMigration {
5
+ readonly mode: "check" | "apply";
6
+ readonly destructive: boolean;
7
+ readonly pageSize: number;
8
+ /** Given to `provider.migrate` in `apply`, before the rows are walked. */
9
+ readonly plan: SchemaPlan;
10
+ }
11
+ /**
12
+ * Brings the stored rows of the repository of `state` up to the current
13
+ * shape of its entity (see `app.migrate`): in `apply`, first gives
14
+ * `options.plan` to `provider.migrate`, then rewrites the rows below the
15
+ * current version or with a `renamedFrom` or `fill` pending, and moves the
16
+ * rows of each previous name. Keys no longer declared, and the rows of the
17
+ * previous names, are only removed when `destructive`. Idempotent: a second
18
+ * run finds every valid row up to date.
19
+ */
20
+ export declare function migrateRepository(state: RepositoryState, options: RepositoryMigration): Promise<EntityMigrationReport>;
@@ -1,3 +1,4 @@
1
+ import { SCHEMA_VERSION_KEY } from "../entity/abstract-entity.js";
1
2
  import { type EnvironmentDependencies } from "../environment/environment.js";
2
3
  import type { ILogger } from "../logger/logger.js";
3
4
  import type { RepositoryIndex, RepositoryScans } from "./types/query.interface.js";
@@ -27,6 +28,7 @@ export declare const states: WeakMap<object, RepositoryState>;
27
28
  export declare function findState(repository: unknown): RepositoryState | undefined;
28
29
  /** The state of the repository class of `repository`, or of a class it extends. */
29
30
  export declare function stateOf(repository: object): RepositoryState;
31
+ export { SCHEMA_VERSION_KEY };
30
32
  /** The context of the provider, with the environment read on first use. */
31
33
  export declare function contextOf(state: RepositoryState): RepositoryContext;
32
34
  /** What an application reads from a repository, without reaching its state. */
@@ -4,6 +4,7 @@ import type { AnyEntityClass } from "../../entity/event.js";
4
4
  import type { RecordSettings } from "../../record/types/record.interface.js";
5
5
  import type { AbstractRepository } from "../repository.js";
6
6
  import type { ProviderCapabilities, ProviderQuery, RepositoryExists, RepositoryExplain, RepositoryFindCursor, RepositoryFindMany, RepositoryFindPage, RepositoryForEachPage, RepositoryIndex, RepositoryQuery } from "./query.interface.js";
7
+ import type { SchemaPlan } from "./schema.interface.js";
7
8
  /** Any class made by `entityOf`, or extending one, that a repository holds. */
8
9
  export type AnyRepositoryEntity = AnyRecordClass & AnyEntityClass & {
9
10
  readonly [EntityUniqueKeys]: readonly string[];
@@ -154,6 +155,12 @@ export interface ProviderIncrementOptions {
154
155
  */
155
156
  readonly now?: string;
156
157
  }
158
+ /** `RepositoryContext.schema`. */
159
+ export interface RepositorySchema {
160
+ readonly version: number;
161
+ readonly key: string;
162
+ readonly previousNames: readonly string[];
163
+ }
157
164
  /** What a provider gets with every call: whose entities, and the environment. */
158
165
  export interface RepositoryContext<Environment = unknown> {
159
166
  /** The entity class; name a table after `recordNameOf(entity)`, not `entity.name`. */
@@ -180,6 +187,13 @@ export interface RepositoryContext<Environment = unknown> {
180
187
  readonly expiry?: {
181
188
  readonly key: string;
182
189
  };
190
+ /**
191
+ * For an entity declared with `schemaVersion`: the current version, the
192
+ * key of each row holding it (`SCHEMA_VERSION_KEY`, which repositories
193
+ * write and strip, so a provider keeping it in a column of its own maps
194
+ * that key), and the names the entity had before.
195
+ */
196
+ readonly schema?: RepositorySchema;
183
197
  /** The variables of the `environmentDependencies` of the repository. */
184
198
  readonly env: Environment;
185
199
  }
@@ -189,6 +203,14 @@ export interface RepositoryContext<Environment = unknown> {
189
203
  * `RepositoryUniqueViolation`; `update` and `remove` throw
190
204
  * `RepositoryEntityNotFound` for an unknown id.
191
205
  */
206
+ /**
207
+ * What `IRepositoryProvider.migrate` gives when it did not apply the plan to
208
+ * the store (it wrote it to a file to be run later): `app.migrate` skips the
209
+ * backfill of the entity and reports it `deferred`.
210
+ */
211
+ export type ProviderMigrationResult = {
212
+ readonly deferred: true;
213
+ };
192
214
  export interface IRepositoryProvider<Environment = unknown> {
193
215
  findOne(context: RepositoryContext<Environment>, query?: ProviderQuery): Promise<RepositoryData | undefined>;
194
216
  /** `query.after` is only given to a provider with `capabilities.keyset`. */
@@ -218,5 +240,16 @@ export interface IRepositoryProvider<Environment = unknown> {
218
240
  transaction?<Result>(run: (provider: IRepositoryProvider<Environment>) => Promise<Result>): Promise<Result>;
219
241
  /** Releases what the provider holds (connections, files); `app.stop()` calls it. */
220
242
  close?(): void | Promise<void>;
243
+ /**
244
+ * Applies the structural part of `plan` (columns, tables) for a provider
245
+ * with a schema of its own, as SQL; `app.migrate({ mode: "apply" })` calls
246
+ * it before the backfill. Document stores leave it out: they rely on the
247
+ * upgrade on read and the backfill. Only destructive changes when
248
+ * `plan.destructive`. Gives `{ deferred: true }` when it did not apply
249
+ * the plan to the store (it wrote it to a file to be run later): the
250
+ * backfill of the entity is then skipped, as its rows cannot be written
251
+ * in the new shape yet.
252
+ */
253
+ migrate?(context: RepositoryContext<Environment>, plan: SchemaPlan): Promise<void | ProviderMigrationResult>;
221
254
  }
222
255
  export {};
@@ -0,0 +1,126 @@
1
+ import type { Issue } from "../../parse/types/parse.interface.js";
2
+ /**
3
+ * Schema evolution: the lock of the stored shapes, the changes between two of
4
+ * them, and the plan a provider applies (`IRepositoryProvider.migrate`).
5
+ */
6
+ /** What a change between two shapes is. */
7
+ export type SchemaChangeKind = "added-required" | "added-optional" | "removed" | "renamed" | "type-changed" | "tightened" | "loosened" | "made-optional" | "made-required" | "metadata" | "unique-added" | "unique-removed" | "entity-added" | "entity-removed" | "entity-renamed";
8
+ /**
9
+ * Whether afora handles a change alone (`automatic`), needs it declared
10
+ * (`fill`, `renamedFrom`, `previousNames`, an `upgrade` step: `declared`), or
11
+ * needs the stored rows checked before it holds (`check`).
12
+ */
13
+ export type SchemaChangeClass = "automatic" | "declared" | "check";
14
+ /** One change of an entity between the lock and the code. */
15
+ export interface SchemaChange {
16
+ /** The entity, by its current name (its previous one when removed). */
17
+ readonly entity: string;
18
+ /** The path of the key, dot separated for nested blueprints; absent for the entity itself. */
19
+ readonly key?: string;
20
+ readonly change: SchemaChangeKind;
21
+ readonly class: SchemaChangeClass;
22
+ /** Whether the code declares what the change needs (always true when `automatic`). */
23
+ readonly satisfied: boolean;
24
+ /** Whether applying it deletes stored data (a removed key, the rows of an old name). */
25
+ readonly destructive: boolean;
26
+ /** For `renamed` and `entity-renamed`, the old name. */
27
+ readonly from?: string;
28
+ /** What is missing or what to check, for messages and stubs. */
29
+ readonly detail?: string;
30
+ }
31
+ /** A key of an entity in the lock. */
32
+ export interface SchemaLockField {
33
+ /** The JSON Schema of the value. */
34
+ readonly schema: Readonly<Record<string, unknown>>;
35
+ readonly optional: boolean;
36
+ readonly unique?: boolean;
37
+ readonly fill?: unknown;
38
+ readonly renamedFrom?: string;
39
+ /** The keys of a nested blueprint or record. */
40
+ readonly fields?: Readonly<Record<string, SchemaLockField>>;
41
+ }
42
+ /** An entity in the lock. */
43
+ export interface SchemaLockEntity {
44
+ readonly schemaVersion: number;
45
+ readonly previousNames: readonly string[];
46
+ readonly unique: readonly string[];
47
+ readonly fields: Readonly<Record<string, SchemaLockField>>;
48
+ /**
49
+ * The versions its `upgrade` has a step to, ascending; absent when none.
50
+ * `diffSchemas` reads it from the current side to tell whether a change
51
+ * that needs a step has one.
52
+ */
53
+ readonly upgrade?: readonly number[];
54
+ }
55
+ /** `afora.lock.json`: the stored shapes, by entity name, normalized. */
56
+ export interface SchemaLock {
57
+ readonly lockVersion: 1;
58
+ readonly entities: Readonly<Record<string, SchemaLockEntity>>;
59
+ }
60
+ /** What `IRepositoryProvider.migrate` gets: the changes of one entity. */
61
+ export interface SchemaPlan {
62
+ /** The entity, by its current name. */
63
+ readonly entity: string;
64
+ readonly changes: readonly SchemaChange[];
65
+ /** Whether destructive changes may be applied (`app.migrate({ destructive })`). */
66
+ readonly destructive: boolean;
67
+ }
68
+ /** What `app.migrate` takes. */
69
+ export interface SchemaMigrationOptions {
70
+ /** The entities to walk, by name; every entity with a repository by default. */
71
+ readonly entities?: readonly string[];
72
+ /** `check` only reads and reports; `apply` rewrites the rows. */
73
+ readonly mode: "check" | "apply";
74
+ /**
75
+ * Remove the keys the blueprints no longer declare from the stored rows,
76
+ * and the rows under `previousNames` once moved. Default `false`.
77
+ */
78
+ readonly destructive?: boolean;
79
+ /** Rows read per page. Default 100. */
80
+ readonly pageSize?: number;
81
+ /**
82
+ * The committed lock (`afora.lock.json`): in `apply`, the changes of each
83
+ * entity from it to the code are the plan given to
84
+ * `IRepositoryProvider.migrate`. Without it the plan has no changes.
85
+ */
86
+ readonly lock?: SchemaLock;
87
+ }
88
+ /** A row `app.migrate` cannot bring up to the current shape. */
89
+ export interface SchemaMigrationInvalidRow {
90
+ readonly id: string;
91
+ /** Why: the issues of the parse, or one for an error of the upgrade. */
92
+ readonly issues: readonly Issue[];
93
+ }
94
+ /** What `app.migrate` found and did for one entity. */
95
+ export interface EntityMigrationReport {
96
+ /** Rows read, under the current name and the previous ones. */
97
+ readonly scanned: number;
98
+ /** Rows that needed nothing. */
99
+ readonly upToDate: number;
100
+ /** Rows rewritten in the current shape (in `check`, that would be). */
101
+ readonly upgraded: number;
102
+ /**
103
+ * Rows whose only pending change is removing keys, left as they are
104
+ * without `destructive`.
105
+ */
106
+ readonly removalPending: number;
107
+ /** Rows the entity rejects once brought up, left as they are. */
108
+ readonly invalid: readonly SchemaMigrationInvalidRow[];
109
+ /** Rows moved from a previous name (in `check`, that would be). */
110
+ readonly movedFromPreviousNames: number;
111
+ /** Paths of the stored keys the blueprint no longer declares, sorted. */
112
+ readonly removedKeys: readonly string[];
113
+ /**
114
+ * Whether `provider.migrate` deferred the plan (`{ deferred: true }`, it
115
+ * wrote it to be run later): the rows were then not walked, and every
116
+ * count is 0. Run `app.migrate` again once the plan is applied.
117
+ */
118
+ readonly deferred: boolean;
119
+ }
120
+ /** What `app.migrate` gives. */
121
+ export interface SchemaMigrationReport {
122
+ readonly mode: "check" | "apply";
123
+ readonly destructive: boolean;
124
+ /** By entity name. */
125
+ readonly entities: Readonly<Record<string, EntityMigrationReport>>;
126
+ }
@@ -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;