@fougere/core 0.5.0-alpha.0 → 0.6.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 (229) hide show
  1. package/dist/boot/Emissions.d.ts +2 -2
  2. package/dist/boot/Emissions.d.ts.map +1 -1
  3. package/dist/boot/Emissions.js +1 -1
  4. package/dist/boot/HandlerFacade.d.ts +1 -1
  5. package/dist/boot/HandlerFacade.d.ts.map +1 -1
  6. package/dist/boot/HandlerFacade.js +1 -1
  7. package/dist/boot/HandlerFacade.js.map +1 -1
  8. package/dist/boot/auth.d.ts +6 -6
  9. package/dist/boot/auth.d.ts.map +1 -1
  10. package/dist/boot/boot.d.ts +2 -2
  11. package/dist/boot/boot.d.ts.map +1 -1
  12. package/dist/boot/boot.js +1 -1
  13. package/dist/boot/boot.js.map +1 -1
  14. package/dist/boot/bootstrap.d.ts.map +1 -1
  15. package/dist/boot/bootstrap.js +47 -38
  16. package/dist/boot/bootstrap.js.map +1 -1
  17. package/dist/boot/card.d.ts +21 -0
  18. package/dist/boot/card.d.ts.map +1 -0
  19. package/dist/boot/card.js +100 -0
  20. package/dist/boot/card.js.map +1 -0
  21. package/dist/boot/frame.d.ts +3 -3
  22. package/dist/boot/frame.d.ts.map +1 -1
  23. package/dist/boot/frame.js +7 -7
  24. package/dist/boot/frame.js.map +1 -1
  25. package/dist/boot/hosted.d.ts +8 -2
  26. package/dist/boot/hosted.d.ts.map +1 -1
  27. package/dist/boot/hosted.js +10 -10
  28. package/dist/boot/hosted.js.map +1 -1
  29. package/dist/boot/ownership.d.ts +7 -5
  30. package/dist/boot/ownership.d.ts.map +1 -1
  31. package/dist/boot/ownership.js +9 -14
  32. package/dist/boot/ownership.js.map +1 -1
  33. package/dist/boot/ports.d.ts +2 -2
  34. package/dist/boot/ports.d.ts.map +1 -1
  35. package/dist/boot/ports.js +1 -1
  36. package/dist/boot/runner.d.ts +28 -0
  37. package/dist/boot/runner.d.ts.map +1 -0
  38. package/dist/boot/runner.js +26 -0
  39. package/dist/boot/runner.js.map +1 -0
  40. package/dist/boot/seed.d.ts +2 -2
  41. package/dist/boot/seed.d.ts.map +1 -1
  42. package/dist/boot/seed.js +5 -5
  43. package/dist/boot/seed.js.map +1 -1
  44. package/dist/boot/statement-drift.d.ts +2 -1
  45. package/dist/boot/statement-drift.d.ts.map +1 -1
  46. package/dist/boot/statement-drift.js.map +1 -1
  47. package/dist/boot/together.d.ts +4 -4
  48. package/dist/boot/together.d.ts.map +1 -1
  49. package/dist/boot/together.js +17 -19
  50. package/dist/boot/together.js.map +1 -1
  51. package/dist/boot/types.d.ts +12 -11
  52. package/dist/boot/types.d.ts.map +1 -1
  53. package/dist/contract/CallLog.d.ts +2 -2
  54. package/dist/contract/CallLog.d.ts.map +1 -1
  55. package/dist/contract/RouteAddress.d.ts +2 -0
  56. package/dist/contract/RouteAddress.d.ts.map +1 -1
  57. package/dist/contract/RouteAddress.js.map +1 -1
  58. package/dist/{scan/declare.d.ts → declare.d.ts} +3 -3
  59. package/dist/declare.d.ts.map +1 -0
  60. package/dist/{scan/declare.js → declare.js} +3 -3
  61. package/dist/declare.js.map +1 -0
  62. package/dist/{scan → descriptor}/Fronds.d.ts +6 -7
  63. package/dist/descriptor/Fronds.d.ts.map +1 -0
  64. package/dist/{scan → descriptor}/Fronds.js +6 -7
  65. package/dist/descriptor/Fronds.js.map +1 -0
  66. package/dist/{scan → descriptor}/frond.d.ts +7 -47
  67. package/dist/descriptor/frond.d.ts.map +1 -0
  68. package/dist/{scan → descriptor}/frond.js.map +1 -1
  69. package/dist/dispatch/ArgumentResolver.d.ts +5 -2
  70. package/dist/dispatch/ArgumentResolver.d.ts.map +1 -1
  71. package/dist/dispatch/ArgumentResolver.js +48 -3
  72. package/dist/dispatch/ArgumentResolver.js.map +1 -1
  73. package/dist/dispatch/DispatchEvent.d.ts +1 -1
  74. package/dist/dispatch/DispatchEvent.d.ts.map +1 -1
  75. package/dist/dispatch/PresenterArgumentResolver.d.ts +1 -1
  76. package/dist/dispatch/PresenterArgumentResolver.d.ts.map +1 -1
  77. package/dist/dispatch/PresenterArgumentResolver.js +1 -1
  78. package/dist/dispatch/Route.d.ts +1 -2
  79. package/dist/dispatch/Route.d.ts.map +1 -1
  80. package/dist/dispatch/StorageGuard.d.ts +2 -2
  81. package/dist/dispatch/StorageGuard.d.ts.map +1 -1
  82. package/dist/dispatch/StorageGuard.js +7 -7
  83. package/dist/dispatch/StorageGuard.js.map +1 -1
  84. package/dist/effective-operation.d.ts +29 -3
  85. package/dist/effective-operation.d.ts.map +1 -1
  86. package/dist/effective-operation.js +52 -1
  87. package/dist/effective-operation.js.map +1 -1
  88. package/dist/emit.d.ts +1 -1
  89. package/dist/frond-config.d.ts +2 -2
  90. package/dist/graph.d.ts +1 -1
  91. package/dist/graph.d.ts.map +1 -1
  92. package/dist/imports.d.ts +1 -1
  93. package/dist/imports.d.ts.map +1 -1
  94. package/dist/index.d.ts +15 -10
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +9 -6
  97. package/dist/index.js.map +1 -1
  98. package/dist/node.d.ts +2 -2
  99. package/dist/node.d.ts.map +1 -1
  100. package/dist/node.js +2 -2
  101. package/dist/node.js.map +1 -1
  102. package/dist/prefab/collector.d.ts +2 -2
  103. package/dist/prefab/collector.js +2 -2
  104. package/dist/prefab/crud.d.ts +5 -5
  105. package/dist/prefab/crud.d.ts.map +1 -1
  106. package/dist/prefab/crud.js +12 -12
  107. package/dist/prefab/crud.js.map +1 -1
  108. package/dist/prefab/mirror.d.ts +5 -5
  109. package/dist/prefab/mirror.d.ts.map +1 -1
  110. package/dist/prefab/mirror.js +5 -5
  111. package/dist/prefab/mirror.js.map +1 -1
  112. package/dist/prefab/presenter.d.ts +2 -2
  113. package/dist/prefab/presenter.js +2 -2
  114. package/dist/prefab/repository.d.ts +14 -14
  115. package/dist/prefab/repository.d.ts.map +1 -1
  116. package/dist/prefab/repository.js +19 -19
  117. package/dist/prefab/repository.js.map +1 -1
  118. package/dist/rows.d.ts +40 -0
  119. package/dist/rows.d.ts.map +1 -0
  120. package/dist/rows.js +146 -0
  121. package/dist/rows.js.map +1 -0
  122. package/dist/scan/conventions.d.ts +8 -12
  123. package/dist/scan/conventions.d.ts.map +1 -1
  124. package/dist/scan/conventions.js +0 -13
  125. package/dist/scan/conventions.js.map +1 -1
  126. package/dist/scan/emit.d.ts +1 -1
  127. package/dist/scan/emit.d.ts.map +1 -1
  128. package/dist/scan/emit.js +1 -1
  129. package/dist/scan/emit.js.map +1 -1
  130. package/dist/scan/handler-parser.d.ts +11 -56
  131. package/dist/scan/handler-parser.d.ts.map +1 -1
  132. package/dist/scan/handler-parser.js +2 -2
  133. package/dist/scan/handler-parser.js.map +1 -1
  134. package/dist/scan/result.d.ts +41 -0
  135. package/dist/scan/result.d.ts.map +1 -0
  136. package/dist/scan/result.js +2 -0
  137. package/dist/scan/result.js.map +1 -0
  138. package/dist/scan/scanner.d.ts +18 -1
  139. package/dist/scan/scanner.d.ts.map +1 -1
  140. package/dist/scan/scanner.js +34 -17
  141. package/dist/scan/scanner.js.map +1 -1
  142. package/dist/scan/statement.d.ts +1 -1
  143. package/dist/scan/statement.d.ts.map +1 -1
  144. package/dist/scan/statement.js.map +1 -1
  145. package/dist/{orm.d.ts → storage.d.ts} +98 -16
  146. package/dist/storage.d.ts.map +1 -0
  147. package/dist/{orm.js → storage.js} +53 -5
  148. package/dist/storage.js.map +1 -0
  149. package/dist/verify.d.ts +1 -1
  150. package/dist/verify.d.ts.map +1 -1
  151. package/dist/verify.js +2 -2
  152. package/dist/verify.js.map +1 -1
  153. package/dist/{boot → wire}/binding.d.ts +2 -10
  154. package/dist/wire/binding.d.ts.map +1 -0
  155. package/dist/{boot → wire}/binding.js +0 -52
  156. package/dist/wire/binding.js.map +1 -0
  157. package/dist/wire/call.d.ts +2 -40
  158. package/dist/wire/call.d.ts.map +1 -1
  159. package/dist/wire/call.js +0 -125
  160. package/dist/wire/call.js.map +1 -1
  161. package/dist/wire/operation.d.ts +5 -31
  162. package/dist/wire/operation.d.ts.map +1 -1
  163. package/dist/wire/operation.js +0 -51
  164. package/dist/wire/operation.js.map +1 -1
  165. package/dist/wire/signature.d.ts +58 -0
  166. package/dist/wire/signature.d.ts.map +1 -0
  167. package/dist/wire/signature.js +2 -0
  168. package/dist/wire/signature.js.map +1 -0
  169. package/package.json +3 -3
  170. package/src/boot/Emissions.ts +2 -2
  171. package/src/boot/HandlerFacade.ts +3 -3
  172. package/src/boot/auth.ts +6 -6
  173. package/src/boot/boot.ts +3 -3
  174. package/src/boot/bootstrap.ts +50 -39
  175. package/src/boot/card.ts +113 -0
  176. package/src/boot/frame.ts +7 -7
  177. package/src/boot/hosted.ts +11 -14
  178. package/src/boot/ownership.ts +14 -15
  179. package/src/boot/ports.ts +2 -2
  180. package/src/boot/runner.ts +34 -0
  181. package/src/boot/seed.ts +6 -6
  182. package/src/boot/statement-drift.ts +5 -4
  183. package/src/boot/together.ts +24 -25
  184. package/src/boot/types.ts +12 -11
  185. package/src/contract/CallLog.ts +2 -2
  186. package/src/contract/RouteAddress.ts +3 -0
  187. package/src/{scan/declare.ts → declare.ts} +5 -5
  188. package/src/{scan → descriptor}/Fronds.ts +6 -7
  189. package/src/{scan → descriptor}/frond.ts +7 -48
  190. package/src/dispatch/ArgumentResolver.ts +53 -7
  191. package/src/dispatch/DispatchEvent.ts +1 -1
  192. package/src/dispatch/PresenterArgumentResolver.ts +2 -2
  193. package/src/dispatch/Route.ts +1 -3
  194. package/src/dispatch/StorageGuard.ts +7 -7
  195. package/src/effective-operation.ts +63 -11
  196. package/src/emit.ts +1 -1
  197. package/src/frond-config.ts +2 -2
  198. package/src/graph.ts +1 -1
  199. package/src/imports.ts +1 -1
  200. package/src/index.ts +15 -13
  201. package/src/node.ts +2 -2
  202. package/src/prefab/collector.ts +2 -2
  203. package/src/prefab/crud.ts +18 -18
  204. package/src/prefab/mirror.ts +8 -8
  205. package/src/prefab/presenter.ts +2 -2
  206. package/src/prefab/repository.ts +32 -32
  207. package/src/rows.ts +155 -0
  208. package/src/scan/conventions.ts +0 -18
  209. package/src/scan/emit.ts +3 -5
  210. package/src/scan/handler-parser.ts +14 -67
  211. package/src/scan/result.ts +42 -0
  212. package/src/scan/scanner.ts +44 -21
  213. package/src/scan/statement.ts +2 -1
  214. package/src/{orm.ts → storage.ts} +126 -17
  215. package/src/verify.ts +3 -3
  216. package/src/{boot → wire}/binding.ts +2 -66
  217. package/src/wire/call.ts +1 -129
  218. package/src/wire/operation.ts +5 -62
  219. package/src/wire/signature.ts +59 -0
  220. package/dist/boot/binding.d.ts.map +0 -1
  221. package/dist/boot/binding.js.map +0 -1
  222. package/dist/orm.d.ts.map +0 -1
  223. package/dist/orm.js.map +0 -1
  224. package/dist/scan/Fronds.d.ts.map +0 -1
  225. package/dist/scan/Fronds.js.map +0 -1
  226. package/dist/scan/declare.d.ts.map +0 -1
  227. package/dist/scan/declare.js.map +0 -1
  228. package/dist/scan/frond.d.ts.map +0 -1
  229. /package/dist/{scan → descriptor}/frond.js +0 -0
package/src/boot/boot.ts CHANGED
@@ -32,12 +32,12 @@ interface BootOptions {
32
32
  */
33
33
  onEmit?: CreateAppOptions['onEmit'];
34
34
  /**
35
- * ORM setup — returns the storage handle (db), an ormFactory, and its two halves.
35
+ * storage setup — returns the storage handle (db), an storageFactory, and its two halves.
36
36
  * The `db` value is forwarded to the auth provider via AuthContext when `auth` is set.
37
37
  */
38
38
  db?: (config: FougereConfig) => {
39
39
  db?: unknown;
40
- ormFactory: CreateAppOptions['ormFactory'];
40
+ storageFactory: CreateAppOptions['storageFactory'];
41
41
  /**
42
42
  * Bring the schema up to date — an extension's `up`, because it runs after the
43
43
  * container. It used to be called `afterBoot`, a word that also meant the host's
@@ -88,7 +88,7 @@ export async function boot(options: BootOptions): Promise<App> {
88
88
  // handed the answer and reaches for nothing.
89
89
  scan: await scanProject(root, options.fronds, config.conventions),
90
90
  createContainer: options.createContainer,
91
- ormFactory: dbSetup?.ormFactory,
91
+ storageFactory: dbSetup?.storageFactory,
92
92
  db: dbSetup?.db,
93
93
  auth: config.auth,
94
94
  remotes: options.remotes,
@@ -1,6 +1,6 @@
1
1
  import type { Container } from '@fougere/container';
2
2
  import { lowerFirst, type SchemaView } from '@fougere/schema';
3
- import type { EntityEntry, HandlerEntry, PresenterEntry } from '../scan/frond.js';
3
+ import type { EntityEntry, HandlerEntry, PresenterEntry } from '../descriptor/frond.js';
4
4
  import { hostedBy } from './hosted.js';
5
5
  import type { AuthRuntime } from './auth.js';
6
6
  import type { CreateAppOptions, App } from './types.js';
@@ -12,7 +12,7 @@ import { registerFrames } from './together.js';
12
12
  import { Emissions } from './Emissions.js';
13
13
  import { HandlerFacade } from './HandlerFacade.js';
14
14
  import { targetOf } from '../prefab/prefab.js';
15
- import { ownersOf, refuseOrmInUserCode, refuseCrudOnOwned } from './ownership.js';
15
+ import { ownersOf, refuseStorageInUserCode, refuseCrudOnOwned } from './ownership.js';
16
16
  import type { OperationContract, OperationsMap } from '../wire/operation.js';
17
17
  import {
18
18
  resolveEffectiveOperations,
@@ -22,10 +22,11 @@ import { StorageGuard } from '../dispatch/StorageGuard.js';
22
22
  import { portBindings } from './ports.js';
23
23
  import { InFlight } from '../dispatch/InFlight.js';
24
24
  // The keys, each read from where its concept is declared — never respelled here.
25
- import { facadeKeyOf, contractsKeyOf, identityCardOf, type RpcAnswer } from '../wire/call.js';
25
+ import { facadeKeyOf, contractsKeyOf, type RpcAnswer } from '../wire/call.js';
26
+ import { identityCardOf } from './card.js';
26
27
  import { AppLifecycle } from './AppLifecycle.js';
27
28
  import { repositoryKeyOf } from '../prefab/repository.js';
28
- import { ormKeyOf } from '../orm.js';
29
+ import { storageKeyOf } from '../storage.js';
29
30
  import { presenterKeyOf } from '../prefab/presenter.js';
30
31
  import { collectorKeyOf } from '../prefab/collector.js';
31
32
  import { RouteAddress } from '../contract/RouteAddress.js';
@@ -112,6 +113,16 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
112
113
  // no builtin and a Worker can run what it builds.
113
114
  const scanStart = performance.now();
114
115
  const { fronds, diagnostics } = await hostedBy(options);
116
+ // An app that states nothing AND scans nothing is a mistake — unless something else it
117
+ // declares brings its own entities, which an auth provider does. Refused here and not in
118
+ // `hostedBy`, which is handed the frond sources and cannot see the rest of the app. The
119
+ // condition is the KEYS, not the count: a scan that found nothing is an ordinary answer.
120
+ if (!options.fronds && !options.scan && !options.auth) {
121
+ throw new Error(
122
+ 'createApp needs `fronds:` (what this app states) or `scan:` (what a scanner found). '
123
+ + 'Neither was given, and nothing else declares entities of its own.',
124
+ );
125
+ }
115
126
  const operationModel = resolveEffectiveOperations(fronds, {
116
127
  diagnostics,
117
128
  remotes: options.remotes,
@@ -152,11 +163,11 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
152
163
 
153
164
  // Auth runtime — built once from the lazy AuthConfig produced by a provider factory
154
165
  // (e.g. betterAuth({...})) in fougere.config.ts. The provider receives our db +
155
- // ormFactory so all auth writes flow through EntityOrm.
166
+ // storageFactory so all auth writes flow through Storage.
156
167
  let authRuntime: AuthRuntime | undefined;
157
168
  if (options.auth) {
158
- if (!options.ormFactory) {
159
- throw new Error('createApp: `auth` is set but `ormFactory` is missing — auth providers need it to back their adapter.');
169
+ if (!options.storageFactory) {
170
+ throw new Error('createApp: `auth` is set but `storageFactory` is missing — auth providers need it to back their adapter.');
160
171
  }
161
172
  if (options.db === undefined) {
162
173
  throw new Error('createApp: `auth` is set but `db` is missing — pass the storage handle through CreateAppOptions.db.');
@@ -164,7 +175,7 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
164
175
  log.info('initializing auth runtime');
165
176
  authRuntime = await options.auth.create({
166
177
  db: options.db,
167
- ormFactory: options.ormFactory,
178
+ storageFactory: options.storageFactory,
168
179
  });
169
180
  log.info(`auth ready — mounted at ${authRuntime.basePath}`);
170
181
  }
@@ -254,14 +265,14 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
254
265
  // `reads:` is what makes a cross-source reader exist here, and the list IS its
255
266
  // environment — a source holding none of these is never opened. Registered under
256
267
  // the type's own name, which is the key `depKeyOf` already derives for a plain
257
- // parameter: `constructor(private sources: Sources)` and nothing else to say.
268
+ // parameter: `constructor(private reads: Reads)` and nothing else to say.
258
269
  // Declaring `reads:` with nothing to build the reader is a boot that ignores a
259
- // clause: the handler asking for `Sources` then dies at its first call, on a
270
+ // clause: the handler asking for `Reads` then dies at its first call, on a
260
271
  // container message that names neither the clause nor what is missing.
261
272
  if (frond.reads?.length && !options.sourcesFactory) {
262
273
  frondLog.warn(
263
274
  `[reads] ${frond.reads.join(', ')} — declared in frond.config.ts, but this boot passes no `
264
- + '`sourcesFactory`, so no reader is registered and a handler asking for `Sources` will fail '
275
+ + '`sourcesFactory`, so no reader is registered and a handler asking for `Reads` will fail '
265
276
  + 'at its first call. Pass one (`@fougere/adapter-duckdb`), or drop the clause.',
266
277
  );
267
278
  }
@@ -280,14 +291,14 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
280
291
  + 'so a query naming one would find no table. Check the spelling, or the entity file.',
281
292
  );
282
293
  }
283
- scope.registerValue('Sources', await options.sourcesFactory(named, frond.name));
294
+ scope.registerValue('Reads', await options.sourcesFactory(named, frond.name));
284
295
  frondLog.debug(`cross-source reader over ${named.length} entit(ies)`);
285
296
  }
286
297
 
287
298
  // Who owns what, and the rule that makes owning mean something. Before anything is
288
299
  // registered, so a bad line is named by this refusal rather than by the container's.
289
300
  const owners = ownersOf(frond.providers);
290
- refuseOrmInUserCode(frond, owners);
301
+ refuseStorageInUserCode(frond, owners, (entity) => entityByName.has(entity));
291
302
  refuseCrudOnOwned(frond, owners);
292
303
 
293
304
  for (const provider of frond.providers) {
@@ -306,28 +317,28 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
306
317
  frondLog.debug(`${frond.providers.length} provider(s): ${frond.providers.map((p) => p.ctor.name).join(', ')}`);
307
318
  }
308
319
 
309
- // Register EntityOrm for each entity — PascalCase type name (e.g. 'PostOrm')
310
- // When a handler declares Crud(Entity, Output), scope the ORM via .output(Output)
311
- if (options.ormFactory) {
320
+ // Register Storage for each entity — PascalCase type name (e.g. 'PostStorage')
321
+ // When a handler declares Crud(Entity, Output), scope the storage via .output(Output)
322
+ if (options.storageFactory) {
312
323
  for (const entity of frond.entities) {
313
- const ormName = ormKeyOf(entity.name);
314
- const baseOrm = options.ormFactory(entity.entityClass, entity.name);
324
+ const key = storageKeyOf(entity.name);
325
+ const baseStorage = options.storageFactory(entity.entityClass, entity.name);
315
326
 
316
327
  // Check if the default handler (no surface) declares an output override
317
328
  const defaultHandler = frond.handlers.find((h) => h.address === entity.name && !h.surface);
318
329
  const outputSchema = defaultHandler?.outputOverride ?? (defaultHandler?.ctor as any)?.__output;
319
330
  const scoped = outputSchema && outputSchema !== entity.entityClass
320
- ? baseOrm.output(outputSchema)
321
- : baseOrm;
331
+ ? baseStorage.output(outputSchema)
332
+ : baseStorage;
322
333
 
323
334
  // Storage is a way out like the client surface — see `StorageGuard`.
324
335
  const guarded = new StorageGuard(entity.entityClass.getFields(), entity.name).guard(scoped);
325
- scope.registerValue(ormName, guarded);
336
+ scope.registerValue(key, guarded);
326
337
 
327
338
  // The default repository IS the guarded port — it already answers every gesture a
328
339
  // declared one forwards, so the two forms have the same shape and a handler reads
329
- // `repo.list()` either way. The wrapper that used to sit here (`{ orm: guarded }`)
330
- // existed to make `repo.orm` true in both, back when `.orm` was the way in.
340
+ // `repo.list()` either way. The wrapper that used to sit here (`{ storage: guarded }`)
341
+ // existed to make `repo.storage` true in both, back when `.storage` was the way in.
331
342
  //
332
343
  // Not registered for an OWNED entity: an aggregate's members are reached through it
333
344
  // and nowhere else, and the default would be a second door under a name a handler
@@ -342,7 +353,7 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
342
353
  }
343
354
  }
344
355
  if (frond.entities.length > 0) {
345
- frondLog.debug(`${frond.entities.length} entity ORM(s): ${frond.entities.map((e) => e.name).join(', ')}`);
356
+ frondLog.debug(`${frond.entities.length} entity storage(s): ${frond.entities.map((e) => e.name).join(', ')}`);
346
357
  }
347
358
  }
348
359
 
@@ -357,7 +368,7 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
357
368
  entityByName,
358
369
  frondOf,
359
370
  hostedHere: (name) => !(options.remotes && name in options.remotes),
360
- ormFactory: options.ormFactory,
371
+ storageFactory: options.storageFactory,
361
372
  sourceOf: options.sourceOf,
362
373
  transacted: options.transacted,
363
374
  log: frondLog,
@@ -485,7 +496,7 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
485
496
  const facadeKey = facadeKeyOf(handler.address);
486
497
  buildFacade(entity, handler, scope, facadeKey);
487
498
  frondLog.debug(`${facadeKey} [${Object.keys(container.resolve(facadeKey) as any).join(', ')}]`
488
- + (entity ? '' : ' — no entity of that name: no ORM, no projection, no presenter'));
499
+ + (entity ? '' : ' — no entity of that name: no storage, no projection, no presenter'));
489
500
  }
490
501
 
491
502
  // The dual, and it stays: a shape that declares no operation answers nothing. Said
@@ -496,7 +507,7 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
496
507
  }
497
508
  }
498
509
 
499
- // Surface handlers — create sub-scope per surface handler with scoped ORM
510
+ // Surface handlers — create sub-scope per surface handler with scoped storage
500
511
  //
501
512
  // Pointing at nothing is legal HERE TOO. This loop used to `continue` when no entity
502
513
  // carried the handler's name, so `handlers/public/SearchHandler.ts` with no `Search`
@@ -506,25 +517,25 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
506
517
  const entity = frond.entities.find((e) => e.name === handler.address);
507
518
  const surfaceScope = scope.createScope();
508
519
 
509
- // Register scoped ORM if output override differs from entity — under the REPOSITORY
520
+ // Register scoped storage if output override differs from entity — under the REPOSITORY
510
521
  // key, which is what a Crud handler asks for, and under the port's own for a holder
511
522
  // that legitimately names it. Registering only the latter left a named surface with
512
523
  // no door at all once the façade stopped spelling the storage.
513
- if (entity && options.ormFactory) {
514
- const baseOrm = options.ormFactory(entity.entityClass, entity.name);
524
+ if (entity && options.storageFactory) {
525
+ const baseStorage = options.storageFactory(entity.entityClass, entity.name);
515
526
  const outputSchema = handler.outputOverride ?? (handler.ctor as any).__output;
516
527
  const scoped = outputSchema && outputSchema !== entity.entityClass
517
- ? baseOrm.output(outputSchema)
518
- : baseOrm;
528
+ ? baseStorage.output(outputSchema)
529
+ : baseStorage;
519
530
  const guarded = new StorageGuard(entity.entityClass.getFields(), entity.name).guard(scoped);
520
- surfaceScope.registerValue(ormKeyOf(entity.name), guarded);
531
+ surfaceScope.registerValue(storageKeyOf(entity.name), guarded);
521
532
  surfaceScope.registerValue(repositoryKeyOf(entity.name), guarded);
522
533
  }
523
534
 
524
535
  const facadeKey = facadeKeyOf(handler.address, handler.surface);
525
536
  buildFacade(entity, handler, surfaceScope, facadeKey);
526
537
  frondLog.debug(`${facadeKey} [${Object.keys(container.resolve(facadeKey) as any).join(', ')}]`
527
- + (entity ? '' : ' — no entity of that name: no ORM, no projection, no presenter'));
538
+ + (entity ? '' : ' — no entity of that name: no storage, no projection, no presenter'));
528
539
  }
529
540
 
530
541
  // A named surface is closed, so what it contains is a fact worth stating.
@@ -746,15 +757,15 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
746
757
  * client-facing door. Both are ways in; an entity that opens none of them still has rows.
747
758
  *
748
759
  * It resolves through the owning frond's scope, because that is where entity ORMs live:
749
- * `resolve('UserOrm')` reads the ROOT container and never finds one, so callers outside
760
+ * `resolve('UserStorage')` reads the ROOT container and never finds one, so callers outside
750
761
  * the frond concluded there was no storage. The seed loop did exactly that, and skipped
751
762
  * every entity with no façade — the one case its own fallback existed for.
752
763
  */
753
- const ormFor = (entity: string): unknown | undefined => {
764
+ const storageFor = (entity: string): unknown | undefined => {
754
765
  const owner = fronds.owner(entity);
755
766
  if (!owner) return undefined;
756
767
 
757
- const key = ormKeyOf(entity);
768
+ const key = storageKeyOf(entity);
758
769
  try {
759
770
  return container.resolve<Container>(`frond:${owner.name}`).resolve(key);
760
771
  } catch {
@@ -765,7 +776,7 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
765
776
  /**
766
777
  * The presenter of an entity, resolved through its owning frond's scope.
767
778
  *
768
- * Same shape as `ormFor`, and it exists for the same reason: without it an adapter
779
+ * Same shape as `storageFor`, and it exists for the same reason: without it an adapter
769
780
  * has to spell the container key itself. `schema-graphql` did — a hand-written
770
781
  * `${Name}Presenter` — and a key respelled elsewhere finds nothing and says nothing
771
782
  * the day the convention moves, which is exactly the failure `verify.ts` refuses.
@@ -799,7 +810,7 @@ export async function createApp(options: CreateAppOptions): Promise<App> {
799
810
  operationsFor,
800
811
  listensTo: () => emissions.listensTo(),
801
812
  deliver: (fact, payload) => emissions.deliver(fact, payload),
802
- ormFor,
813
+ storageFor,
803
814
  presenterFor,
804
815
  dispose: release,
805
816
  drain,
@@ -0,0 +1,113 @@
1
+ /**
2
+ * What an app says about ITSELF — the `rpc.discover` answer, built from the app.
3
+ *
4
+ * The shape is `wire/`'s: it crosses a process boundary. Building one needs an App,
5
+ * which is why the function lives here and the shape does not.
6
+ */
7
+ import { Card } from '@fougere/schema';
8
+ import type { App } from './types.js';
9
+ import { factsAnnouncedBy } from '../emit.js';
10
+ import type { InvocationContext } from '../contract/Invocation.js';
11
+ import { facadeKeyOf, contractsKeyOf, type CardOp, type IdentityCard } from '../wire/call.js';
12
+ import type { OperationContract } from '../wire/operation.js';
13
+
14
+ type AnyFacade = Record<string, (invocation?: InvocationContext) => Promise<unknown>>;
15
+
16
+ /**
17
+ * Serialize what the app hosts, for one audience. Served on `rpc.discover`,
18
+ * cached by callers.
19
+ *
20
+ * Hosting means answering: an entity with no façade declares no operation, so
21
+ * it is not among the DOORS — publishing its shape would hand an anonymous caller
22
+ * the structure of tables it can never reach (the auth tables, typically), and
23
+ * `fougere sync` would rebuild a schema with nothing to call on it.
24
+ *
25
+ * `facts` is the one thing that shape-without-a-door rule does not cover, and the
26
+ * reason is the opposite of a leak: an entity nobody exposed is silent, whereas a
27
+ * fact was explicitly declared to leave.
28
+ *
29
+ * `surface` makes that sentence audience-aware rather than app-wide: a door
30
+ * answers with what IT serves. Nothing falls back to the full façade — under a
31
+ * named surface, an entity with no façade of its own is simply not there.
32
+ */
33
+ export function identityCardOf(app: App, surface?: string): IdentityCard {
34
+ const declared = app.fronds.schemas();
35
+
36
+ return {
37
+ fronds: app.fronds.map((frond) => {
38
+ // What the frond answers to, not what it stores. This walked `frond.entities`, so a
39
+ // handler carrying no entity — a health check, a search across shapes — was built,
40
+ // served, and absent from the card: `sync` could not generate its door and a remote
41
+ // consumer had no way to know it existed. The boot has said "pointing at nothing is
42
+ // legal" since handlers became the subject; the card had not caught up.
43
+ const byEntity = new Map(frond.entities.map((entity) => [entity.name, entity]));
44
+ const addresses = [...new Set([
45
+ ...frond.entities.map((entity) => entity.name),
46
+ ...frond.handlers.map((handler) => handler.address),
47
+ ])];
48
+
49
+ return {
50
+ name: frond.name,
51
+ doors: addresses.flatMap((address) => {
52
+ const ops = facadeOps(app, address, surface);
53
+ if (ops.length === 0) return [];
54
+ const entity = byEntity.get(address);
55
+ return [{
56
+ name: address,
57
+ ops,
58
+ // Absent when nothing of that name is stored. A door is still a door.
59
+ ...(entity ? { schema: Card.fromSchema(entity.entityClass, address).descriptor } : {}),
60
+ }];
61
+ }),
62
+ /**
63
+ * What leaves on its own — the same list on every surface, deliberately.
64
+ *
65
+ * A door has an audience; a fact does not. `Emit<T>` names a subject and *the
66
+ * number of readers is not the emitter's business* (`emit.ts`), so narrowing this
67
+ * by `surface` would invent an axis the primitive refuses. The consequence is
68
+ * stated rather than hidden: a fact's SHAPE is readable by anyone who can read the
69
+ * card at all. What must not be published must not be announced.
70
+ */
71
+ facts: factsAnnouncedBy(frond.handlers).map((name) => {
72
+ const entityClass = declared.get(name);
73
+ return { name, ...(entityClass ? { schema: Card.fromSchema(entityClass, name).descriptor } : {}) };
74
+ }),
75
+ };
76
+ }),
77
+ };
78
+ }
79
+
80
+ function facadeOps(app: App, entityName: string, surface?: string): CardOp[] {
81
+ let facade: AnyFacade;
82
+ try {
83
+ facade = app.container.resolve<AnyFacade>(facadeKeyOf(entityName, surface));
84
+ } catch {
85
+ return [];
86
+ }
87
+
88
+ // The façade is the list of names; the model is the resolved terms.
89
+ const effective = app.operationsFor(entityName, surface);
90
+ if (!effective) {
91
+ throw new Error(
92
+ `Facade '${facadeKeyOf(entityName, surface)}' exists without an effective operation table.`,
93
+ );
94
+ }
95
+
96
+ return Object.keys(facade).map((name) => {
97
+ const contract = effective.get(name);
98
+ if (!contract) {
99
+ throw new Error(
100
+ `Facade '${facadeKeyOf(entityName, surface)}' serves '${name}' without an effective contract.`,
101
+ );
102
+ }
103
+
104
+ return {
105
+ name,
106
+ ...(contract?.description && { description: contract.description }),
107
+ ...(contract?.input && { input: Card.fromSchema(contract.input, name).descriptor }),
108
+ ...(contract?.output && { output: Card.fromSchema(contract.output, name).descriptor }),
109
+ ...(contract?.cardinality && { cardinality: contract.cardinality }),
110
+ kind: contract.kind,
111
+ };
112
+ });
113
+ }
package/src/boot/frame.ts CHANGED
@@ -52,7 +52,7 @@ const pick = (row: Record<string, unknown>, keys: readonly string[]): Record<str
52
52
  *
53
53
  * The inverse of an upsert is derivable exactly when the conflict is the primary key: read
54
54
  * the rows by key first, then restore the ones that were there and delete the ones that
55
- * were not. `SqlEntityOrm` spells `onConflict(pk)` on SQLite and Postgres, so that holds —
55
+ * were not. `SqlStorage` spells `onConflict(pk)` on SQLite and Postgres, so that holds —
56
56
  * but MySQL's `onDuplicateKeyUpdate` fires on ANY unique constraint, and a row that
57
57
  * conflicts on a unique email while carrying a different key would be restored under a key
58
58
  * the write never touched.
@@ -79,15 +79,15 @@ function refuseAmbiguousUpsert(entity: string, gesture: string): never {
79
79
  }
80
80
 
81
81
  /**
82
- * The ORM a member is handed inside a compensated frame: the same port, writing the same
82
+ * The storage a member is handed inside a compensated frame: the same port, writing the same
83
83
  * rows, leaving an inverse behind each time.
84
84
  *
85
- * `Object.create` for the same reason `StorageGuard` uses it — the ORM keeps every gesture
85
+ * `Object.create` for the same reason `StorageGuard` uses it — the storage keeps every gesture
86
86
  * it had, including the ones this knows nothing about.
87
87
  */
88
- export function recording<T extends object>(orm: T, entity: string, fields: Fields, journal: Undo[]): T {
89
- const base = orm as unknown as Undoable;
90
- if (typeof base.create !== 'function' || typeof base.update !== 'function') return orm;
88
+ export function recording<T extends object>(storage: T, entity: string, fields: Fields, journal: Undo[]): T {
89
+ const base = storage as unknown as Undoable;
90
+ if (typeof base.create !== 'function' || typeof base.update !== 'function') return storage;
91
91
 
92
92
  const key = FieldSet.of(fields).primary;
93
93
  if (!key) {
@@ -97,7 +97,7 @@ export function recording<T extends object>(orm: T, entity: string, fields: Fiel
97
97
  );
98
98
  }
99
99
 
100
- const recorded = Object.create(orm) as T & Undoable;
100
+ const recorded = Object.create(storage) as T & Undoable;
101
101
 
102
102
  recorded.create = async function (...args) {
103
103
  const row = await base.create.apply(this, args);
@@ -16,8 +16,9 @@
16
16
  * because the point of stating is to decide. Empty is what "I named none" looks like, and
17
17
  * `frond()` builds every member, so an empty one is filled rather than enforced.
18
18
  */
19
- import type { FrondDescriptor, ScanResult } from '../scan/frond.js';
20
- import { Fronds } from '../scan/Fronds.js';
19
+ import type { FrondDescriptor } from '../descriptor/frond.js';
20
+ import type { ScanResult } from '../scan/result.js';
21
+ import { Fronds } from '../descriptor/Fronds.js';
21
22
 
22
23
  /** Where an app's fronds come from — either, or both. */
23
24
  export interface HostedSources {
@@ -52,26 +53,22 @@ function completed(stated: FrondDescriptor, found: FrondDescriptor | undefined):
52
53
  };
53
54
  }
54
55
 
55
- /** What the app hosts, and what the scan — if it ran — could not do. */
56
+ /**
57
+ * What the app hosts, and what the scan — if it ran — could not do.
58
+ *
59
+ * Neither key is EMPTY rather than refused: whether an app with no frond is legitimate
60
+ * depends on what else it declares, and this function is handed none of that.
61
+ */
56
62
  export async function hostedBy(sources: HostedSources): Promise<ScanResult> {
57
63
  const scanned = sources.scan
58
64
  ? await (typeof sources.scan === 'function' ? sources.scan() : sources.scan)
59
65
  : undefined;
60
66
 
61
- if (!sources.fronds) {
62
- if (!scanned) {
63
- throw new Error(
64
- 'createApp needs `fronds:` (what this app states) or `scan:` (what a scanner found). '
65
- + 'Neither was given, so nothing is hosted.',
66
- );
67
- }
68
-
69
- return scanned;
70
- }
67
+ if (!sources.fronds) return scanned ?? { fronds: Fronds.hosting([]), diagnostics: [] };
71
68
 
72
69
  const found = scanned?.fronds ?? [];
73
70
  const stated = sources.fronds.map((f) => completed(f, found.find((s) => s.name === f.name)));
74
71
  const unstated = found.filter((f) => !sources.fronds!.some((s) => s.name === f.name));
75
72
 
76
- return { fronds: Fronds.scanned([...stated, ...unstated]), diagnostics: scanned?.diagnostics ?? [] };
73
+ return { fronds: Fronds.hosting([...stated, ...unstated]), diagnostics: scanned?.diagnostics ?? [] };
77
74
  }
@@ -1,8 +1,8 @@
1
1
  import { lowerFirst } from '@fougere/schema';
2
- import type { FrondDescriptor, ProviderEntry } from '../scan/frond.js';
2
+ import type { FrondDescriptor, ProviderEntry } from '../descriptor/frond.js';
3
3
  import { targetOf } from '../prefab/prefab.js';
4
4
  import { ownedBy, repositoryKeyOf } from '../prefab/repository.js';
5
- import { ormKeyOf } from '../orm.js';
5
+ import { entityOfStorageKey } from '../storage.js';
6
6
 
7
7
  /**
8
8
  * Who owns an entity's storage.
@@ -37,7 +37,7 @@ export function ownersOf(providers: readonly ProviderEntry[]): Map<string, strin
37
37
 
38
38
  /**
39
39
  * The entities a class was BUILT ON — what a prefab may legitimately be handed the storage
40
- * of. Empty for a plain service, which is why one cannot ask for an ORM at all.
40
+ * of. Empty for a plain service, which is why one cannot ask for a storage at all.
41
41
  */
42
42
  function builtOn(ctor: unknown): string[] {
43
43
  const owned = ownedBy(ctor);
@@ -47,19 +47,25 @@ function builtOn(ctor: unknown): string[] {
47
47
  }
48
48
 
49
49
  /**
50
- * `EntityOrm<E>` is not a word of the user's vocabulary — `<E>Repository` is the one way in.
50
+ * `Storage<E>` is not a word of the user's vocabulary — `<E>Repository` is the one way in.
51
51
  *
52
52
  * A door judges and projects; a HOLDER keeps the storage. So the rule is not about which
53
53
  * directory a file sits in but about which of the two a class is: a handler, a presenter and
54
- * a collector may never name an ORM, and a provider may name only the ORM of what its prefab
55
- * was built on — which is `Repository` and `Mirror`, without either being named here.
54
+ * a collector may never name the port, and a provider may name only the storage of what its
55
+ * prefab was built on — which is `Repository` and `Mirror`, without either being named here.
56
56
  *
57
57
  * What it buys beyond one word instead of two: an entity that becomes owned later needs no
58
58
  * call site to move, because none of them ever spelled the storage.
59
59
  *
60
+ * `known` answers what the key's suffix cannot: `FileStorage` is an ordinary class name.
61
+ *
60
62
  * Refused at boot rather than reported by `verify`, which nothing calls at startup.
61
63
  */
62
- export function refuseOrmInUserCode(frond: FrondDescriptor, owners: Map<string, string>): void {
64
+ export function refuseStorageInUserCode(
65
+ frond: FrondDescriptor,
66
+ owners: Map<string, string>,
67
+ known: (entity: string) => boolean,
68
+ ): void {
63
69
  const doors = [
64
70
  ...frond.handlers.map((h) => ({ ...h, kind: 'handler' })),
65
71
  ...frond.presenters.map((p) => ({ ...p, kind: 'presenter' })),
@@ -70,7 +76,7 @@ export function refuseOrmInUserCode(frond: FrondDescriptor, owners: Map<string,
70
76
  for (const decl of [...doors, ...holders]) {
71
77
  const allowed = decl.kind === 'provider' ? builtOn(decl.ctor) : [];
72
78
  for (const dep of decl.deps) {
73
- const entity = entityOfOrmKey(dep);
79
+ const entity = entityOfStorageKey(dep, known);
74
80
  if (!entity) continue;
75
81
 
76
82
  const owner = owners.get(entity);
@@ -128,10 +134,3 @@ export function refuseCrudOnOwned(frond: FrondDescriptor, owners: Map<string, st
128
134
  );
129
135
  }
130
136
  }
131
-
132
- /** `'PostOrm'` → `'post'`, and nothing for a key that is not one. */
133
- function entityOfOrmKey(dep: string): string | undefined {
134
- if (!dep.endsWith('Orm') || dep === 'Orm') return undefined;
135
- const entity = lowerFirst(dep.slice(0, -'Orm'.length));
136
- return ormKeyOf(entity) === dep ? entity : undefined;
137
- }
package/src/boot/ports.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ProviderEntry } from '../scan/frond.js';
1
+ import type { ProviderEntry } from '../descriptor/frond.js';
2
2
 
3
3
  /**
4
4
  * A port is a class something already answers under. Nothing declares one — the
@@ -6,7 +6,7 @@ import type { ProviderEntry } from '../scan/frond.js';
6
6
  * registration, and so is `class MyLogger extends Logger` over a builtin.
7
7
  *
8
8
  * This is the fourth reading of the rule the other three already apply: the type
9
- * names the SUBJECT (`EntityOrm<E>`, `Emit<F>`, `Facade<H>`) and the container
9
+ * names the SUBJECT (`Storage<E>`, `Emit<F>`, `Facade<H>`) and the container
10
10
  * holds the realization. Providers were the one case where the type named the
11
11
  * realization instead, so `constructor(private payment: Payment)` resolved to the
12
12
  * base class and a handler got an object whose method did not exist.
@@ -0,0 +1,34 @@
1
+ /**
2
+ * A Transport built from an app — the two ways a call enters this process.
3
+ *
4
+ * `Transport` is `wire/`'s shape; making one needs an App and the transport entry.
5
+ */
6
+ import type { App } from './types.js';
7
+ import type { Transport } from '../wire/call.js';
8
+ import { createTransportEntry } from '../entry/TransportEntry.js';
9
+
10
+ /**
11
+ * Build the local runner — the reference realization of Transport.
12
+ *
13
+ * Resolves strictly from the app's own container: a call that lands here is
14
+ * judged here. A miss is a typed NOT_FOUND, never a forward to another remote.
15
+ */
16
+ export function createLocalRunner(app: App, surface?: string): Transport {
17
+ return createTransportEntry(app.local, surface);
18
+ }
19
+
20
+ /**
21
+ * Build the app runner — same judgment, but resolution follows the app's
22
+ * topology: local façades and remote doublures alike. This is the runner
23
+ * an app's own entry points (browser endpoint, bridges) stand on.
24
+ *
25
+ * `surface` is the audience this runner serves, and it belongs to the DOOR that
26
+ * builds the runner, never to the call: a caller cannot name its own audience
27
+ * any more than it can name its own identity (`state` is stamped server-side
28
+ * for the same reason). Reaching the admin door IS the proof. So `FrondCall`
29
+ * gains nothing and the wire format gains nothing — a remote frond's audience
30
+ * is simply which URL `remotes:` points at.
31
+ */
32
+ export function createAppRunner(app: App, surface?: string): Transport {
33
+ return createTransportEntry(app, surface);
34
+ }
package/src/boot/seed.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Role } from '@fougere/schema';
2
2
  import { facadeKeyOf } from '../wire/call.js';
3
- import type { FrondDescriptor, SeedEntry, SeedFactory } from '../scan/frond.js';
3
+ import type { FrondDescriptor, SeedEntry, SeedFactory } from '../descriptor/frond.js';
4
4
  import type { App } from './types.js';
5
5
  import type { Extension } from './AppLifecycle.js';
6
6
 
@@ -73,7 +73,7 @@ interface SeedDoor {
73
73
  * Plant a set of seeds, in the order given. Reports what it did, one line per entity.
74
74
  *
75
75
  * The one seeding loop. `boot()` had one and the Nuxt module generated a second into its
76
- * Nitro plugin, which had drifted: no ORM fallback, so an entity with no façade was
76
+ * Nitro plugin, which had drifted: no storage fallback, so an entity with no façade was
77
77
  * skipped there and planted here. Two answers to "how does a row get in at boot" is one
78
78
  * too many, and the second was the one running in the browser.
79
79
  */
@@ -88,7 +88,7 @@ export async function runSeeds(
88
88
 
89
89
  const door = doorFor(app, seed.entityName);
90
90
  if (!door) {
91
- report(` ${seed.entityName}: no handler façade nor ORM — skipping seed`);
91
+ report(` ${seed.entityName}: no handler façade nor storage — skipping seed`);
92
92
  continue;
93
93
  }
94
94
 
@@ -130,12 +130,12 @@ function doorFor(app: App, entityName: string): SeedDoor | undefined {
130
130
  };
131
131
  }
132
132
 
133
- const orm = app.ormFor(entityName) as
133
+ const storage = app.storageFor(entityName) as
134
134
  | { list: () => Promise<unknown[]>; create: (input: unknown) => Promise<unknown> }
135
135
  | undefined;
136
- if (!orm) return undefined;
136
+ if (!storage) return undefined;
137
137
 
138
- return { list: () => orm.list(), write: (item) => orm.create(item) };
138
+ return { list: () => storage.list(), write: (item) => storage.create(item) };
139
139
  }
140
140
 
141
141
  /**
@@ -13,12 +13,13 @@
13
13
  * a refusal: the statement is still the author's word, and a boot that ran yesterday must
14
14
  * not stop running because a parameter moved. What it must not do is stay quiet.
15
15
  */
16
- import type { FrondDescriptor, HandlerEntry, ScanDiagnostic } from '../scan/frond.js';
17
- import type { BindingPlan } from './binding.js';
18
- import type { ParsedMethod } from '../scan/handler-parser.js';
16
+ import type { FrondDescriptor, HandlerEntry } from '../descriptor/frond.js';
17
+ import type { ScanDiagnostic } from '../scan/result.js';
18
+ import type { BindingPlan } from '../wire/binding.js';
19
+ import type { Signature } from '../wire/signature.js';
19
20
 
20
21
  /** The parameters a stated binding names, against the ones the method declares. */
21
- function namesDisagreeing(stated: BindingPlan, signature: ParsedMethod): string[] {
22
+ function namesDisagreeing(stated: BindingPlan, signature: Signature): string[] {
22
23
  const declared = new Set(signature.params.map((p: { name: string }) => p.name));
23
24
 
24
25
  return stated.map((b) => b.name).filter((name) => !declared.has(name));