@fougere/core 0.17.4-alpha.0 → 0.19.0-alpha.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 (256) hide show
  1. package/README.md +1 -1
  2. package/dist/BootRefusal.d.ts +9 -0
  3. package/dist/BootRefusal.d.ts.map +1 -0
  4. package/dist/BootRefusal.js +12 -0
  5. package/dist/BootRefusal.js.map +1 -0
  6. package/dist/CallIdentity.d.ts.map +1 -1
  7. package/dist/CallIdentity.js +2 -1
  8. package/dist/CallIdentity.js.map +1 -1
  9. package/dist/Declared.d.ts +2 -5
  10. package/dist/Declared.d.ts.map +1 -1
  11. package/dist/DeclaredHandler.d.ts +3 -8
  12. package/dist/DeclaredHandler.d.ts.map +1 -1
  13. package/dist/DeclaredSubject.d.ts +2 -5
  14. package/dist/DeclaredSubject.d.ts.map +1 -1
  15. package/dist/EffectiveOperation.d.ts +7 -6
  16. package/dist/EffectiveOperation.d.ts.map +1 -1
  17. package/dist/EffectiveOperationModel.d.ts.map +1 -1
  18. package/dist/EffectiveOperationModel.js +9 -6
  19. package/dist/EffectiveOperationModel.js.map +1 -1
  20. package/dist/FrondConfig.d.ts +0 -2
  21. package/dist/FrondConfig.d.ts.map +1 -1
  22. package/dist/FrondConfig.js.map +1 -1
  23. package/dist/FrondDeclaration.d.ts +3 -8
  24. package/dist/FrondDeclaration.d.ts.map +1 -1
  25. package/dist/FrondDeclaration.js +2 -1
  26. package/dist/FrondDeclaration.js.map +1 -1
  27. package/dist/OperationOverride.d.ts +5 -3
  28. package/dist/OperationOverride.d.ts.map +1 -1
  29. package/dist/SourceView.d.ts +10 -8
  30. package/dist/SourceView.d.ts.map +1 -1
  31. package/dist/SourceView.js +10 -1
  32. package/dist/SourceView.js.map +1 -1
  33. package/dist/boot/AppLifecycle.d.ts.map +1 -1
  34. package/dist/boot/AppLifecycle.js +2 -1
  35. package/dist/boot/AppLifecycle.js.map +1 -1
  36. package/dist/boot/Emissions.d.ts +8 -1
  37. package/dist/boot/Emissions.d.ts.map +1 -1
  38. package/dist/boot/Emissions.js +16 -11
  39. package/dist/boot/Emissions.js.map +1 -1
  40. package/dist/boot/bootstrap.d.ts.map +1 -1
  41. package/dist/boot/bootstrap.js +27 -15
  42. package/dist/boot/bootstrap.js.map +1 -1
  43. package/dist/boot/card.d.ts.map +1 -1
  44. package/dist/boot/card.js.map +1 -1
  45. package/dist/boot/install.d.ts.map +1 -1
  46. package/dist/boot/install.js +1 -0
  47. package/dist/boot/install.js.map +1 -1
  48. package/dist/boot/ownership.d.ts.map +1 -1
  49. package/dist/boot/ownership.js +26 -14
  50. package/dist/boot/ownership.js.map +1 -1
  51. package/dist/boot/unregistered.d.ts +4 -2
  52. package/dist/boot/unregistered.d.ts.map +1 -1
  53. package/dist/boot/unregistered.js +30 -30
  54. package/dist/boot/unregistered.js.map +1 -1
  55. package/dist/contract.d.ts +3 -0
  56. package/dist/contract.d.ts.map +1 -1
  57. package/dist/contract.js +3 -0
  58. package/dist/contract.js.map +1 -1
  59. package/dist/descriptor/CollectorEntry.d.ts +2 -7
  60. package/dist/descriptor/CollectorEntry.d.ts.map +1 -1
  61. package/dist/descriptor/FrondDescriptor.d.ts +5 -23
  62. package/dist/descriptor/FrondDescriptor.d.ts.map +1 -1
  63. package/dist/descriptor/FrondDescriptor.js.map +1 -1
  64. package/dist/descriptor/HandlerEntry.d.ts +2 -7
  65. package/dist/descriptor/HandlerEntry.d.ts.map +1 -1
  66. package/dist/descriptor/MiddlewareEntry.d.ts +2 -7
  67. package/dist/descriptor/MiddlewareEntry.d.ts.map +1 -1
  68. package/dist/descriptor/PresenterEntry.d.ts +2 -7
  69. package/dist/descriptor/PresenterEntry.d.ts.map +1 -1
  70. package/dist/descriptor/ProviderEntry.d.ts +2 -7
  71. package/dist/descriptor/ProviderEntry.d.ts.map +1 -1
  72. package/dist/descriptor/Subject.d.ts +10 -0
  73. package/dist/descriptor/Subject.d.ts.map +1 -0
  74. package/dist/descriptor/Subject.js +2 -0
  75. package/dist/descriptor/Subject.js.map +1 -0
  76. package/dist/descriptor/index.d.ts +1 -0
  77. package/dist/descriptor/index.d.ts.map +1 -1
  78. package/dist/descriptor/index.js +1 -0
  79. package/dist/descriptor/index.js.map +1 -1
  80. package/dist/diagnostic.d.ts +2 -1
  81. package/dist/diagnostic.d.ts.map +1 -1
  82. package/dist/diagnostic.js +2 -1
  83. package/dist/diagnostic.js.map +1 -1
  84. package/dist/dispatch/ArgumentResolver.d.ts +13 -1
  85. package/dist/dispatch/ArgumentResolver.d.ts.map +1 -1
  86. package/dist/dispatch/ArgumentResolver.js +48 -2
  87. package/dist/dispatch/ArgumentResolver.js.map +1 -1
  88. package/dist/dispatch/EntityLookup.d.ts +4 -0
  89. package/dist/dispatch/EntityLookup.d.ts.map +1 -0
  90. package/dist/dispatch/EntityLookup.js +2 -0
  91. package/dist/dispatch/EntityLookup.js.map +1 -0
  92. package/dist/dispatch/Facade.d.ts +3 -0
  93. package/dist/dispatch/Facade.d.ts.map +1 -1
  94. package/dist/dispatch/HandlerFacade.d.ts +2 -0
  95. package/dist/dispatch/HandlerFacade.d.ts.map +1 -1
  96. package/dist/dispatch/HandlerFacade.js +14 -4
  97. package/dist/dispatch/HandlerFacade.js.map +1 -1
  98. package/dist/entry/facade.d.ts +10 -1
  99. package/dist/entry/facade.d.ts.map +1 -1
  100. package/dist/entry/facade.js +26 -2
  101. package/dist/entry/facade.js.map +1 -1
  102. package/dist/index.d.ts +5 -1
  103. package/dist/index.d.ts.map +1 -1
  104. package/dist/index.js +4 -0
  105. package/dist/index.js.map +1 -1
  106. package/dist/prefab/AggregateConstructor.d.ts +2 -2
  107. package/dist/prefab/AggregateConstructor.d.ts.map +1 -1
  108. package/dist/prefab/AggregateOf.d.ts +2 -2
  109. package/dist/prefab/AggregateOf.d.ts.map +1 -1
  110. package/dist/prefab/AggregateShape.d.ts +2 -2
  111. package/dist/prefab/AggregateShape.d.ts.map +1 -1
  112. package/dist/prefab/CrudConstructor.d.ts +2 -2
  113. package/dist/prefab/CrudConstructor.d.ts.map +1 -1
  114. package/dist/prefab/CrudConstructor.js +13 -10
  115. package/dist/prefab/CrudConstructor.js.map +1 -1
  116. package/dist/prefab/CrudOps.d.ts +2 -2
  117. package/dist/prefab/CrudOps.d.ts.map +1 -1
  118. package/dist/prefab/CrudViews.d.ts +2 -2
  119. package/dist/prefab/CrudViews.d.ts.map +1 -1
  120. package/dist/prefab/Middleware.d.ts +13 -0
  121. package/dist/prefab/Middleware.d.ts.map +1 -0
  122. package/dist/prefab/Middleware.js +10 -0
  123. package/dist/prefab/Middleware.js.map +1 -0
  124. package/dist/prefab/MirrorConstructor.d.ts +2 -2
  125. package/dist/prefab/MirrorConstructor.d.ts.map +1 -1
  126. package/dist/prefab/MirrorConstructor.js.map +1 -1
  127. package/dist/prefab/RepositoryConstructor.d.ts +4 -4
  128. package/dist/prefab/RepositoryConstructor.d.ts.map +1 -1
  129. package/dist/prefab/RepositoryConstructor.js.map +1 -1
  130. package/dist/prefab/collector.d.ts +2 -1
  131. package/dist/prefab/collector.d.ts.map +1 -1
  132. package/dist/prefab/collector.js.map +1 -1
  133. package/dist/prefab/opsOf.d.ts +4 -0
  134. package/dist/prefab/opsOf.d.ts.map +1 -0
  135. package/dist/prefab/opsOf.js +5 -0
  136. package/dist/prefab/opsOf.js.map +1 -0
  137. package/dist/prefab/prefab.d.ts +3 -3
  138. package/dist/prefab/prefab.d.ts.map +1 -1
  139. package/dist/prefab/presenter.d.ts +3 -3
  140. package/dist/prefab/presenter.d.ts.map +1 -1
  141. package/dist/prefab/presenter.js.map +1 -1
  142. package/dist/wire/Addresses.d.ts +4 -4
  143. package/dist/wire/Addresses.d.ts.map +1 -1
  144. package/dist/wire/Cardinality.d.ts +4 -0
  145. package/dist/wire/Cardinality.d.ts.map +1 -0
  146. package/dist/wire/Cardinality.js +3 -0
  147. package/dist/wire/Cardinality.js.map +1 -0
  148. package/dist/wire/Facade.d.ts +5 -7
  149. package/dist/wire/Facade.d.ts.map +1 -1
  150. package/dist/wire/Facade.js.map +1 -1
  151. package/dist/wire/Invocation.d.ts +2 -1
  152. package/dist/wire/Invocation.d.ts.map +1 -1
  153. package/dist/wire/Invocation.js +4 -1
  154. package/dist/wire/Invocation.js.map +1 -1
  155. package/dist/wire/InvocationContext.d.ts +6 -0
  156. package/dist/wire/InvocationContext.d.ts.map +1 -1
  157. package/dist/wire/OperationContract.d.ts +2 -1
  158. package/dist/wire/OperationContract.d.ts.map +1 -1
  159. package/dist/wire/OperationContract.js +2 -2
  160. package/dist/wire/OperationContract.js.map +1 -1
  161. package/dist/wire/OperationKind.d.ts +3 -1
  162. package/dist/wire/OperationKind.d.ts.map +1 -1
  163. package/dist/wire/OperationKind.js +2 -1
  164. package/dist/wire/OperationKind.js.map +1 -1
  165. package/dist/wire/Placement.d.ts +4 -0
  166. package/dist/wire/Placement.d.ts.map +1 -0
  167. package/dist/wire/Placement.js +3 -0
  168. package/dist/wire/Placement.js.map +1 -0
  169. package/dist/wire/RouteKind.d.ts +2 -1
  170. package/dist/wire/RouteKind.d.ts.map +1 -1
  171. package/dist/wire/SignedCall.d.ts +2 -0
  172. package/dist/wire/SignedCall.d.ts.map +1 -1
  173. package/dist/wire/StateShape.d.ts +1 -2
  174. package/dist/wire/StateShape.d.ts.map +1 -1
  175. package/dist/wire/StateShape.js +11 -11
  176. package/dist/wire/StateShape.js.map +1 -1
  177. package/dist/wire/TypeRef.d.ts +7 -0
  178. package/dist/wire/TypeRef.d.ts.map +1 -1
  179. package/dist/wire/binding.d.ts +11 -1
  180. package/dist/wire/binding.d.ts.map +1 -1
  181. package/dist/wire/binding.js +4 -2
  182. package/dist/wire/binding.js.map +1 -1
  183. package/dist/wire/card/CardOp.d.ts +4 -10
  184. package/dist/wire/card/CardOp.d.ts.map +1 -1
  185. package/dist/wire/refusals.d.ts +2 -1
  186. package/dist/wire/refusals.d.ts.map +1 -1
  187. package/dist/wire/refusals.js.map +1 -1
  188. package/dist/wire/topology/DeclaredFrond.d.ts +2 -1
  189. package/dist/wire/topology/DeclaredFrond.d.ts.map +1 -1
  190. package/dist/wire/topology/FrondPlacement.d.ts +2 -1
  191. package/dist/wire/topology/FrondPlacement.d.ts.map +1 -1
  192. package/package.json +3 -3
  193. package/src/BootRefusal.ts +11 -0
  194. package/src/CallIdentity.ts +2 -1
  195. package/src/Declared.ts +3 -7
  196. package/src/DeclaredHandler.ts +3 -8
  197. package/src/DeclaredSubject.ts +3 -5
  198. package/src/EffectiveOperation.ts +7 -6
  199. package/src/EffectiveOperationModel.ts +11 -7
  200. package/src/FrondConfig.ts +0 -2
  201. package/src/FrondDeclaration.ts +5 -8
  202. package/src/OperationOverride.ts +5 -3
  203. package/src/SourceView.ts +21 -3
  204. package/src/boot/AppLifecycle.ts +2 -1
  205. package/src/boot/Emissions.ts +18 -11
  206. package/src/boot/bootstrap.ts +32 -16
  207. package/src/boot/card.ts +1 -2
  208. package/src/boot/install.ts +1 -0
  209. package/src/boot/ownership.ts +35 -16
  210. package/src/boot/unregistered.ts +31 -33
  211. package/src/contract.ts +3 -0
  212. package/src/descriptor/CollectorEntry.ts +2 -7
  213. package/src/descriptor/FrondDescriptor.ts +5 -19
  214. package/src/descriptor/HandlerEntry.ts +2 -7
  215. package/src/descriptor/MiddlewareEntry.ts +2 -7
  216. package/src/descriptor/PresenterEntry.ts +2 -7
  217. package/src/descriptor/ProviderEntry.ts +2 -7
  218. package/src/descriptor/Subject.ts +10 -0
  219. package/src/descriptor/index.ts +1 -0
  220. package/src/diagnostic.ts +4 -2
  221. package/src/dispatch/ArgumentResolver.ts +54 -2
  222. package/src/dispatch/EntityLookup.ts +4 -0
  223. package/src/dispatch/Facade.ts +3 -0
  224. package/src/dispatch/HandlerFacade.ts +14 -6
  225. package/src/entry/facade.ts +38 -2
  226. package/src/index.ts +5 -1
  227. package/src/prefab/AggregateConstructor.ts +2 -2
  228. package/src/prefab/AggregateOf.ts +2 -2
  229. package/src/prefab/AggregateShape.ts +2 -2
  230. package/src/prefab/CrudConstructor.ts +18 -13
  231. package/src/prefab/CrudOps.ts +3 -3
  232. package/src/prefab/CrudViews.ts +2 -2
  233. package/src/prefab/Middleware.ts +13 -0
  234. package/src/prefab/MirrorConstructor.ts +2 -2
  235. package/src/prefab/RepositoryConstructor.ts +9 -9
  236. package/src/prefab/collector.ts +4 -3
  237. package/src/prefab/opsOf.ts +6 -0
  238. package/src/prefab/prefab.ts +5 -5
  239. package/src/prefab/presenter.ts +3 -3
  240. package/src/wire/Addresses.ts +4 -4
  241. package/src/wire/Cardinality.ts +4 -0
  242. package/src/wire/Facade.ts +6 -9
  243. package/src/wire/Invocation.ts +3 -1
  244. package/src/wire/InvocationContext.ts +6 -0
  245. package/src/wire/OperationContract.ts +4 -3
  246. package/src/wire/OperationKind.ts +4 -1
  247. package/src/wire/Placement.ts +4 -0
  248. package/src/wire/RouteKind.ts +3 -1
  249. package/src/wire/SignedCall.ts +2 -0
  250. package/src/wire/StateShape.ts +9 -11
  251. package/src/wire/TypeRef.ts +4 -0
  252. package/src/wire/binding.ts +10 -2
  253. package/src/wire/card/CardOp.ts +4 -10
  254. package/src/wire/refusals.ts +2 -1
  255. package/src/wire/topology/DeclaredFrond.ts +3 -1
  256. package/src/wire/topology/FrondPlacement.ts +3 -1
@@ -5,6 +5,7 @@ import type { HandlerEntry } from '../descriptor/HandlerEntry.js';
5
5
  import type { PresenterEntry } from '../descriptor/PresenterEntry.js';
6
6
  import type { StateShape } from '../wire/StateShape.js';
7
7
  import type { OwnLogger } from '../builtin/OwnLoggers.js';
8
+ import type { EntityLookup } from './EntityLookup.js';
8
9
 
9
10
  /** What boot resolved around one handler, beyond the handler and the scope it resolves in. */
10
11
  export interface Facade {
@@ -16,6 +17,8 @@ export interface Facade {
16
17
  handlers: readonly HandlerEntry[];
17
18
  /** The canonical operation table resolved before boot performs any side effect. */
18
19
  operations: EffectiveOperationsMap;
20
+ /** Where a parameter typed `Post['title']` finds the rules of `title`. */
21
+ entities: EntityLookup;
19
22
  /** Entity names this frond has a collector for. */
20
23
  collectors: Set<string>;
21
24
  /** The presenter over this handler's entity, when the frond declares one. */
@@ -35,9 +35,7 @@ export class HandlerFacade {
35
35
  private instance: any;
36
36
  private readonly implementationKeys = new Map<string, string>();
37
37
  private readonly implementationInstances = new Map<string, any>();
38
- private readonly arguments = new ArgumentResolver(
39
- (typeName) => this.collectorResolver(typeName),
40
- );
38
+ private readonly arguments: ArgumentResolver;
41
39
  /** A computed field's parameters and the collectors in scope are both boot-time facts. */
42
40
  private readonly presenterPlans: Map<string, BindingPlan>;
43
41
 
@@ -52,6 +50,7 @@ export class HandlerFacade {
52
50
  private readonly facade: Facade,
53
51
  ) {
54
52
  this.refuseCrudWithoutRepository(handler);
53
+ this.arguments = new ArgumentResolver((typeName) => this.collectorResolver(typeName), facade.entities);
55
54
 
56
55
  this.presenterPlans = facade.presenter
57
56
  ? presenterPlans(facade.presenter, facade.collectors)
@@ -124,22 +123,31 @@ export class HandlerFacade {
124
123
  entered: Record<string, unknown>,
125
124
  ): Promise<unknown> {
126
125
  const state = this.facade.state.judge(context.state, this.handler.address, op, entered);
127
- const validated = validateInput(contract.input, { ...invocation, state });
126
+ const written = invocation.args !== undefined;
127
+ const validated = written ? { ...invocation, state } : validateInput(contract.input, { ...invocation, state });
128
128
  context.invocation = validated;
129
129
 
130
- const args = contract.binding ? await this.arguments.resolve(contract.binding, validated) : [];
130
+ const args = await this.argumentsOf(contract, validated);
131
131
  const { instance, method } = this.resolveImplementation(op);
132
132
  const answered = await instance[method](...args);
133
133
 
134
134
  const view = this.viewOf(op);
135
135
  const { presenter } = this.facade;
136
- const computed = view.closed || !presenter
136
+ const computed = written || view.closed || !presenter
137
137
  ? answered
138
138
  : await this.present(op, presenter, answered, validated);
139
139
 
140
140
  return view.project(computed);
141
141
  }
142
142
 
143
+ /** What code wrote, judged; or what a door received, read through the binding plan. */
144
+ private async argumentsOf(contract: OperationContract, invocation: InvocationContext): Promise<unknown[]> {
145
+ if (!contract.binding) return [];
146
+ if (invocation.args !== undefined) return this.arguments.given(contract.binding, contract.input, invocation);
147
+
148
+ return this.arguments.resolve(contract.binding, invocation);
149
+ }
150
+
143
151
  /**
144
152
  * The computed fields a presenter adds, over the rows the handler answered and BEFORE the
145
153
  * view projects them. A presenter runs on the server, so what it reads is the row the
@@ -3,6 +3,7 @@ import { Call } from '../wire/Call.js';
3
3
  import { RouteAddress } from '../wire/RouteAddress.js';
4
4
  import type { DispatchPort } from '../dispatch/DispatchPort.js';
5
5
  import type { Received } from '../dispatch/Received.js';
6
+ import type { PartialInvocation } from '../wire/PartialInvocation.js';
6
7
 
7
8
  type Operation = (...args: any[]) => unknown;
8
9
 
@@ -24,11 +25,14 @@ export function dynamicOperations(operation: (name: string) => Operation): Recor
24
25
  }
25
26
 
26
27
  /**
27
- * Turns facade method calls into canonical dispatches.
28
+ * The facade code calls: each operation takes the handler's own arguments.
28
29
  *
29
30
  * `received` is what this side puts back before handing the answer over: a row leaves as data
30
31
  * and `date-time` means a `Date` on both sides. A facade built without one hands over what the
31
32
  * wire carried, which is what a caller holding no schema can do.
33
+ *
34
+ * The trailing `undefined`s are dropped because a JSON array writes them `null`, and the far
35
+ * side would read a value where the caller left one out. An optional parameter is always last.
32
36
  */
33
37
  export function facadeOperations(
34
38
  dispatcher: DispatchPort,
@@ -37,7 +41,30 @@ export function facadeOperations(
37
41
  surface?: string,
38
42
  received?: Received,
39
43
  ): Record<string, Operation> {
40
- const operation = (name: string): Operation => async (invocation) => {
44
+ const send = sending(dispatcher, address, surface, received);
45
+
46
+ return operationsOf((name) => (...args: unknown[]) => send(name, { args: withoutTrailingAbsence(args) }), operationNames);
47
+ }
48
+
49
+ /**
50
+ * What a door sends: the invocation it received, as a `Call`. A door holds a request, not the
51
+ * handler's arguments, so the binding plan reads it on the far side — collectors and presenter
52
+ * included.
53
+ */
54
+ export function callOperations(
55
+ dispatcher: DispatchPort,
56
+ address: string,
57
+ operationNames?: Iterable<string>,
58
+ surface?: string,
59
+ received?: Received,
60
+ ): Record<string, Operation> {
61
+ const send = sending(dispatcher, address, surface, received);
62
+
63
+ return operationsOf((name) => (invocation?: PartialInvocation) => send(name, invocation), operationNames);
64
+ }
65
+
66
+ function sending(dispatcher: DispatchPort, address: string, surface?: string, received?: Received) {
67
+ return async (name: string, invocation?: PartialInvocation): Promise<unknown> => {
41
68
  const answer = await dispatcher.dispatch(new Call(
42
69
  new RouteAddress({
43
70
  address,
@@ -49,8 +76,17 @@ export function facadeOperations(
49
76
 
50
77
  return received ? received(name, answer) : answer;
51
78
  };
79
+ }
52
80
 
81
+ function operationsOf(operation: (name: string) => Operation, operationNames?: Iterable<string>): Record<string, Operation> {
53
82
  return operationNames
54
83
  ? Object.fromEntries([...operationNames].map((name) => [name, operation(name)]))
55
84
  : dynamicOperations(operation);
56
85
  }
86
+
87
+ function withoutTrailingAbsence(args: unknown[]): unknown[] {
88
+ let length = args.length;
89
+ while (length > 0 && args[length - 1] === undefined) length--;
90
+
91
+ return args.slice(0, length);
92
+ }
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ export type { CrudConstructor } from './prefab/CrudConstructor.js';
23
23
  export type { CrudOpName } from './prefab/CrudOpName.js';
24
24
  export type { CrudOps } from './prefab/CrudOps.js';
25
25
  export type { CrudViews } from './prefab/CrudViews.js';
26
+ export { Middleware } from './prefab/Middleware.js';
26
27
  export { Mirror } from './prefab/MirrorConstructor.js';
27
28
  export type { MirrorConstructor } from './prefab/MirrorConstructor.js';
28
29
  export type { MirrorOf } from './prefab/MirrorOf.js';
@@ -63,6 +64,7 @@ export type { AppMiddleware } from './wire/AppMiddleware.js';
63
64
  export type { AppNext } from './wire/AppNext.js';
64
65
  export type { OperationContext } from './wire/OperationContext.js';
65
66
  export { assertIdentityCard, RPC_ADDRESS, ENVELOPE_BYTES, maxBodyBytes, maxFrameBytes, setMaxBodyBytes } from './contract.js';
67
+ export { OPERATION_KINDS, CARDINALITIES, PLACEMENTS, type OperationKind, type Cardinality, type Placement } from './contract.js';
66
68
  export { createLocalRunner, createAppRunner } from './boot/runner.js';
67
69
 
68
70
  // A frond an app STATES, for a boot that will not scan. No disk, no `typescript` — which
@@ -81,6 +83,7 @@ export { declaredTopologyOf } from './boot/declared.js';
81
83
  // Both halves of what a call can refuse, put together where they are READ — the framework's
82
84
  // follows from `kind` and `input`, so only the frond's travels.
83
85
  export { refusalsOf, type Refusable } from './wire/refusals.js';
86
+ export type { Binding } from './Binding.js';
84
87
  // The facades as TYPES — empty here, filled by the `.d.ts` the scan writes beside the app.
85
88
  export type { Addresses } from './wire/Addresses.js';
86
89
  export type { Answer } from './wire/Answer.js';
@@ -129,7 +132,7 @@ export { Storage, togetherKeyOf } from './storage/Storage.js';
129
132
  export type { Constraint } from './Constraint.js';
130
133
  export type { Source } from './Source.js';
131
134
  export type { SourceConfig } from './SourceConfig.js';
132
- export type { SourceView } from './SourceView.js';
135
+ export { sourceViewOf, type SourceView } from './SourceView.js';
133
136
  export { Sources } from './Source.js';
134
137
  export { storageOver } from './storage/Store.js';
135
138
  export type { Store } from './storage/Store.js';
@@ -138,6 +141,7 @@ export type { App } from './boot/App.js';
138
141
  export type { CreateAppOptions } from './boot/CreateAppOptions.js';
139
142
  export type { ScanResult } from './scan.js';
140
143
  export type { Diagnostic } from './diagnostic.js';
144
+ export { BootRefusal } from './BootRefusal.js';
141
145
  export { DEFAULT_CONVENTIONS, frondDirsOf, frondPackage, providerDirsOf, resolveConventions, type Conventions } from './Conventions.js';
142
146
  export { type ConventionsInput } from './ConventionsInput.js';
143
147
  export type { CollectorEntry } from './descriptor/CollectorEntry.js';
@@ -1,7 +1,7 @@
1
1
  import type { AggregateShape } from './AggregateShape.js';
2
- import { type EntityConstructor } from '@fougere/schema';
2
+ import { type Entity } from '@fougere/schema';
3
3
 
4
- export type AggregateConstructor<E extends readonly EntityConstructor[]> =
4
+ export type AggregateConstructor<E extends readonly Entity[]> =
5
5
  (new (...storages: unknown[]) => AggregateShape<E>) & {
6
6
  readonly __entity: unknown;
7
7
  /** The entities this class owns. Present from two on — an owner of one owns nothing. */
@@ -1,6 +1,6 @@
1
- import { type EntityConstructor } from '@fougere/schema';
1
+ import { type Entity } from '@fougere/schema';
2
2
  import type { Storage } from '../storage/Storage.js';
3
3
 
4
4
  /** The tuple an aggregate holds — the readable form of {@link AggregateShape}'s member. */
5
- export type AggregateOf<E extends readonly EntityConstructor[]> =
5
+ export type AggregateOf<E extends readonly Entity[]> =
6
6
  { [K in keyof E]: Storage<InstanceType<E[K]>> };
@@ -1,7 +1,7 @@
1
1
  import type { Storage } from '../storage/Storage.js';
2
- import type { EntityConstructor } from '@fougere/schema';
2
+ import type { Entity } from '@fougere/schema';
3
3
 
4
4
  /** What an aggregate's subclass sees. */
5
- export declare abstract class AggregateShape<E extends readonly EntityConstructor[]> {
5
+ export declare abstract class AggregateShape<E extends readonly Entity[]> {
6
6
  protected storages: { [K in keyof E]: Storage<InstanceType<E[K]>> };
7
7
  }
@@ -1,4 +1,4 @@
1
- import { lowerFirst, type EntityConstructor, type SchemaView } from '@fougere/schema';
1
+ import { FieldSet, lowerFirst, type Entity, type SchemaView } from '@fougere/schema';
2
2
  import type { ListOptions } from '../storage/ListOptions.js';
3
3
  import type { Storage } from '../storage/Storage.js';
4
4
  import type { OperationContract } from '../wire/OperationContract.js';
@@ -29,13 +29,15 @@ export function subjectOf(ctor: unknown, address: string): string {
29
29
  return target?.name ? lowerFirst(target.name) : address;
30
30
  }
31
31
 
32
- /** The id of the row an op acts on — a route segment, or a query fallback. */
33
- const byId = { name: 'id', source: { kind: 'param' as const, name: 'id' }, optional: false };
32
+ /** The id of the row an op acts on — `id: Post['id']`, which a route places before the op. */
33
+ const byId = (entity: string, field: string) =>
34
+ ({ name: 'id', source: { kind: 'param' as const, name: 'id', field: { entity, name: field }, identifies: entity }, optional: false });
34
35
 
35
36
  const fromBody = { name: 'input', source: { kind: 'input' as const }, optional: false };
36
37
 
37
38
  /** The same five, written as the scan would have written them. */
38
- const idParam = { name: 'id', type: { raw: 'string', name: 'string' } };
39
+ const idParam = (entity: string, field: string) =>
40
+ ({ name: 'id', type: { raw: `${entity}['${field}']`, name: 'string', field: { entity, name: field }, identifies: entity } });
39
41
 
40
42
  const inputParam = (entity: string) => ({ name: 'input', type: { raw: `Partial<${entity}>`, name: entity } });
41
43
 
@@ -50,6 +52,9 @@ const returns = (raw: string, name: string, extra?: { array?: boolean; nullable?
50
52
  function crudOps(entity: SchemaView & { partial?: () => SchemaView }): Record<string, OperationContract> {
51
53
  const name = (entity as { name?: string }).name ?? 'Entity';
52
54
  const input = inputParam(name);
55
+ const primary = FieldSet.of(entity.getFields()).primary ?? 'id';
56
+ const id = byId(name, primary);
57
+ const idSignature = idParam(name, primary);
53
58
 
54
59
  return {
55
60
  list: {
@@ -61,8 +66,8 @@ function crudOps(entity: SchemaView & { partial?: () => SchemaView }): Record<st
61
66
  },
62
67
  },
63
68
  findById: {
64
- output: entity, cardinality: 'maybe', binding: [byId],
65
- signature: { name: 'findById', returnType: returns(`${name} | undefined`, name, { nullable: true }), params: [idParam] },
69
+ output: entity, cardinality: 'maybe', binding: [id],
70
+ signature: { name: 'findById', returnType: returns(`${name} | undefined`, name, { nullable: true }), params: [idSignature] },
66
71
  },
67
72
  create: {
68
73
  input: entity, output: entity, cardinality: 'one', binding: [fromBody],
@@ -71,12 +76,12 @@ function crudOps(entity: SchemaView & { partial?: () => SchemaView }): Record<st
71
76
  // The patch view carries its own mode: an absent field is untouched, an
72
77
  // immutable one re-supplied is refused.
73
78
  update: {
74
- input: entity.partial?.(), output: entity, cardinality: 'one', binding: [byId, fromBody],
75
- signature: { name: 'update', returnType: returns(name, name), params: [idParam, input] },
79
+ input: entity.partial?.(), output: entity, cardinality: 'one', binding: [id, fromBody],
80
+ signature: { name: 'update', returnType: returns(name, name), params: [idSignature, input] },
76
81
  },
77
82
  delete: {
78
- cardinality: 'none', binding: [byId],
79
- signature: { name: 'delete', returnType: returns('boolean', 'boolean'), params: [idParam] },
83
+ cardinality: 'none', binding: [id],
84
+ signature: { name: 'delete', returnType: returns('boolean', 'boolean'), params: [idSignature] },
80
85
  },
81
86
  };
82
87
  }
@@ -103,7 +108,7 @@ export interface CrudConstructor<T, V = {}> {
103
108
  *
104
109
  * Documented: [handlers](https://fougere.dev/docs/business/handlers).
105
110
  */
106
- export function Crud<E extends EntityConstructor, V extends CrudViews | EntityConstructor = {}>(
111
+ export function Crud<E extends Entity, V extends CrudViews | Entity = {}>(
107
112
  entity: E,
108
113
  output?: V,
109
114
  ): CrudConstructor<InstanceType<E>, V> {
@@ -112,7 +117,7 @@ export function Crud<E extends EntityConstructor, V extends CrudViews | EntityCo
112
117
 
113
118
  // A view is a class (it carries fields) ; a map of views is a plain object.
114
119
  const perOp = typeof output === 'object' && output !== null ? (output as CrudViews) : undefined;
115
- const wholeHandler = typeof output === 'function' ? (output as EntityConstructor) : undefined;
120
+ const wholeHandler = typeof output === 'function' ? (output as Entity) : undefined;
116
121
 
117
122
  return asCrudConstructor<T, V>(class CrudHandler {
118
123
  static __entity = entity;
@@ -123,7 +128,7 @@ export function Crud<E extends EntityConstructor, V extends CrudViews | EntityCo
123
128
  * What this prefab handler declares — read by the façade, merged under the author's own
124
129
  * methods.
125
130
  */
126
- static __ops: Record<string, OperationContract> = crudOps(entity as unknown as SchemaView & { partial?: () => SchemaView });
131
+ static __ops: Record<string, OperationContract> = crudOps(entity);
127
132
 
128
133
  // Private: the five ops are how a subclass reaches the rows — `super.findById(id)` — and a
129
134
  // query worth a name belongs to the repository, which it asks for like any handler does.
@@ -1,5 +1,5 @@
1
1
  import type { CrudOpName } from './CrudOpName.js';
2
- import type { EntityConstructor } from '@fougere/schema';
2
+ import type { Entity } from '@fougere/schema';
3
3
  import type { ListOptions } from '../storage/ListOptions.js';
4
4
  import type { Page } from '../wire/Page.js';
5
5
 
@@ -8,8 +8,8 @@ type OutOf<V, K extends CrudOpName, T> =
8
8
  // Bracketed on purpose: a naked `V extends …` DISTRIBUTES, and the no-view default
9
9
  // is the empty map, whose `keyof` is `never` — distribution would then collapse
10
10
  // every op's output to `never` instead of falling through to the entity.
11
- [V] extends [EntityConstructor] ? InstanceType<V & EntityConstructor>
12
- : K extends keyof V ? (V[K] extends EntityConstructor ? InstanceType<V[K]> : T)
11
+ [V] extends [Entity] ? InstanceType<V & Entity>
12
+ : K extends keyof V ? (V[K] extends Entity ? InstanceType<V[K]> : T)
13
13
  : T;
14
14
 
15
15
  /** The five ops, typed from the entity and its views. */
@@ -1,5 +1,5 @@
1
- import { type EntityConstructor } from '@fougere/schema';
1
+ import { type Entity } from '@fougere/schema';
2
2
  import type { CrudOpName } from './CrudOpName.js';
3
3
 
4
4
  /** Which view each op speaks — omitted ops speak the entity, the trivial view. */
5
- export type CrudViews = Partial<Record<CrudOpName, EntityConstructor>>;
5
+ export type CrudViews = Partial<Record<CrudOpName, Entity>>;
@@ -0,0 +1,13 @@
1
+ import type { AppNext } from '../wire/AppNext.js';
2
+ import type { OperationContext } from '../wire/OperationContext.js';
3
+
4
+ /**
5
+ * What runs around every operation of its frond, and of the fronds under it — a class in
6
+ * `middlewares/` that extends this one.
7
+ *
8
+ * It answers what `next()` answered, the same type: a caller reads the handler's signature,
9
+ * and nothing tells that signature a middleware stood in the way.
10
+ */
11
+ export abstract class Middleware {
12
+ abstract around<T>(context: OperationContext, next: AppNext<T>): Promise<T>;
13
+ }
@@ -1,4 +1,4 @@
1
- import type { EntityConstructor } from '@fougere/schema';
1
+ import type { Entity } from '@fougere/schema';
2
2
  import type { Storage } from '../storage/Storage.js';
3
3
  import type { Refreshed } from './Refreshed.js';
4
4
  import type { MirrorOf } from './MirrorOf.js';
@@ -8,7 +8,7 @@ export interface MirrorConstructor<T> {
8
8
  readonly __entity: unknown;
9
9
  }
10
10
 
11
- export function Mirror<E extends EntityConstructor>(shape: E): MirrorConstructor<InstanceType<E>> {
11
+ export function Mirror<E extends Entity>(shape: E): MirrorConstructor<InstanceType<E>> {
12
12
  type T = InstanceType<E>;
13
13
 
14
14
  abstract class MirrorBase implements MirrorOf<T> {
@@ -1,4 +1,4 @@
1
- import { upperFirst, type EntityConstructor } from '@fougere/schema';
1
+ import { upperFirst, type Entity } from '@fougere/schema';
2
2
  import type { ListOptions } from '../storage/ListOptions.js';
3
3
  import type { ListResult } from '../storage/ListResult.js';
4
4
  import type { SelectOption } from '../storage/SelectOption.js';
@@ -14,22 +14,22 @@ export interface RepositoryConstructor<T> {
14
14
  }
15
15
 
16
16
  /** What an aggregate's subclass sees. */
17
- declare abstract class AggregateShape<E extends readonly EntityConstructor[]> {
17
+ declare abstract class AggregateShape<E extends readonly Entity[]> {
18
18
  protected storages: { [K in keyof E]: Storage<InstanceType<E[K]>> };
19
19
  }
20
20
 
21
- export function Repository<E extends EntityConstructor>(entity: E): RepositoryConstructor<InstanceType<E>>;
21
+ export function Repository<E extends Entity>(entity: E): RepositoryConstructor<InstanceType<E>>;
22
22
 
23
- export function Repository<E extends readonly [EntityConstructor, EntityConstructor, ...EntityConstructor[]]>(
23
+ export function Repository<E extends readonly [Entity, Entity, ...Entity[]]>(
24
24
  ...entities: E
25
25
  ): AggregateConstructor<E>;
26
26
 
27
- export function Repository(...entities: EntityConstructor[]): unknown {
27
+ export function Repository(...entities: Entity[]): unknown {
28
28
  return entities.length === 1 ? one(entities[0]) : many(entities);
29
29
  }
30
30
 
31
31
  /** The port forwarded, member by member. */
32
- function one<E extends EntityConstructor>(entity: E): RepositoryConstructor<InstanceType<E>> {
32
+ function one<E extends Entity>(entity: E): RepositoryConstructor<InstanceType<E>> {
33
33
  type T = InstanceType<E>;
34
34
 
35
35
  class RepositoryBase implements Storage<T> {
@@ -56,7 +56,7 @@ function one<E extends EntityConstructor>(entity: E): RepositoryConstructor<Inst
56
56
  }
57
57
 
58
58
  /** The aggregate. */
59
- function many<E extends readonly EntityConstructor[]>(entities: E): AggregateConstructor<E> {
59
+ function many<E extends readonly Entity[]>(entities: E): AggregateConstructor<E> {
60
60
  class AggregateBase {
61
61
  static readonly __entity = entities[0];
62
62
  static readonly __owns = entities;
@@ -77,6 +77,6 @@ export function repositoryKeyOf(entity: string): string {
77
77
  }
78
78
 
79
79
  /** The entities a class owns — empty for anything that is not an aggregate. */
80
- export function ownedBy(ctor: unknown): readonly EntityConstructor[] {
81
- return (ctor as { __owns?: readonly EntityConstructor[] } | undefined)?.__owns ?? [];
80
+ export function ownedBy(ctor: unknown): readonly Entity[] {
81
+ return (ctor as { __owns?: readonly Entity[] } | undefined)?.__owns ?? [];
82
82
  }
@@ -4,11 +4,12 @@
4
4
  * Documented: [collectors](https://fougere.dev/docs/business/collectors).
5
5
  */
6
6
 
7
- import { FieldValueValidator, dotted, json, upperFirst, type Field, type SchemaView } from '@fougere/schema';
7
+ import type { AbstractCtor } from '../Ctor.js';
8
+ import { FieldValueValidator, dotted, json, upperFirst, type Entity, type Field, type SchemaView } from '@fougere/schema';
8
9
  import { ErrorCode } from '../wire/ErrorCode.js';
9
10
  import { FougereError } from '../wire/FougereError.js';
10
11
 
11
- export function Collector<T extends abstract new (...args: any[]) => any>(target: T) {
12
+ export function Collector<T extends AbstractCtor>(target: T) {
12
13
  class CollectorBase {
13
14
  static readonly __entity = target;
14
15
  }
@@ -34,7 +35,7 @@ export function collectedAs(collector: object, value: unknown): unknown {
34
35
 
35
36
  let reader = readers.get(target);
36
37
  if (!reader) {
37
- reader = json(target as SchemaView & (new () => unknown));
38
+ reader = json(target as Entity);
38
39
  readers.set(target, reader);
39
40
  }
40
41
 
@@ -0,0 +1,6 @@
1
+ import type { OperationContract } from '../wire/OperationContract.js';
2
+
3
+ /** The contracts a class carries on itself — `Crud(Post)` states its five as `__ops`. */
4
+ export function opsOf(ctor: unknown): Record<string, OperationContract> {
5
+ return (ctor as { __ops?: Record<string, OperationContract> }).__ops ?? {};
6
+ }
@@ -1,18 +1,18 @@
1
- import type { EntityConstructor } from '@fougere/schema';
1
+ import type { Entity } from '@fougere/schema';
2
2
  import type { PresenterViews } from './presenter.js';
3
3
 
4
4
  /** What a prefab class declares about ITSELF, at runtime. */
5
5
  interface Prefab {
6
6
  /** The shape this class was built on. `Mirror` calls it a shape; it is the same slot. */
7
- readonly __entity?: EntityConstructor;
7
+ readonly __entity?: Entity;
8
8
  /** The view each computed field emits — `Presenter(Order, { items: [OrderItemView] })`. */
9
9
  readonly __views?: PresenterViews;
10
10
  /** The view the whole handler speaks — `Crud(Post, PostPublic)`. Same slot, one level up. */
11
- readonly __output?: EntityConstructor;
11
+ readonly __output?: Entity;
12
12
  }
13
13
 
14
14
  /** The shape a prefab was built on, or `undefined` when the class is not one. */
15
- export function targetOf(ctor: unknown): EntityConstructor | undefined {
15
+ export function targetOf(ctor: unknown): Entity | undefined {
16
16
  return (ctor as Prefab | undefined)?.__entity;
17
17
  }
18
18
 
@@ -22,6 +22,6 @@ export function viewsOf(ctor: unknown): PresenterViews | undefined {
22
22
  }
23
23
 
24
24
  /** The handler-wide view a prefab declares, same rule. */
25
- export function outputOf(ctor: unknown): EntityConstructor | undefined {
25
+ export function outputOf(ctor: unknown): Entity | undefined {
26
26
  return (ctor as Prefab | undefined)?.__output;
27
27
  }
@@ -4,16 +4,16 @@
4
4
  * Documented: [presenters](https://fougere.dev/docs/business/presenters).
5
5
  */
6
6
 
7
- import { upperFirst, type EntityConstructor } from '@fougere/schema';
7
+ import { upperFirst, type Entity } from '@fougere/schema';
8
8
 
9
9
  /**
10
10
  * The view a computed field emits — `OrderItemView` for one, `[OrderItemView]` for many.
11
11
  * A view is a schema, so it derives: `Order.pick('id', 'status')`, never a hand-written type.
12
12
  */
13
- export type PresenterViews = Record<string, EntityConstructor | [EntityConstructor]>;
13
+ export type PresenterViews = Record<string, Entity | [Entity]>;
14
14
 
15
15
  /** `Presenter(Order, { items: [OrderItemView] })` — the views its computed fields emit. */
16
- export function Presenter<E extends EntityConstructor>(entity: E, views?: PresenterViews) {
16
+ export function Presenter<E extends Entity>(entity: E, views?: PresenterViews) {
17
17
  class PresenterBase {
18
18
  static readonly __entity = entity;
19
19
  static readonly __views = views;
@@ -1,17 +1,17 @@
1
1
  import type { FougereOperations } from './FougereOperations.js';
2
2
 
3
- type Served = keyof FougereOperations & string;
3
+ type Generated = keyof FougereOperations & string;
4
4
 
5
5
  /**
6
6
  * Both halves below take the union through a type PARAMETER, which is what makes the
7
- * conditional distribute: `Served` is an alias, and an alias is matched whole — `'a.x' | 'b.y'`
7
+ * conditional distribute: `Generated` is an alias, and an alias is matched whole — `'a.x' | 'b.y'`
8
8
  * does not extend `` `${string}.${string}` `` as one thing, so the naive form answers `never`.
9
9
  */
10
10
  type AddressIn<Key> = Key extends `${infer Address}.${string}` ? Address : never;
11
11
 
12
12
  /**
13
13
  * The addresses this app answers at — or any string, until a scan has said otherwise. The
14
- * `[Served] extends [never]` form is what keeps an ungenerated project compiling: a bare `never`
14
+ * `[Generated] extends [never]` form is what keeps an ungenerated project compiling: a bare `never`
15
15
  * in a parameter position refuses every call, the correct ones included.
16
16
  */
17
- export type Addresses = [Served] extends [never] ? string : AddressIn<Served>;
17
+ export type Addresses = [Generated] extends [never] ? string : AddressIn<Generated>;
@@ -0,0 +1,4 @@
1
+ /** How MUCH an operation answers — the half of a return type that a view cannot say. */
2
+ export const CARDINALITIES = ['one', 'maybe', 'many', 'page', 'none'] as const;
3
+
4
+ export type Cardinality = (typeof CARDINALITIES)[number];
@@ -1,23 +1,20 @@
1
1
  import { lowerFirst } from '@fougere/schema';
2
2
 
3
- import type { InvocationContext } from './InvocationContext.js';
4
-
5
3
  /**
6
4
  * The facade built in front of a handler — the framework's second port, after `Storage`.
7
5
  *
8
- * What the handler knows is WHICH operations exist and what each one answers. The two ends are
9
- * the port's own: an invocation goes in where the handler takes positional arguments, and a
10
- * promise comes back where the handler may answer a bare value. A facade is a crossing, and a
11
- * crossing is awaited before any transport — the dispatch resolves a route, runs the middlewares
12
- * and awaits the collectors, so `readLocation(): string` was typed as answering now and never did.
6
+ * It takes what the handler takes: code calling it already holds the values, so nothing is
7
+ * collected for it and nothing is presented — that is a door's work, done on what a door
8
+ * received. A promise comes back where the handler may answer a bare value, because a facade
9
+ * is a crossing and a crossing is awaited, whether or not a transport carries it.
13
10
  *
14
11
  * `Awaited` because a promise does not stack: `Promise.resolve(p)` IS `p`, so writing
15
12
  * `Promise<R>` over an async handler would describe a `Promise<Promise<Post>>` that no value can
16
13
  * have — `.then` would hand its callback a promise the runtime never delivers.
17
14
  */
18
15
  export type Facade<T> = {
19
- [K in keyof T]: T[K] extends (...args: never[]) => infer R
20
- ? (invocation?: InvocationContext) => Promise<Awaited<R>>
16
+ [K in keyof T]: T[K] extends (...args: infer A) => infer R
17
+ ? (...args: A) => Promise<Awaited<R>>
21
18
  : never;
22
19
  };
23
20
 
@@ -31,6 +31,7 @@ export class Invocation implements InvocationContext {
31
31
  readonly params: Record<string, unknown>;
32
32
  readonly query: Record<string, unknown>;
33
33
  readonly input: unknown;
34
+ readonly args?: readonly unknown[];
34
35
  readonly state: Record<string, unknown>;
35
36
  readonly trace?: string;
36
37
  readonly identity?: string;
@@ -38,11 +39,12 @@ export class Invocation implements InvocationContext {
38
39
  readonly runAt?: number;
39
40
  readonly crossed?: true;
40
41
 
41
- /** Gives every entry and every transport the same nine-member value, frozen. */
42
+ /** Gives every entry and every transport the same ten-member value, frozen. */
42
43
  private constructor(context: PartialInvocation) {
43
44
  this.params = canonicalRecord(context.params);
44
45
  this.query = canonicalRecord(context.query);
45
46
  this.input = canonicalValue(context.input);
47
+ if (context.args !== undefined) this.args = canonicalValue(context.args) as readonly unknown[];
46
48
  // State is host-owned and may still be enriched by existing middlewares. Moving it
47
49
  // to immutable lifecycle context is a separate migration.
48
50
  this.state = context.state ?? {};
@@ -2,6 +2,12 @@ export interface InvocationContext {
2
2
  params: Record<string, unknown>;
3
3
  query: Record<string, unknown>;
4
4
  input: unknown;
5
+ /**
6
+ * The handler's own arguments, written by code that called its facade. A door writes
7
+ * `params`, `query` and `input` — what it received — and the binding plan reads them; code
8
+ * already holds the values, so nothing is collected and nothing is presented.
9
+ */
10
+ args?: readonly unknown[];
5
11
  state: Record<string, unknown>;
6
12
  trace?: string;
7
13
  identity?: string;
@@ -5,6 +5,7 @@ import type { TypeRef } from './TypeRef.js';
5
5
  import type { ErrorCode } from './ErrorCode.js';
6
6
  import type { OperationKind } from './OperationKind.js';
7
7
  import type { OperationKindInference } from './OperationKindInference.js';
8
+ import type { Cardinality } from './Cardinality.js';
8
9
 
9
10
  /** The contract of one operation — everything the façade needs to serve a call. */
10
11
  export interface OperationContract {
@@ -17,7 +18,7 @@ export interface OperationContract {
17
18
  /** The operation in words, for a caller that meets it over the wire and has to choose. */
18
19
  description?: string;
19
20
  /** How MUCH comes back — the half of the return type that `output` cannot say. */
20
- cardinality?: 'one' | 'maybe' | 'many' | 'page' | 'none';
21
+ cardinality?: Cardinality;
21
22
  /** The argument names and TYPES — the one thing `binding` cannot say. */
22
23
  signature?: Signature;
23
24
  /**
@@ -48,8 +49,8 @@ const PRIMITIVE_RETURNS = new Set(['boolean', 'string', 'number', 'void', 'undef
48
49
 
49
50
  /** Deliberately finite. */
50
51
  const QUERY_VERBS = new Set([
51
- 'all', 'check', 'count', 'exists', 'fetch', 'find', 'get', 'has', 'health', 'list',
52
- 'load', 'mine', 'ping', 'quote', 'read', 'search', 'stats', 'status', 'who',
52
+ 'all', 'can', 'check', 'count', 'exists', 'fetch', 'find', 'get', 'has', 'health', 'is',
53
+ 'list', 'load', 'mine', 'ping', 'quote', 'read', 'search', 'stats', 'status', 'who',
53
54
  ]);
54
55
 
55
56
  /**
@@ -1 +1,4 @@
1
- export type OperationKind = 'query' | 'command';
1
+ /** `query` reads, `command` writes — the same call REST turns into GET vs POST. */
2
+ export const OPERATION_KINDS = ['query', 'command'] as const;
3
+
4
+ export type OperationKind = (typeof OPERATION_KINDS)[number];