@softarc/native-federation-orchestrator 4.5.3 → 4.6.1

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.
Files changed (33) hide show
  1. package/README.md +2 -2
  2. package/fesm2022/@softarc/native-federation-orchestrator.mjs +1184 -266
  3. package/fesm2022/@softarc/native-federation-orchestrator.mjs.map +4 -4
  4. package/fesm2022/module-federation.mjs +34 -20
  5. package/fesm2022/module-federation.mjs.map +3 -3
  6. package/fesm2022/node.mjs +1084 -247
  7. package/fesm2022/node.mjs.map +4 -4
  8. package/node-loader/loader.mjs +19 -2
  9. package/node-loader/loader.mjs.map +2 -2
  10. package/package.json +1 -1
  11. package/quickstart.mjs +15 -15
  12. package/types/lib/core/1.domain/externals/basis.d.ts +17 -2
  13. package/types/lib/core/1.domain/externals/version.contract.d.ts +1 -0
  14. package/types/lib/core/1.domain/remote-entry/remote-entry.contract.d.ts +1 -0
  15. package/types/lib/core/2.app/driver-ports/init/drivers.contract.d.ts +2 -0
  16. package/types/lib/core/2.app/driver-ports/init/for-determining-shared-externals.port.d.ts +8 -1
  17. package/types/lib/core/2.app/driver-ports/init/for-marking-pools-for-reelection.port.d.ts +6 -0
  18. package/types/lib/core/2.app/driver-ports/init/for-pooling-shared-externals.port.d.ts +3 -1
  19. package/types/lib/core/2.app/driving-ports/for-shared-chunks-storage.port.d.ts +1 -0
  20. package/types/lib/core/2.app/driving-ports/for-shared-externals-storage.port.d.ts +1 -2
  21. package/types/lib/core/2.app/steps/apply-winner.d.ts +18 -0
  22. package/types/lib/core/2.app/steps/pooling/anchoring.d.ts +69 -0
  23. package/types/lib/core/2.app/steps/pooling/mark-pools-for-reelection.d.ts +5 -0
  24. package/types/lib/core/2.app/steps/pooling/pool-dynamic-externals.d.ts +3 -1
  25. package/types/lib/core/2.app/steps/pooling/pool-graph.d.ts +10 -5
  26. package/types/lib/core/2.app/steps/pooling/pool-shared-externals.d.ts +30 -1
  27. package/types/lib/core/2.app/steps/pooling/pool-views.d.ts +63 -0
  28. package/types/lib/core/2.app/steps/pooling/pool.types.d.ts +30 -1
  29. package/types/lib/core/2.app/steps/pooling/pool.util.d.ts +15 -0
  30. package/types/lib/core/2.app/steps/store-remote-entry.d.ts +1 -1
  31. package/types/lib/core/3.adapters/browser/sse-handler.d.ts +0 -8
  32. package/types/lib/testing/pooling/no-tear.d.ts +37 -0
  33. package/types/scripts/node-loader.d.ts +1 -0
@@ -3,5 +3,20 @@ export declare function isBetterBasis(candidate: SharedVersionMeta, basis: Share
3
3
  export declare function addRemoteToVersion(version: SharedVersion, remote: SharedVersionMeta, isHost?: boolean): void;
4
4
  export declare function versionDemands(version: SharedVersion): SharedVersionMeta[];
5
5
  export declare function findVersionForTag(versions: SharedVersion[], tag: string): SharedVersion | undefined;
6
- export declare function uncoveredEntrypoints(remote: SharedVersionMeta, basis: Record<string, string>): string[];
7
- export declare function countUncoveredEntrypoints(remote: SharedVersionMeta, basis: Record<string, string>): number;
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 {};
@@ -21,4 +21,5 @@ export type SharedVersionMeta = {
21
21
  bundle?: string;
22
22
  pool?: string;
23
23
  entries: Record<string, string>;
24
+ servedBy?: RemoteName;
24
25
  };
@@ -12,5 +12,6 @@ type SharedInfoActions = Record<string, {
12
12
  action: SharedVersionAction;
13
13
  override?: Record<string, string>;
14
14
  covered?: string[];
15
+ sameVersion?: boolean;
15
16
  }>;
16
17
  export { RawRemoteEntry, RemoteEntry, FederationInfo, ExposesInfo, SharedInfo, DenseSharedInfo, SharedInfoActions, };
@@ -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
- export type ForDeterminingSharedExternals = () => Promise<void>;
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
- export type ForPoolingSharedExternals = () => Promise<void>;
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
  };
@@ -9,7 +9,6 @@ export type ForSharedExternalsStorage = {
9
9
  scopeType: (shareScope?: string) => 'global' | 'strict' | 'shareScope';
10
10
  removeFromAllScopes: (remoteNames: ReadonlySet<RemoteName>) => void;
11
11
  addOrUpdate: (name: string, external: SharedExternal, shareScope?: string) => ForSharedExternalsStorage;
12
- markPoolTagPresent: () => void;
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
- export declare function createPoolDynamicExternals(config: ModeConfig): ForPoolingDynamicExternals;
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 with edges from auto-pooling (`external -> scope`, global) and declared tags
18
- * (`external -> tag@remote`, remote-local). See docs/version-resolver.md + plan.md. Returns only real
19
- * pools (>=2 members), keyed by and iterated in order of their canonical name (smallest member —
20
- * reload-stable). An explicit-tag member that pooled with nothing is warned (likely typo or missing
21
- * sibling); auto-scope singletons stay silent.
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
- export declare function createPoolSharedExternals(config: LoggingConfig & ModeConfig, ports: Pick<DrivingContract, 'sharedExternalsRepo'>): ForPoolingSharedExternals;
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
+ };
@@ -9,7 +9,7 @@ export type RemoveCachedRemoteEntries = (remoteNames: ReadonlySet<RemoteName>) =
9
9
  * because `removeFromAllScopes` costs a full traversal of the shared-externals graph: the init
10
10
  * pipeline evicts its whole override set at once, the dynamic pipeline passes a single name.
11
11
  */
12
- export declare function createRemoveCachedRemoteEntries(ports: Pick<DrivingContract, 'remoteInfoRepo' | 'scopedExternalsRepo' | 'sharedExternalsRepo'>): RemoveCachedRemoteEntries;
12
+ export declare function createRemoveCachedRemoteEntries(ports: Pick<DrivingContract, 'remoteInfoRepo' | 'scopedExternalsRepo' | 'sharedExternalsRepo' | 'sharedChunksRepo'>): RemoveCachedRemoteEntries;
13
13
  export type SharedExternalHandler = (remoteEntry: RemoteEntry, external: DenseSharedInfo, ctx: SharedExternalContext) => void;
14
14
  export type SharedExternalContext = {
15
15
  tag: string;
@@ -1,13 +1,5 @@
1
1
  import type { ImportMapConfig } from '../../2.app/config/import-map.contract';
2
2
  import type { ForSSE } from '../../2.app/driving-ports/for-sse.port';
3
3
  import type { LoggingConfig } from '../../2.app/config/log.contract';
4
- /**
5
- * Watches for federation build completion events and automatically reloads the page.
6
- *
7
- * This function establishes a Server-Sent Events (SSE) connection to listen for
8
- * 'federation-rebuild-complete' notifications. When a build completes successfully,
9
- * it triggers a page reload to reflect the latest changes.
10
- * @param endpoint - The SSE endpoint URL to watch for build notifications.
11
- */
12
4
  declare const createSSEHandler: (config: ImportMapConfig & LoggingConfig) => ForSSE;
13
5
  export { createSSEHandler };
@@ -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 {};
@@ -35,6 +35,7 @@ type LoadResult = {
35
35
  format: string;
36
36
  source?: string | ArrayBuffer | Uint8Array;
37
37
  shortCircuit?: boolean;
38
+ responseURL?: string;
38
39
  };
39
40
  type NextLoad = (url: string, context?: LoadContext) => Promise<LoadResult>;
40
41
  export declare function load(url: string, context: LoadContext, nextLoad: NextLoad): Promise<LoadResult>;