@softarc/native-federation-orchestrator 4.5.2 → 4.6.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/README.md +2 -2
- package/fesm2022/@softarc/native-federation-orchestrator.mjs +1310 -257
- package/fesm2022/@softarc/native-federation-orchestrator.mjs.map +4 -4
- package/fesm2022/module-federation.mjs +27 -8
- package/fesm2022/module-federation.mjs.map +3 -3
- package/fesm2022/node.mjs +1307 -254
- package/fesm2022/node.mjs.map +4 -4
- package/fesm2022/options.mjs +5 -10
- package/fesm2022/options.mjs.map +2 -2
- package/package.json +2 -2
- package/quickstart.mjs +15 -15
- package/types/lib/core/1.domain/externals/basis.d.ts +22 -0
- package/types/lib/core/1.domain/externals/version.contract.d.ts +1 -0
- package/types/lib/core/1.domain/remote-entry/remote-entry.contract.d.ts +2 -0
- package/types/lib/core/2.app/config/mode.contract.d.ts +1 -0
- package/types/lib/core/2.app/driver-ports/init/drivers.contract.d.ts +2 -0
- package/types/lib/core/2.app/driver-ports/init/for-determining-shared-externals.port.d.ts +8 -1
- package/types/lib/core/2.app/driver-ports/init/for-marking-pools-for-reelection.port.d.ts +6 -0
- package/types/lib/core/2.app/driver-ports/init/for-pooling-shared-externals.port.d.ts +3 -1
- package/types/lib/core/2.app/driving-ports/for-shared-chunks-storage.port.d.ts +1 -0
- package/types/lib/core/2.app/driving-ports/for-shared-externals-storage.port.d.ts +2 -3
- package/types/lib/core/2.app/steps/apply-winner.d.ts +18 -0
- package/types/lib/core/2.app/steps/pooling/anchoring.d.ts +69 -0
- package/types/lib/core/2.app/steps/pooling/mark-pools-for-reelection.d.ts +5 -0
- package/types/lib/core/2.app/steps/pooling/pool-dynamic-externals.d.ts +3 -1
- package/types/lib/core/2.app/steps/pooling/pool-graph.d.ts +10 -5
- package/types/lib/core/2.app/steps/pooling/pool-shared-externals.d.ts +30 -1
- package/types/lib/core/2.app/steps/pooling/pool-views.d.ts +63 -0
- package/types/lib/core/2.app/steps/pooling/pool.types.d.ts +30 -1
- package/types/lib/core/2.app/steps/pooling/pool.util.d.ts +15 -0
- package/types/lib/core/2.app/steps/store-remote-entry.d.ts +15 -7
- package/types/lib/testing/pooling/no-tear.d.ts +37 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SharedVersion, SharedVersionMeta } from './version.contract';
|
|
2
|
+
export declare function isBetterBasis(candidate: SharedVersionMeta, basis: SharedVersionMeta): boolean;
|
|
3
|
+
export declare function addRemoteToVersion(version: SharedVersion, remote: SharedVersionMeta, isHost?: boolean): void;
|
|
4
|
+
export declare function versionDemands(version: SharedVersion): SharedVersionMeta[];
|
|
5
|
+
export declare function findVersionForTag(versions: SharedVersion[], tag: string): SharedVersion | undefined;
|
|
6
|
+
export declare function versionEntries(version: SharedVersion): Map<string, SharedVersionMeta>;
|
|
7
|
+
export declare function committedEntries(version: SharedVersion): Map<string, SharedVersionMeta>;
|
|
8
|
+
/**
|
|
9
|
+
* The walk both of those keep the first claim of: which copies of a version may publish a specifier, in basis
|
|
10
|
+
* precedence. Exposed so a caller that models the same fill order without needing the map — the pooling gates
|
|
11
|
+
* ask which build a consumer lands on, per specifier — reads the `servedBy` rule from here instead of
|
|
12
|
+
* restating it. It deliberately does not dedup: the callers do, and this way the walk allocates nothing.
|
|
13
|
+
*
|
|
14
|
+
* `accepts` is for a caller with a further reason to discount a copy (pooling islands one, so it self-serves).
|
|
15
|
+
* Filtering the *result* is not the same thing — that drops the specifier instead of letting the next copy
|
|
16
|
+
* claim it.
|
|
17
|
+
*/
|
|
18
|
+
export declare function forEachVersionEntry(version: SharedVersion, accepts: ((remote: SharedVersionMeta) => boolean) | undefined, visit: (entrypoint: string, remote: SharedVersionMeta) => void): void;
|
|
19
|
+
type Covered = Pick<ReadonlySet<string>, 'has'>;
|
|
20
|
+
export declare function uncoveredEntrypoints(remote: SharedVersionMeta, covered: Covered): string[];
|
|
21
|
+
export declare function countUncoveredEntrypoints(remote: SharedVersionMeta, covered: Covered): number;
|
|
22
|
+
export {};
|
|
@@ -11,5 +11,7 @@ type RemoteEntry = Omit<FederationInfo, 'shared'> & {
|
|
|
11
11
|
type SharedInfoActions = Record<string, {
|
|
12
12
|
action: SharedVersionAction;
|
|
13
13
|
override?: Record<string, string>;
|
|
14
|
+
covered?: string[];
|
|
15
|
+
sameVersion?: boolean;
|
|
14
16
|
}>;
|
|
15
17
|
export { RawRemoteEntry, RemoteEntry, FederationInfo, ExposesInfo, SharedInfo, DenseSharedInfo, SharedInfoActions, };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type ModeProfileConfig = {
|
|
2
2
|
latestSharedExternal: boolean;
|
|
3
3
|
skipInvalidExternalVersions: boolean;
|
|
4
|
+
scopeUncoveredEntrypoints: boolean;
|
|
4
5
|
overrideCachedRemotes: 'always' | 'never' | 'init-only';
|
|
5
6
|
overrideCachedRemotesIfURLMatches: boolean;
|
|
6
7
|
cacheTag?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ForCommittingChanges } from './for-committing-changes.port';
|
|
2
2
|
import type { ForDeterminingSharedExternals } from './for-determining-shared-externals.port';
|
|
3
3
|
import type { ForPoolingSharedExternals } from './for-pooling-shared-externals.port';
|
|
4
|
+
import type { ForMarkingPoolsForReelection } from './for-marking-pools-for-reelection.port';
|
|
4
5
|
import type { ForPoolingDynamicExternals } from './for-pooling-dynamic-externals.port';
|
|
5
6
|
import type { ForExposingModuleLoader } from './for-exposing-module-loader.port';
|
|
6
7
|
import type { ForGeneratingImportMap } from './for-generating-import-map';
|
|
@@ -14,6 +15,7 @@ import type { DrivingContract } from '../../driving-ports/driving.contract';
|
|
|
14
15
|
export type InitDriversContract = {
|
|
15
16
|
getRemoteEntries: ForGettingRemoteEntries;
|
|
16
17
|
processRemoteEntries: ForProcessingRemoteEntries;
|
|
18
|
+
markPoolsForReelection: ForMarkingPoolsForReelection;
|
|
17
19
|
determineSharedExternals: ForDeterminingSharedExternals;
|
|
18
20
|
poolSharedExternals: ForPoolingSharedExternals;
|
|
19
21
|
generateImportMap: ForGeneratingImportMap;
|
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ExternalName } from '../../../1.domain';
|
|
2
|
+
/**
|
|
3
|
+
* The externals this pass re-elected, per shareScope. Determine clears `dirty` before pooling runs,
|
|
4
|
+
* so this is the only signal left of what changed; a scope with nothing re-elected is absent rather
|
|
5
|
+
* than empty.
|
|
6
|
+
*/
|
|
7
|
+
export type TouchedExternals = ReadonlyMap<string, ReadonlySet<ExternalName>>;
|
|
8
|
+
export type ForDeterminingSharedExternals = () => Promise<TouchedExternals>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marks every member of a pool dirty when any one of them is, so `determine` re-elects the pool as a unit
|
|
3
|
+
* and pooling never reads a verdict left over from an earlier portfolio. Mutates the stored externals only;
|
|
4
|
+
* it writes nothing.
|
|
5
|
+
*/
|
|
6
|
+
export type ForMarkingPoolsForReelection = () => Promise<void>;
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import type { TouchedExternals } from './for-determining-shared-externals.port';
|
|
2
|
+
/** Omitting `touched` means "no signal": every pool is processed, as before W2. */
|
|
3
|
+
export type ForPoolingSharedExternals = (touched?: TouchedExternals) => Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Optional } from '../../../utils/optional';
|
|
2
2
|
export type ForSharedChunksStorage = {
|
|
3
3
|
addOrReplace: (remote: string, bundleName: string, chunks: string[]) => ForSharedChunksStorage;
|
|
4
|
+
remove: (remote: string) => ForSharedChunksStorage;
|
|
4
5
|
commit: () => ForSharedChunksStorage;
|
|
5
6
|
tryGet: (remote: string, bundleName: string) => Optional<string[]>;
|
|
6
7
|
};
|
|
@@ -7,9 +7,8 @@ export type ForSharedExternalsStorage = {
|
|
|
7
7
|
includeGlobal: boolean;
|
|
8
8
|
}) => string[];
|
|
9
9
|
scopeType: (shareScope?: string) => 'global' | 'strict' | 'shareScope';
|
|
10
|
-
removeFromAllScopes: (
|
|
10
|
+
removeFromAllScopes: (remoteNames: ReadonlySet<RemoteName>) => void;
|
|
11
11
|
addOrUpdate: (name: string, external: SharedExternal, shareScope?: string) => ForSharedExternalsStorage;
|
|
12
|
-
|
|
13
|
-
hasPoolTag: () => boolean;
|
|
12
|
+
hasPoolTag: (shareScope?: string) => boolean;
|
|
14
13
|
commit: () => ForSharedExternalsStorage;
|
|
15
14
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SharedExternal, SharedVersion, SharedVersionMeta } from '../../1.domain';
|
|
2
|
+
import type { LoggingConfig } from '../config/log.contract';
|
|
3
|
+
import type { ModeConfig } from '../config/mode.contract';
|
|
4
|
+
export type IsCompatible = (tag: string, requiredVersion: string) => boolean;
|
|
5
|
+
export type VersionAcceptance = {
|
|
6
|
+
accepts: (version: SharedVersion, tag: string) => boolean;
|
|
7
|
+
objector: (version: SharedVersion, tag: string) => SharedVersionMeta | undefined;
|
|
8
|
+
};
|
|
9
|
+
export declare function versionAcceptance(external: SharedExternal, isCompatible: IsCompatible): VersionAcceptance;
|
|
10
|
+
/**
|
|
11
|
+
* The tail of winner election: derive every other version's verdict from the chosen one, apply the
|
|
12
|
+
* entrypoint coverage policy, clear `dirty`. `determine` is the only caller, and passes its memoized
|
|
13
|
+
* `isCompatible` plus the `acceptance` it already built for the election.
|
|
14
|
+
*
|
|
15
|
+
* A hazard for anyone who ever adds a second caller that re-points a winner: `findTears` keys off the
|
|
16
|
+
* winner's merged entries, so moving the winner moves the surface coverage is measured against.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createApplyWinner(config: LoggingConfig & ModeConfig): (externalName: string, external: SharedExternal, winner: SharedVersion, isCompatible: IsCompatible, acceptance?: VersionAcceptance) => SharedExternal;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ExternalName, RemoteName, VersionName } from '../../../1.domain';
|
|
2
|
+
import type { BuildView, Coverage, FamilyInstance, PoolMember, Specifier } from './pool.types';
|
|
3
|
+
/**
|
|
4
|
+
* The gates that choose which build serves a consumer. Everything here reads **coverage** and
|
|
5
|
+
* `isCompatible`; no function in this file compares two tags for distance, only for identity. The
|
|
6
|
+
* projections they read are in `pool-views.ts`, the rationale in docs/version-resolver.md §"The
|
|
7
|
+
* provenance promise".
|
|
8
|
+
*/
|
|
9
|
+
/** `remote -> member -> every tag that remote's own `requiredVersion` accepts.` */
|
|
10
|
+
export type Acceptance = Map<RemoteName, Map<ExternalName, Set<VersionName>>>;
|
|
11
|
+
/** The build serving each remote, or `undefined` where it serves its own family. */
|
|
12
|
+
export type Assignment = Map<RemoteName, RemoteName | undefined>;
|
|
13
|
+
/**
|
|
14
|
+
* Every tag each remote's own range accepts, per member. Precomputed rather than asked per
|
|
15
|
+
* consumer/candidate pair, and `isCompatible` is expected to be `determine`'s memoized one — this must
|
|
16
|
+
* never replicate its O(versions²) search.
|
|
17
|
+
*/
|
|
18
|
+
export declare function acceptanceTable(members: PoolMember[], isCompatible: (tag: VersionName, range: string) => boolean): Acceptance;
|
|
19
|
+
/** Does a build serve a superset of what a consumer imports? */
|
|
20
|
+
export declare function covers(coverage: Coverage, consumed: Iterable<Specifier>): boolean;
|
|
21
|
+
/** Does a build offer every member a consumer needs, at a tag the consumer's own range accepts? */
|
|
22
|
+
export declare function acceptsAll(acceptance: Acceptance, build: FamilyInstance, consumer: RemoteName, consumed: readonly ExternalName[]): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Is the combination the global mapping would hand this remote one that some live build shipped?
|
|
25
|
+
*
|
|
26
|
+
* All-or-nothing across the family: **every** specifier it imports has to be published at the tag that one
|
|
27
|
+
* single build ships it at. Taken per specifier instead it reintroduces the defect, a combination nothing
|
|
28
|
+
* compiled. Why the general form is sound and not just the remote's-own-build case: §"Why the witness is
|
|
29
|
+
* sound".
|
|
30
|
+
*
|
|
31
|
+
* Asked before coverage on both paths, because a witnessed remote needs no anchor at all: asking coverage
|
|
32
|
+
* first pins remotes already sitting at the shared tags onto one build for no gain.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isWitnessed(specifiers: Iterable<Specifier>, shared: Map<Specifier, VersionName>, builds: Map<RemoteName, BuildView>): boolean;
|
|
35
|
+
export type SelfServeReason = {
|
|
36
|
+
gap: string;
|
|
37
|
+
closest?: RemoteName;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Why no build could serve this remote, in the terms a portfolio owner can act on. Runs only for a remote
|
|
41
|
+
* that is actually self-serving, so it costs nothing on a healthy portfolio.
|
|
42
|
+
*
|
|
43
|
+
* "Closest" is fewest missing entrypoints; a build that misses none failed on versions instead, and the gap
|
|
44
|
+
* is then the member whose offered tag this remote's own range rejects.
|
|
45
|
+
*/
|
|
46
|
+
export declare function explainSelfServe(remote: RemoteName, wants: readonly ExternalName[], specifiers: Set<Specifier>, pool: {
|
|
47
|
+
builds: Map<RemoteName, BuildView>;
|
|
48
|
+
acceptance: Acceptance;
|
|
49
|
+
}): SelfServeReason;
|
|
50
|
+
export type AnchorInput = {
|
|
51
|
+
builds: Map<RemoteName, BuildView>;
|
|
52
|
+
acceptance: Acceptance;
|
|
53
|
+
consumedSpecifiers: Map<RemoteName, Set<Specifier>>;
|
|
54
|
+
consumedMembers: Map<RemoteName, ExternalName[]>;
|
|
55
|
+
hosts: Set<RemoteName>;
|
|
56
|
+
arrival: Map<RemoteName, number>;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Assign each consumer the build that serves it, or `undefined` where it serves its own family.
|
|
60
|
+
*
|
|
61
|
+
* Multi-anchor is mandatory rather than an optimisation (constraint 3): a single anchor per pool scopes
|
|
62
|
+
* members nothing required to be scoped, and two remotes sharing no member already satisfy the promise.
|
|
63
|
+
*
|
|
64
|
+
* Greedy and deterministic, never a search. Tiebreaks in order: host, then most consumers fully covered,
|
|
65
|
+
* then arrival, then name. A build chosen as an anchor **runs its own build**, so it is never itself
|
|
66
|
+
* assigned elsewhere — a consumer deduping onto a remote that is itself deduping would inherit that
|
|
67
|
+
* remote's foreign copies.
|
|
68
|
+
*/
|
|
69
|
+
export declare function assignAnchors(input: AnchorInput): Assignment;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ForMarkingPoolsForReelection } from '../../driver-ports/init/for-marking-pools-for-reelection.port';
|
|
2
|
+
import type { DrivingContract } from '../../driving-ports/driving.contract';
|
|
3
|
+
import type { LoggingConfig } from '../../config/log.contract';
|
|
4
|
+
import type { ModeConfig } from '../../config/mode.contract';
|
|
5
|
+
export declare function createMarkPoolsForReelection(config: LoggingConfig & ModeConfig, ports: Pick<DrivingContract, 'sharedExternalsRepo'>): ForMarkingPoolsForReelection;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { ForPoolingDynamicExternals } from '../../driver-ports/init/for-pooling-dynamic-externals.port';
|
|
2
2
|
import type { ModeConfig } from '../../config/mode.contract';
|
|
3
|
-
|
|
3
|
+
import type { LoggingConfig } from '../../config/log.contract';
|
|
4
|
+
import type { DrivingContract } from '../../driving-ports/driving.contract';
|
|
5
|
+
export declare function createPoolDynamicExternals(config: LoggingConfig & ModeConfig, ports: Pick<DrivingContract, 'sharedExternalsRepo' | 'remoteInfoRepo' | 'versionCheck'>): ForPoolingDynamicExternals;
|
|
@@ -9,16 +9,21 @@ export type PoolCandidate<T> = {
|
|
|
9
9
|
name: ExternalName;
|
|
10
10
|
scope?: string;
|
|
11
11
|
tags: readonly PoolEdge[];
|
|
12
|
+
remotes: readonly string[];
|
|
12
13
|
value: T;
|
|
13
14
|
};
|
|
14
15
|
export declare function autoScope(name: string, useAutoExternalPooling: boolean): string | undefined;
|
|
16
|
+
export declare function owningPackage(name: ExternalName): ExternalName | undefined;
|
|
15
17
|
/**
|
|
16
18
|
* Group one shareScope's candidates into pools by shared membership: pool = connected component of a
|
|
17
|
-
* graph
|
|
18
|
-
* (`external -> tag@remote
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* graph whose edges are all remote-local — `external -> scope@remote` for each remote that declares
|
|
20
|
+
* it (auto-pooling) and `external -> tag@remote` for each declared tag — plus an unconditional
|
|
21
|
+
* `entrypoint -> package` edge. A pool therefore forms exactly when some remote declares members
|
|
22
|
+
* from both sides. See docs/version-resolver.md.
|
|
23
|
+
*
|
|
24
|
+
* Returns only real pools (>=2 members), keyed by and iterated in order of their canonical name
|
|
25
|
+
* (smallest member — reload-stable). An explicit-tag member that pooled with nothing is warned
|
|
26
|
+
* (likely typo or missing sibling); auto-scope singletons stay silent.
|
|
22
27
|
*/
|
|
23
28
|
export declare function groupByMembership<T>(candidates: readonly PoolCandidate<T>[], log?: LogHandler): Map<PoolName, T[]>;
|
|
24
29
|
/** Init-path grouping: build candidates from the stored shared externals of one shareScope. */
|
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
import type { ForPoolingSharedExternals } from '../../driver-ports/init/for-pooling-shared-externals.port';
|
|
2
|
+
import type { ExternalName, RemoteName } from '../../../1.domain';
|
|
2
3
|
import type { DrivingContract } from '../../driving-ports/driving.contract';
|
|
3
4
|
import type { LoggingConfig } from '../../config/log.contract';
|
|
4
5
|
import type { ModeConfig } from '../../config/mode.contract';
|
|
5
|
-
|
|
6
|
+
import type { Islanded, PoolMember } from './pool.types';
|
|
7
|
+
/**
|
|
8
|
+
* Per remote, the members it must be served from a build other than that member's own basis — an anchor's,
|
|
9
|
+
* or its own where it is an anchor for somebody else. Absent means the default: the copy resolves through
|
|
10
|
+
* the global mapping, which is right whenever the shared tag is already the remote's own.
|
|
11
|
+
*/
|
|
12
|
+
type Served = Map<RemoteName, Map<ExternalName, RemoteName>>;
|
|
13
|
+
/**
|
|
14
|
+
* The no-tear guarantee, checked on what is about to be written rather than argued from the gates: every
|
|
15
|
+
* non-host remote must resolve a `(specifier → tag)` combination that some single build shipped. Which remote
|
|
16
|
+
* serves a tag is free — two builds at one tag are interchangeable providers — so this compares tags only,
|
|
17
|
+
* never origins. A host is never judged: it cannot be repointed onto another build.
|
|
18
|
+
*
|
|
19
|
+
* Keyed by specifier like everything else in the design, not by member: a specifier no copy of the winning
|
|
20
|
+
* version bundles is published from a `skip` copy at that version's tag (`selfFillUncovered`), so a
|
|
21
|
+
* member-level check reads one tag for a package the remote really resolves at two. Copies of the winning
|
|
22
|
+
* tag merging their entrypoints (`mergeVersionEntries`) never do this — one tag, nothing to tear.
|
|
23
|
+
*
|
|
24
|
+
* No portfolio is known to reach a tear through `poolFamily` — the gates leave it true — and it is checked
|
|
25
|
+
* regardless, because it is the one thing pooling exists to prevent and because `rebuildMember`'s last rule
|
|
26
|
+
* (a copy whose member lost its basis self-serves) is decided after gate 2 has stopped looking. Exported so
|
|
27
|
+
* `find-torn-remotes.spec.ts` can pin the contract on hand-built input, the only way to reach it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function findTornRemotes(members: PoolMember[], islanded: Islanded, basis: Map<ExternalName, RemoteName>, served: Served, hosts: ReadonlySet<RemoteName>): {
|
|
30
|
+
remote: RemoteName;
|
|
31
|
+
combination: string;
|
|
32
|
+
}[];
|
|
33
|
+
export declare function createPoolSharedExternals(config: LoggingConfig & ModeConfig, ports: Pick<DrivingContract, 'sharedExternalsRepo' | 'versionCheck'>): ForPoolingSharedExternals;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ExternalName, RemoteName, VersionName } from '../../../1.domain';
|
|
2
|
+
import type { BuildView, CommittedView, Islanded, OwnCopy, PoolMember, Specifier } from './pool.types';
|
|
3
|
+
/**
|
|
4
|
+
* Per build: the specifiers it serves and from which file, the tag each is served at, and the
|
|
5
|
+
* `(member → tag)` instance it runs. Built once per pool and reused across consumers, so a coverage question
|
|
6
|
+
* is a subset test rather than a walk of `entries`.
|
|
7
|
+
*
|
|
8
|
+
* A `scope` copy is excluded — it is about to self-serve, so it serves nobody else.
|
|
9
|
+
*/
|
|
10
|
+
export declare function liveBuilds(members: PoolMember[], islanded?: Islanded): Map<RemoteName, BuildView>;
|
|
11
|
+
/**
|
|
12
|
+
* The same walk over the *committed* record, where a `scope` copy is a **stable island** rather than a
|
|
13
|
+
* remote about to self-serve: its files are already in the map under its own scope and it demonstrably runs
|
|
14
|
+
* its own build, so it can serve a remote loaded later.
|
|
15
|
+
*/
|
|
16
|
+
export declare function committedView(members: PoolMember[]): CommittedView;
|
|
17
|
+
/**
|
|
18
|
+
* Per remote, every copy it holds — **including** ones marked `scope`, which is what separates this from
|
|
19
|
+
* `liveBuilds`. That one answers "what can this build serve others"; this one answers "what does this remote
|
|
20
|
+
* run itself", where a `scope` copy is precisely what it runs. Only the second question can see a torn
|
|
21
|
+
* family.
|
|
22
|
+
*/
|
|
23
|
+
export declare function ownCopies(members: PoolMember[], only?: ReadonlySet<RemoteName>): Map<RemoteName, OwnCopy[]>;
|
|
24
|
+
export declare function consumedMembers(members: PoolMember[]): Map<RemoteName, ExternalName[]>;
|
|
25
|
+
/** `consumedMembers` in specifier space. */
|
|
26
|
+
export declare function consumedSpecifiers(members: PoolMember[]): Map<RemoteName, Set<Specifier>>;
|
|
27
|
+
/**
|
|
28
|
+
* Does one build serve every specifier the pool's live copies consume? The cheap form of
|
|
29
|
+
* `consumedSpecifiers` against one `liveBuilds` entry, for the only question the healthy path asks.
|
|
30
|
+
*
|
|
31
|
+
* Keyed by specifier, because being the basis of every *member* is not the same thing: an entrypoint the
|
|
32
|
+
* basis does not carry is served from the declaring remote's own build, at that remote's own tag (see
|
|
33
|
+
* §"Entrypoint coverage and tearing"), which is a torn package and not something a member-level test can
|
|
34
|
+
* see.
|
|
35
|
+
*/
|
|
36
|
+
export declare function coversWholePool(members: PoolMember[], build: RemoteName, islanded: Islanded): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* The build serving each member, i.e. `remotes[0]` of its shared version — skipping copies islanding has
|
|
39
|
+
* taken, since those are about to self-serve. A member with no entry is served by nobody and every
|
|
40
|
+
* consumer falls back to its own build.
|
|
41
|
+
*/
|
|
42
|
+
export declare function servingBuilds(members: PoolMember[], islanded: Islanded): Map<ExternalName, RemoteName>;
|
|
43
|
+
/**
|
|
44
|
+
* The copy whose file the global mapping publishes, per member: the first copy of the member's `share`
|
|
45
|
+
* version that still runs its own build. A remote deduping onto a foreign build is skipped, because
|
|
46
|
+
* publishing its file while it runs somebody else's makes the global mapping and `servedBy` name two
|
|
47
|
+
* different builds for one remote (constraint 17). Order is otherwise the basis precedence `commit()`
|
|
48
|
+
* established, so skipping never promotes a worse-covered copy over a better one. A member with no entry has
|
|
49
|
+
* no basis left and leaves the shared set.
|
|
50
|
+
*/
|
|
51
|
+
export declare function basisPerMember(members: PoolMember[], islanded?: Islanded, dedupsElsewhere?: (remote: RemoteName) => boolean): Map<ExternalName, RemoteName>;
|
|
52
|
+
/**
|
|
53
|
+
* The host, when it ships anything in this pool. A host-contributed version carries `host: true` and
|
|
54
|
+
* basis precedence puts the host's own copy first on it, so `remotes[0]` of such a version is the host.
|
|
55
|
+
*/
|
|
56
|
+
export declare function hostRemotes(members: PoolMember[]): Set<RemoteName>;
|
|
57
|
+
/** First appearance of each remote across the pool — the arrival order anchor tiebreaks read. */
|
|
58
|
+
export declare function arrivalOrder(members: PoolMember[]): Map<RemoteName, number>;
|
|
59
|
+
/**
|
|
60
|
+
* The tag the global `imports` publishes per specifier. Reading the winning version's basis alone
|
|
61
|
+
* understates it, which islands remotes that were never at risk.
|
|
62
|
+
*/
|
|
63
|
+
export declare function sharedTagPerSpecifier(members: PoolMember[], islanded: Islanded): Map<Specifier, VersionName>;
|
|
@@ -1,6 +1,35 @@
|
|
|
1
|
-
import type { ExternalName, SharedExternal } from '../../../1.domain';
|
|
1
|
+
import type { ExternalName, RemoteName, SharedExternal, VersionName } from '../../../1.domain';
|
|
2
2
|
export type PoolName = string;
|
|
3
|
+
/** An entrypoint as a consumer imports it. Nothing is keyed by external name — see `pool-views.ts`. */
|
|
4
|
+
export type Specifier = string;
|
|
3
5
|
export type PoolMember = {
|
|
4
6
|
name: ExternalName;
|
|
5
7
|
external: SharedExternal;
|
|
6
8
|
};
|
|
9
|
+
export type FamilyInstance = Map<ExternalName, VersionName>;
|
|
10
|
+
/** Every specifier a build serves, mapped to the file it serves it from. */
|
|
11
|
+
export type Coverage = Map<Specifier, string>;
|
|
12
|
+
/** One copy a remote holds, as it runs it itself: `scope` rows included. */
|
|
13
|
+
export type OwnCopy = {
|
|
14
|
+
member: ExternalName;
|
|
15
|
+
tag: VersionName;
|
|
16
|
+
entries: Record<Specifier, string>;
|
|
17
|
+
};
|
|
18
|
+
/** One build, as every gate reads it. */
|
|
19
|
+
export type BuildView = {
|
|
20
|
+
coverage: Coverage;
|
|
21
|
+
tags: Map<Specifier, VersionName>;
|
|
22
|
+
instance: FamilyInstance;
|
|
23
|
+
};
|
|
24
|
+
export type CommittedView = {
|
|
25
|
+
builds: Map<RemoteName, BuildView>;
|
|
26
|
+
/** Per specifier, what the committed `imports` serves and from where. */
|
|
27
|
+
global: Map<Specifier, {
|
|
28
|
+
tag: VersionName;
|
|
29
|
+
remote: RemoteName;
|
|
30
|
+
file: string;
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
33
|
+
export type Islanded = {
|
|
34
|
+
has: (remote: RemoteName) => boolean;
|
|
35
|
+
};
|
|
@@ -1,3 +1,18 @@
|
|
|
1
1
|
import type { RemoteName } from '../../../1.domain';
|
|
2
|
+
import type { ForSharedExternalsStorage } from '../../driving-ports/for-shared-externals-storage.port';
|
|
3
|
+
import type { ModeConfig } from '../../config/mode.contract';
|
|
2
4
|
import type { PoolMember } from './pool.types';
|
|
5
|
+
export declare function lazy<T>(make: () => T): () => T;
|
|
3
6
|
export declare function remotesInPool(members: PoolMember[]): RemoteName[];
|
|
7
|
+
/**
|
|
8
|
+
* The scopes either pooling step has anything to do in. With auto-pooling off only a scope that carries a
|
|
9
|
+
* `pool` tag of its own can pool anything, and a pool never spans share scopes — so one tag must not put
|
|
10
|
+
* every other scope through a graph build. Auto-pooling on must never early-out, since any scoped package is
|
|
11
|
+
* potentially poolable. The `strict` scope is never pooled.
|
|
12
|
+
*
|
|
13
|
+
* Names only, so a caller that decides to skip a scope never reads it out of storage.
|
|
14
|
+
*/
|
|
15
|
+
export declare function poolableScopes(config: ModeConfig, repo: Pick<ForSharedExternalsStorage, 'getScopes' | 'scopeType' | 'hasPoolTag'>): {
|
|
16
|
+
useAutoExternalPooling: boolean;
|
|
17
|
+
scopes: string[];
|
|
18
|
+
};
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { type RemoteEntry, type SharedExternal, type DenseSharedInfo, type SharedVersion, type SharedVersionMeta } from '../../1.domain';
|
|
1
|
+
import { type RemoteEntry, type RemoteName, type SharedExternal, type DenseSharedInfo, type SharedVersion, type SharedVersionMeta } from '../../1.domain';
|
|
2
2
|
import type { DrivingContract } from '../driving-ports/driving.contract';
|
|
3
3
|
import type { LoggingConfig } from '../config/log.contract';
|
|
4
4
|
import type { ModeConfig } from '../config/mode.contract';
|
|
5
5
|
export type StoreRemoteEntry = (remoteEntry: RemoteEntry, onSharedExternal: SharedExternalHandler) => void;
|
|
6
|
+
export type RemoveCachedRemoteEntries = (remoteNames: ReadonlySet<RemoteName>) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Evict overridden remotes before their replacements are merged in. Kept out of `storeRemoteEntry`
|
|
9
|
+
* because `removeFromAllScopes` costs a full traversal of the shared-externals graph: the init
|
|
10
|
+
* pipeline evicts its whole override set at once, the dynamic pipeline passes a single name.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createRemoveCachedRemoteEntries(ports: Pick<DrivingContract, 'remoteInfoRepo' | 'scopedExternalsRepo' | 'sharedExternalsRepo' | 'sharedChunksRepo'>): RemoveCachedRemoteEntries;
|
|
6
13
|
export type SharedExternalHandler = (remoteEntry: RemoteEntry, external: DenseSharedInfo, ctx: SharedExternalContext) => void;
|
|
7
14
|
export type SharedExternalContext = {
|
|
8
15
|
tag: string;
|
|
@@ -13,11 +20,12 @@ export type SharedExternalContext = {
|
|
|
13
20
|
commit: () => void;
|
|
14
21
|
};
|
|
15
22
|
/**
|
|
16
|
-
* Not a pipeline step but the template both storage steps specialize: it owns the
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
23
|
+
* Not a pipeline step but the template both storage steps specialize: it owns the merge sequence
|
|
24
|
+
* for a remoteEntry, and only the resolution of a singleton external differs between the init
|
|
25
|
+
* pipeline (process-remote-entries, step 2) and the dynamic pipeline (update-cache, step 8),
|
|
26
|
+
* injected per call. Evicting an overridden remote is the caller's job, see
|
|
27
|
+
* `createRemoveCachedRemoteEntries` — every entry reaching here is merged additively. The
|
|
28
|
+
* pipelines must not be merged, see docs/version-resolver.md "Dynamic Init". If a change ever
|
|
29
|
+
* needs a flag or parameter here to serve only one caller, inline this back into the steps instead.
|
|
22
30
|
*/
|
|
23
31
|
export declare function createStoreRemoteEntry(config: LoggingConfig & ModeConfig, ports: Pick<DrivingContract, 'remoteInfoRepo' | 'scopedExternalsRepo' | 'sharedExternalsRepo' | 'sharedChunksRepo' | 'versionCheck'>, logStep: number): StoreRemoteEntry;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ExternalName, ImportMap, RemoteName, SharedExternal, VersionName } from '../../core/1.domain';
|
|
2
|
+
type Specifier = string;
|
|
3
|
+
/** Per remote, the `(specifier → tag)` set its own build ships — the witness candidates. */
|
|
4
|
+
export type Builds = Map<RemoteName, Map<Specifier, VersionName>>;
|
|
5
|
+
export type Incoherence = {
|
|
6
|
+
remote: RemoteName;
|
|
7
|
+
/** What the map resolves for it, per specifier of the pool. */
|
|
8
|
+
resolved: Record<Specifier, VersionName>;
|
|
9
|
+
/** The build that came closest to witnessing it, for a legible failure message. */
|
|
10
|
+
closest?: {
|
|
11
|
+
build: RemoteName;
|
|
12
|
+
matched: number;
|
|
13
|
+
of: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export type CoherenceInput = {
|
|
17
|
+
importMap: ImportMap;
|
|
18
|
+
/** The pool's members as stored — what `sharedExternalsRepo.getFromScope` returns, filtered to the pool. */
|
|
19
|
+
members: Record<ExternalName, SharedExternal>;
|
|
20
|
+
scopeUrls: Record<RemoteName, string>;
|
|
21
|
+
/** I3's only exemption: a host cannot be repointed onto another build, so it is never judged. */
|
|
22
|
+
hosts?: RemoteName[];
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* I3, the no-tear invariant, checked off the emitted import map rather than off the stored verdicts:
|
|
26
|
+
* for every non-host remote, the `(specifier → tag)` combination it really resolves must be a subset of
|
|
27
|
+
* some live build's own combination. Which remote *serves* a given tag is free — two builds at one tag
|
|
28
|
+
* are interchangeable providers — so this compares tags only, never origins.
|
|
29
|
+
*
|
|
30
|
+
* Reads the map the way a browser would (a remote's own scope entry wins over `imports`), so it fails on
|
|
31
|
+
* exactly the combinations a page would really run. Deliberately not called `findTears`:
|
|
32
|
+
* `apply-winner.ts:110` owns that name for entrypoint coverage, which is a different failure.
|
|
33
|
+
*/
|
|
34
|
+
export declare function findIncoherentRemotes({ importMap, members, scopeUrls, hosts, }: CoherenceInput): Incoherence[];
|
|
35
|
+
/** Every file the map can make a browser fetch — the static stand-in for the e2e download count. */
|
|
36
|
+
export declare function emittedUrls(importMap: ImportMap): Set<string>;
|
|
37
|
+
export {};
|