@afora/core 0.0.5 → 0.0.7

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 (50) hide show
  1. package/README.md +4 -1
  2. package/dist/core/application/types/application.interface.d.ts +56 -11
  3. package/dist/core/blueprint/types/slice.interface.d.ts +5 -2
  4. package/dist/core/command/command.d.ts +11 -4
  5. package/dist/core/command/types/command.interface.d.ts +19 -0
  6. package/dist/core/entity/abstract-entity.d.ts +1 -1
  7. package/dist/core/entity/types/entity.interface.d.ts +13 -7
  8. package/dist/core/exception/exception.d.ts +10 -1
  9. package/dist/core/output/status.d.ts +4 -3
  10. package/dist/core/property/input.d.ts +20 -0
  11. package/dist/core/property/property.d.ts +4 -4
  12. package/dist/core/property/types/optional-property.type.d.ts +2 -2
  13. package/dist/core/property/types/property-type.interface.d.ts +17 -0
  14. package/dist/core/record/abstract-record.d.ts +4 -4
  15. package/dist/core/record/exceptions.d.ts +2 -0
  16. package/dist/core/record/name.d.ts +16 -0
  17. package/dist/core/record/plugin.d.ts +6 -2
  18. package/dist/core/record/record.d.ts +4 -3
  19. package/dist/core/record/types/plugin.interface.d.ts +11 -0
  20. package/dist/core/record/types/record.interface.d.ts +71 -10
  21. package/dist/core/repository/cursor.d.ts +13 -0
  22. package/dist/core/repository/exceptions.d.ts +10 -0
  23. package/dist/core/repository/query.d.ts +4 -4
  24. package/dist/core/repository/repository.d.ts +2 -2
  25. package/dist/core/repository/state.d.ts +9 -0
  26. package/dist/core/repository/types/query.interface.d.ts +37 -0
  27. package/dist/core/repository/types/repository.interface.d.ts +17 -6
  28. package/dist/core/use-case/exceptions.d.ts +36 -0
  29. package/dist/core/use-case/plugin-catalog.d.ts +11 -0
  30. package/dist/core/use-case/plugin.d.ts +25 -0
  31. package/dist/core/use-case/steps/types/steps.interface.d.ts +24 -7
  32. package/dist/core/use-case/target.d.ts +9 -0
  33. package/dist/core/use-case/types/plugin.interface.d.ts +185 -0
  34. package/dist/experimental.js +4 -3
  35. package/dist/{index-8m80wjzf.js → index-q6hggpzw.js} +1210 -85
  36. package/dist/{index-hn82hm31.js → index-r78dsvtc.js} +136 -66
  37. package/dist/index.d.ts +22 -12
  38. package/dist/index.js +47 -5
  39. package/dist/std/command.d.ts +11 -0
  40. package/dist/std/properties/password.d.ts +21 -2
  41. package/dist/std/record-plugins.d.ts +5 -5
  42. package/dist/std/use-cases/auth.d.ts +5 -0
  43. package/dist/std/use-cases/crud.d.ts +23 -0
  44. package/dist/std/use-cases/index.d.ts +11 -0
  45. package/dist/std/use-cases/lifecycle.d.ts +13 -0
  46. package/dist/std/use-cases/shared.d.ts +60 -0
  47. package/dist/std/use-cases/types.d.ts +212 -0
  48. package/dist/testing/index.js +99 -2
  49. package/dist/testing/provider-contract.d.ts +5 -0
  50. package/package.json +1 -1
package/README.md CHANGED
@@ -11,6 +11,7 @@ afora is not a general-purpose validator. It is for code that models a domain, a
11
11
  - **Exceptions that know their layer.** Each error carries a layer (`Property`, `Record`, `UseCase`, `Output`...) and a `trace` that grows as it goes up.
12
12
  - **Standard Schema V1 on every field.** Properties, blueprints, lists and record classes work as is in Hono, tRPC and TanStack Form, and `safeParse` never throws for an invalid value.
13
13
  - **Commands and outputs.** `commandOf` groups use cases behind one execution boundary (context, transactions, event policies); `outputOf` turns commands into operations and pipes them to a sink: an HTTP API with `@afora/hono`, an OpenAPI 3.1 document, or MCP tools, with problem+json errors and masked responses.
14
+ - **Standard use cases.** `commandOf({ useCases: (ctx) => ({ create: ctx.create(Posts), publish: ctx.publish(Posts) }) })` picks ready-made use cases that follow the repository and its entity: the actions it declares, its record plugins and the properties of its keys ([docs](docs/std-use-cases.md)).
14
15
  - **The whole application in JSON.** An `afora.json` generates the code (`@afora/codegen`), use cases included, as data (steps), and `extractSpec(app)` gives back the `afora.json` of an application written by hand.
15
16
  - **Repositories on any store.** A provider contract with typed queries, soft deletion and transactions, a runner-agnostic kit to test one (`@afora/core/testing`), in memory by default and on Drizzle ORM with `@afora/drizzle`.
16
17
 
@@ -304,7 +305,7 @@ Every field (a property, blueprint, list or record class) implements Standard Sc
304
305
 
305
306
  ## Example
306
307
 
307
- `examples/blog` is a blog (users with the `authenticatable` plugin, posts with soft deletion, use cases written as steps, a Hono API on Drizzle and SQLite) whose whole `src/domain` is generated from one `afora.json` by `@afora/codegen`. Only the database, the schema, the provider (made from a `Database` environment with `providerFrom` when the server calls `App.start()`), a password hashing function and the server are hand-written. Its tests check that the committed code is what the spec generates, drive the API end to end and extract the spec back from the application: `bun run test:blog`.
308
+ `examples/blog` is a blog (users with the `authenticatable` plugin, posts with soft deletion, every use case a [standard one](docs/std-use-cases.md), a Hono API on Drizzle and SQLite) whose whole `src/domain` is generated from one `afora.json` by `@afora/codegen`. Only the database, the schema, the provider (made from a `Database` environment with `providerFrom` when the server calls `App.start()`, and named `database` in the `providers` of the spec) and the server are hand-written. Its tests check that the committed code is what the spec generates, drive the API end to end and extract the spec back from the application: `bun run test:blog`.
308
309
 
309
310
  ## Performance
310
311
 
@@ -335,6 +336,8 @@ Where the others win: Zod and Valibot are faster, have far larger ecosystems and
335
336
 
336
337
  ## Contributing
337
338
 
339
+ Issues open from a template: bug report, feature request, or feedback from building on afora. Questions go to [Discussions](https://github.com/afora-framework/core/discussions). Pull requests follow [the PR template](.github/PULL_REQUEST_TEMPLATE.md).
340
+
338
341
  Commits follow [Conventional Commits](https://www.conventionalcommits.org) (`feat(repository): …`, `fix: …`), checked by commitlint in a Husky `commit-msg` hook; the `pre-commit` hook runs Biome and Knip. CI runs lint, Knip, type checks, unit and interop tests, the build and the Node smoke test on every push and pull request. Pushing a `v*` tag that matches `package.json` publishes to npm with provenance.
339
342
 
340
343
  Scripts:
@@ -1,3 +1,4 @@
1
+ import type { AnyBlueprint, BlueprintInput, IBlueprint } from "../../blueprint/types/blueprint.interface.js";
1
2
  import type { AnyCommand } from "../../command/types/command.interface.js";
2
3
  import type { AnyEnvironment, EnvironmentValue } from "../../environment/environment.js";
3
4
  import type { ExceptionLayers } from "../../exception/enums/exception-layers.js";
@@ -8,10 +9,32 @@ import type { ProviderDescription } from "../../repository/types/query.interface
8
9
  import type { AnyRepository, IRepositoryProvider } from "../../repository/types/repository.interface.js";
9
10
  import type { Kind } from "../../symbols/kind.js";
10
11
  import type { UseCaseStepDescription } from "../../use-case/steps/types/steps.interface.js";
11
- import type { AnyExceptionClass, AnyUseCaseClass } from "../../use-case/use-case.js";
12
+ import type { AnyExceptionClass, AnyUseCaseClass, UseCaseInput, UseCaseResponse } from "../../use-case/use-case.js";
12
13
  import type { ProviderSlot, ProviderStorage } from "../providers.js";
13
14
  import type { LogFactory, ProviderFactory } from "../start.js";
14
15
  type Named<Value> = Readonly<Record<string, Value>>;
16
+ /**
17
+ * The options of `app.run`: the `context` of the use case, required when it
18
+ * declares one, and `dependencies`, what its `execute` takes to replace them.
19
+ */
20
+ export type ApplicationRunOptions<UseCase extends AnyUseCaseClass> = (UseCase["context"] extends IBlueprint<infer Properties> ? {
21
+ readonly context: BlueprintInput<Properties>;
22
+ } : {
23
+ readonly context?: undefined;
24
+ }) & {
25
+ readonly dependencies?: Parameters<InstanceType<UseCase>["execute"]>[0];
26
+ };
27
+ /**
28
+ * What `app.run` takes after the use case: its input, optional when no key
29
+ * of it is required, and the options, required when it declares a context.
30
+ */
31
+ export type ApplicationRunArgs<UseCase extends AnyUseCaseClass> = UseCase["context"] extends AnyBlueprint ? [input: UseCaseInput<UseCase>, options: ApplicationRunOptions<UseCase>] : {} extends UseCaseInput<UseCase> ? [
32
+ input?: UseCaseInput<UseCase>,
33
+ options?: ApplicationRunOptions<UseCase>
34
+ ] : [
35
+ input: UseCaseInput<UseCase>,
36
+ options?: ApplicationRunOptions<UseCase>
37
+ ];
15
38
  /** The declarations of an application, by name, one collection for each kind. */
16
39
  export interface ApplicationCollections {
17
40
  readonly properties: Named<unknown>;
@@ -115,25 +138,29 @@ export interface IApplication<Name extends string, Collections extends Applicati
115
138
  readonly entities: Collections["entities"];
116
139
  readonly environments: Collections["environments"];
117
140
  /**
118
- * Its repositories, inherited ones included. With a `provider` they are
119
- * bound to it: they share the declaration of the original but read and
120
- * write through that provider (unless they declared their own).
141
+ * Its repositories, inherited ones included. With a `provider` (or
142
+ * `providers`) they are bound to it: they share the declaration of the
143
+ * original but read and write through that provider (unless they declared
144
+ * their own).
121
145
  */
122
146
  readonly repositories: Collections["repositories"];
123
147
  /**
124
- * Its use cases. With a `provider` they are subclasses whose repositories
125
- * are the bound ones, through child use cases too; the same subclass every
126
- * time. Without one they are the declared classes.
148
+ * Its use cases. With a `provider`, `providers` or `log` they are
149
+ * subclasses whose repositories are the bound ones, through child use
150
+ * cases too; the same subclass every time. Without any of them they are
151
+ * the declared classes.
127
152
  */
128
153
  readonly useCases: Collections["useCases"];
129
154
  /**
130
- * Its commands. With a `provider` they are rebuilt over the bound use
131
- * cases (same settings), memoized; without one, the declared commands.
155
+ * Its commands. With a `provider`, `providers` or `log` they are rebuilt
156
+ * over the bound use cases (same settings), memoized; without any of them,
157
+ * the declared commands.
132
158
  */
133
159
  readonly commands: Collections["commands"];
134
160
  /**
135
- * Its outputs. With a `provider` they are rebuilt over the bound commands
136
- * (same name, statuses and stages), memoized; without one, the declared outputs.
161
+ * Its outputs. With a `provider`, `providers` or `log` they are rebuilt
162
+ * over the bound commands (same name, statuses and stages), memoized;
163
+ * without any of them, the declared outputs.
137
164
  */
138
165
  readonly outputs: Collections["outputs"];
139
166
  /** Its exceptions. */
@@ -142,6 +169,13 @@ export interface IApplication<Name extends string, Collections extends Applicati
142
169
  repository<Repository extends AnyRepository>(repository: Repository): Repository;
143
170
  /** The bound version of a use case registered in the application. */
144
171
  useCase<UseCase extends AnyUseCaseClass>(useCase: UseCase): UseCase;
172
+ /**
173
+ * Runs a use case registered in the application over its bound
174
+ * repositories: `new (app.useCase(UseCase))(input, { context }).execute(dependencies)`.
175
+ * It rejects with what the constructor or `execute` throw. The events the
176
+ * use case collected are not handled; a command is what dispatches them.
177
+ */
178
+ run<UseCase extends AnyUseCaseClass>(useCase: UseCase, ...args: ApplicationRunArgs<UseCase>): Promise<UseCaseResponse<UseCase>>;
145
179
  /** The bound version of a command registered in the application. */
146
180
  command<Command extends AnyCommand>(command: Command): Command;
147
181
  /** The bound version of an output registered in the application. */
@@ -306,6 +340,17 @@ export interface UseCaseDescription {
306
340
  } | {
307
341
  readonly kind: "opaque";
308
342
  };
343
+ /**
344
+ * The use case plugin that made it, the registered repository it was made
345
+ * for and the options given, with classes by name as in the steps; absent
346
+ * for a use case declared by hand.
347
+ */
348
+ readonly generic?: {
349
+ readonly name: string;
350
+ readonly version: string;
351
+ readonly repository: string;
352
+ readonly options: Readonly<Record<string, unknown>>;
353
+ };
309
354
  }
310
355
  /** A command: its metadata, context, operations and policies. */
311
356
  export interface CommandDescription {
@@ -1,4 +1,4 @@
1
- import type { RecordUniqueKeys } from "../../record/types/plugin.interface.js";
1
+ import type { RecordSensitiveKeys, RecordUniqueKeys } from "../../record/types/plugin.interface.js";
2
2
  import type { RecordConstructor } from "../../record/types/record.interface.js";
3
3
  import type { Meta } from "../../symbols/meta.js";
4
4
  import type { AnyBlueprint, AnyBlueprintList, AnyOptionalRecord, AnyRecordClass, BlueprintData, BlueprintField, IBlueprint, IBlueprintCollection, IBlueprintList, OptionalRecord } from "./blueprint.interface.js";
@@ -37,13 +37,16 @@ type SlicedRecord<Original extends AnyRecordClass, Mask> = SlicedProperties<Orig
37
37
  readonly [Meta]: {
38
38
  readonly blueprint: Original["blueprint"];
39
39
  };
40
- }> : never;
40
+ }, KeptSensitive<Original, keyof Properties>> : never;
41
41
  type KeptChange<Instance, Keys> = Instance extends {
42
42
  readonly change: infer Change;
43
43
  } ? Extract<keyof Change, Keys & string> : never;
44
44
  type KeptUnique<Original, Keys> = Original extends {
45
45
  readonly [RecordUniqueKeys]?: infer Unique;
46
46
  } ? Extract<Unique, Keys & string> : never;
47
+ type KeptSensitive<Original, Keys> = Original extends {
48
+ readonly [RecordSensitiveKeys]?: infer Sensitive;
49
+ } ? Extract<Sensitive, Keys & string> : never;
47
50
  /** The properties of `Blueprint`, read lazily so generic uses stay shallow. */
48
51
  export type PropertiesOf<Blueprint> = Blueprint extends IBlueprint<infer Properties, boolean> ? Properties : never;
49
52
  /** The blueprint of `Properties` that keeps only the keys of `Mask`. */
@@ -1,7 +1,6 @@
1
- import type { IBlueprintCollection } from "../blueprint/types/blueprint.interface.js";
2
- import { type EnvironmentDependencies } from "../environment/environment.js";
3
1
  import type { IRepositoryProvider } from "../repository/types/repository.interface.js";
4
- import type { AnyCommand, CommandOf, CommandSettings, CommandUseCases } from "./types/command.interface.js";
2
+ import type { IUseCaseCatalog, UseCasePluginContext } from "../use-case/types/plugin.interface.js";
3
+ import type { AnyCommand, CommandBuilder } from "./types/command.interface.js";
5
4
  /**
6
5
  * Checks now that the repositories of `command` share a provider that has
7
6
  * `transaction`, as its first call would; an application calls it when it
@@ -15,11 +14,19 @@ export declare function verifyCommand(command: object): void;
15
14
  * @internal
16
15
  */
17
16
  export declare function commandTransactionProvider(command: object): IRepositoryProvider | undefined;
17
+ /**
18
+ * A `commandOf` whose `useCases` may be a function of the use case plugins
19
+ * of `catalogs`, by name, a later catalog winning a name:
20
+ * `useCases: (ctx) => ({ create: ctx.create(Posts), custom: MyUseCase })`.
21
+ * Throws `InvalidCommandDeclaration` for what is not a catalog made by
22
+ * `useCaseCatalogOf`.
23
+ */
24
+ export declare function commandBuilderOf<const Catalogs extends readonly IUseCaseCatalog[] = []>(...catalogs: Catalogs): CommandBuilder<UseCasePluginContext<Catalogs>>;
18
25
  /**
19
26
  * Groups use cases under operation names and is the boundary of a call: the
20
27
  * context is validated once, a transaction can wrap the use case, and the
21
28
  * events it raised are handled by a policy once it succeeded.
22
29
  */
23
- export declare function commandOf<const UseCases extends CommandUseCases, Context extends IBlueprintCollection | undefined = undefined, Environment extends EnvironmentDependencies | undefined = undefined>(settings: CommandSettings<UseCases, Context, Environment>): CommandOf<UseCases, Context, Environment>;
30
+ export declare const commandOf: CommandBuilder<Record<never, never>>;
24
31
  /** Whether `value` is a command made by `commandOf`. */
25
32
  export declare function isCommand(value: unknown): value is AnyCommand;
@@ -48,6 +48,25 @@ export interface CommandSettings<UseCases extends CommandUseCases, Context exten
48
48
  readonly events?: CommandEvents;
49
49
  readonly onEventError?: CommandEventErrorHandler;
50
50
  }
51
+ /**
52
+ * The `useCases` of a command made by a builder: the object, or a function
53
+ * of the use case plugins of its catalogs that gives it.
54
+ */
55
+ export type CommandUseCasesOption<UseCases extends CommandUseCases, Generics> = UseCases | ((ctx: Generics) => UseCases);
56
+ /** `CommandSettings` with `useCases` as a builder takes it. */
57
+ export interface CommandBuilderSettings<UseCases extends CommandUseCases, Context extends IBlueprintCollection | undefined, Environment extends EnvironmentDependencies | undefined, Generics> extends Omit<CommandSettings<UseCases, Context, Environment>, "useCases"> {
58
+ /**
59
+ * The use cases, by operation name; or a function that gets the use
60
+ * case plugins of the builder's catalogs, each called with a repository,
61
+ * and gives them: `(ctx) => ({ create: ctx.create(Posts) })`.
62
+ */
63
+ readonly useCases: CommandUseCasesOption<UseCases, Generics>;
64
+ }
65
+ /**
66
+ * What `commandBuilderOf` gives: `commandOf`, with the use case plugins
67
+ * `Generics` to pick from.
68
+ */
69
+ export type CommandBuilder<Generics> = <const UseCases extends CommandUseCases, Context extends IBlueprintCollection | undefined = undefined, Environment extends EnvironmentDependencies | undefined = undefined>(settings: CommandBuilderSettings<UseCases, Context, Environment, Generics>) => CommandOf<UseCases, Context, Environment>;
51
70
  /** The names of the operations of a command. */
52
71
  export type CommandOperation<Command extends {
53
72
  readonly useCases: object;
@@ -17,7 +17,7 @@ export declare const EntityUniqueKeys: unique symbol;
17
17
  * and never changed after, an `updatedAt` set by each change, and a
18
18
  * `deletedAt` set by `destroy`, after which it no longer changes.
19
19
  */
20
- export declare abstract class AbstractEntity<Properties extends IBlueprintCollection> extends AbstractRecord<Properties> {
20
+ export declare abstract class AbstractEntity<Properties extends IBlueprintCollection, Sensitive extends string = never> extends AbstractRecord<Properties, Sensitive> {
21
21
  constructor(value: BlueprintInput<Properties>);
22
22
  /** Queues an event for this entity without running its handler. */
23
23
  protected emit(event: EventDefinition): void;
@@ -1,6 +1,6 @@
1
1
  import type { BlueprintField, BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection, JoinedProperties } from "../../blueprint/types/blueprint.interface.js";
2
2
  import type { ParseResult } from "../../parse/types/parse.interface.js";
3
- import type { AnyRecordPlugin, IRecordPluginCatalog, PluginMembers, PluginUniqueKey, WithPluginProperties } from "../../record/types/plugin.interface.js";
3
+ import type { AnyRecordPlugin, IRecordPluginCatalog, PluginMembers, PluginSensitiveKey, PluginUniqueKey, WithPluginProperties } from "../../record/types/plugin.interface.js";
4
4
  import type { MarkedUniqueKey, RecordChanges, RecordKey, RecordOptions, RecordSettings } from "../../record/types/record.interface.js";
5
5
  import type { StandardSchemaV1 } from "../../standard/standard-schema.js";
6
6
  import type { Kind } from "../../symbols/kind.js";
@@ -45,32 +45,38 @@ export type EntityProperties<Identity extends IEntityIdentity, Properties extend
45
45
  */
46
46
  export type EntityInput<Identity extends IEntityIdentity, Properties extends IBlueprintCollection> = BlueprintInput<Properties> & Partial<BlueprintInput<Identity>>;
47
47
  /** 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> = RecordOptions<Properties, Change, Unique, Plugins, Context> & EntityEvents;
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;
49
49
  /**
50
50
  * The unique keys of an entity: `id`, those listed in `options.unique`, and
51
51
  * those holding a property declared `unique`, as `Slug`.
52
52
  */
53
53
  export type EntityUniqueKey<Identity extends IEntityIdentity, Properties extends IBlueprintCollection, Unique extends readonly string[]> = "id" | Unique[number] | MarkedUniqueKey<EntityProperties<Identity, Properties>>;
54
54
  /** The entities of the class `entityOf` returns, with what its plugins add, `Members`. */
55
- export type DeclaredEntityInstance<Identity extends IEntityIdentity, Properties extends IBlueprintCollection, Change extends readonly string[], Members = unknown> = AbstractEntity<EntityProperties<Identity, Properties>> & Readonly<BlueprintValue<EntityProperties<Identity, Properties>>> & RecordChanges<Properties, Change> & Members;
55
+ export type DeclaredEntityInstance<Identity extends IEntityIdentity, Properties extends IBlueprintCollection, Change extends readonly string[], Members = unknown, Sensitive extends string = never> = AbstractEntity<EntityProperties<Identity, Properties>, Sensitive> & Readonly<BlueprintValue<EntityProperties<Identity, Properties>>> & RecordChanges<Properties, Change> & Members;
56
56
  /** What `entityOf` returns: a record class with identity. */
57
- export type EntityConstructor<Identity extends IEntityIdentity, Properties extends IBlueprintCollection, Change extends readonly string[], Unique extends readonly string[] = [], Members = unknown> = {
57
+ export type EntityConstructor<Identity extends IEntityIdentity, Properties extends IBlueprintCollection, Change extends readonly string[], Unique extends readonly string[] = [], Members = unknown, Sensitive extends string = never> = {
58
58
  readonly [EntityClassBrand]: true;
59
59
  /** The keys no two entities may share, as `options.unique` lists them. */
60
60
  readonly [EntityUniqueKeys]: readonly EntityUniqueKey<Identity, Properties, Unique>[];
61
- new (value: EntityInput<Identity, Properties>): DeclaredEntityInstance<Identity, Properties, Change, Members>;
61
+ new (value: EntityInput<Identity, Properties>): DeclaredEntityInstance<Identity, Properties, Change, Members, Sensitive>;
62
62
  /**
63
63
  * The Standard Schema of the class: `validate` gives an instance of it, or
64
64
  * the issues that reject the value. Typed with the instance of the class
65
65
  * `entityOf` returns, not of a subclass.
66
66
  */
67
- readonly "~standard": StandardSchemaV1.Props<EntityInput<Identity, Properties>, DeclaredEntityInstance<Identity, Properties, Change, Members>>;
67
+ readonly "~standard": StandardSchemaV1.Props<EntityInput<Identity, Properties>, DeclaredEntityInstance<Identity, Properties, Change, Members, Sensitive>>;
68
68
  /**
69
69
  * An instance of the class (a subclass included) for `value`, or every
70
70
  * issue that rejects it; never throws for an invalid value.
71
71
  */
72
72
  safeParse<Self extends new (value: never) => object>(this: Self, value: unknown): ParseResult<InstanceType<Self>>;
73
73
  readonly [Kind]: "record";
74
+ /**
75
+ * The name of the class as declared: the `name` option, which a bundler
76
+ * does not rename, or the name of the class without one. A subclass may
77
+ * give its own with `static override readonly recordName = "..."`.
78
+ */
79
+ readonly recordName: string;
74
80
  readonly blueprint: IBlueprint<EntityProperties<Identity, Properties>>;
75
81
  readonly options: RecordSettings<EntityProperties<Identity, Properties>>;
76
82
  /** Its plugins, those they require included, each once. */
@@ -80,4 +86,4 @@ export type EntityConstructor<Identity extends IEntityIdentity, Properties exten
80
86
  * What `entityBuilderOf` returns: `entityOf`, declaring entities with the
81
87
  * identity it was given, whose `options.plugins` picks from `Context`.
82
88
  */
83
- 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[] = []>(blueprint: IBlueprint<Properties, boolean>, options?: EntityOptions<NoInfer<Properties>, Change, Unique, Plugins, Context>) => EntityConstructor<Identity, WithPluginProperties<Properties, Plugins>, Change, readonly (Unique[number] | PluginUniqueKey<Plugins>)[], PluginMembers<Plugins>>;
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>>;
@@ -31,8 +31,17 @@ export declare class Exception<Details = unknown> extends Error {
31
31
  /**
32
32
  * Passes the exception to an upper layer: a new exception in `scope`, with
33
33
  * this one as its cause, keeping its name and message unless given others.
34
+ * With the same name and no new `details`, an exception of a class declared
35
+ * with `exceptionOf` (or of a subclass of one) stays an instance of that
36
+ * class and keeps its `details`, so `instanceof` holds across layers; the
37
+ * constructor of the class does not run again.
34
38
  */
35
- propagate<Next = undefined>({ layer, source, name, message, details, }: ExceptionPropagation<Next>): Exception<Next>;
39
+ propagate(scope: ExceptionScope & {
40
+ readonly name?: undefined;
41
+ readonly message?: string;
42
+ readonly details?: undefined;
43
+ }): Exception<Details>;
44
+ propagate<Next = undefined>(scope: ExceptionPropagation<Next>): Exception<Next>;
36
45
  toJSON(): ExceptionFrame<Details> & {
37
46
  trace: readonly ExceptionFrame[];
38
47
  };
@@ -2,15 +2,16 @@ import { Exception } from "../exception/exception.js";
2
2
  import type { OperationDescriptor, OutputStatuses } from "./types/output.interface.js";
3
3
  /**
4
4
  * The exception that raised `error` under its name: a use case or a command
5
- * passes an exception up as a copy with the same name, no details and the
6
- * original as its cause, so the original is the deepest one with that name.
5
+ * passes an exception up as a copy with the same name and the original as
6
+ * its cause, so the original is the deepest one with that name.
7
7
  */
8
8
  export declare function raisedOf(error: Exception): Exception;
9
9
  /**
10
10
  * The HTTP-style status of an error, in this order: `statuses` by 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
- * a unique violation; 422 for a class in `operation.errors`; 500 for the
13
+ * a unique violation or an entity that already exists; 401 for
14
+ * `UseCaseInvalidCredentials`; 422 for a class in `operation.errors`; 500 for the
14
15
  * rest, errors that are not exceptions included. Exceptions match by name,
15
16
  * or by class for `operation.errors`.
16
17
  */
@@ -0,0 +1,20 @@
1
+ import type { AnyPropertyConstructor } from "./types/property-collection.type.js";
2
+ /**
3
+ * The static of a property class whose callers give another form of its
4
+ * value than the one it stores: a password is given plain and stored as a
5
+ * hash. Classes extending or refining the property keep it.
6
+ */
7
+ export declare const PropertyInput: unique symbol;
8
+ /** What a property class holds under `PropertyInput`. */
9
+ export interface IPropertyInput<Value = unknown> {
10
+ /** The property that validates what a caller gives, of the same value type. */
11
+ readonly accepts: AnyPropertyConstructor;
12
+ /** Turns what a caller gave into the value the property stores. */
13
+ convert(value: Value): Value | Promise<Value>;
14
+ }
15
+ /**
16
+ * What `field` declares under `PropertyInput`; `undefined` for a property
17
+ * that takes its value as it stores it, and for a field that is not a
18
+ * property.
19
+ */
20
+ export declare function propertyInputOf(field: unknown): IPropertyInput | undefined;
@@ -6,20 +6,20 @@ import { AbstractDefineProperty } from "./define-property.js";
6
6
  import type { AnyPropertyConstructor } from "./types/property-collection.type.js";
7
7
  import type { PropertyValue } from "./types/property-constructor.type.js";
8
8
  import type { PropertyOptions } from "./types/property-options.interface.js";
9
- import type { IPropertyType, UniqueMark } from "./types/property-type.interface.js";
9
+ import type { IPropertyType, SensitiveMark, UniqueMark } from "./types/property-type.interface.js";
10
10
  type SchemaValueType<SchemaType extends TSchema, Optional extends boolean = false> = Optional extends true ? TOptional<SchemaType> : SchemaType;
11
11
  /**
12
12
  * What `propertyOf` takes. `SchemaType` is inferred from `schema` alone: the
13
13
  * value is `NoInfer`, since inferring through `Static` makes TypeScript
14
14
  * measure `Static` itself, which costs about a second on every check.
15
15
  */
16
- type PropertyOfArgs<SchemaType extends TSchema, Optional extends boolean = false, Unique extends boolean = false, Value = Static<SchemaType>> = {
16
+ type PropertyOfArgs<SchemaType extends TSchema, Optional extends boolean = false, Unique extends boolean = false, Sensitive extends boolean = false, Value = Static<SchemaType>> = {
17
17
  name: string;
18
18
  schema: SchemaType;
19
19
  default?: NoInfer<Value>;
20
20
  optional?: Optional;
21
21
  /** A record masks its value in `toJSON`, at any depth, as `Password`. */
22
- sensitive?: boolean;
22
+ sensitive?: Sensitive;
23
23
  /**
24
24
  * A record lists a key holding it in `options.unique`, as `Slug`; records
25
25
  * do not check it, repositories (or the database) enforce it.
@@ -53,5 +53,5 @@ export interface PropertyClass<SchemaType extends TSchema, Value = Static<Schema
53
53
  with<Self extends AnyPropertyConstructor>(this: Self, options: PropertyOptions<SchemaType, Value>): Self;
54
54
  demo<Self extends AbstractProperty<any>>(this: new (value: never) => Self): Self;
55
55
  }
56
- export declare function propertyOf<SchemaType extends TSchema, Optional extends boolean = false, const Unique extends boolean = false>({ name, schema, optional, sensitive, unique, default: _default, preHandle: onPreHandle, afterHandle: onAfterHandle, }: PropertyOfArgs<SchemaType, Optional, Unique>): PropertyClass<SchemaValueType<SchemaType, Optional>, Static<SchemaType>> & UniqueMark<Unique>;
56
+ export declare function propertyOf<SchemaType extends TSchema, Optional extends boolean = false, const Unique extends boolean = false, const Sensitive extends boolean = false>({ name, schema, optional, sensitive, unique, default: _default, preHandle: onPreHandle, afterHandle: onAfterHandle, }: PropertyOfArgs<SchemaType, Optional, Unique, Sensitive>): PropertyClass<SchemaValueType<SchemaType, Optional>, Static<SchemaType>> & UniqueMark<Unique> & SensitiveMark<Sensitive>;
57
57
  export {};
@@ -5,7 +5,7 @@ import type { AbstractProperty } from "../abstract-property.js";
5
5
  import type { AnyPropertyConstructor } from "./property-collection.type.js";
6
6
  import type { PropertyValue } from "./property-constructor.type.js";
7
7
  import type { CallableKey, StoredField } from "./property-options.interface.js";
8
- import type { IPropertyType, UniqueTypeOf } from "./property-type.interface.js";
8
+ import type { IPropertyType, SensitiveTypeOf, UniqueTypeOf } from "./property-type.interface.js";
9
9
  /**
10
10
  * `Property` accepting `undefined`: its schema becomes `TOptional`, while its
11
11
  * own methods and statics (e.g. `Password#verify`, `Id.generate`) are kept.
@@ -14,7 +14,7 @@ export type OptionalProperty<Property extends AnyPropertyConstructor> = Property
14
14
  readonly type: IPropertyType<infer SchemaType, infer Value>;
15
15
  } ? SchemaType extends TOptional<TSchema> ? StoredField<Property> : Omit<Property, "type" | "prototype" | "safeParse" | "~standard" | CallableKey> & {
16
16
  new (value: PropertyValue<TOptional<SchemaType>, Value>): AbstractProperty<TOptional<SchemaType>, Value> & Omit<InstanceType<Property>, keyof AbstractProperty<TSchema>>;
17
- readonly type: IPropertyType<TOptional<SchemaType>, Value> & UniqueTypeOf<Property>;
17
+ readonly type: IPropertyType<TOptional<SchemaType>, Value> & UniqueTypeOf<Property> & SensitiveTypeOf<Property>;
18
18
  safeParse(value: unknown): ParseResult<PropertyValue<TOptional<SchemaType>, Value>>;
19
19
  readonly "~standard": StandardSchemaV1.Props<PropertyValue<TOptional<SchemaType>, Value>, PropertyValue<TOptional<SchemaType>, Value>>;
20
20
  } : never;
@@ -25,6 +25,23 @@ export type UniqueMark<Unique extends boolean> = Unique extends true ? {
25
25
  readonly unique: true;
26
26
  };
27
27
  } : unknown;
28
+ /**
29
+ * The type of a property declared `sensitive`, as `Password`, so that the
30
+ * type of `toJSON` masks the keys holding it.
31
+ */
32
+ export type SensitiveMark<Sensitive extends boolean> = Sensitive extends true ? {
33
+ readonly type: {
34
+ readonly sensitive: true;
35
+ };
36
+ } : unknown;
37
+ /** `{ sensitive: true }` when `Property` is declared `sensitive`, for its optional version. */
38
+ export type SensitiveTypeOf<Property> = Property extends {
39
+ readonly type: {
40
+ readonly sensitive: true;
41
+ };
42
+ } ? {
43
+ readonly sensitive: true;
44
+ } : unknown;
28
45
  /** `{ unique: true }` when `Property` is declared `unique`, for its optional version. */
29
46
  export type UniqueTypeOf<Property> = Property extends {
30
47
  readonly type: {
@@ -1,8 +1,8 @@
1
- import type { BlueprintData, BlueprintField, BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection } from "../blueprint/types/blueprint.interface.js";
1
+ import type { BlueprintField, BlueprintInput, BlueprintValue, IBlueprint, IBlueprintCollection } from "../blueprint/types/blueprint.interface.js";
2
2
  import type { ParseResult } from "../parse/types/parse.interface.js";
3
3
  import type { StandardSchemaV1 } from "../standard/standard-schema.js";
4
4
  import { Meta } from "../symbols/meta.js";
5
- import type { IRecord, RecordChange, RecordEffects, RecordSettings } from "./types/record.interface.js";
5
+ import type { IRecord, RecordChange, RecordEffects, RecordJson, RecordSettings } from "./types/record.interface.js";
6
6
  /** The statics of a class made by `recordOf`, that its records read. */
7
7
  interface RecordDeclaration<Properties extends IBlueprintCollection> {
8
8
  readonly blueprint: IBlueprint<Properties>;
@@ -54,7 +54,7 @@ export declare const RecordPluginHooks: unique symbol;
54
54
  * having `key`, then those of the record, for a stored change.
55
55
  */
56
56
  export declare function runRecordEffects<Self extends object>(record: Self, key: PropertyKey, next: unknown, previous: unknown): Self;
57
- export declare abstract class AbstractRecord<Properties extends IBlueprintCollection> implements IRecord<Properties> {
57
+ export declare abstract class AbstractRecord<Properties extends IBlueprintCollection, Sensitive extends string = never> implements IRecord<Properties, Sensitive> {
58
58
  get [Meta](): RecordDeclaration<Properties>;
59
59
  constructor(value: BlueprintInput<Properties>);
60
60
  /**
@@ -72,7 +72,7 @@ export declare abstract class AbstractRecord<Properties extends IBlueprintCollec
72
72
  * The value with the sensitive keys, and every property declared
73
73
  * `sensitive` (as `Password`), masked.
74
74
  */
75
- toJSON(): Readonly<Partial<BlueprintData<Properties>>>;
75
+ toJSON(): RecordJson<Properties, Sensitive>;
76
76
  /**
77
77
  * Validates `value`, stores it under `key`, then runs `onSetAll` and the
78
78
  * effect of `key` in `onSet`.
@@ -9,6 +9,8 @@ export declare const InvalidRecordValue: import("../../index.js").DeclaredExcept
9
9
  readonly errors: readonly string[];
10
10
  readonly issues: readonly Issue[];
11
11
  }>;
12
+ /** `recordOf` or `entityOf` got an option it cannot use, as a `name` that is not an identifier. */
13
+ export declare const InvalidRecordDeclaration: import("../../index.js").DeclaredExceptionClass<string>;
12
14
  /**
13
15
  * A record is given a key its blueprint does not declare, in its value or by
14
16
  * `set`; `key` is its path, e.g. `address.extra`, and `source` the record name.
@@ -0,0 +1,16 @@
1
+ /** Whether `name` can be the `name` option of `recordOf` or `entityOf`. */
2
+ export declare function isRecordName(name: unknown): name is string;
3
+ /**
4
+ * The name of a record or entity class: the `name` option it, or the class it
5
+ * extends, was declared with, which bundlers and minifiers do not change;
6
+ * without one, the name of the class.
7
+ */
8
+ export declare function recordNameOf(Class: object): string;
9
+ /** Keeps the `name` option `Class` was declared with. */
10
+ export declare function declareRecordName(Class: object, name: string): void;
11
+ /**
12
+ * The name `Class` declares: the `name` option of it or of a class it
13
+ * extends, or a `static recordName` of its own; `undefined` when the name of
14
+ * the record is only the name of its class.
15
+ */
16
+ export declare function declaredRecordNameOf(Class: object): string | undefined;
@@ -9,8 +9,12 @@ export declare const RecordPlugins: unique symbol;
9
9
  export declare const IdentifiedRecord: unique symbol;
10
10
  /** Whether `version` is a semantic version. */
11
11
  export declare function isVersion(version: unknown): version is string;
12
- /** The major, minor and patch of a semantic version. */
13
- export declare function versionParts(version: string): readonly [number, number, number];
12
+ /**
13
+ * Whether a plugin of version `available` satisfies a reference to
14
+ * `requested`: the same major version (the same minor below `1.0.0`), and
15
+ * not older.
16
+ */
17
+ export declare function satisfies(available: string, requested: string): boolean;
14
18
  /** The `onSet` and `onSetAll` a plugin runs, for changes to its `keys`. */
15
19
  interface RecordPluginHook {
16
20
  readonly plugin: AnyRecordPlugin;
@@ -1,12 +1,12 @@
1
1
  import type { AnyBlueprint, IBlueprint, IBlueprintCollection } from "../blueprint/types/blueprint.interface.js";
2
2
  import { AbstractRecord } from "./abstract-record.js";
3
- import type { AnyRecordPlugin, IRecordPluginCatalog, PluginMembers, PluginUniqueKey, RecordPluginContext, WithPluginProperties } from "./types/plugin.interface.js";
3
+ import type { AnyRecordPlugin, IRecordPluginCatalog, PluginMembers, PluginSensitiveKey, PluginUniqueKey, RecordPluginContext, WithPluginProperties } from "./types/plugin.interface.js";
4
4
  import type { RecordConstructor, RecordKey, RecordOptions } from "./types/record.interface.js";
5
5
  /**
6
6
  * What `recordBuilderOf` returns: `recordOf`, whose `options.plugins` can
7
7
  * pick plugins from `Context`, the plugins of its catalogs by name.
8
8
  */
9
- export type RecordBuilder<Context> = <Properties extends IBlueprintCollection, const Change extends readonly RecordKey<Properties>[] = [], const Unique extends readonly RecordKey<Properties>[] = [], const Plugins extends readonly AnyRecordPlugin[] = []>(blueprint: IBlueprint<Properties, boolean>, options?: RecordOptions<NoInfer<Properties>, Change, Unique, Plugins, Context>) => RecordConstructor<WithPluginProperties<Properties, Plugins>, Change, Unique[number] | PluginUniqueKey<Plugins>, PluginMembers<Plugins>>;
9
+ export type RecordBuilder<Context> = <Properties extends IBlueprintCollection, 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?: RecordOptions<NoInfer<Properties>, Change, Unique, Plugins, Context, Sensitive>) => RecordConstructor<WithPluginProperties<Properties, Plugins>, Change, Unique[number] | PluginUniqueKey<Plugins>, PluginMembers<Plugins>, never, Sensitive[number] | PluginSensitiveKey<Plugins>>;
10
10
  /**
11
11
  * A `recordOf` whose `options.plugins` can pick plugins from `catalogs`,
12
12
  * e.g. `recordBuilderOf(StdRecordPlugins)`, as `(ctx) => [ctx.publishable]`;
@@ -27,6 +27,7 @@ export declare function pluginContextOf(catalogs: readonly IRecordPluginCatalog[
27
27
  export declare const recordOf: RecordBuilder<Record<never, never>>;
28
28
  /** The options `declareRecord` takes, untyped. */
29
29
  interface RecordOptionsInput {
30
+ readonly name?: string;
30
31
  readonly sensitive?: readonly string[];
31
32
  readonly unique?: readonly string[];
32
33
  readonly change?: readonly string[];
@@ -37,5 +38,5 @@ interface RecordOptionsInput {
37
38
  * and named `name`; `reserved` are more names no key can take, the members
38
39
  * `Base` adds, and `context` the plugins `options.plugins` can pick from.
39
40
  */
40
- export declare function declareRecord(Base: abstract new (value: never) => AbstractRecord<IBlueprintCollection>, declared: AnyBlueprint, { plugins, ...declaredOptions }: RecordOptionsInput, name: string, reserved?: readonly string[], context?: Readonly<Record<string, AnyRecordPlugin>>): abstract new (value: never) => AbstractRecord<IBlueprintCollection>;
41
+ export declare function declareRecord(Base: abstract new (value: never) => AbstractRecord<IBlueprintCollection>, declared: AnyBlueprint, { plugins, name: declaredName, ...declaredOptions }: RecordOptionsInput, fallback: string, reserved?: readonly string[], context?: Readonly<Record<string, AnyRecordPlugin>>): abstract new (value: never) => AbstractRecord<IBlueprintCollection>;
41
42
  export {};
@@ -7,6 +7,11 @@ import type { RecordSettings } from "./record.interface.js";
7
7
  * class holds a value under it.
8
8
  */
9
9
  export declare const RecordUniqueKeys: unique symbol;
10
+ /**
11
+ * Where the type of the keys a record class lists in `options.sensitive` is
12
+ * kept, for the type of `toJSON` of what its plugin is applied to. Type only.
13
+ */
14
+ export declare const RecordSensitiveKeys: unique symbol;
10
15
  /** What `recordPluginOf` takes, after the record class. */
11
16
  export interface RecordPluginOptions {
12
17
  /** A semantic version, e.g. `1.2.0`; `1.0.0` when left out. */
@@ -139,4 +144,10 @@ export type PluginUniqueKey<Plugins extends readonly AnyRecordPlugin[]> = Plugin
139
144
  readonly [RecordUniqueKeys]?: infer Key;
140
145
  };
141
146
  } ? Extract<Key, string> : never : never;
147
+ /** The keys the record classes of the plugins list in `options.sensitive`. */
148
+ export type PluginSensitiveKey<Plugins extends readonly AnyRecordPlugin[]> = Plugins[number] extends infer Plugin ? Plugin extends {
149
+ readonly record: {
150
+ readonly [RecordSensitiveKeys]?: infer Key;
151
+ };
152
+ } ? Extract<Key, string> : never : never;
142
153
  export {};