@fougere/core 0.11.0-alpha.0 → 0.12.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FougereConfig.d.ts +10 -6
- package/dist/FougereConfig.d.ts.map +1 -1
- package/dist/FougereConfig.js +19 -4
- package/dist/FougereConfig.js.map +1 -1
- package/dist/FrondConfig.d.ts +0 -6
- package/dist/FrondConfig.d.ts.map +1 -1
- package/dist/FrondConfig.js.map +1 -1
- package/dist/FrondDeclaration.d.ts +2 -9
- package/dist/FrondDeclaration.d.ts.map +1 -1
- package/dist/FrondDeclaration.js +0 -1
- package/dist/FrondDeclaration.js.map +1 -1
- package/dist/FrondsStated.d.ts +54 -0
- package/dist/FrondsStated.d.ts.map +1 -0
- package/dist/FrondsStated.js +26 -0
- package/dist/FrondsStated.js.map +1 -0
- package/dist/StatedFrond.d.ts +21 -0
- package/dist/StatedFrond.d.ts.map +1 -0
- package/dist/StatedFrond.js +34 -0
- package/dist/StatedFrond.js.map +1 -0
- package/dist/StatedModules.d.ts +19 -0
- package/dist/StatedModules.d.ts.map +1 -0
- package/dist/StatedModules.js +37 -0
- package/dist/StatedModules.js.map +1 -0
- package/dist/boot/CreateAppOptions.d.ts +19 -2
- package/dist/boot/CreateAppOptions.d.ts.map +1 -1
- package/dist/boot/Emissions.d.ts +9 -0
- package/dist/boot/Emissions.d.ts.map +1 -1
- package/dist/boot/Emissions.js +11 -5
- package/dist/boot/Emissions.js.map +1 -1
- package/dist/boot/bootstrap.d.ts.map +1 -1
- package/dist/boot/bootstrap.js +342 -307
- package/dist/boot/bootstrap.js.map +1 -1
- package/dist/boot/declared.d.ts.map +1 -1
- package/dist/boot/declared.js +3 -0
- package/dist/boot/declared.js.map +1 -1
- package/dist/boot/frame.d.ts +0 -4
- package/dist/boot/frame.d.ts.map +1 -1
- package/dist/boot/frame.js +31 -14
- package/dist/boot/frame.js.map +1 -1
- package/dist/boot/hosted.d.ts.map +1 -1
- package/dist/boot/hosted.js +4 -3
- package/dist/boot/hosted.js.map +1 -1
- package/dist/boot/install.d.ts +9 -0
- package/dist/boot/install.d.ts.map +1 -1
- package/dist/boot/install.js +271 -185
- package/dist/boot/install.js.map +1 -1
- package/dist/boot/nesting.d.ts +33 -0
- package/dist/boot/nesting.d.ts.map +1 -0
- package/dist/boot/nesting.js +164 -0
- package/dist/boot/nesting.js.map +1 -0
- package/dist/boot/ownership.d.ts +0 -1
- package/dist/boot/ownership.d.ts.map +1 -1
- package/dist/boot/ownership.js +66 -51
- package/dist/boot/ownership.js.map +1 -1
- package/dist/boot/ports.js +30 -25
- package/dist/boot/ports.js.map +1 -1
- package/dist/boot/remote.d.ts.map +1 -1
- package/dist/boot/remote.js +31 -28
- package/dist/boot/remote.js.map +1 -1
- package/dist/boot/seed.d.ts.map +1 -1
- package/dist/boot/seed.js +27 -20
- package/dist/boot/seed.js.map +1 -1
- package/dist/boot/together.d.ts.map +1 -1
- package/dist/boot/together.js +71 -56
- package/dist/boot/together.js.map +1 -1
- package/dist/descriptor/FrondDescriptor.d.ts +7 -0
- package/dist/descriptor/FrondDescriptor.d.ts.map +1 -1
- package/dist/descriptor/MiddlewareEntry.d.ts +6 -5
- package/dist/descriptor/MiddlewareEntry.d.ts.map +1 -1
- package/dist/descriptor/ProviderEntry.d.ts +7 -0
- package/dist/descriptor/ProviderEntry.d.ts.map +1 -1
- package/dist/dispatch/HandlerFacade.d.ts +6 -1
- package/dist/dispatch/HandlerFacade.d.ts.map +1 -1
- package/dist/dispatch/HandlerFacade.js +23 -20
- package/dist/dispatch/HandlerFacade.js.map +1 -1
- package/dist/dispatch/PresenterExecutor.d.ts +6 -0
- package/dist/dispatch/PresenterExecutor.d.ts.map +1 -1
- package/dist/dispatch/PresenterExecutor.js +30 -22
- package/dist/dispatch/PresenterExecutor.js.map +1 -1
- package/dist/dispatch/Release.js +53 -47
- package/dist/dispatch/Release.js.map +1 -1
- package/dist/dispatch/StorageGuard.d.ts +21 -0
- package/dist/dispatch/StorageGuard.d.ts.map +1 -1
- package/dist/dispatch/StorageGuard.js +84 -66
- package/dist/dispatch/StorageGuard.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +2 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +2 -1
- package/dist/node.js.map +1 -1
- package/dist/storage/Storage.js +1 -1
- package/dist/storage/Storage.js.map +1 -1
- package/dist/storage/Store.d.ts.map +1 -1
- package/dist/storage/Store.js +29 -21
- package/dist/storage/Store.js.map +1 -1
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +14 -0
- package/dist/verify.js.map +1 -1
- package/dist/wire/Invocation.d.ts +1 -1
- package/dist/wire/Invocation.d.ts.map +1 -1
- package/dist/wire/Invocation.js +1 -1
- package/dist/wire/Invocation.js.map +1 -1
- package/dist/wire/binding.d.ts.map +1 -1
- package/dist/wire/binding.js +29 -57
- package/dist/wire/binding.js.map +1 -1
- package/dist/wire/drift.d.ts.map +1 -1
- package/dist/wire/drift.js +22 -19
- package/dist/wire/drift.js.map +1 -1
- package/package.json +4 -4
- package/src/FougereConfig.ts +24 -10
- package/src/FrondConfig.ts +0 -6
- package/src/FrondDeclaration.ts +2 -7
- package/src/FrondsStated.ts +72 -0
- package/src/StatedFrond.ts +51 -0
- package/src/StatedModules.ts +58 -0
- package/src/boot/CreateAppOptions.ts +19 -2
- package/src/boot/Emissions.ts +16 -5
- package/src/boot/bootstrap.ts +478 -359
- package/src/boot/declared.ts +3 -0
- package/src/boot/frame.ts +42 -11
- package/src/boot/hosted.ts +4 -3
- package/src/boot/install.ts +388 -212
- package/src/boot/nesting.ts +196 -0
- package/src/boot/ownership.ts +79 -51
- package/src/boot/ports.ts +45 -31
- package/src/boot/remote.ts +47 -29
- package/src/boot/seed.ts +36 -27
- package/src/boot/together.ts +98 -63
- package/src/descriptor/FrondDescriptor.ts +7 -0
- package/src/descriptor/MiddlewareEntry.ts +6 -5
- package/src/descriptor/ProviderEntry.ts +7 -0
- package/src/dispatch/HandlerFacade.ts +27 -17
- package/src/dispatch/PresenterExecutor.ts +33 -22
- package/src/dispatch/Release.ts +68 -44
- package/src/dispatch/StorageGuard.ts +90 -69
- package/src/index.ts +3 -0
- package/src/node.ts +2 -1
- package/src/storage/Storage.ts +2 -2
- package/src/storage/Store.ts +39 -18
- package/src/verify.ts +15 -0
- package/src/wire/Invocation.ts +2 -1
- package/src/wire/binding.ts +26 -58
- package/src/wire/drift.ts +48 -21
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { NameOf } from './NameOf.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What the config says about the fronds this app is made of — where each one comes from, and
|
|
5
|
+
* which one it inherits code from.
|
|
6
|
+
*
|
|
7
|
+
* `fronds:` on an entry says ONE thing: those fronds resolve what this one declared. It says
|
|
8
|
+
* nothing about placement, and nothing about the right to call — two fronds reach each other
|
|
9
|
+
* through a façade or an announced fact, whatever their place here.
|
|
10
|
+
*
|
|
11
|
+
* The key SUGGESTS what the scan found and refuses nothing: `NameOf<'frond'>` alone would
|
|
12
|
+
* close the record, and a module specifier is a legal key no scan can have seen. `string & {}`
|
|
13
|
+
* is what keeps the union open while an editor still lists the names.
|
|
14
|
+
*
|
|
15
|
+
* Documented: [fronds](https://fougere.dev/docs/infra/fronds).
|
|
16
|
+
*/
|
|
17
|
+
export type FrondsStated = {
|
|
18
|
+
readonly [frond in NameOf<'frond'> | (string & {})]?: FrondStated;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Everything an entry says, or the one string that is the whole of it — an address for a frond
|
|
23
|
+
* that answers elsewhere, the argument for a module key.
|
|
24
|
+
*/
|
|
25
|
+
export type FrondStated = string | FrondAttributes;
|
|
26
|
+
|
|
27
|
+
export interface FrondAttributes {
|
|
28
|
+
/**
|
|
29
|
+
* The frond this one inherits code from — its scope hangs off that one's.
|
|
30
|
+
*
|
|
31
|
+
* Said by the INHERITOR, and scalar: a name then lives in one place, so moving a frond from
|
|
32
|
+
* one family to another is one edit and removing it leaves nothing dangling. It is the form
|
|
33
|
+
* `tsconfig`, Maven and Kubernetes all chose for inheritance, and the one that makes
|
|
34
|
+
* "one parent" a fact of the shape rather than a refusal at boot.
|
|
35
|
+
*
|
|
36
|
+
* ONE level: the frond it names may not inherit itself (`frond-extends-chain`). Nothing in
|
|
37
|
+
* this repo has ever wanted a chain, and refusing it is also what keeps a cycle out.
|
|
38
|
+
*/
|
|
39
|
+
extends?: NameOf<'frond'> | (string & {});
|
|
40
|
+
/** Where it answers — the same thing the shorthand says. */
|
|
41
|
+
remote?: string;
|
|
42
|
+
/** What the host hands a module key when it imports it. */
|
|
43
|
+
options?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A key that names a module rather than a frond. A frond's name is a directory name or the
|
|
48
|
+
* `fougere.frond` field of a package, and neither can hold a separator — so the two never
|
|
49
|
+
* collide, and no allow-list of short names has to be kept anywhere.
|
|
50
|
+
*/
|
|
51
|
+
export function statesModule(key: string): boolean {
|
|
52
|
+
return key.includes('/') || key.startsWith('.');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Two levels of the cascade, folded. An entry merges by attribute, so an app naming a frond's
|
|
57
|
+
* address keeps the `extends` the workspace gave it — and an app naming a different `extends`
|
|
58
|
+
* replaces it, silently, the way every scalar key of this config replaces.
|
|
59
|
+
*/
|
|
60
|
+
export function mergeStated(base: FrondsStated, override: FrondsStated): FrondsStated {
|
|
61
|
+
const merged: Record<string, FrondStated> = { ...base } as Record<string, FrondStated>;
|
|
62
|
+
for (const [key, value] of Object.entries(override)) {
|
|
63
|
+
if (value === undefined) continue;
|
|
64
|
+
|
|
65
|
+
const mine = merged[key];
|
|
66
|
+
merged[key] = typeof value === 'string' || typeof mine === 'string' || mine === undefined
|
|
67
|
+
? value
|
|
68
|
+
: { ...mine, ...value };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return merged;
|
|
72
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { statesModule, type FrondAttributes, type FrondStated, type FrondsStated } from './FrondsStated.js';
|
|
2
|
+
|
|
3
|
+
/** One entry of the config, read whole. */
|
|
4
|
+
export interface StatedFrond {
|
|
5
|
+
/** The key as written — a frond name, or a module specifier. */
|
|
6
|
+
key: string;
|
|
7
|
+
/** The frond it inherits code from, absent when it names none. */
|
|
8
|
+
extends?: string;
|
|
9
|
+
/** The entry that named it, for a refusal that says where to look. */
|
|
10
|
+
path: string;
|
|
11
|
+
/** An address for a frond that is elsewhere, an argument for a module. */
|
|
12
|
+
value?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** What an entry says, whichever of its two forms it was written in. */
|
|
16
|
+
function attributesOf(stated: FrondStated, key: string): FrondAttributes {
|
|
17
|
+
if (typeof stated !== 'string') return stated;
|
|
18
|
+
|
|
19
|
+
return statesModule(key) ? { options: stated } : { remote: stated };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Every frond the config states, those inheriting from nothing first — which is also the order
|
|
24
|
+
* a boot installs them in, since a scope hangs off the one above it.
|
|
25
|
+
*
|
|
26
|
+
* A partition and not a sort: with one level, a frond that inherits has nothing under it, so
|
|
27
|
+
* two passes put every parent before every child without knowing the tree.
|
|
28
|
+
*/
|
|
29
|
+
export function statedFronds(stated: FrondsStated | undefined): StatedFrond[] {
|
|
30
|
+
const entries = Object.entries(stated ?? {}) as [string, FrondStated | undefined][];
|
|
31
|
+
|
|
32
|
+
const read = ([key, value]: [string, FrondStated | undefined]): StatedFrond | undefined => {
|
|
33
|
+
if (value === undefined) return undefined;
|
|
34
|
+
|
|
35
|
+
const attributes = attributesOf(value, key);
|
|
36
|
+
const above = attributes.extends;
|
|
37
|
+
const carries = statesModule(key) ? attributes.options : attributes.remote;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
key,
|
|
41
|
+
path: above !== undefined ? `${above}.${key}` : key,
|
|
42
|
+
...(above !== undefined ? { extends: above } : {}),
|
|
43
|
+
...(carries !== undefined ? { value: carries } : {}),
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const stands = (one: StatedFrond | undefined): one is StatedFrond => one !== undefined;
|
|
48
|
+
const all = entries.map(read).filter(stands);
|
|
49
|
+
|
|
50
|
+
return [...all.filter((one) => one.extends === undefined), ...all.filter((one) => one.extends !== undefined)];
|
|
51
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fronds a config names by MODULE rather than by directory — `@fougere/log`, or a package
|
|
3
|
+
* of your own. Reading one means importing it, which is why this lives on the node entry and
|
|
4
|
+
* never on the one a Worker runs.
|
|
5
|
+
*
|
|
6
|
+
* What a module hands back decides where it goes: a shape stating `up` or `down` is an
|
|
7
|
+
* extension and rises with the app, anything else is a frond and is installed like the rest.
|
|
8
|
+
* The author of a config does not have to know which one their package exports — the same
|
|
9
|
+
* reading `extensions/` already does off a module's form.
|
|
10
|
+
*/
|
|
11
|
+
import type { Extension } from './boot/Extension.js';
|
|
12
|
+
import type { FrondDescriptor } from './descriptor/FrondDescriptor.js';
|
|
13
|
+
import { statesModule, type FrondsStated } from './FrondsStated.js';
|
|
14
|
+
import { statedFronds } from './StatedFrond.js';
|
|
15
|
+
import { getModuleLoader } from './loader.js';
|
|
16
|
+
|
|
17
|
+
/** The export a specifier reaches: its last segment, `default` failing that. */
|
|
18
|
+
function exportedBy(specifier: string): string {
|
|
19
|
+
const last = specifier.split('/').at(-1) ?? specifier;
|
|
20
|
+
|
|
21
|
+
return last.replace(/\.[cm]?[jt]s$/, '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function rises(built: unknown): built is Extension {
|
|
25
|
+
const shape = built as { up?: unknown; down?: unknown } | null;
|
|
26
|
+
|
|
27
|
+
return typeof shape?.up === 'function' || typeof shape?.down === 'function';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** What a config's module keys hand over, sorted by what each one turned out to be. */
|
|
31
|
+
export async function statedModules(
|
|
32
|
+
stated: FrondsStated | undefined,
|
|
33
|
+
): Promise<{ fronds: FrondDescriptor[]; extensions: Extension[] }> {
|
|
34
|
+
const fronds: FrondDescriptor[] = [];
|
|
35
|
+
const extensions: Extension[] = [];
|
|
36
|
+
const loader = getModuleLoader();
|
|
37
|
+
|
|
38
|
+
for (const entry of statedFronds(stated)) {
|
|
39
|
+
if (!statesModule(entry.key)) continue;
|
|
40
|
+
|
|
41
|
+
const module = await loader(entry.key) as Record<string, unknown>;
|
|
42
|
+
const name = exportedBy(entry.key);
|
|
43
|
+
const factory = module[name] ?? module.default;
|
|
44
|
+
if (typeof factory !== 'function') {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Fougere config: '${entry.key}' exports no '${name}'. A module a config names hands its `
|
|
47
|
+
+ `frond back from the export its last segment spells — \`export const ${name} = …\` — or `
|
|
48
|
+
+ 'from its default.',
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const built = (factory as (argument?: unknown) => unknown)(entry.value);
|
|
53
|
+
if (rises(built)) extensions.push(built);
|
|
54
|
+
else fronds.push(built as FrondDescriptor);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { fronds, extensions };
|
|
58
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Container } from '@fougere/container';
|
|
2
2
|
import type { FrondDescriptor } from '../descriptor/FrondDescriptor.js';
|
|
3
|
+
import type { FrondsStated } from '../FrondsStated.js';
|
|
3
4
|
import type { ScanResult } from '../scan.js';
|
|
4
5
|
import type { StorageFactory } from '../storage/StorageFactory.js';
|
|
5
6
|
import type { Constraint } from '../Constraint.js';
|
|
@@ -11,8 +12,13 @@ import type { App } from './App.js';
|
|
|
11
12
|
|
|
12
13
|
/** Options for createApp(). */
|
|
13
14
|
export interface CreateAppOptions {
|
|
14
|
-
/**
|
|
15
|
-
|
|
15
|
+
/**
|
|
16
|
+
* The container this app resolves through. Absent, core builds its own — the only
|
|
17
|
+
* realization there is. A host states one to fill the root BEFORE the boot runs: the CLI
|
|
18
|
+
* registers its terminal that way, and a seed resolving a handler during the ascent would
|
|
19
|
+
* never see a value registered after `createApp` returned.
|
|
20
|
+
*/
|
|
21
|
+
createContainer?: () => Container;
|
|
16
22
|
/** Factory to auto-generate Storage for each scanned entity. */
|
|
17
23
|
storageFactory?: StorageFactory;
|
|
18
24
|
/**
|
|
@@ -41,6 +47,17 @@ export interface CreateAppOptions {
|
|
|
41
47
|
scan?: ScanResult | (() => Promise<ScanResult> | ScanResult);
|
|
42
48
|
/** What this app STATES it hosts — `frond('blog', { entities: [Post] })`. */
|
|
43
49
|
fronds?: readonly FrondDescriptor[];
|
|
50
|
+
/**
|
|
51
|
+
* Who inherits code from whom — `FougereConfig.fronds`, handed over whole rather than
|
|
52
|
+
* flattened, because a refusal names where an entry sits in it (`shop.cart`). The host has
|
|
53
|
+
* already turned its addresses and its modules into `remotes` and `fronds`.
|
|
54
|
+
*/
|
|
55
|
+
under?: FrondsStated;
|
|
56
|
+
/**
|
|
57
|
+
* This process carries a subset of what the project declares — `only:` at the host. A name
|
|
58
|
+
* the tree states and this process left out is then expected, not a typo.
|
|
59
|
+
*/
|
|
60
|
+
narrowed?: boolean;
|
|
44
61
|
/**
|
|
45
62
|
* Remote fronds — label → address. What each remote hosts is discovered
|
|
46
63
|
* at the first miss (rpc.discover), never declared here.
|
package/src/boot/Emissions.ts
CHANGED
|
@@ -223,17 +223,30 @@ export class Emissions {
|
|
|
223
223
|
return [];
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
return this.answersOf(fact, handed);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* A subscriber that did not answer REFUSES the announcement, it does not shrink it: an
|
|
231
|
+
* announcer handed the survivors cannot tell three answers from two, and its own law then
|
|
232
|
+
* reads silence as consent — the room one would have refused gets booked.
|
|
233
|
+
*
|
|
234
|
+
* What answers NOTHING contributes nothing: a subscriber is free to have no opinion, and
|
|
235
|
+
* `Promise<void>` is how it says so.
|
|
236
|
+
*/
|
|
237
|
+
private async answersOf(
|
|
238
|
+
fact: string,
|
|
239
|
+
handed: (Listener & { done: Promise<unknown> })[],
|
|
240
|
+
): Promise<unknown[]> {
|
|
226
241
|
const settled = await Promise.allSettled(handed.map((one) => one.done));
|
|
227
242
|
|
|
228
|
-
// A subscriber that did not answer REFUSES the announcement, it does not shrink it: an
|
|
229
|
-
// announcer handed the survivors cannot tell three answers from two, and its own law
|
|
230
|
-
// then reads silence as consent — the room one would have refused gets booked.
|
|
231
243
|
const missing = settled.flatMap((result, at) =>
|
|
232
244
|
(result.status === 'rejected' ? [{ ...handed[at]!, reason: result.reason as unknown }] : []));
|
|
233
245
|
if (missing.length > 0) {
|
|
234
246
|
for (const { facade, op, reason } of missing) {
|
|
235
247
|
this.log.error(`${fact} → ${facade}.${op}`, this.describeRefusal(fact, reason) ?? reason);
|
|
236
248
|
}
|
|
249
|
+
|
|
237
250
|
throw new AggregateError(
|
|
238
251
|
missing.map((one) => one.reason),
|
|
239
252
|
`${fact} — ${missing.length} of ${handed.length} subscriber(s) did not answer`
|
|
@@ -243,8 +256,6 @@ export class Emissions {
|
|
|
243
256
|
);
|
|
244
257
|
}
|
|
245
258
|
|
|
246
|
-
// What answers NOTHING contributes nothing — a subscriber is free to have no opinion,
|
|
247
|
-
// and `Promise<void>` is how it says so.
|
|
248
259
|
return settled.flatMap((result) =>
|
|
249
260
|
(result.status === 'fulfilled' && result.value != null ? [result.value] : []));
|
|
250
261
|
}
|