hexok 1.0.0 → 2.0.0
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.
- package/README.md +9 -0
- package/dist/adapter.d.ts +18 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +21 -0
- package/dist/adapter.js.map +1 -0
- package/dist/catalog.d.ts +88 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +79 -0
- package/dist/catalog.js.map +1 -0
- package/dist/{core/coded-error.d.ts → coded-error.d.ts} +9 -4
- package/dist/coded-error.d.ts.map +1 -0
- package/dist/{core/coded-error.js → coded-error.js} +9 -4
- package/dist/coded-error.js.map +1 -0
- package/dist/cow-draft.d.ts.map +1 -0
- package/dist/cow-draft.js.map +1 -0
- package/dist/entity.d.ts +63 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/{domain/entity.js → entity.js} +52 -80
- package/dist/entity.js.map +1 -0
- package/dist/error-map.d.ts +22 -0
- package/dist/error-map.d.ts.map +1 -0
- package/dist/error-map.js +2 -0
- package/dist/error-map.js.map +1 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +85 -0
- package/dist/errors.js.map +1 -0
- package/dist/event-handler.d.ts +31 -0
- package/dist/event-handler.d.ts.map +1 -0
- package/dist/event-handler.js +28 -0
- package/dist/event-handler.js.map +1 -0
- package/dist/event.d.ts +37 -0
- package/dist/event.d.ts.map +1 -0
- package/dist/event.js +44 -0
- package/dist/event.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/mapper.d.ts +100 -0
- package/dist/mapper.d.ts.map +1 -0
- package/dist/mapper.js +167 -0
- package/dist/mapper.js.map +1 -0
- package/dist/port.d.ts +20 -0
- package/dist/port.d.ts.map +1 -0
- package/dist/port.js +25 -0
- package/dist/port.js.map +1 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js.map +1 -0
- package/dist/schema.d.ts +37 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +50 -0
- package/dist/schema.js.map +1 -0
- package/dist/{core/standard-schema.d.ts → standard-schema.d.ts} +1 -1
- package/dist/standard-schema.d.ts.map +1 -0
- package/dist/standard-schema.js.map +1 -0
- package/dist/use-case.d.ts +183 -0
- package/dist/use-case.d.ts.map +1 -0
- package/dist/use-case.js +221 -0
- package/dist/use-case.js.map +1 -0
- package/dist/{core/schema.d.ts → validate.d.ts} +1 -1
- package/dist/validate.d.ts.map +1 -0
- package/dist/{core/schema.js → validate.js} +1 -1
- package/dist/validate.js.map +1 -0
- package/package.json +16 -26
- package/dist/app/api-use-case.d.ts +0 -52
- package/dist/app/api-use-case.d.ts.map +0 -1
- package/dist/app/api-use-case.js +0 -59
- package/dist/app/api-use-case.js.map +0 -1
- package/dist/app/callable-use-case.d.ts +0 -34
- package/dist/app/callable-use-case.d.ts.map +0 -1
- package/dist/app/callable-use-case.js +0 -41
- package/dist/app/callable-use-case.js.map +0 -1
- package/dist/app/contract.d.ts +0 -46
- package/dist/app/contract.d.ts.map +0 -1
- package/dist/app/contract.js +0 -28
- package/dist/app/contract.js.map +0 -1
- package/dist/app/error-factory.d.ts +0 -9
- package/dist/app/error-factory.d.ts.map +0 -1
- package/dist/app/error-factory.js +0 -30
- package/dist/app/error-factory.js.map +0 -1
- package/dist/app/event-channel.d.ts +0 -133
- package/dist/app/event-channel.d.ts.map +0 -1
- package/dist/app/event-channel.js +0 -49
- package/dist/app/event-channel.js.map +0 -1
- package/dist/app/event-use-case.d.ts +0 -47
- package/dist/app/event-use-case.d.ts.map +0 -1
- package/dist/app/event-use-case.js +0 -54
- package/dist/app/event-use-case.js.map +0 -1
- package/dist/app/execute-ctx.d.ts +0 -92
- package/dist/app/execute-ctx.d.ts.map +0 -1
- package/dist/app/execute-ctx.js +0 -2
- package/dist/app/execute-ctx.js.map +0 -1
- package/dist/app/external-use-case.d.ts +0 -25
- package/dist/app/external-use-case.d.ts.map +0 -1
- package/dist/app/external-use-case.js +0 -25
- package/dist/app/external-use-case.js.map +0 -1
- package/dist/app/index.d.ts +0 -9
- package/dist/app/index.d.ts.map +0 -1
- package/dist/app/index.js +0 -8
- package/dist/app/index.js.map +0 -1
- package/dist/app/internal-use-case.d.ts +0 -26
- package/dist/app/internal-use-case.d.ts.map +0 -1
- package/dist/app/internal-use-case.js +0 -26
- package/dist/app/internal-use-case.js.map +0 -1
- package/dist/app/nest.d.ts +0 -9
- package/dist/app/nest.d.ts.map +0 -1
- package/dist/app/nest.js +0 -28
- package/dist/app/nest.js.map +0 -1
- package/dist/app/types.d.ts +0 -153
- package/dist/app/types.d.ts.map +0 -1
- package/dist/app/types.js +0 -17
- package/dist/app/types.js.map +0 -1
- package/dist/core/coded-error.d.ts.map +0 -1
- package/dist/core/coded-error.js.map +0 -1
- package/dist/core/error-map.d.ts +0 -21
- package/dist/core/error-map.d.ts.map +0 -1
- package/dist/core/error-map.js +0 -5
- package/dist/core/error-map.js.map +0 -1
- package/dist/core/index.d.ts +0 -6
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -5
- package/dist/core/index.js.map +0 -1
- package/dist/core/result.d.ts.map +0 -1
- package/dist/core/result.js.map +0 -1
- package/dist/core/schema.d.ts.map +0 -1
- package/dist/core/schema.js.map +0 -1
- package/dist/core/standard-schema.d.ts.map +0 -1
- package/dist/core/standard-schema.js.map +0 -1
- package/dist/domain/capabilities.d.ts +0 -42
- package/dist/domain/capabilities.d.ts.map +0 -1
- package/dist/domain/capabilities.js +0 -2
- package/dist/domain/capabilities.js.map +0 -1
- package/dist/domain/catalog.d.ts +0 -46
- package/dist/domain/catalog.d.ts.map +0 -1
- package/dist/domain/catalog.js +0 -114
- package/dist/domain/catalog.js.map +0 -1
- package/dist/domain/channel.d.ts +0 -36
- package/dist/domain/channel.d.ts.map +0 -1
- package/dist/domain/channel.js +0 -2
- package/dist/domain/channel.js.map +0 -1
- package/dist/domain/cow-draft.d.ts.map +0 -1
- package/dist/domain/cow-draft.js.map +0 -1
- package/dist/domain/entity.d.ts +0 -120
- package/dist/domain/entity.d.ts.map +0 -1
- package/dist/domain/entity.js.map +0 -1
- package/dist/domain/envelope.d.ts +0 -59
- package/dist/domain/envelope.d.ts.map +0 -1
- package/dist/domain/envelope.js +0 -2
- package/dist/domain/envelope.js.map +0 -1
- package/dist/domain/event.d.ts +0 -44
- package/dist/domain/event.d.ts.map +0 -1
- package/dist/domain/event.js +0 -21
- package/dist/domain/event.js.map +0 -1
- package/dist/domain/index.d.ts +0 -8
- package/dist/domain/index.d.ts.map +0 -1
- package/dist/domain/index.js +0 -5
- package/dist/domain/index.js.map +0 -1
- package/dist/domain/port.d.ts +0 -45
- package/dist/domain/port.d.ts.map +0 -1
- package/dist/domain/port.js +0 -41
- package/dist/domain/port.js.map +0 -1
- package/dist/infra/adapter.d.ts +0 -22
- package/dist/infra/adapter.d.ts.map +0 -1
- package/dist/infra/adapter.js +0 -18
- package/dist/infra/adapter.js.map +0 -1
- package/dist/infra/index.d.ts +0 -3
- package/dist/infra/index.d.ts.map +0 -1
- package/dist/infra/index.js +0 -3
- package/dist/infra/index.js.map +0 -1
- package/dist/infra/mapper.d.ts +0 -48
- package/dist/infra/mapper.d.ts.map +0 -1
- package/dist/infra/mapper.js +0 -77
- package/dist/infra/mapper.js.map +0 -1
- package/dist/runtime/app.d.ts +0 -100
- package/dist/runtime/app.d.ts.map +0 -1
- package/dist/runtime/app.js +0 -292
- package/dist/runtime/app.js.map +0 -1
- package/dist/runtime/channel.d.ts +0 -34
- package/dist/runtime/channel.d.ts.map +0 -1
- package/dist/runtime/channel.js +0 -110
- package/dist/runtime/channel.js.map +0 -1
- package/dist/runtime/client.d.ts +0 -21
- package/dist/runtime/client.d.ts.map +0 -1
- package/dist/runtime/client.js +0 -2
- package/dist/runtime/client.js.map +0 -1
- package/dist/runtime/completeness.d.ts +0 -89
- package/dist/runtime/completeness.d.ts.map +0 -1
- package/dist/runtime/completeness.js +0 -2
- package/dist/runtime/completeness.js.map +0 -1
- package/dist/runtime/envelope.d.ts +0 -7
- package/dist/runtime/envelope.d.ts.map +0 -1
- package/dist/runtime/envelope.js +0 -69
- package/dist/runtime/envelope.js.map +0 -1
- package/dist/runtime/events.d.ts +0 -6
- package/dist/runtime/events.d.ts.map +0 -1
- package/dist/runtime/events.js +0 -48
- package/dist/runtime/events.js.map +0 -1
- package/dist/runtime/http-status.d.ts +0 -3
- package/dist/runtime/http-status.d.ts.map +0 -1
- package/dist/runtime/http-status.js +0 -11
- package/dist/runtime/http-status.js.map +0 -1
- package/dist/runtime/http.d.ts +0 -4
- package/dist/runtime/http.d.ts.map +0 -1
- package/dist/runtime/http.js +0 -58
- package/dist/runtime/http.js.map +0 -1
- package/dist/runtime/index.d.ts +0 -10
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js +0 -6
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/interceptor.d.ts +0 -61
- package/dist/runtime/interceptor.d.ts.map +0 -1
- package/dist/runtime/interceptor.js +0 -7
- package/dist/runtime/interceptor.js.map +0 -1
- package/dist/runtime/invoke.d.ts +0 -34
- package/dist/runtime/invoke.d.ts.map +0 -1
- package/dist/runtime/invoke.js +0 -235
- package/dist/runtime/invoke.js.map +0 -1
- package/dist/runtime/middleware.d.ts +0 -20
- package/dist/runtime/middleware.d.ts.map +0 -1
- package/dist/runtime/middleware.js +0 -2
- package/dist/runtime/middleware.js.map +0 -1
- package/dist/runtime/rpc.d.ts +0 -24
- package/dist/runtime/rpc.d.ts.map +0 -1
- package/dist/runtime/rpc.js +0 -16
- package/dist/runtime/rpc.js.map +0 -1
- package/dist/testing/app-test.d.ts +0 -77
- package/dist/testing/app-test.d.ts.map +0 -1
- package/dist/testing/app-test.js +0 -109
- package/dist/testing/app-test.js.map +0 -1
- package/dist/testing/in-memory-broker.d.ts +0 -18
- package/dist/testing/in-memory-broker.d.ts.map +0 -1
- package/dist/testing/in-memory-broker.js +0 -42
- package/dist/testing/in-memory-broker.js.map +0 -1
- package/dist/testing/in-memory-bus.d.ts +0 -19
- package/dist/testing/in-memory-bus.d.ts.map +0 -1
- package/dist/testing/in-memory-bus.js +0 -31
- package/dist/testing/in-memory-bus.js.map +0 -1
- package/dist/testing/in-memory-channel.d.ts +0 -32
- package/dist/testing/in-memory-channel.d.ts.map +0 -1
- package/dist/testing/in-memory-channel.js +0 -65
- package/dist/testing/in-memory-channel.js.map +0 -1
- package/dist/testing/in-memory-queue.d.ts +0 -24
- package/dist/testing/in-memory-queue.d.ts.map +0 -1
- package/dist/testing/in-memory-queue.js +0 -66
- package/dist/testing/in-memory-queue.js.map +0 -1
- package/dist/testing/in-memory-repository.d.ts +0 -44
- package/dist/testing/in-memory-repository.d.ts.map +0 -1
- package/dist/testing/in-memory-repository.js +0 -60
- package/dist/testing/in-memory-repository.js.map +0 -1
- package/dist/testing/index.d.ts +0 -6
- package/dist/testing/index.d.ts.map +0 -1
- package/dist/testing/index.js +0 -6
- package/dist/testing/index.js.map +0 -1
- /package/dist/{domain/cow-draft.d.ts → cow-draft.d.ts} +0 -0
- /package/dist/{domain/cow-draft.js → cow-draft.js} +0 -0
- /package/dist/{core/result.d.ts → result.d.ts} +0 -0
- /package/dist/{core/result.js → result.js} +0 -0
- /package/dist/{core/standard-schema.js → standard-schema.js} +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# hexok
|
|
2
|
+
|
|
3
|
+
TypeScript primitives for hexagonal software: `Schema`, `Entity`, `UseCase`, `Port`, `Adapter`, `Mapper`, `Event`, `EventCatalog`, `EventHandler`, and `Errors`. A `Mapper` subclass implements `fromSource` and `toSource`; adapters call `toModel` and `fromModel`.
|
|
4
|
+
|
|
5
|
+
Import them from `hexok`. There is no HTTP layer and no composition root in this package. Construct use cases with the ports they need. A schema argument is a Standard Schema. Zod satisfies that interface. `@hexok/typebox` adapts a `typebox` 1 schematic: `typebox(schema)` infers `Static`, and `typeboxDecode(schema)` infers `StaticDecode` and decodes. `EventInstance<typeof Catalog>` is the catalog's event objects. `EventMessage<typeof Catalog>` is the `{ key, payload }` union an adapter sends. `message` and `parse` on the catalog convert between them.
|
|
6
|
+
|
|
7
|
+
`UseCase.context<Ctx, Spec>()` is a family factory for a per-call context, optional guards, optional hooks, and required statics. The optional argument is a settings object with no keys yet. Pass nothing. `{}` is allowed. `guard` and `hooks` are methods, not fields of that object. A missing `<Ctx>` type argument is a type error. `Spec` defaults to no required statics. `.guard(fn)` and `.hooks(def)` each return the same factory. Order does not matter. Either may be omitted. Another `.guard()` runs after guards already registered. A second `.hooks()` throws `hexok: UseCase hooks are already set` when the factory is created, if the first registration had at least one callback. An empty `.hooks({})` does not register hooks and does not wrap `execute`. The call is `{ ctx, spec, token, input }`. `input` is the argument passed to `execute`. The method receives that same value. Hexok does not validate it. `UseCase.GuardParameters<typeof Factory>` is that call. `UseCase.Ctx<typeof Factory>` is that `ctx`. Pass the factory. `.hooks` infers the value returned from `preExecute` as the `state` argument of `postExecute`, `onCatch`, and `onFinally`. If `preExecute` is omitted, that state is `void`. `postExecute` also receives `result`. `onFinally` receives `status: 'success'` with `result`, or `status: 'failure'` with `error`. The pipeline is guard (outside try), `preExecute`, the method, `postExecute`, `onCatch` then rethrow, `onFinally`. A guard throw does not enter the hooks. A throw inside `onCatch` replaces the error after `onFinally` sees the original one. `UseCase(token)` still has `execute(input)`.
|
|
8
|
+
|
|
9
|
+
The root [README](../../README.md) shows the shape. Tokens and schemas are arguments of each primitive, so they cannot be forgotten. `execute`, `handle`, and port methods are abstract, so a missing method is a compiler error on the class. `start`, `stop`, `parse`, and `set` are concrete methods you can override.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implementation of a port. Missing port methods are errors on the class.
|
|
3
|
+
* `start` and `stop` are optional overrides. The token is the port's token.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* class InMemoryUsers extends Adapter(UserRepository) {
|
|
7
|
+
* override async get(id: string): Promise<User | null> { ... }
|
|
8
|
+
* override async save(user: User): Promise<void> { ... }
|
|
9
|
+
* }
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare function Adapter<P extends abstract new (...args: any[]) => object>(PortClass: P): (abstract new (...args: any[]) => {
|
|
13
|
+
/** Called by the application before the adapter is used. Override to connect. */
|
|
14
|
+
start(): Promise<void>;
|
|
15
|
+
/** Called by the application when it is finished with the adapter. Override to disconnect. */
|
|
16
|
+
stop(): Promise<void>;
|
|
17
|
+
}) & P;
|
|
18
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CACrB,CAAC,SAAS,QAAQ,MAEhB,GAAG,IAAI,EAAE,GAAG,EAAE,KACX,MAAM,EACX,SAAS,EAAE,CAAC,2BAFD,GAAG,EAAE;IAId,iFAAiF;aAClE,OAAO,CAAC,IAAI,CAAC;IAE5B,8FAA8F;YAChF,OAAO,CAAC,IAAI,CAAC;OAI9B"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implementation of a port. Missing port methods are errors on the class.
|
|
3
|
+
* `start` and `stop` are optional overrides. The token is the port's token.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* class InMemoryUsers extends Adapter(UserRepository) {
|
|
7
|
+
* override async get(id: string): Promise<User | null> { ... }
|
|
8
|
+
* override async save(user: User): Promise<void> { ... }
|
|
9
|
+
* }
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export function Adapter(PortClass) {
|
|
13
|
+
class AdapterClass extends PortClass {
|
|
14
|
+
/** Called by the application before the adapter is used. Override to connect. */
|
|
15
|
+
async start() { }
|
|
16
|
+
/** Called by the application when it is finished with the adapter. Override to disconnect. */
|
|
17
|
+
async stop() { }
|
|
18
|
+
}
|
|
19
|
+
return AdapterClass;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,OAAO,CAKrB,SAAY;IACZ,MAAe,YAAa,SAAQ,SAAS;QAC3C,iFAAiF;QACjF,KAAK,CAAC,KAAK,KAAmB,CAAC;QAE/B,8FAA8F;QAC9F,KAAK,CAAC,IAAI,KAAmB,CAAC;KAC/B;IAED,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from './standard-schema.js';
|
|
2
|
+
/** Constructor registered on an {@link EventCatalog}. */
|
|
3
|
+
export type EventConstructor = {
|
|
4
|
+
/** Event name. */
|
|
5
|
+
readonly token: string;
|
|
6
|
+
/** Payload schema. */
|
|
7
|
+
readonly schema: StandardSchemaV1;
|
|
8
|
+
new (...args: any[]): object;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Named set of event classes. Map keys stay literal without `as const`.
|
|
12
|
+
* Duplicate event tokens throw when the catalog is defined.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* class DomainEvents extends EventCatalog('domain', {
|
|
16
|
+
* userCreated: UserCreated,
|
|
17
|
+
* }) {}
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function EventCatalog<const Token extends string, const Events extends Record<string, EventConstructor>>(token: Token, events: Events): (abstract new () => {}) & {
|
|
21
|
+
/** Catalog name. Literal type of the string passed to {@link EventCatalog}. */
|
|
22
|
+
readonly token: Token;
|
|
23
|
+
/** Event classes, in the shape passed to {@link EventCatalog}. */
|
|
24
|
+
readonly events: Events;
|
|
25
|
+
/** Registered class for `name`. Throws when `name` is not in this catalog. */
|
|
26
|
+
get<Name extends keyof Events & string>(name: Name): Events[Name];
|
|
27
|
+
/**
|
|
28
|
+
* Serializable `{ key, payload }` for an instance from this catalog.
|
|
29
|
+
* `key` is the map name. Throws when `event` is not registered here.
|
|
30
|
+
*/
|
|
31
|
+
message(event: EventInstance<CatalogOf<Events>>): EventMessage<CatalogOf<Events>>;
|
|
32
|
+
/**
|
|
33
|
+
* Validate a `{ key, payload }` message and construct the event.
|
|
34
|
+
* Throws when `key` is not in this catalog.
|
|
35
|
+
* Throws `CodedError` `VALIDATION` when the payload schema rejects it.
|
|
36
|
+
*/
|
|
37
|
+
parse(value: unknown): EventInstance<CatalogOf<Events>>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Instance of an event class. Read from the constructor so
|
|
41
|
+
* {@link EventConstructor}'s `object` return does not erase it.
|
|
42
|
+
*/
|
|
43
|
+
type EventInstanceOf<Event> = Event extends abstract new (...args: never[]) => infer Instance ? Instance : never;
|
|
44
|
+
/**
|
|
45
|
+
* Payload of an event class. Read from the instance so
|
|
46
|
+
* {@link EventConstructor}'s `object` return does not erase it.
|
|
47
|
+
*/
|
|
48
|
+
type EventPayload<Event> = EventInstanceOf<Event> extends {
|
|
49
|
+
readonly payload: infer Payload;
|
|
50
|
+
} ? Payload : never;
|
|
51
|
+
/** Catalog shape {@link EventInstance} and {@link EventMessage} read. */
|
|
52
|
+
type CatalogOf<Events extends Record<string, EventConstructor>> = {
|
|
53
|
+
readonly events: Events;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Instance of one catalog entry, or every entry when `Key` is omitted.
|
|
57
|
+
* `token` is the event name. `payload` is the schema output.
|
|
58
|
+
*
|
|
59
|
+
* ```ts
|
|
60
|
+
* abstract publish(event: EventInstance<typeof DomainEvents>): Promise<void>
|
|
61
|
+
* publish(new UserCreated({ id, name, email }))
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* `EventInstance<typeof DomainEvents, 'userCreated'>` keeps that one entry.
|
|
65
|
+
*/
|
|
66
|
+
export type EventInstance<Catalog extends {
|
|
67
|
+
readonly events: Record<string, EventConstructor>;
|
|
68
|
+
}, Key extends keyof Catalog['events'] & string = keyof Catalog['events'] & string> = {
|
|
69
|
+
[K in Key]: EventInstanceOf<Catalog['events'][K]>;
|
|
70
|
+
}[Key];
|
|
71
|
+
/**
|
|
72
|
+
* `{ key, payload }` for one catalog entry, or every entry when `Key` is omitted.
|
|
73
|
+
* `key` is the catalog map name. The event token stays on the instance.
|
|
74
|
+
* Adapters send this value. {@link EventCatalog} `message` and `parse` convert
|
|
75
|
+
* it to and from an {@link EventInstance}.
|
|
76
|
+
*
|
|
77
|
+
* `EventMessage<typeof DomainEvents, 'userCreated'>` keeps that one entry.
|
|
78
|
+
*/
|
|
79
|
+
export type EventMessage<Catalog extends {
|
|
80
|
+
readonly events: Record<string, EventConstructor>;
|
|
81
|
+
}, Key extends keyof Catalog['events'] & string = keyof Catalog['events'] & string> = {
|
|
82
|
+
[K in Key]: {
|
|
83
|
+
key: K;
|
|
84
|
+
payload: EventPayload<Catalog['events'][K]>;
|
|
85
|
+
};
|
|
86
|
+
}[Key];
|
|
87
|
+
export {};
|
|
88
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,yDAAyD;AACzD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,kBAAkB;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sBAAsB;IACtB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAElC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;CAC9B,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,KAAK,CAAC,KAAK,SAAS,MAAM,EAC1B,KAAK,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACrD,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM;IAa1B,+EAA+E;oBACxD,KAAK;IAC5B,kEAAkE;qBAC1C,MAAM;IAE9B,8EAA8E;QACnE,IAAI,SAAS,MAAM,MAAM,GAAG,MAAM,QAAQ,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;IAQxE;;;OAGG;mBAEM,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,GACtC,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAgBlC;;;;OAIG;iBACiB,OAAO,GAAG,aAAa,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;EAsBjE;AAED;;;GAGG;AACH,KAAK,eAAe,CAAC,KAAK,IAAI,KAAK,SAAS,QAAQ,MAClD,GAAG,IAAI,EAAE,KAAK,EAAE,KACb,MAAM,QAAQ,GACf,QAAQ,GACR,KAAK,CAAC;AAEV;;;GAGG;AACH,KAAK,YAAY,CAAC,KAAK,IACrB,eAAe,CAAC,KAAK,CAAC,SAAS;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC;CACjC,GACG,OAAO,GACP,KAAK,CAAC;AAEZ,yEAAyE;AACzE,KAAK,SAAS,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,IAAI;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,MAAM,aAAa,CACvB,OAAO,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CAAE,EACrE,GAAG,SAAS,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,GACpE,MAAM,IACN;KACD,CAAC,IAAI,GAAG,GAAG,eAAe,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;CAClD,CAAC,GAAG,CAAC,CAAC;AAEP;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,CACtB,OAAO,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CAAE,EACrE,GAAG,SAAS,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,MAAM,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,GACpE,MAAM,IACN;KACD,CAAC,IAAI,GAAG,GAAG;QACV,GAAG,EAAE,CAAC,CAAC;QACP,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;KAC7C;CACF,CAAC,GAAG,CAAC,CAAC"}
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named set of event classes. Map keys stay literal without `as const`.
|
|
3
|
+
* Duplicate event tokens throw when the catalog is defined.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* class DomainEvents extends EventCatalog('domain', {
|
|
7
|
+
* userCreated: UserCreated,
|
|
8
|
+
* }) {}
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export function EventCatalog(token, events) {
|
|
12
|
+
const seen = new Set();
|
|
13
|
+
for (const event of Object.values(events)) {
|
|
14
|
+
if (seen.has(event.token)) {
|
|
15
|
+
throw new Error(`hexok: duplicate event "${event.token}" in catalog "${token}"`);
|
|
16
|
+
}
|
|
17
|
+
seen.add(event.token);
|
|
18
|
+
}
|
|
19
|
+
const registered = Object.freeze({ ...events });
|
|
20
|
+
class CatalogClass {
|
|
21
|
+
/** Catalog name. Literal type of the string passed to {@link EventCatalog}. */
|
|
22
|
+
static token = token;
|
|
23
|
+
/** Event classes, in the shape passed to {@link EventCatalog}. */
|
|
24
|
+
static events = registered;
|
|
25
|
+
/** Registered class for `name`. Throws when `name` is not in this catalog. */
|
|
26
|
+
static get(name) {
|
|
27
|
+
const event = registered[name];
|
|
28
|
+
if (event === undefined) {
|
|
29
|
+
throw new Error(`hexok: event "${name}" is not in catalog "${token}"`);
|
|
30
|
+
}
|
|
31
|
+
return event;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Serializable `{ key, payload }` for an instance from this catalog.
|
|
35
|
+
* `key` is the map name. Throws when `event` is not registered here.
|
|
36
|
+
*/
|
|
37
|
+
static message(event) {
|
|
38
|
+
for (const key of Object.keys(registered)) {
|
|
39
|
+
const Ctor = registered[key];
|
|
40
|
+
if (Ctor !== undefined && event instanceof Ctor) {
|
|
41
|
+
// The instance type does not narrow to one catalog key.
|
|
42
|
+
const payload = event.payload;
|
|
43
|
+
return { key, payload };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const name = 'token' in event && typeof event.token === 'string'
|
|
47
|
+
? event.token
|
|
48
|
+
: 'unknown';
|
|
49
|
+
throw new Error(`hexok: event "${name}" is not in catalog "${token}"`);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Validate a `{ key, payload }` message and construct the event.
|
|
53
|
+
* Throws when `key` is not in this catalog.
|
|
54
|
+
* Throws `CodedError` `VALIDATION` when the payload schema rejects it.
|
|
55
|
+
*/
|
|
56
|
+
static parse(value) {
|
|
57
|
+
if (!isMessage(value)) {
|
|
58
|
+
throw new Error(`hexok: catalog "${token}" expected { key, payload }`);
|
|
59
|
+
}
|
|
60
|
+
if (!Object.hasOwn(registered, value.key)) {
|
|
61
|
+
throw new Error(`hexok: event "${value.key}" is not in catalog "${token}"`);
|
|
62
|
+
}
|
|
63
|
+
const EventClass = registered[value.key];
|
|
64
|
+
if (typeof EventClass.parse !== 'function') {
|
|
65
|
+
throw new Error(`hexok: event "${value.key}" in catalog "${token}" has no parse`);
|
|
66
|
+
}
|
|
67
|
+
return EventClass.parse(value.payload);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return CatalogClass;
|
|
71
|
+
}
|
|
72
|
+
function isMessage(value) {
|
|
73
|
+
if (typeof value !== 'object' || value === null)
|
|
74
|
+
return false;
|
|
75
|
+
if (!('key' in value) || typeof value.key !== 'string')
|
|
76
|
+
return false;
|
|
77
|
+
return 'payload' in value;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from './standard-schema.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Code, message, and optional data. Schema checks throw this with code
|
|
4
|
+
* `VALIDATION`. The application catches it where errors bubble.
|
|
5
|
+
*
|
|
6
|
+
* Application refusals use an {@link Errors} catalog.
|
|
5
7
|
*
|
|
6
8
|
* ```ts
|
|
7
|
-
*
|
|
9
|
+
* new CodedError({
|
|
10
|
+
* code: 'VALIDATION',
|
|
11
|
+
* message: 'hexok: User validation failed',
|
|
12
|
+
* })
|
|
8
13
|
* ```
|
|
9
14
|
*/
|
|
10
15
|
export declare class CodedError<C extends string = string> extends Error {
|
|
11
|
-
/** Machine-readable
|
|
16
|
+
/** Machine-readable code. Schema checks use `VALIDATION`. */
|
|
12
17
|
readonly code: C;
|
|
13
18
|
/** Optional payload. Set only when the constructor received `data`. */
|
|
14
19
|
readonly data?: unknown;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coded-error.d.ts","sourceRoot":"","sources":["../src/coded-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D;;;;;;;;;;;;GAYG;AACH,qBAAa,UAAU,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,KAAK;IAC9D,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACjB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAExB,oCAAoC;gBACxB,IAAI,EAAE;QAChB,IAAI,EAAE,CAAC,CAAC;QACR,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB;CAQF;AAED,0FAA0F;AAC1F,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,SAAS,gBAAgB,CAAC,KAAK,EAAE,GACzC,UAAU,CAAC,YAAY,CAAC,CAM1B"}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Code, message, and optional data. Schema checks throw this with code
|
|
3
|
+
* `VALIDATION`. The application catches it where errors bubble.
|
|
4
|
+
*
|
|
5
|
+
* Application refusals use an {@link Errors} catalog.
|
|
4
6
|
*
|
|
5
7
|
* ```ts
|
|
6
|
-
*
|
|
8
|
+
* new CodedError({
|
|
9
|
+
* code: 'VALIDATION',
|
|
10
|
+
* message: 'hexok: User validation failed',
|
|
11
|
+
* })
|
|
7
12
|
* ```
|
|
8
13
|
*/
|
|
9
14
|
export class CodedError extends Error {
|
|
10
|
-
/** Machine-readable
|
|
15
|
+
/** Machine-readable code. Schema checks use `VALIDATION`. */
|
|
11
16
|
code;
|
|
12
17
|
/** Optional payload. Set only when the constructor received `data`. */
|
|
13
18
|
data;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coded-error.js","sourceRoot":"","sources":["../src/coded-error.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,OAAO,UAAsC,SAAQ,KAAK;IAC9D,6DAA6D;IACpD,IAAI,CAAI;IACjB,uEAAuE;IAC9D,IAAI,CAAW;IAExB,oCAAoC;IACpC,YAAY,IAIX;QACC,KAAK,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACxB,CAAC;IACH,CAAC;CACF;AAED,0FAA0F;AAC1F,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,MAA0C;IAE1C,OAAO,IAAI,UAAU,CAAC;QACpB,IAAI,EAAE,YAAY;QAClB,OAAO;QACP,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cow-draft.d.ts","sourceRoot":"","sources":["../src/cow-draft.ts"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKlC;AAMD,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAC5C,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,EAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAC3B;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CA+FjE;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAE7C;AAED,gEAAgE;AAChE,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAEzC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cow-draft.js","sourceRoot":"","sources":["../src/cow-draft.ts"],"names":[],"mappings":"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"}
|
package/dist/entity.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type InferSchema, type ResolvedSchema, type SchemaSource } from './schema.js';
|
|
2
|
+
/** Recursively readonly view of entity props. `Date` values stay `Date`. */
|
|
3
|
+
export type DeepReadonly<T> = T extends Date ? T : T extends readonly (infer U)[] ? ReadonlyArray<DeepReadonly<U>> : T extends object ? {
|
|
4
|
+
readonly [K in keyof T]: DeepReadonly<T[K]>;
|
|
5
|
+
} : T;
|
|
6
|
+
/** `keyof object` is `never`; widen so an open props type still has string keys. */
|
|
7
|
+
type ChangedKey<P extends object> = keyof P extends never ? string : keyof P & string;
|
|
8
|
+
/** Public instance of an entity. Subclass methods stay on `this`. */
|
|
9
|
+
export interface EntityInstance<P extends object> {
|
|
10
|
+
readonly props: DeepReadonly<P>;
|
|
11
|
+
readonly isNew: boolean;
|
|
12
|
+
readonly isValidated: boolean;
|
|
13
|
+
readonly original: DeepReadonly<P> | undefined;
|
|
14
|
+
set(producer: (draft: P) => void): this;
|
|
15
|
+
validate(): this;
|
|
16
|
+
getChangedKeys(): Array<ChangedKey<P>>;
|
|
17
|
+
getChangedKeys(opts: {
|
|
18
|
+
deep: true;
|
|
19
|
+
}): string[];
|
|
20
|
+
getChangedKeys(opts?: {
|
|
21
|
+
deep?: boolean;
|
|
22
|
+
}): string[];
|
|
23
|
+
isDirty(): boolean;
|
|
24
|
+
commit(): this;
|
|
25
|
+
toProps(): DeepReadonly<P>;
|
|
26
|
+
toJSON(): DeepReadonly<P>;
|
|
27
|
+
}
|
|
28
|
+
type EntityHandle<Token extends string, S extends SchemaSource> = {
|
|
29
|
+
readonly token: Token;
|
|
30
|
+
readonly schema: ResolvedSchema<S>;
|
|
31
|
+
readonly prototype: EntityInstance<EntityProps<S>>;
|
|
32
|
+
create<T extends {
|
|
33
|
+
readonly prototype: EntityInstance<EntityProps<S>>;
|
|
34
|
+
}>(this: T, props: EntityProps<S> | DeepReadonly<EntityProps<S>>): T['prototype'];
|
|
35
|
+
restore<T extends {
|
|
36
|
+
readonly prototype: EntityInstance<EntityProps<S>>;
|
|
37
|
+
}>(this: T, props: EntityProps<S> | DeepReadonly<EntityProps<S>>): T['prototype'];
|
|
38
|
+
parse<T extends {
|
|
39
|
+
readonly prototype: EntityInstance<EntityProps<S>>;
|
|
40
|
+
}>(this: T, value: unknown): T['prototype'];
|
|
41
|
+
} & (abstract new (props: EntityProps<S>, init: never) => EntityInstance<EntityProps<S>>);
|
|
42
|
+
/**
|
|
43
|
+
* Mutable aggregate. The token and schema are arguments of {@link Entity},
|
|
44
|
+
* so a subclass cannot forget them. Rules are methods. A refusal throws an
|
|
45
|
+
* {@link Errors} catalog member. Override `set` only when the copy-on-write
|
|
46
|
+
* write path itself must change.
|
|
47
|
+
*
|
|
48
|
+
* ```ts
|
|
49
|
+
* class Incident extends Entity('Incident', incidentSchema) {
|
|
50
|
+
* close(now: Date): this {
|
|
51
|
+
* if (this.props.status === 'closed') throw DomainError.AlreadyClosed()
|
|
52
|
+
* return this.set((draft) => {
|
|
53
|
+
* draft.status = 'closed'
|
|
54
|
+
* draft.closedAt = now
|
|
55
|
+
* })
|
|
56
|
+
* }
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
type EntityProps<S extends SchemaSource> = InferSchema<S> extends object ? InferSchema<S> : never;
|
|
61
|
+
export declare function Entity<const Token extends string, S extends SchemaSource>(token: Token, schema: S): EntityHandle<Token, S>;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../src/entity.ts"],"names":[],"mappings":"AAOA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,YAAY,EAElB,MAAM,aAAa,CAAC;AAMrB,4EAA4E;AAC5E,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,GACxC,CAAC,GACD,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAC5B,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAC9B,CAAC,SAAS,MAAM,GACd;IAAE,QAAQ,EAAE,CAAC,IAAI,MAAM,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,GAC/C,CAAC,CAAC;AAWV,oFAAoF;AACpF,KAAK,UAAU,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,CAAC,SAAS,KAAK,GACrD,MAAM,GACN,MAAM,CAAC,GAAG,MAAM,CAAC;AAErB,qEAAqE;AACrE,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,MAAM;IAC9C,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC;IAC/C,GAAG,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACxC,QAAQ,IAAI,IAAI,CAAC;IACjB,cAAc,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,cAAc,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,GAAG,MAAM,EAAE,CAAC;IAC/C,cAAc,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,MAAM,EAAE,CAAC;IACpD,OAAO,IAAI,OAAO,CAAC;IACnB,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,IAAI,YAAY,CAAC,CAAC,CAAC,CAAC;CAC3B;AAED,KAAK,YAAY,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC,SAAS,YAAY,IAAI;IAChE,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACnD,MAAM,CAAC,CAAC,SAAS;QAAE,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;KAAE,EACrE,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GACnD,CAAC,CAAC,WAAW,CAAC,CAAC;IAClB,OAAO,CAAC,CAAC,SAAS;QAAE,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;KAAE,EACtE,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GACnD,CAAC,CAAC,WAAW,CAAC,CAAC;IAClB,KAAK,CAAC,CAAC,SAAS;QAAE,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;KAAE,EACpE,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,OAAO,GACb,CAAC,CAAC,WAAW,CAAC,CAAC;CACnB,GAAG,CAAC,QAAQ,MACX,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EACrB,IAAI,EAAE,KAAK,KACR,cAAc,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAErC;;;;;;;;;;;;;;;;;GAiBG;AACH,KAAK,WAAW,CAAC,CAAC,SAAS,YAAY,IACrC,WAAW,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;AAEzD,wBAAgB,MAAM,CAAC,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,CAAC,SAAS,YAAY,EACvE,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,CAAC,GACR,YAAY,CAAC,KAAK,EAAE,CAAC,CAAC,CAkBxB"}
|
|
@@ -1,46 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { validationError } from './coded-error.js';
|
|
2
2
|
import { applyCowDraft, cloneValue, frozenSnapshot, isPlainObject, } from './cow-draft.js';
|
|
3
|
+
import { schemaDefinition, } from './schema.js';
|
|
4
|
+
import { validate } from './validate.js';
|
|
3
5
|
const EMPTY_CHANGED_KEYS = Object.freeze([]);
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* }
|
|
25
|
-
* }
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export class Entity {
|
|
29
|
-
/** Entity name used in validation and undeclared-error messages. Declare on each subclass. */
|
|
30
|
-
static key;
|
|
31
|
-
/** Standard Schema for `create` / `parse` / `set` / `validate()`. Declare on each subclass. */
|
|
6
|
+
export function Entity(token, schema) {
|
|
7
|
+
const definition = schemaDefinition(schema);
|
|
8
|
+
class EntityClass extends EntityBase {
|
|
9
|
+
/** Entity name. Literal type of the string passed to {@link Entity}. */
|
|
10
|
+
static token = token;
|
|
11
|
+
/** Schema used by `create`, `parse`, `set`, and `validate`. */
|
|
12
|
+
static schema = definition;
|
|
13
|
+
/** Nominal marker. Each `Entity(...)` call is a distinct class. */
|
|
14
|
+
#brand = true;
|
|
15
|
+
constructor(props) {
|
|
16
|
+
super(props);
|
|
17
|
+
void this.#brand;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return EntityClass;
|
|
21
|
+
}
|
|
22
|
+
class EntityBase {
|
|
23
|
+
/** Entity name used in validation messages. */
|
|
24
|
+
static token;
|
|
25
|
+
/** Schema for `create` / `parse` / `set` / `validate()`. */
|
|
32
26
|
static schema;
|
|
33
|
-
/** Declared refusal codes. Keys are the `error()` union. Declare on each subclass. */
|
|
34
|
-
static errors;
|
|
35
27
|
#isNew = false;
|
|
36
28
|
#validated = false;
|
|
37
29
|
#original = undefined;
|
|
38
30
|
#owned = undefined;
|
|
39
31
|
#snapshot = undefined;
|
|
40
|
-
|
|
32
|
+
#props;
|
|
41
33
|
/** Live props. Read-only; write through `set`. */
|
|
42
34
|
get props() {
|
|
43
|
-
return this
|
|
35
|
+
return this.#props;
|
|
44
36
|
}
|
|
45
37
|
/** True after `create` until `commit`. `restore` / `parse` start false. */
|
|
46
38
|
get isNew() {
|
|
@@ -59,22 +51,22 @@ export class Entity {
|
|
|
59
51
|
*/
|
|
60
52
|
get original() {
|
|
61
53
|
if (this.#original === undefined ||
|
|
62
|
-
Object.is(this
|
|
54
|
+
Object.is(this.#props, this.#original)) {
|
|
63
55
|
return undefined;
|
|
64
56
|
}
|
|
65
57
|
return this.#original;
|
|
66
58
|
}
|
|
67
59
|
constructor(props) {
|
|
68
|
-
this
|
|
60
|
+
this.#props = props;
|
|
69
61
|
}
|
|
70
|
-
/** Copy-on-write mutate. Edit `draft` and return `this`. Re-validates
|
|
62
|
+
/** Copy-on-write mutate. Edit `draft` and return `this`. Re-validates after a write. */
|
|
71
63
|
set(producer) {
|
|
72
|
-
const previousRoot = this
|
|
73
|
-
const backup = cloneValue(this
|
|
64
|
+
const previousRoot = this.#props;
|
|
65
|
+
const backup = cloneValue(this.#props);
|
|
74
66
|
const previousSnapshot = this.#snapshot;
|
|
75
|
-
const wasOwned = this.#owned?.has(this
|
|
76
|
-
const result = applyCowDraft(this
|
|
77
|
-
this
|
|
67
|
+
const wasOwned = this.#owned?.has(this.#props) === true;
|
|
68
|
+
const result = applyCowDraft(this.#props, this.#owned, producer);
|
|
69
|
+
this.#props = result.root;
|
|
78
70
|
this.#owned = result.owned;
|
|
79
71
|
if (result.wrote) {
|
|
80
72
|
this.#snapshot = undefined;
|
|
@@ -82,7 +74,7 @@ export class Entity {
|
|
|
82
74
|
this.#assertSchema();
|
|
83
75
|
}
|
|
84
76
|
catch (error) {
|
|
85
|
-
this
|
|
77
|
+
this.#props = wasOwned ? backup : previousRoot;
|
|
86
78
|
this.#owned = wasOwned ? new WeakSet([backup]) : undefined;
|
|
87
79
|
this.#snapshot = previousSnapshot;
|
|
88
80
|
throw error;
|
|
@@ -105,18 +97,18 @@ export class Entity {
|
|
|
105
97
|
}
|
|
106
98
|
getChangedKeys(opts) {
|
|
107
99
|
if (!this.#isNew &&
|
|
108
|
-
(this.#original === undefined || Object.is(this
|
|
100
|
+
(this.#original === undefined || Object.is(this.#props, this.#original))) {
|
|
109
101
|
return EMPTY_CHANGED_KEYS;
|
|
110
102
|
}
|
|
111
103
|
if (opts?.deep) {
|
|
112
104
|
const out = [];
|
|
113
|
-
diffDeep(this
|
|
105
|
+
diffDeep(this.#props, this.#isNew ? undefined : this.#original, '', out);
|
|
114
106
|
return out.length === 0 ? EMPTY_CHANGED_KEYS : out;
|
|
115
107
|
}
|
|
116
108
|
if (this.#isNew) {
|
|
117
|
-
return Object.keys(this
|
|
109
|
+
return Object.keys(this.#props);
|
|
118
110
|
}
|
|
119
|
-
const current = this
|
|
111
|
+
const current = this.#props;
|
|
120
112
|
const original = this.#original;
|
|
121
113
|
const keys = new Set([
|
|
122
114
|
...Object.keys(current),
|
|
@@ -134,9 +126,9 @@ export class Entity {
|
|
|
134
126
|
isDirty() {
|
|
135
127
|
return this.#isNew || this.getChangedKeys().length > 0;
|
|
136
128
|
}
|
|
137
|
-
/** Accept current props as original; `isNew` becomes false.
|
|
129
|
+
/** Accept current props as original; `isNew` becomes false. */
|
|
138
130
|
commit() {
|
|
139
|
-
this.#original = this
|
|
131
|
+
this.#original = this.#props;
|
|
140
132
|
this.#isNew = false;
|
|
141
133
|
this.#owned = undefined;
|
|
142
134
|
return this;
|
|
@@ -144,7 +136,7 @@ export class Entity {
|
|
|
144
136
|
/** Deep frozen snapshot for persistence and use-case output. Reused until the next `set`. */
|
|
145
137
|
toProps() {
|
|
146
138
|
if (this.#snapshot === undefined) {
|
|
147
|
-
this.#snapshot = frozenSnapshot(this
|
|
139
|
+
this.#snapshot = frozenSnapshot(this.#props);
|
|
148
140
|
}
|
|
149
141
|
return this.#snapshot;
|
|
150
142
|
}
|
|
@@ -152,15 +144,6 @@ export class Entity {
|
|
|
152
144
|
toJSON() {
|
|
153
145
|
return this.toProps();
|
|
154
146
|
}
|
|
155
|
-
/**
|
|
156
|
-
* Throw a declared entity error. Codes are the keys of the subclass
|
|
157
|
-
* `static errors` when called as `Incident.error('ALREADY_CLOSED')`.
|
|
158
|
-
* An undeclared code is a type error on that call, and a programming
|
|
159
|
-
* error at runtime.
|
|
160
|
-
*/
|
|
161
|
-
static error(code, data) {
|
|
162
|
-
throwEntityError(this, code, data);
|
|
163
|
-
}
|
|
164
147
|
/**
|
|
165
148
|
* Validate typed props and construct a new instance (`isNew: true`).
|
|
166
149
|
* Throws `CodedError` `VALIDATION` if the schema rejects the props.
|
|
@@ -173,8 +156,8 @@ export class Entity {
|
|
|
173
156
|
}
|
|
174
157
|
/**
|
|
175
158
|
* Reconstruct an existing instance (`isNew: false`) without running the
|
|
176
|
-
* schema. Props are stored as given
|
|
177
|
-
*
|
|
159
|
+
* schema. Props are stored as given. `isValidated` starts false until
|
|
160
|
+
* `set` writes or `validate()` runs.
|
|
178
161
|
*/
|
|
179
162
|
static restore(props) {
|
|
180
163
|
const instance = construct(this, props);
|
|
@@ -182,8 +165,8 @@ export class Entity {
|
|
|
182
165
|
return instance;
|
|
183
166
|
}
|
|
184
167
|
/**
|
|
185
|
-
* Trust boundary for untyped input. Validates like `create
|
|
186
|
-
*
|
|
168
|
+
* Trust boundary for untyped input. Validates like `create`.
|
|
169
|
+
* Tracking matches `restore` (`isNew: false`).
|
|
187
170
|
* Throws `CodedError` `VALIDATION` if the schema rejects the value.
|
|
188
171
|
*/
|
|
189
172
|
static parse(value) {
|
|
@@ -193,31 +176,31 @@ export class Entity {
|
|
|
193
176
|
return instance;
|
|
194
177
|
}
|
|
195
178
|
#markNew() {
|
|
196
|
-
this
|
|
179
|
+
this.#props = { ...this.#props };
|
|
197
180
|
this.#original = undefined;
|
|
198
181
|
this.#isNew = true;
|
|
199
182
|
this.#owned = new WeakSet();
|
|
200
|
-
this.#owned.add(this
|
|
183
|
+
this.#owned.add(this.#props);
|
|
201
184
|
this.#snapshot = undefined;
|
|
202
185
|
}
|
|
203
186
|
#markRestored() {
|
|
204
|
-
this.#original = this
|
|
187
|
+
this.#original = this.#props;
|
|
205
188
|
this.#isNew = false;
|
|
206
189
|
this.#owned = undefined;
|
|
207
190
|
this.#snapshot = undefined;
|
|
208
191
|
}
|
|
209
192
|
#assertSchema() {
|
|
210
|
-
const
|
|
211
|
-
const parsed = validate(
|
|
193
|
+
const ctor = this.constructor;
|
|
194
|
+
const parsed = validate(ctor.schema, this.#props);
|
|
212
195
|
if (!parsed.ok) {
|
|
213
|
-
throw validationError(`hexok: ${
|
|
196
|
+
throw validationError(`hexok: ${ctor.token} validation failed`, parsed.issues);
|
|
214
197
|
}
|
|
215
198
|
}
|
|
216
199
|
}
|
|
217
200
|
function instantiate(Ctor, value) {
|
|
218
201
|
const parsed = validate(Ctor.schema, value);
|
|
219
202
|
if (!parsed.ok) {
|
|
220
|
-
throw validationError(`hexok: ${Ctor.
|
|
203
|
+
throw validationError(`hexok: ${Ctor.token} validation failed`, parsed.issues);
|
|
221
204
|
}
|
|
222
205
|
return construct(Ctor, parsed.value);
|
|
223
206
|
}
|
|
@@ -249,15 +232,4 @@ function diffDeep(current, original, path, out) {
|
|
|
249
232
|
if (path)
|
|
250
233
|
out.push(path);
|
|
251
234
|
}
|
|
252
|
-
function throwEntityError(ctor, code, data) {
|
|
253
|
-
const def = ctor.errors[code];
|
|
254
|
-
if (def === undefined) {
|
|
255
|
-
throw new Error(`hexok: undeclared error "${code}" on ${ctor.key}`);
|
|
256
|
-
}
|
|
257
|
-
throw new CodedError({
|
|
258
|
-
code,
|
|
259
|
-
message: def.message ?? code,
|
|
260
|
-
...(data !== undefined ? { data } : {}),
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
235
|
//# sourceMappingURL=entity.js.map
|