@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/dist/boot/Hosting.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { SchemaView } from '@fougere/schema';
|
|
|
2
2
|
import type { Constraint } from '../Constraint.js';
|
|
3
3
|
import type { Peer } from './Peer.js';
|
|
4
4
|
import type { Journal } from '../dispatch/Journal.js';
|
|
5
|
+
import type { Storage } from '../storage/Storage.js';
|
|
5
6
|
/**
|
|
6
7
|
* Where an entity's rows are hosted, and what the host keeps — what a `ref()` asks the boot.
|
|
7
8
|
*
|
|
@@ -16,7 +17,7 @@ export interface Hosting {
|
|
|
16
17
|
* Resolved through the frond that owns it, never the asking frond's scope: a scope sees its
|
|
17
18
|
* parent and not its siblings, so a target of another frond was answered by nothing.
|
|
18
19
|
*/
|
|
19
|
-
storageOf(entity: string):
|
|
20
|
+
storageOf(entity: string): Storage | undefined;
|
|
20
21
|
/** Every entity of every frond — a row is named from wherever its namer was declared. */
|
|
21
22
|
entities(): ReadonlyMap<string, SchemaView>;
|
|
22
23
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Hosting.d.ts","sourceRoot":"","sources":["../../src/boot/Hosting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"Hosting.d.ts","sourceRoot":"","sources":["../../src/boot/Hosting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAErD;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,+FAA+F;IAC/F,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC;IAC1D;;;OAGG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;IAC/C,yFAAyF;IACzF,QAAQ,IAAI,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC5C;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC;;;;;;OAMG;IACH,KAAK,IAAI,SAAS,IAAI,EAAE,CAAC;IACzB,yFAAyF;IACzF,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC;CAC9C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/boot/bootstrap.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../../src/boot/bootstrap.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAymB9D,uCAAuC;AACvC,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,CAwHvE"}
|
package/dist/boot/bootstrap.js
CHANGED
|
@@ -12,19 +12,16 @@ import { Carry } from '../builtin/Carry.js';
|
|
|
12
12
|
import { Logger } from '../builtin/Logger.js';
|
|
13
13
|
import LogLine, { CARRIES_LINE } from '../builtin/LogLine.js';
|
|
14
14
|
import { emitKeyOf } from '../wire/Emit.js';
|
|
15
|
-
/** The fact the boot announces, spelled once. */
|
|
16
|
-
const LOG_LINE = lowerFirst(LogLine.name);
|
|
17
15
|
import { Config } from '../builtin/config.js';
|
|
18
16
|
import { createRemoteRouter, createRemoteFacade } from './remote.js';
|
|
19
17
|
import { Emissions } from './Emissions.js';
|
|
20
18
|
import { resolveEffectiveOperations } from '../EffectiveOperationModel.js';
|
|
21
19
|
import { InFlight } from '../dispatch/InFlight.js';
|
|
22
|
-
// The keys, each read from where its concept is declared — never respelled here.
|
|
23
20
|
import { RPC_ENTITY } from '../wire/RpcAnswer.js';
|
|
24
21
|
import { Invocation } from '../wire/Invocation.js';
|
|
25
|
-
import { facadeKeyOf } from '../wire/Facade.js';
|
|
22
|
+
import { addressOf, facadeKeyOf, isFacadeKey } from '../wire/Facade.js';
|
|
26
23
|
import { identityCardOf } from './card.js';
|
|
27
|
-
import { AppLifecycle, migrating } from './AppLifecycle.js';
|
|
24
|
+
import { AppLifecycle, closeAll, migrating } from './AppLifecycle.js';
|
|
28
25
|
import { seeding } from './seed.js';
|
|
29
26
|
import { storageKeyOf } from '../storage/Storage.js';
|
|
30
27
|
import { presenterKeyOf } from '../prefab/presenter.js';
|
|
@@ -36,6 +33,7 @@ import { OperationRoute } from '../dispatch/OperationRoute.js';
|
|
|
36
33
|
import { remoteRoutes } from '../dispatch/remoteRoutes.js';
|
|
37
34
|
import { RouteRegistry } from '../dispatch/RouteRegistry.js';
|
|
38
35
|
import { facadeOperations } from '../entry/facade.js';
|
|
36
|
+
const LOG_LINE = lowerFirst(LogLine.name);
|
|
39
37
|
/** The one wording for "nobody hosts this here", with both ways out. */
|
|
40
38
|
const notLoaded = (entity) => `Frond for '${entity}' is not loaded.\n` +
|
|
41
39
|
` - Add '${entity}' to --fronds flag\n` +
|
|
@@ -87,11 +85,11 @@ async function readFronds(options, log) {
|
|
|
87
85
|
.flatMap((extension) => extension?.fronds ?? [])
|
|
88
86
|
.map((frond) => ({ ...frond, brought: true }));
|
|
89
87
|
const { fronds, diagnostics } = await hostedBy(brought.length > 0 ? { ...options, fronds: [...(options.fronds ?? []), ...brought] } : options);
|
|
90
|
-
// An app that states nothing AND scans nothing is a mistake — unless
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
if (!options.fronds && !options.scan &&
|
|
88
|
+
// An app that states nothing AND scans nothing is a mistake — unless an extension brings
|
|
89
|
+
// fronds of its own. Refused here and not in `hostedBy`, which is handed the frond sources
|
|
90
|
+
// and cannot see the rest of the app. The condition is the KEYS, not the count: a scan that
|
|
91
|
+
// found nothing is an ordinary answer.
|
|
92
|
+
if (!options.fronds && !options.scan && brought.length === 0) {
|
|
95
93
|
throw new Error('createApp needs `fronds:` (what this app states) or `scan:` (what a scanner found). '
|
|
96
94
|
+ 'Neither was given, and nothing else declares entities of its own.\n'
|
|
97
95
|
+ ' createApp({ fronds: [blog] })\n'
|
|
@@ -177,25 +175,20 @@ function peerBehind(entity, router) {
|
|
|
177
175
|
release: async (named, key) => { await ask('release', { entity: named, key }); },
|
|
178
176
|
};
|
|
179
177
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (
|
|
184
|
-
return
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
inflight.whenIdle().then(() => clearTimeout(timer)),
|
|
188
|
-
new Promise((_, reject) => {
|
|
189
|
-
timer = setTimeout(() => reject(new Error(`[drain] ${inflight.count} call(s) still running after ${timeoutMs}ms`)), timeoutMs);
|
|
190
|
-
}),
|
|
191
|
-
]);
|
|
178
|
+
function ownedBy(fronds, container, entity, key) {
|
|
179
|
+
const owner = fronds.owner(entity);
|
|
180
|
+
const scopeKey = `frond:${owner?.name}`;
|
|
181
|
+
if (!owner || !container.has(scopeKey))
|
|
182
|
+
return undefined;
|
|
183
|
+
const scope = container.resolve(scopeKey);
|
|
184
|
+
return scope.has(key) ? scope.resolve(key) : undefined;
|
|
192
185
|
}
|
|
193
186
|
function dispatching(options, log, fronds, journalOf) {
|
|
194
187
|
const inflight = new InFlight();
|
|
195
188
|
const globalMiddlewares = [];
|
|
196
189
|
const scopedMiddlewares = new Map();
|
|
197
190
|
const routeRegistry = new RouteRegistry();
|
|
198
|
-
const dispatchLifecycle = new DispatchLifecycle(
|
|
191
|
+
const dispatchLifecycle = new DispatchLifecycle([], (error, event) => log.error(`[dispatch-observer] ${event.stage} ${event.call.address.toString()}`, error));
|
|
199
192
|
return {
|
|
200
193
|
inflight,
|
|
201
194
|
routeRegistry,
|
|
@@ -235,7 +228,7 @@ function whatTheAppKnows(fronds, container, options, refused) {
|
|
|
235
228
|
new Logger('boot:app'), refused, options.onEmit),
|
|
236
229
|
};
|
|
237
230
|
}
|
|
238
|
-
function hostingFor({ fronds, container, options,
|
|
231
|
+
function hostingFor({ fronds, container, options, remoteRouter, entityByName, frondOf, storageOf }) {
|
|
239
232
|
// What carries a release writes none: an instrumentation frond's own rows are kept while a
|
|
240
233
|
// release happens, and journalling them would begin one inside the one being written down.
|
|
241
234
|
const carriesRelease = new Set(fronds.filter((one) => one.brought).flatMap((one) => one.entities.map((entity) => entity.name)));
|
|
@@ -265,7 +258,7 @@ function hostingFor({ fronds, container, options, declaredRemotes, remoteRouter,
|
|
|
265
258
|
// off a card, while the question here is asked of a PROCESS about rows it may be alone in
|
|
266
259
|
// knowing about. Nothing is cached — a peer that was down at boot answers the next call.
|
|
267
260
|
peers: () => (options.remoteTransport
|
|
268
|
-
?
|
|
261
|
+
? Object.values(options.remotes ?? {}).map((url) => peerOver(options.remoteTransport(url)))
|
|
269
262
|
: []),
|
|
270
263
|
peerOf: (entity) => (remoteRouter ? peerBehind(entity, remoteRouter) : undefined),
|
|
271
264
|
};
|
|
@@ -277,15 +270,13 @@ function hostingFor({ fronds, container, options, declaredRemotes, remoteRouter,
|
|
|
277
270
|
* `facadeFor` is THE rule, stated once: every projection reads it and nothing else.
|
|
278
271
|
*/
|
|
279
272
|
function readings({ container, fronds, remoteRouter, localDispatcher, routeRegistry, effectiveByKey, log }) {
|
|
280
|
-
/** Stop taking calls, and resolve once the ones already running are done. */
|
|
281
273
|
const resolve = (name) => {
|
|
282
274
|
try {
|
|
283
275
|
return container.resolve(name);
|
|
284
276
|
}
|
|
285
277
|
catch (err) {
|
|
286
|
-
if (
|
|
287
|
-
throw new Error(notLoaded(name
|
|
288
|
-
}
|
|
278
|
+
if (isFacadeKey(name) && !remoteRouter)
|
|
279
|
+
throw new Error(notLoaded(addressOf(name)));
|
|
289
280
|
throw err;
|
|
290
281
|
}
|
|
291
282
|
};
|
|
@@ -335,252 +326,192 @@ function readings({ container, fronds, remoteRouter, localDispatcher, routeRegis
|
|
|
335
326
|
if (!surface)
|
|
336
327
|
return facadeAt(facadeKeyOf(entity), true);
|
|
337
328
|
const own = facadeAt(facadeKeyOf(entity, surface), false);
|
|
338
|
-
|
|
339
|
-
if (!owner) {
|
|
329
|
+
if (!fronds.owner(entity)) {
|
|
340
330
|
sayNoSurfaceAcross(entity, surface);
|
|
341
331
|
return own;
|
|
342
332
|
}
|
|
343
|
-
const
|
|
344
|
-
if (
|
|
345
|
-
return own;
|
|
346
|
-
if (!declared.some((n) => n.toLowerCase() === entity.toLowerCase()))
|
|
333
|
+
const admitted = fronds.admits(surface, entity);
|
|
334
|
+
if (admitted === false)
|
|
347
335
|
return undefined;
|
|
348
|
-
if (own)
|
|
336
|
+
if (own || admitted === undefined)
|
|
349
337
|
return own;
|
|
350
338
|
const fallback = facadeAt(facadeKeyOf(entity), false);
|
|
351
339
|
return fallback
|
|
352
340
|
? facadeOperations(localDispatcher, entity, routeRegistry.operationNames(entity, surface), surface)
|
|
353
341
|
: undefined;
|
|
354
342
|
};
|
|
355
|
-
/** The terms beside a facade, with the exact same named-surface fallback rule. */
|
|
356
343
|
const operationsFor = (entity, surface) => {
|
|
357
344
|
if (!surface)
|
|
358
345
|
return effectiveByKey.get(facadeKeyOf(entity));
|
|
359
346
|
const own = effectiveByKey.get(facadeKeyOf(entity, surface));
|
|
360
|
-
const
|
|
361
|
-
if (
|
|
362
|
-
return own;
|
|
363
|
-
return declared.some((name) => name.toLowerCase() === entity.toLowerCase())
|
|
364
|
-
? (own ?? effectiveByKey.get(facadeKeyOf(entity)))
|
|
365
|
-
: undefined;
|
|
366
|
-
};
|
|
367
|
-
/** The presenter of an entity, resolved through its owning frond's scope. */
|
|
368
|
-
const presenterFor = (entity) => {
|
|
369
|
-
const owner = fronds.owner(entity);
|
|
370
|
-
if (!owner)
|
|
347
|
+
const admitted = fronds.admits(surface, entity);
|
|
348
|
+
if (admitted === false)
|
|
371
349
|
return undefined;
|
|
372
|
-
|
|
373
|
-
return container.resolve(`frond:${owner.name}`).resolve(presenterKeyOf(entity));
|
|
374
|
-
}
|
|
375
|
-
catch {
|
|
376
|
-
return undefined;
|
|
377
|
-
}
|
|
350
|
+
return admitted ? (own ?? effectiveByKey.get(facadeKeyOf(entity))) : own;
|
|
378
351
|
};
|
|
352
|
+
const presenterFor = (entity) => ownedBy(fronds, container, entity, presenterKeyOf(entity));
|
|
379
353
|
return { resolve, schemaFor, facadeFor, operationsFor, presenterFor };
|
|
380
354
|
}
|
|
381
355
|
/**
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
* flows through `Storage` like any other.
|
|
356
|
+
* Registered under the class name, for type-based DI. A logger holds no level — it reads
|
|
357
|
+
* `setLogLevel`'s at each line — and a frond declaring `class X extends Logger` takes the key.
|
|
385
358
|
*/
|
|
386
|
-
|
|
387
|
-
|
|
359
|
+
function registerBuiltins(container, carry) {
|
|
360
|
+
container.registerValue('Logger', new Logger('app', { carry }));
|
|
361
|
+
container.register('Config', Config, { lifetime: 'singleton' });
|
|
362
|
+
}
|
|
363
|
+
/** `remotes:` IS the topology statement: the frond's code may sit here, it runs elsewhere. */
|
|
364
|
+
function remoteRouterOf(options) {
|
|
365
|
+
const declared = Object.entries(options.remotes ?? {});
|
|
366
|
+
if (declared.length === 0)
|
|
388
367
|
return undefined;
|
|
389
|
-
if (!options.
|
|
390
|
-
throw new Error('createApp: `
|
|
368
|
+
if (!options.remoteTransport) {
|
|
369
|
+
throw new Error('createApp: `remotes` is declared but `remoteTransport` is missing — pass one (e.g. from @fougere/transport-http).');
|
|
391
370
|
}
|
|
392
|
-
|
|
393
|
-
|
|
371
|
+
return createRemoteRouter(Object.fromEntries(declared), options.remoteTransport);
|
|
372
|
+
}
|
|
373
|
+
/** Every check of a phase, said at once — a boot that stops at the first shows the next after a restart. */
|
|
374
|
+
function refuseWhatDoesNotHold(refused) {
|
|
375
|
+
const refusal = refusalOf(refused, 'declaration(s) that do not hold');
|
|
376
|
+
if (refusal)
|
|
377
|
+
throw refusal;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* A `ports:` key no port matched reads as a choice that was made. Said once every frond is
|
|
381
|
+
* installed: the entry is app-wide, and no single frond can tell a typo from a neighbour's port.
|
|
382
|
+
*/
|
|
383
|
+
function warnAboutUnboundPorts(options, boundPorts, log) {
|
|
384
|
+
const unbound = Object.keys(options.ports ?? {}).filter((port) => !boundPorts.has(port));
|
|
385
|
+
if (unbound.length === 0)
|
|
386
|
+
return;
|
|
387
|
+
log.warn(`[ports] ${unbound.join(', ')} — named in fougere.config.ts, but no scanned class extends `
|
|
388
|
+
+ 'them, so nothing was chosen. Check the spelling, or drop the entry.');
|
|
389
|
+
}
|
|
390
|
+
/** Read from who SUBSCRIBED, so a third party's destination is left alone by the two middlewares that observe every operation. */
|
|
391
|
+
function markLineCarriers(emissions) {
|
|
392
|
+
for (const facade of emissions.facadesFor(LOG_LINE)) {
|
|
393
|
+
CARRIES_LINE.add(addressOf(facade));
|
|
394
|
+
CARRIES_LINE.add(facade);
|
|
394
395
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* A handler another process serves: a stand-in for whoever asks for it by type, and a route for
|
|
399
|
+
* whoever calls it. A dependency names the type as written — `ProductHandler` — while a card
|
|
400
|
+
* declares `product`.
|
|
401
|
+
*/
|
|
402
|
+
function answerRemotes({ container, routeRegistry, dispatcher, getMiddlewares, options, state }, remoteRouter, fronds) {
|
|
403
|
+
// Only for what another process may serve: an entity a frond hosts HERE and gives no handler
|
|
404
|
+
// is served by nobody, and a stand-in for it had GraphQL project an entity with no operation.
|
|
405
|
+
const servedElsewhere = (address) => {
|
|
406
|
+
const owner = fronds.owner(address);
|
|
407
|
+
return owner === undefined || owner.name in (options.remotes ?? {});
|
|
408
|
+
};
|
|
409
|
+
container.setFallback((name) => (isFacadeKey(name) && servedElsewhere(addressOf(name))
|
|
410
|
+
? facadeOperations(dispatcher, addressOf(name))
|
|
411
|
+
: undefined));
|
|
412
|
+
const remoteFacades = new Map();
|
|
413
|
+
routeRegistry.addResolver(remoteRoutes((entity) => {
|
|
414
|
+
const known = remoteFacades.get(entity);
|
|
415
|
+
if (known)
|
|
416
|
+
return known;
|
|
417
|
+
const facade = createRemoteFacade(entity, remoteRouter, getMiddlewares, state);
|
|
418
|
+
remoteFacades.set(entity, facade);
|
|
419
|
+
return facade;
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
422
|
+
/** Refused rather than replaced: two declarations of one name would make the answer depend on wiring order. */
|
|
423
|
+
function serveRpcOn(routeRegistry) {
|
|
424
|
+
return (op, answer) => {
|
|
425
|
+
const address = new RouteAddress({ entity: 'rpc', operation: op });
|
|
426
|
+
if (routeRegistry.find(address)) {
|
|
427
|
+
throw new Error(`[claim] rpc operation '${op}' is already served; a second declaration would depend on wiring order.\n`
|
|
428
|
+
+ ' Two extensions declare it — keep one out of `extensions:`.');
|
|
429
|
+
}
|
|
430
|
+
routeRegistry.register(new OperationRoute('system', address, (call) => answer(call.invocation, call.address.surface)));
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
/** A frond's extension travels with its code, so it mounts on whichever process serves that frond. */
|
|
434
|
+
function frondExtensions(fronds) {
|
|
435
|
+
return fronds.flatMap((frond) => (frond.extensions ?? []).map((one) => ({ name: one.name, ...one.extension })));
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* The boot's held lines, and every line after them, handed to the app's destinations — or let go
|
|
439
|
+
* when it has none. After the ascent, because handing them over resolves the destination, and what
|
|
440
|
+
* an extension's destination depends on is registered by that extension's `up`: resolved before,
|
|
441
|
+
* every held line died on `'LogRing' is not registered` (`demos/observability`). A line keeps
|
|
442
|
+
* `at`, when it was WRITTEN, which for a held boot line is not when it is handed over.
|
|
443
|
+
*/
|
|
444
|
+
function announceLines(emissions, container, carry) {
|
|
445
|
+
if (!emissions.listensTo().includes(LOG_LINE)) {
|
|
446
|
+
carry.forget();
|
|
447
|
+
return undefined;
|
|
448
|
+
}
|
|
449
|
+
const emit = container.resolve(emitKeyOf(LogLine.name));
|
|
450
|
+
return carry.to(({ at, ...line }) => void emit({ ...line, at: new Date(at) }));
|
|
399
451
|
}
|
|
400
452
|
/** Bootstrap a fougere application. */
|
|
401
453
|
export async function createApp(options) {
|
|
402
454
|
const container = (options.createContainer ?? createContainer)();
|
|
403
|
-
//
|
|
404
|
-
// releasing has to work from the first line the boot takes something.
|
|
405
|
-
// The conventional ascent, ordered here: tables, then rows, then whatever the host took
|
|
406
|
-
// on. Four hosts assembled these two members themselves — the order is not theirs to
|
|
407
|
-
// choose, and a host that forgot lost its migration in silence.
|
|
455
|
+
// Tables, then rows, then whatever the host took on — the order is not a host's to choose.
|
|
408
456
|
const appLifecycle = new AppLifecycle().add(migrating(options.migrate), seeding(), ...(options.extensions ?? []));
|
|
409
|
-
/** The app once it exists — a refusal before that releases the two levels that do. */
|
|
410
|
-
let built;
|
|
411
|
-
/** Where THIS boot's lines wait — never a process-wide slot, see `Carry`. */
|
|
412
457
|
const carry = new Carry();
|
|
413
|
-
|
|
458
|
+
let built;
|
|
414
459
|
let stopAnnouncing;
|
|
415
|
-
/**
|
|
416
|
-
* Everything this app holds, let go in reverse of how it was taken: what an extension took on
|
|
417
|
-
* last, then the container's own, then whoever handed a resource in.
|
|
418
|
-
*/
|
|
460
|
+
/** What an extension took on last, then the container's own, then whoever handed a resource in. */
|
|
419
461
|
const release = async () => {
|
|
420
|
-
// Every level is told to close even when one refuses, and the refusals leave together —
|
|
421
|
-
// the rule `Lifecycle.down` applies INSIDE its list, applied ACROSS the three. Stated
|
|
422
|
-
// there and broken here, a refusing extension took the container and the connection
|
|
423
|
-
// down with it, which is the leak this gesture exists to prevent.
|
|
424
|
-
const refused = [];
|
|
425
|
-
// Whatever is still held will never reach a destination — the console had it.
|
|
426
462
|
stopAnnouncing?.();
|
|
427
463
|
carry.forget();
|
|
428
|
-
|
|
464
|
+
await closeAll([
|
|
429
465
|
...(built ? [() => appLifecycle.down(built)] : []),
|
|
430
466
|
() => container.dispose(),
|
|
431
467
|
() => options.onDispose?.(),
|
|
432
|
-
];
|
|
433
|
-
for (const level of levels) {
|
|
434
|
-
try {
|
|
435
|
-
await level();
|
|
436
|
-
}
|
|
437
|
-
catch (error) {
|
|
438
|
-
// Flattened one level: an extension's refusals are already an AggregateError, and
|
|
439
|
-
// nesting them would make the caller unwrap twice to read one list.
|
|
440
|
-
if (error instanceof AggregateError)
|
|
441
|
-
refused.push(...error.errors);
|
|
442
|
-
else
|
|
443
|
-
refused.push(error);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
if (refused.length > 0) {
|
|
447
|
-
throw new AggregateError(refused, `${refused.length} refusal(s) while releasing the app`);
|
|
448
|
-
}
|
|
468
|
+
], 'refusal(s) while releasing the app');
|
|
449
469
|
};
|
|
450
470
|
try {
|
|
451
|
-
// Boot chatter is debug by default; a host (e.g. the CLI) can quiet it.
|
|
452
471
|
const log = new Logger('boot:app', { carry });
|
|
453
|
-
|
|
454
|
-
// No level here and none anywhere: a logger consults `setLogLevel`'s value at each
|
|
455
|
-
// emission, so this instance survives a level change and so does every handler that
|
|
456
|
-
// was handed it. A frond declaring `class X extends Logger` takes this key over,
|
|
457
|
-
// like any other port.
|
|
458
|
-
container.registerValue('Logger', new Logger('app', { carry }));
|
|
459
|
-
container.register('Config', Config, { lifetime: 'singleton' });
|
|
472
|
+
registerBuiltins(container, carry);
|
|
460
473
|
log.debug('builtins registered (Logger, Config)');
|
|
461
474
|
const { fronds, operationModel } = await readFronds(options, log);
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
// A remote declaration wins over local presence: `remotes: { blog: url }` IS
|
|
465
|
-
// the topology statement — the frond's code may sit in fronds/**, it runs elsewhere.
|
|
466
|
-
const declaredRemotes = Object.entries(options.remotes ?? {});
|
|
467
|
-
if (declaredRemotes.length > 0 && !options.remoteTransport) {
|
|
468
|
-
throw new Error('createApp: `remotes` is declared but `remoteTransport` is missing — pass one (e.g. from @fougere/transport-http).');
|
|
469
|
-
}
|
|
470
|
-
const remoteRouter = declaredRemotes.length > 0 && options.remoteTransport
|
|
471
|
-
? createRemoteRouter(Object.fromEntries(declaredRemotes), options.remoteTransport)
|
|
472
|
-
: undefined;
|
|
475
|
+
appLifecycle.add(...frondExtensions(fronds));
|
|
476
|
+
const remoteRouter = remoteRouterOf(options);
|
|
473
477
|
const { inflight, routeRegistry, dispatchLifecycle, dispatcher, localDispatcher, getMiddlewares, use } = dispatching(options, log, fronds, () => journalOf());
|
|
474
|
-
/** What every check of this boot writes into — refused together, once they have all run. */
|
|
475
478
|
const refused = [];
|
|
476
|
-
const relations = [];
|
|
477
479
|
keyClaims(fronds, options.remotes, refused);
|
|
478
|
-
|
|
479
|
-
// the route registry collides first, and names a route instead of the two fronds.
|
|
480
|
-
const claimed = refusalOf(refused, 'declaration(s) that do not hold');
|
|
481
|
-
if (claimed)
|
|
482
|
-
throw claimed;
|
|
480
|
+
refuseWhatDoesNotHold(refused);
|
|
483
481
|
const { entityByName, frondOf, emissions } = whatTheAppKnows(fronds, container, options, refused);
|
|
484
|
-
/** Canonical operation tables, indexed by the same audience key as their facades. */
|
|
485
482
|
const effectiveByKey = new Map();
|
|
486
|
-
const
|
|
487
|
-
/**
|
|
488
|
-
* The storage an entity is backed by — the dual of `facadeFor`, which serves its client-facing
|
|
489
|
-
* facade.
|
|
490
|
-
*/
|
|
491
|
-
const storageFor = (entity) => {
|
|
492
|
-
const owner = fronds.owner(entity);
|
|
493
|
-
if (!owner)
|
|
494
|
-
return undefined;
|
|
495
|
-
const key = storageKeyOf(entity);
|
|
496
|
-
try {
|
|
497
|
-
return container.resolve(`frond:${owner.name}`).resolve(key);
|
|
498
|
-
}
|
|
499
|
-
catch {
|
|
500
|
-
return undefined;
|
|
501
|
-
}
|
|
502
|
-
};
|
|
503
|
-
// What carries a release writes none: an instrumentation frond's own rows are kept while a
|
|
504
|
-
// release happens, and journalling them would begin one inside the one being written down.
|
|
483
|
+
const storageFor = (entity) => ownedBy(fronds, container, entity, storageKeyOf(entity));
|
|
505
484
|
const { hosting, journalOf } = hostingFor({
|
|
506
|
-
fronds, container, options,
|
|
485
|
+
fronds, container, options, remoteRouter, entityByName, frondOf, storageOf: storageFor,
|
|
507
486
|
});
|
|
508
|
-
|
|
509
|
-
// can say so rather than look obeyed.
|
|
487
|
+
const relations = [];
|
|
510
488
|
const boundPorts = new Set();
|
|
511
|
-
// Register frond scopes
|
|
512
|
-
// What every frond is installed into, and reads while it is: one container, one route
|
|
513
|
-
// table, one emission list — so what a frond serves is there for the next one to find.
|
|
514
489
|
const assembly = {
|
|
515
490
|
container, routeRegistry, emissions, dispatcher, localDispatcher, effectiveByKey,
|
|
516
491
|
boundPorts, refused, relations, hosting, operationModel, entityByName, frondOf, contractsOf,
|
|
517
492
|
getMiddlewares, use, middlewaresOf: new Map(), seamsOf: new Map(), log, options,
|
|
493
|
+
state: appLifecycle.state(),
|
|
518
494
|
};
|
|
519
495
|
for (const frond of fronds)
|
|
520
496
|
await installFrond(frond, assembly);
|
|
521
497
|
warnAboutRelations(relations, hosting, log);
|
|
522
498
|
refused.push(...unpaired(fronds.flatMap((frond) => frond.entities), hosting));
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
if (bootRefusal)
|
|
527
|
-
throw bootRefusal;
|
|
528
|
-
// A `ports:` key that matched no port anywhere reads as a choice that was made, and
|
|
529
|
-
// was not. Said once, at the end, because the entry is app-wide while a port is a
|
|
530
|
-
// frond's — no single frond can tell whether a key is a typo or a neighbour's.
|
|
531
|
-
const unused = Object.keys(options.ports ?? {}).filter((port) => !boundPorts.has(port));
|
|
532
|
-
if (unused.length > 0) {
|
|
533
|
-
log.warn(`[ports] ${unused.join(', ')} — named in fougere.config.ts, but no scanned class extends `
|
|
534
|
-
+ 'them, so nothing was chosen. Check the spelling, or drop the entry.');
|
|
535
|
-
}
|
|
536
|
-
// Once every facade exists: what is announced here and what is listened to are both known.
|
|
499
|
+
refuseWhatDoesNotHold(refused);
|
|
500
|
+
warnAboutUnboundPorts(options, boundPorts, log);
|
|
501
|
+
// A pipe order and an `Emit<T, A>` are read here, once every facade exists.
|
|
537
502
|
emissions.register();
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
throw announced;
|
|
543
|
-
// Which facades carry a line, read from who SUBSCRIBED — so a third party's destination
|
|
544
|
-
// is left alone by the two middlewares that observe every operation.
|
|
545
|
-
for (const facade of emissions.facadesFor(LOG_LINE)) {
|
|
546
|
-
CARRIES_LINE.add(facade.replace(/Handler$/, '').replace(/^./, (c) => c.toLowerCase()));
|
|
547
|
-
CARRIES_LINE.add(facade);
|
|
548
|
-
}
|
|
549
|
-
/** The last resort, held by the container so every resolution path shares it. */
|
|
550
|
-
container.setFallback?.((name) => {
|
|
551
|
-
if (!remoteRouter)
|
|
552
|
-
return undefined;
|
|
553
|
-
if (!name.endsWith('Handler') || name.includes(':'))
|
|
554
|
-
return undefined;
|
|
555
|
-
// Façade-shaped stand-in; routing happens lazily at the first call. Through
|
|
556
|
-
// `lowerFirst` because a DEPENDENCY names the type as written — `ProductHandler`,
|
|
557
|
-
// PascalCase — while a card declares `product`, so the raw strip asked the router for
|
|
558
|
-
// 'Product' and every by-type dependency on a remote handler answered NOT_FOUND.
|
|
559
|
-
return facadeOperations(dispatcher, lowerFirst(name.replace(/Handler$/, '')));
|
|
560
|
-
});
|
|
561
|
-
if (remoteRouter) {
|
|
562
|
-
const remoteFacades = new Map();
|
|
563
|
-
routeRegistry.addResolver(remoteRoutes((entity) => {
|
|
564
|
-
const known = remoteFacades.get(entity);
|
|
565
|
-
if (known)
|
|
566
|
-
return known;
|
|
567
|
-
const facade = createRemoteFacade(entity, remoteRouter, getMiddlewares);
|
|
568
|
-
remoteFacades.set(entity, facade);
|
|
569
|
-
return facade;
|
|
570
|
-
}));
|
|
571
|
-
}
|
|
503
|
+
refuseWhatDoesNotHold(refused);
|
|
504
|
+
markLineCarriers(emissions);
|
|
505
|
+
if (remoteRouter)
|
|
506
|
+
answerRemotes(assembly, remoteRouter, fronds);
|
|
572
507
|
const { resolve, schemaFor, facadeFor, operationsFor, presenterFor } = readings({
|
|
573
508
|
container, fronds, remoteRouter, localDispatcher, routeRegistry, effectiveByKey, log,
|
|
574
509
|
});
|
|
575
510
|
const app = {
|
|
576
511
|
container,
|
|
577
512
|
fronds,
|
|
578
|
-
// What this app publishes, straight from fougere.config.ts — the facades read it,
|
|
579
|
-
// so an undeclared adapter serves nothing whatever a host mounted.
|
|
580
513
|
adapters: options.adapters ?? {},
|
|
581
|
-
//
|
|
582
|
-
// runtime OBSERVED — `rpc.topology` calls a frond remote because it answered, never
|
|
583
|
-
// because a key said so, and the two disagree exactly when something is misconfigured.
|
|
514
|
+
// As DECLARED, beside what the runtime observes — the two disagree exactly when something is misconfigured.
|
|
584
515
|
remotes: Object.freeze({ ...options.remotes }),
|
|
585
516
|
dispatch: (call) => dispatcher.dispatch(call),
|
|
586
517
|
local: localDispatcher,
|
|
@@ -593,76 +524,28 @@ export async function createApp(options) {
|
|
|
593
524
|
storageFor,
|
|
594
525
|
presenterFor,
|
|
595
526
|
dispose: release,
|
|
596
|
-
drain: (timeoutMs) =>
|
|
527
|
+
drain: (timeoutMs) => inflight.drain(timeoutMs),
|
|
597
528
|
inFlight: () => inflight.count,
|
|
598
529
|
[Symbol.asyncDispose]: release,
|
|
599
|
-
serveRpc(
|
|
600
|
-
// Refused rather than replaced: two declarations of one name would make the answer
|
|
601
|
-
// depend on wiring order, and `discover` is in here precisely so it cannot be taken.
|
|
602
|
-
const address = new RouteAddress({ entity: 'rpc', operation: op });
|
|
603
|
-
if (routeRegistry.find(address)) {
|
|
604
|
-
throw new Error(`[claim] rpc operation '${op}' is already served; a second declaration would depend on wiring order.\n`
|
|
605
|
-
+ ' Two extensions declare it — keep one out of `extensions:`.');
|
|
606
|
-
}
|
|
607
|
-
routeRegistry.register(new OperationRoute('system', address, (call) => answer(call.invocation, call.address.surface)));
|
|
608
|
-
},
|
|
530
|
+
serveRpc: serveRpcOn(routeRegistry),
|
|
609
531
|
extensions: () => appLifecycle.names(),
|
|
610
|
-
observe(observer)
|
|
611
|
-
return dispatchLifecycle.add(observer);
|
|
612
|
-
},
|
|
532
|
+
observe: (observer) => dispatchLifecycle.add(observer),
|
|
613
533
|
use(...args) {
|
|
614
534
|
return typeof args[0] === 'string'
|
|
615
535
|
? use(args[1], args[0])
|
|
616
536
|
: use(args[0]);
|
|
617
537
|
},
|
|
618
|
-
auth: authRuntime,
|
|
619
538
|
};
|
|
620
|
-
// The card is an rpc op like any other, so one registry answers and one refusal names
|
|
621
|
-
// what is served. A package's op is declared the same way, from outside.
|
|
622
|
-
// What a release is, resolved once and registered: a package that drives one asks for it
|
|
623
|
-
// by type, the way every other dependency is asked for.
|
|
624
539
|
container.registerValue('Releasing', releasing(hosting));
|
|
625
540
|
serveCoreRpc(app, hosting, storageFor);
|
|
626
|
-
// The last thing the boot does, and the first thing a release undoes. An extension may
|
|
627
|
-
// await here — which is what a provider needing to OPEN something could never do.
|
|
628
541
|
built = app;
|
|
629
|
-
// What the FRONDS declared, folded in after what the host handed over — a frond's
|
|
630
|
-
// extension is written beside the code it instruments and travels with it, so it mounts
|
|
631
|
-
// on whichever process ends up serving that frond. Added here rather than at the top
|
|
632
|
-
// because the fronds are read below that line, and `up` has not run yet.
|
|
633
|
-
appLifecycle.add(...fronds.flatMap((frond) => (frond.extensions ?? []).map((one) => ({
|
|
634
|
-
name: one.name,
|
|
635
|
-
...one.extension,
|
|
636
|
-
}))));
|
|
637
542
|
await appLifecycle.up(app);
|
|
638
|
-
|
|
639
|
-
// writes most of what a process logs and writes it before any facade exists — so the
|
|
640
|
-
// lines that say what this app is made of are the ones a destination would miss.
|
|
641
|
-
// `LogLine` is core's for this reason: naming it costs no optional package.
|
|
642
|
-
//
|
|
643
|
-
// AFTER the ascent, because handing them over RESOLVES the destination, and what a
|
|
644
|
-
// destination an extension brought depends on is registered by that same extension's
|
|
645
|
-
// `up`: `calls()` registers `LogRing` and `ErrorRing` there, and `KeepHandler` asks for
|
|
646
|
-
// both. Resolved before the ascent, every held line died on `'LogRing' is not
|
|
647
|
-
// registered` — measured on `demos/observability`.
|
|
648
|
-
if (emissions.listensTo().includes(LOG_LINE)) {
|
|
649
|
-
const emit = container.resolve(emitKeyOf(LogLine.name));
|
|
650
|
-
// `at` is the record's own epoch, and the entity says `created()` — so the line
|
|
651
|
-
// keeps WHEN IT WAS WRITTEN rather than when it was handed over, which for a held
|
|
652
|
-
// boot line is a different moment.
|
|
653
|
-
stopAnnouncing = carry.to(({ at, ...line }) => void emit({ ...line, at: new Date(at) }));
|
|
654
|
-
}
|
|
655
|
-
else {
|
|
656
|
-
// No destination in this app: the console had them, and holding more would grow.
|
|
657
|
-
carry.forget();
|
|
658
|
-
}
|
|
543
|
+
stopAnnouncing = announceLines(emissions, container, carry);
|
|
659
544
|
return app;
|
|
660
545
|
}
|
|
661
546
|
catch (cause) {
|
|
662
|
-
// The caller
|
|
663
|
-
//
|
|
664
|
-
// connection to give us would otherwise leak it on every refusal — a source, a scan
|
|
665
|
-
// that threw, a port bound twice, and not only an extension that refused to rise.
|
|
547
|
+
// The caller never receives the app that would carry `onDispose` back, so a refused boot
|
|
548
|
+
// releases what it took itself — a source, a scan that threw, a port bound twice.
|
|
666
549
|
try {
|
|
667
550
|
await release();
|
|
668
551
|
}
|
|
@@ -672,4 +555,7 @@ export async function createApp(options) {
|
|
|
672
555
|
throw cause;
|
|
673
556
|
}
|
|
674
557
|
}
|
|
558
|
+
function contractsOf(operations) {
|
|
559
|
+
return new Map([...operations].map(([name, operation]) => [name, operation]));
|
|
560
|
+
}
|
|
675
561
|
//# sourceMappingURL=bootstrap.js.map
|