hexok 1.0.0 → 1.0.1

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 ADDED
@@ -0,0 +1,31 @@
1
+ # hexok
2
+
3
+ Hexo Kit — a TypeScript kit for writing a clean-architecture backend as ordinary classes: Entity, Port, Adapter, UseCase, Event, Interceptor.
4
+
5
+ **Docs:** https://crobinson42.github.io/hexok/
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm i hexok
11
+ ```
12
+
13
+ Import from a layer. There is no root barrel.
14
+
15
+ ```ts
16
+ import { Entity } from 'hexok/domain'
17
+ import { ExternalUseCase } from 'hexok/app'
18
+ import { App } from 'hexok/runtime'
19
+ import { App as TestApp, InMemoryRepository } from 'hexok/testing'
20
+ ```
21
+
22
+ | Import | What it is |
23
+ | --- | --- |
24
+ | `hexok/core` | `Result`, Standard Schema V1, `ErrorMap`, `CodedError` |
25
+ | `hexok/domain` | `Entity`, `Port`, `EventCatalog`, `DomainEvent` |
26
+ | `hexok/app` | `ExternalUseCase`, `InternalUseCase`, `EventUseCase`, `EventChannel` |
27
+ | `hexok/infra` | `Mapper`, `Adapter.of` |
28
+ | `hexok/runtime` | `App.from`, interceptors, local client, HTTP |
29
+ | `hexok/testing` | `App.test`, in-memory adapters |
30
+
31
+ Full API: https://crobinson42.github.io/hexok/
package/package.json CHANGED
@@ -1,16 +1,26 @@
1
1
  {
2
2
  "name": "hexok",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A TypeScript kit for writing a clean-architecture backend as ordinary classes.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,
8
+ "keywords": [
9
+ "hexagonal-architecture",
10
+ "clean-architecture",
11
+ "ports-and-adapters",
12
+ "typescript",
13
+ "ddd",
14
+ "entity",
15
+ "use-case",
16
+ "domain-driven-design"
17
+ ],
8
18
  "repository": {
9
19
  "type": "git",
10
20
  "url": "git+https://github.com/crobinson42/hexok.git",
11
21
  "directory": "packages/hexok"
12
22
  },
13
- "homepage": "https://github.com/crobinson42/hexok#readme",
23
+ "homepage": "https://crobinson42.github.io/hexok/",
14
24
  "bugs": {
15
25
  "url": "https://github.com/crobinson42/hexok/issues"
16
26
  },
@@ -1,52 +0,0 @@
1
- import { type ErrorMap, type Result } from '../core/index.js';
2
- import type { AnyEventCatalog, PortToken } from '../domain/index.js';
3
- import type { EventChannelCtor } from './types.js';
4
- /**
5
- * Request/response application use case. Declare static `key`, `input`, `output`, `errors`, `ports`.
6
- *
7
- * ```ts
8
- * class CloseIncident extends ApiUseCase {
9
- * static readonly key = 'incident.close'
10
- * static readonly input = z.object({ id: z.string() })
11
- * static readonly output = Incident.schema
12
- * static readonly errors = { NOT_FOUND: { message: 'Incident not found' } } as const
13
- * static readonly ports = { incidents: IncidentRepository }
14
- * async execute({ input, ports, errors }: ExecuteCtx<typeof CloseIncident>) {
15
- * const incident = await ports.incidents.get(input.id)
16
- * if (!incident) throw errors.NOT_FOUND()
17
- * return incident.toProps()
18
- * }
19
- * }
20
- * ```
21
- */
22
- export declare abstract class ApiUseCase {
23
- /** Discriminator for `App.from` / `isApiUseCase`. Do not override. */
24
- static readonly trigger: "api";
25
- /** When true, omitted from contract, HTTP RPC, and `app.local`. Still in completeness. */
26
- static readonly internal?: boolean;
27
- /** Dotted RPC path (`incident.close`). Must be unique among API use cases. */
28
- static readonly key: string;
29
- /** Request Standard Schema. Validated before `execute`. */
30
- static readonly input: unknown;
31
- /** Success Standard Schema. Types `execute`, `run`, and the contract. */
32
- static readonly output: unknown;
33
- /** Declared refusals. Keys become `errors.CODE()` factories on `ExecuteCtx`. */
34
- static readonly errors: ErrorMap;
35
- /** Port tokens keyed by the alias used in `execute`. Required by `App.from`. */
36
- static readonly ports?: Record<string, PortToken<unknown>>;
37
- /** `static publishes = [DomainEvents] as const` — without `as const`, Events widens to EventClass. */
38
- static readonly publishes?: readonly AnyEventCatalog[];
39
- /** `static channels = [ClientChannel] as const` — without `as const`, catalog keys widen. */
40
- static readonly channels?: readonly EventChannelCtor[];
41
- /** RPC middleware for this use case, after app-level `App.use`. */
42
- static readonly middleware?: readonly unknown[];
43
- protected constructor();
44
- /** Typed `never` so subclasses may take `ExecuteCtx` (tighter `publish`). */
45
- abstract execute(ctx: never): Promise<unknown>;
46
- /**
47
- * Same one-liner as `if (!result.ok) throw new CodedError({ code: result.code })`.
48
- * For `validate` / custom Results. Entity methods throw themselves.
49
- */
50
- unwrap<T, E extends string>(result: Result<T, E>): T;
51
- }
52
- //# sourceMappingURL=api-use-case.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"api-use-case.d.ts","sourceRoot":"","sources":["../../src/app/api-use-case.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACrE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD;;;;;;;;;;;;;;;;;GAiBG;AACH,8BAAsB,UAAU;IAC9B,sEAAsE;IACtE,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAG,KAAK,CAAU;IACzC,0FAA0F;IAC1F,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnC,8EAA8E;IAC9E,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAC5B,2DAA2D;IAC3D,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IAC/B,yEAAyE;IACzE,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IAChC,gFAAgF;IAChF,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAM;IACtC,gFAAgF;IAChF,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3D,sGAAsG;IACtG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACvD,6FAA6F;IAC7F,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACvD,mEAAmE;IACnE,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;IAEhD,SAAS;IAET,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IAE9C;;;OAGG;IACH,MAAM,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC;CAYrD"}
@@ -1,59 +0,0 @@
1
- import { CodedError } from '../core/index.js';
2
- /**
3
- * Request/response application use case. Declare static `key`, `input`, `output`, `errors`, `ports`.
4
- *
5
- * ```ts
6
- * class CloseIncident extends ApiUseCase {
7
- * static readonly key = 'incident.close'
8
- * static readonly input = z.object({ id: z.string() })
9
- * static readonly output = Incident.schema
10
- * static readonly errors = { NOT_FOUND: { message: 'Incident not found' } } as const
11
- * static readonly ports = { incidents: IncidentRepository }
12
- * async execute({ input, ports, errors }: ExecuteCtx<typeof CloseIncident>) {
13
- * const incident = await ports.incidents.get(input.id)
14
- * if (!incident) throw errors.NOT_FOUND()
15
- * return incident.toProps()
16
- * }
17
- * }
18
- * ```
19
- */
20
- export class ApiUseCase {
21
- /** Discriminator for `App.from` / `isApiUseCase`. Do not override. */
22
- static trigger = 'api';
23
- /** When true, omitted from contract, HTTP RPC, and `app.local`. Still in completeness. */
24
- static internal;
25
- /** Dotted RPC path (`incident.close`). Must be unique among API use cases. */
26
- static key;
27
- /** Request Standard Schema. Validated before `execute`. */
28
- static input;
29
- /** Success Standard Schema. Types `execute`, `run`, and the contract. */
30
- static output;
31
- /** Declared refusals. Keys become `errors.CODE()` factories on `ExecuteCtx`. */
32
- static errors = {};
33
- /** Port tokens keyed by the alias used in `execute`. Required by `App.from`. */
34
- static ports;
35
- /** `static publishes = [DomainEvents] as const` — without `as const`, Events widens to EventClass. */
36
- static publishes;
37
- /** `static channels = [ClientChannel] as const` — without `as const`, catalog keys widen. */
38
- static channels;
39
- /** RPC middleware for this use case, after app-level `App.use`. */
40
- static middleware;
41
- constructor() { }
42
- /**
43
- * Same one-liner as `if (!result.ok) throw new CodedError({ code: result.code })`.
44
- * For `validate` / custom Results. Entity methods throw themselves.
45
- */
46
- unwrap(result) {
47
- if (!result.ok) {
48
- throw new CodedError({
49
- code: result.code,
50
- message: result.code,
51
- ...(result.issues !== undefined
52
- ? { data: { issues: result.issues } }
53
- : {}),
54
- });
55
- }
56
- return result.value;
57
- }
58
- }
59
- //# sourceMappingURL=api-use-case.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"api-use-case.js","sourceRoot":"","sources":["../../src/app/api-use-case.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA8B,MAAM,kBAAkB,CAAC;AAI1E;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAgB,UAAU;IAC9B,sEAAsE;IACtE,MAAM,CAAU,OAAO,GAAG,KAAc,CAAC;IACzC,0FAA0F;IAC1F,MAAM,CAAU,QAAQ,CAAW;IACnC,8EAA8E;IAC9E,MAAM,CAAU,GAAG,CAAS;IAC5B,2DAA2D;IAC3D,MAAM,CAAU,KAAK,CAAU;IAC/B,yEAAyE;IACzE,MAAM,CAAU,MAAM,CAAU;IAChC,gFAAgF;IAChF,MAAM,CAAU,MAAM,GAAa,EAAE,CAAC;IACtC,gFAAgF;IAChF,MAAM,CAAU,KAAK,CAAsC;IAC3D,sGAAsG;IACtG,MAAM,CAAU,SAAS,CAA8B;IACvD,6FAA6F;IAC7F,MAAM,CAAU,QAAQ,CAA+B;IACvD,mEAAmE;IACnE,MAAM,CAAU,UAAU,CAAsB;IAEhD,gBAAyB,CAAC;IAK1B;;;OAGG;IACH,MAAM,CAAsB,MAAoB;QAC9C,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,IAAI,UAAU,CAAC;gBACnB,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,OAAO,EAAE,MAAM,CAAC,IAAI;gBACpB,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS;oBAC7B,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE;oBACrC,CAAC,CAAC,EAAE,CAAC;aACR,CAAC,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC"}
@@ -1,18 +0,0 @@
1
- import type { BrokerAdapter, BrokerConsumeCtx, Envelope } from '../domain/index.js';
2
- type Consumer = (envelope: Envelope, ctx: BrokerConsumeCtx) => Promise<void>;
3
- /**
4
- * In-process broker. `consume` dispatches with attempt; ack/nack are no-ops
5
- * besides recording. `stop()` clears consumers.
6
- */
7
- export declare class InMemoryBroker implements BrokerAdapter {
8
- #private;
9
- readonly kind: "broker";
10
- readonly published: Envelope[];
11
- attempts: number;
12
- static create(): InMemoryBroker;
13
- publish(envelope: Envelope): Promise<void>;
14
- consume(key: string, group: string, handler: Consumer): void;
15
- stop(): Promise<void>;
16
- }
17
- export {};
18
- //# sourceMappingURL=in-memory-broker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"in-memory-broker.d.ts","sourceRoot":"","sources":["../../src/testing/in-memory-broker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACT,MAAM,oBAAoB,CAAC;AAE5B,KAAK,QAAQ,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE7E;;;GAGG;AACH,qBAAa,cAAe,YAAW,aAAa;;IAClD,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAClC,QAAQ,CAAC,SAAS,EAAE,QAAQ,EAAE,CAAM;IAEpC,QAAQ,SAAK;IAEb,MAAM,CAAC,MAAM,IAAI,cAAc;IAIzB,OAAO,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBhD,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI;IAMtD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B"}
@@ -1,42 +0,0 @@
1
- /**
2
- * In-process broker. `consume` dispatches with attempt; ack/nack are no-ops
3
- * besides recording. `stop()` clears consumers.
4
- */
5
- export class InMemoryBroker {
6
- kind = 'broker';
7
- published = [];
8
- #consumers = new Map();
9
- attempts = 1;
10
- static create() {
11
- return new InMemoryBroker();
12
- }
13
- async publish(envelope) {
14
- this.published.push(envelope);
15
- const groups = this.#consumers.get(envelope.key);
16
- if (!groups)
17
- return;
18
- for (const consumer of groups.values()) {
19
- let acked = false;
20
- const ctx = {
21
- attempt: this.attempts,
22
- ack: async () => {
23
- acked = true;
24
- },
25
- nack: async () => {
26
- acked = false;
27
- },
28
- };
29
- await consumer(envelope, ctx);
30
- void acked;
31
- }
32
- }
33
- consume(key, group, handler) {
34
- const groups = this.#consumers.get(key) ?? new Map();
35
- groups.set(group, handler);
36
- this.#consumers.set(key, groups);
37
- }
38
- async stop() {
39
- this.#consumers.clear();
40
- }
41
- }
42
- //# sourceMappingURL=in-memory-broker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"in-memory-broker.js","sourceRoot":"","sources":["../../src/testing/in-memory-broker.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,MAAM,OAAO,cAAc;IAChB,IAAI,GAAG,QAAiB,CAAC;IACzB,SAAS,GAAe,EAAE,CAAC;IACpC,UAAU,GAAG,IAAI,GAAG,EAAiC,CAAC;IACtD,QAAQ,GAAG,CAAC,CAAC;IAEb,MAAM,CAAC,MAAM;QACX,OAAO,IAAI,cAAc,EAAE,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,QAAkB;QAC9B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACjD,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;YACvC,IAAI,KAAK,GAAG,KAAK,CAAC;YAClB,MAAM,GAAG,GAAqB;gBAC5B,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,GAAG,EAAE,KAAK,IAAI,EAAE;oBACd,KAAK,GAAG,IAAI,CAAC;gBACf,CAAC;gBACD,IAAI,EAAE,KAAK,IAAI,EAAE;oBACf,KAAK,GAAG,KAAK,CAAC;gBAChB,CAAC;aACF,CAAC;YACF,MAAM,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAC9B,KAAK,KAAK,CAAC;QACb,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAW,EAAE,KAAa,EAAE,OAAiB;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,EAAoB,CAAC;QACvE,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC3B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;CACF"}