@fougere/core 0.8.3-alpha.0 → 0.9.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 (279) hide show
  1. package/dist/boot/AppLifecycle.d.ts +8 -0
  2. package/dist/boot/AppLifecycle.d.ts.map +1 -1
  3. package/dist/boot/AppLifecycle.js.map +1 -1
  4. package/dist/boot/Emissions.d.ts +26 -0
  5. package/dist/boot/Emissions.d.ts.map +1 -1
  6. package/dist/boot/Emissions.js +144 -9
  7. package/dist/boot/Emissions.js.map +1 -1
  8. package/dist/boot/auth.d.ts +1 -1
  9. package/dist/boot/auth.d.ts.map +1 -1
  10. package/dist/boot/bootstrap.d.ts.map +1 -1
  11. package/dist/boot/bootstrap.js +401 -598
  12. package/dist/boot/bootstrap.js.map +1 -1
  13. package/dist/boot/card.js +1 -1
  14. package/dist/boot/card.js.map +1 -1
  15. package/dist/boot/hosted.d.ts +1 -1
  16. package/dist/boot/hosted.d.ts.map +1 -1
  17. package/dist/boot/hosted.js +1 -0
  18. package/dist/boot/hosted.js.map +1 -1
  19. package/dist/boot/install.d.ts +42 -0
  20. package/dist/boot/install.d.ts.map +1 -0
  21. package/dist/boot/install.js +327 -0
  22. package/dist/boot/install.js.map +1 -0
  23. package/dist/boot/ownership.d.ts +1 -1
  24. package/dist/boot/ownership.d.ts.map +1 -1
  25. package/dist/boot/ownership.js +14 -1
  26. package/dist/boot/ownership.js.map +1 -1
  27. package/dist/boot/ports.d.ts +12 -3
  28. package/dist/boot/ports.d.ts.map +1 -1
  29. package/dist/boot/ports.js +51 -21
  30. package/dist/boot/ports.js.map +1 -1
  31. package/dist/boot/remote.d.ts.map +1 -1
  32. package/dist/boot/remote.js +3 -3
  33. package/dist/boot/remote.js.map +1 -1
  34. package/dist/boot/seed.d.ts +7 -1
  35. package/dist/boot/seed.d.ts.map +1 -1
  36. package/dist/boot/seed.js +7 -2
  37. package/dist/boot/seed.js.map +1 -1
  38. package/dist/boot/statement-drift.d.ts +1 -1
  39. package/dist/boot/statement-drift.d.ts.map +1 -1
  40. package/dist/boot/together.d.ts +1 -1
  41. package/dist/boot/together.d.ts.map +1 -1
  42. package/dist/boot/together.js +1 -1
  43. package/dist/boot/together.js.map +1 -1
  44. package/dist/boot/types.d.ts +18 -3
  45. package/dist/boot/types.d.ts.map +1 -1
  46. package/dist/builtin/LogLine.d.ts +38 -0
  47. package/dist/builtin/LogLine.d.ts.map +1 -0
  48. package/dist/builtin/LogLine.js +37 -0
  49. package/dist/builtin/LogLine.js.map +1 -0
  50. package/dist/builtin/logger.d.ts +57 -0
  51. package/dist/builtin/logger.d.ts.map +1 -1
  52. package/dist/builtin/logger.js +72 -17
  53. package/dist/builtin/logger.js.map +1 -1
  54. package/dist/config-loader.d.ts +3 -2
  55. package/dist/config-loader.d.ts.map +1 -1
  56. package/dist/config-loader.js.map +1 -1
  57. package/dist/contract.d.ts +3 -7
  58. package/dist/contract.d.ts.map +1 -1
  59. package/dist/contract.js +3 -4
  60. package/dist/contract.js.map +1 -1
  61. package/dist/{scan/conventions.d.ts → conventions.d.ts} +6 -0
  62. package/dist/conventions.d.ts.map +1 -0
  63. package/dist/{scan/conventions.js → conventions.js} +3 -2
  64. package/dist/conventions.js.map +1 -0
  65. package/dist/crypto/encoding.d.ts +0 -1
  66. package/dist/crypto/encoding.d.ts.map +1 -1
  67. package/dist/crypto/encoding.js +0 -7
  68. package/dist/crypto/encoding.js.map +1 -1
  69. package/dist/declare.d.ts +11 -1
  70. package/dist/declare.d.ts.map +1 -1
  71. package/dist/declare.js +10 -1
  72. package/dist/declare.js.map +1 -1
  73. package/dist/descriptor/frond.d.ts +34 -0
  74. package/dist/descriptor/frond.d.ts.map +1 -1
  75. package/dist/descriptor/index.d.ts +15 -0
  76. package/dist/descriptor/index.d.ts.map +1 -0
  77. package/dist/descriptor/index.js +14 -0
  78. package/dist/descriptor/index.js.map +1 -0
  79. package/dist/descriptor/surface.d.ts +8 -0
  80. package/dist/descriptor/surface.d.ts.map +1 -0
  81. package/dist/descriptor/surface.js +16 -0
  82. package/dist/descriptor/surface.js.map +1 -0
  83. package/dist/dispatch/ArgumentResolver.d.ts.map +1 -1
  84. package/dist/dispatch/ArgumentResolver.js +6 -2
  85. package/dist/dispatch/ArgumentResolver.js.map +1 -1
  86. package/dist/dispatch/HandlerFacade.d.ts +2 -0
  87. package/dist/dispatch/HandlerFacade.d.ts.map +1 -1
  88. package/dist/dispatch/HandlerFacade.js +9 -4
  89. package/dist/dispatch/HandlerFacade.js.map +1 -1
  90. package/dist/dispatch/StorageGuard.d.ts.map +1 -1
  91. package/dist/dispatch/StorageGuard.js +9 -23
  92. package/dist/dispatch/StorageGuard.js.map +1 -1
  93. package/dist/dispatch/presenterArguments.d.ts +7 -1
  94. package/dist/dispatch/presenterArguments.d.ts.map +1 -1
  95. package/dist/dispatch/presenterArguments.js +15 -4
  96. package/dist/dispatch/presenterArguments.js.map +1 -1
  97. package/dist/dispatch/validateInput.d.ts.map +1 -1
  98. package/dist/dispatch/validateInput.js +2 -2
  99. package/dist/dispatch/validateInput.js.map +1 -1
  100. package/dist/effective-operation.d.ts +1 -1
  101. package/dist/effective-operation.d.ts.map +1 -1
  102. package/dist/effective-operation.js +13 -19
  103. package/dist/effective-operation.js.map +1 -1
  104. package/dist/frond-config.d.ts +24 -0
  105. package/dist/frond-config.d.ts.map +1 -1
  106. package/dist/frond-config.js.map +1 -1
  107. package/dist/index.d.ts +35 -40
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +17 -14
  110. package/dist/index.js.map +1 -1
  111. package/dist/node.d.ts +2 -12
  112. package/dist/node.d.ts.map +1 -1
  113. package/dist/node.js +2 -12
  114. package/dist/node.js.map +1 -1
  115. package/dist/prefab/crud.d.ts +1 -1
  116. package/dist/prefab/crud.d.ts.map +1 -1
  117. package/dist/prefab/mirror.d.ts +10 -11
  118. package/dist/prefab/mirror.d.ts.map +1 -1
  119. package/dist/prefab/mirror.js +1 -27
  120. package/dist/prefab/mirror.js.map +1 -1
  121. package/dist/prefab/repository.d.ts +1 -1
  122. package/dist/prefab/repository.d.ts.map +1 -1
  123. package/dist/{scan/result.d.ts → scan.d.ts} +2 -2
  124. package/dist/scan.d.ts.map +1 -0
  125. package/dist/scan.js +2 -0
  126. package/dist/scan.js.map +1 -0
  127. package/dist/source.d.ts +1 -1
  128. package/dist/source.d.ts.map +1 -1
  129. package/dist/storage/criterion.d.ts.map +1 -0
  130. package/dist/{criterion.js → storage/criterion.js} +2 -2
  131. package/dist/storage/criterion.js.map +1 -0
  132. package/dist/{storage.d.ts → storage/port.d.ts} +6 -3
  133. package/dist/storage/port.d.ts.map +1 -0
  134. package/dist/{storage.js → storage/port.js} +13 -6
  135. package/dist/storage/port.js.map +1 -0
  136. package/dist/{store.d.ts → storage/store.d.ts} +1 -1
  137. package/dist/storage/store.d.ts.map +1 -0
  138. package/dist/{store.js → storage/store.js} +33 -17
  139. package/dist/storage/store.js.map +1 -0
  140. package/dist/verify.js +1 -1
  141. package/dist/verify.js.map +1 -1
  142. package/dist/wire/Invocation.d.ts +1 -3
  143. package/dist/wire/Invocation.d.ts.map +1 -1
  144. package/dist/wire/Invocation.js +1 -5
  145. package/dist/wire/Invocation.js.map +1 -1
  146. package/dist/wire/binding.d.ts +9 -0
  147. package/dist/wire/binding.d.ts.map +1 -1
  148. package/dist/wire/binding.js +11 -6
  149. package/dist/wire/binding.js.map +1 -1
  150. package/dist/wire/emit.d.ts +45 -0
  151. package/dist/wire/emit.d.ts.map +1 -0
  152. package/dist/wire/emit.js +44 -0
  153. package/dist/wire/emit.js.map +1 -0
  154. package/dist/wire/errors.js +2 -2
  155. package/dist/wire/errors.js.map +1 -1
  156. package/dist/wire/loggerMiddleware.d.ts.map +1 -1
  157. package/dist/wire/loggerMiddleware.js +6 -0
  158. package/dist/wire/loggerMiddleware.js.map +1 -1
  159. package/package.json +10 -4
  160. package/src/boot/AppLifecycle.ts +8 -0
  161. package/src/boot/Emissions.ts +177 -9
  162. package/src/boot/auth.ts +1 -1
  163. package/src/boot/bootstrap.ts +456 -672
  164. package/src/boot/card.ts +1 -1
  165. package/src/boot/hosted.ts +4 -2
  166. package/src/boot/install.ts +421 -0
  167. package/src/boot/ownership.ts +17 -2
  168. package/src/boot/ports.ts +71 -27
  169. package/src/boot/remote.ts +3 -3
  170. package/src/boot/seed.ts +18 -3
  171. package/src/boot/statement-drift.ts +1 -1
  172. package/src/boot/together.ts +1 -1
  173. package/src/boot/types.ts +18 -3
  174. package/src/builtin/LogLine.ts +38 -0
  175. package/src/builtin/logger.ts +109 -17
  176. package/src/config-loader.ts +3 -2
  177. package/src/contract.ts +5 -9
  178. package/src/{scan/conventions.ts → conventions.ts} +9 -2
  179. package/src/crypto/encoding.ts +0 -7
  180. package/src/declare.ts +20 -2
  181. package/src/descriptor/frond.ts +31 -0
  182. package/src/descriptor/index.ts +15 -0
  183. package/src/descriptor/surface.ts +23 -0
  184. package/src/dispatch/ArgumentResolver.ts +6 -2
  185. package/src/dispatch/HandlerFacade.ts +11 -4
  186. package/src/dispatch/StorageGuard.ts +8 -21
  187. package/src/dispatch/presenterArguments.ts +21 -9
  188. package/src/dispatch/validateInput.ts +2 -2
  189. package/src/effective-operation.ts +16 -21
  190. package/src/frond-config.ts +20 -0
  191. package/src/index.ts +44 -57
  192. package/src/node.ts +2 -16
  193. package/src/prefab/crud.ts +1 -1
  194. package/src/prefab/mirror.ts +12 -38
  195. package/src/prefab/repository.ts +1 -1
  196. package/src/{scan/result.ts → scan.ts} +1 -1
  197. package/src/source.ts +1 -1
  198. package/src/{criterion.ts → storage/criterion.ts} +2 -2
  199. package/src/{storage.ts → storage/port.ts} +20 -5
  200. package/src/{store.ts → storage/store.ts} +35 -16
  201. package/src/verify.ts +1 -1
  202. package/src/wire/Invocation.ts +2 -7
  203. package/src/wire/binding.ts +17 -6
  204. package/src/wire/call.ts +1 -1
  205. package/src/wire/emit.ts +84 -0
  206. package/src/wire/errors.ts +2 -2
  207. package/src/wire/loggerMiddleware.ts +6 -0
  208. package/dist/boot/boot.d.ts +0 -45
  209. package/dist/boot/boot.d.ts.map +0 -1
  210. package/dist/boot/boot.js +0 -63
  211. package/dist/boot/boot.js.map +0 -1
  212. package/dist/criterion.d.ts.map +0 -1
  213. package/dist/criterion.js.map +0 -1
  214. package/dist/declarations.d.ts +0 -34
  215. package/dist/declarations.d.ts.map +0 -1
  216. package/dist/declarations.js +0 -75
  217. package/dist/declarations.js.map +0 -1
  218. package/dist/emit.d.ts +0 -13
  219. package/dist/emit.d.ts.map +0 -1
  220. package/dist/emit.js +0 -21
  221. package/dist/emit.js.map +0 -1
  222. package/dist/imports.d.ts +0 -16
  223. package/dist/imports.d.ts.map +0 -1
  224. package/dist/imports.js +0 -78
  225. package/dist/imports.js.map +0 -1
  226. package/dist/placement.d.ts +0 -25
  227. package/dist/placement.d.ts.map +0 -1
  228. package/dist/placement.js +0 -69
  229. package/dist/placement.js.map +0 -1
  230. package/dist/scan/adapters.d.ts +0 -3
  231. package/dist/scan/adapters.d.ts.map +0 -1
  232. package/dist/scan/adapters.js +0 -25
  233. package/dist/scan/adapters.js.map +0 -1
  234. package/dist/scan/bundling.d.ts +0 -3
  235. package/dist/scan/bundling.d.ts.map +0 -1
  236. package/dist/scan/bundling.js +0 -15
  237. package/dist/scan/bundling.js.map +0 -1
  238. package/dist/scan/contract.d.ts +0 -27
  239. package/dist/scan/contract.d.ts.map +0 -1
  240. package/dist/scan/contract.js +0 -46
  241. package/dist/scan/contract.js.map +0 -1
  242. package/dist/scan/conventions.d.ts.map +0 -1
  243. package/dist/scan/conventions.js.map +0 -1
  244. package/dist/scan/emit.d.ts +0 -10
  245. package/dist/scan/emit.d.ts.map +0 -1
  246. package/dist/scan/emit.js +0 -144
  247. package/dist/scan/emit.js.map +0 -1
  248. package/dist/scan/handler-parser.d.ts +0 -26
  249. package/dist/scan/handler-parser.d.ts.map +0 -1
  250. package/dist/scan/handler-parser.js +0 -607
  251. package/dist/scan/handler-parser.js.map +0 -1
  252. package/dist/scan/result.d.ts.map +0 -1
  253. package/dist/scan/result.js +0 -2
  254. package/dist/scan/result.js.map +0 -1
  255. package/dist/scan/scanner.d.ts +0 -12
  256. package/dist/scan/scanner.d.ts.map +0 -1
  257. package/dist/scan/scanner.js +0 -573
  258. package/dist/scan/scanner.js.map +0 -1
  259. package/dist/scan/statement.d.ts +0 -4
  260. package/dist/scan/statement.d.ts.map +0 -1
  261. package/dist/scan/statement.js +0 -135
  262. package/dist/scan/statement.js.map +0 -1
  263. package/dist/storage.d.ts.map +0 -1
  264. package/dist/storage.js.map +0 -1
  265. package/dist/store.d.ts.map +0 -1
  266. package/dist/store.js.map +0 -1
  267. package/src/boot/boot.ts +0 -110
  268. package/src/declarations.ts +0 -110
  269. package/src/emit.ts +0 -32
  270. package/src/imports.ts +0 -94
  271. package/src/placement.ts +0 -86
  272. package/src/scan/adapters.ts +0 -32
  273. package/src/scan/bundling.ts +0 -14
  274. package/src/scan/contract.ts +0 -80
  275. package/src/scan/emit.ts +0 -160
  276. package/src/scan/handler-parser.ts +0 -695
  277. package/src/scan/scanner.ts +0 -698
  278. package/src/scan/statement.ts +0 -159
  279. /package/dist/{criterion.d.ts → storage/criterion.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
- import { Boundary, FieldSet, FieldValueValidator, InputRefusal, type Fields } from '@fougere/schema';
2
- import { COMPARISONS, comparisonOf, unknownIn } from '../criterion.js';
3
- import { assertListOptions } from '../storage.js';
1
+ import { FieldSet, FieldValueValidator, InputRefusal, type Fields } from '@fougere/schema';
2
+ import { COMPARISONS, comparisonOf, unknownIn } from '../storage/criterion.js';
3
+ import { assertListOptions } from '../storage/port.js';
4
4
  import { ErrorCode, FougereError } from '../wire/errors.js';
5
5
 
6
6
  /** The gestures this guard grafts onto. */
@@ -70,7 +70,7 @@ export class StorageGuard {
70
70
  if (typeof list === 'function') {
71
71
  guarded.list = async function (...args: unknown[]) {
72
72
  const options = args[0] as { where?: Record<string, unknown> } | undefined;
73
- assertListOptions(options, validation.entity);
73
+ assertListOptions(options, validation.entity, Object.keys(validation.fields));
74
74
  if (options?.where) args[0] = { ...options, where: validation.criteria(options.where) };
75
75
 
76
76
  return list.apply(this, args);
@@ -169,11 +169,7 @@ export class StorageGuard {
169
169
  /** One value against one field — validated, then decoded the way the wire hands it. */
170
170
  private value(field: Fields[string], asked: unknown): { value: unknown } | { error: string } {
171
171
  if (asked === null || asked === undefined) return { value: asked };
172
- const checked = FieldValueValidator.of(field).validate(asked);
173
- if ('error' in checked) return checked;
174
- if (checked.value === null) return { value: null };
175
-
176
- return Boundary.of(field).decode(checked.value);
172
+ return FieldValueValidator.of(field).parse(asked);
177
173
  }
178
174
 
179
175
  private validated<T>(value: T, operation: string, index?: number): T {
@@ -195,18 +191,9 @@ export class StorageGuard {
195
191
  parsed[key] = item;
196
192
  continue;
197
193
  }
198
- const checked = FieldValueValidator.of(field).validate(item);
199
- if ('error' in checked) {
200
- errors.push(`${where}${key}: ${checked.error}`);
201
- continue;
202
- }
203
- if (checked.value === null) {
204
- parsed[key] = null;
205
- continue;
206
- }
207
- const decoded = Boundary.of(field).decode(checked.value);
208
- if ('error' in decoded) errors.push(`${where}${key}: ${decoded.error}`);
209
- else parsed[key] = decoded.value;
194
+ const value = FieldValueValidator.of(field).parse(item);
195
+ if ('error' in value) errors.push(`${where}${key}: ${value.error}`);
196
+ else parsed[key] = value.value;
210
197
  }
211
198
 
212
199
  if (errors.length > 0) {
@@ -1,24 +1,36 @@
1
- import { computeBindingPlan } from '../wire/binding.js';
1
+ import { computeBindingPlan, type BindingPlan } from '../wire/binding.js';
2
2
  import type { InvocationContext } from '../wire/Invocation.js';
3
3
  import type { PresenterEntry } from '../descriptor/frond.js';
4
4
  import type { ArgumentResolver } from './ArgumentResolver.js';
5
5
  import type { PresenterArgs } from './PresenterExecutor.js';
6
6
 
7
+ /**
8
+ * Where each computed field's extra arguments come from — read off the declaration, so a
9
+ * field that takes only the page is absent and asks for nothing.
10
+ */
11
+ export function presenterPlans(
12
+ meta: PresenterEntry,
13
+ collectorTypes: Set<string>,
14
+ ): Map<string, BindingPlan> {
15
+ const plans = new Map<string, BindingPlan>();
16
+ for (const field of meta.fieldMeta) {
17
+ if (!field.params?.length) continue;
18
+ plans.set(field.name, computeBindingPlan(field.params, collectorTypes));
19
+ }
20
+
21
+ return plans;
22
+ }
23
+
7
24
  /** The invocation-dependent arguments of every computed presenter field. */
8
25
  export async function presenterArguments(
9
- meta: PresenterEntry,
26
+ plans: Map<string, BindingPlan>,
10
27
  invocation: InvocationContext,
11
28
  resolver: ArgumentResolver,
12
- collectorTypes: Set<string>,
13
29
  ): Promise<PresenterArgs> {
14
30
  const argumentsByField: PresenterArgs = {};
15
31
 
16
- for (const field of meta.fieldMeta) {
17
- if (!field.params?.length) continue;
18
- argumentsByField[field.name] = await resolver.resolve(
19
- computeBindingPlan(field.params, collectorTypes),
20
- invocation,
21
- );
32
+ for (const [field, plan] of plans) {
33
+ argumentsByField[field] = await resolver.resolve(plan, invocation);
22
34
  }
23
35
 
24
36
  return argumentsByField;
@@ -1,4 +1,4 @@
1
- import { InputValidator, type SchemaView } from '@fougere/schema';
1
+ import { InputValidator, dotted, type SchemaView } from '@fougere/schema';
2
2
  import type { InvocationContext } from '../wire/Invocation.js';
3
3
  import { ErrorCode, FougereError } from '../wire/errors.js';
4
4
 
@@ -17,7 +17,7 @@ export function validateInput(
17
17
  if (!result.success) {
18
18
  throw new FougereError({
19
19
  code: ErrorCode.VALIDATION_FAILED,
20
- message: result.errors.map((error) => `${error.path}: ${error.message}`).join(', '),
20
+ message: result.errors.map((error) => `${dotted(error.path)}: ${error.message}`).join(', '),
21
21
  details: result.errors,
22
22
  entity,
23
23
  operation,
@@ -4,7 +4,8 @@ import { statementDrift } from './boot/statement-drift.js';
4
4
  import { computeBindingPlan, type BindingPlan } from './wire/binding.js';
5
5
  import { targetOf } from './prefab/prefab.js';
6
6
  import type { CollectorEntry, FrondDescriptor, HandlerEntry } from './descriptor/frond.js';
7
- import type { ScanDiagnostic } from './scan/result.js';
7
+ import { servedSurfaces } from './descriptor/surface.js';
8
+ import type { ScanDiagnostic } from './scan.js';
8
9
  import { verify } from './verify.js';
9
10
  import {
10
11
  inferOperationKind,
@@ -228,13 +229,19 @@ export function resolveEffectiveOperations(
228
229
 
229
230
  // A fact is validated by the entity it names. This used to be patched into the
230
231
  // facade after resolution, leaving check/explain with a different input.
231
- let input = contract.input;
232
- if (!input) {
233
- const fact = contract.binding.find((binding) => binding.source.kind === 'fact');
234
- if (fact?.source.kind === 'fact') input = schemas.get(fact.source.factName);
235
- }
236
-
237
- const output = effectiveOutput(frond, handler, name, contract);
232
+ const announced = contract.binding.find((binding) =>
233
+ binding.source.kind === 'fact' || binding.source.kind === 'pipe');
234
+ const fact = announced?.source.kind === 'fact' || announced?.source.kind === 'pipe'
235
+ ? schemas.get(announced.source.factName)
236
+ : undefined;
237
+ const input = contract.input ?? fact;
238
+
239
+ // An op that FINISHES a fact answers the fact, so its answer is the fact's shape:
240
+ // derived here, where its input already is. Left to the ordinary projection it
241
+ // came back stripped — measured, the subscriber got a value with no `id`.
242
+ const output = announced?.source.kind === 'pipe' && fact
243
+ ? { schema: fact, closed: false }
244
+ : effectiveOutput(frond, handler, name, contract);
238
245
  const className = handler.ctor.name.endsWith('Handler')
239
246
  ? handler.ctor.name.slice(0, -'Handler'.length)
240
247
  : handler.ctor.name;
@@ -279,7 +286,7 @@ export function resolveEffectiveOperations(
279
286
  ...(remote ? { remote } : {}),
280
287
  },
281
288
  exposure: {
282
- surfaces: surfacesOf(frond, handler),
289
+ surfaces: servedSurfaces(frond, handler).map((surface) => surface ?? 'default'),
283
290
  adapters: exposedAdapters(handler, options.adapters),
284
291
  },
285
292
  outputClosed: output.closed,
@@ -557,18 +564,6 @@ function effectiveOutput(
557
564
  return { ...(entity ? { schema: entity.entityClass } : {}), closed: false };
558
565
  }
559
566
 
560
- function surfacesOf(frond: FrondDescriptor, handler: HandlerEntry): string[] {
561
- if (handler.surface) return [handler.surface];
562
- const surfaces = ['default'];
563
- for (const [surface, addresses] of Object.entries(frond.surfaces ?? {})) {
564
- const dedicated = frond.handlers.some((candidate) =>
565
- candidate.address === handler.address && candidate.surface === surface);
566
- if (!dedicated && addresses.some((address) =>
567
- address.toLowerCase() === handler.address.toLowerCase())) surfaces.push(surface);
568
- }
569
- return [surfaces[0]!, ...surfaces.slice(1).sort()];
570
- }
571
-
572
567
  function exposedAdapters(
573
568
  handler: HandlerEntry,
574
569
  adapters: Record<string, boolean | undefined> | undefined,
@@ -11,6 +11,12 @@ export interface OperationOverride {
11
11
  kind?: 'query' | 'command';
12
12
  /** The GraphQL root field this op answers to. */
13
13
  graphql?: string;
14
+ /**
15
+ * Where this op answers over REST — the dual of `graphql:`, and the frond's to say for
16
+ * the same reason: how an operation is CALLED belongs beside it, not in whatever host
17
+ * mounts the router. The prefix stays the host's: naming is the frond's, mounting is not.
18
+ */
19
+ rest?: { method?: string; path?: string; status?: number };
14
20
  /**
15
21
  * Handler class to delegate to (overrides the default `{Entity}Handler` lookup).
16
22
  * Class name is used to resolve from DI. E.g. `ArchiveHandler` → `app.resolve('ArchiveHandler')`.
@@ -44,6 +50,20 @@ export interface FrondConfig {
44
50
  surfaces?: Record<string, string[]>;
45
51
  /** Interface → implementation bindings for DI (e.g. { Database: 'SqliteDatabase' }). */
46
52
  bindings?: Record<string, string>;
53
+ /**
54
+ * The ops that FINISH a fact, in the order they run — by class name, keyed by fact.
55
+ *
56
+ * Declared by the frond that OWNS the fact, because ordering is a decision about the
57
+ * fact itself and it has one owner. Without it two links refuse: nothing would say which
58
+ * ran first, and scan order is not an answer.
59
+ */
60
+ pipes?: Record<string, string[]>;
61
+ /**
62
+ * How far a middleware reaches, by class name. A middleware answers for its own frond
63
+ * without being named here; `'app'` is the exception, and it is stated by the frond
64
+ * that decides for the others.
65
+ */
66
+ middlewares?: Record<string, 'frond' | 'app'>;
47
67
  /** Per-operation overrides. Key = operation name (method name on a handler). */
48
68
  operations?: Record<string, OperationOverride>;
49
69
  }
package/src/index.ts CHANGED
@@ -4,93 +4,84 @@ export { orderSeeds, runSeeds, seeding } from './boot/seed.js';
4
4
  export { AppLifecycle, migrating } from './boot/AppLifecycle.js';
5
5
  export type { Extension } from './boot/AppLifecycle.js';
6
6
  export { defineFougere } from './define.js';
7
- export type { AdapterConfig, FougereConfig } from './config-loader.js';
8
- export type { FrondConfig, OperationOverride } from './frond-config.js';
7
+ export type { FougereConfig, AdapterConfig } from './config-loader.js';
8
+ export type { OperationOverride, FrondConfig } from './frond-config.js';
9
9
  export { buildGraph, clusterEntities, suggestSplit, type EntityNode, type DomainCluster } from './graph.js';
10
10
  export { Crud } from './prefab/crud.js';
11
- export type { CrudOps, CrudOpName, CrudViews, CrudConstructor } from './prefab/crud.js';
11
+ export type { CrudViews, CrudOps, CrudOpName, CrudConstructor } from './prefab/crud.js';
12
12
  export { Mirror } from './prefab/mirror.js';
13
- export type { MirrorOf, MirrorConstructor, Refreshed } from './prefab/mirror.js';
13
+ export type { Refreshed, MirrorOf, MirrorConstructor } from './prefab/mirror.js';
14
14
  export { Presenter } from './prefab/presenter.js';
15
15
  export type { PresenterViews } from './prefab/presenter.js';
16
16
  export { Collector } from './prefab/collector.js';
17
17
  export { Repository, type RepositoryOf, type RepositoryConstructor, type AggregateOf, type AggregateConstructor } from './prefab/repository.js';
18
- export { Invocation, canonicalInvocation, EMPTY_INVOCATION } from './wire/Invocation.js';
19
- export type { InvocationContext, PartialInvocation } from './wire/Invocation.js';
20
- export { Call } from './wire/call.js';
21
- export { RouteAddress } from './wire/RouteAddress.js';
22
- export type { RouteAddressInput } from './wire/RouteAddress.js';
18
+ export { Invocation } from './contract.js';
19
+ export type { InvocationContext } from './contract.js';
20
+ export { Call } from './contract.js';
21
+ export { RouteAddress } from './contract.js';
23
22
  export { DispatchLifecycle } from './dispatch/DispatchLifecycle.js';
24
23
  export { Dispatcher } from './dispatch/Dispatcher.js';
25
24
  // The class and not only its type: an observer RECEIVES these, so a package that folds
26
25
  // them — or a test of one — has to be able to make one through the door.
27
26
  export { DispatchEvent } from './dispatch/DispatchEvent.js';
28
- export type { CallPage, CallRecord } from './wire/CallLog.js';
29
- export { driftOf, agrees, explain, type CardDrift } from './wire/drift.js';
30
- export type { DispatchObserver } from './dispatch/DispatchEvent.js';
31
- export type { DispatchPort } from './dispatch/DispatchPort.js';
32
- export {
33
- inferOperationKind,
34
- resolveIsReadOp,
35
- resolveOperationKind,
36
- } from './wire/operation.js';
37
- export { resolveContracts } from './effective-operation.js';
27
+ export type { CallPage, CallRecord } from './contract.js';
28
+ export { driftOf, agrees, explain, type CardDrift } from './contract.js';
38
29
  export type {
39
30
  OperationContract,
40
- OperationKind,
41
- OperationKindInference,
42
31
  OperationsMap,
43
32
  Param,
44
33
  TypeRef,
45
34
  } from './wire/operation.js';
46
- export {
47
- EFFECTIVE_OPERATION_SEMANTICS,
48
- EffectiveOperationModel,
49
- resolveEffectiveOperations,
50
- } from './effective-operation.js';
51
- export type {
52
- EffectiveCollector,
53
- EffectiveOperation,
54
- EffectiveOperationOptions,
55
- EffectiveOperationsMap,
56
- EffectiveParameter,
57
- } from './effective-operation.js';
35
+ export { resolveEffectiveOperations } from './effective-operation.js';
36
+ export type { EffectiveOperation } from './effective-operation.js';
58
37
  export type { ModuleLoader } from './loader.js';
59
- export { FougereError, ErrorCode, validationErrorsOf, type FougereErrorOptions } from './wire/errors.js';
60
- export type { OperationContext, AppNext, AppMiddleware } from './wire/middleware.js';
61
- export { assertIdentityCard, RPC_ENTITY, MAX_BODY_BYTES } from './wire/call.js';
38
+ export { FougereError, ErrorCode, validationErrorsOf } from './contract.js';
39
+ export type { AppMiddleware, OperationContext, AppNext } from './wire/middleware.js';
40
+ export { assertIdentityCard, RPC_ENTITY, MAX_BODY_BYTES } from './contract.js';
62
41
  export { createLocalRunner, createAppRunner } from './boot/runner.js';
63
42
 
64
43
  // A frond an app STATES, for a boot that will not scan. No disk, no `typescript` — which
65
44
  // is why it sits here and not in `/node`, beside the scanner that reads a project.
66
45
  export { frond } from './declare.js';
67
- export type { FrondDeclaration, Declared, DeclaredSubject } from './declare.js';
46
+ export type { Declared, FrondDeclaration, DeclaredSubject } from './declare.js';
68
47
  // What this app would answer to `rpc.discover`. Served by the boot, and reached until now
69
48
  // only by tests through a deep path — a consumer comparing its own card to a producer's
70
49
  // had to dispatch a call to ask a question about itself.
71
50
  export { identityCardOf } from './boot/card.js';
51
+ export type { Facade } from './wire/call.js';
72
52
  export type {
73
- FrondCall, Transport, IdentityCard, CardOp, Facade, RpcAnswer,
74
- TopologyReport, FrondPlacement, Edge,
75
- } from './wire/call.js';
76
- export { type Emit, type Fact } from './emit.js';
53
+ FrondCall,
54
+ Transport,
55
+ IdentityCard,
56
+ CardOp,
57
+ TopologyReport,
58
+ FrondPlacement,
59
+ Edge,
60
+ } from './contract.js';
61
+ export { type Emit, type Fact, type Pipe } from './wire/emit.js';
77
62
  export { callValueOf } from './contract.js';
78
- export type { CallValue } from './contract.js';
79
- export { toHttpError, toPublicError } from './wire/http-error.js';
63
+ export { toHttpError } from './wire/http-error.js';
64
+ export { toPublicError } from './contract.js';
80
65
  export { loggerMiddleware } from './wire/loggerMiddleware.js';
81
- export { Logger, setLogLevel, logLevel, envLevel, onLog } from './builtin/logger.js';
82
- export type { LogLevel, LogRecord, LogSink } from './builtin/logger.js';
66
+ export { Carry, Logger, setLogLevel, logLevel, envLevel, onLog, formatted } from './builtin/logger.js';
67
+ export type { LogRecord, Rendered, LogSink } from './builtin/logger.js';
68
+ // The fact a boot announces. Its destinations are `@fougere/log`'s; the line is core's.
69
+ export { default as LogLine, CARRIES_LINE, LOG_LINE } from './builtin/LogLine.js';
83
70
  // What a re-read config changes in a running process — and what it cannot.
84
- export { applyConfig, type ConfigApplication } from './boot/apply.js';
71
+ export { applyConfig } from './boot/apply.js';
85
72
  export { Config } from './builtin/config.js';
86
- export type { Storage, StorageFactory, ListOptions, ListResult, Together } from './storage.js';
87
- export { togetherKeyOf, membersOfTogetherKey } from './storage.js';
73
+ export type { Storage, StorageFactory, ListOptions, ListResult, Together } from './storage/port.js';
74
+ export { togetherKeyOf } from './storage/port.js';
88
75
  export type { Constraint, Source, SourceConfig, SourceView } from './source.js';
89
76
  export { Sources } from './source.js';
90
- export { storageOver } from './store.js';
91
- export type { Store, Values } from './store.js';
77
+ export { storageOver } from './storage/store.js';
78
+ export type { Store, Values } from './storage/store.js';
92
79
  export type { App, CreateAppOptions } from './boot/types.js';
93
- export type { ScanResult, ScanDiagnostic } from './scan/result.js';
80
+ export type { ScanResult, ScanDiagnostic } from './scan.js';
81
+ export {
82
+ type Conventions, type ConventionsInput, DEFAULT_CONVENTIONS,
83
+ resolveConventions, frondPackage, frondDirsOf, providerDirsOf,
84
+ } from './conventions.js';
94
85
  export type {
95
86
  FrondDescriptor,
96
87
  FrondSource,
@@ -98,20 +89,16 @@ export type {
98
89
  EntityEntry,
99
90
  HandlerEntry,
100
91
  SeedEntry,
101
- SeedFactory,
102
92
  PresenterEntry,
103
93
  PresenterFieldMeta,
104
94
  CollectorEntry,
105
95
  } from './descriptor/frond.js';
106
96
  export type { AuthConfig, AuthContext, AuthRuntime } from './boot/auth.js';
107
97
  export { Fronds } from './descriptor/Fronds.js';
108
- export { verify, assertSplittable, type Violation } from './verify.js';
98
+ export { verify, type Violation } from './verify.js';
109
99
  // Same question as verify(), answered from the source text instead of the model.
110
- export type { CrossFrondImport } from './imports.js';
111
- export type { HandlerDeclaration } from './declarations.js';
112
- export type { OutsideConvention } from './placement.js';
113
100
  // Who is calling, established rather than accepted — the proof beside `state`'s claim.
114
101
  // `generateKeyPair` and `issueGrant` are NOT here: they make keys at a deployment and
115
102
  // need `node:crypto` for a gesture WebCrypto has no equal of. They sit on `/node`.
116
103
  export { signEnvelope, verifyEnvelope, identityFromEnv } from './identity.js';
117
- export type { FrondIdentity, VerifiedCall, CallIdentity, SignedCall } from './identity.js';
104
+ export type { FrondIdentity, SignedCall } from './identity.js';
package/src/node.ts CHANGED
@@ -1,21 +1,7 @@
1
- /** What Fougere does with a filesystem — the half of core that a Worker cannot run. */
2
- export { boot } from './boot/boot.js';
3
-
4
- export { scanProject, frondAliases, watchPathsOf } from './scan/scanner.js';
5
- export {
6
- type Conventions, type ConventionsInput, DEFAULT_CONVENTIONS,
7
- resolveConventions, frondPackage, frondDirsOf, providerDirsOf,
8
- } from './scan/conventions.js';
9
- export { RUNTIME_PACKAGES } from './scan/bundling.js';
10
- export { emitScan, type EmitOptions } from './scan/emit.js';
11
- export { emitStatement } from './scan/statement.js';
1
+ /** What Fougere does with a filesystem, minus the scan — that is `@fougere/compiler`. */
12
2
  export { setModuleLoader, getModuleLoader } from './loader.js';
13
3
  export { loadConfig, loadCascadedConfig } from './config-loader.js';
14
- export { defineFrond } from './frond-config.js';
15
- export { crossFrondImports } from './imports.js';
16
- export { handlerDeclarations } from './declarations.js';
17
- export { outsideConventions } from './placement.js';
18
- export { adaptersOf } from './scan/adapters.js';
4
+ export { defineFrond, loadFrondConfig } from './frond-config.js';
19
5
 
20
6
  // Making a key and binding a name to it happen once, at a deployment, on a machine with
21
7
  // a filesystem — the CLI speaking. Verifying happens per call, everywhere, and stays on
@@ -1,5 +1,5 @@
1
1
  import { lowerFirst, type EntityConstructor, type SchemaView } from '@fougere/schema';
2
- import type { Storage, ListOptions, ListResult } from '../storage.js';
2
+ import type { Storage, ListOptions, ListResult } from '../storage/port.js';
3
3
  import type { OperationContract } from '../wire/operation.js';
4
4
  import { targetOf } from './prefab.js';
5
5
 
@@ -1,26 +1,27 @@
1
- import { Lifecycle, type EntityConstructor, type Fields } from '@fougere/schema';
2
- import type { Storage } from '../storage.js';
1
+ import type { EntityConstructor } from '@fougere/schema';
2
+ import type { Storage } from '../storage/port.js';
3
3
 
4
- /** A paginated local copy of a source that cannot be queried directly. */
5
4
  /** What one refresh did — enough to log it, and to decide whether to run again. */
6
5
  export interface Refreshed {
7
6
  /** Instances written, counting a replaced one once. */
8
7
  written: number;
9
- /** The age the pull was asked to start from — absent on the first ever run. */
8
+ /** The age the pull was asked to start from — absent when it asked for everything. */
10
9
  since?: Date;
11
10
  /** How long the whole pass took, pull included. */
12
11
  ms: number;
13
12
  }
14
13
 
14
+ /** A paginated local copy of a source that cannot be queried directly. */
15
15
  export interface MirrorOf<T> {
16
- /** The copy's own storage — where a page lands, and where its age is read. */
16
+ /** The copy's own storage — where a page lands. */
17
17
  storage: Storage<T>;
18
18
  /** Pages of rows to write. The one thing a mirror's author supplies. */
19
19
  pull(since?: Date): AsyncIterable<Partial<T>[]>;
20
- /** Run one pass: read the high-water mark, pull from there, write each page. */
21
- refresh(): Promise<Refreshed>;
22
- /** When the freshest row here was written — `undefined` while the copy is empty. */
23
- freshness(): Promise<Date | undefined>;
20
+ /**
21
+ * Pull from `since` and write every page. The mark is the CALLER's: only it knows
22
+ * whether a pass completed, and a pass that throws must not advance it.
23
+ */
24
+ refresh(since?: Date): Promise<Refreshed>;
24
25
  }
25
26
 
26
27
  export interface MirrorConstructor<T> {
@@ -31,16 +32,6 @@ export interface MirrorConstructor<T> {
31
32
  export function Mirror<E extends EntityConstructor>(shape: E): MirrorConstructor<InstanceType<E>> {
32
33
  type T = InstanceType<E>;
33
34
 
34
- // Refuse an undated copy before it can be used as if it held live rows.
35
- const declared = ageFieldOf(shape);
36
- if (declared === undefined) {
37
- throw new Error(
38
- `Mirror(${(shape as { name?: string }).name ?? '?'}): the shape carries no \`updated()\` field — ` +
39
- `a copy has to be able to say when it was pulled, and \`refresh\` reads its high-water mark from it.`,
40
- );
41
- }
42
- const age: string = declared;
43
-
44
35
  abstract class MirrorBase implements MirrorOf<T> {
45
36
  static readonly __entity = shape;
46
37
 
@@ -48,35 +39,18 @@ export function Mirror<E extends EntityConstructor>(shape: E): MirrorConstructor
48
39
 
49
40
  abstract pull(since?: Date): AsyncIterable<Partial<T>[]>;
50
41
 
51
- /** The freshest persisted stamp; no process-local freshness state is kept. */
52
- async freshness(): Promise<Date | undefined> {
53
- const [newest] = await this.storage.list({ orderBy: age, order: 'desc', limit: 1 });
54
- const value = (newest as Record<string, unknown> | undefined)?.[age];
55
- return value instanceof Date ? value : undefined;
56
- }
57
-
58
- async refresh(): Promise<Refreshed> {
42
+ async refresh(since?: Date): Promise<Refreshed> {
59
43
  const started = Date.now();
60
- const since = await this.freshness();
61
44
  let written = 0;
62
45
  // Preserve the source's page boundaries: one page becomes one upsert.
63
46
  for await (const page of this.pull(since)) {
64
47
  if (page.length === 0) continue;
65
48
  written += await this.storage.upsertAll(page);
66
49
  }
50
+
67
51
  return { written, since, ms: Date.now() - started };
68
52
  }
69
53
  }
70
54
 
71
55
  return MirrorBase as unknown as MirrorConstructor<T>;
72
56
  }
73
-
74
- /** The field whose lifecycle stamps every update. */
75
- export function ageFieldOf(shape: unknown): string | undefined {
76
- const fields = (shape as { getFields?: () => Fields }).getFields?.();
77
- if (!fields) return undefined;
78
- for (const [name, field] of Object.entries(fields)) {
79
- if (Lifecycle.of(field).stampedOnUpdate) return name;
80
- }
81
- return undefined;
82
- }
@@ -1,5 +1,5 @@
1
1
  import { upperFirst, type EntityConstructor } from '@fougere/schema';
2
- import type { Storage, ListOptions, ListResult, SelectOption } from '../storage.js';
2
+ import type { Storage, ListOptions, ListResult, SelectOption } from '../storage/port.js';
3
3
  import type { SchemaView } from '@fougere/schema';
4
4
 
5
5
  /** Repository(…entities) — who owns an entity's storage, and where its questions are named. */
@@ -1,4 +1,4 @@
1
- import type { Fronds } from '../descriptor/Fronds.js';
1
+ import type { Fronds } from './descriptor/Fronds.js';
2
2
 
3
3
  /** Result of scanning a project directory. */
4
4
  /** Something the scan could NOT do — recorded instead of swallowed. */
package/src/source.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Registry, Role, type SchemaView } from '@fougere/schema';
2
- import type { StorageFactory } from './storage.js';
2
+ import type { StorageFactory } from './storage/port.js';
3
3
 
4
4
  /** A promise the judge at the door cannot keep, because it is about rows it never sees. */
5
5
  export type Constraint = 'unique';
@@ -35,8 +35,8 @@ export type ComparisonName = (typeof COMPARISONS)[number];
35
35
  */
36
36
  export function comparisonOf(field: Field | undefined, asked: unknown): Comparison | undefined {
37
37
  if (asked === null || typeof asked !== 'object' || Array.isArray(asked)) return undefined;
38
- // `Shapes.of` answers with `null` already taken out — `optional(json())` is an object.
39
- if (!field || Shapes.of(field.shape).base?.type === 'object') return undefined;
38
+ // The type is read off the base, so `optional(json())` still answers `object`.
39
+ if (!field || Shapes.typeOf(field.shape) === 'object') return undefined;
40
40
 
41
41
  return asked as Comparison;
42
42
  }
@@ -45,15 +45,30 @@ const LIST_OPTION_KEYS = [
45
45
  'limit', 'offset', 'page', 'after', 'orderBy', 'order', 'count', 'where', 'select',
46
46
  ] as const;
47
47
 
48
- /** Refuse an option the port does not answer to, naming it and what was expected. */
49
- export function assertListOptions(options: object | undefined, entity: string): void {
48
+ /**
49
+ * Refuse an option the port does not answer to, and an order by a field the entity does not
50
+ * declare — an unhonoured `orderBy` returns the page in whatever order the engine chose.
51
+ */
52
+ export function assertListOptions(
53
+ options: object | undefined,
54
+ entity: string,
55
+ declared: readonly string[],
56
+ ): void {
50
57
  if (!options) return;
51
58
  const legal = new Set<string>(LIST_OPTION_KEYS);
52
59
  const strangers = Object.keys(options).filter((key) => !legal.has(key));
53
- if (strangers.length === 0) return;
60
+ if (strangers.length > 0) {
61
+ throw new Error(
62
+ `${entity}.list(): unknown option ${strangers.map((s) => `\`${s}\``).join(', ')}. ` +
63
+ `Known options are ${LIST_OPTION_KEYS.join(', ')} — to filter, pass \`where: { ${strangers[0]}: … }\`.`,
64
+ );
65
+ }
66
+
67
+ const { orderBy } = options as ListOptions;
68
+ if (orderBy === undefined || declared.includes(orderBy)) return;
69
+
54
70
  throw new Error(
55
- `${entity}.list(): unknown option ${strangers.map((s) => `\`${s}\``).join(', ')}. ` +
56
- `Known options are ${LIST_OPTION_KEYS.join(', ')} — to filter, pass \`where: { ${strangers[0]}: … }\`.`,
71
+ `${entity}.list(): unknown orderBy \`${orderBy}\`. ${entity} declares ${declared.join(', ')}.`,
57
72
  );
58
73
  }
59
74