@fougere/core 0.12.2-alpha.0 → 0.12.4-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 +5 -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.map +1 -1
- package/dist/FrondDeclaration.js +1 -5
- package/dist/FrondDeclaration.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/boot/App.d.ts +2 -4
- package/dist/boot/App.d.ts.map +1 -1
- package/dist/boot/AppLifecycle.d.ts +5 -0
- package/dist/boot/AppLifecycle.d.ts.map +1 -1
- package/dist/boot/AppLifecycle.js +19 -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/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 +140 -263
- package/dist/boot/bootstrap.js.map +1 -1
- package/dist/boot/install.d.ts.map +1 -1
- package/dist/boot/install.js +102 -114
- package/dist/boot/install.js.map +1 -1
- package/dist/boot/relations.d.ts.map +1 -1
- package/dist/boot/relations.js.map +1 -1
- package/dist/boot/seed.d.ts.map +1 -1
- package/dist/boot/seed.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/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/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/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/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/package.json +4 -4
- package/src/EffectiveOperationModel.ts +221 -201
- package/src/FougereConfig.ts +14 -16
- package/src/FrondDeclaration.ts +1 -7
- package/src/Source.ts +1 -1
- package/src/SourceView.ts +0 -2
- package/src/boot/App.ts +2 -4
- package/src/boot/AppLifecycle.ts +21 -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/Hosting.ts +2 -1
- package/src/boot/bootstrap.ts +168 -300
- package/src/boot/install.ts +120 -146
- package/src/boot/relations.ts +3 -8
- package/src/boot/seed.ts +2 -4
- package/src/descriptor/Fronds.ts +10 -0
- package/src/descriptor/index.ts +1 -0
- 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/storage/Comparison.ts +7 -4
- package/src/wire/Emit.ts +1 -1
- package/src/wire/Facade.ts +17 -10
- package/src/wire/OperationContract.ts +1 -1
- package/src/wire/OperationsMap.ts +0 -10
- 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
|
@@ -457,90 +457,51 @@ function buildFacadeInto(
|
|
|
457
457
|
));
|
|
458
458
|
}
|
|
459
459
|
|
|
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;
|
|
460
|
+
/** Its facades answer elsewhere, but what they LISTEN to is read here, and the bridges route with it. */
|
|
461
|
+
function noteRemoteFrond(frond: FrondDescriptor, { operationModel, effectiveByKey, emissions, contractsOf, log }: Assembly): void {
|
|
462
|
+
log.child(frond.name).info('declared remote — not hosted locally');
|
|
463
|
+
for (const handler of frond.handlers) {
|
|
464
|
+
const key = facadeKeyOf(handler.address, handler.surface);
|
|
465
|
+
const operations = operationModel.forHandler(handler);
|
|
466
|
+
effectiveByKey.set(key, operations);
|
|
467
|
+
emissions.note(contractsOf(operations), key);
|
|
481
468
|
}
|
|
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);
|
|
469
|
+
}
|
|
500
470
|
|
|
501
|
-
|
|
502
|
-
|
|
471
|
+
/**
|
|
472
|
+
* Who owns what, and the rule that makes owning mean something — refused before anything is
|
|
473
|
+
* registered, so a bad line is named here rather than by the container.
|
|
474
|
+
*/
|
|
475
|
+
function ownershipOf(frond: FrondDescriptor, { entityByName, refused }: Assembly): Map<string, string> {
|
|
503
476
|
sharedNames(frond, refused);
|
|
504
477
|
refuseArityDrift(frond, refused);
|
|
505
478
|
const owners = ownersOf(frond.providers, frond.name, refused);
|
|
506
479
|
storageInUserCode(frond, owners, (entity) => entityByName.has(entity), refused);
|
|
507
480
|
crudOnOwned(frond, owners, refused);
|
|
508
481
|
|
|
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);
|
|
482
|
+
return owners;
|
|
483
|
+
}
|
|
515
484
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
485
|
+
/** A frame is read from the same `deps` every port is: asking for one IS declaring it. */
|
|
486
|
+
function registerFramesOf(frond: FrondDescriptor, assembly: Assembly, scope: Container, frondLog: Logger): void {
|
|
487
|
+
const { entityByName, frondOf, options, hosting, refused } = assembly;
|
|
488
|
+
const wanted = [...frond.handlers, ...frond.providers, ...frond.presenters, ...frond.collectors]
|
|
489
|
+
.flatMap((declared) => declared.deps.map((key) => ({ key, filePath: declared.filePath })));
|
|
490
|
+
|
|
491
|
+
registerFrames(scope, wanted, frond.providers, {
|
|
492
|
+
entityByName,
|
|
493
|
+
frondOf,
|
|
494
|
+
hostedHere: (name) => !(options.remotes && name in options.remotes),
|
|
495
|
+
storageFactory: options.storageFactory,
|
|
496
|
+
sourceOf: options.sourceOf,
|
|
497
|
+
transacts: options.transacts,
|
|
498
|
+
transacted: options.transacted,
|
|
499
|
+
hosting,
|
|
500
|
+
log: frondLog,
|
|
501
|
+
}, refused);
|
|
502
|
+
}
|
|
519
503
|
|
|
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]));
|
|
504
|
+
function registerPresenters(frond: FrondDescriptor, scope: Container, frondLog: Logger): Map<string, PresenterEntry> {
|
|
544
505
|
for (const presenter of frond.presenters) {
|
|
545
506
|
scope.register(presenterKeyOf(presenter.entityName), presenter.ctor, { deps: presenter.deps });
|
|
546
507
|
}
|
|
@@ -548,104 +509,117 @@ export async function installFrond(frond: FrondDescriptor, assembly: Assembly):
|
|
|
548
509
|
frondLog.debug(`${frond.presenters.length} presenter(s): ${frond.presenters.map((p) => p.entityName).join(', ')}`);
|
|
549
510
|
}
|
|
550
511
|
|
|
551
|
-
|
|
552
|
-
|
|
512
|
+
return new Map(frond.presenters.map((presenter) => [presenter.entityName, presenter]));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function registerCollectors(frond: FrondDescriptor, scope: Container, frondLog: Logger): Set<string> {
|
|
553
516
|
for (const collector of frond.collectors) {
|
|
554
|
-
|
|
555
|
-
scope.register(key, collector.ctor, { deps: collector.deps });
|
|
517
|
+
scope.register(collectorKeyOf(collector.typeName), collector.ctor, { deps: collector.deps });
|
|
556
518
|
}
|
|
557
519
|
if (frond.collectors.length > 0) {
|
|
558
520
|
frondLog.debug(`${frond.collectors.length} collector(s): ${frond.collectors.map((c) => c.typeName).join(', ')}`);
|
|
559
521
|
}
|
|
560
522
|
|
|
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]));
|
|
523
|
+
return new Set(frond.collectors.map((collector) => collector.typeName));
|
|
524
|
+
}
|
|
570
525
|
|
|
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);
|
|
526
|
+
function sayFacade(building: Building, facadeKey: string, entity: EntityEntry | undefined): void {
|
|
527
|
+
const operations = Object.keys(building.assembly.container.resolve(facadeKey) as object).join(', ');
|
|
528
|
+
building.frondLog.debug(`${facadeKey} [${operations}]`
|
|
529
|
+
+ (entity ? '' : ' — no entity of that name: no storage, no projection, no presenter'));
|
|
530
|
+
}
|
|
582
531
|
|
|
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.
|
|
532
|
+
/**
|
|
533
|
+
* A facade is about a handler, so this walks HANDLERS — pointing at no entity is legal. The
|
|
534
|
+
* subject is `Crud(Item)`'s when it names one, the handler's own name otherwise, compared by
|
|
535
|
+
* NAME: the scanner and the runtime load one class as two objects.
|
|
536
|
+
*/
|
|
537
|
+
function buildDefaultFacades(building: Building, handlers: readonly HandlerEntry[]): void {
|
|
538
|
+
for (const handler of handlers) {
|
|
594
539
|
const crudTarget = targetOf(handler.ctor);
|
|
595
540
|
const subject = crudTarget?.name ? lowerFirst(crudTarget.name) : handler.address;
|
|
596
|
-
const entity = frond.entities.find((e) => e.name === subject);
|
|
541
|
+
const entity = building.frond.entities.find((e) => e.name === subject);
|
|
597
542
|
const facadeKey = facadeKeyOf(handler.address);
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
+ (entity ? '' : ' — no entity of that name: no storage, no projection, no presenter'));
|
|
543
|
+
buildFacadeInto(building, entity, handler, building.scope, facadeKey);
|
|
544
|
+
sayFacade(building, facadeKey, entity);
|
|
601
545
|
}
|
|
546
|
+
}
|
|
602
547
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
548
|
+
/** A shape that declares no operation answers nothing — said once per entity rather than deduced from a silence. */
|
|
549
|
+
function sayUnhandledEntities(building: Building, handlers: readonly HandlerEntry[]): void {
|
|
550
|
+
const handled = new Set(handlers.map((handler) => handler.address));
|
|
551
|
+
for (const entity of building.frond.entities) {
|
|
552
|
+
if (!handled.has(entity.name)) building.frondLog.debug(`${entity.name} — entity only, no handler: exposes nothing`);
|
|
609
553
|
}
|
|
554
|
+
}
|
|
610
555
|
|
|
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);
|
|
556
|
+
/**
|
|
557
|
+
* One sub-scope per surface handler, holding the storage its view scopes — under the REPOSITORY
|
|
558
|
+
* key a Crud handler asks for. Pointing at no entity is legal here too: one rule, both surfaces.
|
|
559
|
+
*/
|
|
560
|
+
function buildSurfaceFacades(building: Building, handlers: readonly HandlerEntry[]): void {
|
|
561
|
+
for (const handler of handlers) {
|
|
562
|
+
const entity = building.frond.entities.find((e) => e.name === handler.address);
|
|
563
|
+
const surfaceScope = building.scope.createScope();
|
|
564
|
+
if (entity) registerSurfaceStorage(entity, handler, surfaceScope, building.assembly, building.frondLog);
|
|
626
565
|
|
|
627
566
|
const facadeKey = facadeKeyOf(handler.address, handler.surface);
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
+ (entity ? '' : ' — no entity of that name: no storage, no projection, no presenter'));
|
|
567
|
+
buildFacadeInto(building, entity, handler, surfaceScope, facadeKey);
|
|
568
|
+
sayFacade(building, facadeKey, entity);
|
|
631
569
|
}
|
|
570
|
+
}
|
|
632
571
|
|
|
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();
|
|
572
|
+
/** A named surface is closed, so what it serves — and what it leaves out — is said at boot, in one line. */
|
|
573
|
+
function sayWhatSurfacesServe(frond: FrondDescriptor, handlers: readonly HandlerEntry[], frondLog: Logger): void {
|
|
574
|
+
const surfaceNames = [...new Set(handlers.map((h) => h.surface as string))].sort();
|
|
638
575
|
for (const surfaceName of surfaceNames) {
|
|
639
|
-
const served =
|
|
640
|
-
.filter((h) => h.surface === surfaceName)
|
|
641
|
-
.map((h) => h.address)
|
|
642
|
-
.sort();
|
|
576
|
+
const served = handlers.filter((h) => h.surface === surfaceName).map((h) => h.address).sort();
|
|
643
577
|
const absent = frond.entities.map((e) => e.name).filter((n) => !served.includes(n));
|
|
644
578
|
frondLog.info(
|
|
645
579
|
`surface '${surfaceName}' — ${served.length} entit${served.length === 1 ? 'y' : 'ies'}: ${served.join(', ')}` +
|
|
646
580
|
(absent.length > 0 ? ` (not served: ${absent.join(', ')})` : ''),
|
|
647
581
|
);
|
|
648
582
|
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export async function installFrond(frond: FrondDescriptor, assembly: Assembly): Promise<void> {
|
|
586
|
+
const { container, entityByName, options, log } = assembly;
|
|
587
|
+
|
|
588
|
+
if (options.remotes && frond.name in options.remotes) {
|
|
589
|
+
noteRemoteFrond(frond, assembly);
|
|
590
|
+
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// A child hangs off its parent, so what the parent registered answers here too — parents are installed first.
|
|
595
|
+
const above = frond.extends ? container.resolve<Container>(`frond:${frond.extends}`) : container;
|
|
596
|
+
const scope = above.createScope();
|
|
597
|
+
const frondLog = log.child(frond.name);
|
|
598
|
+
// A child of the APP logger, never of the boot's, whose `boot:` would outlive the boot.
|
|
599
|
+
scope.registerValue('Logger', container.resolve<Logger>('Logger').child(frond.name));
|
|
600
|
+
|
|
601
|
+
await registerReads(frond, scope, entityByName, options.sourcesFactory, frondLog);
|
|
602
|
+
const owners = ownershipOf(frond, assembly);
|
|
603
|
+
|
|
604
|
+
const declared = registerProviders(frond, scope, options.ports, assembly.boundPorts, assembly.refused, frondLog);
|
|
605
|
+
// An ancestor's links stand OUTSIDE this frond's own; a seam has no container key to inherit through.
|
|
606
|
+
const seams = inheritedSeams(frond, assembly.seamsOf, declared);
|
|
607
|
+
assembly.seamsOf.set(frond.name, seams);
|
|
608
|
+
registerStorages(frond, assembly, scope, seams, owners, frondLog);
|
|
609
|
+
registerFramesOf(frond, assembly, scope, frondLog);
|
|
610
|
+
|
|
611
|
+
const presenterMap = registerPresenters(frond, scope, frondLog);
|
|
612
|
+
const collectorTypeNames = registerCollectors(frond, scope, frondLog);
|
|
613
|
+
registerMiddlewares(frond, scope, assembly, frondLog);
|
|
614
|
+
exposePresenters(frond, presenterMap, container, scope);
|
|
615
|
+
|
|
616
|
+
const building: Building = { frond, assembly, scope, collectorTypeNames, presenterMap, frondLog };
|
|
617
|
+
const defaultHandlers = frond.handlers.filter((h) => !h.surface);
|
|
618
|
+
const surfaceHandlers = frond.handlers.filter((h) => h.surface);
|
|
619
|
+
buildDefaultFacades(building, defaultHandlers);
|
|
620
|
+
sayUnhandledEntities(building, defaultHandlers);
|
|
621
|
+
buildSurfaceFacades(building, surfaceHandlers);
|
|
622
|
+
sayWhatSurfacesServe(frond, surfaceHandlers, frondLog);
|
|
649
623
|
|
|
650
624
|
container.registerValue(`frond:${frond.name}`, scope);
|
|
651
625
|
frondLog.info(`registered — ${frond.entities.length} entities, ${frond.handlers.length} handlers, ${frond.seeds.length} seeds`);
|
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/seed.ts
CHANGED
|
@@ -67,7 +67,7 @@ function whatEachWaitsFor(
|
|
|
67
67
|
/** Where a seed writes, and what it may skip — resolved per entity. */
|
|
68
68
|
interface SeedFacade {
|
|
69
69
|
list(): Promise<unknown[]>;
|
|
70
|
-
write(item: unknown): Promise<unknown>;
|
|
70
|
+
write(item: Record<string, unknown>): Promise<unknown>;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -126,9 +126,7 @@ function facadeFor(app: App, entityName: string): SeedFacade | undefined {
|
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
const storage = app.storageFor(entityName)
|
|
130
|
-
| { list: () => Promise<unknown[]>; create: (input: unknown) => Promise<unknown> }
|
|
131
|
-
| undefined;
|
|
129
|
+
const storage = app.storageFor(entityName);
|
|
132
130
|
if (!storage) return undefined;
|
|
133
131
|
|
|
134
132
|
return { list: () => storage.list(), write: (item) => storage.create(item) };
|
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';
|
package/src/dispatch/InFlight.ts
CHANGED
|
@@ -36,11 +36,25 @@ export class InFlight {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
/** Close the door, and answer once the calls already running are done — or refuse at the deadline, naming what is left. */
|
|
40
|
+
async drain(timeoutMs?: number): Promise<void> {
|
|
40
41
|
this.accepting = false;
|
|
42
|
+
if (timeoutMs === undefined) return this.whenIdle();
|
|
43
|
+
|
|
44
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
45
|
+
|
|
46
|
+
await Promise.race([
|
|
47
|
+
this.whenIdle().then(() => clearTimeout(timer)),
|
|
48
|
+
new Promise<never>((_, reject) => {
|
|
49
|
+
timer = setTimeout(
|
|
50
|
+
() => reject(new Error(`[drain] ${this.running} call(s) still running after ${timeoutMs}ms`)),
|
|
51
|
+
timeoutMs,
|
|
52
|
+
);
|
|
53
|
+
}),
|
|
54
|
+
]);
|
|
41
55
|
}
|
|
42
56
|
|
|
43
|
-
whenIdle(): Promise<void> {
|
|
57
|
+
private whenIdle(): Promise<void> {
|
|
44
58
|
if (this.running === 0) return Promise.resolve();
|
|
45
59
|
|
|
46
60
|
return new Promise((resolve) => this.idle.push(resolve));
|
package/src/index.ts
CHANGED
|
@@ -91,7 +91,7 @@ export type { FougereOperations } from './wire/FougereOperations.js';
|
|
|
91
91
|
export type { HandlerOf } from './wire/HandlerOf.js';
|
|
92
92
|
export type { Refused } from './wire/Refused.js';
|
|
93
93
|
export type { Rows } from './wire/Rows.js';
|
|
94
|
-
export type
|
|
94
|
+
export { addressOf, type Facade } from './wire/Facade.js';
|
|
95
95
|
export type {
|
|
96
96
|
FrondCall,
|
|
97
97
|
Transport,
|
|
@@ -150,9 +150,7 @@ export type { PresenterEntry } from './descriptor/PresenterEntry.js';
|
|
|
150
150
|
export type { PresenterFieldMeta } from './descriptor/PresenterFieldMeta.js';
|
|
151
151
|
export type { ProviderEntry } from './descriptor/ProviderEntry.js';
|
|
152
152
|
export type { SeedEntry } from './descriptor/SeedEntry.js';
|
|
153
|
-
export type
|
|
154
|
-
export type { AuthContext } from './boot/AuthContext.js';
|
|
155
|
-
export type { AuthRuntime } from './boot/AuthRuntime.js';
|
|
153
|
+
export { AUTH, type AuthRuntime } from './boot/AuthRuntime.js';
|
|
156
154
|
export { Fronds } from './descriptor/Fronds.js';
|
|
157
155
|
export { verify, type Misplaced } from './verify.js';
|
|
158
156
|
// Same question as verify(), answered from the source text instead of the model.
|
package/src/node.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** What Fougere does with a filesystem, minus the scan — that is `@fougere/compiler`. */
|
|
2
2
|
export { setModuleLoader, getModuleLoader } from './loader.js';
|
|
3
|
-
export { loadConfig, loadCascadedConfig, remotesOf } from './FougereConfig.js';
|
|
3
|
+
export { configFileIn, loadConfig, loadCascadedConfig, remotesOf } from './FougereConfig.js';
|
|
4
4
|
export { statedModules } from './StatedModules.js';
|
|
5
5
|
export { defineFrond, loadFrondConfig } from './FrondConfig.js';
|
|
6
6
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { lowerFirst, type EntityConstructor, type SchemaView } from '@fougere/schema';
|
|
2
2
|
import type { ListOptions } from '../storage/ListOptions.js';
|
|
3
|
-
import type { ListResult } from '../storage/ListResult.js';
|
|
4
3
|
import type { Storage } from '../storage/Storage.js';
|
|
5
4
|
import type { OperationContract } from '../wire/OperationContract.js';
|
|
6
5
|
import { targetOf } from './prefab.js';
|
|
7
|
-
import type { CrudOpName } from './CrudOpName.js';
|
|
8
6
|
import type { CrudViews } from './CrudViews.js';
|
|
9
7
|
import { pageOf, type Page } from '../wire/Page.js';
|
|
10
8
|
import type { CrudOps } from './CrudOps.js';
|
|
@@ -88,15 +86,6 @@ function asCrudConstructor<T, V>(impl: object): CrudConstructor<T, V> {
|
|
|
88
86
|
return impl as CrudConstructor<T, V>;
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
/** The view an op emits, fabricated. */
|
|
92
|
-
type OutOf<V, K extends CrudOpName, T> =
|
|
93
|
-
// Bracketed on purpose: a naked `V extends …` DISTRIBUTES, and the no-view default
|
|
94
|
-
// is the empty map, whose `keyof` is `never` — distribution would then collapse
|
|
95
|
-
// every op's output to `never` instead of falling through to the entity.
|
|
96
|
-
[V] extends [EntityConstructor] ? InstanceType<V & EntityConstructor>
|
|
97
|
-
: K extends keyof V ? (V[K] extends EntityConstructor ? InstanceType<V[K]> : T)
|
|
98
|
-
: T;
|
|
99
|
-
|
|
100
89
|
/** The prefab handler class — its ops, plus the statics the bootstrap and adapters read. */
|
|
101
90
|
export interface CrudConstructor<T, V = {}> {
|
|
102
91
|
// `Storage<T>`, not the bare `Storage`: a handler that injects a second storage has to
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type Field, Shapes } from '@fougere/schema';
|
|
2
|
-
import type { ComparisonName } from './ComparisonName.js';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* The comparisons a criterion may name.
|
|
@@ -21,7 +20,11 @@ export interface Comparison {
|
|
|
21
20
|
isNull?: boolean;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
export
|
|
23
|
+
export type ComparisonName = keyof Comparison;
|
|
24
|
+
|
|
25
|
+
export const COMPARISONS = Object.keys({
|
|
26
|
+
gte: true, lte: true, gt: true, lt: true, ne: true, between: true, contains: true, notIn: true, isNull: true,
|
|
27
|
+
} satisfies Record<ComparisonName, true>);
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* Is this criterion a comparison, or a value that happens to be an object?
|
|
@@ -42,11 +45,11 @@ export function comparisonOf(field: Field | undefined, asked: unknown): Comparis
|
|
|
42
45
|
/** The comparisons a criterion names, in the order the caller wrote them. */
|
|
43
46
|
export function comparisonsIn(comparison: Comparison): [ComparisonName, unknown][] {
|
|
44
47
|
return Object.entries(comparison).filter(
|
|
45
|
-
(entry): entry is [ComparisonName, unknown] =>
|
|
48
|
+
(entry): entry is [ComparisonName, unknown] => COMPARISONS.includes(entry[0]),
|
|
46
49
|
);
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
/** What a comparison names that this vocabulary does not — a typo, said as one. */
|
|
50
53
|
export function unknownIn(comparison: Comparison): string[] {
|
|
51
|
-
return Object.keys(comparison).filter((name) => !
|
|
54
|
+
return Object.keys(comparison).filter((name) => !COMPARISONS.includes(name));
|
|
52
55
|
}
|
package/src/wire/Emit.ts
CHANGED
|
@@ -35,7 +35,7 @@ export function factOfEmitKey(key: string): string | undefined {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/** The fact behind an awaited emission's key — the dual, read the same way. */
|
|
38
|
-
|
|
38
|
+
function factOfAwaitKey(key: string): string | undefined {
|
|
39
39
|
return behind(key, AWAIT_SUFFIX);
|
|
40
40
|
}
|
|
41
41
|
|
package/src/wire/Facade.ts
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { FougereOperations } from './FougereOperations.js';
|
|
3
|
-
|
|
4
|
-
type Served = keyof FougereOperations & string;
|
|
1
|
+
import { lowerFirst } from '@fougere/schema';
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
* Both halves below take the union through a type PARAMETER, which is what makes the
|
|
8
|
-
* conditional distribute: `Served` is an alias, and an alias is matched whole — `'a.x' | 'b.y'`
|
|
9
|
-
* does not extend `` `${string}.${string}` `` as one thing, so the naive form answers `never`.
|
|
10
|
-
*/
|
|
11
|
-
type AddressIn<Key> = Key extends `${infer Address}.${string}` ? Address : never;
|
|
3
|
+
import type { InvocationContext } from './InvocationContext.js';
|
|
12
4
|
|
|
13
5
|
/**
|
|
14
6
|
* The facade built in front of a handler — the framework's second port, after `Storage`.
|
|
@@ -34,6 +26,21 @@ export function facadeKeyOf(entityName: string, surface?: string): string {
|
|
|
34
26
|
return surface ? `${surface}:${entityName}Handler` : `${entityName}Handler`;
|
|
35
27
|
}
|
|
36
28
|
|
|
29
|
+
/**
|
|
30
|
+
* The address a handler answers at, read off its class name or its facade key — the dual of
|
|
31
|
+
* `facadeKeyOf`. `PostHandler` → `post`, `postHandler` → `post`.
|
|
32
|
+
*/
|
|
33
|
+
export function addressOf(name: string): string {
|
|
34
|
+
return lowerFirst(name.endsWith(HANDLER) ? name.slice(0, -HANDLER.length) : name);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A default facade's key — `postHandler`, never `admin:postHandler` nor `PostStorage`. */
|
|
38
|
+
export function isFacadeKey(key: string): boolean {
|
|
39
|
+
return key.endsWith(HANDLER) && !key.includes(':');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const HANDLER = 'Handler';
|
|
43
|
+
|
|
37
44
|
/** Where the contracts behind a façade live — the dual of `facadeKeyOf`. */
|
|
38
45
|
export function contractsKeyOf(entityName: string, surface?: string): string {
|
|
39
46
|
return `${facadeKeyOf(entityName, surface)}:contracts`;
|
|
@@ -117,7 +117,7 @@ export function inferOperationKind(name: string): OperationKindInference {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
/** Explicit config is authoritative; otherwise the finite convention may decline. */
|
|
120
|
-
|
|
120
|
+
function resolveOperationKind(
|
|
121
121
|
name: string,
|
|
122
122
|
overrides?: Record<string, { kind?: OperationKind }>,
|
|
123
123
|
): OperationKind | undefined {
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
import type { Param } from './Param.js';
|
|
2
|
-
import type { TypeRef } from './TypeRef.js';
|
|
3
1
|
import type { OperationContract } from './OperationContract.js';
|
|
4
2
|
|
|
5
3
|
/** Map of operation name → its contract. */
|
|
6
4
|
export type OperationsMap = Map<string, OperationContract>;
|
|
7
|
-
|
|
8
|
-
export type { TypeRef, Param };
|
|
9
|
-
|
|
10
|
-
// ─── Operation intent (read vs write) ──────────
|
|
11
|
-
// Naming convention for inferred handlers — scheduled to die with the
|
|
12
|
-
// handler-kind plan (docs/notes/handler-kind.md). Lives here, NOT in
|
|
13
|
-
// @fougere/schema: it is a runtime convention about operations, not a
|
|
14
|
-
// schema concept.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { SchemaView } from '@fougere/schema';
|
|
2
|
-
import type { AuthContext } from './AuthContext.js';
|
|
3
|
-
import type { AuthRuntime } from './AuthRuntime.js';
|
|
4
|
-
/** Lazy auth declaration written in fougere.config.ts. */
|
|
5
|
-
export interface AuthConfig {
|
|
6
|
-
/** Build the runtime — invoked by createApp at boot with the resolved storage handles. */
|
|
7
|
-
create(ctx: AuthContext): AuthRuntime | Promise<AuthRuntime>;
|
|
8
|
-
/**
|
|
9
|
-
* Entities the provider will use. Optional metadata — useful for the core's
|
|
10
|
-
* future migration registry to know which tables auth needs.
|
|
11
|
-
*/
|
|
12
|
-
entities?: Record<string, SchemaView>;
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=AuthConfig.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AuthConfig.d.ts","sourceRoot":"","sources":["../../src/boot/AuthConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,0DAA0D;AAC1D,MAAM,WAAW,UAAU;IACzB,0FAA0F;IAC1F,MAAM,CAAC,GAAG,EAAE,WAAW,GAAG,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7D;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACvC"}
|
package/dist/boot/AuthConfig.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AuthConfig.js","sourceRoot":"","sources":["../../src/boot/AuthConfig.ts"],"names":[],"mappings":""}
|