@softarc/native-federation-orchestrator 4.6.0 → 4.7.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 (29) hide show
  1. package/README.md +2 -2
  2. package/fesm2022/@softarc/native-federation-orchestrator.mjs +340 -111
  3. package/fesm2022/@softarc/native-federation-orchestrator.mjs.map +4 -4
  4. package/fesm2022/node.mjs +231 -72
  5. package/fesm2022/node.mjs.map +4 -4
  6. package/fesm2022/options.mjs +6 -0
  7. package/fesm2022/options.mjs.map +2 -2
  8. package/fesm2022/sdk.mjs.map +1 -1
  9. package/node-loader/loader.mjs +26 -6
  10. package/node-loader/loader.mjs.map +2 -2
  11. package/package.json +1 -1
  12. package/quickstart.mjs +15 -15
  13. package/types/lib/core/1.domain/externals/external.contract.d.ts +1 -0
  14. package/types/lib/core/1.domain/externals/version.contract.d.ts +2 -0
  15. package/types/lib/core/2.app/config/index.d.ts +2 -1
  16. package/types/lib/core/2.app/config/mode.contract.d.ts +0 -1
  17. package/types/lib/core/2.app/config/orchestrator-global.contract.d.ts +15 -0
  18. package/types/lib/core/2.app/config/storage.contract.d.ts +14 -2
  19. package/types/lib/core/2.app/driving-ports/for-shared-externals-storage.port.d.ts +1 -1
  20. package/types/lib/core/2.app/steps/pooling/pool-graph.d.ts +7 -12
  21. package/types/lib/core/2.app/steps/pooling/pool.util.d.ts +12 -11
  22. package/types/lib/core/3.adapters/browser/sse-handler.d.ts +0 -8
  23. package/types/lib/core/3.adapters/storage/chunk.repository.d.ts +1 -1
  24. package/types/lib/core/3.adapters/storage/remote-info.repository.d.ts +1 -1
  25. package/types/lib/core/3.adapters/storage/scoped-externals.repository.d.ts +1 -1
  26. package/types/lib/core/3.adapters/storage/shared-externals.repository.d.ts +1 -1
  27. package/types/lib/core/4.config/storage/publish-storage-info.d.ts +9 -0
  28. package/types/lib/testing/pooling/tag-by-npm-scope.d.ts +9 -0
  29. package/types/scripts/node-loader.d.ts +1 -0
package/fesm2022/node.mjs CHANGED
@@ -1430,6 +1430,14 @@ var createNoopSSE = () => ({
1430
1430
  }
1431
1431
  });
1432
1432
 
1433
+ // src/lib/core/2.app/config/storage.contract.ts
1434
+ var STORAGE_KEYS = {
1435
+ remotes: "remotes",
1436
+ sharedExternals: "shared-externals",
1437
+ scopedExternals: "scoped-externals",
1438
+ sharedChunks: "shared-chunks"
1439
+ };
1440
+
1433
1441
  // src/lib/utils/path.ts
1434
1442
  function join(pathA, pathB) {
1435
1443
  pathA = pathA.endsWith("/") ? pathA.slice(0, -1) : pathA;
@@ -1487,7 +1495,7 @@ var Optional = class _Optional {
1487
1495
 
1488
1496
  // src/lib/core/3.adapters/storage/remote-info.repository.ts
1489
1497
  var createRemoteInfoRepository = (config) => {
1490
- const STORAGE = config.storage("remotes", {});
1498
+ const STORAGE = config.storage(STORAGE_KEYS.remotes, {});
1491
1499
  if (config.clearStorage) STORAGE.clear();
1492
1500
  const _cache = STORAGE.get() ?? {};
1493
1501
  let _dirty = false;
@@ -1529,7 +1537,7 @@ var createRemoteInfoRepository = (config) => {
1529
1537
 
1530
1538
  // src/lib/core/3.adapters/storage/scoped-externals.repository.ts
1531
1539
  var createScopedExternalsRepository = (config) => {
1532
- const STORAGE = config.storage("scoped-externals", {});
1540
+ const STORAGE = config.storage(STORAGE_KEYS.scopedExternals, {});
1533
1541
  if (config.clearStorage) STORAGE.clear();
1534
1542
  const _cache = STORAGE.get() ?? {};
1535
1543
  let _dirty = false;
@@ -1569,7 +1577,7 @@ var STRICT_SCOPE = "strict";
1569
1577
  // src/lib/core/3.adapters/storage/shared-externals.repository.ts
1570
1578
  var createSharedExternalsRepository = (config) => {
1571
1579
  const STORAGE = config.storage(
1572
- "shared-externals",
1580
+ STORAGE_KEYS.sharedExternals,
1573
1581
  { [GLOBAL_SCOPE]: {} }
1574
1582
  );
1575
1583
  if (config.clearStorage) STORAGE.clear();
@@ -1579,12 +1587,17 @@ var createSharedExternalsRepository = (config) => {
1579
1587
  // Read from the cache rather than remembered from this init's entries: a warm init may not refetch
1580
1588
  // the tagged remote at all, and pooling has to coordinate its pool anyway. Exits on the first hit.
1581
1589
  // Per share scope, because a pool never spans one: a tag in another scope is no reason to pool here.
1582
- hasPoolTag: function(shareScope) {
1590
+ // Stored pool results count too, since a scope whose last tag left still has pool state to clear.
1591
+ hasPoolState: function(shareScope) {
1583
1592
  const scope = _cache[shareScope ?? GLOBAL_SCOPE];
1584
1593
  if (!scope) return false;
1585
- for (const external of Object.values(scope))
1594
+ for (const external of Object.values(scope)) {
1595
+ if (external.poolName !== void 0) return true;
1586
1596
  for (const version of external.versions)
1587
- for (const remote of version.remotes) if (remote.pool?.trim()) return true;
1597
+ for (const remote of version.remotes)
1598
+ if (remote.pool?.trim() || remote.servedBy !== void 0 || remote.poolCause !== void 0)
1599
+ return true;
1600
+ }
1588
1601
  return false;
1589
1602
  },
1590
1603
  getFromScope: function(shareScope) {
@@ -1661,7 +1674,7 @@ var createSharedExternalsRepository = (config) => {
1661
1674
 
1662
1675
  // src/lib/core/3.adapters/storage/chunk.repository.ts
1663
1676
  var createChunkRepository = (config) => {
1664
- const STORAGE = config.storage("shared-chunks", {});
1677
+ const STORAGE = config.storage(STORAGE_KEYS.sharedChunks, {});
1665
1678
  if (config.clearStorage) STORAGE.clear();
1666
1679
  const _cache = STORAGE.get() ?? {};
1667
1680
  let _dirty = false;
@@ -1891,12 +1904,55 @@ var globalThisStorageEntry = (namespace) => (key, initialValue) => {
1891
1904
  };
1892
1905
  return entry;
1893
1906
  };
1907
+ globalThisStorageEntry.type = "globalThis";
1908
+ Object.freeze(globalThisStorageEntry);
1909
+
1910
+ // src/lib/core/4.config/storage/publish-storage-info.ts
1911
+ var VERSION = true ? "4.7.0" : "dev";
1912
+ var KEYS = Object.freeze(Object.values(STORAGE_KEYS));
1913
+ var isStorageKey = (key) => KEYS.includes(key);
1914
+ var createStorageInfo = ({
1915
+ type,
1916
+ namespace,
1917
+ storage,
1918
+ exposeGetter
1919
+ }) => {
1920
+ const info = { version: VERSION, type, namespace, keys: KEYS };
1921
+ if (!exposeGetter) return Object.freeze(info);
1922
+ return Object.freeze({
1923
+ ...info,
1924
+ // Cloned again because custom storages may hand out live references.
1925
+ get: (key) => isStorageKey(key) ? cloneEntry(key, storage(key, void 0).get()) : void 0
1926
+ });
1927
+ };
1928
+ var publishStorageInfo = (options) => {
1929
+ try {
1930
+ globalThis.__NF_ORCHESTRATOR__ = Object.freeze({
1931
+ storage: Object.freeze({
1932
+ ...globalThis.__NF_ORCHESTRATOR__?.storage,
1933
+ [options.namespace]: createStorageInfo(options)
1934
+ })
1935
+ });
1936
+ } catch {
1937
+ }
1938
+ };
1894
1939
 
1895
1940
  // src/lib/core/4.config/storage/storage.config.ts
1896
- var createStorageConfig = (override) => ({
1897
- storage: override.storage ? override.storage(override.storageNamespace ?? "__NATIVE_FEDERATION__") : globalThisStorageEntry(override.storageNamespace ?? "__NATIVE_FEDERATION__"),
1898
- clearStorage: override.clearStorage ?? false
1899
- });
1941
+ var createStorageConfig = (override) => {
1942
+ const creator = override.storage ?? globalThisStorageEntry;
1943
+ const namespace = override.storageNamespace ?? "__NATIVE_FEDERATION__";
1944
+ const storage = creator(namespace);
1945
+ publishStorageInfo({
1946
+ type: creator.type ?? "custom",
1947
+ namespace,
1948
+ storage,
1949
+ exposeGetter: override.exposeStorageGetter ?? true
1950
+ });
1951
+ return {
1952
+ storage,
1953
+ clearStorage: override.clearStorage ?? false
1954
+ };
1955
+ };
1900
1956
 
1901
1957
  // src/lib/core/4.config/mode/default.profile.ts
1902
1958
  var defaultProfile = {
@@ -1928,8 +1984,7 @@ var createModeConfig = (override) => {
1928
1984
  strict: strictnessConfig,
1929
1985
  profile: { ...defaultProfile, ...override.profile ?? {} },
1930
1986
  feature: {
1931
- convertFlatSharedInfo: override.feature?.convertFlatSharedInfo ?? false,
1932
- useAutoExternalPooling: override.feature?.useAutoExternalPooling ?? false
1987
+ convertFlatSharedInfo: override.feature?.convertFlatSharedInfo ?? false
1933
1988
  }
1934
1989
  };
1935
1990
  };
@@ -2219,7 +2274,7 @@ function createStoreRemoteEntry(config, ports, logStep) {
2219
2274
  ...sharedInfo.pool?.trim() ? { pool: sharedInfo.pool } : {},
2220
2275
  entries: sharedInfo.entries
2221
2276
  };
2222
- const cached2 = ports.sharedExternalsRepo.tryGet(sharedInfo.packageName, sharedInfo.shareScope).orElse({ dirty: false, versions: [] });
2277
+ const cached2 = ports.sharedExternalsRepo.tryGet(sharedInfo.packageName, sharedInfo.shareScope).get() ?? { dirty: false, versions: [] };
2223
2278
  return {
2224
2279
  tag,
2225
2280
  remote,
@@ -2240,6 +2295,7 @@ function createStoreRemoteEntry(config, ports, logStep) {
2240
2295
  sharedInfo.packageName,
2241
2296
  {
2242
2297
  dirty: cached2.dirty,
2298
+ ...cached2.poolName !== void 0 && { poolName: cached2.poolName },
2243
2299
  versions: cached2.versions.sort((a, b) => ports.versionCheck.compare(b.tag, a.tag))
2244
2300
  },
2245
2301
  sharedInfo.shareScope
@@ -2837,7 +2893,6 @@ function assignAnchors(input) {
2837
2893
  }
2838
2894
 
2839
2895
  // src/lib/core/2.app/steps/pooling/pool-graph.ts
2840
- var SCOPED_PACKAGE = /^@([^/]+)\//;
2841
2896
  function createDSU() {
2842
2897
  const ids = /* @__PURE__ */ new Map();
2843
2898
  const parent = [];
@@ -2876,10 +2931,6 @@ function createDSU() {
2876
2931
  }
2877
2932
  var extNode = (name) => `ext\0${name}`;
2878
2933
  var tagNode = (remote, tag) => `tag\0${remote}\0${tag}`;
2879
- var scopeNode = (remote, scope) => `scope\0${remote}\0${scope}`;
2880
- function autoScope(name, useAutoExternalPooling) {
2881
- return useAutoExternalPooling ? SCOPED_PACKAGE.exec(name)?.[1] : void 0;
2882
- }
2883
2934
  function owningPackage(name) {
2884
2935
  const depth = name.startsWith("@") ? 2 : 1;
2885
2936
  let cut = -1;
@@ -2892,23 +2943,10 @@ function owningPackage(name) {
2892
2943
  function groupByMembership(candidates, log) {
2893
2944
  const dsu = createDSU();
2894
2945
  const tagged = /* @__PURE__ */ new Set();
2895
- const joined = /* @__PURE__ */ new Set();
2896
- const tagRules = /* @__PURE__ */ new Set();
2897
- for (const candidate of candidates) {
2898
- if (candidate.scope === void 0) continue;
2899
- for (const edge of candidate.tags) tagRules.add(`${edge.remote}\0${candidate.scope}`);
2900
- }
2901
2946
  for (const candidate of candidates) {
2902
2947
  for (const edge of candidate.tags) {
2903
2948
  dsu.union(extNode(candidate.name), tagNode(edge.remote, edge.tag));
2904
2949
  tagged.add(candidate.name);
2905
- joined.add(candidate.name);
2906
- }
2907
- if (candidate.scope === void 0) continue;
2908
- for (const remote of candidate.remotes) {
2909
- if (tagRules.has(`${remote}\0${candidate.scope}`)) continue;
2910
- dsu.union(extNode(candidate.name), scopeNode(remote, candidate.scope));
2911
- joined.add(candidate.name);
2912
2950
  }
2913
2951
  }
2914
2952
  const declared = new Set(candidates.map((c) => c.name));
@@ -2921,11 +2959,11 @@ function groupByMembership(candidates, log) {
2921
2959
  for (const candidate of candidates) {
2922
2960
  const root = dsu.component(extNode(candidate.name));
2923
2961
  const members = byComponent.get(root) ?? byComponent.set(root, []).get(root);
2924
- members.push({ name: candidate.name, value: candidate.value });
2962
+ members.push(candidate);
2925
2963
  }
2926
2964
  const pools = [];
2927
2965
  for (const members of byComponent.values()) {
2928
- if (!members.some((m) => joined.has(m.name))) continue;
2966
+ if (!members.some((m) => tagged.has(m.name))) continue;
2929
2967
  members.sort((a, b) => a.name.localeCompare(b.name));
2930
2968
  if (members.length < 2) {
2931
2969
  const only = members[0];
@@ -2940,20 +2978,39 @@ function groupByMembership(candidates, log) {
2940
2978
  pools.push(members);
2941
2979
  }
2942
2980
  pools.sort((a, b) => a[0].name.localeCompare(b[0].name));
2943
- return new Map(pools.map((members) => [members[0].name, members.map((m) => m.value)]));
2981
+ const named = /* @__PURE__ */ new Map();
2982
+ for (const members of pools) {
2983
+ const label = mostDeclaredTag(members) ?? members[0].name;
2984
+ let name = label;
2985
+ for (let n = 2; named.has(name); n++) name = `${label}~${n}`;
2986
+ named.set(
2987
+ name,
2988
+ members.map((m) => m.value)
2989
+ );
2990
+ }
2991
+ return named;
2944
2992
  }
2945
- function buildPools(sharedExternals, useAutoExternalPooling, log) {
2993
+ function mostDeclaredTag(members) {
2994
+ const counts = /* @__PURE__ */ new Map();
2995
+ for (const member of members)
2996
+ for (const edge of member.tags) counts.set(edge.tag, (counts.get(edge.tag) ?? 0) + 1);
2997
+ let best;
2998
+ for (const [tag, count] of counts) {
2999
+ const top = best === void 0 ? 0 : counts.get(best);
3000
+ if (count > top || count === top && tag.localeCompare(best) < 0) best = tag;
3001
+ }
3002
+ return best;
3003
+ }
3004
+ function buildPools(sharedExternals, log) {
2946
3005
  const candidates = Object.entries(sharedExternals).map(
2947
3006
  ([name, external]) => ({
2948
3007
  name,
2949
- scope: autoScope(name, useAutoExternalPooling),
2950
3008
  tags: external.versions.flatMap(
2951
3009
  (v) => v.remotes.flatMap((r) => {
2952
3010
  const tag = r.pool?.trim();
2953
3011
  return tag ? [{ remote: r.name, tag }] : [];
2954
3012
  })
2955
3013
  ),
2956
- remotes: external.versions.flatMap((v) => v.remotes.map((r) => r.name)),
2957
3014
  value: { name, external }
2958
3015
  })
2959
3016
  );
@@ -2970,14 +3027,29 @@ function remotesInPool(members) {
2970
3027
  ...new Set(members.flatMap((m) => m.external.versions.flatMap((v) => v.remotes.map((r) => r.name))))
2971
3028
  ];
2972
3029
  }
2973
- function poolableScopes(config, repo) {
2974
- const { useAutoExternalPooling } = config.feature;
2975
- return {
2976
- useAutoExternalPooling,
2977
- scopes: repo.getScopes().filter(
2978
- (scope) => repo.scopeType(scope) !== "strict" && (useAutoExternalPooling || repo.hasPoolTag(scope))
2979
- )
2980
- };
3030
+ function poolableScopes(repo) {
3031
+ return repo.getScopes().filter((scope) => repo.scopeType(scope) !== "strict" && repo.hasPoolState(scope));
3032
+ }
3033
+ function syncPoolNames(sharedExternals, pools, repo, scope, skip = /* @__PURE__ */ new Set()) {
3034
+ const named = /* @__PURE__ */ new Map();
3035
+ for (const [name, members] of pools) for (const member of members) named.set(member.name, name);
3036
+ for (const [name, external] of Object.entries(sharedExternals)) {
3037
+ const pool = named.get(name);
3038
+ if (pool !== void 0 && skip.has(pool)) continue;
3039
+ if (external.poolName === pool) continue;
3040
+ const { poolName: _stale, ...rest } = external;
3041
+ repo.addOrUpdate(
3042
+ name,
3043
+ pool !== void 0 ? { ...rest, poolName: pool } : {
3044
+ ...rest,
3045
+ versions: external.versions.map((v) => ({
3046
+ ...v,
3047
+ remotes: v.remotes.map(({ poolCause: _cause, ...meta }) => meta)
3048
+ }))
3049
+ },
3050
+ scope
3051
+ );
3052
+ }
2981
3053
  }
2982
3054
 
2983
3055
  // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
@@ -3011,7 +3083,11 @@ function servedPerRemote(consumed, serving, basis) {
3011
3083
  }
3012
3084
  return served;
3013
3085
  }
3014
- var anyAnchorStored = (members) => members.some((m) => m.external.versions.some((v) => v.remotes.some((r) => r.servedBy !== void 0)));
3086
+ var anyVerdictStored = (members) => members.some(
3087
+ (m) => m.external.versions.some(
3088
+ (v) => v.remotes.some((r) => r.servedBy !== void 0 || r.poolCause !== void 0)
3089
+ )
3090
+ );
3015
3091
  function findTornRemotes(members, islanded, basis, served, hosts) {
3016
3092
  const { suspect, selfServed } = movedRemotes(members, islanded, basis, served, hosts);
3017
3093
  if (suspect.size === 0) return [];
@@ -3082,20 +3158,18 @@ function movedRemotes(members, islanded, basis, served, hosts) {
3082
3158
  }
3083
3159
  function createPoolSharedExternals(config, ports) {
3084
3160
  return (touched) => {
3085
- const { useAutoExternalPooling, scopes } = poolableScopes(config, ports.sharedExternalsRepo);
3086
- for (const scope of scopes) {
3161
+ for (const scope of poolableScopes(ports.sharedExternalsRepo)) {
3087
3162
  const touchedInScope = touched?.get(scope);
3088
3163
  if (touched && !touchedInScope) continue;
3089
3164
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(scope);
3090
3165
  try {
3091
- for (const [poolName, members] of buildPools(
3092
- sharedExternals,
3093
- useAutoExternalPooling,
3094
- config.log
3095
- )) {
3166
+ const pools = buildPools(sharedExternals, config.log);
3167
+ const rebuilt = /* @__PURE__ */ new Set();
3168
+ for (const [poolName, members] of pools) {
3096
3169
  if (touchedInScope && !members.some((m) => touchedInScope.has(m.name))) continue;
3097
- poolFamily(poolName, members, scope);
3170
+ if (poolFamily(poolName, members, scope)) rebuilt.add(poolName);
3098
3171
  }
3172
+ syncPoolNames(sharedExternals, pools, ports.sharedExternalsRepo, scope, rebuilt);
3099
3173
  } catch (error) {
3100
3174
  if (error instanceof NFError) return Promise.reject(error);
3101
3175
  config.log.error(3, `[${scope}] failed to pool shared externals.`, {
@@ -3110,9 +3184,19 @@ function createPoolSharedExternals(config, ports) {
3110
3184
  return Promise.resolve();
3111
3185
  };
3112
3186
  function poolFamily(poolName, members, scope) {
3113
- if (members.length < 2) return;
3187
+ if (members.length < 2) return false;
3114
3188
  const allRemotes = remotesInPool(members);
3115
- if (allRemotes.length < 2) return;
3189
+ if (allRemotes.length < 2) {
3190
+ if (!anyVerdictStored(members)) return false;
3191
+ const basis2 = basisFor(members, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
3192
+ for (const member of members)
3193
+ ports.sharedExternalsRepo.addOrUpdate(
3194
+ member.name,
3195
+ rebuildMember(poolName, member, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), basis2.get(member.name)),
3196
+ scope
3197
+ );
3198
+ return true;
3199
+ }
3116
3200
  const islanded = islandedRemotes(members);
3117
3201
  const consumed = consumedMembers(members);
3118
3202
  const hosts = hostRemotes(members);
@@ -3132,7 +3216,7 @@ function createPoolSharedExternals(config, ports) {
3132
3216
  return { serving: serving2, basis: basis2, served: servedPerRemote(consumed, serving2, basis2) };
3133
3217
  };
3134
3218
  let { serving, basis, served } = assign();
3135
- if (islanded.size === 0 && serving.size === 0 && !anyAnchorStored(members)) return;
3219
+ if (islanded.size === 0 && serving.size === 0 && !anyVerdictStored(members)) return false;
3136
3220
  for (; ; ) {
3137
3221
  const torn = findTornRemotes(members, islanded, basis, served, hosts);
3138
3222
  if (torn.length === 0) break;
@@ -3153,10 +3237,11 @@ function createPoolSharedExternals(config, ports) {
3153
3237
  config.log.warn(3, `[${scope}][pool:${poolName}] ${islandWarning(remote, cause, scoped)}`);
3154
3238
  }
3155
3239
  for (const member of members) {
3156
- const rebuilt = rebuildMember(member, islanded, served, basis.get(member.name));
3240
+ const rebuilt = rebuildMember(poolName, member, islanded, served, basis.get(member.name));
3157
3241
  warnIfScopedOnly(poolName, member, rebuilt, islanded, scope);
3158
3242
  ports.sharedExternalsRepo.addOrUpdate(member.name, rebuilt, scope);
3159
3243
  }
3244
+ return true;
3160
3245
  }
3161
3246
  function assignServingBuilds(members, islanded, consumed, hosts, views) {
3162
3247
  for (; ; ) {
@@ -3220,7 +3305,7 @@ function createPoolSharedExternals(config, ports) {
3220
3305
  `[${scope}][pool:${poolName}] '${member.name}' is scoped-only \u2014 no coherent shared build provides it; ${consumers} remotes download their own copy.`
3221
3306
  );
3222
3307
  }
3223
- function rebuildMember(member, islanded, served, basis) {
3308
+ function rebuildMember(poolName, member, islanded, served, basis) {
3224
3309
  let scoped = [];
3225
3310
  let clean = [];
3226
3311
  for (const version of member.external.versions) {
@@ -3240,11 +3325,14 @@ function createPoolSharedExternals(config, ports) {
3240
3325
  const build = served.get(entry.remote)?.get(member.name);
3241
3326
  if (build !== void 0) entry.meta.servedBy = build;
3242
3327
  else delete entry.meta.servedBy;
3328
+ delete entry.meta.poolCause;
3243
3329
  }
3244
3330
  if (basis === void 0) {
3245
3331
  scoped = [...scoped, ...clean.filter((e) => e.meta.servedBy === void 0)];
3246
3332
  clean = clean.filter((e) => e.meta.servedBy !== void 0).map((e) => ({ ...e, action: "skip" }));
3247
3333
  }
3334
+ for (const entry of scoped)
3335
+ entry.meta.poolCause = islanded.get(entry.remote)?.kind ?? "unshared";
3248
3336
  const shareEntries = clean.filter((e) => e.action === "share").sort((a, b) => Number(b.remote === basis) - Number(a.remote === basis));
3249
3337
  const shareVersion = shareEntries.length > 0 ? [
3250
3338
  {
@@ -3270,6 +3358,7 @@ function createPoolSharedExternals(config, ports) {
3270
3358
  };
3271
3359
  return {
3272
3360
  dirty: false,
3361
+ poolName,
3273
3362
  versions: [
3274
3363
  ...shareVersion,
3275
3364
  ...byTag(
@@ -3295,14 +3384,27 @@ function islandWarning(remote, cause, scoped) {
3295
3384
  }
3296
3385
 
3297
3386
  // src/lib/core/2.app/steps/pooling/mark-pools-for-reelection.ts
3387
+ function clearPoolState(external) {
3388
+ let cleared = external.poolName !== void 0;
3389
+ delete external.poolName;
3390
+ for (const version of external.versions)
3391
+ for (const meta of version.remotes) {
3392
+ if (meta.servedBy !== void 0 || meta.poolCause !== void 0) cleared = true;
3393
+ delete meta.servedBy;
3394
+ delete meta.poolCause;
3395
+ }
3396
+ return cleared;
3397
+ }
3298
3398
  function createMarkPoolsForReelection(config, ports) {
3299
3399
  return () => {
3300
- const { useAutoExternalPooling, scopes } = poolableScopes(config, ports.sharedExternalsRepo);
3301
- for (const scope of scopes) {
3400
+ for (const scope of poolableScopes(ports.sharedExternalsRepo)) {
3302
3401
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(scope);
3303
3402
  if (!Object.values(sharedExternals).some((external) => external.dirty)) continue;
3304
3403
  let spread = 0;
3305
- for (const [, members] of buildPools(sharedExternals, useAutoExternalPooling)) {
3404
+ let unpooled = 0;
3405
+ const pooled = /* @__PURE__ */ new Set();
3406
+ for (const [, members] of buildPools(sharedExternals)) {
3407
+ for (const member of members) pooled.add(member.name);
3306
3408
  if (!members.some((m) => m.external.dirty)) continue;
3307
3409
  for (const member of members)
3308
3410
  if (!member.external.dirty) {
@@ -3310,14 +3412,24 @@ function createMarkPoolsForReelection(config, ports) {
3310
3412
  spread++;
3311
3413
  }
3312
3414
  }
3415
+ for (const [name, external] of Object.entries(sharedExternals)) {
3416
+ if (pooled.has(name) || !clearPoolState(external)) continue;
3417
+ external.dirty = true;
3418
+ unpooled++;
3419
+ }
3313
3420
  if (spread > 0)
3314
3421
  config.log.debug(3, `[${scope}] ${spread} pool member(s) marked dirty for re-election.`);
3422
+ if (unpooled > 0)
3423
+ config.log.debug(
3424
+ 3,
3425
+ `[${scope}] ${unpooled} external(s) left every pool; cleared their pool state for re-election.`
3426
+ );
3315
3427
  }
3316
3428
  return Promise.resolve();
3317
3429
  };
3318
3430
  }
3319
3431
 
3320
- // node_modules/.pnpm/@softarc+native-federation@4.4.0_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
3432
+ // node_modules/.pnpm/@softarc+native-federation@4.7.0/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
3321
3433
  var CHUNK_PREFIX = "@nf-internal";
3322
3434
  function toChunkImport(fileName) {
3323
3435
  if (fileName.startsWith("./")) {
@@ -3922,7 +4034,6 @@ function createUpdateCache(config, ports) {
3922
4034
  // src/lib/core/2.app/steps/pooling/pool-dynamic-externals.ts
3923
4035
  function createPoolDynamicExternals(config, ports) {
3924
4036
  return ({ entry, actions }) => {
3925
- const { useAutoExternalPooling } = config.feature;
3926
4037
  const declared = /* @__PURE__ */ new Map();
3927
4038
  for (const external of entry.shared ?? []) {
3928
4039
  const name = external.packageName;
@@ -3934,18 +4045,22 @@ function createPoolDynamicExternals(config, ports) {
3934
4045
  names.add(name);
3935
4046
  }
3936
4047
  if (declared.size === 0) return Promise.resolve({ entry, actions });
3937
- const scope = (name) => {
4048
+ let verdicts = /* @__PURE__ */ new Map();
4049
+ const scope = (name, cause) => {
3938
4050
  actions[name].action = "scope";
3939
4051
  delete actions[name].override;
4052
+ verdicts.set(name, { cause });
3940
4053
  };
3941
4054
  for (const [shareScope, names] of declared) {
3942
- if (!useAutoExternalPooling && !ports.sharedExternalsRepo.hasPoolTag(shareScope)) continue;
4055
+ if (!ports.sharedExternalsRepo.hasPoolState(shareScope)) continue;
3943
4056
  const committed = ports.sharedExternalsRepo.getFromScope(shareScope);
3944
- for (const pool of buildPools(committed, useAutoExternalPooling).values()) {
4057
+ const pools = buildPools(committed);
4058
+ verdicts = /* @__PURE__ */ new Map();
4059
+ for (const pool of pools.values()) {
3945
4060
  const mine = pool.filter((member) => names.has(member.name));
3946
4061
  if (mine.length === 0) continue;
3947
4062
  if (mine.some((member) => actions[member.name].action === "scope")) {
3948
- mine.forEach((member) => scope(member.name));
4063
+ mine.forEach((member) => scope(member.name, "incompatible"));
3949
4064
  continue;
3950
4065
  }
3951
4066
  const asked = gateViews(entry.name, pool);
@@ -3958,11 +4073,18 @@ function createPoolDynamicExternals(config, ports) {
3958
4073
  8,
3959
4074
  `[${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.`
3960
4075
  );
3961
- mine.forEach((member) => scope(member.name));
4076
+ mine.forEach((member) => scope(member.name, "uncovered"));
3962
4077
  continue;
3963
4078
  }
3964
- redirect(entry.name, anchor, pool, asked.view, actions, shareScope);
4079
+ for (const name of redirect(entry.name, anchor, pool, asked.view, actions, shareScope))
4080
+ verdicts.set(name, { servedBy: anchor });
3965
4081
  }
4082
+ const written = {};
4083
+ for (const [name, verdict] of verdicts) {
4084
+ written[name] = recordVerdict(committed[name], entry.name, verdict);
4085
+ ports.sharedExternalsRepo.addOrUpdate(name, written[name], shareScope);
4086
+ }
4087
+ syncPoolNames({ ...committed, ...written }, pools, ports.sharedExternalsRepo, shareScope);
3966
4088
  }
3967
4089
  return Promise.resolve({ entry, actions });
3968
4090
  };
@@ -4027,8 +4149,10 @@ function createPoolDynamicExternals(config, ports) {
4027
4149
  8,
4028
4150
  `[${shareScope}][${remote}] '${anchor}' is not in the cache, so its files cannot be mapped.`
4029
4151
  );
4030
- return;
4152
+ return [];
4031
4153
  }
4154
+ const basis = basisPerMember(pool);
4155
+ const served = [];
4032
4156
  for (const member of pool) {
4033
4157
  const action = actions[member.name];
4034
4158
  if (!action || action.action !== "skip") continue;
@@ -4044,7 +4168,42 @@ function createPoolDynamicExternals(config, ports) {
4044
4168
  }
4045
4169
  action.covered = specifiers;
4046
4170
  if (Object.keys(override).length > 0) action.override = override;
4171
+ if (basis.get(member.name) !== anchor) served.push(member.name);
4172
+ }
4173
+ return served;
4174
+ }
4175
+ function recordVerdict(external, remote, verdict) {
4176
+ if ("servedBy" in verdict) {
4177
+ return {
4178
+ ...external,
4179
+ versions: external.versions.map((v) => ({
4180
+ ...v,
4181
+ remotes: v.remotes.map(
4182
+ (r) => r.name === remote ? { ...r, servedBy: verdict.servedBy } : r
4183
+ )
4184
+ }))
4185
+ };
4047
4186
  }
4187
+ const moved = [];
4188
+ const versions = external.versions.map((v) => {
4189
+ const own = v.remotes.find((r) => r.name === remote);
4190
+ if (!own) return v;
4191
+ const { servedBy: _anchor, ...rest } = own;
4192
+ const meta = { ...rest, cached: true, poolCause: verdict.cause };
4193
+ if (v.action === "scope")
4194
+ return { ...v, remotes: v.remotes.map((r) => r === own ? meta : r) };
4195
+ moved.push({ tag: v.tag, meta });
4196
+ return { ...v, remotes: v.remotes.filter((r) => r !== own) };
4197
+ }).filter((v) => v.remotes.length > 0);
4198
+ for (const { tag, meta } of moved) {
4199
+ const at = versions.findIndex((v) => v.tag === tag && v.action === "scope");
4200
+ if (at >= 0) versions[at] = { ...versions[at], remotes: [...versions[at].remotes, meta] };
4201
+ else versions.push({ tag, action: "scope", host: false, remotes: [meta] });
4202
+ }
4203
+ return {
4204
+ ...external,
4205
+ versions: versions.sort((a, b) => ports.versionCheck.compare(b.tag, a.tag))
4206
+ };
4048
4207
  }
4049
4208
  }
4050
4209