hexok 1.0.1 → 2.1.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.
Files changed (262) hide show
  1. package/README.md +9 -22
  2. package/dist/adapter.d.ts +18 -0
  3. package/dist/adapter.d.ts.map +1 -0
  4. package/dist/adapter.js +21 -0
  5. package/dist/adapter.js.map +1 -0
  6. package/dist/catalog.d.ts +88 -0
  7. package/dist/catalog.d.ts.map +1 -0
  8. package/dist/catalog.js +79 -0
  9. package/dist/catalog.js.map +1 -0
  10. package/dist/{core/coded-error.d.ts → coded-error.d.ts} +9 -4
  11. package/dist/coded-error.d.ts.map +1 -0
  12. package/dist/{core/coded-error.js → coded-error.js} +9 -4
  13. package/dist/coded-error.js.map +1 -0
  14. package/dist/cow-draft.d.ts.map +1 -0
  15. package/dist/cow-draft.js.map +1 -0
  16. package/dist/entity.d.ts +63 -0
  17. package/dist/entity.d.ts.map +1 -0
  18. package/dist/{domain/entity.js → entity.js} +52 -80
  19. package/dist/entity.js.map +1 -0
  20. package/dist/error-map.d.ts +22 -0
  21. package/dist/error-map.d.ts.map +1 -0
  22. package/dist/error-map.js +2 -0
  23. package/dist/error-map.js.map +1 -0
  24. package/dist/errors.d.ts +85 -0
  25. package/dist/errors.d.ts.map +1 -0
  26. package/dist/errors.js +85 -0
  27. package/dist/errors.js.map +1 -0
  28. package/dist/event-handler.d.ts +31 -0
  29. package/dist/event-handler.d.ts.map +1 -0
  30. package/dist/event-handler.js +28 -0
  31. package/dist/event-handler.js.map +1 -0
  32. package/dist/event.d.ts +37 -0
  33. package/dist/event.d.ts.map +1 -0
  34. package/dist/event.js +44 -0
  35. package/dist/event.js.map +1 -0
  36. package/dist/index.d.ts +16 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +14 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/mapper.d.ts +100 -0
  41. package/dist/mapper.d.ts.map +1 -0
  42. package/dist/mapper.js +167 -0
  43. package/dist/mapper.js.map +1 -0
  44. package/dist/port.d.ts +20 -0
  45. package/dist/port.d.ts.map +1 -0
  46. package/dist/port.js +25 -0
  47. package/dist/port.js.map +1 -0
  48. package/dist/result.d.ts.map +1 -0
  49. package/dist/result.js.map +1 -0
  50. package/dist/schema.d.ts +37 -0
  51. package/dist/schema.d.ts.map +1 -0
  52. package/dist/schema.js +50 -0
  53. package/dist/schema.js.map +1 -0
  54. package/dist/{core/standard-schema.d.ts → standard-schema.d.ts} +1 -1
  55. package/dist/standard-schema.d.ts.map +1 -0
  56. package/dist/standard-schema.js.map +1 -0
  57. package/dist/use-case.d.ts +183 -0
  58. package/dist/use-case.d.ts.map +1 -0
  59. package/dist/use-case.js +221 -0
  60. package/dist/use-case.js.map +1 -0
  61. package/dist/{core/schema.d.ts → validate.d.ts} +1 -1
  62. package/dist/validate.d.ts.map +1 -0
  63. package/dist/{core/schema.js → validate.js} +1 -1
  64. package/dist/validate.js.map +1 -0
  65. package/package.json +7 -26
  66. package/skills/hexok/SKILL.md +127 -0
  67. package/skills/hexok/references/adapter.md +66 -0
  68. package/skills/hexok/references/entity.md +74 -0
  69. package/skills/hexok/references/error.md +140 -0
  70. package/skills/hexok/references/event-catalog.md +57 -0
  71. package/skills/hexok/references/event-handler.md +56 -0
  72. package/skills/hexok/references/event.md +53 -0
  73. package/skills/hexok/references/mapper.md +77 -0
  74. package/skills/hexok/references/port.md +58 -0
  75. package/skills/hexok/references/schema.md +47 -0
  76. package/skills/hexok/references/use-case.md +164 -0
  77. package/dist/app/callable-use-case.d.ts +0 -34
  78. package/dist/app/callable-use-case.d.ts.map +0 -1
  79. package/dist/app/callable-use-case.js +0 -41
  80. package/dist/app/callable-use-case.js.map +0 -1
  81. package/dist/app/contract.d.ts +0 -46
  82. package/dist/app/contract.d.ts.map +0 -1
  83. package/dist/app/contract.js +0 -28
  84. package/dist/app/contract.js.map +0 -1
  85. package/dist/app/error-factory.d.ts +0 -9
  86. package/dist/app/error-factory.d.ts.map +0 -1
  87. package/dist/app/error-factory.js +0 -30
  88. package/dist/app/error-factory.js.map +0 -1
  89. package/dist/app/event-channel.d.ts +0 -133
  90. package/dist/app/event-channel.d.ts.map +0 -1
  91. package/dist/app/event-channel.js +0 -49
  92. package/dist/app/event-channel.js.map +0 -1
  93. package/dist/app/event-use-case.d.ts +0 -47
  94. package/dist/app/event-use-case.d.ts.map +0 -1
  95. package/dist/app/event-use-case.js +0 -54
  96. package/dist/app/event-use-case.js.map +0 -1
  97. package/dist/app/execute-ctx.d.ts +0 -92
  98. package/dist/app/execute-ctx.d.ts.map +0 -1
  99. package/dist/app/execute-ctx.js +0 -2
  100. package/dist/app/execute-ctx.js.map +0 -1
  101. package/dist/app/external-use-case.d.ts +0 -25
  102. package/dist/app/external-use-case.d.ts.map +0 -1
  103. package/dist/app/external-use-case.js +0 -25
  104. package/dist/app/external-use-case.js.map +0 -1
  105. package/dist/app/index.d.ts +0 -9
  106. package/dist/app/index.d.ts.map +0 -1
  107. package/dist/app/index.js +0 -8
  108. package/dist/app/index.js.map +0 -1
  109. package/dist/app/internal-use-case.d.ts +0 -26
  110. package/dist/app/internal-use-case.d.ts.map +0 -1
  111. package/dist/app/internal-use-case.js +0 -26
  112. package/dist/app/internal-use-case.js.map +0 -1
  113. package/dist/app/nest.d.ts +0 -9
  114. package/dist/app/nest.d.ts.map +0 -1
  115. package/dist/app/nest.js +0 -28
  116. package/dist/app/nest.js.map +0 -1
  117. package/dist/app/types.d.ts +0 -153
  118. package/dist/app/types.d.ts.map +0 -1
  119. package/dist/app/types.js +0 -17
  120. package/dist/app/types.js.map +0 -1
  121. package/dist/core/coded-error.d.ts.map +0 -1
  122. package/dist/core/coded-error.js.map +0 -1
  123. package/dist/core/error-map.d.ts +0 -21
  124. package/dist/core/error-map.d.ts.map +0 -1
  125. package/dist/core/error-map.js +0 -5
  126. package/dist/core/error-map.js.map +0 -1
  127. package/dist/core/index.d.ts +0 -6
  128. package/dist/core/index.d.ts.map +0 -1
  129. package/dist/core/index.js +0 -5
  130. package/dist/core/index.js.map +0 -1
  131. package/dist/core/result.d.ts.map +0 -1
  132. package/dist/core/result.js.map +0 -1
  133. package/dist/core/schema.d.ts.map +0 -1
  134. package/dist/core/schema.js.map +0 -1
  135. package/dist/core/standard-schema.d.ts.map +0 -1
  136. package/dist/core/standard-schema.js.map +0 -1
  137. package/dist/domain/capabilities.d.ts +0 -42
  138. package/dist/domain/capabilities.d.ts.map +0 -1
  139. package/dist/domain/capabilities.js +0 -2
  140. package/dist/domain/capabilities.js.map +0 -1
  141. package/dist/domain/catalog.d.ts +0 -46
  142. package/dist/domain/catalog.d.ts.map +0 -1
  143. package/dist/domain/catalog.js +0 -114
  144. package/dist/domain/catalog.js.map +0 -1
  145. package/dist/domain/channel.d.ts +0 -36
  146. package/dist/domain/channel.d.ts.map +0 -1
  147. package/dist/domain/channel.js +0 -2
  148. package/dist/domain/channel.js.map +0 -1
  149. package/dist/domain/cow-draft.d.ts.map +0 -1
  150. package/dist/domain/cow-draft.js.map +0 -1
  151. package/dist/domain/entity.d.ts +0 -120
  152. package/dist/domain/entity.d.ts.map +0 -1
  153. package/dist/domain/entity.js.map +0 -1
  154. package/dist/domain/envelope.d.ts +0 -59
  155. package/dist/domain/envelope.d.ts.map +0 -1
  156. package/dist/domain/envelope.js +0 -2
  157. package/dist/domain/envelope.js.map +0 -1
  158. package/dist/domain/event.d.ts +0 -44
  159. package/dist/domain/event.d.ts.map +0 -1
  160. package/dist/domain/event.js +0 -21
  161. package/dist/domain/event.js.map +0 -1
  162. package/dist/domain/index.d.ts +0 -8
  163. package/dist/domain/index.d.ts.map +0 -1
  164. package/dist/domain/index.js +0 -5
  165. package/dist/domain/index.js.map +0 -1
  166. package/dist/domain/port.d.ts +0 -45
  167. package/dist/domain/port.d.ts.map +0 -1
  168. package/dist/domain/port.js +0 -41
  169. package/dist/domain/port.js.map +0 -1
  170. package/dist/infra/adapter.d.ts +0 -22
  171. package/dist/infra/adapter.d.ts.map +0 -1
  172. package/dist/infra/adapter.js +0 -18
  173. package/dist/infra/adapter.js.map +0 -1
  174. package/dist/infra/index.d.ts +0 -3
  175. package/dist/infra/index.d.ts.map +0 -1
  176. package/dist/infra/index.js +0 -3
  177. package/dist/infra/index.js.map +0 -1
  178. package/dist/infra/mapper.d.ts +0 -48
  179. package/dist/infra/mapper.d.ts.map +0 -1
  180. package/dist/infra/mapper.js +0 -77
  181. package/dist/infra/mapper.js.map +0 -1
  182. package/dist/runtime/app.d.ts +0 -100
  183. package/dist/runtime/app.d.ts.map +0 -1
  184. package/dist/runtime/app.js +0 -292
  185. package/dist/runtime/app.js.map +0 -1
  186. package/dist/runtime/channel.d.ts +0 -34
  187. package/dist/runtime/channel.d.ts.map +0 -1
  188. package/dist/runtime/channel.js +0 -110
  189. package/dist/runtime/channel.js.map +0 -1
  190. package/dist/runtime/client.d.ts +0 -21
  191. package/dist/runtime/client.d.ts.map +0 -1
  192. package/dist/runtime/client.js +0 -2
  193. package/dist/runtime/client.js.map +0 -1
  194. package/dist/runtime/completeness.d.ts +0 -89
  195. package/dist/runtime/completeness.d.ts.map +0 -1
  196. package/dist/runtime/completeness.js +0 -2
  197. package/dist/runtime/completeness.js.map +0 -1
  198. package/dist/runtime/envelope.d.ts +0 -7
  199. package/dist/runtime/envelope.d.ts.map +0 -1
  200. package/dist/runtime/envelope.js +0 -69
  201. package/dist/runtime/envelope.js.map +0 -1
  202. package/dist/runtime/events.d.ts +0 -6
  203. package/dist/runtime/events.d.ts.map +0 -1
  204. package/dist/runtime/events.js +0 -48
  205. package/dist/runtime/events.js.map +0 -1
  206. package/dist/runtime/http-status.d.ts +0 -3
  207. package/dist/runtime/http-status.d.ts.map +0 -1
  208. package/dist/runtime/http-status.js +0 -11
  209. package/dist/runtime/http-status.js.map +0 -1
  210. package/dist/runtime/http.d.ts +0 -4
  211. package/dist/runtime/http.d.ts.map +0 -1
  212. package/dist/runtime/http.js +0 -58
  213. package/dist/runtime/http.js.map +0 -1
  214. package/dist/runtime/index.d.ts +0 -10
  215. package/dist/runtime/index.d.ts.map +0 -1
  216. package/dist/runtime/index.js +0 -6
  217. package/dist/runtime/index.js.map +0 -1
  218. package/dist/runtime/interceptor.d.ts +0 -61
  219. package/dist/runtime/interceptor.d.ts.map +0 -1
  220. package/dist/runtime/interceptor.js +0 -7
  221. package/dist/runtime/interceptor.js.map +0 -1
  222. package/dist/runtime/invoke.d.ts +0 -34
  223. package/dist/runtime/invoke.d.ts.map +0 -1
  224. package/dist/runtime/invoke.js +0 -235
  225. package/dist/runtime/invoke.js.map +0 -1
  226. package/dist/runtime/middleware.d.ts +0 -20
  227. package/dist/runtime/middleware.d.ts.map +0 -1
  228. package/dist/runtime/middleware.js +0 -2
  229. package/dist/runtime/middleware.js.map +0 -1
  230. package/dist/runtime/rpc.d.ts +0 -24
  231. package/dist/runtime/rpc.d.ts.map +0 -1
  232. package/dist/runtime/rpc.js +0 -16
  233. package/dist/runtime/rpc.js.map +0 -1
  234. package/dist/testing/app-test.d.ts +0 -77
  235. package/dist/testing/app-test.d.ts.map +0 -1
  236. package/dist/testing/app-test.js +0 -109
  237. package/dist/testing/app-test.js.map +0 -1
  238. package/dist/testing/in-memory-bus.d.ts +0 -19
  239. package/dist/testing/in-memory-bus.d.ts.map +0 -1
  240. package/dist/testing/in-memory-bus.js +0 -31
  241. package/dist/testing/in-memory-bus.js.map +0 -1
  242. package/dist/testing/in-memory-channel.d.ts +0 -32
  243. package/dist/testing/in-memory-channel.d.ts.map +0 -1
  244. package/dist/testing/in-memory-channel.js +0 -65
  245. package/dist/testing/in-memory-channel.js.map +0 -1
  246. package/dist/testing/in-memory-queue.d.ts +0 -24
  247. package/dist/testing/in-memory-queue.d.ts.map +0 -1
  248. package/dist/testing/in-memory-queue.js +0 -66
  249. package/dist/testing/in-memory-queue.js.map +0 -1
  250. package/dist/testing/in-memory-repository.d.ts +0 -44
  251. package/dist/testing/in-memory-repository.d.ts.map +0 -1
  252. package/dist/testing/in-memory-repository.js +0 -60
  253. package/dist/testing/in-memory-repository.js.map +0 -1
  254. package/dist/testing/index.d.ts +0 -6
  255. package/dist/testing/index.d.ts.map +0 -1
  256. package/dist/testing/index.js +0 -6
  257. package/dist/testing/index.js.map +0 -1
  258. /package/dist/{domain/cow-draft.d.ts → cow-draft.d.ts} +0 -0
  259. /package/dist/{domain/cow-draft.js → cow-draft.js} +0 -0
  260. /package/dist/{core/result.d.ts → result.d.ts} +0 -0
  261. /package/dist/{core/result.js → result.js} +0 -0
  262. /package/dist/{core/standard-schema.js → standard-schema.js} +0 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Application service. Pass ports through the constructor.
3
+ * `execute` is required; the compiler errors on the class when it is missing.
4
+ * Declare the input and output on `execute` — those are the types callers see.
5
+ * A refusal throws an {@link Errors} catalog member. The application catches it.
6
+ *
7
+ * ```ts
8
+ * class CreateUser extends UseCase('user.create') {
9
+ * constructor(private readonly users: UserRepository) { super() }
10
+ * async execute(input: { email: string }): Promise<{ id: string }> {
11
+ * return { id: input.email }
12
+ * }
13
+ * }
14
+ * ```
15
+ */
16
+ export declare function UseCase<const Token extends string>(token: Token): (abstract new () => {
17
+ /** Nominal marker. Each `UseCase(...)` call is a distinct class. */
18
+ readonly "__#private@#brand": true;
19
+ /**
20
+ * Required. Annotate `input` and the return type on the subclass;
21
+ * callers of the subclass see those types.
22
+ */
23
+ execute(input: unknown): Promise<unknown>;
24
+ }) & {
25
+ /** Use-case name. Literal type of the string passed to {@link UseCase}. */
26
+ readonly token: Token;
27
+ };
28
+ type ReservedKey = 'token' | 'prototype' | 'name' | 'length';
29
+ /** A bag that uses a reserved class static fails on that property. */
30
+ type ReservedOk<S> = {
31
+ [K in keyof S]: K extends ReservedKey ? never : S[K];
32
+ };
33
+ type NoStatics = Record<never, never>;
34
+ /** Static bag minus names that would overwrite the class constructor. */
35
+ type Statics<S> = Omit<S, ReservedKey>;
36
+ /**
37
+ * Family of use cases that receive a caller context on `execute`.
38
+ * Returns a factory: a token, or a token and statics when `Spec` has keys.
39
+ * `execute` is required; annotate it on the subclass — callers see those types.
40
+ * A guard and hooks, when set, run around that method.
41
+ * Statics are application data copied onto the class. The application validates `input`.
42
+ * {@link UseCase.GuardParameters} reads the guard's call off the factory.
43
+ * {@link UseCase.Ctx} reads that call's context type.
44
+ * {@link context} returns that factory. {@link ContextFactory.guard} and
45
+ * {@link ContextFactory.hooks} return it again.
46
+ *
47
+ * ```ts
48
+ * const ApiUseCase = UseCase.context<
49
+ * ApiContext,
50
+ * { input: StandardSchemaV1; permission: string }
51
+ * >()
52
+ * .guard((call) => {
53
+ * if (call.ctx.sessionId.length < 1 || call.spec.permission.length < 1) {
54
+ * throw new Error('unauthorized')
55
+ * }
56
+ * })
57
+ * .hooks({
58
+ * preExecute: () => ({ started: Date.now() }),
59
+ * postExecute(_call, state) {
60
+ * void state.started
61
+ * },
62
+ * })
63
+ *
64
+ * class FindUsers extends ApiUseCase('user.find', {
65
+ * input: z.object({ query: z.string() }),
66
+ * permission: 'users.read',
67
+ * }) {
68
+ * constructor(private readonly users: UserRepository) { super() }
69
+ * async execute(
70
+ * ctx: UseCase.Ctx<typeof ApiUseCase>,
71
+ * input: { query: string },
72
+ * ): Promise<User[]> {
73
+ * return this.users.search(input.query)
74
+ * }
75
+ * }
76
+ * ```
77
+ */
78
+ export declare namespace UseCase {
79
+ /**
80
+ * Call passed to a {@link context} guard.
81
+ * `ctx` is the per-call context. `spec` is the static contract.
82
+ * `token` is the use-case name. `input` is the argument passed to `execute`.
83
+ *
84
+ * ```ts
85
+ * type Call = UseCase.GuardParameters<typeof ApiUseCase>
86
+ * ```
87
+ */
88
+ type GuardParameters<Factory> = Factory extends GuardParametersSlot<infer Ctx, infer Spec> ? Call<Ctx, Spec> : never;
89
+ /**
90
+ * Context type of a {@link context} factory.
91
+ * Pass the factory. A subclass, and a plain {@link UseCase} class, are `never`.
92
+ *
93
+ * ```ts
94
+ * async execute(
95
+ * ctx: UseCase.Ctx<typeof ApiUseCase>,
96
+ * input: { query: string },
97
+ * ): Promise<User[]> {}
98
+ * ```
99
+ */
100
+ type Ctx<Factory> = GuardParameters<Factory>['ctx'];
101
+ /**
102
+ * Factory returned by {@link context}.
103
+ * An exported const infers this type.
104
+ * `guard` and `hooks` return the same factory.
105
+ */
106
+ interface ContextFactory<Ctx, Spec = NoStatics> extends ReturnType<typeof buildContext<Ctx, Spec>> {
107
+ /**
108
+ * Runs after guards already registered, and before `execute`.
109
+ * Returns the same factory.
110
+ */
111
+ guard(guard: (call: Call<Ctx, Spec>) => void | Promise<void>): ContextFactory<Ctx, Spec>;
112
+ /**
113
+ * Lifecycle around `execute`. The value returned from `preExecute` is the
114
+ * `state` argument of `postExecute`, `onCatch`, and `onFinally`.
115
+ * A second call throws when this factory already has hooks.
116
+ */
117
+ hooks<State = void>(definition: HookDefinition<Ctx, Spec, State>): ContextFactory<Ctx, Spec>;
118
+ }
119
+ /**
120
+ * Family factory. Pass the context type.
121
+ * The argument is a settings object with no keys yet.
122
+ */
123
+ function context<Ctx, Spec = NoStatics>(config?: ContextConfig): unknown extends Ctx ? 'Pass a type argument: UseCase.context<Ctx>()' : ContextFactory<Ctx, Spec>;
124
+ }
125
+ /** Values a guard and the hooks share for one invocation. */
126
+ type Call<Ctx, Spec> = {
127
+ readonly ctx: Ctx;
128
+ readonly spec: Spec;
129
+ readonly token: string;
130
+ /** The `input` argument the caller passed to `execute`. */
131
+ readonly input: unknown;
132
+ };
133
+ type Performed<Ctx, Spec> = Call<Ctx, Spec> & {
134
+ readonly result: unknown;
135
+ };
136
+ type Caught<Ctx, Spec> = Call<Ctx, Spec> & {
137
+ readonly error: unknown;
138
+ };
139
+ type Settled<Ctx, Spec> = (Call<Ctx, Spec> & {
140
+ readonly status: 'success';
141
+ readonly result: unknown;
142
+ }) | (Call<Ctx, Spec> & {
143
+ readonly status: 'failure';
144
+ readonly error: unknown;
145
+ });
146
+ /** Settings for {@link UseCase.context}. There are none yet. */
147
+ type ContextConfig = Record<string, never>;
148
+ type ContextGuard<Ctx, Spec> = (call: Call<Ctx, Spec>) => void | Promise<void>;
149
+ type HookDefinition<Ctx, Spec, State> = {
150
+ preExecute?: (call: Call<Ctx, Spec>) => State | Promise<State>;
151
+ postExecute?: (call: Performed<Ctx, Spec>, state: Awaited<State>) => void | Promise<void>;
152
+ onCatch?: (call: Caught<Ctx, Spec>, state: Awaited<State> | undefined) => void | Promise<void>;
153
+ onFinally?: (call: Settled<Ctx, Spec>, state: Awaited<State> | undefined) => void | Promise<void>;
154
+ };
155
+ /** Hooks stored on one factory. State is erased after `.hooks()` checks it. */
156
+ type StoredHooks<Ctx, Spec> = {
157
+ preExecute?: (call: Call<Ctx, Spec>) => unknown;
158
+ postExecute?: (call: Performed<Ctx, Spec>, state: unknown) => void | Promise<void>;
159
+ onCatch?: (call: Caught<Ctx, Spec>, state: unknown) => void | Promise<void>;
160
+ onFinally?: (call: Settled<Ctx, Spec>, state: unknown) => void | Promise<void>;
161
+ };
162
+ /** Type-only mark. Absent at runtime. Same symbol in {@link UseCase.GuardParameters}. */
163
+ declare const guardParameters: unique symbol;
164
+ type GuardParametersSlot<Ctx, Spec> = {
165
+ readonly [guardParameters]?: (call: Call<Ctx, Spec>) => void;
166
+ };
167
+ declare function buildContext<Ctx, Spec = NoStatics>(options?: {
168
+ guard?: ContextGuard<Ctx, Spec>;
169
+ hooks?: StoredHooks<Ctx, Spec>;
170
+ }): (<const Token extends string, const S extends [keyof Spec] extends [never] ? NoStatics : Spec>(token: Token, ...args: [keyof Spec] extends [never] ? [] : [statics: ReservedOk<S> & Spec]) => ((abstract new () => {
171
+ /** Nominal marker. Each factory call is a distinct class. */
172
+ readonly "__#private@#brand": true;
173
+ /**
174
+ * Required. Annotate `ctx`, `input`, and the return type on the subclass.
175
+ * A guard and hooks run around this method when set.
176
+ */
177
+ execute(ctx: Ctx, input: unknown): Promise<unknown>;
178
+ }) & {
179
+ /** Use-case name. Literal type of the string passed to the factory. */
180
+ readonly token: Token;
181
+ }) & ([keyof Spec] extends [never] ? NoStatics : Statics<S>)) & GuardParametersSlot<Ctx, Spec>;
182
+ export {};
183
+ //# sourceMappingURL=use-case.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-case.d.ts","sourceRoot":"","sources":["../src/use-case.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Application service. Pass ports through the constructor.
3
+ * `execute` is required; the compiler errors on the class when it is missing.
4
+ * Declare the input and output on `execute` — those are the types callers see.
5
+ * A refusal throws an {@link Errors} catalog member. The application catches it.
6
+ *
7
+ * ```ts
8
+ * class CreateUser extends UseCase('user.create') {
9
+ * constructor(private readonly users: UserRepository) { super() }
10
+ * async execute(input: { email: string }): Promise<{ id: string }> {
11
+ * return { id: input.email }
12
+ * }
13
+ * }
14
+ * ```
15
+ */
16
+ export function UseCase(token) {
17
+ class UseCaseClass {
18
+ /** Use-case name. Literal type of the string passed to {@link UseCase}. */
19
+ static token = token;
20
+ /** Nominal marker. Each `UseCase(...)` call is a distinct class. */
21
+ #brand = true;
22
+ constructor() {
23
+ void this.#brand;
24
+ }
25
+ }
26
+ return UseCaseClass;
27
+ }
28
+ const RESERVED_STATICS = new Set([
29
+ 'token',
30
+ 'prototype',
31
+ 'name',
32
+ 'length',
33
+ ]);
34
+ /**
35
+ * Family of use cases that receive a caller context on `execute`.
36
+ * Returns a factory: a token, or a token and statics when `Spec` has keys.
37
+ * `execute` is required; annotate it on the subclass — callers see those types.
38
+ * A guard and hooks, when set, run around that method.
39
+ * Statics are application data copied onto the class. The application validates `input`.
40
+ * {@link UseCase.GuardParameters} reads the guard's call off the factory.
41
+ * {@link UseCase.Ctx} reads that call's context type.
42
+ * {@link context} returns that factory. {@link ContextFactory.guard} and
43
+ * {@link ContextFactory.hooks} return it again.
44
+ *
45
+ * ```ts
46
+ * const ApiUseCase = UseCase.context<
47
+ * ApiContext,
48
+ * { input: StandardSchemaV1; permission: string }
49
+ * >()
50
+ * .guard((call) => {
51
+ * if (call.ctx.sessionId.length < 1 || call.spec.permission.length < 1) {
52
+ * throw new Error('unauthorized')
53
+ * }
54
+ * })
55
+ * .hooks({
56
+ * preExecute: () => ({ started: Date.now() }),
57
+ * postExecute(_call, state) {
58
+ * void state.started
59
+ * },
60
+ * })
61
+ *
62
+ * class FindUsers extends ApiUseCase('user.find', {
63
+ * input: z.object({ query: z.string() }),
64
+ * permission: 'users.read',
65
+ * }) {
66
+ * constructor(private readonly users: UserRepository) { super() }
67
+ * async execute(
68
+ * ctx: UseCase.Ctx<typeof ApiUseCase>,
69
+ * input: { query: string },
70
+ * ): Promise<User[]> {
71
+ * return this.users.search(input.query)
72
+ * }
73
+ * }
74
+ * ```
75
+ */
76
+ (function (UseCase) {
77
+ /**
78
+ * Family factory. Pass the context type.
79
+ * The argument is a settings object with no keys yet.
80
+ */
81
+ function context(config) {
82
+ void config;
83
+ return bindFactory({});
84
+ }
85
+ UseCase.context = context;
86
+ })(UseCase || (UseCase = {}));
87
+ function storedHooks(definition) {
88
+ if (definition.preExecute === undefined &&
89
+ definition.postExecute === undefined &&
90
+ definition.onCatch === undefined &&
91
+ definition.onFinally === undefined) {
92
+ return undefined;
93
+ }
94
+ return definition;
95
+ }
96
+ function composeGuard(current, next) {
97
+ if (current === undefined)
98
+ return next;
99
+ return async (call) => {
100
+ await current(call);
101
+ await next(call);
102
+ };
103
+ }
104
+ function bindFactory(slots) {
105
+ const factory = buildContext(slots);
106
+ Object.defineProperty(factory, 'guard', {
107
+ configurable: true,
108
+ enumerable: false,
109
+ writable: true,
110
+ value: (guard) => bindFactory({
111
+ ...slots,
112
+ guard: composeGuard(slots.guard, guard),
113
+ }),
114
+ });
115
+ Object.defineProperty(factory, 'hooks', {
116
+ configurable: true,
117
+ enumerable: false,
118
+ writable: true,
119
+ value: (definition) => {
120
+ const hooks = storedHooks(definition);
121
+ if (slots.hooks !== undefined && hooks !== undefined) {
122
+ throw new Error('hexok: UseCase hooks are already set');
123
+ }
124
+ return bindFactory({
125
+ ...slots,
126
+ ...(hooks !== undefined ? { hooks } : {}),
127
+ });
128
+ },
129
+ });
130
+ return factory;
131
+ }
132
+ function buildContext(options) {
133
+ const guard = options?.guard;
134
+ const hooks = options?.hooks;
135
+ const factory = function family(token, ...args) {
136
+ // Conditional rest tuple: index 0 is the statics bag, or absent.
137
+ const statics = bagOf(args);
138
+ const spec = (statics ?? {});
139
+ class Runtime {
140
+ /** Use-case name. Literal type of the string passed to the factory. */
141
+ static token = token;
142
+ /** Nominal marker. Each factory call is a distinct class. */
143
+ #brand = true;
144
+ constructor() {
145
+ void this.#brand;
146
+ if (guard === undefined && hooks === undefined)
147
+ return;
148
+ installExecute(this, token, guard, hooks, spec);
149
+ }
150
+ }
151
+ if (statics !== undefined)
152
+ assignStatics(Runtime, statics);
153
+ return Runtime;
154
+ };
155
+ return factory;
156
+ }
157
+ function bagOf(args) {
158
+ const value = args[0];
159
+ if (typeof value !== 'object' || value === null)
160
+ return undefined;
161
+ return value;
162
+ }
163
+ function assignStatics(target, statics) {
164
+ const copy = {};
165
+ const record = statics;
166
+ for (const key of Object.keys(statics)) {
167
+ if (RESERVED_STATICS.has(key))
168
+ continue;
169
+ copy[key] = record[key];
170
+ }
171
+ Object.assign(target, copy);
172
+ }
173
+ function installExecute(instance, token, guard, hooks, spec) {
174
+ const prototype = Object.getPrototypeOf(instance);
175
+ // Own property would shadow this and recurse.
176
+ const execute = prototype.execute;
177
+ Object.defineProperty(instance, 'execute', {
178
+ configurable: true,
179
+ writable: true,
180
+ value: async (ctx, input) => {
181
+ const call = { ctx, spec, token, input };
182
+ if (guard !== undefined)
183
+ await guard(call);
184
+ // Local to this invocation. Overlapping execute calls must not share it.
185
+ let state;
186
+ let succeeded = false;
187
+ let result;
188
+ let caught;
189
+ let failed = false;
190
+ try {
191
+ if (hooks?.preExecute !== undefined) {
192
+ state = await hooks.preExecute(call);
193
+ }
194
+ result = await execute.call(instance, ctx, input);
195
+ if (hooks?.postExecute !== undefined) {
196
+ await hooks.postExecute({ ...call, result }, state);
197
+ }
198
+ succeeded = true;
199
+ return result;
200
+ }
201
+ catch (error) {
202
+ failed = true;
203
+ caught = error;
204
+ if (hooks?.onCatch !== undefined) {
205
+ await hooks.onCatch({ ...call, error }, state);
206
+ }
207
+ throw error;
208
+ }
209
+ finally {
210
+ if (hooks?.onFinally !== undefined) {
211
+ const settled = succeeded
212
+ ? { ...call, status: 'success', result }
213
+ : { ...call, status: 'failure', error: caught };
214
+ if (failed || succeeded)
215
+ await hooks.onFinally(settled, state);
216
+ }
217
+ }
218
+ },
219
+ });
220
+ }
221
+ //# sourceMappingURL=use-case.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-case.js","sourceRoot":"","sources":["../src/use-case.ts"],"names":[],"mappings":"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"}
@@ -16,4 +16,4 @@ export type Infer<S extends StandardSchemaV1> = NonNullable<S['~standard']['type
16
16
  * ```
17
17
  */
18
18
  export declare function validate<S extends StandardSchemaV1>(schema: S, value: unknown): Result<Infer<S>, 'VALIDATION'>;
19
- //# sourceMappingURL=schema.d.ts.map
19
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D,kDAAkD;AAClD,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,gBAAgB,IAAI,WAAW,CACzD,CAAC,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CACxB,CAAC,QAAQ,CAAC,CAAC;AAEZ;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,gBAAgB,EACjD,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,OAAO,GACb,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,CAAC,CAShC"}
@@ -25,4 +25,4 @@ export function validate(schema, value) {
25
25
  function isPromise(value) {
26
26
  return typeof value.then === 'function';
27
27
  }
28
- //# sourceMappingURL=schema.js.map
28
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,EAAE,EAAe,MAAM,aAAa,CAAC;AAQpD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,QAAQ,CACtB,MAAS,EACT,KAAc;IAEd,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACnD,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,EAAE,CAAC,MAAM,CAAC,KAAiB,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,SAAS,CAChB,KAE6C;IAE7C,OAAO,OAAQ,KAA0B,CAAC,IAAI,KAAK,UAAU,CAAC;AAChE,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hexok",
3
- "version": "1.0.1",
4
- "description": "A TypeScript kit for writing a clean-architecture backend as ordinary classes.",
3
+ "version": "2.1.0",
4
+ "description": "TypeScript primitives for hexagonal software: Schema, Entity, UseCase, Port, Adapter, Mapper, Event, EventCatalog, EventHandler, and Errors.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "sideEffects": false,
@@ -28,34 +28,15 @@
28
28
  "access": "public"
29
29
  },
30
30
  "exports": {
31
- "./core": {
32
- "types": "./dist/core/index.d.ts",
33
- "import": "./dist/core/index.js"
34
- },
35
- "./domain": {
36
- "types": "./dist/domain/index.d.ts",
37
- "import": "./dist/domain/index.js"
38
- },
39
- "./app": {
40
- "types": "./dist/app/index.d.ts",
41
- "import": "./dist/app/index.js"
42
- },
43
- "./infra": {
44
- "types": "./dist/infra/index.d.ts",
45
- "import": "./dist/infra/index.js"
46
- },
47
- "./runtime": {
48
- "types": "./dist/runtime/index.d.ts",
49
- "import": "./dist/runtime/index.js"
50
- },
51
- "./testing": {
52
- "types": "./dist/testing/index.d.ts",
53
- "import": "./dist/testing/index.js"
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
54
34
  },
55
35
  "./package.json": "./package.json"
56
36
  },
57
37
  "files": [
58
- "dist"
38
+ "dist",
39
+ "skills"
59
40
  ],
60
41
  "scripts": {
61
42
  "build": "tsc -b",
@@ -0,0 +1,127 @@
1
+ ---
2
+ name: hexok
3
+ description: >
4
+ Build application features with the hexok primitives. Use when the task adds
5
+ or changes a user story, entity, schema, use case, port, adapter, event,
6
+ event handler, gateway, or background worker in a project that depends on
7
+ hexok. Follow its thought, research, and plan steps, and read the primitive
8
+ reference it names before writing that primitive.
9
+ license: MIT
10
+ metadata:
11
+ short-description: Build features with hexok
12
+ ---
13
+
14
+ # Hexok
15
+
16
+ Import primitives from `hexok`. The application owns HTTP, RPC, websockets, gRPC, the object graph, brokers, ack, retry, and consumer groups.
17
+
18
+ The running shape is a person who creates a blog post and publishes it. Subscribers are emailed afterward. Use the application's real story. The blog is only the shape.
19
+
20
+ ## Concepts
21
+
22
+ Read the reference before writing that concept. Each file is the supporting documentation for one primitive.
23
+
24
+ | Concept | Reference | Place |
25
+ | --- | --- | --- |
26
+ | Schema | [references/schema.md](references/schema.md) | Domain. The shape of stored data and event payloads. |
27
+ | Entity | [references/entity.md](references/entity.md) | Domain. One stored thing and its rules. |
28
+ | Error | [references/error.md](references/error.md) | Domain. Named refusals, the error map, and `CodedError` `VALIDATION`. |
29
+ | Event | [references/event.md](references/event.md) | Domain. A fact the use case records. |
30
+ | EventCatalog | [references/event-catalog.md](references/event-catalog.md) | Domain. The set of events, and the conversion to a wire message. |
31
+ | UseCase | [references/use-case.md](references/use-case.md) | Application. One caller action. `UseCase.context` is in that file. |
32
+ | Port | [references/port.md](references/port.md) | Application. The contract a use case or handler calls. |
33
+ | EventHandler | [references/event-handler.md](references/event-handler.md) | Application. Work that follows one event. |
34
+ | Adapter | [references/adapter.md](references/adapter.md) | Interface adapter. The current implementation of one port. |
35
+ | Mapper | [references/mapper.md](references/mapper.md) | Interface adapter. Read it when the stored document is not the domain props. |
36
+
37
+ Dependencies point inward. Domain code does not import a database client, a queue, an HTTP library, or a mail SDK. Use cases and handlers depend on ports. Adapters implement ports. A gateway calls use cases.
38
+
39
+ ## How to build a feature
40
+
41
+ Walk the steps below three times, in this order, before you edit files.
42
+
43
+ 1. **Thought.** State one user story: who acts, and what they can do. Example: as a user I can create a blog post and publish it. Name the actions a caller can invoke on their own. Creating a draft and publishing it are two use cases when both are reachable. One use case is enough when the caller can only publish.
44
+ 2. **Research.** Search the codebase for that story. Note the entity, schema, error catalog, use case, ports, events, handlers, adapters, gateway route, and worker entry that already exist.
45
+ 3. **Plan.** Write a short plan in the step order below. Mark each item `exists` or `add`. A step with nothing to add is `none`. Then implement in that same order.
46
+
47
+ When the task is only a plan, stop after the plan. When the task is to build the feature, implement the plan after it is written. Read the matching reference at the start of each step you implement.
48
+
49
+ ### 1. User story
50
+
51
+ Write the story in one sentence. That sentence is the feature. Later files, tokens, and routes should be recognizable as parts of it.
52
+
53
+ ### 2. Entities and schemas
54
+
55
+ Identify what the story persists. Define the schema, then the entity, when they are not already there.
56
+
57
+ A blog post is an id, a title, a body, and whether it is published. The schema is that shape. The entity holds the rules, such as refusing a blank title or a second publish. Read [references/schema.md](references/schema.md) and [references/entity.md](references/entity.md). Add catalog members used by those rules while you are here. Read [references/error.md](references/error.md).
58
+
59
+ ### 3. Use cases and ports
60
+
61
+ Define each use case as one action, and the ports that action calls, when they are not already there.
62
+
63
+ Saving a post is a port. Publishing an event is a different port. The use case receives both in its constructor and calls them from `execute`. It does not import a driver. Read [references/use-case.md](references/use-case.md) and [references/port.md](references/port.md).
64
+
65
+ Use `UseCase(token)` when `execute` only needs the input. Use `UseCase.context<Ctx>()` when every call in the family carries the same extra facts, such as a trace id, the actor, or the caller IP. Share one family across the gateway's use cases.
66
+
67
+ ### 3.b. Side effects
68
+
69
+ Identify work that is a consequence of the story and is outside the use case's job. Emailing subscribers after a post is published is that kind of work. Indexing and heavy computation are too.
70
+
71
+ When that work exists and the event does not, define the event and register it on a catalog. Give the use case an event-publisher port and publish the event instance at the end of `execute`, after the entity rule has run and the save has succeeded. Read [references/event.md](references/event.md) and [references/event-catalog.md](references/event-catalog.md).
72
+
73
+ When the story has no consequence beyond its own write, this step is `none`. Do not add an event so the step looks complete.
74
+
75
+ ### 3.c. Event handlers
76
+
77
+ When step 3.b named a side effect, implement the handler if it does not exist. The handler takes its own ports in the constructor. `handle` receives the event. Emailing subscribers loads addresses through a subscriber port and sends mail through a mail port. Read [references/event-handler.md](references/event-handler.md).
78
+
79
+ The handler does not subscribe itself. Subscription belongs to the adapter in step 4.
80
+
81
+ ### 4. Adapters
82
+
83
+ List every port a use case or handler calls, including repositories, the event publisher, the event subscription, and outbound ports such as email. Implement each missing port as an adapter.
84
+
85
+ An adapter is the concrete port: memory while sketching, a database in the API, a fake in a test, a broker when a worker runs elsewhere. Swapping it changes the composition root. `execute` stays put. Read [references/adapter.md](references/adapter.md).
86
+
87
+ Read [references/mapper.md](references/mapper.md) when the stored document is a different shape from the domain props. Persisting `toProps()` as JSON needs no mapper.
88
+
89
+ The publisher adapter accepts `EventInstance<typeof Catalog>`. It sends `Catalog.message(event)` when the transport stores bytes. The subscription adapter runs `Catalog.parse` and then `handle`. Ack, retry, prefetch, and consumer groups stay on that adapter.
90
+
91
+ ### 5. Gateway
92
+
93
+ Identify how a caller outside the process reaches the use case. The common surface is HTTP in an RPC shape: one path per use case, such as `POST /posts` and `POST /posts/:id/publish`. REST, websockets, and gRPC are the same kind of edge when the story needs them.
94
+
95
+ Use the HTTP library the application already depends on. Express, Hono, and Koa are all suitable. Add one only when the application has none.
96
+
97
+ The route builds the call context, calls `execute`, and maps failures to the reply. A missing entity is a 404. `CodedError` code `VALIDATION` is a 400 and `data.issues` names the fields. A catalog conflict, such as an already published post, is a 409. The catalog does not contain status codes.
98
+
99
+ Construct adapters, call `start`, and construct use cases in the entry point. A use case instance is long-lived. Context is an argument to `execute`, not a field set per request.
100
+
101
+ ### 6. Where handlers run
102
+
103
+ Skip this step when there is no handler.
104
+
105
+ Decide whether each handler shares the API process. Share it when the work is small, or when no adapter can publish in one process and subscribe in another.
106
+
107
+ Add a background-worker entry point when the handler does heavy work and a distributed publish/subscribe adapter exists. The API entry publishes. The worker entry starts the subscription adapter, registers the handlers, and runs them. The two processes use the same catalog. The worker parses the wire message, then calls `handle`.
108
+
109
+ Do not add a broker only to create a second process. Do not move the handler's work back into `execute`.
110
+
111
+ ## Conventions
112
+
113
+ - Tokens are factory arguments: `Entity('Post', PostSchema)`, `UseCase('post.publish')`, `Port('PostRepository')`, `Event('post.published', PostSchema)`, `EventCatalog('blog', { postPublished })`, `EventHandler('email.subscribers', PostPublished)`, `Errors('blog', { ... })`. A subclass does not redeclare the token.
114
+ - `execute`, `handle`, port methods, and a mapper's `fromSource` and `toSource` are methods on the class. The compiler reports a missing one. `start`, `stop`, `parse`, and `set` already exist and may be overridden.
115
+ - A use case throws a catalog member when the story refuses. Schema checks throw `CodedError` with code `VALIDATION`. Catch both at the gateway or the worker.
116
+ - Call catalog factories without `new`: `BlogError.BlankTitle()`.
117
+ - Check use-case input in a guard or in the first lines of `execute`. A schema stored on a `UseCase.context` static bag is application data. Hexok does not run it.
118
+ - Annotate `execute` on the subclass. Callers see that annotation. On a context family, the parameters are `(ctx, input)` and `ctx` is `UseCase.Ctx<typeof Factory>`.
119
+ - Publish `new PostPublished(payload)`. Compare events with `instanceof` or `event.token`. The catalog map key (`postPublished`) and the event token (`post.published`) are different strings.
120
+ - After a successful driver write, the adapter calls `commit()` on the entity. The mapper does not commit.
121
+ - Tests construct the use case with adapters of the same ports. A test of the story does not need a live broker.
122
+ - Put one concept in one file. A practical layout is `schemas/`, `entities/`, `errors/`, `events/`, `ports/`, `use-cases/`, `handlers/`, `adapters/`, an HTTP entry, and a worker entry when step 6 splits a process.
123
+ - Zod, Valibot, and ArkType are passed straight to the primitives. A TypeBox 1 schematic goes through `@hexok/typebox` (`typebox(schema)` or `typeboxDecode(schema)`) before it reaches `Schema`, `Entity`, or `Event`.
124
+
125
+ ## When a primitive changes
126
+
127
+ A change to primitive behavior updates the matching file under `references/` in the same change. `npx skills update` refreshes installed copies from the GitHub repository.