@softarc/native-federation-orchestrator 4.7.0 → 4.8.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.
Files changed (72) hide show
  1. package/fesm2022/@softarc/native-federation-orchestrator.mjs +1810 -1359
  2. package/fesm2022/@softarc/native-federation-orchestrator.mjs.map +4 -4
  3. package/fesm2022/audit.mjs +1 -1
  4. package/fesm2022/audit.mjs.map +2 -2
  5. package/fesm2022/module-federation.mjs +199 -32
  6. package/fesm2022/module-federation.mjs.map +3 -3
  7. package/fesm2022/node.mjs +1804 -1353
  8. package/fesm2022/node.mjs.map +4 -4
  9. package/fesm2022/options.mjs +1 -3
  10. package/fesm2022/options.mjs.map +2 -2
  11. package/fesm2022/registry.mjs +1 -1
  12. package/fesm2022/registry.mjs.map +2 -2
  13. package/fesm2022/sdk.mjs +1 -1
  14. package/fesm2022/sdk.mjs.map +2 -2
  15. package/init-registry.mjs +1 -1
  16. package/package.json +9 -1
  17. package/quickstart.mjs +14 -15
  18. package/types/lib/core/1.domain/externals/basis.d.ts +2 -15
  19. package/types/lib/core/1.domain/externals/claims.d.ts +22 -0
  20. package/types/lib/core/1.domain/externals/compatibility.d.ts +9 -0
  21. package/types/lib/core/1.domain/externals/external.contract.d.ts +3 -0
  22. package/types/lib/core/1.domain/externals/rows.d.ts +9 -0
  23. package/types/lib/core/1.domain/externals/serving.d.ts +52 -0
  24. package/types/lib/core/1.domain/externals/specifier.d.ts +13 -0
  25. package/types/lib/core/1.domain/externals/version.contract.d.ts +1 -1
  26. package/types/lib/core/1.domain/pooling/builds.d.ts +20 -0
  27. package/types/lib/core/1.domain/pooling/election.d.ts +40 -0
  28. package/types/lib/core/1.domain/pooling/gate.d.ts +62 -0
  29. package/types/lib/core/1.domain/pooling/membership.d.ts +17 -0
  30. package/types/lib/core/1.domain/pooling/plan.d.ts +9 -0
  31. package/types/lib/core/1.domain/pooling/pool-state.d.ts +4 -0
  32. package/types/lib/core/1.domain/pooling/records.d.ts +16 -0
  33. package/types/lib/core/1.domain/pooling/rules.d.ts +6 -0
  34. package/types/lib/core/1.domain/remote-entry/load-actions.contract.d.ts +4 -0
  35. package/types/lib/core/1.domain/remote-entry/remote-entry.contract.d.ts +0 -2
  36. package/types/lib/core/2.app/config/mode.contract.d.ts +8 -4
  37. package/types/lib/core/2.app/config/storage.contract.d.ts +6 -1
  38. package/types/lib/core/2.app/driver-ports/init/drivers.contract.d.ts +4 -2
  39. package/types/lib/core/2.app/driver-ports/init/for-committing-changes.port.d.ts +1 -0
  40. package/types/lib/core/2.app/driver-ports/init/for-deciding-serving-builds.port.d.ts +1 -0
  41. package/types/lib/core/2.app/driver-ports/init/for-determining-shared-externals.port.d.ts +1 -8
  42. package/types/lib/core/2.app/driver-ports/init/for-pooling-dynamic-externals.port.d.ts +2 -1
  43. package/types/lib/core/2.app/driver-ports/init/for-pooling-shared-externals.port.d.ts +1 -3
  44. package/types/lib/core/2.app/driver-ports/init/for-rolling-back-changes.port.d.ts +1 -0
  45. package/types/lib/core/2.app/driver-ports/init/for-updating-cache.d.ts +3 -2
  46. package/types/lib/core/2.app/driver-ports/init/index.d.ts +2 -0
  47. package/types/lib/core/2.app/driving-ports/for-remote-info-storage.port.d.ts +1 -0
  48. package/types/lib/core/2.app/driving-ports/for-scoped-externals-storage.port.d.ts +1 -0
  49. package/types/lib/core/2.app/driving-ports/for-shared-chunks-storage.port.d.ts +1 -0
  50. package/types/lib/core/2.app/driving-ports/for-shared-externals-storage.port.d.ts +5 -1
  51. package/types/lib/core/2.app/steps/apply-winner.d.ts +6 -13
  52. package/types/lib/core/2.app/steps/decide-serving-builds.d.ts +5 -0
  53. package/types/lib/core/2.app/steps/pooling/pool-shared-externals.d.ts +0 -29
  54. package/types/lib/core/2.app/steps/rollback-changes.d.ts +5 -0
  55. package/types/lib/core/2.app/steps/store-remote-entry.d.ts +2 -2
  56. package/types/lib/core/3.adapters/storage/cache-state.d.ts +5 -0
  57. package/types/lib/core/4.config/orchestrator-version.d.ts +1 -0
  58. package/types/lib/core/4.config/storage/storage.config.d.ts +1 -1
  59. package/types/lib/core/5.di/federation-result.factory.d.ts +1 -1
  60. package/types/lib/sdk.index.d.ts +4 -0
  61. package/types/lib/utils/compare-strings.d.ts +2 -0
  62. package/types/lib/utils/deep-readonly.d.ts +3 -0
  63. package/types/lib/core/2.app/driver-ports/init/for-marking-pools-for-reelection.port.d.ts +0 -6
  64. package/types/lib/core/2.app/steps/pooling/anchoring.d.ts +0 -69
  65. package/types/lib/core/2.app/steps/pooling/mark-pools-for-reelection.d.ts +0 -5
  66. package/types/lib/core/2.app/steps/pooling/pool-graph.d.ts +0 -25
  67. package/types/lib/core/2.app/steps/pooling/pool-views.d.ts +0 -63
  68. package/types/lib/core/2.app/steps/pooling/pool.types.d.ts +0 -35
  69. package/types/lib/core/2.app/steps/pooling/pool.util.d.ts +0 -19
  70. package/types/lib/testing/handlers/log.handler.d.ts +0 -3
  71. package/types/lib/testing/pooling/no-tear.d.ts +0 -37
  72. package/types/lib/testing/pooling/tag-by-npm-scope.d.ts +0 -9
@@ -223,7 +223,7 @@ var require_semver = __commonJS({
223
223
  }
224
224
  return true;
225
225
  };
226
- var SemVer = class _SemVer {
226
+ var SemVer2 = class _SemVer {
227
227
  constructor(version, options) {
228
228
  options = parseOptions(options);
229
229
  if (version instanceof _SemVer) {
@@ -490,7 +490,7 @@ var require_semver = __commonJS({
490
490
  return this;
491
491
  }
492
492
  };
493
- module.exports = SemVer;
493
+ module.exports = SemVer2;
494
494
  }
495
495
  });
496
496
 
@@ -498,13 +498,13 @@ var require_semver = __commonJS({
498
498
  var require_parse = __commonJS({
499
499
  "node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/parse.js"(exports, module) {
500
500
  "use strict";
501
- var SemVer = require_semver();
501
+ var SemVer2 = require_semver();
502
502
  var parse = (version, options, throwErrors = false) => {
503
- if (version instanceof SemVer) {
503
+ if (version instanceof SemVer2) {
504
504
  return version;
505
505
  }
506
506
  try {
507
- return new SemVer(version, options);
507
+ return new SemVer2(version, options);
508
508
  } catch (er) {
509
509
  if (!throwErrors) {
510
510
  return null;
@@ -571,8 +571,8 @@ var require_lrucache = __commonJS({
571
571
  var require_compare = __commonJS({
572
572
  "node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/compare.js"(exports, module) {
573
573
  "use strict";
574
- var SemVer = require_semver();
575
- var compare = (a, b, loose) => new SemVer(a, loose).compare(new SemVer(b, loose));
574
+ var SemVer2 = require_semver();
575
+ var compare = (a, b, loose) => new SemVer2(a, loose).compare(new SemVer2(b, loose));
576
576
  module.exports = compare;
577
577
  }
578
578
  });
@@ -730,7 +730,7 @@ var require_comparator = __commonJS({
730
730
  if (!m[2]) {
731
731
  this.semver = ANY;
732
732
  } else {
733
- this.semver = new SemVer(m[2], this.options.loose);
733
+ this.semver = new SemVer2(m[2], this.options.loose);
734
734
  }
735
735
  }
736
736
  toString() {
@@ -743,7 +743,7 @@ var require_comparator = __commonJS({
743
743
  }
744
744
  if (typeof version === "string") {
745
745
  try {
746
- version = new SemVer(version, this.options);
746
+ version = new SemVer2(version, this.options);
747
747
  } catch (er) {
748
748
  return false;
749
749
  }
@@ -795,7 +795,7 @@ var require_comparator = __commonJS({
795
795
  var { safeRe: re, t } = require_re();
796
796
  var cmp = require_cmp();
797
797
  var debug = require_debug();
798
- var SemVer = require_semver();
798
+ var SemVer2 = require_semver();
799
799
  var Range = require_range();
800
800
  }
801
801
  });
@@ -931,7 +931,7 @@ var require_range = __commonJS({
931
931
  }
932
932
  if (typeof version === "string") {
933
933
  try {
934
- version = new SemVer(version, this.options);
934
+ version = new SemVer2(version, this.options);
935
935
  } catch (er) {
936
936
  return false;
937
937
  }
@@ -950,7 +950,7 @@ var require_range = __commonJS({
950
950
  var parseOptions = require_parse_options();
951
951
  var Comparator = require_comparator();
952
952
  var debug = require_debug();
953
- var SemVer = require_semver();
953
+ var SemVer2 = require_semver();
954
954
  var {
955
955
  safeRe: re,
956
956
  src,
@@ -1206,16 +1206,16 @@ var require_satisfies = __commonJS({
1206
1206
  var require_min_version = __commonJS({
1207
1207
  "node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/min-version.js"(exports, module) {
1208
1208
  "use strict";
1209
- var SemVer = require_semver();
1209
+ var SemVer2 = require_semver();
1210
1210
  var Range = require_range();
1211
1211
  var gt = require_gt();
1212
1212
  var minVersion = (range, loose) => {
1213
1213
  range = new Range(range, loose);
1214
- let minver = new SemVer("0.0.0");
1214
+ let minver = new SemVer2("0.0.0");
1215
1215
  if (range.test(minver)) {
1216
1216
  return minver;
1217
1217
  }
1218
- minver = new SemVer("0.0.0-0");
1218
+ minver = new SemVer2("0.0.0-0");
1219
1219
  if (range.test(minver)) {
1220
1220
  return minver;
1221
1221
  }
@@ -1224,7 +1224,7 @@ var require_min_version = __commonJS({
1224
1224
  const comparators = range.set[i];
1225
1225
  let setMin = null;
1226
1226
  comparators.forEach((comparator) => {
1227
- const compver = new SemVer(comparator.semver.version);
1227
+ const compver = new SemVer2(comparator.semver.version);
1228
1228
  switch (comparator.operator) {
1229
1229
  case ">":
1230
1230
  if (compver.prerelease.length === 0) {
@@ -1283,7 +1283,7 @@ var createInitFlow = ({
1283
1283
  adapters,
1284
1284
  config
1285
1285
  }) => {
1286
- return (remotesOrManifestUrl) => flow.getRemoteEntries(remotesOrManifestUrl).then(flow.processRemoteEntries).then(flow.markPoolsForReelection).then(flow.determineSharedExternals).then(flow.poolSharedExternals).then(flow.generateImportMap).then((importMap) => flow.commitChanges(importMap, { override: true })).then(flow.exposeModuleLoader).then((loadRemoteModule) => ({
1286
+ return (remotesOrManifestUrl) => flow.getRemoteEntries(remotesOrManifestUrl).then(flow.processRemoteEntries).then(flow.poolSharedExternals).then(flow.determineSharedExternals).then(flow.decideServingBuilds).then(flow.generateImportMap).then((importMap) => flow.commitChanges(importMap, { override: true })).then(flow.exposeModuleLoader).then((loadRemoteModule) => ({
1287
1287
  config,
1288
1288
  adapters,
1289
1289
  loadRemoteModule
@@ -1294,14 +1294,17 @@ var createInitFlow = ({
1294
1294
  var createInitRemoteEntryFlow = ({
1295
1295
  flow
1296
1296
  }) => {
1297
- const processDynamicRemoteEntry = (remoteEntry) => flow.updateCache(remoteEntry).then(flow.poolDynamicExternals).then(flow.convertToImportMap).then(flow.commitChanges);
1298
- return (remoteEntryUrl, remote) => flow.getRemoteEntry(remoteEntryUrl, remote).then((entry) => entry.map(processDynamicRemoteEntry).orElse(Promise.resolve())).then(() => void 0);
1297
+ const processDynamicRemoteEntry = (remoteEntry) => flow.updateCache(remoteEntry).then(flow.poolDynamicExternals).then(flow.convertToImportMap).then((importMap) => flow.commitChanges(importMap, { markUndecided: true }));
1298
+ return (remoteEntryUrl, remote) => flow.getRemoteEntry(remoteEntryUrl, remote).then((entry) => entry.map(processDynamicRemoteEntry).orElse(Promise.resolve())).then(() => void 0).catch((error) => {
1299
+ const remoteName = typeof remote === "string" ? remote : remote?.name;
1300
+ return flow.rollbackChanges(`[dynamic-init][${remoteName ?? remoteEntryUrl}]`).then(() => Promise.reject(error));
1301
+ });
1299
1302
  };
1300
1303
 
1301
1304
  // src/lib/core/native-federation.error.ts
1302
1305
  var NFError = class extends Error {
1303
1306
  constructor(message, cause) {
1304
- super(message, cause);
1307
+ super(message, cause ? { cause } : void 0);
1305
1308
  this.name = "NFError";
1306
1309
  }
1307
1310
  };
@@ -1426,11 +1429,17 @@ function createGetRemoteEntries(config, ports) {
1426
1429
  }
1427
1430
  }
1428
1431
 
1432
+ // src/lib/utils/compare-strings.ts
1433
+ function compareStrings(a, b) {
1434
+ return a < b ? -1 : a > b ? 1 : 0;
1435
+ }
1436
+
1429
1437
  // src/lib/core/1.domain/externals/basis.ts
1430
1438
  var coverage = (remote) => Object.keys(remote.entries).length;
1431
1439
  function isBetterBasis(candidate, basis) {
1432
1440
  if (basis.cached !== candidate.cached) return candidate.cached;
1433
- return coverage(candidate) > coverage(basis);
1441
+ const byCoverage = coverage(candidate) - coverage(basis);
1442
+ return byCoverage === 0 ? compareStrings(candidate.name, basis.name) < 0 : byCoverage > 0;
1434
1443
  }
1435
1444
  function addRemoteToVersion(version, remote, isHost = false) {
1436
1445
  if (isHost) {
@@ -1454,21 +1463,6 @@ function versionDemands(version) {
1454
1463
  }
1455
1464
  return Array.from(distinct.values());
1456
1465
  }
1457
- function findVersionForTag(versions, tag) {
1458
- let scoped;
1459
- for (const version of versions) {
1460
- if (version.tag !== tag) continue;
1461
- if (version.action !== "scope") return version;
1462
- scoped ??= version;
1463
- }
1464
- return scoped;
1465
- }
1466
- function versionEntries(version) {
1467
- return collectEntries(version, void 0);
1468
- }
1469
- function committedEntries(version) {
1470
- return collectEntries(version, (remote) => remote.cached);
1471
- }
1472
1466
  function forEachVersionEntry(version, accepts, visit) {
1473
1467
  for (const remote of version.remotes) {
1474
1468
  if (remote.servedBy) continue;
@@ -1476,16 +1470,6 @@ function forEachVersionEntry(version, accepts, visit) {
1476
1470
  for (const entrypoint in remote.entries) visit(entrypoint, remote);
1477
1471
  }
1478
1472
  }
1479
- function collectEntries(version, accepts) {
1480
- const entries = /* @__PURE__ */ new Map();
1481
- forEachVersionEntry(version, accepts, (entrypoint, remote) => {
1482
- if (!entries.has(entrypoint)) entries.set(entrypoint, remote);
1483
- });
1484
- return entries;
1485
- }
1486
- function uncoveredEntrypoints(remote, covered) {
1487
- return Object.keys(remote.entries).filter((entrypoint) => !covered.has(entrypoint));
1488
- }
1489
1473
  function countUncoveredEntrypoints(remote, covered) {
1490
1474
  let uncovered = 0;
1491
1475
  for (const entrypoint in remote.entries) {
@@ -1494,6 +1478,89 @@ function countUncoveredEntrypoints(remote, covered) {
1494
1478
  return uncovered;
1495
1479
  }
1496
1480
 
1481
+ // src/lib/core/1.domain/externals/rows.ts
1482
+ var byTag = (compare) => (a, b) => compare(b.tag, a.tag);
1483
+ var rowAt = (versions, tag, action) => versions.find((v) => v.tag === tag && v.action === action);
1484
+ function mergeRows(versions, lead) {
1485
+ const merged = /* @__PURE__ */ new Map();
1486
+ if (lead) merged.set(`${lead.tag}|${lead.action}`, lead);
1487
+ return versions.filter((v) => {
1488
+ const key = `${v.tag}|${v.action}`;
1489
+ const first = merged.get(key);
1490
+ if (first === v) return true;
1491
+ if (!first) {
1492
+ merged.set(key, v);
1493
+ return true;
1494
+ }
1495
+ first.remotes.push(...v.remotes);
1496
+ return false;
1497
+ });
1498
+ }
1499
+ function findVersionForTag(versions, tag) {
1500
+ let scoped;
1501
+ for (const version of versions) {
1502
+ if (version.tag !== tag) continue;
1503
+ if (version.action !== "scope") return version;
1504
+ scoped ??= version;
1505
+ }
1506
+ return scoped;
1507
+ }
1508
+ function redirectRow(version, tag, acceptsTag) {
1509
+ const objecting = new Set(
1510
+ version.remotes.filter((r) => r.strictVersion && !acceptsTag(tag, version.tag, r.requiredVersion))
1511
+ );
1512
+ if (objecting.size === 0) {
1513
+ version.action = "skip";
1514
+ return [version];
1515
+ }
1516
+ if (objecting.size === version.remotes.length) {
1517
+ version.action = "scope";
1518
+ return [version];
1519
+ }
1520
+ version.remotes = version.remotes.filter((r) => !objecting.has(r));
1521
+ version.action = "skip";
1522
+ return [version, { tag: version.tag, host: false, action: "scope", remotes: [...objecting] }];
1523
+ }
1524
+ function scopeRemotes(versions, remotes) {
1525
+ if (remotes.length === 0) return versions;
1526
+ const turn = new Map(remotes.map((remote, i) => [remote, i]));
1527
+ const moves = /* @__PURE__ */ new Map();
1528
+ versions.forEach((source, at) => {
1529
+ if (source.action === "scope") return;
1530
+ const byTurn = /* @__PURE__ */ new Map();
1531
+ for (const copy of source.remotes) {
1532
+ const i = turn.get(copy.name);
1533
+ if (i === void 0) continue;
1534
+ const copies = byTurn.get(i);
1535
+ if (copies) copies.push(copy);
1536
+ else byTurn.set(i, [copy]);
1537
+ }
1538
+ if (byTurn.size === 0) return;
1539
+ const hostTurn = source.host ? turn.get(source.remotes[0].name) : void 0;
1540
+ source.remotes = source.remotes.filter((r) => !turn.has(r.name));
1541
+ if (hostTurn !== void 0) source.host = false;
1542
+ const tagMoves = moves.get(source.tag) ?? [];
1543
+ for (const [i, copies] of byTurn)
1544
+ tagMoves.push({ turn: i, at, source, copies, host: i === hostTurn });
1545
+ moves.set(source.tag, tagMoves);
1546
+ });
1547
+ const rows = [...versions];
1548
+ for (const [tag, tagMoves] of moves) {
1549
+ tagMoves.sort((a, b) => a.turn - b.turn || a.at - b.at);
1550
+ let scoped = rowAt(rows, tag, "scope");
1551
+ if (!scoped) {
1552
+ scoped = { tag, host: false, action: "scope", remotes: [] };
1553
+ rows.splice(rows.indexOf(tagMoves[0].source) + 1, 0, scoped);
1554
+ }
1555
+ for (const { copies, host } of tagMoves)
1556
+ if (host) {
1557
+ scoped.host = true;
1558
+ scoped.remotes.unshift(...copies);
1559
+ } else scoped.remotes.push(...copies);
1560
+ }
1561
+ return rows.filter((v) => v.remotes.length > 0);
1562
+ }
1563
+
1497
1564
  // src/lib/core/2.app/steps/store-remote-entry.ts
1498
1565
  function createRemoveCachedRemoteEntries(ports) {
1499
1566
  return (remoteNames) => {
@@ -1527,6 +1594,11 @@ function createStoreRemoteEntry(config, ports, logStep) {
1527
1594
  const tag = resolveVersion(remoteEntry, external);
1528
1595
  if (tag === null) return;
1529
1596
  if (external.singleton) {
1597
+ if (external.requiredVersion && !ports.versionCheck.isCompatible(tag, external.requiredVersion))
1598
+ config.log.warn(
1599
+ logStep,
1600
+ `[${remoteEntry.name}][${external.packageName}] requiredVersion '${external.requiredVersion}' excludes its own version '${tag}'; '${tag}' is still accepted for it.`
1601
+ );
1530
1602
  onSharedExternal(remoteEntry, external, sharedExternalContext(remoteEntry, external, tag));
1531
1603
  } else {
1532
1604
  addScopedExternal(remoteEntry, external, tag);
@@ -1577,11 +1649,12 @@ function createStoreRemoteEntry(config, ports, logStep) {
1577
1649
  ...sharedInfo.pool?.trim() ? { pool: sharedInfo.pool } : {},
1578
1650
  entries: sharedInfo.entries
1579
1651
  };
1580
- const cached = ports.sharedExternalsRepo.tryGet(sharedInfo.packageName, sharedInfo.shareScope).get() ?? { dirty: false, versions: [] };
1652
+ let stored;
1653
+ const readRecord = () => stored ??= ports.sharedExternalsRepo.tryGet(sharedInfo.packageName, sharedInfo.shareScope).get() ?? { dirty: false, versions: [] };
1581
1654
  return {
1582
1655
  tag,
1583
1656
  remote,
1584
- cached,
1657
+ readRecord,
1585
1658
  scopeType,
1586
1659
  assertSameVersionCompatibility(matchingVersion) {
1587
1660
  if (!remote.strictVersion) return;
@@ -1593,13 +1666,12 @@ function createStoreRemoteEntry(config, ports, logStep) {
1593
1666
  }
1594
1667
  config.log.warn(logStep, errorMsg);
1595
1668
  },
1596
- commit() {
1669
+ write() {
1597
1670
  ports.sharedExternalsRepo.addOrUpdate(
1598
1671
  sharedInfo.packageName,
1599
1672
  {
1600
- dirty: cached.dirty,
1601
- ...cached.poolName !== void 0 && { poolName: cached.poolName },
1602
- versions: cached.versions.sort((a, b) => ports.versionCheck.compare(b.tag, a.tag))
1673
+ ...readRecord(),
1674
+ versions: readRecord().versions.sort((a, b) => ports.versionCheck.compare(b.tag, a.tag))
1603
1675
  },
1604
1676
  sharedInfo.shareScope
1605
1677
  );
@@ -1643,11 +1715,12 @@ function createProcessRemoteEntries(config, ports) {
1643
1715
  function addSharedExternal(remoteEntry, _sharedInfo, {
1644
1716
  tag,
1645
1717
  remote,
1646
- cached,
1718
+ readRecord,
1647
1719
  scopeType,
1648
1720
  assertSameVersionCompatibility,
1649
- commit
1721
+ write
1650
1722
  }) {
1723
+ const cached = readRecord();
1651
1724
  if (scopeType !== "strict") cached.dirty = true;
1652
1725
  const matchingVersion = findVersionForTag(cached.versions, tag);
1653
1726
  if (matchingVersion) {
@@ -1661,7 +1734,7 @@ function createProcessRemoteEntries(config, ports) {
1661
1734
  remotes: [remote]
1662
1735
  });
1663
1736
  }
1664
- commit();
1737
+ write();
1665
1738
  }
1666
1739
  }
1667
1740
 
@@ -1669,154 +1742,70 @@ function createProcessRemoteEntries(config, ports) {
1669
1742
  var GLOBAL_SCOPE = "__GLOBAL__";
1670
1743
  var STRICT_SCOPE = "strict";
1671
1744
 
1672
- // src/lib/core/2.app/steps/apply-winner.ts
1673
- function versionAcceptance(external, isCompatible) {
1745
+ // src/lib/core/1.domain/externals/compatibility.ts
1746
+ function createAcceptsTag(isCompatible, compare) {
1747
+ return (offered, ownTag, requiredVersion) => isCompatible(offered, requiredVersion) || offered === ownTag || compare(offered, ownTag) === 0;
1748
+ }
1749
+ function versionAcceptance(external, acceptsTag) {
1674
1750
  const demands = new Map(
1675
1751
  external.versions.map((v) => [v, versionDemands(v)])
1676
1752
  );
1677
1753
  return {
1678
- accepts: (version, tag) => demands.get(version).every((d) => isCompatible(tag, d.requiredVersion)),
1679
- objector: (version, tag) => demands.get(version).find((d) => d.strictVersion && !isCompatible(tag, d.requiredVersion))
1754
+ accepts: (version, tag) => demands.get(version).every((d) => acceptsTag(tag, version.tag, d.requiredVersion)),
1755
+ objector: (version, tag) => demands.get(version).find((d) => d.strictVersion && !acceptsTag(tag, version.tag, d.requiredVersion))
1680
1756
  };
1681
1757
  }
1758
+
1759
+ // src/lib/core/2.app/steps/apply-winner.ts
1760
+ function rejectIncompatible(config, copy, rejected, servedAt) {
1761
+ config.log.error(
1762
+ 3,
1763
+ `[${copy.name}][${rejected}] Is not compatible with requiredRange '${copy.requiredVersion}' of shared ${servedAt}.`
1764
+ );
1765
+ throw new NFError(`External ${rejected} could not be shared.`);
1766
+ }
1682
1767
  function createApplyWinner(config) {
1683
- return function applyWinner(externalName, external, winner, isCompatible, acceptance) {
1768
+ return function applyWinner(externalName, external, winner, acceptsTag, acceptance) {
1684
1769
  if (external.versions.length > 1) {
1685
- const { accepts, objector } = acceptance ?? versionAcceptance(external, isCompatible);
1686
1770
  const rebuilt = [];
1687
1771
  for (const v of external.versions) {
1688
- rebuilt.push(v);
1689
- if (accepts(v, winner.tag)) {
1690
- v.action = "skip";
1691
- continue;
1692
- }
1693
- const strict = objector(v, winner.tag);
1694
- if (config.strict.strictExternalCompatibility && strict) {
1695
- config.log.error(
1696
- 3,
1697
- `[${strict.name}][${externalName}@${v.tag}] Is not compatible with requiredRange '${strict.requiredVersion}' of shared ${externalName}@${winner.tag}.`
1772
+ const objector = config.strict.strictExternalCompatibility && acceptance.objector(v, winner.tag);
1773
+ if (objector)
1774
+ rejectIncompatible(
1775
+ config,
1776
+ objector,
1777
+ `${externalName}@${v.tag}`,
1778
+ `${externalName}@${winner.tag}`
1698
1779
  );
1699
- throw new NFError(`External ${externalName}@${v.tag} could not be shared.`);
1700
- }
1701
- if (v === winner) continue;
1702
- if (!strict) {
1703
- v.action = "skip";
1704
- continue;
1705
- }
1706
- const objecting = new Set(
1707
- v.remotes.filter((r) => r.strictVersion && !isCompatible(winner.tag, r.requiredVersion))
1708
- );
1709
- if (objecting.size === v.remotes.length) {
1710
- v.action = "scope";
1711
- continue;
1712
- }
1713
- v.remotes = v.remotes.filter((r) => !objecting.has(r));
1714
- v.action = "skip";
1715
- rebuilt.push({ tag: v.tag, host: false, action: "scope", remotes: [...objecting] });
1780
+ rebuilt.push(...redirectRow(v, winner.tag, acceptsTag));
1716
1781
  }
1717
- const merged = /* @__PURE__ */ new Map();
1718
- external.versions = rebuilt.filter((v) => {
1719
- const first = merged.get(`${v.tag}|${v.action}`);
1720
- if (!first) {
1721
- merged.set(`${v.tag}|${v.action}`, v);
1722
- return true;
1723
- }
1724
- first.remotes.push(...v.remotes);
1725
- return false;
1726
- });
1782
+ external.versions = mergeRows(rebuilt, winner);
1727
1783
  }
1728
1784
  winner.action = "share";
1729
- applyEntrypointCoveragePolicy(externalName, external);
1730
1785
  external.dirty = false;
1731
1786
  return external;
1732
1787
  };
1733
- function applyEntrypointCoveragePolicy(externalName, external) {
1734
- const { strictEntryPointCoverage } = config.strict;
1735
- if (!strictEntryPointCoverage && !config.profile.scopeUncoveredEntrypoints) return;
1736
- const tears = findTears(external);
1737
- if (tears.length === 0) return;
1738
- if (strictEntryPointCoverage) {
1739
- const { version, remote, uncovered } = tears[0];
1740
- config.log.error(
1741
- 3,
1742
- `[${externalName}@${version.tag}][${remote.name}] Entrypoints not covered by the shared version: ${uncovered.join(", ")}.`
1743
- );
1744
- throw new NFError(
1745
- `External ${externalName} could not be shared without tearing entrypoints.`
1746
- );
1747
- }
1748
- scopeTornRemotes(externalName, external, tears);
1749
- }
1750
- function findTears(external) {
1751
- const shared = external.versions.find((v) => v.action === "share");
1752
- if (!shared) return [];
1753
- const basis = versionEntries(shared);
1754
- const tears = [];
1755
- for (const version of external.versions) {
1756
- if (version.action === "scope") continue;
1757
- if (version === shared) continue;
1758
- version.remotes.forEach((remote) => {
1759
- if (remote.servedBy) return;
1760
- const uncovered = uncoveredEntrypoints(remote, basis);
1761
- if (uncovered.length > 0) tears.push({ version, remote, uncovered });
1762
- });
1763
- }
1764
- return tears;
1765
- }
1766
- function scopeTornRemotes(externalName, external, tears) {
1767
- const torn = new Set(tears.map((t) => t.remote));
1768
- const demotedByTag = /* @__PURE__ */ new Map();
1769
- for (const { version, remote, uncovered } of tears) {
1770
- const group = demotedByTag.get(version.tag);
1771
- if (group) group.push(remote);
1772
- else demotedByTag.set(version.tag, [remote]);
1773
- config.log.debug(
1774
- 3,
1775
- `[${externalName}@${version.tag}][${remote.name}] Scoped: entrypoints not covered by the shared version: ${uncovered.join(", ")}.`
1776
- );
1777
- }
1778
- for (const version of external.versions) {
1779
- if (version.remotes.some((r) => torn.has(r))) {
1780
- version.remotes = version.remotes.filter((r) => !torn.has(r));
1781
- }
1782
- }
1783
- external.versions = external.versions.filter((v) => v.remotes.length > 0);
1784
- for (const [tag, remotes] of demotedByTag) {
1785
- const scoped = external.versions.find((v) => v.tag === tag && v.action === "scope");
1786
- if (scoped) scoped.remotes.push(...remotes);
1787
- else external.versions.push({ tag, host: false, action: "scope", remotes });
1788
- }
1789
- }
1790
1788
  }
1791
1789
 
1792
1790
  // src/lib/core/2.app/steps/determine-shared-externals.ts
1793
1791
  function createDetermineSharedExternals(config, ports) {
1794
1792
  const applyWinner = createApplyWinner(config);
1795
1793
  return () => {
1796
- const memo = /* @__PURE__ */ new Map();
1797
- const isCompatible = (tag, requiredVersion) => {
1798
- const key = `${tag}|${requiredVersion}`;
1799
- let hit = memo.get(key);
1800
- if (hit === void 0) {
1801
- hit = ports.versionCheck.isCompatible(tag, requiredVersion);
1802
- memo.set(key, hit);
1803
- }
1804
- return hit;
1805
- };
1806
- const touched = /* @__PURE__ */ new Map();
1794
+ const acceptsTag = createAcceptsTag(
1795
+ ports.versionCheck.isCompatible,
1796
+ ports.versionCheck.compare
1797
+ );
1807
1798
  for (const shareScope of ports.sharedExternalsRepo.getScopes()) {
1799
+ if (ports.sharedExternalsRepo.scopeType(shareScope) === "strict") continue;
1808
1800
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
1809
1801
  try {
1810
- const elected = /* @__PURE__ */ new Set();
1811
1802
  Object.entries(sharedExternals).filter(([_, e]) => e.dirty).forEach(([name, external]) => {
1812
1803
  ports.sharedExternalsRepo.addOrUpdate(
1813
1804
  name,
1814
- setVersionActions(name, external, isCompatible),
1805
+ setVersionActions(name, external, acceptsTag),
1815
1806
  shareScope
1816
1807
  );
1817
- elected.add(name);
1818
1808
  });
1819
- if (elected.size > 0) touched.set(shareScope, elected);
1820
1809
  } catch (error) {
1821
1810
  config.log.error(
1822
1811
  3,
@@ -1834,24 +1823,22 @@ function createDetermineSharedExternals(config, ports) {
1834
1823
  );
1835
1824
  }
1836
1825
  }
1837
- return Promise.resolve(touched);
1826
+ return Promise.resolve();
1838
1827
  };
1839
1828
  function uncoveredTears(external, winner, accepts) {
1840
- const basis = versionEntries(winner);
1829
+ const covered = /* @__PURE__ */ new Set();
1830
+ forEachVersionEntry(winner, void 0, (entrypoint) => covered.add(entrypoint));
1841
1831
  return external.versions.reduce((sum, v) => {
1842
1832
  if (v === winner) return sum;
1843
1833
  if (!accepts(v, winner.tag)) return sum;
1844
- return v.remotes.reduce(
1845
- (n, r) => r.servedBy ? n : n + countUncoveredEntrypoints(r, basis),
1846
- sum
1847
- );
1834
+ return v.remotes.reduce((n, r) => n + countUncoveredEntrypoints(r, covered), sum);
1848
1835
  }, 0);
1849
1836
  }
1850
- function setVersionActions(externalName, external, isCompatible) {
1837
+ function setVersionActions(externalName, external, acceptsTag) {
1838
+ const acceptance = versionAcceptance(external, acceptsTag);
1851
1839
  if (external.versions.length === 1) {
1852
- return applyWinner(externalName, external, external.versions[0], isCompatible);
1840
+ return applyWinner(externalName, external, external.versions[0], acceptsTag, acceptance);
1853
1841
  }
1854
- const acceptance = versionAcceptance(external, isCompatible);
1855
1842
  const { accepts } = acceptance;
1856
1843
  let sharedVersion = external.versions.find((v) => v.host);
1857
1844
  if (!sharedVersion && config.profile.latestSharedExternal) {
@@ -1876,14 +1863,12 @@ function createDetermineSharedExternals(config, ports) {
1876
1863
  return [v, [...groups.values()]];
1877
1864
  })
1878
1865
  );
1879
- const costOf = (version, tag) => selfServing.get(version).reduce((n, g) => isCompatible(tag, g.requiredVersion) ? n : n + g.copies, 0);
1866
+ const costOf = (version, tag) => selfServing.get(version).reduce(
1867
+ (n, g) => acceptsTag(tag, version.tag, g.requiredVersion) ? n : n + g.copies,
1868
+ 0
1869
+ );
1880
1870
  external.versions.forEach((vA) => {
1881
- const extraDownloads = external.versions.reduce(
1882
- // A copy of the winner is never redirected, so it never self-serves however its own range
1883
- // reads — see `applyWinner`, which does not split the winner either.
1884
- (sum, vB) => vB === vA ? sum : sum + costOf(vB, vA.tag),
1885
- 0
1886
- );
1871
+ const extraDownloads = external.versions.reduce((sum, vB) => sum + costOf(vB, vA.tag), 0);
1887
1872
  if (extraDownloads < leastExtraDownloads) {
1888
1873
  leastExtraDownloads = extraDownloads;
1889
1874
  leastTears = uncoveredTears(external, vA, accepts);
@@ -1901,582 +1886,582 @@ function createDetermineSharedExternals(config, ports) {
1901
1886
  if (!sharedVersion) {
1902
1887
  throw new NFError(`[${externalName}] Could not determine shared version!`);
1903
1888
  }
1904
- return applyWinner(externalName, external, sharedVersion, isCompatible, acceptance);
1889
+ return applyWinner(externalName, external, sharedVersion, acceptsTag, acceptance);
1905
1890
  }
1906
1891
  }
1907
1892
 
1908
- // src/lib/core/2.app/steps/pooling/pool-views.ts
1909
- function liveBuilds(members, islanded) {
1910
- return walkBuilds(members, islanded, true);
1911
- }
1912
- function committedView(members) {
1913
- const global = /* @__PURE__ */ new Map();
1914
- forEachGlobalClaim(members, void 0, (specifier, tag, meta) => {
1915
- if (!global.has(specifier))
1916
- global.set(specifier, { tag, remote: meta.name, file: meta.entries[specifier] });
1917
- });
1918
- return { builds: walkBuilds(members, void 0, false), global };
1893
+ // src/lib/core/1.domain/externals/specifier.ts
1894
+ function owningPackage(name) {
1895
+ const depth = name.startsWith("@") ? 2 : 1;
1896
+ let cut = -1;
1897
+ for (let seen = 0; seen < depth; seen++) {
1898
+ cut = name.indexOf("/", cut + 1);
1899
+ if (cut === -1) return void 0;
1900
+ }
1901
+ return name.slice(0, cut);
1919
1902
  }
1920
- function walkBuilds(members, islanded, skipScoped) {
1921
- const builds = /* @__PURE__ */ new Map();
1922
- for (const member of members) {
1923
- const versions = member.external.versions;
1924
- for (let v = 0; v < versions.length; v++) {
1925
- const version = versions[v];
1926
- if (skipScoped && version.action === "scope") continue;
1927
- const remotes = version.remotes;
1928
- for (let r = 0; r < remotes.length; r++) {
1929
- const meta = remotes[r];
1930
- if (islanded?.has(meta.name)) continue;
1931
- let own = builds.get(meta.name);
1932
- if (!own) {
1933
- builds.set(
1934
- meta.name,
1935
- own = { coverage: /* @__PURE__ */ new Map(), tags: /* @__PURE__ */ new Map(), instance: /* @__PURE__ */ new Map() }
1936
- );
1937
- }
1938
- if (!own.instance.has(member.name)) own.instance.set(member.name, version.tag);
1939
- for (const specifier in meta.entries) {
1940
- own.coverage.set(specifier, meta.entries[specifier]);
1941
- if (!own.tags.has(specifier)) own.tags.set(specifier, version.tag);
1942
- }
1943
- }
1944
- }
1903
+ var packageOf = (name) => owningPackage(name) ?? name;
1904
+ var SpecifierTags = class extends Map {
1905
+ packages = /* @__PURE__ */ new Map();
1906
+ constructor(entries = []) {
1907
+ super();
1908
+ for (const [specifier, tag] of entries) this.set(specifier, tag);
1909
+ }
1910
+ set(specifier, tag) {
1911
+ super.set(specifier, tag);
1912
+ const pkg = owningPackage(specifier);
1913
+ if (pkg === void 0) this.packages.set(specifier, tag);
1914
+ else if (!this.packages.has(pkg)) this.packages.set(pkg, tag);
1915
+ return this;
1916
+ }
1917
+ clear() {
1918
+ super.clear();
1919
+ this.packages.clear();
1920
+ }
1921
+ pinnedTag(specifier) {
1922
+ return this.get(specifier) ?? this.packages.get(owningPackage(specifier) ?? specifier);
1945
1923
  }
1946
- return builds;
1924
+ };
1925
+ function groupByPackage(items, nameOf) {
1926
+ const packages = /* @__PURE__ */ new Map();
1927
+ for (const item of items) {
1928
+ const pkg = packageOf(nameOf(item));
1929
+ const group = packages.get(pkg);
1930
+ if (group) group.push(item);
1931
+ else packages.set(pkg, [item]);
1932
+ }
1933
+ return packages;
1947
1934
  }
1948
- function ownCopies(members, only) {
1949
- const own = /* @__PURE__ */ new Map();
1935
+
1936
+ // src/lib/core/1.domain/pooling/builds.ts
1937
+ function copiesByRemote(members) {
1938
+ const shipped = /* @__PURE__ */ new Map();
1950
1939
  for (const member of members) {
1951
- for (const version of member.external.versions) {
1940
+ const seen = /* @__PURE__ */ new Set();
1941
+ for (const version of member.external.versions)
1952
1942
  for (const meta of version.remotes) {
1953
- if (only && !only.has(meta.name)) continue;
1954
- let copies = own.get(meta.name);
1955
- if (!copies) own.set(meta.name, copies = []);
1956
- if (!copies.some((c) => c.member === member.name))
1957
- copies.push({ member: member.name, tag: version.tag, entries: meta.entries });
1943
+ if (seen.has(meta.name)) continue;
1944
+ seen.add(meta.name);
1945
+ let copies = shipped.get(meta.name);
1946
+ if (!copies) shipped.set(meta.name, copies = []);
1947
+ copies.push({
1948
+ member: member.name,
1949
+ tag: version.tag,
1950
+ requiredVersion: meta.requiredVersion,
1951
+ strict: meta.strictVersion,
1952
+ host: version.host && meta === version.remotes[0],
1953
+ specifiers: Object.keys(meta.entries)
1954
+ });
1958
1955
  }
1959
- }
1960
1956
  }
1961
- return own;
1957
+ return shipped;
1962
1958
  }
1963
- function consumedMembers(members) {
1964
- const consumed = /* @__PURE__ */ new Map();
1965
- for (const member of members) {
1966
- const versions = member.external.versions;
1967
- for (let v = 0; v < versions.length; v++) {
1968
- const remotes = versions[v].remotes;
1969
- for (let r = 0; r < remotes.length; r++) {
1970
- const name = remotes[r].name;
1971
- const list = consumed.get(name);
1972
- if (!list) consumed.set(name, [member.name]);
1973
- else if (list[list.length - 1] !== member.name) list.push(member.name);
1974
- }
1975
- }
1976
- }
1977
- return consumed;
1959
+ var specifierCount = (copies) => copies.reduce((n, copy) => n + copy.specifiers.length, 0);
1960
+ function buildOf(owner, copies) {
1961
+ const tags = new SpecifierTags();
1962
+ const tagByMember = /* @__PURE__ */ new Map();
1963
+ for (const copy of copies) {
1964
+ for (const specifier of copy.specifiers)
1965
+ if (!tags.has(specifier)) tags.set(specifier, copy.tag);
1966
+ tagByMember.set(copy.member, copy.tag);
1967
+ }
1968
+ return { owner, tags, tagByMember, host: copies.some((c) => c.host) };
1978
1969
  }
1979
- function consumedSpecifiers(members) {
1980
- const consumed = /* @__PURE__ */ new Map();
1981
- for (const member of members) {
1982
- const versions = member.external.versions;
1983
- for (let v = 0; v < versions.length; v++) {
1984
- const remotes = versions[v].remotes;
1985
- for (let r = 0; r < remotes.length; r++) {
1986
- const meta = remotes[r];
1987
- let own = consumed.get(meta.name);
1988
- if (!own) consumed.set(meta.name, own = /* @__PURE__ */ new Set());
1989
- for (const specifier in meta.entries) own.add(specifier);
1990
- }
1970
+
1971
+ // src/lib/core/1.domain/pooling/rules.ts
1972
+ function agrees(copies, tags) {
1973
+ for (const copy of copies)
1974
+ for (const s of copy.specifiers) {
1975
+ const tag = tags.pinnedTag(s);
1976
+ if (tag !== void 0 && tag !== copy.tag) return false;
1991
1977
  }
1992
- }
1993
- return consumed;
1978
+ return true;
1994
1979
  }
1995
- function coversWholePool(members, build, islanded) {
1996
- const served = /* @__PURE__ */ new Set();
1997
- const wanted = /* @__PURE__ */ new Set();
1998
- for (const member of members) {
1999
- for (const version of member.external.versions) {
2000
- for (const meta of version.remotes) {
2001
- if (islanded.has(meta.name)) continue;
2002
- const into = meta.name === build ? served : wanted;
2003
- for (const specifier in meta.entries) into.add(specifier);
2004
- }
1980
+ function serves(copies, tags, acceptsTag) {
1981
+ for (const copy of copies)
1982
+ for (const s of copy.specifiers) {
1983
+ const tag = tags.get(s);
1984
+ if (tag === void 0 || !acceptsTag(tag, copy.tag, copy.requiredVersion)) return false;
2005
1985
  }
2006
- }
2007
- for (const specifier of wanted) if (!served.has(specifier)) return false;
2008
1986
  return true;
2009
1987
  }
2010
- function servingBuilds(members, islanded) {
2011
- const serving = /* @__PURE__ */ new Map();
2012
- for (const member of members) {
2013
- const shared = member.external.versions.find((v) => v.action === "share");
2014
- const basis = shared?.remotes.find((r) => !islanded.has(r.name));
2015
- if (basis) serving.set(member.name, basis.name);
2016
- }
2017
- return serving;
1988
+ function unwitnessedGap(copies, tags, builds) {
1989
+ const specifiers = copies.flatMap((c) => c.specifiers);
1990
+ let gap = 0;
1991
+ for (const build of builds) {
1992
+ const miss = specifiers.findIndex((s) => build.tags.pinnedTag(s) !== tags.get(s));
1993
+ if (miss === -1) return void 0;
1994
+ gap = Math.max(gap, miss);
1995
+ }
1996
+ return specifiers[gap];
2018
1997
  }
2019
- function basisPerMember(members, islanded, dedupsElsewhere) {
2020
- const basis = /* @__PURE__ */ new Map();
2021
- for (const member of members) {
2022
- const winner = member.external.versions.find((v) => v.action === "share");
2023
- if (!winner) continue;
2024
- const own = winner.remotes.find(
2025
- (r) => !islanded?.has(r.name) && !dedupsElsewhere?.(r.name)
2026
- );
2027
- if (own) basis.set(member.name, own.name);
2028
- }
2029
- return basis;
1998
+
1999
+ // src/lib/core/1.domain/pooling/election.ts
2000
+ function elect(input) {
2001
+ const shipped = copiesByRemote(input.members);
2002
+ const builds = /* @__PURE__ */ new Map();
2003
+ for (const [owner, copies] of shipped) builds.set(owner, buildOf(owner, copies));
2004
+ const pool = {
2005
+ ...input,
2006
+ shipped,
2007
+ builds,
2008
+ packages: new Map([...shipped].map(([owner, copies]) => [owner, packageCopies(owner, copies)])),
2009
+ remotes: [...shipped.keys()],
2010
+ servesAt: (remote, tags) => serves(shipped.get(remote), tags, input.acceptsTag),
2011
+ agreesAt: (remote, tags) => agrees(shipped.get(remote), tags)
2012
+ };
2013
+ const hostRemote = pool.remotes.find((r) => builds.get(r).host);
2014
+ const state = roundOne(pool, hostRemote);
2015
+ formSubpools(pool, state);
2016
+ addExtensions(pool, state);
2017
+ promote(pool, state);
2018
+ while (settle(pool, state)) formSubpools(pool, state);
2019
+ const firstSeen = new Map(pool.remotes.map((r, i) => [r, i]));
2020
+ const self = state.pending.sort((a, b) => firstSeen.get(a) - firstSeen.get(b));
2021
+ return {
2022
+ winner: state.winner,
2023
+ winnerIsHost: hostRemote !== void 0,
2024
+ globalTags: state.globalTags,
2025
+ ...placeRemotes(state.globalRemotes, state.subpools, self, (r) => missOf(pool, state, r))
2026
+ };
2030
2027
  }
2031
- function hostRemotes(members) {
2032
- const hosts = /* @__PURE__ */ new Set();
2033
- for (const member of members) {
2034
- for (const version of member.external.versions) {
2035
- if (version.host && version.remotes.length > 0) hosts.add(version.remotes[0].name);
2028
+ function newer(pool, a, b) {
2029
+ for (const { name } of pool.members) {
2030
+ const tagA = a.tagByMember.get(name);
2031
+ const tagB = b.tagByMember.get(name);
2032
+ if (tagA === void 0 || tagB === void 0) continue;
2033
+ const d = pool.compare(tagA, tagB);
2034
+ if (d !== 0) return d;
2035
+ }
2036
+ return 0;
2037
+ }
2038
+ function rank(pool, candidates, keys) {
2039
+ const scored = [...candidates].sort((a, b) => compareStrings(a.build.owner, b.build.owner)).map((c) => ({ c, k: keys.map((key) => key(c)) }));
2040
+ scored.sort((a, b) => {
2041
+ for (let i = 0; i < keys.length; i++) {
2042
+ const d = b.k[i] - a.k[i];
2043
+ if (d !== 0) return d;
2036
2044
  }
2037
- }
2038
- return hosts;
2045
+ return newer(pool, b.c.build, a.c.build) || compareStrings(a.c.build.owner, b.c.build.owner);
2046
+ });
2047
+ return scored[0]?.c;
2039
2048
  }
2040
- function arrivalOrder(members) {
2041
- const arrival = /* @__PURE__ */ new Map();
2042
- for (const member of members) {
2043
- for (const version of member.external.versions) {
2044
- for (const meta of version.remotes) {
2045
- if (!arrival.has(meta.name)) arrival.set(meta.name, arrival.size);
2046
- }
2049
+ function roundOne(pool, hostRemote) {
2050
+ const { remotes } = pool;
2051
+ const scored = /* @__PURE__ */ new Map();
2052
+ const round1 = (hostRemote ? [hostRemote] : remotes).map((owner) => {
2053
+ const build = pool.builds.get(owner);
2054
+ const key = JSON.stringify([[...build.tags], [...build.tagByMember]]);
2055
+ let score = scored.get(key);
2056
+ if (!score) {
2057
+ const served = new Set(remotes.filter((r) => pool.servesAt(r, build.tags)));
2058
+ const agreeing = remotes.filter((r) => !served.has(r) && pool.agreesAt(r, build.tags)).length;
2059
+ scored.set(key, score = { served: [...served], agreeing });
2047
2060
  }
2061
+ return { build, ...score };
2062
+ });
2063
+ const newerThan = (c) => round1.filter((o) => newer(pool, c.build, o.build) > 0).length;
2064
+ const round1Winner = rank(pool, round1, [
2065
+ ...pool.latestFirst ? [newerThan] : [],
2066
+ (c) => c.served.length,
2067
+ (c) => c.agreeing,
2068
+ (c) => c.build.owner === pool.previousWinner ? 1 : 0
2069
+ ]);
2070
+ const winner = round1Winner.build.owner;
2071
+ const globalTags = new SpecifierTags(round1Winner.build.tags);
2072
+ const globalRemotes = /* @__PURE__ */ new Set([winner, ...round1Winner.served]);
2073
+ const pending = remotes.filter((r) => !globalRemotes.has(r));
2074
+ return { winner, globalTags, globalRemotes, extensions: /* @__PURE__ */ new Set(), subpools: [], pending };
2075
+ }
2076
+ function formSubpools(pool, state) {
2077
+ const servable = new Map(
2078
+ state.pending.map((owner) => {
2079
+ const build = pool.builds.get(owner);
2080
+ const served = state.pending.filter((r) => pool.servesAt(r, build.tags));
2081
+ return [owner, { build, served }];
2082
+ })
2083
+ );
2084
+ for (; ; ) {
2085
+ const stillPending = new Set(state.pending);
2086
+ const candidates = state.pending.map((owner) => {
2087
+ const { build, served } = servable.get(owner);
2088
+ return { build, served: served.filter((r) => stillPending.has(r)) };
2089
+ }).filter((c) => c.served.includes(c.build.owner) && c.served.length >= 2);
2090
+ const best = rank(pool, candidates, [(c) => c.served.length]);
2091
+ if (!best) break;
2092
+ state.subpools.push({ owner: best.build.owner, remotes: best.served });
2093
+ state.pending = state.pending.filter((r) => !best.served.includes(r));
2048
2094
  }
2049
- return arrival;
2050
2095
  }
2051
- function sharedTagPerSpecifier(members, islanded) {
2052
- const shared = /* @__PURE__ */ new Map();
2053
- forEachGlobalClaim(members, islanded, (specifier, tag) => {
2054
- if (!shared.has(specifier)) shared.set(specifier, tag);
2096
+ function promote(pool, state) {
2097
+ const { globalTags, globalRemotes } = state;
2098
+ const takesGlobal = (remote) => pool.servesAt(remote, globalTags) && unwitnessedGap(pool.shipped.get(remote), globalTags, pool.builds.values()) === void 0;
2099
+ for (const remote of state.pending) if (takesGlobal(remote)) globalRemotes.add(remote);
2100
+ state.pending = state.pending.filter((r) => !globalRemotes.has(r));
2101
+ for (const subpool of state.subpools) {
2102
+ const moved = subpool.remotes.filter((r) => r !== subpool.owner && takesGlobal(r));
2103
+ for (const remote of moved) globalRemotes.add(remote);
2104
+ subpool.remotes = subpool.remotes.filter((r) => !moved.includes(r));
2105
+ }
2106
+ state.subpools = state.subpools.filter((subpool) => {
2107
+ if (subpool.remotes.length >= 2) return true;
2108
+ if (takesGlobal(subpool.owner)) globalRemotes.add(subpool.owner);
2109
+ else state.pending.push(subpool.owner);
2110
+ return false;
2055
2111
  });
2056
- return shared;
2057
2112
  }
2058
- function forEachGlobalClaim(members, islanded, visit) {
2059
- const accepts = islanded ? (meta) => !islanded.has(meta.name) : void 0;
2060
- const claim = (version) => forEachVersionEntry(version, accepts, (specifier, meta) => visit(specifier, version.tag, meta));
2061
- for (const member of members) {
2062
- const versions = member.external.versions;
2063
- const winner = versions.find((v) => v.action === "share");
2064
- if (winner) claim(winner);
2065
- for (let v = 0; v < versions.length; v++) {
2066
- if (versions[v].action === "skip") claim(versions[v]);
2067
- }
2113
+ function addExtensions(pool, state) {
2114
+ const { globalTags, globalRemotes } = state;
2115
+ const runsOn = (remote) => state.subpools.find((p) => p.remotes.includes(remote))?.owner ?? remote;
2116
+ const agreeing = pool.remotes.filter((r) => !globalRemotes.has(r) && pool.agreesAt(r, globalTags));
2117
+ const runsGlobally = (remote) => pool.agreesAt(runsOn(remote), globalTags);
2118
+ const offered = offers(pool, globalTags, agreeing.filter(runsGlobally));
2119
+ const fill = pool.builds.get(state.winner).host ? agreeing.filter((r) => !runsGlobally(r)) : [];
2120
+ for (const [pkg, offer] of offers(pool, globalTags, fill))
2121
+ if (!offered.has(pkg)) offered.set(pkg, offer);
2122
+ for (const [pkg, offer] of offered) {
2123
+ if (!offer) continue;
2124
+ state.extensions.add(pkg);
2125
+ for (const s of offer.specifiers) globalTags.set(s, offer.tag);
2068
2126
  }
2069
2127
  }
2070
-
2071
- // src/lib/core/2.app/steps/pooling/anchoring.ts
2072
- function acceptanceTable(members, isCompatible) {
2073
- const table = /* @__PURE__ */ new Map();
2074
- for (const member of members) {
2075
- const tags = [];
2076
- for (const version of member.external.versions) tags.push(version.tag);
2077
- for (const version of member.external.versions) {
2078
- for (const meta of version.remotes) {
2079
- let byMember = table.get(meta.name);
2080
- if (!byMember) table.set(meta.name, byMember = /* @__PURE__ */ new Map());
2081
- if (byMember.has(member.name)) continue;
2082
- const accepted = /* @__PURE__ */ new Set();
2083
- for (let t = 0; t < tags.length; t++) {
2084
- if (isCompatible(tags[t], meta.requiredVersion)) accepted.add(tags[t]);
2085
- }
2086
- byMember.set(member.name, accepted);
2087
- }
2128
+ function packageCopies(owner, copies) {
2129
+ const packages = /* @__PURE__ */ new Map();
2130
+ for (const copy of copies)
2131
+ for (const s of copy.specifiers) {
2132
+ const pkg = packageOf(s);
2133
+ const seen = packages.get(pkg);
2134
+ if (seen === void 0) packages.set(pkg, { owner, tag: copy.tag, specifiers: /* @__PURE__ */ new Set([s]) });
2135
+ else if (seen && seen.tag !== copy.tag) packages.set(pkg, null);
2136
+ else seen?.specifiers.add(s);
2088
2137
  }
2089
- }
2090
- return table;
2138
+ return packages;
2091
2139
  }
2092
- function covers(coverage2, consumed) {
2093
- for (const specifier of consumed) if (!coverage2.has(specifier)) return false;
2094
- return true;
2140
+ function offers(pool, globalTags, remotes) {
2141
+ const candidates = /* @__PURE__ */ new Map();
2142
+ for (const owner of remotes)
2143
+ for (const [pkg, copy] of pool.packages.get(owner))
2144
+ if (globalTags.pinnedTag(pkg) === void 0)
2145
+ candidates.set(pkg, [...candidates.get(pkg) ?? [], copy]);
2146
+ const offered = /* @__PURE__ */ new Map();
2147
+ for (const [pkg, copies] of candidates)
2148
+ offered.set(pkg, oneTag(copies) ? coveringCopy(copies) : null);
2149
+ return offered;
2095
2150
  }
2096
- function acceptsAll(acceptance, build, consumer, consumed) {
2097
- const byMember = acceptance.get(consumer);
2098
- if (!byMember) return false;
2099
- for (let i = 0; i < consumed.length; i++) {
2100
- const offered = build.get(consumed[i]);
2101
- if (offered === void 0) return false;
2102
- if (!byMember.get(consumed[i])?.has(offered)) return false;
2103
- }
2104
- return true;
2151
+ var oneTag = (copies) => copies.every((c) => c !== null && c.tag === copies[0].tag);
2152
+ function coveringCopy(candidates) {
2153
+ const scored = candidates.map((c) => ({
2154
+ c,
2155
+ covers: candidates.filter((o) => contains(c.specifiers, o.specifiers)).length
2156
+ }));
2157
+ scored.sort(
2158
+ (a, b) => b.covers - a.covers || b.c.specifiers.size - a.c.specifiers.size || compareStrings(a.c.owner, b.c.owner)
2159
+ );
2160
+ return scored[0].c;
2105
2161
  }
2106
- function isWitnessed(specifiers, shared, builds) {
2107
- const wanted = [...specifiers];
2108
- for (const specifier of wanted) if (!shared.has(specifier)) return false;
2109
- for (const [, build] of builds) {
2110
- let matches = true;
2111
- for (let i = 0; i < wanted.length; i++) {
2112
- if (build.tags.get(wanted[i]) !== shared.get(wanted[i])) {
2113
- matches = false;
2114
- break;
2162
+ var contains = (a, b) => [...b].every((s) => a.has(s));
2163
+ function settle(pool, state) {
2164
+ const { globalTags, globalRemotes, extensions } = state;
2165
+ let demoted = false;
2166
+ for (; ; ) {
2167
+ const dropped = new Set([...extensions].filter((pkg) => !publishedWhole(pool, state, pkg)));
2168
+ if (dropped.size === 0) return demoted;
2169
+ for (const pkg of dropped) extensions.delete(pkg);
2170
+ const kept = [...globalTags].filter(([s]) => !dropped.has(packageOf(s)));
2171
+ globalTags.clear();
2172
+ for (const [s, tag] of kept) globalTags.set(s, tag);
2173
+ for (const remote of globalRemotes)
2174
+ if (remote !== state.winner && !pool.servesAt(remote, globalTags)) {
2175
+ globalRemotes.delete(remote);
2176
+ const subpool = state.subpools.find(
2177
+ (p) => pool.servesAt(remote, pool.builds.get(p.owner).tags)
2178
+ );
2179
+ if (subpool) subpool.remotes.push(remote);
2180
+ else state.pending.push(remote);
2181
+ demoted = true;
2115
2182
  }
2116
- }
2117
- if (matches) return true;
2118
2183
  }
2119
- return false;
2120
2184
  }
2121
- function explainSelfServe(remote, wants, specifiers, pool) {
2122
- let closest;
2123
- let fewest = Number.MAX_SAFE_INTEGER;
2124
- let gap = wants[0] ?? "";
2125
- for (const [build, view] of pool.builds) {
2126
- if (build === remote) continue;
2127
- let missing;
2128
- let count = 0;
2129
- for (const specifier of specifiers) {
2130
- if (view.coverage.has(specifier)) continue;
2131
- count++;
2132
- missing ??= specifier;
2185
+ function publishedWhole(pool, state, pkg) {
2186
+ const published = new Set([...state.globalTags.keys()].filter((s) => packageOf(s) === pkg));
2187
+ const tag = state.globalTags.pinnedTag(pkg);
2188
+ return [...state.globalRemotes].some((remote) => {
2189
+ const copy = pool.packages.get(remote).get(pkg);
2190
+ return !!copy && copy.tag === tag && copy.specifiers.size === published.size && contains(copy.specifiers, published);
2191
+ });
2192
+ }
2193
+ function missOf(pool, state, remote) {
2194
+ const { globalTags } = state;
2195
+ const copies = pool.shipped.get(remote);
2196
+ let rejected;
2197
+ for (const copy of copies)
2198
+ for (const s of copy.specifiers) {
2199
+ const tag = globalTags.get(s);
2200
+ if (tag === void 0 || pool.acceptsTag(tag, copy.tag, copy.requiredVersion)) continue;
2201
+ if (copy.strict) return { cause: "incompatible", member: copy.member, tag, strict: true };
2202
+ rejected ??= { cause: "incompatible", member: copy.member, tag, strict: false };
2133
2203
  }
2134
- if (count >= fewest) continue;
2135
- fewest = count;
2136
- closest = build;
2137
- gap = missing ?? rejectedMember(view.instance, remote, wants, pool.acceptance) ?? gap;
2138
- }
2139
- return { gap, closest };
2204
+ if (rejected) return rejected;
2205
+ for (const copy of copies)
2206
+ for (const s of copy.specifiers) if (!globalTags.has(s)) return { cause: "uncovered", gap: s };
2207
+ const gap = unwitnessedGap(copies, globalTags, pool.builds.values());
2208
+ if (gap) return { cause: "uncovered", gap, unshipped: true };
2209
+ if (state.subpools.some((p) => p.owner === remote)) return void 0;
2210
+ throw new Error(`'${remote}' missed the global map with nothing it rejects or lacks.`);
2140
2211
  }
2141
- function rejectedMember(offered, consumer, wants, acceptance) {
2142
- const accepts = acceptance.get(consumer);
2143
- if (!accepts) return void 0;
2144
- for (const member of wants) {
2145
- const tag = offered.get(member);
2146
- if (tag === void 0) return member;
2147
- if (!accepts.get(member)?.has(tag)) return `${member}@${tag}`;
2148
- }
2149
- return void 0;
2212
+ function previousWinner(members) {
2213
+ const winners = new Set(members.flatMap((m) => m.external.poolWinner ?? []));
2214
+ const [winner] = winners;
2215
+ if (winners.size !== 1) return void 0;
2216
+ const ships = members.some(
2217
+ (m) => m.external.versions.some((v) => v.remotes.some((r) => r.name === winner))
2218
+ );
2219
+ return ships ? winner : void 0;
2150
2220
  }
2151
- function assignAnchors(input) {
2152
- const { builds, acceptance, consumedMembers: consumedMembers2, hosts, arrival } = input;
2153
- const assignment = /* @__PURE__ */ new Map();
2154
- const byPriority = (a, b) => (arrival.get(a) ?? Number.MAX_SAFE_INTEGER) - (arrival.get(b) ?? Number.MAX_SAFE_INTEGER) || a.localeCompare(b);
2155
- const candidates = [...builds.keys()].sort(byPriority);
2156
- const pending = new Set([...consumedMembers2.keys()].sort(byPriority));
2157
- const anchored = /* @__PURE__ */ new Set();
2158
- const canServe = (candidate, consumer) => {
2159
- const build = builds.get(candidate);
2160
- if (!covers(build.coverage, input.consumedSpecifiers.get(consumer) ?? [])) return false;
2161
- return acceptsAll(acceptance, build.instance, consumer, consumedMembers2.get(consumer) ?? []);
2162
- };
2163
- const electAnchor = (anchor, served) => {
2164
- pending.delete(anchor);
2165
- assignment.set(anchor, void 0);
2166
- for (const consumer of served) {
2167
- pending.delete(consumer);
2168
- anchored.add(consumer);
2169
- assignment.set(consumer, anchor);
2170
- }
2171
- };
2172
- for (const host of [...hosts].sort(byPriority)) {
2173
- const served = [];
2174
- if (builds.has(host)) {
2175
- for (const consumer of pending) {
2176
- if (consumer !== host && canServe(host, consumer)) served.push(consumer);
2177
- }
2178
- }
2179
- electAnchor(host, served);
2180
- }
2181
- for (; ; ) {
2182
- let best;
2183
- let bestServed = [];
2184
- for (const candidate of candidates) {
2185
- if (anchored.has(candidate)) continue;
2186
- const served = [];
2187
- for (const consumer of pending) {
2188
- if (consumer !== candidate && canServe(candidate, consumer)) served.push(consumer);
2189
- }
2190
- if (served.length > bestServed.length) {
2191
- best = candidate;
2192
- bestServed = served;
2193
- }
2194
- }
2195
- if (best === void 0) break;
2196
- electAnchor(best, bestServed);
2197
- }
2198
- for (const consumer of pending) assignment.set(consumer, void 0);
2199
- return assignment;
2221
+ function placeRemotes(globalRemotes, subpools, self, missOf2) {
2222
+ const misses = /* @__PURE__ */ new Map();
2223
+ for (const remote of [...subpools.flatMap((p) => p.remotes), ...self])
2224
+ misses.set(remote, missOf2(remote));
2225
+ const placements = /* @__PURE__ */ new Map();
2226
+ for (const remote of globalRemotes) placements.set(remote, { kind: "global" });
2227
+ for (const { owner, remotes } of subpools)
2228
+ for (const remote of remotes) placements.set(remote, { kind: "subpool", owner });
2229
+ for (const remote of self)
2230
+ placements.set(remote, { kind: "self", cause: misses.get(remote).cause });
2231
+ return { placements, misses };
2200
2232
  }
2201
2233
 
2202
- // src/lib/core/2.app/steps/pooling/pool-graph.ts
2203
- function createDSU() {
2204
- const ids = /* @__PURE__ */ new Map();
2205
- const parent = [];
2206
- const size = [];
2207
- const intern = (key) => {
2208
- let id = ids.get(key);
2209
- if (id === void 0) {
2210
- id = parent.length;
2211
- ids.set(key, id);
2212
- parent.push(id);
2213
- size.push(1);
2234
+ // src/lib/core/1.domain/pooling/membership.ts
2235
+ function unionFind() {
2236
+ const parent = /* @__PURE__ */ new Map();
2237
+ const find = (node) => {
2238
+ for (let up = parent.get(node); up !== void 0; up = parent.get(node)) {
2239
+ const grand = parent.get(up);
2240
+ if (grand === void 0) return up;
2241
+ parent.set(node, grand);
2242
+ node = grand;
2214
2243
  }
2215
- return id;
2244
+ return node;
2216
2245
  };
2217
- const findRoot = (x) => {
2218
- while (parent[x] !== x) {
2219
- parent[x] = parent[parent[x]];
2220
- x = parent[x];
2221
- }
2222
- return x;
2223
- };
2224
- return {
2225
- union(a, b) {
2226
- let rootA = findRoot(intern(a));
2227
- let rootB = findRoot(intern(b));
2228
- if (rootA === rootB) return;
2229
- if (size[rootA] < size[rootB]) [rootA, rootB] = [rootB, rootA];
2230
- parent[rootB] = rootA;
2231
- size[rootA] = size[rootA] + size[rootB];
2232
- },
2233
- // Interns the key if unseen — an isolated key is its own component.
2234
- component(key) {
2235
- return findRoot(intern(key));
2236
- }
2246
+ const union = (a, b) => {
2247
+ const rootA = find(a);
2248
+ const rootB = find(b);
2249
+ if (rootA !== rootB) parent.set(rootB, rootA);
2237
2250
  };
2251
+ return { find, union };
2238
2252
  }
2239
2253
  var extNode = (name) => `ext\0${name}`;
2240
- var tagNode = (remote, tag) => `tag\0${remote}\0${tag}`;
2241
- function owningPackage(name) {
2242
- const depth = name.startsWith("@") ? 2 : 1;
2243
- let cut = -1;
2244
- for (let seen = 0; seen < depth; seen++) {
2245
- cut = name.indexOf("/", cut + 1);
2246
- if (cut === -1) return void 0;
2247
- }
2248
- return name.slice(0, cut);
2254
+ var labelNode = (label) => `label\0${label}`;
2255
+ var labelOf = (pool) => pool?.trim() || void 0;
2256
+ function labelsIn(sharedExternals) {
2257
+ const labelsOf = /* @__PURE__ */ new Map();
2258
+ for (const [name, external] of Object.entries(sharedExternals))
2259
+ labelsOf.set(
2260
+ name,
2261
+ external.versions.flatMap((v) => v.remotes.flatMap((r) => labelOf(r.pool) ?? []))
2262
+ );
2263
+ return labelsOf;
2249
2264
  }
2250
- function groupByMembership(candidates, log) {
2251
- const dsu = createDSU();
2252
- const tagged = /* @__PURE__ */ new Set();
2253
- for (const candidate of candidates) {
2254
- for (const edge of candidate.tags) {
2255
- dsu.union(extNode(candidate.name), tagNode(edge.remote, edge.tag));
2256
- tagged.add(candidate.name);
2257
- }
2258
- }
2259
- const declared = new Set(candidates.map((c) => c.name));
2260
- for (const candidate of candidates) {
2261
- const owner = owningPackage(candidate.name);
2262
- if (owner !== void 0 && declared.has(owner))
2263
- dsu.union(extNode(candidate.name), extNode(owner));
2265
+ function labelledComponents(labelsOf) {
2266
+ const { find, union } = unionFind();
2267
+ for (const [name, labels] of labelsOf)
2268
+ for (const label of labels) union(extNode(name), labelNode(label));
2269
+ for (const name of labelsOf.keys()) {
2270
+ const owner = owningPackage(name);
2271
+ if (owner !== void 0) union(extNode(name), extNode(owner));
2264
2272
  }
2265
2273
  const byComponent = /* @__PURE__ */ new Map();
2266
- for (const candidate of candidates) {
2267
- const root = dsu.component(extNode(candidate.name));
2268
- const members = byComponent.get(root) ?? byComponent.set(root, []).get(root);
2269
- members.push(candidate);
2270
- }
2271
- const pools = [];
2272
- for (const members of byComponent.values()) {
2273
- if (!members.some((m) => tagged.has(m.name))) continue;
2274
- members.sort((a, b) => a.name.localeCompare(b.name));
2275
- if (members.length < 2) {
2276
- const only = members[0];
2277
- if (tagged.has(only.name)) {
2278
- log?.warn(
2279
- 3,
2280
- `[${only.name}] declares a 'pool' tag but no other external joined its pool; likely a typo or a missing sibling.`
2281
- );
2282
- }
2283
- continue;
2274
+ for (const name of labelsOf.keys()) {
2275
+ const root = find(extNode(name));
2276
+ const names = byComponent.get(root) ?? byComponent.set(root, []).get(root);
2277
+ names.push(name);
2278
+ }
2279
+ return [...byComponent.values()].filter((names) => names.some((n) => labelsOf.get(n).length > 0));
2280
+ }
2281
+ function buildPools(sharedExternals) {
2282
+ const labelsOf = labelsIn(sharedExternals);
2283
+ const pools = /* @__PURE__ */ new Map();
2284
+ const labelledAlone = [];
2285
+ for (const names of labelledComponents(labelsOf)) {
2286
+ if (names.length < 2) labelledAlone.push(names[0]);
2287
+ else {
2288
+ const members = names.map((name) => ({ name, external: sharedExternals[name] }));
2289
+ pools.set(
2290
+ mostDeclaredLabel(members, labelsOf),
2291
+ members.sort((a, b) => compareStrings(a.name, b.name))
2292
+ );
2284
2293
  }
2285
- pools.push(members);
2286
- }
2287
- pools.sort((a, b) => a[0].name.localeCompare(b[0].name));
2288
- const named = /* @__PURE__ */ new Map();
2289
- for (const members of pools) {
2290
- const label = mostDeclaredTag(members) ?? members[0].name;
2291
- let name = label;
2292
- for (let n = 2; named.has(name); n++) name = `${label}~${n}`;
2293
- named.set(
2294
- name,
2295
- members.map((m) => m.value)
2296
- );
2297
2294
  }
2298
- return named;
2295
+ return { pools, labelledAlone };
2296
+ }
2297
+ function pooledPackagesWith(sharedExternals, copies) {
2298
+ const labelsOf = labelsIn(sharedExternals);
2299
+ for (const { name, pool } of copies) {
2300
+ const labels = labelsOf.get(name) ?? [];
2301
+ const label = labelOf(pool);
2302
+ labelsOf.set(name, label === void 0 ? labels : [...labels, label]);
2303
+ }
2304
+ return new Set(
2305
+ labelledComponents(labelsOf).filter((names) => names.length > 1).flatMap((names) => names.map(packageOf))
2306
+ );
2299
2307
  }
2300
- function mostDeclaredTag(members) {
2308
+ function mostDeclaredLabel(members, labelsOf) {
2301
2309
  const counts = /* @__PURE__ */ new Map();
2302
2310
  for (const member of members)
2303
- for (const edge of member.tags) counts.set(edge.tag, (counts.get(edge.tag) ?? 0) + 1);
2304
- let best;
2305
- for (const [tag, count] of counts) {
2306
- const top = best === void 0 ? 0 : counts.get(best);
2307
- if (count > top || count === top && tag.localeCompare(best) < 0) best = tag;
2308
- }
2311
+ for (const label of labelsOf.get(member.name)) counts.set(label, (counts.get(label) ?? 0) + 1);
2312
+ let best = "";
2313
+ let top = 0;
2314
+ for (const [label, count] of counts)
2315
+ if (count > top || count === top && compareStrings(label, best) < 0)
2316
+ [best, top] = [label, count];
2309
2317
  return best;
2310
2318
  }
2311
- function buildPools(sharedExternals, log) {
2312
- const candidates = Object.entries(sharedExternals).map(
2313
- ([name, external]) => ({
2314
- name,
2315
- tags: external.versions.flatMap(
2316
- (v) => v.remotes.flatMap((r) => {
2317
- const tag = r.pool?.trim();
2318
- return tag ? [{ remote: r.name, tag }] : [];
2319
- })
2320
- ),
2321
- value: { name, external }
2322
- })
2319
+
2320
+ // src/lib/core/1.domain/pooling/pool-state.ts
2321
+ function hasPoolResults(external) {
2322
+ if (external.poolName !== void 0 || external.poolWinner !== void 0) return true;
2323
+ return external.versions.some(
2324
+ (v) => v.remotes.some((r) => r.servedBy !== void 0 || r.poolCause !== void 0)
2323
2325
  );
2324
- return groupByMembership(candidates, log);
2326
+ }
2327
+ function scopeHasPoolState(scope) {
2328
+ for (const name in scope) {
2329
+ const external = scope[name];
2330
+ if (hasPoolResults(external)) return true;
2331
+ for (const version of external.versions)
2332
+ for (const remote of version.remotes) if (remote.pool?.trim()) return true;
2333
+ }
2334
+ return false;
2325
2335
  }
2326
2336
 
2327
- // src/lib/core/2.app/steps/pooling/pool.util.ts
2328
- function lazy(make) {
2329
- let value;
2330
- return () => value ??= make();
2337
+ // src/lib/core/1.domain/pooling/records.ts
2338
+ function memberRecord(member, pool, compare) {
2339
+ const { globalTags, placements } = pool;
2340
+ let sharedTag = globalTags.get(member.name);
2341
+ for (const version of member.external.versions)
2342
+ for (const meta of version.remotes)
2343
+ for (const s in meta.entries) sharedTag ??= globalTags.get(s);
2344
+ const claimsGlobally = (name) => placements.get(name).kind === "global";
2345
+ const placed = [];
2346
+ let winnerRow;
2347
+ const place = (tag, action, meta) => {
2348
+ const row = { tag, host: false, action, remotes: [meta] };
2349
+ placed.push(row);
2350
+ if (meta.name !== pool.winner) return;
2351
+ winnerRow = row;
2352
+ row.host = pool.winnerIsHost;
2353
+ };
2354
+ for (const version of member.external.versions)
2355
+ for (const stored of version.remotes) {
2356
+ const { servedBy: _servedBy, poolCause: _poolCause, ...rest } = stored;
2357
+ const meta = { ...rest, cached: false };
2358
+ const placement = placements.get(meta.name);
2359
+ if (placement.kind === "global")
2360
+ place(version.tag, version.tag === sharedTag ? "share" : "skip", meta);
2361
+ else if (placement.kind === "subpool")
2362
+ place(version.tag, "skip", { ...meta, servedBy: placement.owner });
2363
+ else place(version.tag, "scope", { ...meta, poolCause: placement.cause });
2364
+ }
2365
+ const rows = mergeRows(placed, winnerRow);
2366
+ for (const row of rows)
2367
+ row.remotes.sort((a, b) => Number(!claimsGlobally(a.name)) - Number(!claimsGlobally(b.name)));
2368
+ const order = { share: 0, skip: 1, scope: 2 };
2369
+ const newest = byTag(compare);
2370
+ const versions = rows.sort((a, b) => newest(a, b) || order[a.action] - order[b.action]);
2371
+ return { dirty: false, poolName: pool.poolName, versions };
2331
2372
  }
2332
- function remotesInPool(members) {
2333
- return [
2334
- ...new Set(members.flatMap((m) => m.external.versions.flatMap((v) => v.remotes.map((r) => r.name))))
2335
- ];
2373
+ function movedRecord(external, remote, move, compare) {
2374
+ return withoutStaleServingBuild(moveCopy(external, remote, move, compare));
2336
2375
  }
2337
- function poolableScopes(repo) {
2338
- return repo.getScopes().filter((scope) => repo.scopeType(scope) !== "strict" && repo.hasPoolState(scope));
2376
+ function withoutStaleServingBuild(record) {
2377
+ const { servingBuild, ...rest } = record;
2378
+ if (servingBuild === void 0) return record;
2379
+ const shares = record.versions.some(
2380
+ (v) => v.action === "share" && v.remotes.some((r) => r.name === servingBuild)
2381
+ );
2382
+ return shares ? record : rest;
2339
2383
  }
2340
- function syncPoolNames(sharedExternals, pools, repo, scope, skip = /* @__PURE__ */ new Set()) {
2341
- const named = /* @__PURE__ */ new Map();
2342
- for (const [name, members] of pools) for (const member of members) named.set(member.name, name);
2343
- for (const [name, external] of Object.entries(sharedExternals)) {
2344
- const pool = named.get(name);
2345
- if (pool !== void 0 && skip.has(pool)) continue;
2346
- if (external.poolName === pool) continue;
2347
- const { poolName: _stale, ...rest } = external;
2348
- repo.addOrUpdate(
2349
- name,
2350
- pool !== void 0 ? { ...rest, poolName: pool } : {
2351
- ...rest,
2352
- versions: external.versions.map((v) => ({
2353
- ...v,
2354
- remotes: v.remotes.map(({ poolCause: _cause, ...meta }) => meta)
2355
- }))
2356
- },
2357
- scope
2358
- );
2359
- }
2384
+ function moveCopy(external, remote, move, compare) {
2385
+ if ("fromMap" in move) return recordFromMap(external, remote);
2386
+ const moved = [];
2387
+ const versions = external.versions.map((v) => {
2388
+ const copy = v.remotes.find((r) => r.name === remote);
2389
+ if (!copy) return v;
2390
+ const { servedBy: _servedBy, ...rest } = copy;
2391
+ const meta = { ...rest, cached: true, poolCause: move.cause };
2392
+ if (v.action === "scope")
2393
+ return { ...v, remotes: v.remotes.map((r) => r === copy ? meta : r) };
2394
+ moved.push({ tag: v.tag, meta });
2395
+ return { ...v, remotes: v.remotes.filter((r) => r !== copy) };
2396
+ }).filter((v) => v.remotes.length > 0);
2397
+ for (const { tag, meta } of moved) {
2398
+ const scoped = rowAt(versions, tag, "scope");
2399
+ if (!scoped) versions.push({ tag, action: "scope", host: false, remotes: [meta] });
2400
+ else versions[versions.indexOf(scoped)] = { ...scoped, remotes: [...scoped.remotes, meta] };
2401
+ }
2402
+ return { ...external, versions: versions.sort(byTag(compare)) };
2360
2403
  }
2361
-
2362
- // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
2363
- function islandedRemotes(members) {
2364
- const islanded = /* @__PURE__ */ new Map();
2365
- for (const member of members)
2366
- for (const version of member.external.versions)
2367
- if (version.action === "scope") {
2368
- for (const remote of version.remotes)
2369
- if (!islanded.has(remote.name))
2370
- islanded.set(remote.name, {
2371
- kind: "incompatible",
2372
- member: member.name,
2373
- tag: version.tag
2374
- });
2375
- }
2376
- return islanded;
2404
+ function recordFromMap(external, remote) {
2405
+ const opened = external.versions.find(
2406
+ (v) => v.action !== "skip" && v.remotes.some((r) => r.name === remote)
2407
+ );
2408
+ const meta = { ...opened.remotes.find((r) => r.name === remote), cached: false };
2409
+ const joined = rowAt(external.versions, opened.tag, "skip");
2410
+ const into = joined ? { ...joined, remotes: [...joined.remotes] } : { ...opened, action: "skip", remotes: [] };
2411
+ addRemoteToVersion(into, meta);
2412
+ const versions = external.versions.flatMap((v) => {
2413
+ if (v === joined) return [into];
2414
+ if (v !== opened) return [v];
2415
+ const left = { ...v, remotes: v.remotes.filter((r) => r.name !== remote) };
2416
+ return [...left.remotes.length > 0 ? [left] : [], ...joined ? [] : [into]];
2417
+ });
2418
+ return { ...external, versions };
2377
2419
  }
2378
- var basisFor = (members, islanded, serving) => basisPerMember(members, islanded, (remote) => {
2379
- const build = serving.get(remote);
2380
- return build !== void 0 && build !== remote;
2381
- });
2382
- function servedPerRemote(consumed, serving, basis) {
2383
- const served = /* @__PURE__ */ new Map();
2384
- for (const [remote, build] of serving) {
2385
- const byMember = /* @__PURE__ */ new Map();
2386
- for (const name of consumed.get(remote) ?? []) {
2387
- if (basis.get(name) !== build) byMember.set(name, build);
2388
- }
2389
- if (byMember.size > 0) served.set(remote, byMember);
2390
- }
2391
- return served;
2420
+ function withoutPoolResults(external) {
2421
+ const { poolName: _poolName, poolWinner: _poolWinner, ...rest } = external;
2422
+ return {
2423
+ ...rest,
2424
+ versions: external.versions.map((v) => ({
2425
+ ...v,
2426
+ remotes: v.remotes.map(({ servedBy: _servedBy, poolCause: _poolCause, ...meta }) => meta)
2427
+ }))
2428
+ };
2392
2429
  }
2393
- var anyVerdictStored = (members) => members.some(
2394
- (m) => m.external.versions.some(
2395
- (v) => v.remotes.some((r) => r.servedBy !== void 0 || r.poolCause !== void 0)
2396
- )
2397
- );
2398
- function findTornRemotes(members, islanded, basis, served, hosts) {
2399
- const { suspect, selfServed } = movedRemotes(members, islanded, basis, served, hosts);
2400
- if (suspect.size === 0) return [];
2401
- const builds = liveBuilds(members, islanded);
2402
- const shared = sharedTagPerSpecifier(members, islanded);
2403
- const own = ownCopies(members, suspect);
2404
- const torn = [];
2405
- for (const remote of suspect) {
2406
- const copies = own.get(remote) ?? [];
2407
- const mine = selfServed.get(remote);
2408
- const anchors = served.get(remote);
2409
- const ownTags = /* @__PURE__ */ new Map();
2410
- const resolved = /* @__PURE__ */ new Map();
2411
- for (const copy of copies) {
2412
- const from = mine?.has(copy.member) ? remote : anchors?.get(copy.member);
2413
- for (const specifier in copy.entries) {
2414
- if (!ownTags.has(specifier)) ownTags.set(specifier, copy.tag);
2415
- const tag = from === remote ? copy.tag : from !== void 0 ? builds.get(from)?.tags.get(specifier) : shared.get(specifier);
2416
- if (tag !== void 0 && !resolved.has(specifier)) resolved.set(specifier, tag);
2417
- }
2418
- }
2419
- if (resolved.size === 0) continue;
2420
- if (ships(ownTags, resolved)) continue;
2421
- let witnessed = false;
2422
- for (const [, build] of builds) {
2423
- if (ships(build.tags, resolved)) {
2424
- witnessed = true;
2425
- break;
2426
- }
2427
- }
2428
- if (!witnessed) {
2429
- torn.push({
2430
- remote,
2431
- combination: [...resolved].map(([specifier, tag]) => `${specifier}@${tag}`).join(", ")
2432
- });
2433
- }
2434
- }
2435
- return torn;
2430
+ function renamedRecords(scope, pools) {
2431
+ const renamed = [];
2432
+ for (const [poolName, members] of pools)
2433
+ for (const { name } of members)
2434
+ if (scope[name].poolName !== poolName) renamed.push([name, { ...scope[name], poolName }]);
2435
+ return renamed;
2436
2436
  }
2437
- var ships = (build, resolved) => {
2438
- if (!build) return false;
2439
- for (const [specifier, tag] of resolved) if (build.get(specifier) !== tag) return false;
2440
- return true;
2441
- };
2442
- function movedRemotes(members, islanded, basis, served, hosts) {
2443
- const suspect = /* @__PURE__ */ new Set();
2444
- const selfServed = /* @__PURE__ */ new Map();
2445
- const add = (remote) => {
2446
- if (!islanded.has(remote) && !hosts.has(remote)) suspect.add(remote);
2447
- };
2448
- for (const remote of served.keys()) add(remote);
2449
- for (const member of members) {
2450
- const unpublished = !basis.has(member.name);
2451
- for (const version of member.external.versions) {
2452
- const scoped = version.action === "scope";
2453
- if (!unpublished && !scoped) continue;
2454
- for (const meta of version.remotes) {
2455
- add(meta.name);
2456
- const servesItself = scoped || unpublished && served.get(meta.name)?.get(member.name) === void 0;
2457
- if (!servesItself) continue;
2458
- let mine = selfServed.get(meta.name);
2459
- if (!mine) selfServed.set(meta.name, mine = /* @__PURE__ */ new Set());
2460
- mine.add(member.name);
2461
- }
2462
- }
2463
- }
2464
- return { suspect, selfServed };
2437
+
2438
+ // src/lib/core/1.domain/pooling/plan.ts
2439
+ function planElection(scope, poolable) {
2440
+ if (!poolable || !Object.values(scope).some((external) => external.dirty) || !scopeHasPoolState(scope))
2441
+ return { pools: /* @__PURE__ */ new Map(), stripped: /* @__PURE__ */ new Map(), labelledAlone: [] };
2442
+ const { pools, labelledAlone } = buildPools(scope);
2443
+ const pooled = new Set([...pools.values()].flatMap((members) => members.map((m) => m.name)));
2444
+ const stripped = /* @__PURE__ */ new Map();
2445
+ for (const [name, external] of Object.entries(scope))
2446
+ if (!pooled.has(name) && hasPoolResults(external))
2447
+ stripped.set(name, { ...withoutPoolResults(external), dirty: true });
2448
+ return { pools, stripped, labelledAlone };
2465
2449
  }
2450
+
2451
+ // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
2466
2452
  function createPoolSharedExternals(config, ports) {
2467
- return (touched) => {
2468
- for (const scope of poolableScopes(ports.sharedExternalsRepo)) {
2469
- const touchedInScope = touched?.get(scope);
2470
- if (touched && !touchedInScope) continue;
2471
- const sharedExternals = ports.sharedExternalsRepo.getFromScope(scope);
2453
+ const { compare } = ports.versionCheck;
2454
+ return () => {
2455
+ const repo = ports.sharedExternalsRepo;
2456
+ for (const scope of repo.getScopes()) {
2457
+ const sharedExternals = repo.getFromScope(scope);
2472
2458
  try {
2473
- const pools = buildPools(sharedExternals, config.log);
2474
- const rebuilt = /* @__PURE__ */ new Set();
2475
- for (const [poolName, members] of pools) {
2476
- if (touchedInScope && !members.some((m) => touchedInScope.has(m.name))) continue;
2477
- if (poolFamily(poolName, members, scope)) rebuilt.add(poolName);
2478
- }
2479
- syncPoolNames(sharedExternals, pools, ports.sharedExternalsRepo, scope, rebuilt);
2459
+ const plan = planElection(sharedExternals, repo.scopeType(scope) !== "strict");
2460
+ logPlan(plan, sharedExternals, scope);
2461
+ for (const [poolName, members] of plan.pools)
2462
+ for (const [name, record] of poolRecords(poolName, members, scope))
2463
+ repo.addOrUpdate(name, record, scope);
2464
+ for (const [name, external] of plan.stripped) repo.addOrUpdate(name, external, scope);
2480
2465
  } catch (error) {
2481
2466
  if (error instanceof NFError) return Promise.reject(error);
2482
2467
  config.log.error(3, `[${scope}] failed to pool shared externals.`, {
@@ -2490,253 +2475,439 @@ function createPoolSharedExternals(config, ports) {
2490
2475
  }
2491
2476
  return Promise.resolve();
2492
2477
  };
2493
- function poolFamily(poolName, members, scope) {
2494
- if (members.length < 2) return false;
2495
- const allRemotes = remotesInPool(members);
2496
- if (allRemotes.length < 2) {
2497
- if (!anyVerdictStored(members)) return false;
2498
- const basis2 = basisFor(members, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
2499
- for (const member of members)
2500
- ports.sharedExternalsRepo.addOrUpdate(
2501
- member.name,
2502
- rebuildMember(poolName, member, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), basis2.get(member.name)),
2503
- scope
2504
- );
2505
- return true;
2506
- }
2507
- const islanded = islandedRemotes(members);
2508
- const consumed = consumedMembers(members);
2509
- const hosts = hostRemotes(members);
2510
- const views = {
2511
- consumedSpecifiers: lazy(() => consumedSpecifiers(members)),
2512
- acceptance: lazy(() => acceptanceTable(members, ports.versionCheck.isCompatible)),
2513
- arrival: lazy(() => arrivalOrder(members))
2514
- };
2515
- config.log.debug(
2516
- 3,
2517
- `[${scope}][pool:${poolName}] ${members.length} members across ${allRemotes.length} remotes, incompatible={${[...islanded.keys()].join(", ") || "\u2205"}}
2518
- ` + members.map((m) => ` - ${m.name}`).join("\n")
2519
- );
2520
- const assign = () => {
2521
- const serving2 = assignServingBuilds(members, islanded, consumed, hosts, views);
2522
- const basis2 = basisFor(members, islanded, serving2);
2523
- return { serving: serving2, basis: basis2, served: servedPerRemote(consumed, serving2, basis2) };
2524
- };
2525
- let { serving, basis, served } = assign();
2526
- if (islanded.size === 0 && serving.size === 0 && !anyVerdictStored(members)) return false;
2527
- for (; ; ) {
2528
- const torn = findTornRemotes(members, islanded, basis, served, hosts);
2529
- if (torn.length === 0) break;
2530
- for (const { remote, combination } of torn)
2531
- islanded.set(remote, { kind: "torn", combination });
2532
- ({ serving, basis, served } = assign());
2478
+ function logPlan(plan, sharedExternals, scope) {
2479
+ if (plan.pools.size > 0) {
2480
+ const dirty = Object.values(sharedExternals).filter((external) => external.dirty).length;
2481
+ config.log.debug(
2482
+ 3,
2483
+ `[${scope}] re-electing ${plan.pools.size} pool(s): ${dirty} dirty external(s).`
2484
+ );
2533
2485
  }
2534
- const incompatible = [...islanded].filter(([, cause]) => cause.kind === "incompatible").map(([remote]) => remote);
2535
- if (config.strict.strictExternalCompatibility && incompatible.length > 0) {
2486
+ if (plan.stripped.size > 0)
2487
+ config.log.debug(
2488
+ 3,
2489
+ `[${scope}] ${plan.stripped.size} external(s) left every pool; cleared their pool state for re-election.`
2490
+ );
2491
+ for (const name of plan.labelledAlone)
2492
+ config.log.debug(
2493
+ 3,
2494
+ `[${scope}] '${name}' has a 'pool' label that joins no other external; likely a typo or a missing sibling.`
2495
+ );
2496
+ }
2497
+ function poolRecords(poolName, members, scope) {
2498
+ const election = elect({
2499
+ members,
2500
+ acceptsTag: createAcceptsTag(ports.versionCheck.isCompatible, compare),
2501
+ compare,
2502
+ previousWinner: previousWinner(members),
2503
+ latestFirst: config.profile.latestSharedExternal
2504
+ });
2505
+ const placed = { ...election, poolName };
2506
+ report(poolName, members, placed, scope);
2507
+ return members.map((m) => [
2508
+ m.name,
2509
+ { ...memberRecord(m, placed, compare), poolWinner: placed.winner }
2510
+ ]);
2511
+ }
2512
+ function report(poolName, members, placed, scope) {
2513
+ const { misses } = placed;
2514
+ const rejecting = [...misses].filter(([, miss]) => miss?.cause === "incompatible" && miss.strict).map(([remote]) => remote);
2515
+ if (config.strict.strictExternalCompatibility && rejecting.length > 0) {
2536
2516
  config.log.error(
2537
2517
  3,
2538
- `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${incompatible.join(", ")}}.`
2518
+ `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${rejecting.join(", ")}}.`
2539
2519
  );
2540
2520
  throw new NFError(`Could not pool '${poolName}' in scope ${scope}.`);
2541
2521
  }
2542
- for (const [remote, cause] of islanded) {
2543
- const scoped = consumed.get(remote)?.length ?? 0;
2544
- config.log.warn(3, `[${scope}][pool:${poolName}] ${islandWarning(remote, cause, scoped)}`);
2545
- }
2546
- for (const member of members) {
2547
- const rebuilt = rebuildMember(poolName, member, islanded, served, basis.get(member.name));
2548
- warnIfScopedOnly(poolName, member, rebuilt, islanded, scope);
2549
- ports.sharedExternalsRepo.addOrUpdate(member.name, rebuilt, scope);
2522
+ const subpoolSizes = /* @__PURE__ */ new Map();
2523
+ for (const placement of placed.placements.values())
2524
+ if (placement.kind === "subpool")
2525
+ subpoolSizes.set(placement.owner, (subpoolSizes.get(placement.owner) ?? 0) + 1);
2526
+ let shipped;
2527
+ for (const [remote, miss] of misses) {
2528
+ if (miss === void 0) {
2529
+ config.log.warn(
2530
+ 3,
2531
+ `[${scope}][pool:${poolName}] '${remote}' keeps subpool '${remote}': the elected build would serve it, but ${subpoolSizes.get(remote) - 1} other remote(s) in it need its build.`
2532
+ );
2533
+ continue;
2534
+ }
2535
+ shipped ??= copiesByRemote(members);
2536
+ const counts = {
2537
+ imports: specifierCount(shipped.get(remote)),
2538
+ subpoolSize: subpoolSizes.get(remote) ?? 0
2539
+ };
2540
+ config.log.warn(
2541
+ 3,
2542
+ `[${scope}][pool:${poolName}] ${missWarning(remote, miss, placed, counts)}`
2543
+ );
2550
2544
  }
2551
- return true;
2552
- }
2553
- function assignServingBuilds(members, islanded, consumed, hosts, views) {
2554
- for (; ; ) {
2555
- const bases = servingBuilds(members, islanded);
2556
- const distinct = new Set(bases.values());
2557
- if (distinct.size === 1 && bases.size === members.length) {
2558
- const sole = [...distinct][0];
2559
- if (coversWholePool(members, sole, islanded)) return /* @__PURE__ */ new Map();
2545
+ }
2546
+ }
2547
+ function missWarning(remote, miss, placed, counts) {
2548
+ const { imports, subpoolSize } = counts;
2549
+ const placement = placed.placements.get(remote);
2550
+ const where = placement.kind === "subpool" && placement.owner !== remote ? `It runs in subpool '${placement.owner}' for all ${imports} of its entrypoints.` : subpoolSize > 1 ? `Its build serves subpool '${remote}': its ${imports} entrypoints and ${subpoolSize - 1} other remote(s).` : `All ${imports} of its entrypoints are scoped for it.`;
2551
+ if (miss.cause === "incompatible")
2552
+ return `'${remote}' is islanded: its range rejects '${miss.member}@${miss.tag}' of the elected build '${placed.winner}'. ${where}`;
2553
+ if (miss.unshipped)
2554
+ return `'${remote}' serves its own family: no build shipped its entrypoints together at the elected versions (gap '${miss.gap}', closest '${placed.winner}'). ${where}`;
2555
+ return `'${remote}' serves its own family: no elected build offers every entrypoint it imports at a version it accepts (gap '${miss.gap}', closest '${placed.winner}'). ${where}`;
2556
+ }
2557
+
2558
+ // src/lib/core/1.domain/externals/serving.ts
2559
+ var byServingRank = (a, b) => Number(b.host) - Number(a.host) || a.downloads - b.downloads || a.fallbacks - b.fallbacks || Number(b.rootBasis) - Number(a.rootBasis) || compareStrings(a.remote, b.remote);
2560
+ var packagesOf = (scope) => groupByPackage(Object.keys(scope), (name) => name);
2561
+ var isPooled = (externals) => externals.some(([, e]) => e.poolName !== void 0);
2562
+ function servingBuildOf(externals, accepts) {
2563
+ if (externals.length === 0) return void 0;
2564
+ const pkg = packageOf(externals[0][0]);
2565
+ const rootRows = shareRows(externals.find(([name]) => name === pkg)?.[1]);
2566
+ const rows = rootRows.length > 0 ? rootRows : externals.flatMap(([, e]) => shareRows(e));
2567
+ if (isPooled(externals)) return pooledBuild(externals, rows);
2568
+ const consumers = consumersOf(externals);
2569
+ const builds = buildsOf(rows, rootRows, consumers);
2570
+ const hosts = builds.filter((b) => b.host);
2571
+ const field = hosts.length > 0 ? hosts : builds;
2572
+ if (field.length <= 1) return field[0] && { remote: field[0].remote, tag: field[0].tag };
2573
+ return cheapestBuild(field, costTable(consumers, accepts));
2574
+ }
2575
+ function cheapestBuild(field, costOf) {
2576
+ let best;
2577
+ for (const b of field) {
2578
+ const { downloads, fallbacks } = costOf(b);
2579
+ const candidate = {
2580
+ remote: b.remote,
2581
+ tag: b.tag,
2582
+ host: b.host,
2583
+ rootBasis: b.rootBasis,
2584
+ downloads,
2585
+ fallbacks
2586
+ };
2587
+ if (!best || byServingRank(candidate, best) < 0) best = candidate;
2588
+ }
2589
+ const { remote, tag } = best;
2590
+ return { remote, tag };
2591
+ }
2592
+ function shareServingCopies(external, serving) {
2593
+ for (const row of external.versions) {
2594
+ const copy = row.remotes.find((c) => c.name === serving.remote);
2595
+ if (row.action !== "skip" || !copy || rowAt(external.versions, row.tag, "share")) continue;
2596
+ row.action = "share";
2597
+ if (row.remotes[0] !== copy) row.host = false;
2598
+ row.remotes = [copy, ...row.remotes.filter((c) => c !== copy)];
2599
+ }
2600
+ }
2601
+ function followServingBuild(external, serving, accepts) {
2602
+ let objector;
2603
+ const rebuilt = external.versions.flatMap((version) => {
2604
+ if (version.action !== "share" || version.tag === serving.tag || version.remotes[0].name === serving.remote)
2605
+ return [version];
2606
+ const rows = redirectRow(version, serving.tag, accepts);
2607
+ const scoped = rows.find((row) => row.action === "scope");
2608
+ if (scoped && !objector) objector = { copy: scoped.remotes[0], tag: scoped.tag };
2609
+ return rows;
2610
+ });
2611
+ external.versions = mergeRows(rebuilt);
2612
+ return { external, objector };
2613
+ }
2614
+ var sameTagOnly = (offered, ownTag) => offered === ownTag;
2615
+ var servedScopes = (scope, strict) => strict ? strictTagScopes(scope) : [scope];
2616
+ function strictTagScopes(scope) {
2617
+ const tagScopes = [];
2618
+ for (const [pkg, names] of packagesOf(scope)) {
2619
+ const byTag2 = /* @__PURE__ */ new Map();
2620
+ for (const name of names)
2621
+ for (const row of scope[name].versions) {
2622
+ let tagScope = byTag2.get(row.tag);
2623
+ if (!tagScope) byTag2.set(row.tag, tagScope = {});
2624
+ const external = tagScope[name] ??= { ...scope[name], versions: [] };
2625
+ external.versions.push({ ...row, remotes: [...row.remotes] });
2560
2626
  }
2561
- const builds = liveBuilds(members, islanded);
2562
- const shared = sharedTagPerSpecifier(members, islanded);
2563
- const consumedSpec = views.consumedSpecifiers();
2564
- const needAnchor = /* @__PURE__ */ new Map();
2565
- const needSpecifiers = /* @__PURE__ */ new Map();
2566
- for (const [remote, wants] of consumed) {
2567
- if (islanded.has(remote)) continue;
2568
- if (isWitnessed(consumedSpec.get(remote) ?? [], shared, builds)) continue;
2569
- needAnchor.set(remote, wants);
2570
- needSpecifiers.set(remote, consumedSpec.get(remote) ?? /* @__PURE__ */ new Set());
2627
+ for (const [tag, tagScope] of byTag2) {
2628
+ const lead = [tagScope[pkg], ...Object.values(tagScope)].map((external) => external && rowAt(external.versions, tag, "share")).find((row) => row !== void 0);
2629
+ if (lead) {
2630
+ const serving = { remote: lead.remotes[0].name, tag };
2631
+ for (const external of Object.values(tagScope)) external.servingBuild = serving.remote;
2632
+ scopeFallbacks(Object.entries(tagScope), serving, sameTagOnly);
2571
2633
  }
2572
- if (needAnchor.size === 0) return /* @__PURE__ */ new Map();
2573
- const acceptance = views.acceptance();
2574
- const assignment = assignAnchors({
2575
- builds,
2576
- acceptance,
2577
- consumedSpecifiers: needSpecifiers,
2578
- consumedMembers: needAnchor,
2579
- hosts,
2580
- arrival: views.arrival()
2634
+ tagScopes.push(tagScope);
2635
+ }
2636
+ }
2637
+ return tagScopes;
2638
+ }
2639
+ function shareRows(external) {
2640
+ return external?.versions.filter((v) => v.action === "share") ?? [];
2641
+ }
2642
+ function buildsOf(rows, rootRows, consumers) {
2643
+ const candidates = /* @__PURE__ */ new Map();
2644
+ for (const row of rows)
2645
+ for (const copy of row.remotes) {
2646
+ const key = `${row.tag}|${copy.name}`;
2647
+ if (copy.servedBy || candidates.has(key) || shipsTwoTags(consumers.get(copy.name))) continue;
2648
+ const basis = copy === row.remotes[0];
2649
+ candidates.set(key, {
2650
+ remote: copy.name,
2651
+ tag: row.tag,
2652
+ host: basis && row.host,
2653
+ rootBasis: basis && rootRows.includes(row)
2581
2654
  });
2582
- const anchors = /* @__PURE__ */ new Set();
2583
- for (const build of assignment.values()) if (build !== void 0) anchors.add(build);
2584
- const serving = /* @__PURE__ */ new Map();
2585
- let scoped = false;
2586
- for (const [remote, wants] of needAnchor) {
2587
- const assigned = assignment.get(remote);
2588
- const build = assigned ?? (anchors.has(remote) || hosts.has(remote) ? remote : void 0);
2589
- if (build === void 0) {
2590
- islanded.set(remote, {
2591
- kind: "uncovered",
2592
- ...explainSelfServe(remote, wants, needSpecifiers.get(remote), { builds, acceptance })
2593
- });
2594
- scoped = true;
2595
- continue;
2596
- }
2597
- serving.set(remote, build);
2655
+ }
2656
+ return [...candidates.values()];
2657
+ }
2658
+ function pooledBuild(externals, rows) {
2659
+ const winner = externals.find(([, e]) => e.poolWinner !== void 0)?.[1].poolWinner;
2660
+ const shipped = rows.flatMap(
2661
+ (row) => row.remotes.filter((copy) => !copy.servedBy).map((copy) => ({ remote: copy.name, tag: row.tag }))
2662
+ );
2663
+ const own = shipped.find((c) => c.remote === winner);
2664
+ if (own) return own;
2665
+ const width = ({ remote, tag }) => externals.reduce((n, [, e]) => {
2666
+ const copy = shareRows(e).find((row) => row.tag === tag)?.remotes.find((c) => c.name === remote);
2667
+ return n + Object.keys(copy?.entries ?? {}).length;
2668
+ }, 0);
2669
+ return shipped.map((c) => ({ c, width: width(c) })).sort((a, b) => b.width - a.width || compareStrings(a.c.remote, b.c.remote))[0]?.c;
2670
+ }
2671
+ function consumersOf(externals) {
2672
+ const consumers = /* @__PURE__ */ new Map();
2673
+ for (const [, external] of externals)
2674
+ for (const row of external.versions)
2675
+ for (const copy of row.remotes) {
2676
+ const placed = consumers.get(copy.name);
2677
+ if (placed) placed.push({ row, copy });
2678
+ else consumers.set(copy.name, [{ row, copy }]);
2598
2679
  }
2599
- if (!scoped) return serving;
2680
+ return consumers;
2681
+ }
2682
+ function costTable(consumers, accepts) {
2683
+ const scanned = /* @__PURE__ */ new Map();
2684
+ const keyed = /* @__PURE__ */ new Map();
2685
+ const tollFor = (build, surface) => {
2686
+ let atTag = scanned.get(build.tag);
2687
+ if (!atTag) scanned.set(build.tag, atTag = []);
2688
+ const known = atTag.find((t) => sameSurface(t.surface, surface));
2689
+ if (known) return known.toll;
2690
+ if (atTag.length < SCANNED_SURFACES_PER_TAG) {
2691
+ const toll2 = tollOf(consumers, surface, build.tag, accepts);
2692
+ atTag.push({ surface, toll: toll2 });
2693
+ return toll2;
2600
2694
  }
2601
- }
2602
- function warnIfScopedOnly(poolName, member, rebuilt, islanded, scope) {
2603
- if (rebuilt.versions.some((v) => v.action === "share")) return;
2604
- const consumers = new Set(
2605
- rebuilt.versions.filter((v) => v.action === "scope").flatMap((v) => v.remotes.map((r) => r.name))
2606
- ).size;
2607
- if (consumers < 2) return;
2608
- const provider = member.external.versions.find((v) => v.action === "share");
2609
- if (provider?.remotes.some((r) => islanded.has(r.name))) return;
2610
- config.log.warn(
2611
- 3,
2612
- `[${scope}][pool:${poolName}] '${member.name}' is scoped-only \u2014 no coherent shared build provides it; ${consumers} remotes download their own copy.`
2613
- );
2614
- }
2615
- function rebuildMember(poolName, member, islanded, served, basis) {
2616
- let scoped = [];
2617
- let clean = [];
2618
- for (const version of member.external.versions) {
2619
- for (const meta of version.remotes) {
2620
- const entry = {
2621
- remote: meta.name,
2622
- tag: version.tag,
2623
- host: version.host,
2624
- action: version.action,
2625
- meta
2626
- };
2627
- (islanded.has(entry.remote) || entry.action === "scope" ? scoped : clean).push(entry);
2695
+ const key = [build.tag, ...[...surface].map(([s, tag]) => `${s}@${tag}`).sort()].join("\n");
2696
+ let toll = keyed.get(key);
2697
+ if (!toll) keyed.set(key, toll = tollOf(consumers, surface, build.tag, accepts));
2698
+ return toll;
2699
+ };
2700
+ return (build) => {
2701
+ const surface = surfaceOf(consumers, build);
2702
+ const toll = tollFor(build, surface);
2703
+ const others = (remotes) => remotes.size - Number(remotes.has(build.remote));
2704
+ return {
2705
+ downloads: others(toll.downloads) + Number(!allCached(consumers.get(build.remote) ?? [])),
2706
+ fallbacks: others(toll.fallbacks)
2707
+ };
2708
+ };
2709
+ }
2710
+ function tollOf(consumers, surface, tag, accepts) {
2711
+ const toll = { fallbacks: /* @__PURE__ */ new Set(), downloads: /* @__PURE__ */ new Set() };
2712
+ for (const [remote, placed] of consumers) {
2713
+ const fallback = fallsBack(placed, surface, tag, accepts);
2714
+ if (fallback) toll.fallbacks.add(remote);
2715
+ if (!allCached(placed) && (fallback || runsOffMap(placed, tag, accepts)))
2716
+ toll.downloads.add(remote);
2717
+ }
2718
+ return toll;
2719
+ }
2720
+ var allCached = (placed) => placed.every(({ copy }) => copy.cached);
2721
+ function fallbacksOf(externals, serving, accepts) {
2722
+ const consumers = consumersOf(externals);
2723
+ const surface = serving ? surfaceOf(consumers, serving) : /* @__PURE__ */ new Map();
2724
+ return [...consumers].filter(
2725
+ ([remote, placed]) => remote !== serving?.remote && fallsBack(placed, surface, serving?.tag, accepts)
2726
+ ).map(([remote]) => remote);
2727
+ }
2728
+ function scopeFallbacks(externals, serving, accepts) {
2729
+ const remotes = fallbacksOf(externals, serving, accepts);
2730
+ for (const [, external] of externals)
2731
+ external.versions = scopeRemotes(external.versions, remotes);
2732
+ }
2733
+ function strictObjectorOf(externals, serving, accepts) {
2734
+ const consumers = consumersOf(externals);
2735
+ const surface = surfaceOf(consumers, serving);
2736
+ for (const [name, external] of externals)
2737
+ for (const row of external.versions)
2738
+ for (const copy of row.remotes)
2739
+ if (copy.name !== serving.remote && rejectsServingTag({ row, copy }, surface, serving.tag, accepts) && !splitsPackage(consumers.get(copy.name), surface, serving.tag, accepts))
2740
+ return { external: name, copy, tag: row.tag };
2741
+ return void 0;
2742
+ }
2743
+ var SCANNED_SURFACES_PER_TAG = 8;
2744
+ function sameSurface(a, b) {
2745
+ if (a.size !== b.size) return false;
2746
+ for (const [specifier, tag] of a) if (b.get(specifier) !== tag) return false;
2747
+ return true;
2748
+ }
2749
+ function surfaceOf(consumers, serving) {
2750
+ const surface = /* @__PURE__ */ new Map();
2751
+ for (const p of consumers.get(serving.remote) ?? [])
2752
+ if (p.row.tag === serving.tag && onMap(p) || leadsShareRow(p)) {
2753
+ for (const s in p.copy.entries) if (!surface.has(s)) surface.set(s, p.row.tag);
2754
+ }
2755
+ return surface;
2756
+ }
2757
+ var leadsShareRow = ({ row, copy }) => row.action === "share" && row.remotes[0] === copy;
2758
+ var onMap = ({ row }) => row.action !== "scope";
2759
+ function splitsPackage(placed, surface, tag, accepts) {
2760
+ const uncovered = (p) => Object.keys(p.copy.entries).some((s) => {
2761
+ const servedAt = surface.get(s);
2762
+ return servedAt === void 0 || servedAt !== tag && rejectsTag(p, servedAt, accepts);
2763
+ });
2764
+ return placed.some(onMap) && (shipsTwoTags(placed) || placed.some((p) => !onMap(p) || uncovered(p)));
2765
+ }
2766
+ function loadSplitsPackage(copies, claims, accepts) {
2767
+ const root = claims.find(([specifier]) => specifier === packageOf(specifier))?.[1];
2768
+ const servingTag = root?.tag ?? claims[0][1].tag;
2769
+ const surface = new Map(
2770
+ claims.map(([specifier, { tag }]) => [specifier, root ? tag : servingTag])
2771
+ );
2772
+ const placed = copies.map(({ tag, copy }) => ({
2773
+ row: { tag, action: "share", host: false, remotes: [copy] },
2774
+ copy
2775
+ }));
2776
+ return splitsPackage(placed, surface, servingTag, accepts);
2777
+ }
2778
+ function fallsBack(placed, surface, tag, accepts) {
2779
+ return splitsPackage(placed, surface, tag, accepts) || tag !== void 0 && placed.some((p) => rejectsServingTag(p, surface, tag, accepts));
2780
+ }
2781
+ function rejectsServingTag(p, surface, tag, accepts) {
2782
+ return onMap(p) && rejectsTag(p, tag, accepts) && Object.keys(p.copy.entries).some((s) => surface.get(s) === tag);
2783
+ }
2784
+ var rejectsTag = ({ row, copy }, tag, accepts) => copy.strictVersion && !accepts(tag, row.tag, copy.requiredVersion);
2785
+ var shipsTwoTags = (placed) => !placed.some((p) => p.row.host && p.row.remotes[0] === p.copy) && placed.some((p) => p.row.tag !== placed[0].row.tag);
2786
+ function runsOffMap(placed, tag, accepts) {
2787
+ return placed.some((p) => !onMap(p) || rejectsTag(p, tag, accepts));
2788
+ }
2789
+
2790
+ // src/lib/core/2.app/steps/decide-serving-builds.ts
2791
+ function createDecideServingBuilds(config, ports) {
2792
+ return () => {
2793
+ const accepts = createAcceptsTag(ports.versionCheck.isCompatible, ports.versionCheck.compare);
2794
+ const repo = ports.sharedExternalsRepo;
2795
+ const written = new Set(repo.uncommittedScopes());
2796
+ const needsDeciding = (shareScope) => written.has(shareScope) || repo.isUndecided(shareScope);
2797
+ try {
2798
+ for (const shareScope of repo.getScopes()) {
2799
+ if (repo.scopeType(shareScope) === "strict" || !needsDeciding(shareScope)) continue;
2800
+ const sharedExternals = repo.getFromScope(shareScope);
2801
+ for (const [pkg, names] of packagesOf(sharedExternals)) {
2802
+ const externals = names.map((name) => [name, sharedExternals[name]]);
2803
+ decidePackage(shareScope, pkg, externals, accepts);
2804
+ }
2805
+ repo.markDecided(shareScope);
2628
2806
  }
2807
+ return Promise.resolve();
2808
+ } catch (e) {
2809
+ return Promise.reject(e);
2629
2810
  }
2630
- for (const entry of scoped) delete entry.meta.servedBy;
2631
- for (const entry of clean) {
2632
- const build = served.get(entry.remote)?.get(member.name);
2633
- if (build !== void 0) entry.meta.servedBy = build;
2634
- else delete entry.meta.servedBy;
2635
- delete entry.meta.poolCause;
2636
- }
2637
- if (basis === void 0) {
2638
- scoped = [...scoped, ...clean.filter((e) => e.meta.servedBy === void 0)];
2639
- clean = clean.filter((e) => e.meta.servedBy !== void 0).map((e) => ({ ...e, action: "skip" }));
2640
- }
2641
- for (const entry of scoped)
2642
- entry.meta.poolCause = islanded.get(entry.remote)?.kind ?? "unshared";
2643
- const shareEntries = clean.filter((e) => e.action === "share").sort((a, b) => Number(b.remote === basis) - Number(a.remote === basis));
2644
- const shareVersion = shareEntries.length > 0 ? [
2645
- {
2646
- tag: shareEntries[0].tag,
2647
- host: shareEntries[0].host,
2648
- action: "share",
2649
- remotes: shareEntries.map((e) => e.meta)
2650
- }
2651
- ] : [];
2652
- const byTag = (entries, action) => {
2653
- const versions = /* @__PURE__ */ new Map();
2654
- for (const entry of entries) {
2655
- const version = versions.get(entry.tag) ?? {
2656
- tag: entry.tag,
2657
- host: entry.host,
2658
- action,
2659
- remotes: []
2660
- };
2661
- version.remotes.push(entry.meta);
2662
- versions.set(entry.tag, version);
2663
- }
2664
- return [...versions.values()];
2665
- };
2666
- return {
2667
- dirty: false,
2668
- poolName,
2669
- versions: [
2670
- ...shareVersion,
2671
- ...byTag(
2672
- clean.filter((e) => e.action === "skip"),
2673
- "skip"
2674
- ),
2675
- ...byTag(scoped, "scope")
2676
- ].sort((a, b) => ports.versionCheck.compare(b.tag, a.tag))
2677
- };
2811
+ };
2812
+ function decidePackage(shareScope, pkg, externals, accepts) {
2813
+ const serving = servingBuildOf(externals, accepts);
2814
+ const before = externals.map(([, external]) => shapeOf(external));
2815
+ const pooled = isPooled(externals);
2816
+ const toFollow = pooled ? void 0 : serving;
2817
+ if (!pooled) fallBack(pkg, externals, accepts, serving);
2818
+ externals.forEach(([name, external], i) => {
2819
+ const decided = withServingBuild(
2820
+ toFollow ? follow(pkg, name, external, toFollow, accepts) : external,
2821
+ serving?.remote
2822
+ );
2823
+ if (!sameShape(before[i], decided))
2824
+ ports.sharedExternalsRepo.addOrUpdate(name, decided, shareScope);
2825
+ });
2678
2826
  }
2679
- }
2680
- function islandWarning(remote, cause, scoped) {
2681
- switch (cause.kind) {
2682
- case "incompatible":
2683
- return `'${remote}' is islanded: the resolver scoped its '${cause.member}@${cause.tag}', so all ${scoped} members it imports are scoped for it.`;
2684
- case "torn":
2685
- return `'${remote}' serves its own family: the mapping would have handed it ${cause.combination}, which no build shipped together, so all ${scoped} members it imports are scoped for it.`;
2686
- default: {
2687
- const closest = cause.closest ? `closest is '${cause.closest}'` : "no other build in the pool serves any of it";
2688
- return `'${remote}' serves its own family: no shared build offers every entrypoint it imports at a version it accepts \u2014 '${cause.gap}' is the gap, ${closest}. All ${scoped} members it imports are scoped for it.`;
2827
+ function follow(pkg, name, external, serving, accepts) {
2828
+ const { external: followed, objector } = followServingBuild(external, serving, accepts);
2829
+ if (objector && config.strict.strictExternalCompatibility)
2830
+ rejectIncompatible(config, objector.copy, `${name}@${objector.tag}`, `${pkg}@${serving.tag}`);
2831
+ return followed;
2832
+ }
2833
+ function fallBack(pkg, externals, accepts, serving) {
2834
+ if (serving) {
2835
+ for (const [, external] of externals) shareServingCopies(external, serving);
2836
+ const objector = config.strict.strictExternalCompatibility && strictObjectorOf(externals, serving, accepts);
2837
+ if (objector)
2838
+ rejectIncompatible(
2839
+ config,
2840
+ objector.copy,
2841
+ `${objector.external}@${objector.tag}`,
2842
+ `${pkg}@${serving.tag}`
2843
+ );
2689
2844
  }
2845
+ scopeFallbacks(externals, serving, accepts);
2690
2846
  }
2691
2847
  }
2848
+ function withServingBuild(external, servingBuild) {
2849
+ if (external.servingBuild === servingBuild) return external;
2850
+ const { servingBuild: _, ...rest } = external;
2851
+ return servingBuild ? { ...rest, servingBuild } : rest;
2852
+ }
2853
+ var shapeOf = (external) => ({
2854
+ servingBuild: external.servingBuild,
2855
+ rows: external.versions.map(
2856
+ ({ tag, action, host, remotes }) => ({ tag, action, host, remotes: [...remotes] })
2857
+ )
2858
+ });
2859
+ var sameRow = (was, row) => Object.keys(was).every(
2860
+ (key) => key === "remotes" ? was.remotes.length === row.remotes.length && row.remotes.every((copy, j) => copy === was.remotes[j]) : was[key] === row[key]
2861
+ );
2862
+ var sameShape = (shape, external) => shape.servingBuild === external.servingBuild && shape.rows.length === external.versions.length && external.versions.every((row, i) => sameRow(shape.rows[i], row));
2692
2863
 
2693
- // src/lib/core/2.app/steps/pooling/mark-pools-for-reelection.ts
2694
- function clearPoolState(external) {
2695
- let cleared = external.poolName !== void 0;
2696
- delete external.poolName;
2697
- for (const version of external.versions)
2698
- for (const meta of version.remotes) {
2699
- if (meta.servedBy !== void 0 || meta.poolCause !== void 0) cleared = true;
2700
- delete meta.servedBy;
2701
- delete meta.poolCause;
2864
+ // src/lib/core/1.domain/externals/claims.ts
2865
+ function claimTable(externals, publishes) {
2866
+ const table = { claims: /* @__PURE__ */ new Map(), duplicates: /* @__PURE__ */ new Set(), shareClaims: /* @__PURE__ */ new Map() };
2867
+ const ordered = [...externals];
2868
+ const claim = (into, external, version, accepts = publishes) => forEachVersionEntry(version, accepts, (specifier, copy) => {
2869
+ if (!into.has(specifier)) into.set(specifier, { external, tag: version.tag, copy });
2870
+ });
2871
+ const seedServingBuild = (name, external) => {
2872
+ const isServing = (copy) => copy.name === external.servingBuild && (!publishes || publishes(copy));
2873
+ for (const version of external.versions) {
2874
+ if (version.action !== "share") continue;
2875
+ claim(table.shareClaims, name, version, isServing);
2876
+ claim(table.claims, name, version, isServing);
2702
2877
  }
2703
- return cleared;
2878
+ };
2879
+ const claimShareRow = (name, external, version) => {
2880
+ const prior = table.claims.get(name);
2881
+ const duplicate = prior !== void 0 && prior.tag !== version.tag;
2882
+ if (duplicate && !(version.host && version.remotes[0].name === external.servingBuild))
2883
+ table.duplicates.add(version);
2884
+ if (external.servingBuild) return;
2885
+ claim(table.shareClaims, name, version);
2886
+ if (!duplicate && (!prior || prior.external === name)) claim(table.claims, name, version);
2887
+ };
2888
+ for (const [name, external] of ordered)
2889
+ if (external.servingBuild) seedServingBuild(name, external);
2890
+ for (const [name, external] of ordered)
2891
+ for (const version of external.versions)
2892
+ if (version.action === "share") claimShareRow(name, external, version);
2893
+ return table;
2704
2894
  }
2705
- function createMarkPoolsForReelection(config, ports) {
2706
- return () => {
2707
- for (const scope of poolableScopes(ports.sharedExternalsRepo)) {
2708
- const sharedExternals = ports.sharedExternalsRepo.getFromScope(scope);
2709
- if (!Object.values(sharedExternals).some((external) => external.dirty)) continue;
2710
- let spread = 0;
2711
- let unpooled = 0;
2712
- const pooled = /* @__PURE__ */ new Set();
2713
- for (const [, members] of buildPools(sharedExternals)) {
2714
- for (const member of members) pooled.add(member.name);
2715
- if (!members.some((m) => m.external.dirty)) continue;
2716
- for (const member of members)
2717
- if (!member.external.dirty) {
2718
- member.external.dirty = true;
2719
- spread++;
2720
- }
2721
- }
2722
- for (const [name, external] of Object.entries(sharedExternals)) {
2723
- if (pooled.has(name) || !clearPoolState(external)) continue;
2724
- external.dirty = true;
2725
- unpooled++;
2726
- }
2727
- if (spread > 0)
2728
- config.log.debug(3, `[${scope}] ${spread} pool member(s) marked dirty for re-election.`);
2729
- if (unpooled > 0)
2730
- config.log.debug(
2731
- 3,
2732
- `[${scope}] ${unpooled} external(s) left every pool; cleared their pool state for re-election.`
2733
- );
2734
- }
2735
- return Promise.resolve();
2895
+ function committedClaims(externals, { shareScope, without }) {
2896
+ const publishes = (copy) => copy.name !== without && (shareScope !== GLOBAL_SCOPE || copy.cached);
2897
+ return claimTable(externals, publishes).claims;
2898
+ }
2899
+ var claimsByPackage = (claims) => groupByPackage(claims, ([specifier]) => specifier);
2900
+ function versionClaims(version, servingBuild) {
2901
+ const claims = /* @__PURE__ */ new Map();
2902
+ const claim = (specifier, copy) => {
2903
+ if (!claims.has(specifier)) claims.set(specifier, copy);
2736
2904
  };
2905
+ const accepts = servingBuild ? (copy) => copy.name === servingBuild : void 0;
2906
+ forEachVersionEntry(version, accepts, claim);
2907
+ return claims;
2737
2908
  }
2738
2909
 
2739
- // node_modules/.pnpm/@softarc+native-federation@4.7.0/node_modules/@softarc/native-federation/dist/lib/domain/core/build-notification-options.contract.js
2910
+ // node_modules/.pnpm/@softarc+native-federation@4.8.0/node_modules/@softarc/native-federation/dist/lib/domain/core/build-notification-options.contract.js
2740
2911
  var BuildNotificationType = /* @__PURE__ */ ((BuildNotificationType2) => {
2741
2912
  BuildNotificationType2["COMPLETED"] = "federation-rebuild-complete";
2742
2913
  BuildNotificationType2["ERROR"] = "federation-rebuild-error";
@@ -2744,7 +2915,7 @@ var BuildNotificationType = /* @__PURE__ */ ((BuildNotificationType2) => {
2744
2915
  return BuildNotificationType2;
2745
2916
  })(BuildNotificationType || {});
2746
2917
 
2747
- // node_modules/.pnpm/@softarc+native-federation@4.7.0/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
2918
+ // node_modules/.pnpm/@softarc+native-federation@4.8.0/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
2748
2919
  var CHUNK_PREFIX = "@nf-internal";
2749
2920
  function toChunkImport(fileName) {
2750
2921
  if (fileName.startsWith("./")) {
@@ -2761,8 +2932,8 @@ function createGenerateImportMap(config, ports) {
2761
2932
  const chunkBundles = {};
2762
2933
  try {
2763
2934
  addScopedExternals(importMap, chunkBundles);
2764
- addshareScopeExternals(importMap, chunkBundles);
2765
- addGlobalSharedExternals(importMap, chunkBundles);
2935
+ addNamedShareScopes(importMap, chunkBundles);
2936
+ addGlobalShareScope(importMap, chunkBundles);
2766
2937
  addRemoteInfos(importMap, chunkBundles);
2767
2938
  addChunkImports(importMap, chunkBundles);
2768
2939
  return Promise.resolve(importMap);
@@ -2796,47 +2967,61 @@ function createGenerateImportMap(config, ports) {
2796
2967
  }
2797
2968
  return modules;
2798
2969
  }
2799
- function addshareScopeExternals(importMap, chunkBundles) {
2970
+ function addNamedShareScopes(importMap, chunkBundles) {
2800
2971
  const shareScopes = ports.sharedExternalsRepo.getScopes({ includeGlobal: false });
2801
2972
  for (const shareScope of shareScopes) {
2802
- processshareScope(importMap, shareScope, chunkBundles);
2973
+ const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
2974
+ const cachedBefore = /* @__PURE__ */ new Map();
2975
+ for (const [externalName, external] of Object.entries(sharedExternals))
2976
+ cachedBefore.set(externalName, cachedFingerprint(external));
2977
+ const strict = ports.sharedExternalsRepo.scopeType(shareScope) === "strict";
2978
+ for (const served of servedScopes(sharedExternals, strict))
2979
+ addShareScope(importMap, shareScope, served, chunkBundles);
2980
+ for (const [externalName, external] of Object.entries(sharedExternals)) {
2981
+ if (cachedFingerprint(external) !== cachedBefore.get(externalName)) {
2982
+ ports.sharedExternalsRepo.addOrUpdate(externalName, external, shareScope);
2983
+ }
2984
+ }
2803
2985
  }
2804
2986
  return importMap;
2805
2987
  }
2806
- function processshareScope(importMap, shareScope, chunkBundles) {
2807
- const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
2988
+ function addShareScope(importMap, shareScope, sharedExternals, chunkBundles) {
2808
2989
  const index = buildServedIndex(sharedExternals);
2990
+ const claims = shareRowMappings(sharedExternals, shareScope);
2991
+ const claimedElsewhere = (externalName, specifier) => {
2992
+ const claim = claims.get(specifier);
2993
+ return claim?.external === externalName ? void 0 : claim;
2994
+ };
2809
2995
  for (const [externalName, external] of Object.entries(sharedExternals)) {
2810
2996
  let override = void 0;
2811
- const cachedBefore = cachedFingerprint(external);
2812
2997
  for (const version of external.versions) {
2813
2998
  if (version.action === "scope") {
2814
- for (const remote of version.remotes) {
2815
- const remoteScope = getScope2(shareScope, remote.name, externalName);
2816
- for (const [packageName, file] of Object.entries(remote.entries)) {
2817
- const url = join(remoteScope, file);
2818
- addToScope(importMap, remoteScope, { [packageName]: url });
2819
- addIntegrity(importMap, url, remote.name, file);
2820
- }
2821
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
2822
- remote.cached = true;
2823
- }
2999
+ addScopeRow(importMap, chunkBundles, shareScope, externalName, version);
2824
3000
  continue;
2825
3001
  }
2826
- version.remotes[0].cached = true;
2827
3002
  let serving = version;
2828
3003
  if (version.action === "skip") {
2829
3004
  if (!override) {
2830
- override = findOverride(external, shareScope, externalName) ?? "NOT_AVAILABLE";
3005
+ override = findOverride(
3006
+ external,
3007
+ shareScope,
3008
+ externalName,
3009
+ (s) => Boolean(claimedElsewhere(externalName, s))
3010
+ ) ?? "NOT_AVAILABLE";
2831
3011
  }
2832
3012
  if (override !== "NOT_AVAILABLE") {
2833
3013
  serving = override;
2834
3014
  version.remotes[0].cached = false;
2835
3015
  }
2836
3016
  }
2837
- const provided = versionEntries(serving);
3017
+ const provided = versionClaims(serving, external.servingBuild);
2838
3018
  const mappings = [];
2839
3019
  for (const [packageName, provider] of provided) {
3020
+ const claim = claimedElsewhere(externalName, packageName);
3021
+ if (claim) {
3022
+ mappings.push({ packageName, ...claim });
3023
+ continue;
3024
+ }
2840
3025
  const file = provider.entries[packageName];
2841
3026
  mappings.push({
2842
3027
  packageName,
@@ -2849,56 +3034,60 @@ function createGenerateImportMap(config, ports) {
2849
3034
  registerBundleChunks(chunkBundles, provider.name, provider.bundle);
2850
3035
  }
2851
3036
  }
2852
- version.remotes.forEach((r) => {
2853
- const rScope = getScope2(shareScope, r.name, externalName);
2854
- if (index && r.servedBy) {
2855
- const files = index.get(r.servedBy);
2856
- const servedScope = getScope2(shareScope, r.servedBy, externalName);
2857
- for (const specifier of Object.keys(r.entries)) {
3037
+ version.remotes.forEach((consumer) => {
3038
+ const consumerScope = getScope2(shareScope, consumer.name, externalName);
3039
+ if (index && consumer.servedBy) {
3040
+ const files = index.get(consumer.servedBy);
3041
+ const servedScope = getScope2(shareScope, consumer.servedBy, externalName);
3042
+ for (const specifier of Object.keys(consumer.entries)) {
2858
3043
  const source = files?.get(specifier);
2859
3044
  if (!source) continue;
2860
3045
  const url = join(servedScope, source.file);
2861
- addToScope(importMap, rScope, { [specifier]: url });
2862
- addIntegrity(importMap, url, r.servedBy, source.file);
2863
- registerBundleChunks(chunkBundles, r.servedBy, source.bundle);
3046
+ addToScope(importMap, consumerScope, { [specifier]: url });
3047
+ addIntegrity(importMap, url, consumer.servedBy, source.file);
3048
+ registerBundleChunks(chunkBundles, consumer.servedBy, source.bundle);
2864
3049
  source.meta.cached = true;
2865
3050
  }
2866
3051
  return;
2867
3052
  }
2868
3053
  for (const m of mappings) {
2869
- addToScope(importMap, rScope, { [m.packageName]: m.url });
3054
+ addToScope(importMap, consumerScope, { [m.packageName]: m.url });
2870
3055
  }
2871
- for (const [packageName, file] of Object.entries(r.entries)) {
3056
+ for (const packageName of Object.keys(consumer.entries)) {
2872
3057
  if (provided.has(packageName)) continue;
2873
- if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
2874
- warnUncoveredEntrypoint(shareScope, externalName, r.name, packageName);
2875
- continue;
2876
- }
2877
- const url = join(rScope, file);
2878
- addToScope(importMap, rScope, { [packageName]: url });
2879
- addIntegrity(importMap, url, r.name, file);
2880
- r.cached = true;
3058
+ const claim = claimedElsewhere(externalName, packageName);
3059
+ if (claim) addToScope(importMap, consumerScope, { [packageName]: claim.url });
2881
3060
  }
2882
3061
  });
2883
3062
  for (const m of mappings) {
2884
3063
  addIntegrity(importMap, m.url, m.name, m.file);
2885
3064
  }
2886
3065
  }
2887
- if (cachedFingerprint(external) !== cachedBefore) {
2888
- ports.sharedExternalsRepo.addOrUpdate(externalName, external, shareScope);
3066
+ }
3067
+ }
3068
+ function addScopeRow(importMap, chunkBundles, shareScope, externalName, version) {
3069
+ for (const remote of version.remotes) {
3070
+ const remoteScope = getScope2(shareScope, remote.name, externalName);
3071
+ for (const [packageName, file] of Object.entries(remote.entries)) {
3072
+ const url = join(remoteScope, file);
3073
+ addToScope(importMap, remoteScope, { [packageName]: url });
3074
+ addIntegrity(importMap, url, remote.name, file);
2889
3075
  }
3076
+ registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3077
+ remote.cached = true;
2890
3078
  }
2891
3079
  }
2892
3080
  function cachedFingerprint(external) {
2893
3081
  return external.versions.map((v) => v.remotes.map((r) => r.cached ? 1 : 0).join("")).join("|");
2894
3082
  }
2895
- function findOverride(external, shareScope, externalName) {
3083
+ function findOverride(external, shareScope, externalName, servedElsewhere) {
2896
3084
  const sharedVersions = external.versions.filter((v) => v.action === "share");
2897
3085
  const scopedExternalName = `${shareScope}.${externalName}`;
2898
3086
  if (sharedVersions.length > 1) {
2899
3087
  handleMultipleSharedVersions(scopedExternalName);
2900
3088
  }
2901
- if (sharedVersions.length < 1) {
3089
+ const servedByClaims = () => external.versions.filter((v) => v.action === "skip").every((v) => shipsOnly(v, servedElsewhere));
3090
+ if (sharedVersions.length < 1 && !servedByClaims()) {
2902
3091
  if (config.strict.strictImportMap) {
2903
3092
  config.log.error(4, `[${shareScope}][${externalName}] shareScope has no override version.`);
2904
3093
  throw new NFError("Could not create ImportMap.");
@@ -2943,17 +3132,30 @@ function createGenerateImportMap(config, ports) {
2943
3132
  }
2944
3133
  return index;
2945
3134
  }
2946
- function collectServed(into, index, ctx, externalName, version) {
3135
+ function shareRowMappings(externals, shareScope) {
3136
+ const mappings = /* @__PURE__ */ new Map();
3137
+ for (const [specifier, { external, copy }] of claimTable(Object.entries(externals)).shareClaims) {
3138
+ const file = copy.entries[specifier];
3139
+ mappings.set(specifier, {
3140
+ url: join(getScope2(shareScope, copy.name, external), file),
3141
+ name: copy.name,
3142
+ file,
3143
+ external
3144
+ });
3145
+ }
3146
+ return mappings;
3147
+ }
3148
+ function collectServed(into, index, shareScope, externalName, version) {
2947
3149
  for (const consumer of version.remotes) {
2948
3150
  if (!consumer.servedBy) continue;
2949
3151
  const files = index.get(consumer.servedBy);
2950
- const scope = getScope2(ctx, consumer.name, externalName);
3152
+ const scope = getScope2(shareScope, consumer.name, externalName);
2951
3153
  for (const specifier of Object.keys(consumer.entries)) {
2952
3154
  const source = files?.get(specifier);
2953
3155
  if (!source) {
2954
3156
  config.log.warn(
2955
3157
  4,
2956
- `[${ctx}][${externalName}][${consumer.name}] '${consumer.servedBy}' does not serve '${specifier}'.`
3158
+ `[${shareScope}][${externalName}][${consumer.name}] '${consumer.servedBy}' does not serve '${specifier}'.`
2957
3159
  );
2958
3160
  continue;
2959
3161
  }
@@ -2971,9 +3173,11 @@ function createGenerateImportMap(config, ports) {
2971
3173
  source.meta.cached = true;
2972
3174
  }
2973
3175
  }
2974
- function addGlobalSharedExternals(importMap, chunkBundles) {
3176
+ function addGlobalShareScope(importMap, chunkBundles) {
2975
3177
  const sharedExternals = ports.sharedExternalsRepo.getFromScope();
2976
3178
  const index = buildServedIndex(sharedExternals);
3179
+ const { claims, duplicates } = claimTable(Object.entries(sharedExternals));
3180
+ const claimed = claimsByCopy(claims);
2977
3181
  const served = [];
2978
3182
  const cachedBefore = /* @__PURE__ */ new Map();
2979
3183
  for (const [externalName, external] of Object.entries(sharedExternals)) {
@@ -2984,27 +3188,14 @@ function createGenerateImportMap(config, ports) {
2984
3188
  }
2985
3189
  if (version.action === "skip") continue;
2986
3190
  if (version.action === "scope") {
2987
- for (const remote of version.remotes) {
2988
- const remoteScope = getScope2(GLOBAL_SCOPE, remote.name, externalName);
2989
- for (const [packageName, file] of Object.entries(remote.entries)) {
2990
- const url = join(remoteScope, file);
2991
- addToScope(importMap, remoteScope, { [packageName]: url });
2992
- addIntegrity(importMap, url, remote.name, file);
2993
- }
2994
- remote.cached = true;
2995
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
2996
- }
3191
+ addScopeRow(importMap, chunkBundles, GLOBAL_SCOPE, externalName, version);
2997
3192
  continue;
2998
3193
  }
2999
- if (importMap.imports[externalName]) {
3194
+ if (duplicates.has(version)) {
3000
3195
  notifyDuplicateGlobalExternal(externalName);
3001
3196
  continue;
3002
3197
  }
3003
- mergeVersionEntries(importMap, chunkBundles, externalName, version);
3004
- }
3005
- for (const version of external.versions) {
3006
- if (version.action !== "skip") continue;
3007
- selfFillUncovered(importMap, chunkBundles, externalName, version.remotes);
3198
+ publishClaims(importMap, chunkBundles, externalName, version, claimed);
3008
3199
  }
3009
3200
  }
3010
3201
  flushServed(importMap, chunkBundles, served);
@@ -3015,54 +3206,30 @@ function createGenerateImportMap(config, ports) {
3015
3206
  }
3016
3207
  return importMap;
3017
3208
  }
3018
- function mergeVersionEntries(importMap, chunkBundles, externalName, version) {
3019
- const scopes = /* @__PURE__ */ new Map();
3020
- for (const [packageName, remote] of versionEntries(version)) {
3021
- if (importMap.imports[packageName]) {
3022
- config.log.debug(
3023
- 4,
3024
- `[${GLOBAL_SCOPE}][${externalName}][${remote.name}] Entrypoint '${packageName}' is already mapped by another external, keeping '${importMap.imports[packageName]}'.`
3025
- );
3026
- continue;
3027
- }
3028
- let scope = scopes.get(remote.name);
3029
- if (scope === void 0) {
3030
- scopes.set(remote.name, scope = getScope2(GLOBAL_SCOPE, remote.name, externalName));
3031
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3032
- remote.cached = true;
3033
- }
3034
- const file = remote.entries[packageName];
3035
- const url = join(scope, file);
3036
- addToGlobal(importMap, { [packageName]: url });
3037
- addIntegrity(importMap, url, remote.name, file);
3209
+ function claimsByCopy(claims) {
3210
+ const claimed = /* @__PURE__ */ new Map();
3211
+ for (const [specifier, { copy }] of claims) {
3212
+ const specifiers = claimed.get(copy);
3213
+ if (specifiers) specifiers.push(specifier);
3214
+ else claimed.set(copy, [specifier]);
3038
3215
  }
3039
- }
3040
- function selfFillUncovered(importMap, chunkBundles, externalName, remotes) {
3041
- for (const remote of remotes) {
3042
- if (remote.servedBy) continue;
3043
- for (const [packageName, file] of Object.entries(remote.entries)) {
3044
- if (importMap.imports[packageName]) continue;
3045
- if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
3046
- warnUncoveredEntrypoint(GLOBAL_SCOPE, externalName, remote.name, packageName);
3047
- continue;
3048
- }
3049
- const scope = getScope2(GLOBAL_SCOPE, remote.name, externalName);
3216
+ return claimed;
3217
+ }
3218
+ function publishClaims(importMap, chunkBundles, externalName, version, claimed) {
3219
+ for (const copy of version.remotes) {
3220
+ const specifiers = claimed.get(copy);
3221
+ if (!specifiers) continue;
3222
+ const scope = getScope2(GLOBAL_SCOPE, copy.name, externalName);
3223
+ registerBundleChunks(chunkBundles, copy.name, copy.bundle);
3224
+ copy.cached = true;
3225
+ for (const specifier of specifiers) {
3226
+ const file = copy.entries[specifier];
3050
3227
  const url = join(scope, file);
3051
- addToGlobal(importMap, { [packageName]: url });
3052
- addIntegrity(importMap, url, remote.name, file);
3053
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3054
- remote.cached = true;
3228
+ addToGlobal(importMap, { [specifier]: url });
3229
+ addIntegrity(importMap, url, copy.name, file);
3055
3230
  }
3056
3231
  }
3057
3232
  }
3058
- function warnUncoveredEntrypoint(scope, externalName, remoteName, packageName) {
3059
- const msg = `[${scope}][${externalName}][${remoteName}] Entrypoint '${packageName}' is not covered by the shared version.`;
3060
- if (config.strict.strictEntryPointCoverage || config.strict.strictImportMap) {
3061
- config.log.error(4, msg);
3062
- throw new NFError("Could not create ImportMap.");
3063
- }
3064
- config.log.warn(4, msg);
3065
- }
3066
3233
  function notifyDuplicateGlobalExternal(externalName) {
3067
3234
  if (config.strict.strictImportMap) {
3068
3235
  config.log.error(4, `[${externalName}] Shared external has multiple shared versions.`);
@@ -3136,15 +3303,30 @@ function createGenerateImportMap(config, ports) {
3136
3303
  });
3137
3304
  }
3138
3305
  }
3306
+ function shipsOnly(version, served) {
3307
+ let only = true;
3308
+ forEachVersionEntry(version, void 0, (specifier) => {
3309
+ only &&= served(specifier);
3310
+ });
3311
+ return only;
3312
+ }
3139
3313
 
3140
3314
  // src/lib/core/2.app/steps/commit-changes.ts
3141
3315
  function createCommitChanges(config, ports) {
3142
- return (importMap, opts = {}) => Promise.resolve(importMap).then((map) => addToBrowser(map, opts)).then(persistRepositoryChanges);
3316
+ return (importMap, { markUndecided = false, ...browserOpts } = {}) => Promise.resolve(importMap).then((map) => addToBrowser(map, browserOpts)).then(() => {
3317
+ if (markUndecided) markUndecidedScopes();
3318
+ persistRepositoryChanges();
3319
+ });
3143
3320
  function addToBrowser(importMap, opts) {
3144
3321
  ports.browser.setImportMapFn(importMap, opts);
3145
3322
  config.log.debug(5, "Added import map to browser.", importMap);
3146
3323
  return importMap;
3147
3324
  }
3325
+ function markUndecidedScopes() {
3326
+ const repo = ports.sharedExternalsRepo;
3327
+ for (const shareScope of repo.uncommittedScopes())
3328
+ if (repo.scopeType(shareScope) !== "strict") repo.markUndecided(shareScope);
3329
+ }
3148
3330
  function persistRepositoryChanges() {
3149
3331
  ports.remoteInfoRepo.commit();
3150
3332
  ports.scopedExternalsRepo.commit();
@@ -3154,6 +3336,29 @@ function createCommitChanges(config, ports) {
3154
3336
  }
3155
3337
  }
3156
3338
 
3339
+ // src/lib/core/2.app/steps/rollback-changes.ts
3340
+ var createStateDump = (config, ports) => (msg) => config.log.debug(0, msg, {
3341
+ remotes: { ...ports.remoteInfoRepo.getAll() },
3342
+ "shared-externals": ports.sharedExternalsRepo.getScopes({ includeGlobal: true }).reduce(
3343
+ (acc, scope) => ({ ...acc, [scope]: ports.sharedExternalsRepo.getFromScope(scope) }),
3344
+ {}
3345
+ ),
3346
+ "scoped-externals": ports.scopedExternalsRepo.getAll()
3347
+ });
3348
+ function createRollbackChanges(config, ports) {
3349
+ const stateDump = createStateDump(config, ports);
3350
+ return (label) => Promise.resolve().then(() => {
3351
+ try {
3352
+ stateDump(`${label} STATE DUMP`);
3353
+ } finally {
3354
+ ports.remoteInfoRepo.rollback();
3355
+ ports.scopedExternalsRepo.rollback();
3356
+ ports.sharedExternalsRepo.rollback();
3357
+ ports.sharedChunksRepo.rollback();
3358
+ }
3359
+ });
3360
+ }
3361
+
3157
3362
  // src/lib/core/2.app/steps/expose-module-loader.ts
3158
3363
  function createExposeModuleLoader(config, ports) {
3159
3364
  const loadRemoteModule = (remoteName, exposedModule) => {
@@ -3228,10 +3433,16 @@ var normalizeRemoteRef = (remote) => {
3228
3433
  function createGetRemoteEntry(config, ports) {
3229
3434
  return async (remoteEntryUrl, remote) => {
3230
3435
  const { name: remoteName, integrity } = normalizeRemoteRef(remote);
3231
- if (!!remoteName && shouldSkipCachedRemote(remoteEntryUrl, remoteName)) {
3232
- config.log.debug(7, `Found remote '${remoteName}' in storage, omitting fetch.`);
3436
+ const knownAtUrl = findCachedRemoteAt(remoteEntryUrl);
3437
+ if (knownAtUrl) {
3438
+ config.log.debug(
3439
+ 7,
3440
+ `Found remote '${knownAtUrl}' at '${remoteEntryUrl}' in storage, omitting fetch.`
3441
+ );
3233
3442
  return Optional.empty();
3234
3443
  }
3444
+ if (!!remoteName && ports.remoteInfoRepo.contains(remoteName))
3445
+ return warnInitialized(remoteName, remoteEntryUrl);
3235
3446
  try {
3236
3447
  const remoteEntry = integrity ? await ports.remoteEntryProvider.provide(remoteEntryUrl, { integrity }) : await ports.remoteEntryProvider.provide(remoteEntryUrl);
3237
3448
  config.log.debug(
@@ -3246,10 +3457,8 @@ function createGetRemoteEntry(config, ports) {
3246
3457
  }
3247
3458
  config.log.warn(7, errorMsg + " Omitting expected name.");
3248
3459
  }
3249
- if (ports.remoteInfoRepo.contains(remoteEntry.name)) {
3250
- remoteEntry.override = true;
3251
- config.log.debug(7, `Overriding existing remote '${remoteName}' with '${remoteEntryUrl}'.`);
3252
- }
3460
+ if (ports.remoteInfoRepo.contains(remoteEntry.name))
3461
+ return warnInitialized(remoteEntry.name, remoteEntryUrl);
3253
3462
  return Optional.of(checkForSSE(remoteEntry));
3254
3463
  } catch (error) {
3255
3464
  config.log.error(
@@ -3262,10 +3471,18 @@ function createGetRemoteEntry(config, ports) {
3262
3471
  );
3263
3472
  }
3264
3473
  };
3265
- function shouldSkipCachedRemote(remoteEntryUrl, remoteName) {
3266
- return ports.remoteInfoRepo.tryGet(remoteName).map(
3267
- (cachedRemoteInfo) => config.profile.overrideCachedRemotes !== "always" || !config.profile.overrideCachedRemotesIfURLMatches && remoteEntryUrl === join(cachedRemoteInfo.scopeUrl, "remoteEntry.json")
3268
- ).orElse(false);
3474
+ function warnInitialized(name, remoteEntryUrl) {
3475
+ const initializedFrom = ports.remoteInfoRepo.tryGet(name).map((info) => join(info.scopeUrl, "remoteEntry.json")).get();
3476
+ config.log.warn(
3477
+ 7,
3478
+ `[${name}] Already initialized from '${initializedFrom}', ignoring '${remoteEntryUrl}'.`
3479
+ );
3480
+ return Optional.empty();
3481
+ }
3482
+ function findCachedRemoteAt(remoteEntryUrl) {
3483
+ return Object.entries(ports.remoteInfoRepo.getAll()).find(
3484
+ ([, info]) => join(info.scopeUrl, "remoteEntry.json") === remoteEntryUrl
3485
+ )?.[0];
3269
3486
  }
3270
3487
  function checkForSSE(entry) {
3271
3488
  if (config.sse) {
@@ -3285,92 +3502,170 @@ function createGetRemoteEntry(config, ports) {
3285
3502
  // src/lib/core/2.app/steps/update-cache.ts
3286
3503
  function createUpdateCache(config, ports) {
3287
3504
  const storeRemoteEntry = createStoreRemoteEntry(config, ports, 8);
3288
- const removeCachedRemoteEntries = createRemoveCachedRemoteEntries(ports);
3505
+ const accepts = createAcceptsTag(
3506
+ (tag, range) => ports.versionCheck.isCompatible(tag, range),
3507
+ (a, b) => ports.versionCheck.compare(a, b)
3508
+ );
3289
3509
  return (remoteEntry) => {
3290
3510
  try {
3511
+ const pending = [];
3512
+ storeRemoteEntry(remoteEntry, (_, external, ctx) => pending.push({ external, ctx }));
3513
+ const decisions = decidePackages(remoteEntry, pending);
3291
3514
  const actions = {};
3292
- if (remoteEntry?.override) removeCachedRemoteEntries(/* @__PURE__ */ new Set([remoteEntry.name]));
3293
- storeRemoteEntry(remoteEntry, (entry, external, ctx) => {
3294
- const { action, provided, sameVersion } = resolveSharedExternal(entry, external, ctx);
3295
- actions[external.packageName] = { action };
3296
- if (action === "skip" && provided) {
3297
- actions[external.packageName].covered = Array.from(provided.keys());
3298
- if (sameVersion) actions[external.packageName].sameVersion = true;
3299
- if (external.shareScope) {
3300
- actions[external.packageName].override = resolveOverrideEntries(
3301
- entry,
3302
- external,
3303
- provided
3304
- );
3305
- }
3306
- }
3307
- });
3515
+ for (const item of pending)
3516
+ actions[item.external.packageName] = applyDecision(remoteEntry, item, decisions.get(item));
3308
3517
  return Promise.resolve({ entry: remoteEntry, actions });
3309
3518
  } catch (error) {
3310
3519
  return Promise.reject(error);
3311
3520
  }
3312
3521
  };
3313
- function resolveSharedExternal(remoteEntry, sharedInfo, {
3314
- tag,
3315
- remote,
3316
- cached,
3317
- scopeType,
3318
- assertSameVersionCompatibility,
3319
- commit
3320
- }) {
3321
- let action = scopeType === "strict" ? "share" : "skip";
3322
- const sharedVersion = cached.versions.find((c) => c.action === "share");
3323
- const isCompatible = !sharedVersion || ports.versionCheck.isCompatible(sharedVersion.tag, remote.requiredVersion);
3324
- if (action === "skip" && !isCompatible && remote.strictVersion) {
3325
- action = "scope";
3326
- const errorMsg = `[${sharedInfo.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}] ${sharedInfo.packageName}@${sharedInfo.version} Is not compatible with existing ${sharedInfo.packageName}@${sharedVersion.tag} requiredRange '${sharedVersion.remotes[0]?.requiredVersion}'`;
3327
- if (config.strict.strictExternalCompatibility) {
3328
- config.log.error(8, errorMsg);
3329
- throw new NFError(`Could not process remote '${remoteEntry.name}'`);
3330
- }
3331
- config.log.warn(8, errorMsg);
3522
+ function decidePackages(remoteEntry, pending) {
3523
+ const decisions = /* @__PURE__ */ new Map();
3524
+ const viewOf = loadViewOf(remoteEntry, pending);
3525
+ for (const group of pendingPerPackage(pending)) {
3526
+ const decision = group[0].ctx.scopeType === "strict" ? { action: "share" } : decidePackage(remoteEntry, group, viewOf(group[0].external.shareScope));
3527
+ for (const item of group) decisions.set(item, decision);
3332
3528
  }
3333
- const provided = sharedVersion ? sharedInfo.shareScope ? versionEntries(sharedVersion) : committedEntries(sharedVersion) : void 0;
3334
- const sameVersion = sharedVersion?.tag === tag;
3335
- if (action === "skip" && provided && !sameVersion) {
3336
- const uncovered = uncoveredEntrypoints(remote, provided);
3337
- if (uncovered.length > 0) {
3338
- const msg = `[${sharedInfo.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}][${sharedInfo.packageName}] Entrypoints not covered by the shared version: ${uncovered.join(", ")}.`;
3339
- if (config.strict.strictEntryPointCoverage) {
3340
- config.log.error(8, msg);
3341
- throw new NFError(`Could not process remote '${remoteEntry.name}'`);
3342
- }
3343
- if (config.profile.scopeUncoveredEntrypoints) {
3344
- config.log.debug(8, msg);
3345
- action = "scope";
3346
- }
3529
+ scopeOpenedPackages(decisions);
3530
+ return decisions;
3531
+ }
3532
+ function pendingPerPackage(pending) {
3533
+ const packages = /* @__PURE__ */ new Map();
3534
+ for (const item of pending) {
3535
+ const { shareScope, packageName } = item.external;
3536
+ const key = `${shareScope ?? GLOBAL_SCOPE}\0${packageOf(packageName)}`;
3537
+ const group = packages.get(key);
3538
+ if (group) group.push(item);
3539
+ else packages.set(key, [item]);
3540
+ }
3541
+ return [...packages.values()];
3542
+ }
3543
+ function loadViewOf(remoteEntry, pending) {
3544
+ const views = /* @__PURE__ */ new Map();
3545
+ return (shareScope) => {
3546
+ const scope = shareScope ?? GLOBAL_SCOPE;
3547
+ let view = views.get(scope);
3548
+ if (!view) {
3549
+ const record = ports.sharedExternalsRepo.getFromScope(shareScope);
3550
+ const claims = committedClaims(Object.entries(record), {
3551
+ shareScope: scope,
3552
+ without: remoteEntry.name
3553
+ });
3554
+ const claimsIn = claimsByPackage(claims);
3555
+ let pooled;
3556
+ view = {
3557
+ claims,
3558
+ claimsOf: (pkg) => claimsIn.get(pkg) ?? [],
3559
+ inPool: (pkg) => (pooled ??= pooledPackagesWith(record, copiesIn(pending, scope))).has(pkg)
3560
+ };
3561
+ views.set(scope, view);
3347
3562
  }
3563
+ return view;
3564
+ };
3565
+ }
3566
+ function copiesIn(pending, scope) {
3567
+ return pending.filter(({ external }) => (external.shareScope ?? GLOBAL_SCOPE) === scope).map(({ external, ctx }) => ({ name: external.packageName, pool: ctx.remote.pool }));
3568
+ }
3569
+ function scopeOpenedPackages(decisions) {
3570
+ if (![...decisions.values()].some((d) => d.action === "scope" && d.partlyServed)) return;
3571
+ for (const [item, decision] of decisions)
3572
+ if (decision.action === "share" && item.ctx.scopeType !== "strict")
3573
+ decisions.set(item, { action: "scope", partlyServed: false });
3574
+ }
3575
+ function decidePackage(remoteEntry, group, view) {
3576
+ const pkg = packageOf(group[0].external.packageName);
3577
+ const claims = view.claimsOf(pkg);
3578
+ if (claims.length === 0) return { action: "share" };
3579
+ const specifiers = group.flatMap(({ external }) => Object.keys(external.entries));
3580
+ const builds = specifiers.every((specifier) => view.claims.has(specifier)) ? new Set(specifiers.map((specifier) => view.claims.get(specifier).copy.name)) : void 0;
3581
+ const rejections = group.flatMap((item) => rejectionsOf(item, view.claims));
3582
+ if (rejections.length > 0) {
3583
+ const fromTwoBuilds = builds !== void 0 && builds.size > 1;
3584
+ for (const rejection of refused(group, rejections, fromTwoBuilds, claims, view.inPool(pkg)))
3585
+ reportIncompatible(remoteEntry, rejection);
3586
+ return { action: "scope", partlyServed: false };
3348
3587
  }
3349
- const matchingVersion = findVersionForTag(cached.versions, tag);
3350
- if (action === "scope") {
3351
- remote.cached = true;
3352
- const scoped = cached.versions.find((v) => v.tag === tag && v.action === "scope");
3353
- if (scoped) scoped.remotes.push(remote);
3354
- else cached.versions.push({ tag, action, host: false, remotes: [remote] });
3355
- } else if (matchingVersion) {
3356
- assertSameVersionCompatibility(matchingVersion);
3357
- addRemoteToVersion(matchingVersion, remote);
3358
- } else {
3359
- if (!sharedVersion) action = "share";
3360
- remote.cached = action !== "skip";
3361
- cached.versions.push({ tag, action, host: false, remotes: [remote] });
3588
+ if (!builds || builds.size > 1) return { action: "scope", partlyServed: true };
3589
+ return { action: "skip", files: servingFiles(claims, builds) };
3590
+ }
3591
+ function servingFiles(claims, builds) {
3592
+ const files = /* @__PURE__ */ new Map();
3593
+ for (const [specifier, { copy }] of claims)
3594
+ if (builds.has(copy.name)) files.set(specifier, copy);
3595
+ return files;
3596
+ }
3597
+ function rejectionsOf(item, claims) {
3598
+ const { external, ctx } = item;
3599
+ if (!ctx.remote.strictVersion) return [];
3600
+ return Object.keys(external.entries).flatMap((specifier) => {
3601
+ const claim = claims.get(specifier);
3602
+ return claim && !accepts(claim.tag, ctx.tag, ctx.remote.requiredVersion) ? [{ item, specifier, claim }] : [];
3603
+ });
3604
+ }
3605
+ function refused(group, rejections, fromTwoBuilds, claims, pooled) {
3606
+ const copies = group.map(({ ctx }) => ({ tag: ctx.tag, copy: ctx.remote }));
3607
+ const splits = fromTwoBuilds || loadSplitsPackage(copies, claims, accepts);
3608
+ const first = /* @__PURE__ */ new Map();
3609
+ for (const rejection of rejections)
3610
+ if (!first.has(rejection.item) && (pooled || !splits || electionKeeps(rejection)))
3611
+ first.set(rejection.item, rejection);
3612
+ return [...first.values()];
3613
+ }
3614
+ function electionKeeps({ item: { external, ctx }, claim }) {
3615
+ if (claim.external !== external.packageName) return false;
3616
+ const versions = ports.sharedExternalsRepo.tryGet(claim.external, external.shareScope).get()?.versions ?? [];
3617
+ const strict = [
3618
+ { tag: ctx.tag, copy: ctx.remote },
3619
+ ...versions.flatMap(
3620
+ (v) => v.remotes.filter((r) => r.strictVersion).map((copy) => ({ tag: v.tag, copy }))
3621
+ )
3622
+ ];
3623
+ const tags = [ctx.tag, ...versions.map((v) => v.tag)];
3624
+ if (!tags.some((tag) => strict.every((s) => accepts(tag, s.tag, s.copy.requiredVersion))))
3625
+ return true;
3626
+ const host = versions.find((v) => v.host);
3627
+ if (host) return host.tag === claim.tag;
3628
+ if (!config.profile.latestSharedExternal) return false;
3629
+ return tags.every((tag) => ports.versionCheck.compare(claim.tag, tag) >= 0);
3630
+ }
3631
+ function reportIncompatible(remoteEntry, { item: { external, ctx }, specifier, claim }) {
3632
+ const errorMsg = `[${external.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}] ${specifier}@${ctx.tag} Is not compatible with existing ${specifier}@${claim.tag} requiredRange '${ctx.remote.requiredVersion}'`;
3633
+ if (config.strict.strictExternalCompatibility) {
3634
+ config.log.error(8, errorMsg);
3635
+ throw new NFError(`Could not process remote '${remoteEntry.name}'`);
3362
3636
  }
3363
- commit();
3364
- return { action, provided, sameVersion };
3637
+ config.log.warn(8, errorMsg);
3638
+ }
3639
+ function applyDecision(remoteEntry, { external, ctx }, decision) {
3640
+ storeCopy(ctx, decision.action);
3641
+ if (decision.action !== "skip") return { action: decision.action };
3642
+ const covered = Object.keys(external.entries);
3643
+ if (!external.shareScope) return { action: "skip", covered };
3644
+ return {
3645
+ action: "skip",
3646
+ covered,
3647
+ override: overrideFrom(remoteEntry, external, decision.files)
3648
+ };
3365
3649
  }
3366
- function resolveOverrideEntries(remoteEntry, external, provided) {
3650
+ function storeCopy(ctx, action) {
3651
+ const { tag, remote } = ctx;
3652
+ const cached = ctx.readRecord();
3653
+ const shared = cached.versions.find((v) => v.tag === tag && v.action === "share");
3654
+ if (shared && action !== "scope") ctx.assertSameVersionCompatibility(shared);
3655
+ remote.cached = action !== "skip";
3656
+ const row = action === "share" ? shared : cached.versions.find((v) => v.tag === tag && v.action === action);
3657
+ const loaderServes = action === "share" && ctx.scopeType !== "strict";
3658
+ if (!row) cached.versions.push({ tag, action, host: false, remotes: [remote] });
3659
+ else if (loaderServes) addRemoteToVersion(row, remote);
3660
+ else row.remotes.push(remote);
3661
+ if (loaderServes) cached.servingBuild = remote.name;
3662
+ ctx.write();
3663
+ }
3664
+ function overrideFrom(remoteEntry, external, files) {
3367
3665
  return Object.fromEntries(
3368
- Array.from(provided, ([packageName, provider]) => [
3369
- packageName,
3370
- join(
3371
- providerScopeUrl(remoteEntry, external, provider.name),
3372
- provider.entries[packageName]
3373
- )
3666
+ Array.from(files, ([specifier, copy]) => [
3667
+ specifier,
3668
+ join(providerScopeUrl(remoteEntry, external, copy.name), copy.entries[specifier])
3374
3669
  ])
3375
3670
  );
3376
3671
  }
@@ -3385,181 +3680,298 @@ function createUpdateCache(config, ports) {
3385
3680
  }
3386
3681
  }
3387
3682
 
3388
- // src/lib/core/2.app/steps/pooling/pool-dynamic-externals.ts
3389
- function createPoolDynamicExternals(config, ports) {
3390
- return ({ entry, actions }) => {
3391
- const declared = /* @__PURE__ */ new Map();
3392
- for (const external of entry.shared ?? []) {
3393
- const name = external.packageName;
3394
- if (!external.singleton || !actions[name]) continue;
3395
- if (external.shareScope === STRICT_SCOPE) continue;
3396
- const shareScope = external.shareScope ?? GLOBAL_SCOPE;
3397
- let names = declared.get(shareScope);
3398
- if (!names) declared.set(shareScope, names = /* @__PURE__ */ new Set());
3399
- names.add(name);
3400
- }
3401
- if (declared.size === 0) return Promise.resolve({ entry, actions });
3402
- let verdicts = /* @__PURE__ */ new Map();
3403
- const scope = (name, cause) => {
3404
- actions[name].action = "scope";
3405
- delete actions[name].override;
3406
- verdicts.set(name, { cause });
3407
- };
3408
- for (const [shareScope, names] of declared) {
3409
- if (!ports.sharedExternalsRepo.hasPoolState(shareScope)) continue;
3410
- const committed = ports.sharedExternalsRepo.getFromScope(shareScope);
3411
- const pools = buildPools(committed);
3412
- verdicts = /* @__PURE__ */ new Map();
3413
- for (const pool of pools.values()) {
3414
- const mine = pool.filter((member) => names.has(member.name));
3415
- if (mine.length === 0) continue;
3416
- if (mine.some((member) => actions[member.name].action === "scope")) {
3417
- mine.forEach((member) => scope(member.name, "incompatible"));
3418
- continue;
3419
- }
3420
- const asked = gateViews(entry.name, pool);
3421
- const anchor = anchorFor(entry.name, asked);
3422
- if (anchor === "witnessed") continue;
3423
- if (anchor === void 0) {
3424
- const reason = explainDynamic(entry.name, asked);
3425
- const closest = reason.closest ? `closest is '${reason.closest}'` : "no committed build serves any of it";
3426
- config.log.warn(
3427
- 8,
3428
- `[${shareScope}] '${entry.name}' serves its own family: no committed build offers every entrypoint it imports at a version it accepts \u2014 '${reason.gap}' is the gap, ${closest}. All ${mine.length} members it imports are scoped for it.`
3683
+ // src/lib/core/1.domain/pooling/gate.ts
3684
+ function committedView(members, shipped, recordOrder, { shareScope, without }) {
3685
+ const global = /* @__PURE__ */ new Map();
3686
+ const claims = committedClaims(inRecordOrder(members, recordOrder), { shareScope, without });
3687
+ for (const [specifier, { tag, copy }] of claims)
3688
+ global.set(specifier, { tag, remote: copy.name, file: copy.entries[specifier] });
3689
+ const builds = /* @__PURE__ */ new Map();
3690
+ for (const [owner, copies] of shipped)
3691
+ if (owner !== without) builds.set(owner, buildOf(owner, copies));
3692
+ const fileOf = subpoolFilesOf(members);
3693
+ return { builds, global, runsOffMap: runsOffMapOf(members, global, builds, fileOf) };
3694
+ }
3695
+ function subpoolFilesOf(members) {
3696
+ const subpoolOwners = /* @__PURE__ */ new Set();
3697
+ for (const member of members)
3698
+ for (const version of member.external.versions)
3699
+ for (const { servedBy } of version.remotes) if (servedBy) subpoolOwners.add(servedBy);
3700
+ const fileOf = /* @__PURE__ */ new Map();
3701
+ for (const member of members)
3702
+ for (const version of member.external.versions)
3703
+ for (const { name, entries } of version.remotes) {
3704
+ if (!subpoolOwners.has(name)) continue;
3705
+ let files = fileOf.get(name);
3706
+ if (!files) fileOf.set(name, files = /* @__PURE__ */ new Map());
3707
+ for (const specifier in entries)
3708
+ if (!files.has(specifier)) files.set(specifier, entries[specifier]);
3709
+ }
3710
+ return fileOf;
3711
+ }
3712
+ function runsOffMapOf(members, global, builds, fileOf) {
3713
+ const runsOffMap2 = /* @__PURE__ */ new Map();
3714
+ for (const member of members)
3715
+ for (const version of member.external.versions)
3716
+ for (const meta of version.remotes) {
3717
+ const { servedBy } = meta;
3718
+ if (version.action === "share" && !servedBy) continue;
3719
+ let runs = runsOffMap2.get(meta.name);
3720
+ if (!runs) runsOffMap2.set(meta.name, runs = /* @__PURE__ */ new Map());
3721
+ for (const specifier in meta.entries) {
3722
+ if (version.action === "skip" && !servedBy && global.has(specifier)) continue;
3723
+ const tag = (servedBy ? builds.get(servedBy)?.tags.pinnedTag(specifier) : void 0) ?? version.tag;
3724
+ const file = servedBy && fileOf.get(servedBy)?.get(specifier);
3725
+ runs.set(
3726
+ specifier,
3727
+ file ? { tag, remote: servedBy, file } : { tag, remote: meta.name, file: meta.entries[specifier] }
3429
3728
  );
3430
- mine.forEach((member) => scope(member.name, "uncovered"));
3431
- continue;
3432
3729
  }
3433
- for (const name of redirect(entry.name, anchor, pool, asked.view, actions, shareScope))
3434
- verdicts.set(name, { servedBy: anchor });
3435
3730
  }
3436
- const written = {};
3437
- for (const [name, verdict] of verdicts) {
3438
- written[name] = recordVerdict(committed[name], entry.name, verdict);
3439
- ports.sharedExternalsRepo.addOrUpdate(name, written[name], shareScope);
3731
+ return runsOffMap2;
3732
+ }
3733
+ function judgeRemote(copies, view, accepts) {
3734
+ const { rejected, missing } = scan(copies, view, accepts);
3735
+ if (rejected) return { cause: "incompatible", ...rejected };
3736
+ const globalTags = new SpecifierTags([...view.global].map(([s, { tag }]) => [s, tag]));
3737
+ const binding = offMapBinding(copies, view, globalTags);
3738
+ if (binding) return { cause: "uncovered", ...binding };
3739
+ if (agrees(copies, globalTags)) return "global";
3740
+ if (missing !== void 0) return { cause: "uncovered", specifier: missing };
3741
+ if (unwitnessedGap(copies, globalTags, view.builds.values()) === void 0) return "global";
3742
+ return { cause: "uncovered", unshipped: true };
3743
+ }
3744
+ function coverFromMap(remote, mine, view, actions, policy) {
3745
+ const files = [];
3746
+ for (const member of mine) {
3747
+ const action = actions[member.name];
3748
+ const copy = member.external.versions.flatMap((v) => v.remotes).find((r) => r.name === remote);
3749
+ if (!copy) continue;
3750
+ const mapped = mapFiles(copy, view, policy, action);
3751
+ if ("unmapped" in mapped) return mapped;
3752
+ files.push({ member, action, copy, ...mapped });
3753
+ }
3754
+ const reaches = reachesOf(remote, files, view);
3755
+ const bound = boundByFile(reaches, view);
3756
+ if (bound !== void 0) return { bound };
3757
+ const servedPackages = new Set([...view.global.keys()].map(packageOf));
3758
+ const gap = files.map((file) => gapOf(file, servedPackages)).find((specifier) => specifier !== void 0);
3759
+ if (gap !== void 0) return { gap };
3760
+ return { covers: files.filter(covering).map(coverOf) };
3761
+ }
3762
+ function gapOf({ copy, covered }, servedPackages) {
3763
+ const entrypoints = Object.keys(copy.entries);
3764
+ const missing = entrypoints.filter((specifier) => !covered.has(specifier));
3765
+ if (missing.length < entrypoints.length) return missing[0];
3766
+ return missing.find((specifier) => servedPackages.has(packageOf(specifier)));
3767
+ }
3768
+ function covering({ action, copy, covered }) {
3769
+ if (action.action === "skip") return true;
3770
+ const entrypoints = Object.keys(copy.entries);
3771
+ return entrypoints.length > 0 && entrypoints.every((specifier) => covered.has(specifier));
3772
+ }
3773
+ function coverOf({ member, action, copy, covered, override }) {
3774
+ const toSkip = action.action !== "skip" || covered.size > action.covered.length && member.external.versions.some((v) => v.action === "share" && v.remotes.includes(copy));
3775
+ return { name: member.name, toSkip, override };
3776
+ }
3777
+ function reachesOf(remote, files, view) {
3778
+ const reaches = /* @__PURE__ */ new Map();
3779
+ const fromMap = (specifier) => {
3780
+ const served = view.global.get(specifier);
3781
+ return served && { remote: served.remote, file: served.file };
3782
+ };
3783
+ for (const file of files) {
3784
+ const takesMap = covering(file);
3785
+ for (const [specifier, own] of Object.entries(file.copy.entries)) {
3786
+ if (!takesMap || !file.covered.has(specifier)) reaches.set(specifier, { remote, file: own });
3787
+ else {
3788
+ const reach = fromMap(specifier);
3789
+ if (reach) reaches.set(specifier, reach);
3440
3790
  }
3441
- syncPoolNames({ ...committed, ...written }, pools, ports.sharedExternalsRepo, shareScope);
3442
3791
  }
3443
- return Promise.resolve({ entry, actions });
3444
- };
3445
- function gateViews(remote, pool) {
3446
- return {
3447
- pool,
3448
- view: committedView(pool),
3449
- wants: consumedMembers(pool).get(remote) ?? [],
3450
- specifiers: consumedSpecifiers(pool).get(remote) ?? /* @__PURE__ */ new Set(),
3451
- acceptance: lazy(() => acceptanceTable(pool, ports.versionCheck.isCompatible))
3452
- };
3453
3792
  }
3454
- function anchorFor(remote, { pool, view, wants, specifiers, acceptance }) {
3455
- const shared = /* @__PURE__ */ new Map();
3456
- for (const [specifier, source] of view.global) shared.set(specifier, source.tag);
3457
- if (isWitnessed(specifiers, shared, view.builds)) return "witnessed";
3458
- const basis = basisPerMember(pool);
3459
- for (const build of candidateOrder(pool, view)) {
3460
- if (build === remote) continue;
3461
- const candidate = view.builds.get(build);
3462
- if (!servesItsOwnFamily(build, pool, basis)) continue;
3463
- if (!covers(candidate.coverage, specifiers)) continue;
3464
- if (!acceptsAll(acceptance(), candidate.instance, remote, wants)) continue;
3465
- return build;
3793
+ return reaches;
3794
+ }
3795
+ function boundByFile(reaches, view) {
3796
+ const servers = /* @__PURE__ */ new Set();
3797
+ for (const { remote } of reaches.values()) if (view.builds.has(remote)) servers.add(remote);
3798
+ const firstRun = /* @__PURE__ */ new Map();
3799
+ for (const build of servers)
3800
+ for (const specifier of view.builds.get(build).tags.keys()) {
3801
+ const served = view.runsOffMap.get(build)?.get(specifier) ?? view.global.get(specifier);
3802
+ if (served === void 0) continue;
3803
+ const reached = reaches.get(specifier) ?? firstRun.get(specifier);
3804
+ if (reached === void 0) {
3805
+ firstRun.set(specifier, served);
3806
+ if (view.builds.has(served.remote)) servers.add(served.remote);
3807
+ } else if (reached.remote !== served.remote || reached.file !== served.file)
3808
+ return { owner: build, specifier, tag: served.tag };
3466
3809
  }
3467
- return void 0;
3468
- }
3469
- function candidateOrder(pool, view) {
3470
- const serving = /* @__PURE__ */ new Set();
3471
- for (const source of view.global.values()) serving.add(source.remote);
3472
- const hosts = hostRemotes(pool);
3473
- const rank = (build) => serving.has(build) ? 0 : hosts.has(build) ? 1 : 2;
3474
- return [...view.builds.keys()].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
3475
- }
3476
- function servesItsOwnFamily(build, pool, basis) {
3477
- let ships2 = 0;
3478
- let winsAll = true;
3479
- let allScoped = true;
3480
- for (const member of pool) {
3481
- for (const version of member.external.versions) {
3482
- const meta = version.remotes.find((r) => r.name === build);
3483
- if (!meta) continue;
3484
- if (meta.servedBy !== void 0) return false;
3485
- ships2++;
3486
- if (version.action !== "scope") allScoped = false;
3487
- if (basis.get(member.name) !== build) winsAll = false;
3488
- }
3810
+ return void 0;
3811
+ }
3812
+ function mapFiles(copy, view, policy, action) {
3813
+ const covered = new Set(action.covered);
3814
+ const override = { ...action.override };
3815
+ for (const specifier in copy.entries) {
3816
+ const source = view.global.get(specifier);
3817
+ if (!source) continue;
3818
+ if (policy.shareScope !== GLOBAL_SCOPE) {
3819
+ const scopeUrl = policy.scopeUrlOf(source.remote);
3820
+ if (!scopeUrl) return { unmapped: source.remote };
3821
+ override[specifier] = join(scopeUrl, source.file);
3489
3822
  }
3490
- return ships2 > 0 && (winsAll || allScoped);
3491
- }
3492
- function explainDynamic(remote, { view, wants, specifiers, acceptance }) {
3493
- return explainSelfServe(remote, wants, specifiers, {
3494
- builds: view.builds,
3495
- acceptance: acceptance()
3496
- });
3823
+ covered.add(specifier);
3497
3824
  }
3498
- function redirect(remote, anchor, pool, view, actions, shareScope) {
3499
- const files = view.builds.get(anchor).coverage;
3500
- const scopeUrl = ports.remoteInfoRepo.tryGet(anchor).get()?.scopeUrl;
3501
- if (!scopeUrl) {
3502
- config.log.warn(
3503
- 8,
3504
- `[${shareScope}][${remote}] '${anchor}' is not in the cache, so its files cannot be mapped.`
3505
- );
3506
- return [];
3825
+ return { covered, override };
3826
+ }
3827
+ function scan(copies, view, accepts) {
3828
+ let rejected;
3829
+ let missing;
3830
+ for (const copy of copies)
3831
+ for (const s of copy.specifiers) {
3832
+ const global = view.global.get(s);
3833
+ if (global === void 0) missing ??= s;
3834
+ else if (!accepts(global.tag, copy.tag, copy.requiredVersion))
3835
+ rejected ??= { specifier: s, tag: global.tag };
3507
3836
  }
3508
- const basis = basisPerMember(pool);
3509
- const served = [];
3510
- for (const member of pool) {
3511
- const action = actions[member.name];
3512
- if (!action || action.action !== "skip") continue;
3513
- const own = member.external.versions.flatMap((v) => v.remotes).find((r) => r.name === remote);
3514
- if (!own) continue;
3515
- const specifiers = Object.keys(own.entries);
3516
- if (specifiers.length === 0) continue;
3517
- const override = {};
3518
- for (const specifier of specifiers) {
3519
- if (view.global.get(specifier)?.remote === anchor) continue;
3520
- const file = files.get(specifier);
3521
- if (file) override[specifier] = join(scopeUrl, file);
3522
- }
3523
- action.covered = specifiers;
3524
- if (Object.keys(override).length > 0) action.override = override;
3525
- if (basis.get(member.name) !== anchor) served.push(member.name);
3837
+ return { rejected, missing };
3838
+ }
3839
+ function offMapBinding(copies, view, globalTags) {
3840
+ const own = new SpecifierTags(copies.flatMap((c) => c.specifiers.map((s) => [s, c.tag])));
3841
+ const servers = /* @__PURE__ */ new Set();
3842
+ for (const copy of copies)
3843
+ for (const specifier of copy.specifiers) {
3844
+ const build = view.global.get(specifier)?.remote;
3845
+ if (build !== void 0) servers.add(build);
3526
3846
  }
3527
- return served;
3528
- }
3529
- function recordVerdict(external, remote, verdict) {
3530
- if ("servedBy" in verdict) {
3531
- return {
3532
- ...external,
3533
- versions: external.versions.map((v) => ({
3534
- ...v,
3535
- remotes: v.remotes.map(
3536
- (r) => r.name === remote ? { ...r, servedBy: verdict.servedBy } : r
3537
- )
3538
- }))
3539
- };
3847
+ const firstRun = new SpecifierTags();
3848
+ for (const build of servers)
3849
+ for (const [specifier, { tag }] of view.runsOffMap.get(build) ?? []) {
3850
+ const reached = globalTags.pinnedTag(specifier) ?? own.pinnedTag(specifier) ?? firstRun.pinnedTag(specifier);
3851
+ if (reached === void 0) firstRun.set(specifier, tag);
3852
+ else if (reached !== tag) return { owner: build, specifier, tag };
3540
3853
  }
3541
- const moved = [];
3542
- const versions = external.versions.map((v) => {
3543
- const own = v.remotes.find((r) => r.name === remote);
3544
- if (!own) return v;
3545
- const { servedBy: _anchor, ...rest } = own;
3546
- const meta = { ...rest, cached: true, poolCause: verdict.cause };
3547
- if (v.action === "scope")
3548
- return { ...v, remotes: v.remotes.map((r) => r === own ? meta : r) };
3549
- moved.push({ tag: v.tag, meta });
3550
- return { ...v, remotes: v.remotes.filter((r) => r !== own) };
3551
- }).filter((v) => v.remotes.length > 0);
3552
- for (const { tag, meta } of moved) {
3553
- const at = versions.findIndex((v) => v.tag === tag && v.action === "scope");
3554
- if (at >= 0) versions[at] = { ...versions[at], remotes: [...versions[at].remotes, meta] };
3555
- else versions.push({ tag, action: "scope", host: false, remotes: [meta] });
3854
+ return void 0;
3855
+ }
3856
+ function inRecordOrder(members, recordOrder) {
3857
+ const rank2 = new Map(recordOrder.map((name, i) => [name, i]));
3858
+ return [...members].sort((a, b) => rank2.get(a.name) - rank2.get(b.name)).map((m) => [m.name, m.external]);
3859
+ }
3860
+
3861
+ // src/lib/core/2.app/steps/pooling/pool-dynamic-externals.ts
3862
+ function createPoolDynamicExternals(config, ports) {
3863
+ const accepts = createAcceptsTag(
3864
+ (tag, range) => ports.versionCheck.isCompatible(tag, range),
3865
+ (a, b) => ports.versionCheck.compare(a, b)
3866
+ );
3867
+ const scopeUrlOf = (remote) => ports.remoteInfoRepo.tryGet(remote).get()?.scopeUrl;
3868
+ return ({ entry, actions }) => {
3869
+ for (const [scope, names] of poolableNames(entry, actions)) {
3870
+ const committed = ports.sharedExternalsRepo.getFromScope(scope);
3871
+ if (!scopeHasPoolState(committed)) continue;
3872
+ const { pools } = buildPools(committed);
3873
+ const moves = judgeScope(entry.name, names, actions, pools, Object.keys(committed), scope);
3874
+ writeMoves(entry.name, names, moves, committed, pools, scope);
3556
3875
  }
3557
- return {
3558
- ...external,
3559
- versions: versions.sort((a, b) => ports.versionCheck.compare(b.tag, a.tag))
3876
+ return Promise.resolve({ entry, actions: publicActions(actions) });
3877
+ };
3878
+ function judgeScope(remote, names, actions, pools, recordOrder, scope) {
3879
+ const policy = { shareScope: scope, scopeUrlOf };
3880
+ const moves = /* @__PURE__ */ new Map();
3881
+ const selfServe = (members, cause) => {
3882
+ for (const { name } of members) {
3883
+ actions[name].action = "scope";
3884
+ delete actions[name].override;
3885
+ moves.set(name, { cause });
3886
+ }
3560
3887
  };
3888
+ for (const pool of pools.values()) {
3889
+ const mine = pool.filter((member) => names.has(member.name));
3890
+ if (mine.length === 0) continue;
3891
+ const shipped = copiesByRemote(pool);
3892
+ const view = committedView(pool, shipped, recordOrder, {
3893
+ shareScope: scope,
3894
+ without: remote
3895
+ });
3896
+ const own = shipped.get(remote) ?? [];
3897
+ const verdict = judgeRemote(own, view, accepts);
3898
+ if (verdict !== "global") {
3899
+ config.log.warn(8, `[${scope}] ${selfServeWarning(remote, verdict, specifierCount(own))}`);
3900
+ selfServe(mine, verdict.cause);
3901
+ continue;
3902
+ }
3903
+ const cover = coverFromMap(remote, mine, view, actions, policy);
3904
+ if (!("covers" in cover)) {
3905
+ if ("unmapped" in cover) warnUnmapped(scope, remote, cover.unmapped);
3906
+ else
3907
+ config.log.warn(
3908
+ 8,
3909
+ `[${scope}] ${selfServeWarning(remote, coverMiss(cover), specifierCount(own))}`
3910
+ );
3911
+ selfServe(mine, "uncovered");
3912
+ continue;
3913
+ }
3914
+ for (const { name, toSkip, override } of cover.covers) {
3915
+ const action = actions[name];
3916
+ if (toSkip) {
3917
+ action.action = "skip";
3918
+ moves.set(name, { fromMap: true });
3919
+ }
3920
+ if (Object.keys(override).length > 0) action.override = override;
3921
+ }
3922
+ }
3923
+ return moves;
3924
+ }
3925
+ function writeMoves(remote, names, moves, committed, pools, scope) {
3926
+ const written = {};
3927
+ for (const [name, move] of moves)
3928
+ written[name] = movedRecord(committed[name], remote, move, ports.versionCheck.compare);
3929
+ for (const [name, record] of renamedRecords({ ...committed, ...written }, pools))
3930
+ written[name] = record;
3931
+ if (!config.strict.strictExternalCompatibility)
3932
+ for (const { name } of [...pools.values()].flat().filter((m) => names.has(m.name)))
3933
+ written[name] = { ...written[name] ?? committed[name], dirty: true };
3934
+ for (const [name, record] of Object.entries(written))
3935
+ ports.sharedExternalsRepo.addOrUpdate(name, record, scope);
3936
+ }
3937
+ function warnUnmapped(scope, remote, build) {
3938
+ config.log.warn(
3939
+ 8,
3940
+ `[${scope}][${remote}] '${build}' is not in the cache, so its files cannot be mapped.`
3941
+ );
3561
3942
  }
3562
3943
  }
3944
+ function publicActions(actions) {
3945
+ for (const action of Object.values(actions)) delete action.covered;
3946
+ return actions;
3947
+ }
3948
+ function poolableNames(entry, actions) {
3949
+ const declared = /* @__PURE__ */ new Map();
3950
+ for (const external of entry.shared ?? []) {
3951
+ const name = external.packageName;
3952
+ if (!external.singleton || !actions[name]) continue;
3953
+ if (external.shareScope === STRICT_SCOPE) continue;
3954
+ const scope = external.shareScope ?? GLOBAL_SCOPE;
3955
+ let names = declared.get(scope);
3956
+ if (!names) declared.set(scope, names = /* @__PURE__ */ new Set());
3957
+ names.add(name);
3958
+ }
3959
+ return declared;
3960
+ }
3961
+ function coverMiss(cover) {
3962
+ if ("gap" in cover) return { cause: "uncovered", specifier: cover.gap };
3963
+ return { cause: "uncovered", ...cover.bound };
3964
+ }
3965
+ function selfServeWarning(remote, miss, imports) {
3966
+ const where = `All ${imports} of its entrypoints are scoped for it.`;
3967
+ if (miss.cause === "incompatible")
3968
+ return `'${remote}' is islanded: its range rejects '${miss.specifier}@${miss.tag}' of the committed map. ${where}`;
3969
+ if ("owner" in miss)
3970
+ return `'${remote}' serves its own family: committed build '${miss.owner}', whose files it would reach, resolves '${miss.specifier}' to another file than it would. ${where}`;
3971
+ if ("unshipped" in miss)
3972
+ return `'${remote}' serves its own family: no committed build shipped the map's combination for it. ${where}`;
3973
+ return `'${remote}' serves its own family: no committed build offers every entrypoint it imports at a version it accepts (gap '${miss.specifier}'). ${where}`;
3974
+ }
3563
3975
 
3564
3976
  // src/lib/core/2.app/steps/convert-to-import-map.ts
3565
3977
  function createConvertToImportMap(config, ports) {
@@ -3596,27 +4008,14 @@ function createConvertToImportMap(config, ports) {
3596
4008
  return;
3597
4009
  }
3598
4010
  if (actions[external.packageName].action === "skip") {
3599
- const { override, covered, sameVersion } = actions[external.packageName];
4011
+ const { override } = actions[external.packageName];
3600
4012
  if (override) {
3601
4013
  Object.entries(override).forEach(([packageName, url]) => {
3602
4014
  addToScopes(remoteEntryScope, packageName, url, importMap);
3603
4015
  });
3604
- serveUncovered(
3605
- remoteEntry,
3606
- external,
3607
- covered ?? Object.keys(override),
3608
- remoteEntryScope,
3609
- importMap,
3610
- sameVersion
3611
- );
3612
- return;
3613
- }
3614
- if (!external.shareScope) {
3615
- if (covered) {
3616
- serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap, sameVersion);
3617
- }
3618
4016
  return;
3619
4017
  }
4018
+ if (!external.shareScope) return;
3620
4019
  log.error(
3621
4020
  9,
3622
4021
  `[${remoteEntry.name}][${external.packageName}] shareScope skip has no override.`
@@ -3681,27 +4080,6 @@ function createConvertToImportMap(config, ports) {
3681
4080
  });
3682
4081
  return importMap;
3683
4082
  }
3684
- function serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap, sameVersion = false) {
3685
- const provided = new Set(covered);
3686
- Object.entries(external.entries).forEach(([packageName, fileName]) => {
3687
- if (provided.has(packageName)) return;
3688
- if (!sameVersion && (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints)) {
3689
- warnUncoveredEntrypoint(remoteEntry.name, external.packageName, packageName);
3690
- return;
3691
- }
3692
- const url = join(remoteEntryScope, fileName);
3693
- addToScopes(remoteEntryScope, packageName, url, importMap);
3694
- addIntegrity(importMap, url, remoteEntry.integrity, fileName);
3695
- });
3696
- }
3697
- function warnUncoveredEntrypoint(remoteName, externalName, packageName) {
3698
- const msg = `[${remoteName}][${externalName}] Entrypoint '${packageName}' is not covered by the override.`;
3699
- if (config.strict.strictEntryPointCoverage || config.strict.strictImportMap) {
3700
- log.error(9, msg);
3701
- throw new NFError("Could not create ImportMap.");
3702
- }
3703
- log.warn(9, msg);
3704
- }
3705
4083
  function addIntegrity(importMap, url, integrityMap, file) {
3706
4084
  const hash = integrityMap?.[file];
3707
4085
  if (!hash) return;
@@ -3717,11 +4095,12 @@ var createInitDrivers = ({
3717
4095
  }) => ({
3718
4096
  getRemoteEntries: createGetRemoteEntries(config, adapters),
3719
4097
  processRemoteEntries: createProcessRemoteEntries(config, adapters),
3720
- markPoolsForReelection: createMarkPoolsForReelection(config, adapters),
3721
4098
  determineSharedExternals: createDetermineSharedExternals(config, adapters),
3722
4099
  poolSharedExternals: createPoolSharedExternals(config, adapters),
4100
+ decideServingBuilds: createDecideServingBuilds(config, adapters),
3723
4101
  generateImportMap: createGenerateImportMap(config, adapters),
3724
4102
  commitChanges: createCommitChanges(config, adapters),
4103
+ rollbackChanges: createRollbackChanges(config, adapters),
3725
4104
  exposeModuleLoader: createExposeModuleLoader(config, adapters),
3726
4105
  getRemoteEntry: createGetRemoteEntry(config, adapters),
3727
4106
  updateCache: createUpdateCache(config, adapters),
@@ -3751,19 +4130,30 @@ var createBrowser = (config) => {
3751
4130
  // src/lib/core/3.adapters/checks/version.check.ts
3752
4131
  var import_valid = __toESM(require_valid());
3753
4132
  var import_satisfies = __toESM(require_satisfies());
3754
- var import_compare = __toESM(require_compare());
4133
+ var import_semver = __toESM(require_semver());
3755
4134
  var import_min_version = __toESM(require_min_version());
3756
4135
  var import_valid2 = __toESM(require_valid2());
3757
4136
  var createVersionCheck = () => {
4137
+ const parsed = /* @__PURE__ */ new Map();
4138
+ const parse = (v) => {
4139
+ let hit = parsed.get(v);
4140
+ if (!hit) parsed.set(v, hit = new import_semver.default(v, true));
4141
+ return hit;
4142
+ };
4143
+ const satisfied = /* @__PURE__ */ new Map();
3758
4144
  return {
3759
4145
  isValidSemver: function(version) {
3760
4146
  return (0, import_valid.default)(version) !== null;
3761
4147
  },
3762
4148
  isCompatible: function(version, range) {
3763
- return (0, import_satisfies.default)(version, range);
4149
+ let byVersion = satisfied.get(range);
4150
+ if (!byVersion) satisfied.set(range, byVersion = /* @__PURE__ */ new Map());
4151
+ let hit = byVersion.get(version);
4152
+ if (hit === void 0) byVersion.set(version, hit = (0, import_satisfies.default)(version, range));
4153
+ return hit;
3764
4154
  },
3765
4155
  compare: function(versionA, versionB) {
3766
- return (0, import_compare.default)(versionA, versionB, true);
4156
+ return parse(versionA).compare(parse(versionB));
3767
4157
  },
3768
4158
  smallestVersion: function(versionRange) {
3769
4159
  if (!(0, import_valid2.default)(versionRange)) return "0.0.0";
@@ -3836,7 +4226,7 @@ var createManifestProvider = () => {
3836
4226
  };
3837
4227
  };
3838
4228
 
3839
- // node_modules/.pnpm/@softarc+native-federation@4.7.0/node_modules/@softarc/native-federation/dist/lib/utils/normalize.js
4229
+ // node_modules/.pnpm/@softarc+native-federation@4.8.0/node_modules/@softarc/native-federation/dist/lib/utils/normalize.js
3840
4230
  function inferPackageFromSecondary(secondary) {
3841
4231
  const parts = secondary.split("/");
3842
4232
  if (secondary.startsWith("@") && parts.length >= 2) {
@@ -3845,7 +4235,7 @@ function inferPackageFromSecondary(secondary) {
3845
4235
  return parts[0];
3846
4236
  }
3847
4237
 
3848
- // node_modules/.pnpm/@softarc+native-federation@4.7.0/node_modules/@softarc/native-federation/dist/lib/core/output/densify-externals.js
4238
+ // node_modules/.pnpm/@softarc+native-federation@4.8.0/node_modules/@softarc/native-federation/dist/lib/core/output/densify-externals.js
3849
4239
  function isDense(entry) {
3850
4240
  return "entries" in entry;
3851
4241
  }
@@ -3935,14 +4325,15 @@ var STORAGE_KEYS = {
3935
4325
  remotes: "remotes",
3936
4326
  sharedExternals: "shared-externals",
3937
4327
  scopedExternals: "scoped-externals",
3938
- sharedChunks: "shared-chunks"
4328
+ sharedChunks: "shared-chunks",
4329
+ cacheState: "cache-state"
3939
4330
  };
3940
4331
 
3941
4332
  // src/lib/core/3.adapters/storage/remote-info.repository.ts
3942
4333
  var createRemoteInfoRepository = (config) => {
3943
4334
  const STORAGE = config.storage(STORAGE_KEYS.remotes, {});
3944
4335
  if (config.clearStorage) STORAGE.clear();
3945
- const _cache = STORAGE.get() ?? {};
4336
+ let _cache = STORAGE.get() ?? {};
3946
4337
  let _dirty = false;
3947
4338
  return {
3948
4339
  contains: function(remoteName) {
@@ -3976,6 +4367,11 @@ var createRemoteInfoRepository = (config) => {
3976
4367
  STORAGE.set(_cache);
3977
4368
  _dirty = false;
3978
4369
  return this;
4370
+ },
4371
+ rollback: function() {
4372
+ _cache = STORAGE.get() ?? {};
4373
+ _dirty = false;
4374
+ return this;
3979
4375
  }
3980
4376
  };
3981
4377
  };
@@ -3984,7 +4380,7 @@ var createRemoteInfoRepository = (config) => {
3984
4380
  var createScopedExternalsRepository = (config) => {
3985
4381
  const STORAGE = config.storage(STORAGE_KEYS.scopedExternals, {});
3986
4382
  if (config.clearStorage) STORAGE.clear();
3987
- const _cache = STORAGE.get() ?? {};
4383
+ let _cache = STORAGE.get() ?? {};
3988
4384
  let _dirty = false;
3989
4385
  return {
3990
4386
  addExternal: function(remoteName, external, version) {
@@ -4011,43 +4407,57 @@ var createScopedExternalsRepository = (config) => {
4011
4407
  STORAGE.set(_cache);
4012
4408
  _dirty = false;
4013
4409
  return this;
4410
+ },
4411
+ rollback: function() {
4412
+ _cache = STORAGE.get() ?? {};
4413
+ _dirty = false;
4414
+ return this;
4014
4415
  }
4015
4416
  };
4016
4417
  };
4017
4418
 
4419
+ // src/lib/core/3.adapters/storage/cache-state.ts
4420
+ var createCacheState = (storage) => {
4421
+ const entry = storage(STORAGE_KEYS.cacheState, null);
4422
+ const read = () => {
4423
+ const stored = entry.get();
4424
+ if (typeof stored !== "object" || stored === null) return {};
4425
+ const { version, undecidedScopes } = stored;
4426
+ return {
4427
+ ...typeof version === "string" ? { version } : {},
4428
+ ...Array.isArray(undecidedScopes) ? { undecidedScopes: undecidedScopes.filter((s) => typeof s === "string") } : {}
4429
+ };
4430
+ };
4431
+ return {
4432
+ read,
4433
+ update: (fields) => void entry.set({ ...read(), ...fields })
4434
+ };
4435
+ };
4436
+
4018
4437
  // src/lib/core/3.adapters/storage/shared-externals.repository.ts
4019
4438
  var createSharedExternalsRepository = (config) => {
4020
4439
  const STORAGE = config.storage(
4021
4440
  STORAGE_KEYS.sharedExternals,
4022
4441
  { [GLOBAL_SCOPE]: {} }
4023
4442
  );
4024
- if (config.clearStorage) STORAGE.clear();
4025
- const _cache = STORAGE.get() ?? { [GLOBAL_SCOPE]: {} };
4026
- let _dirty = false;
4443
+ const cacheState = createCacheState(config.storage);
4444
+ const storedUndecided = () => new Set(cacheState.read().undecidedScopes);
4445
+ if (config.clearStorage) {
4446
+ STORAGE.clear();
4447
+ cacheState.update({ undecidedScopes: [] });
4448
+ }
4449
+ let _cache = STORAGE.get() ?? { [GLOBAL_SCOPE]: {} };
4450
+ const _uncommitted = /* @__PURE__ */ new Set();
4451
+ let _undecided = storedUndecided();
4452
+ let _undecidedChanged = false;
4027
4453
  return {
4028
- // Read from the cache rather than remembered from this init's entries: a warm init may not refetch
4029
- // the tagged remote at all, and pooling has to coordinate its pool anyway. Exits on the first hit.
4030
- // Per share scope, because a pool never spans one: a tag in another scope is no reason to pool here.
4031
- // Stored pool results count too, since a scope whose last tag left still has pool state to clear.
4032
- hasPoolState: function(shareScope) {
4033
- const scope = _cache[shareScope ?? GLOBAL_SCOPE];
4034
- if (!scope) return false;
4035
- for (const external of Object.values(scope)) {
4036
- if (external.poolName !== void 0) return true;
4037
- for (const version of external.versions)
4038
- for (const remote of version.remotes)
4039
- if (remote.pool?.trim() || remote.servedBy !== void 0 || remote.poolCause !== void 0)
4040
- return true;
4041
- }
4042
- return false;
4043
- },
4044
4454
  getFromScope: function(shareScope) {
4045
4455
  return { ..._cache[shareScope ?? GLOBAL_SCOPE] };
4046
4456
  },
4047
4457
  addOrUpdate: function(externalName, external, shareScope) {
4048
4458
  if (!_cache[shareScope ?? GLOBAL_SCOPE]) _cache[shareScope ?? GLOBAL_SCOPE] = {};
4049
4459
  _cache[shareScope ?? GLOBAL_SCOPE][externalName] = external;
4050
- _dirty = true;
4460
+ _uncommitted.add(shareScope ?? GLOBAL_SCOPE);
4051
4461
  return this;
4052
4462
  },
4053
4463
  getScopes: function(o = { includeGlobal: true }) {
@@ -4057,7 +4467,7 @@ var createSharedExternalsRepository = (config) => {
4057
4467
  // Batched: one traversal of the graph per init instead of one per overridden remote.
4058
4468
  removeFromAllScopes: function(remoteNames) {
4059
4469
  if (remoteNames.size === 0) return;
4060
- Object.values(_cache).forEach((scope) => {
4470
+ Object.entries(_cache).forEach(([scopeName, scope]) => {
4061
4471
  const removeExternals = [];
4062
4472
  Object.entries(scope).forEach(([name, external]) => {
4063
4473
  let removedCopy = false;
@@ -4073,12 +4483,12 @@ var createSharedExternalsRepository = (config) => {
4073
4483
  if (keep !== remotes.length) {
4074
4484
  remotes.length = keep;
4075
4485
  removedCopy = true;
4076
- _dirty = true;
4486
+ _uncommitted.add(scopeName);
4077
4487
  if (host !== void 0 && remoteNames.has(host)) version.host = false;
4078
4488
  }
4079
4489
  if (remotes.length === 0) {
4080
4490
  external.versions.splice(i, 1);
4081
- _dirty = true;
4491
+ _uncommitted.add(scopeName);
4082
4492
  }
4083
4493
  }
4084
4494
  if (removedCopy) {
@@ -4086,7 +4496,17 @@ var createSharedExternalsRepository = (config) => {
4086
4496
  if (external.versions.length === 0) removeExternals.push(name);
4087
4497
  }
4088
4498
  });
4089
- removeExternals.forEach((name) => delete scope[name]);
4499
+ const lostPools = /* @__PURE__ */ new Set();
4500
+ for (const name of removeExternals) {
4501
+ const poolName = scope[name].poolName;
4502
+ if (poolName !== void 0) lostPools.add(poolName);
4503
+ delete scope[name];
4504
+ }
4505
+ if (lostPools.size > 0) {
4506
+ for (const external of Object.values(scope))
4507
+ if (external.poolName !== void 0 && lostPools.has(external.poolName))
4508
+ external.dirty = true;
4509
+ }
4090
4510
  });
4091
4511
  },
4092
4512
  scopeType: function(shareScope) {
@@ -4104,10 +4524,36 @@ var createSharedExternalsRepository = (config) => {
4104
4524
  tryGet: function(external, shareScope) {
4105
4525
  return Optional.of(_cache[shareScope ?? GLOBAL_SCOPE]?.[external]);
4106
4526
  },
4527
+ uncommittedScopes: function() {
4528
+ return [..._uncommitted];
4529
+ },
4530
+ markUndecided: function(shareScope) {
4531
+ if (_undecided.has(shareScope)) return;
4532
+ _undecided.add(shareScope);
4533
+ _undecidedChanged = true;
4534
+ },
4535
+ isUndecided: function(shareScope) {
4536
+ return _undecided.has(shareScope);
4537
+ },
4538
+ markDecided: function(shareScope) {
4539
+ if (_undecided.delete(shareScope)) _undecidedChanged = true;
4540
+ },
4541
+ // Records before marks: should their write throw, the stored marks still send the next init to decide them. A
4542
+ // failed cache-state write after the records loses only this load's marks.
4107
4543
  commit: function() {
4108
- if (!_dirty) return this;
4109
- STORAGE.set(_cache);
4110
- _dirty = false;
4544
+ if (_uncommitted.size > 0) {
4545
+ STORAGE.set(_cache);
4546
+ _uncommitted.clear();
4547
+ }
4548
+ if (_undecidedChanged) cacheState.update({ undecidedScopes: [..._undecided] });
4549
+ _undecidedChanged = false;
4550
+ return this;
4551
+ },
4552
+ rollback: function() {
4553
+ _cache = STORAGE.get() ?? { [GLOBAL_SCOPE]: {} };
4554
+ _uncommitted.clear();
4555
+ _undecided = storedUndecided();
4556
+ _undecidedChanged = false;
4111
4557
  return this;
4112
4558
  }
4113
4559
  };
@@ -4224,7 +4670,7 @@ var createSSEHandler = (config) => {
4224
4670
  var createChunkRepository = (config) => {
4225
4671
  const STORAGE = config.storage(STORAGE_KEYS.sharedChunks, {});
4226
4672
  if (config.clearStorage) STORAGE.clear();
4227
- const _cache = STORAGE.get() ?? {};
4673
+ let _cache = STORAGE.get() ?? {};
4228
4674
  let _dirty = false;
4229
4675
  return {
4230
4676
  addOrReplace: function(remoteName, bundleName, chunks) {
@@ -4249,6 +4695,11 @@ var createChunkRepository = (config) => {
4249
4695
  STORAGE.set(_cache);
4250
4696
  _dirty = false;
4251
4697
  return this;
4698
+ },
4699
+ rollback: function() {
4700
+ _cache = STORAGE.get() ?? {};
4701
+ _dirty = false;
4702
+ return this;
4252
4703
  }
4253
4704
  };
4254
4705
  };
@@ -4471,8 +4922,10 @@ var globalThisStorageEntry = (namespace) => (key, initialValue) => {
4471
4922
  globalThisStorageEntry.type = "globalThis";
4472
4923
  Object.freeze(globalThisStorageEntry);
4473
4924
 
4925
+ // src/lib/core/4.config/orchestrator-version.ts
4926
+ var ORCHESTRATOR_VERSION = true ? "4.8.0" : "dev";
4927
+
4474
4928
  // src/lib/core/4.config/storage/publish-storage-info.ts
4475
- var VERSION = true ? "4.7.0" : "dev";
4476
4929
  var KEYS = Object.freeze(Object.values(STORAGE_KEYS));
4477
4930
  var isStorageKey = (key) => KEYS.includes(key);
4478
4931
  var createStorageInfo = ({
@@ -4481,7 +4934,12 @@ var createStorageInfo = ({
4481
4934
  storage,
4482
4935
  exposeGetter
4483
4936
  }) => {
4484
- const info = { version: VERSION, type, namespace, keys: KEYS };
4937
+ const info = {
4938
+ version: ORCHESTRATOR_VERSION,
4939
+ type,
4940
+ namespace,
4941
+ keys: KEYS
4942
+ };
4485
4943
  if (!exposeGetter) return Object.freeze(info);
4486
4944
  return Object.freeze({
4487
4945
  ...info,
@@ -4502,6 +4960,12 @@ var publishStorageInfo = (options) => {
4502
4960
  };
4503
4961
 
4504
4962
  // src/lib/core/4.config/storage/storage.config.ts
4963
+ var isOutdatedCache = (storage) => {
4964
+ const cacheState = createCacheState(storage);
4965
+ if (cacheState.read().version === ORCHESTRATOR_VERSION) return false;
4966
+ cacheState.update({ version: ORCHESTRATOR_VERSION, undecidedScopes: [] });
4967
+ return Object.values(STORAGE_KEYS).filter((key) => key !== STORAGE_KEYS.cacheState).some((key) => storage(key, null).get() !== null);
4968
+ };
4505
4969
  var createStorageConfig = (override) => {
4506
4970
  const creator = override.storage ?? globalThisStorageEntry;
4507
4971
  const namespace = override.storageNamespace ?? "__NATIVE_FEDERATION__";
@@ -4512,9 +4976,10 @@ var createStorageConfig = (override) => {
4512
4976
  storage,
4513
4977
  exposeGetter: override.exposeStorageGetter ?? true
4514
4978
  });
4979
+ const outdated = isOutdatedCache(storage);
4515
4980
  return {
4516
4981
  storage,
4517
- clearStorage: override.clearStorage ?? false
4982
+ clearStorage: (override.clearStorage ?? false) || outdated
4518
4983
  };
4519
4984
  };
4520
4985
 
@@ -4522,7 +4987,6 @@ var createStorageConfig = (override) => {
4522
4987
  var defaultProfile = {
4523
4988
  latestSharedExternal: false,
4524
4989
  skipInvalidExternalVersions: false,
4525
- scopeUncoveredEntrypoints: false,
4526
4990
  overrideCachedRemotes: "init-only",
4527
4991
  overrideCachedRemotesIfURLMatches: false
4528
4992
  };
@@ -4534,15 +4998,13 @@ var createModeConfig = (override) => {
4534
4998
  strictExternalCompatibility: override.strict,
4535
4999
  strictExternalSameVersionCompatibility: override.strict,
4536
5000
  strictExternalVersion: override.strict,
4537
- strictImportMap: override.strict,
4538
- strictEntryPointCoverage: override.strict
5001
+ strictImportMap: override.strict
4539
5002
  } : {
4540
5003
  strictRemoteEntry: override.strict?.strictRemoteEntry ?? false,
4541
5004
  strictExternalCompatibility: override.strict?.strictExternalCompatibility ?? false,
4542
5005
  strictExternalSameVersionCompatibility: override.strict?.strictExternalSameVersionCompatibility ?? false,
4543
5006
  strictExternalVersion: override.strict?.strictExternalVersion ?? false,
4544
- strictImportMap: override.strict?.strictImportMap ?? false,
4545
- strictEntryPointCoverage: override.strict?.strictEntryPointCoverage ?? false
5007
+ strictImportMap: override.strict?.strictImportMap ?? false
4546
5008
  };
4547
5009
  return {
4548
5010
  strict: strictnessConfig,
@@ -4563,14 +5025,6 @@ var createConfigHandlers = (overrides) => ({
4563
5025
  });
4564
5026
 
4565
5027
  // src/lib/core/5.di/federation-result.factory.ts
4566
- var createStateDump = (config, adapters) => (msg) => config.log.debug(0, msg, {
4567
- remotes: { ...adapters.remoteInfoRepo.getAll() },
4568
- "shared-externals": adapters.sharedExternalsRepo.getScopes({ includeGlobal: true }).reduce(
4569
- (acc, scope) => ({ ...acc, [scope]: adapters.sharedExternalsRepo.getFromScope(scope) }),
4570
- {}
4571
- ),
4572
- "scoped-externals": adapters.scopedExternalsRepo.getAll()
4573
- });
4574
5028
  var createFederationResult = ({
4575
5029
  config,
4576
5030
  adapters,
@@ -4578,11 +5032,8 @@ var createFederationResult = ({
4578
5032
  initRemoteEntryFlow,
4579
5033
  afterInitRemoteEntry
4580
5034
  }) => {
4581
- const stateDump = createStateDump(config, adapters);
4582
5035
  const initRemoteEntry = async (remoteEntryUrl, remote) => {
4583
- const remoteName = typeof remote === "string" ? remote : remote?.name;
4584
5036
  return initRemoteEntryFlow(remoteEntryUrl, remote).catch((e) => {
4585
- stateDump(`[dynamic-init][${remoteName ?? remoteEntryUrl}] STATE DUMP`);
4586
5037
  if (config.strict.strictRemoteEntry) return Promise.reject(e);
4587
5038
  else console.warn("Failed to initialize remote entry, continuing anyway.");
4588
5039
  return Promise.resolve();