@fougere/core 0.12.2-alpha.0 → 0.12.5-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.
- package/dist/EffectiveOperationModel.d.ts +1 -4
- package/dist/EffectiveOperationModel.d.ts.map +1 -1
- package/dist/EffectiveOperationModel.js +181 -173
- package/dist/EffectiveOperationModel.js.map +1 -1
- package/dist/FougereConfig.d.ts +2 -3
- package/dist/FougereConfig.d.ts.map +1 -1
- package/dist/FougereConfig.js +11 -13
- package/dist/FougereConfig.js.map +1 -1
- package/dist/FrondDeclaration.d.ts +5 -0
- package/dist/FrondDeclaration.d.ts.map +1 -1
- package/dist/FrondDeclaration.js +4 -5
- package/dist/FrondDeclaration.js.map +1 -1
- package/dist/FrondsStated.d.ts +4 -2
- package/dist/FrondsStated.d.ts.map +1 -1
- package/dist/FrondsStated.js.map +1 -1
- package/dist/Source.js +1 -1
- package/dist/Source.js.map +1 -1
- package/dist/SourceView.d.ts +0 -2
- package/dist/SourceView.d.ts.map +1 -1
- package/dist/StatedFrond.d.ts +3 -0
- package/dist/StatedFrond.d.ts.map +1 -1
- package/dist/StatedFrond.js +4 -0
- package/dist/StatedFrond.js.map +1 -1
- package/dist/StatedModules.d.ts +5 -2
- package/dist/StatedModules.d.ts.map +1 -1
- package/dist/StatedModules.js +11 -6
- package/dist/StatedModules.js.map +1 -1
- package/dist/boot/App.d.ts +2 -4
- package/dist/boot/App.d.ts.map +1 -1
- package/dist/boot/AppLifecycle.d.ts +8 -0
- package/dist/boot/AppLifecycle.d.ts.map +1 -1
- package/dist/boot/AppLifecycle.js +24 -10
- package/dist/boot/AppLifecycle.js.map +1 -1
- package/dist/boot/AuthRuntime.d.ts +5 -12
- package/dist/boot/AuthRuntime.d.ts.map +1 -1
- package/dist/boot/AuthRuntime.js +2 -1
- package/dist/boot/AuthRuntime.js.map +1 -1
- package/dist/boot/CreateAppOptions.d.ts +0 -11
- package/dist/boot/CreateAppOptions.d.ts.map +1 -1
- package/dist/boot/Emissions.d.ts.map +1 -1
- package/dist/boot/Emissions.js +3 -6
- package/dist/boot/Emissions.js.map +1 -1
- package/dist/boot/Extension.d.ts +6 -0
- package/dist/boot/Extension.d.ts.map +1 -1
- package/dist/boot/Hosting.d.ts +2 -1
- package/dist/boot/Hosting.d.ts.map +1 -1
- package/dist/boot/bootstrap.d.ts.map +1 -1
- package/dist/boot/bootstrap.js +149 -263
- package/dist/boot/bootstrap.js.map +1 -1
- package/dist/boot/install.d.ts +3 -0
- package/dist/boot/install.d.ts.map +1 -1
- package/dist/boot/install.js +103 -114
- package/dist/boot/install.js.map +1 -1
- package/dist/boot/nesting.d.ts.map +1 -1
- package/dist/boot/nesting.js +2 -1
- package/dist/boot/nesting.js.map +1 -1
- package/dist/boot/relations.d.ts.map +1 -1
- package/dist/boot/relations.js.map +1 -1
- package/dist/boot/remote.d.ts +2 -1
- package/dist/boot/remote.d.ts.map +1 -1
- package/dist/boot/remote.js +7 -4
- package/dist/boot/remote.js.map +1 -1
- package/dist/boot/seed.d.ts +2 -1
- package/dist/boot/seed.d.ts.map +1 -1
- package/dist/boot/seed.js +10 -29
- package/dist/boot/seed.js.map +1 -1
- package/dist/descriptor/ExtensionEntry.d.ts +4 -0
- package/dist/descriptor/ExtensionEntry.d.ts.map +1 -1
- package/dist/descriptor/ExtensionEntry.js +6 -1
- package/dist/descriptor/ExtensionEntry.js.map +1 -1
- package/dist/descriptor/FrondDescriptor.d.ts +2 -0
- package/dist/descriptor/FrondDescriptor.d.ts.map +1 -1
- package/dist/descriptor/FrondDescriptor.js +5 -0
- package/dist/descriptor/FrondDescriptor.js.map +1 -1
- package/dist/descriptor/Fronds.d.ts +5 -0
- package/dist/descriptor/Fronds.d.ts.map +1 -1
- package/dist/descriptor/Fronds.js +8 -0
- package/dist/descriptor/Fronds.js.map +1 -1
- package/dist/descriptor/index.d.ts +1 -0
- package/dist/descriptor/index.d.ts.map +1 -1
- package/dist/descriptor/index.js +1 -0
- package/dist/descriptor/index.js.map +1 -1
- package/dist/dispatch/ArgumentResolver.d.ts.map +1 -1
- package/dist/dispatch/ArgumentResolver.js +2 -1
- package/dist/dispatch/ArgumentResolver.js.map +1 -1
- package/dist/dispatch/Facade.d.ts +3 -0
- package/dist/dispatch/Facade.d.ts.map +1 -1
- package/dist/dispatch/HandlerFacade.d.ts.map +1 -1
- package/dist/dispatch/HandlerFacade.js +8 -4
- package/dist/dispatch/HandlerFacade.js.map +1 -1
- package/dist/dispatch/InFlight.d.ts +3 -2
- package/dist/dispatch/InFlight.d.ts.map +1 -1
- package/dist/dispatch/InFlight.js +11 -1
- package/dist/dispatch/InFlight.js.map +1 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +1 -1
- package/dist/node.js.map +1 -1
- package/dist/prefab/CrudConstructor.d.ts.map +1 -1
- package/dist/prefab/CrudConstructor.js.map +1 -1
- package/dist/prefab/collector.d.ts +6 -0
- package/dist/prefab/collector.d.ts.map +1 -1
- package/dist/prefab/collector.js +26 -1
- package/dist/prefab/collector.js.map +1 -1
- package/dist/storage/Comparison.d.ts +2 -2
- package/dist/storage/Comparison.d.ts.map +1 -1
- package/dist/storage/Comparison.js +3 -1
- package/dist/storage/Comparison.js.map +1 -1
- package/dist/storage/Storage.d.ts +3 -2
- package/dist/storage/Storage.d.ts.map +1 -1
- package/dist/storage/Storage.js +23 -10
- package/dist/storage/Storage.js.map +1 -1
- package/dist/storage/Store.d.ts.map +1 -1
- package/dist/storage/Store.js +4 -2
- package/dist/storage/Store.js.map +1 -1
- package/dist/wire/Emit.d.ts +0 -2
- package/dist/wire/Emit.d.ts.map +1 -1
- package/dist/wire/Emit.js +1 -1
- package/dist/wire/Emit.js.map +1 -1
- package/dist/wire/Facade.d.ts +7 -0
- package/dist/wire/Facade.d.ts.map +1 -1
- package/dist/wire/Facade.js +13 -0
- package/dist/wire/Facade.js.map +1 -1
- package/dist/wire/Invocation.d.ts +2 -1
- package/dist/wire/Invocation.d.ts.map +1 -1
- package/dist/wire/Invocation.js +4 -1
- package/dist/wire/Invocation.js.map +1 -1
- package/dist/wire/InvocationContext.d.ts +2 -0
- package/dist/wire/InvocationContext.d.ts.map +1 -1
- package/dist/wire/OperationContract.d.ts +0 -4
- package/dist/wire/OperationContract.d.ts.map +1 -1
- package/dist/wire/OperationContract.js +1 -1
- package/dist/wire/OperationContract.js.map +1 -1
- package/dist/wire/OperationsMap.d.ts +0 -3
- package/dist/wire/OperationsMap.d.ts.map +1 -1
- package/dist/wire/OperationsMap.js +0 -5
- package/dist/wire/OperationsMap.js.map +1 -1
- package/dist/wire/StateShape.d.ts +27 -0
- package/dist/wire/StateShape.d.ts.map +1 -0
- package/dist/wire/StateShape.js +59 -0
- package/dist/wire/StateShape.js.map +1 -0
- package/package.json +4 -4
- package/src/EffectiveOperationModel.ts +221 -201
- package/src/FougereConfig.ts +11 -16
- package/src/FrondDeclaration.ts +7 -7
- package/src/FrondsStated.ts +4 -2
- package/src/Source.ts +1 -1
- package/src/SourceView.ts +0 -2
- package/src/StatedFrond.ts +5 -0
- package/src/StatedModules.ts +17 -9
- package/src/boot/App.ts +2 -4
- package/src/boot/AppLifecycle.ts +27 -10
- package/src/boot/AuthRuntime.ts +6 -13
- package/src/boot/CreateAppOptions.ts +0 -11
- package/src/boot/Emissions.ts +3 -7
- package/src/boot/Extension.ts +6 -0
- package/src/boot/Hosting.ts +2 -1
- package/src/boot/bootstrap.ts +179 -300
- package/src/boot/install.ts +124 -146
- package/src/boot/nesting.ts +2 -1
- package/src/boot/relations.ts +3 -8
- package/src/boot/remote.ts +8 -3
- package/src/boot/seed.ts +11 -38
- package/src/descriptor/ExtensionEntry.ts +16 -1
- package/src/descriptor/FrondDescriptor.ts +7 -0
- package/src/descriptor/Fronds.ts +10 -0
- package/src/descriptor/index.ts +1 -0
- package/src/dispatch/ArgumentResolver.ts +2 -1
- package/src/dispatch/Facade.ts +3 -0
- package/src/dispatch/HandlerFacade.ts +8 -3
- package/src/dispatch/InFlight.ts +16 -2
- package/src/index.ts +2 -4
- package/src/node.ts +1 -1
- package/src/prefab/CrudConstructor.ts +0 -11
- package/src/prefab/collector.ts +29 -1
- package/src/storage/Comparison.ts +7 -4
- package/src/storage/Storage.ts +26 -12
- package/src/storage/Store.ts +4 -2
- package/src/wire/Emit.ts +1 -1
- package/src/wire/Facade.ts +17 -10
- package/src/wire/Invocation.ts +3 -1
- package/src/wire/InvocationContext.ts +2 -0
- package/src/wire/OperationContract.ts +1 -1
- package/src/wire/OperationsMap.ts +0 -10
- package/src/wire/StateShape.ts +76 -0
- package/dist/boot/AuthConfig.d.ts +0 -14
- package/dist/boot/AuthConfig.d.ts.map +0 -1
- package/dist/boot/AuthConfig.js +0 -2
- package/dist/boot/AuthConfig.js.map +0 -1
- package/dist/boot/AuthContext.d.ts +0 -12
- package/dist/boot/AuthContext.d.ts.map +0 -1
- package/dist/boot/AuthContext.js +0 -2
- package/dist/boot/AuthContext.js.map +0 -1
- package/dist/storage/ComparisonName.d.ts +0 -3
- package/dist/storage/ComparisonName.d.ts.map +0 -1
- package/dist/storage/ComparisonName.js +0 -2
- package/dist/storage/ComparisonName.js.map +0 -1
- package/src/boot/AuthConfig.ts +0 -14
- package/src/boot/AuthContext.ts +0 -12
- package/src/storage/ComparisonName.ts +0 -3
package/src/boot/install.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Putting one frond into the app being built: its scope, what it serves, what it takes. */
|
|
2
2
|
import type { Container } from '@fougere/container';
|
|
3
|
+
import type { StateShape } from '../wire/StateShape.js';
|
|
3
4
|
import type { ProviderEntry } from '../descriptor/ProviderEntry.js';
|
|
4
5
|
import type { PresenterEntry } from '../descriptor/PresenterEntry.js';
|
|
5
6
|
import { lowerFirst, type Fields, type SchemaView } from '@fougere/schema';
|
|
@@ -85,6 +86,8 @@ export interface Assembly {
|
|
|
85
86
|
seamsOf: Map<string, Map<string, ProviderEntry[]>>;
|
|
86
87
|
log: Logger;
|
|
87
88
|
options: CreateAppOptions;
|
|
89
|
+
/** What a call's `state` may hold, read off every extension this process mounts. */
|
|
90
|
+
state: StateShape;
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
/**
|
|
@@ -424,6 +427,7 @@ function buildFacadeInto(
|
|
|
424
427
|
presenter: presenterMap.get(handler.address),
|
|
425
428
|
presenterScope: scope,
|
|
426
429
|
middlewares: () => getMiddlewares(handler.address),
|
|
430
|
+
state: assembly.state,
|
|
427
431
|
});
|
|
428
432
|
|
|
429
433
|
// Emissions use the same contracts and execution path as direct calls, and the terms sit
|
|
@@ -457,90 +461,51 @@ function buildFacadeInto(
|
|
|
457
461
|
));
|
|
458
462
|
}
|
|
459
463
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
// register nothing locally — resolve() falls through to the remote façade.
|
|
469
|
-
if (options.remotes && frond.name in options.remotes) {
|
|
470
|
-
log.child(frond.name).info('declared remote — not hosted locally');
|
|
471
|
-
// Its facades answer elsewhere, but what they LISTEN to was read here.
|
|
472
|
-
for (const handler of frond.handlers) {
|
|
473
|
-
const key = facadeKeyOf(handler.address, handler.surface);
|
|
474
|
-
const operations = operationModel.forHandler(handler);
|
|
475
|
-
effectiveByKey.set(key, operations);
|
|
476
|
-
emissions.note(contractsOf(operations), key);
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return;
|
|
464
|
+
/** Its facades answer elsewhere, but what they LISTEN to is read here, and the bridges route with it. */
|
|
465
|
+
function noteRemoteFrond(frond: FrondDescriptor, { operationModel, effectiveByKey, emissions, contractsOf, log }: Assembly): void {
|
|
466
|
+
log.child(frond.name).info('declared remote — not hosted locally');
|
|
467
|
+
for (const handler of frond.handlers) {
|
|
468
|
+
const key = facadeKeyOf(handler.address, handler.surface);
|
|
469
|
+
const operations = operationModel.forHandler(handler);
|
|
470
|
+
effectiveByKey.set(key, operations);
|
|
471
|
+
emissions.note(contractsOf(operations), key);
|
|
481
472
|
}
|
|
482
|
-
|
|
483
|
-
// nothing else has to know: `ScopeContainer.resolve` already walks up. The boot installs
|
|
484
|
-
// parents first, which is what makes the key below resolvable.
|
|
485
|
-
const above = frond.extends ? container.resolve<Container>(`frond:${frond.extends}`) : container;
|
|
486
|
-
const scope = above.createScope();
|
|
487
|
-
const frondLog = log.child(frond.name);
|
|
488
|
-
// The frond's own voice, a child of the APP logger and never of `log` — which is the
|
|
489
|
-
// boot's, so a service's line would have claimed `boot:` long after the boot was over.
|
|
490
|
-
scope.registerValue('Logger', container.resolve<Logger>('Logger').child(frond.name));
|
|
491
|
-
|
|
492
|
-
// `reads:` is what makes a cross-source reader exist here, and the list IS its
|
|
493
|
-
// environment — a source holding none of these is never opened. Registered under
|
|
494
|
-
// the type's own name, which is the key `depKeyOf` already derives for a plain
|
|
495
|
-
// parameter: `constructor(private reads: Reads)` and nothing else to say.
|
|
496
|
-
// Declaring `reads:` with nothing to build the reader is a boot that ignores a
|
|
497
|
-
// clause: the handler asking for `Reads` then dies at its first call, on a
|
|
498
|
-
// container message that names neither the clause nor what is missing.
|
|
499
|
-
await registerReads(frond, scope, entityByName, options.sourcesFactory, frondLog);
|
|
473
|
+
}
|
|
500
474
|
|
|
501
|
-
|
|
502
|
-
|
|
475
|
+
/**
|
|
476
|
+
* Who owns what, and the rule that makes owning mean something — refused before anything is
|
|
477
|
+
* registered, so a bad line is named here rather than by the container.
|
|
478
|
+
*/
|
|
479
|
+
function ownershipOf(frond: FrondDescriptor, { entityByName, refused }: Assembly): Map<string, string> {
|
|
503
480
|
sharedNames(frond, refused);
|
|
504
481
|
refuseArityDrift(frond, refused);
|
|
505
482
|
const owners = ownersOf(frond.providers, frond.name, refused);
|
|
506
483
|
storageInUserCode(frond, owners, (entity) => entityByName.has(entity), refused);
|
|
507
484
|
crudOnOwned(frond, owners, refused);
|
|
508
485
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
// like a middleware, a link cannot be inherited through a container key, since a seam has
|
|
512
|
-
// none: its realization is built rather than resolved.
|
|
513
|
-
const seams = inheritedSeams(frond, assembly.seamsOf, declared);
|
|
514
|
-
assembly.seamsOf.set(frond.name, seams);
|
|
486
|
+
return owners;
|
|
487
|
+
}
|
|
515
488
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
489
|
+
/** A frame is read from the same `deps` every port is: asking for one IS declaring it. */
|
|
490
|
+
function registerFramesOf(frond: FrondDescriptor, assembly: Assembly, scope: Container, frondLog: Logger): void {
|
|
491
|
+
const { entityByName, frondOf, options, hosting, refused } = assembly;
|
|
492
|
+
const wanted = [...frond.handlers, ...frond.providers, ...frond.presenters, ...frond.collectors]
|
|
493
|
+
.flatMap((declared) => declared.deps.map((key) => ({ key, filePath: declared.filePath })));
|
|
494
|
+
|
|
495
|
+
registerFrames(scope, wanted, frond.providers, {
|
|
496
|
+
entityByName,
|
|
497
|
+
frondOf,
|
|
498
|
+
hostedHere: (name) => !(options.remotes && name in options.remotes),
|
|
499
|
+
storageFactory: options.storageFactory,
|
|
500
|
+
sourceOf: options.sourceOf,
|
|
501
|
+
transacts: options.transacts,
|
|
502
|
+
transacted: options.transacted,
|
|
503
|
+
hosting,
|
|
504
|
+
log: frondLog,
|
|
505
|
+
}, refused);
|
|
506
|
+
}
|
|
519
507
|
|
|
520
|
-
|
|
521
|
-
// from the same `deps` every other port is read from — asking for it IS declaring it,
|
|
522
|
-
// so nothing is registered for a frame nobody wants.
|
|
523
|
-
registerFrames(
|
|
524
|
-
scope,
|
|
525
|
-
[...frond.handlers, ...frond.providers, ...frond.presenters, ...frond.collectors]
|
|
526
|
-
.flatMap((d) => d.deps.map((key) => ({ key, filePath: d.filePath }))),
|
|
527
|
-
frond.providers,
|
|
528
|
-
{
|
|
529
|
-
entityByName,
|
|
530
|
-
frondOf,
|
|
531
|
-
hostedHere: (name) => !(options.remotes && name in options.remotes),
|
|
532
|
-
storageFactory: options.storageFactory,
|
|
533
|
-
sourceOf: options.sourceOf,
|
|
534
|
-
transacts: options.transacts,
|
|
535
|
-
transacted: options.transacted,
|
|
536
|
-
hosting: assembly.hosting,
|
|
537
|
-
log: frondLog,
|
|
538
|
-
},
|
|
539
|
-
refused,
|
|
540
|
-
);
|
|
541
|
-
|
|
542
|
-
// Register presenters in scope — PascalCase type name (e.g. 'PostPresenter')
|
|
543
|
-
const presenterMap = new Map(frond.presenters.map((p) => [p.entityName, p]));
|
|
508
|
+
function registerPresenters(frond: FrondDescriptor, scope: Container, frondLog: Logger): Map<string, PresenterEntry> {
|
|
544
509
|
for (const presenter of frond.presenters) {
|
|
545
510
|
scope.register(presenterKeyOf(presenter.entityName), presenter.ctor, { deps: presenter.deps });
|
|
546
511
|
}
|
|
@@ -548,104 +513,117 @@ export async function installFrond(frond: FrondDescriptor, assembly: Assembly):
|
|
|
548
513
|
frondLog.debug(`${frond.presenters.length} presenter(s): ${frond.presenters.map((p) => p.entityName).join(', ')}`);
|
|
549
514
|
}
|
|
550
515
|
|
|
551
|
-
|
|
552
|
-
|
|
516
|
+
return new Map(frond.presenters.map((presenter) => [presenter.entityName, presenter]));
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function registerCollectors(frond: FrondDescriptor, scope: Container, frondLog: Logger): Set<string> {
|
|
553
520
|
for (const collector of frond.collectors) {
|
|
554
|
-
|
|
555
|
-
scope.register(key, collector.ctor, { deps: collector.deps });
|
|
521
|
+
scope.register(collectorKeyOf(collector.typeName), collector.ctor, { deps: collector.deps });
|
|
556
522
|
}
|
|
557
523
|
if (frond.collectors.length > 0) {
|
|
558
524
|
frondLog.debug(`${frond.collectors.length} collector(s): ${frond.collectors.map((c) => c.typeName).join(', ')}`);
|
|
559
525
|
}
|
|
560
526
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
// instance the boot built — the same reason `getMiddlewares` is read at call time.
|
|
564
|
-
registerMiddlewares(frond, scope, assembly, frondLog);
|
|
565
|
-
|
|
566
|
-
// Build handler facades → registered in ROOT container (public contract)
|
|
567
|
-
const defaultHandlers = frond.handlers.filter((h) => !h.surface);
|
|
568
|
-
const surfaceHandlers = frond.handlers.filter((h) => h.surface);
|
|
569
|
-
const defaultHandlerMap = new Map(defaultHandlers.map((h) => [h.address, h]));
|
|
527
|
+
return new Set(frond.collectors.map((collector) => collector.typeName));
|
|
528
|
+
}
|
|
570
529
|
|
|
571
|
-
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
facadeKey: string,
|
|
577
|
-
) => buildFacadeInto(building, entity, handler, targetScope, facadeKey);
|
|
578
|
-
|
|
579
|
-
// A presenter is about an entity — computed fields sit on a shape — so this walks
|
|
580
|
-
// entities. Exposing the instance lazily; the bridge resolves it on first access.
|
|
581
|
-
exposePresenters(frond, presenterMap, container, scope);
|
|
530
|
+
function sayFacade(building: Building, facadeKey: string, entity: EntityEntry | undefined): void {
|
|
531
|
+
const operations = Object.keys(building.assembly.container.resolve(facadeKey) as object).join(', ');
|
|
532
|
+
building.frondLog.debug(`${facadeKey} [${operations}]`
|
|
533
|
+
+ (entity ? '' : ' — no entity of that name: no storage, no projection, no presenter'));
|
|
534
|
+
}
|
|
582
535
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
//
|
|
591
|
-
// By NAME, not by identity: the scanner loads an entity through its own loader and
|
|
592
|
-
// the handler imports it through the runtime's, so the same class arrives as two
|
|
593
|
-
// objects. `===` compares module instances, which is not the question being asked.
|
|
536
|
+
/**
|
|
537
|
+
* A facade is about a handler, so this walks HANDLERS — pointing at no entity is legal. The
|
|
538
|
+
* subject is `Crud(Item)`'s when it names one, the handler's own name otherwise, compared by
|
|
539
|
+
* NAME: the scanner and the runtime load one class as two objects.
|
|
540
|
+
*/
|
|
541
|
+
function buildDefaultFacades(building: Building, handlers: readonly HandlerEntry[]): void {
|
|
542
|
+
for (const handler of handlers) {
|
|
594
543
|
const crudTarget = targetOf(handler.ctor);
|
|
595
544
|
const subject = crudTarget?.name ? lowerFirst(crudTarget.name) : handler.address;
|
|
596
|
-
const entity = frond.entities.find((e) => e.name === subject);
|
|
545
|
+
const entity = building.frond.entities.find((e) => e.name === subject);
|
|
597
546
|
const facadeKey = facadeKeyOf(handler.address);
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
+ (entity ? '' : ' — no entity of that name: no storage, no projection, no presenter'));
|
|
547
|
+
buildFacadeInto(building, entity, handler, building.scope, facadeKey);
|
|
548
|
+
sayFacade(building, facadeKey, entity);
|
|
601
549
|
}
|
|
550
|
+
}
|
|
602
551
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
552
|
+
/** A shape that declares no operation answers nothing — said once per entity rather than deduced from a silence. */
|
|
553
|
+
function sayUnhandledEntities(building: Building, handlers: readonly HandlerEntry[]): void {
|
|
554
|
+
const handled = new Set(handlers.map((handler) => handler.address));
|
|
555
|
+
for (const entity of building.frond.entities) {
|
|
556
|
+
if (!handled.has(entity.name)) building.frondLog.debug(`${entity.name} — entity only, no handler: exposes nothing`);
|
|
609
557
|
}
|
|
558
|
+
}
|
|
610
559
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
// Register scoped storage if output override differs from entity — under the REPOSITORY
|
|
622
|
-
// key, which is what a Crud handler asks for, and under the port's own for a holder
|
|
623
|
-
// that legitimately names it. Registering only the latter left a named surface with
|
|
624
|
-
// no facade at all once the façade stopped spelling the storage.
|
|
625
|
-
if (entity) registerSurfaceStorage(entity, handler, surfaceScope, assembly, frondLog);
|
|
560
|
+
/**
|
|
561
|
+
* One sub-scope per surface handler, holding the storage its view scopes — under the REPOSITORY
|
|
562
|
+
* key a Crud handler asks for. Pointing at no entity is legal here too: one rule, both surfaces.
|
|
563
|
+
*/
|
|
564
|
+
function buildSurfaceFacades(building: Building, handlers: readonly HandlerEntry[]): void {
|
|
565
|
+
for (const handler of handlers) {
|
|
566
|
+
const entity = building.frond.entities.find((e) => e.name === handler.address);
|
|
567
|
+
const surfaceScope = building.scope.createScope();
|
|
568
|
+
if (entity) registerSurfaceStorage(entity, handler, surfaceScope, building.assembly, building.frondLog);
|
|
626
569
|
|
|
627
570
|
const facadeKey = facadeKeyOf(handler.address, handler.surface);
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
+ (entity ? '' : ' — no entity of that name: no storage, no projection, no presenter'));
|
|
571
|
+
buildFacadeInto(building, entity, handler, surfaceScope, facadeKey);
|
|
572
|
+
sayFacade(building, facadeKey, entity);
|
|
631
573
|
}
|
|
574
|
+
}
|
|
632
575
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
// absent HERE, in one line, instead of being discovered missing later.
|
|
637
|
-
const surfaceNames = [...new Set(surfaceHandlers.map((h) => h.surface as string))].sort();
|
|
576
|
+
/** A named surface is closed, so what it serves — and what it leaves out — is said at boot, in one line. */
|
|
577
|
+
function sayWhatSurfacesServe(frond: FrondDescriptor, handlers: readonly HandlerEntry[], frondLog: Logger): void {
|
|
578
|
+
const surfaceNames = [...new Set(handlers.map((h) => h.surface as string))].sort();
|
|
638
579
|
for (const surfaceName of surfaceNames) {
|
|
639
|
-
const served =
|
|
640
|
-
.filter((h) => h.surface === surfaceName)
|
|
641
|
-
.map((h) => h.address)
|
|
642
|
-
.sort();
|
|
580
|
+
const served = handlers.filter((h) => h.surface === surfaceName).map((h) => h.address).sort();
|
|
643
581
|
const absent = frond.entities.map((e) => e.name).filter((n) => !served.includes(n));
|
|
644
582
|
frondLog.info(
|
|
645
583
|
`surface '${surfaceName}' — ${served.length} entit${served.length === 1 ? 'y' : 'ies'}: ${served.join(', ')}` +
|
|
646
584
|
(absent.length > 0 ? ` (not served: ${absent.join(', ')})` : ''),
|
|
647
585
|
);
|
|
648
586
|
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
export async function installFrond(frond: FrondDescriptor, assembly: Assembly): Promise<void> {
|
|
590
|
+
const { container, entityByName, options, log } = assembly;
|
|
591
|
+
|
|
592
|
+
if (options.remotes && frond.name in options.remotes) {
|
|
593
|
+
noteRemoteFrond(frond, assembly);
|
|
594
|
+
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// A child hangs off its parent, so what the parent registered answers here too — parents are installed first.
|
|
599
|
+
const above = frond.extends ? container.resolve<Container>(`frond:${frond.extends}`) : container;
|
|
600
|
+
const scope = above.createScope();
|
|
601
|
+
const frondLog = log.child(frond.name);
|
|
602
|
+
// A child of the APP logger, never of the boot's, whose `boot:` would outlive the boot.
|
|
603
|
+
scope.registerValue('Logger', container.resolve<Logger>('Logger').child(frond.name));
|
|
604
|
+
|
|
605
|
+
await registerReads(frond, scope, entityByName, options.sourcesFactory, frondLog);
|
|
606
|
+
const owners = ownershipOf(frond, assembly);
|
|
607
|
+
|
|
608
|
+
const declared = registerProviders(frond, scope, options.ports, assembly.boundPorts, assembly.refused, frondLog);
|
|
609
|
+
// An ancestor's links stand OUTSIDE this frond's own; a seam has no container key to inherit through.
|
|
610
|
+
const seams = inheritedSeams(frond, assembly.seamsOf, declared);
|
|
611
|
+
assembly.seamsOf.set(frond.name, seams);
|
|
612
|
+
registerStorages(frond, assembly, scope, seams, owners, frondLog);
|
|
613
|
+
registerFramesOf(frond, assembly, scope, frondLog);
|
|
614
|
+
|
|
615
|
+
const presenterMap = registerPresenters(frond, scope, frondLog);
|
|
616
|
+
const collectorTypeNames = registerCollectors(frond, scope, frondLog);
|
|
617
|
+
registerMiddlewares(frond, scope, assembly, frondLog);
|
|
618
|
+
exposePresenters(frond, presenterMap, container, scope);
|
|
619
|
+
|
|
620
|
+
const building: Building = { frond, assembly, scope, collectorTypeNames, presenterMap, frondLog };
|
|
621
|
+
const defaultHandlers = frond.handlers.filter((h) => !h.surface);
|
|
622
|
+
const surfaceHandlers = frond.handlers.filter((h) => h.surface);
|
|
623
|
+
buildDefaultFacades(building, defaultHandlers);
|
|
624
|
+
sayUnhandledEntities(building, defaultHandlers);
|
|
625
|
+
buildSurfaceFacades(building, surfaceHandlers);
|
|
626
|
+
sayWhatSurfacesServe(frond, surfaceHandlers, frondLog);
|
|
649
627
|
|
|
650
628
|
container.registerValue(`frond:${frond.name}`, scope);
|
|
651
629
|
frondLog.info(`registered — ${frond.entities.length} entities, ${frond.handlers.length} handlers, ${frond.seeds.length} seeds`);
|
package/src/boot/nesting.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Documented: [fronds](https://fougere.dev/docs/infra/fronds).
|
|
7
7
|
*/
|
|
8
8
|
import type { Diagnostic } from '../diagnostic.js';
|
|
9
|
+
import { isFrondDescriptor } from '../descriptor/FrondDescriptor.js';
|
|
9
10
|
import type { FrondDescriptor } from '../descriptor/FrondDescriptor.js';
|
|
10
11
|
import { Fronds } from '../descriptor/Fronds.js';
|
|
11
12
|
import { statesModule, type FrondsStated } from '../FrondsStated.js';
|
|
@@ -102,7 +103,7 @@ export function nestingOf(
|
|
|
102
103
|
if (frond === undefined) continue;
|
|
103
104
|
|
|
104
105
|
const placed = stated[parent];
|
|
105
|
-
const address = typeof placed === 'string' ? placed : placed?.remote;
|
|
106
|
+
const address = typeof placed === 'string' ? placed : isFrondDescriptor(placed) ? undefined : placed?.remote;
|
|
106
107
|
if (address !== undefined || (remotes && parent in remotes)) {
|
|
107
108
|
refused.push({
|
|
108
109
|
severity: 'blocking',
|
package/src/boot/relations.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Dependent } from '../dispatch/Dependent.js';
|
|
|
4
4
|
import type { RelationCheck } from '../dispatch/RelationCheck.js';
|
|
5
5
|
import type { Hosting } from './Hosting.js';
|
|
6
6
|
import type { Diagnostic } from '../diagnostic.js';
|
|
7
|
-
import type { Releasing
|
|
7
|
+
import type { Releasing } from '../dispatch/Release.js';
|
|
8
8
|
import type { EntityEntry } from '../descriptor/EntityEntry.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -44,11 +44,6 @@ function referencesTo(target: string, hosting: Hosting): { entity: string; field
|
|
|
44
44
|
return found;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/** What a target's storage has to answer for a key set to be judged in one read. */
|
|
48
|
-
interface ByKeys {
|
|
49
|
-
findByKeys(keys: readonly string[]): Promise<Map<string, unknown>>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
47
|
/**
|
|
53
48
|
* The references a foreign key cannot hold, each as a read the guard makes before the write.
|
|
54
49
|
*
|
|
@@ -68,7 +63,7 @@ export function heldBy(entity: SchemaView, name: string, hosting: Hosting): Rela
|
|
|
68
63
|
field,
|
|
69
64
|
target,
|
|
70
65
|
missing: async (keys) => {
|
|
71
|
-
const storage = hosting.storageOf(target)
|
|
66
|
+
const storage = hosting.storageOf(target);
|
|
72
67
|
// Nothing here holds those rows — the process that does answers the same reading,
|
|
73
68
|
// so the question crosses rather than being given up on.
|
|
74
69
|
if (!storage) return hosting.peerOf(target)?.missing(target, keys) ?? [];
|
|
@@ -176,7 +171,7 @@ export function unheldAmong(checks: readonly RelationCheck[], hosting: Hosting):
|
|
|
176
171
|
export function releasing(hosting: Hosting): Releasing {
|
|
177
172
|
return {
|
|
178
173
|
dependentsOf: (entity) => dependentsOf(entity, hosting),
|
|
179
|
-
rowsOf: (entity) => hosting.storageOf(entity)
|
|
174
|
+
rowsOf: (entity) => hosting.storageOf(entity),
|
|
180
175
|
schemaOf: (entity) => hosting.entities().get(entity),
|
|
181
176
|
peers: () => hosting.peers(),
|
|
182
177
|
journal: (entity) => hosting.journal(entity),
|
package/src/boot/remote.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Documented: [the gradient](https://fougere.dev/docs/infra/gradient).
|
|
5
5
|
*/
|
|
6
6
|
import type { FrondCall } from '../wire/FrondCall.js';
|
|
7
|
+
import type { StateShape } from '../wire/StateShape.js';
|
|
7
8
|
import type { Transport } from '../wire/Transport.js';
|
|
8
9
|
import { RPC_ENTITY } from '../wire/RpcAnswer.js';
|
|
9
10
|
import { assertIdentityCard } from '../wire/card/IdentityCard.js';
|
|
@@ -103,15 +104,19 @@ export function createRemoteFacade(
|
|
|
103
104
|
entity: string,
|
|
104
105
|
router: RemoteRouter,
|
|
105
106
|
middlewaresFor: (address: string) => AppMiddleware[],
|
|
107
|
+
shape: StateShape,
|
|
106
108
|
): Facade {
|
|
107
|
-
const opFn = (op: string) => async (
|
|
109
|
+
const opFn = (op: string) => async (received: InvocationContext = Invocation.empty) => {
|
|
108
110
|
const { frond, transport, schema } = await router.route(entity);
|
|
109
111
|
const call: FrondCall = { frond, entity, op };
|
|
112
|
+
const state = received.crossed ? { ...received.state } : shape.judge(received.state, entity, op);
|
|
113
|
+
const invocation = { ...received, state };
|
|
114
|
+
const entered = { ...state };
|
|
110
115
|
const ctx: OperationContext = {
|
|
111
|
-
entity, frond, operation: op, args: [], state
|
|
116
|
+
entity, frond, operation: op, args: [], state, invocation,
|
|
112
117
|
};
|
|
113
118
|
const answer = await runMiddlewares(middlewaresFor(entity), ctx, () =>
|
|
114
|
-
transport(call, ctx.invocation ?? invocation));
|
|
119
|
+
transport(call, { ...(ctx.invocation ?? invocation), state: shape.judge(ctx.state, entity, op, entered) }));
|
|
115
120
|
|
|
116
121
|
// The schema the card carried, put to work: a row crosses as data and comes back through
|
|
117
122
|
// the same codecs a local facade applies, so a placement does not decide what a caller
|
package/src/boot/seed.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { pageOf } from '../wire/Page.js';
|
|
1
2
|
import { Role } from '@fougere/schema';
|
|
2
|
-
import { facadeKeyOf } from '../wire/Facade.js';
|
|
3
3
|
import type { FrondDescriptor } from '../descriptor/FrondDescriptor.js';
|
|
4
4
|
import type { SeedEntry } from '../descriptor/SeedEntry.js';
|
|
5
5
|
import type { SeedFactory } from '../descriptor/SeedFactory.js';
|
|
@@ -64,14 +64,9 @@ function whatEachWaitsFor(
|
|
|
64
64
|
}));
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/** Where a seed writes, and what it may skip — resolved per entity. */
|
|
68
|
-
interface SeedFacade {
|
|
69
|
-
list(): Promise<unknown[]>;
|
|
70
|
-
write(item: unknown): Promise<unknown>;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
67
|
/**
|
|
74
|
-
* Plant a set of seeds, in the order given
|
|
68
|
+
* Plant a set of seeds, in the order given — by the storage, as a handler writes: a seed is the
|
|
69
|
+
* frond's own code, not a client.
|
|
75
70
|
*
|
|
76
71
|
* Documented: [seeds](https://fougere.dev/docs/business/seeds).
|
|
77
72
|
*/
|
|
@@ -81,26 +76,26 @@ export async function runSeeds(
|
|
|
81
76
|
report: (message: string) => void = () => {},
|
|
82
77
|
): Promise<void> {
|
|
83
78
|
for (const seed of seeds) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const facade = facadeFor(app, seed.entityName);
|
|
88
|
-
if (!facade) {
|
|
89
|
-
report(` ${seed.entityName}: no handler façade nor storage — skipping seed`);
|
|
79
|
+
const storage = app.storageFor(seed.entityName);
|
|
80
|
+
if (!storage) {
|
|
81
|
+
report(` ${seed.entityName}: no storage in this process — skipping seed`);
|
|
90
82
|
continue;
|
|
91
83
|
}
|
|
92
84
|
|
|
93
|
-
const existing = await
|
|
85
|
+
const existing = pageOf(await storage.list()).items;
|
|
94
86
|
if (existing.length > 0) {
|
|
95
87
|
report(` ${seed.entityName}: skipped (${existing.length} exist)`);
|
|
96
88
|
continue;
|
|
97
89
|
}
|
|
98
90
|
|
|
91
|
+
const resolve = <T>(name: string) => app.resolve<T>(name + 'Handler');
|
|
92
|
+
const data = typeof seed.data === 'function' ? await (seed.data as SeedFactory)(resolve) : seed.data;
|
|
93
|
+
|
|
99
94
|
// The driver's error alone names neither the entity nor the row — and a foreign
|
|
100
95
|
// key is exactly what a seed gets wrong.
|
|
101
96
|
for (const item of data) {
|
|
102
97
|
try {
|
|
103
|
-
await
|
|
98
|
+
await storage.create(item);
|
|
104
99
|
} catch (cause) {
|
|
105
100
|
throw new Error(
|
|
106
101
|
`Seed '${seed.entityName}' failed on ${JSON.stringify(item)}: ${(cause as Error)?.message ?? cause}`,
|
|
@@ -112,28 +107,6 @@ export async function runSeeds(
|
|
|
112
107
|
}
|
|
113
108
|
}
|
|
114
109
|
|
|
115
|
-
/** A seed is not a client. */
|
|
116
|
-
function facadeFor(app: App, entityName: string): SeedFacade | undefined {
|
|
117
|
-
let handler: Record<string, Function> | undefined;
|
|
118
|
-
try { handler = app.resolve<Record<string, Function>>(facadeKeyOf(entityName)); } catch {}
|
|
119
|
-
|
|
120
|
-
const list = handler?.list;
|
|
121
|
-
const create = handler?.create;
|
|
122
|
-
if (typeof list === 'function' && typeof create === 'function') {
|
|
123
|
-
return {
|
|
124
|
-
list: () => list.call(handler) as Promise<unknown[]>,
|
|
125
|
-
write: (item) => create.call(handler, { params: {}, query: {}, input: item, state: {} }),
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const storage = app.storageFor(entityName) as
|
|
130
|
-
| { list: () => Promise<unknown[]>; create: (input: unknown) => Promise<unknown> }
|
|
131
|
-
| undefined;
|
|
132
|
-
if (!storage) return undefined;
|
|
133
|
-
|
|
134
|
-
return { list: () => storage.list(), write: (item) => storage.create(item) };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
110
|
/**
|
|
138
111
|
* The framework's own ascent for rows, named — so a host can REPLACE it rather than take over the
|
|
139
112
|
* whole post-boot to get its own.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { Field } from '@fougere/schema';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* An extension this frond declares — what rises and falls with the process, written beside the
|
|
3
5
|
* code it instruments.
|
|
@@ -10,6 +12,19 @@ export interface ExtensionEntry {
|
|
|
10
12
|
/** Its own `name`, or the file's — the ascent REPLACES a name already declared. */
|
|
11
13
|
name: string;
|
|
12
14
|
/** The value handed to the ascent, the same shape `CreateAppOptions.extensions` takes. */
|
|
13
|
-
extension: {
|
|
15
|
+
extension: {
|
|
16
|
+
name?: string;
|
|
17
|
+
state?: Readonly<Record<string, Field>>;
|
|
18
|
+
up?: (app: never) => void | Promise<void>;
|
|
19
|
+
down?: (app: never) => void | Promise<void>;
|
|
20
|
+
};
|
|
14
21
|
filePath: string;
|
|
15
22
|
}
|
|
23
|
+
|
|
24
|
+
/** Recognized by its FORM: a value stating `up`, `down`, or the `state` members it declares. */
|
|
25
|
+
export function isExtension(value: unknown): value is ExtensionEntry['extension'] {
|
|
26
|
+
const shape = value as { up?: unknown; down?: unknown; state?: unknown } | null;
|
|
27
|
+
|
|
28
|
+
return typeof shape?.up === 'function' || typeof shape?.down === 'function'
|
|
29
|
+
|| (typeof shape?.state === 'object' && shape.state !== null);
|
|
30
|
+
}
|
|
@@ -63,3 +63,10 @@ export interface FrondDescriptor {
|
|
|
63
63
|
rest?: { method?: string; path?: string; status?: number };
|
|
64
64
|
}>;
|
|
65
65
|
}
|
|
66
|
+
|
|
67
|
+
/** Recognized by its FORM, like everything a config holds: a name and the lists every frond carries. */
|
|
68
|
+
export function isFrondDescriptor(value: unknown): value is FrondDescriptor {
|
|
69
|
+
const shape = value as Partial<Record<keyof FrondDescriptor, unknown>> | null;
|
|
70
|
+
|
|
71
|
+
return typeof shape?.name === 'string' && Array.isArray(shape.entities) && Array.isArray(shape.handlers);
|
|
72
|
+
}
|
package/src/descriptor/Fronds.ts
CHANGED
|
@@ -41,6 +41,16 @@ export class Fronds extends Array<FrondDescriptor> {
|
|
|
41
41
|
return this.flatMap((frond) => frond.entities.map((e) => e.name)).sort();
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Whether a named surface serves an entity, as its owning frond declared it — `undefined` when
|
|
46
|
+
* that frond declares no such surface, and a facade registered there answers as it is.
|
|
47
|
+
*/
|
|
48
|
+
admits(surface: string, entity: string): boolean | undefined {
|
|
49
|
+
const declared = this.owner(entity)?.surfaces?.[surface];
|
|
50
|
+
|
|
51
|
+
return declared?.some((name) => name.toLowerCase() === entity.toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
/** The names a call may address, sorted — what a NOT_FOUND must print. */
|
|
45
55
|
servedNames(surface?: string): string[] {
|
|
46
56
|
return this.flatMap((frond) => frond.handlers
|
package/src/descriptor/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ export { type TypeRef } from '../wire/TypeRef.js';
|
|
|
23
23
|
export { computeBindingPlan, type BindingPlan } from '../wire/binding.js';
|
|
24
24
|
export type { Signature } from '../wire/Signature.js';
|
|
25
25
|
export { awaitKeyOf, emitKeyOf } from '../wire/Emit.js';
|
|
26
|
+
export { addressOf } from '../wire/Facade.js';
|
|
26
27
|
export { storageKeyOf } from '../storage/Storage.js';
|
|
27
28
|
export { getPresenterFields } from '../prefab/presenter.js';
|
|
28
29
|
export { targetOf, viewsOf, outputOf } from '../prefab/prefab.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { InvocationContext } from '../wire/InvocationContext.js';
|
|
2
2
|
import type { BindingPlan } from '../wire/binding.js';
|
|
3
3
|
import type { CollectorLookup } from './CollectorLookup.js';
|
|
4
|
+
import { collectedAs } from '../prefab/collector.js';
|
|
4
5
|
|
|
5
6
|
/** Resolves an operation's declared binding plan against one invocation. */
|
|
6
7
|
export class ArgumentResolver {
|
|
@@ -13,7 +14,7 @@ export class ArgumentResolver {
|
|
|
13
14
|
switch (binding.source.kind) {
|
|
14
15
|
case 'collector': {
|
|
15
16
|
const collector = this.collectors?.(binding.source.typeName);
|
|
16
|
-
args.push(collector ? await collector.collect(ctx) : undefined);
|
|
17
|
+
args.push(collector ? collectedAs(collector, await collector.collect(ctx)) : undefined);
|
|
17
18
|
break;
|
|
18
19
|
}
|
|
19
20
|
case 'context': {
|
package/src/dispatch/Facade.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { type AppMiddleware } from '../wire/AppMiddleware.js';
|
|
|
3
3
|
import type { EffectiveOperationsMap } from '../EffectiveOperationsMap.js';
|
|
4
4
|
import type { HandlerEntry } from '../descriptor/HandlerEntry.js';
|
|
5
5
|
import type { PresenterEntry } from '../descriptor/PresenterEntry.js';
|
|
6
|
+
import type { StateShape } from '../wire/StateShape.js';
|
|
6
7
|
|
|
7
8
|
/** What boot resolved around one handler, beyond the handler and the scope it resolves in. */
|
|
8
9
|
export interface Facade {
|
|
@@ -22,4 +23,6 @@ export interface Facade {
|
|
|
22
23
|
presenterScope: Container;
|
|
23
24
|
/** The middlewares that apply to this address, read at call time and never at boot. */
|
|
24
25
|
middlewares: () => AppMiddleware[];
|
|
26
|
+
/** What a call's `state` may hold in this process. */
|
|
27
|
+
state: StateShape;
|
|
25
28
|
}
|