@bleedingdev/modern-js-federation-runtime 0.0.0 → 3.9.0-ultramodern.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/cjs/module-federation/consume-surface.js +199 -0
- package/dist/cjs/module-federation/distributed-ssr.js +135 -0
- package/dist/cjs/module-federation/index.js +316 -0
- package/dist/cjs/module-federation/last-known-good.js +157 -0
- package/dist/cjs/module-federation/manifest-recovery-runtime-plugin.js +130 -0
- package/dist/cjs/module-federation/surface-resolution-types.js +61 -0
- package/dist/esm/module-federation/consume-surface.mjs +158 -0
- package/dist/esm/module-federation/distributed-ssr.mjs +82 -0
- package/dist/esm/module-federation/index.mjs +115 -0
- package/dist/esm/module-federation/last-known-good.mjs +113 -0
- package/dist/esm/module-federation/manifest-recovery-runtime-plugin.mjs +90 -0
- package/dist/esm/module-federation/surface-resolution-types.mjs +8 -0
- package/dist/esm-node/module-federation/consume-surface.mjs +159 -0
- package/dist/esm-node/module-federation/distributed-ssr.mjs +83 -0
- package/dist/esm-node/module-federation/index.mjs +116 -0
- package/dist/esm-node/module-federation/last-known-good.mjs +114 -0
- package/dist/esm-node/module-federation/manifest-recovery-runtime-plugin.mjs +91 -0
- package/dist/esm-node/module-federation/surface-resolution-types.mjs +9 -0
- package/dist/types/module-federation/consume-surface.d.ts +104 -0
- package/dist/types/module-federation/distributed-ssr.d.ts +61 -0
- package/dist/types/module-federation/index.d.ts +71 -0
- package/dist/types/module-federation/last-known-good.d.ts +52 -0
- package/dist/types/module-federation/manifest-recovery-runtime-plugin.d.ts +25 -0
- package/dist/types/module-federation/surface-resolution-types.d.ts +23 -0
- package/package.json +86 -4
- package/src/module-federation/consume-surface.ts +369 -0
- package/src/module-federation/distributed-ssr.tsx +240 -0
- package/src/module-federation/index.ts +274 -0
- package/src/module-federation/last-known-good.ts +292 -0
- package/src/module-federation/manifest-recovery-runtime-plugin.ts +215 -0
- package/src/module-federation/surface-resolution-types.ts +60 -0
- package/README.md +0 -3
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface-resolution types for the module-federation runtime.
|
|
3
|
+
*
|
|
4
|
+
* Thin re-export of the canonical module in `@modern-js/surface-resolution`
|
|
5
|
+
* (`packages/toolkit/surface-resolution/src/surface-resolution/`,
|
|
6
|
+
* RESOLUTION-0001). Kept as a file so existing `./surface-resolution-types`
|
|
7
|
+
* imports stay stable. Only helpers the canonical module deliberately does not
|
|
8
|
+
* ship (the (ref, env) cache key) live here, implemented against canonical
|
|
9
|
+
* types.
|
|
10
|
+
*/
|
|
11
|
+
import { type EnvironmentId, type ParsedSurfaceRef } from '@modern-js/surface-resolution';
|
|
12
|
+
export { type CompatibilityStatus, type CompatibilityVerdict, createDiscoveryError, type DiscoveryError, type DiscoveryErrorCode, type DiscoveryResult, type EnvironmentId, formatSurfaceRef, type ParsedSurfaceRef, parseSurfaceRef, type ResolvedDeliveryUnit, type ResolvedSurface, type ResolvedSurfaceKind, type ResolvedSurfaceLocation, type ResolvedSurfaceLocationPlatform, type SurfaceRefParseError, type SurfaceRefParseResult, type SurfaceResolutionProvider, validateSurfaceRef, } from '@modern-js/surface-resolution';
|
|
13
|
+
/** Stable cache/identity key for a (ref, env) pair. */
|
|
14
|
+
export declare function surfaceResolutionKey(ref: ParsedSurfaceRef, env: EnvironmentId): string;
|
|
15
|
+
/**
|
|
16
|
+
* Stable cache key for a (deliveryUnit, env) pair — ONE record per unit per
|
|
17
|
+
* env (ADR-0019 atomicity). All surfaces of a unit share this key so a refresh
|
|
18
|
+
* replaces the whole unit snapshot atomically for every surface, and two
|
|
19
|
+
* surfaces of the same unit can never be served from different cached
|
|
20
|
+
* snapshots. The ref's external-major participates in lookup validation (see
|
|
21
|
+
* the LKG provider), not in this key.
|
|
22
|
+
*/
|
|
23
|
+
export declare function deliveryUnitResolutionKey(unitId: string, env: EnvironmentId): string;
|
package/package.json
CHANGED
|
@@ -1,16 +1,98 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bleedingdev/modern-js-federation-runtime",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.9.0-ultramodern.5",
|
|
4
|
+
"description": "Federated surface consumption, distributed SSR, and bounded manifest recovery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/BleedingDev/ultramodern.js.git"
|
|
8
|
+
"url": "git+https://github.com/BleedingDev/ultramodern.js.git",
|
|
9
|
+
"directory": "packages/runtime/federation-runtime"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=26.7.0"
|
|
13
|
+
},
|
|
14
|
+
"types": "./dist/types/module-federation/index.d.ts",
|
|
15
|
+
"main": "./dist/cjs/module-federation/index.js",
|
|
16
|
+
"module": "./dist/esm/module-federation/index.mjs",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/types/module-federation/index.d.ts",
|
|
20
|
+
"node": {
|
|
21
|
+
"import": "./dist/esm-node/module-federation/index.mjs",
|
|
22
|
+
"require": "./dist/cjs/module-federation/index.js"
|
|
23
|
+
},
|
|
24
|
+
"default": "./dist/esm/module-federation/index.mjs"
|
|
25
|
+
},
|
|
26
|
+
"./distributed-ssr": {
|
|
27
|
+
"types": "./dist/types/module-federation/distributed-ssr.d.ts",
|
|
28
|
+
"node": {
|
|
29
|
+
"import": "./dist/esm-node/module-federation/distributed-ssr.mjs",
|
|
30
|
+
"require": "./dist/cjs/module-federation/distributed-ssr.js"
|
|
31
|
+
},
|
|
32
|
+
"default": "./dist/esm/module-federation/distributed-ssr.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./manifest-recovery-runtime-plugin": {
|
|
35
|
+
"types": "./dist/types/module-federation/manifest-recovery-runtime-plugin.d.ts",
|
|
36
|
+
"node": {
|
|
37
|
+
"import": "./dist/esm-node/module-federation/manifest-recovery-runtime-plugin.mjs",
|
|
38
|
+
"require": "./dist/cjs/module-federation/manifest-recovery-runtime-plugin.js"
|
|
39
|
+
},
|
|
40
|
+
"default": "./dist/esm/module-federation/manifest-recovery-runtime-plugin.mjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"typesVersions": {
|
|
44
|
+
"*": {
|
|
45
|
+
".": [
|
|
46
|
+
"./dist/types/module-federation/index.d.ts",
|
|
47
|
+
"./src/module-federation/index.ts"
|
|
48
|
+
],
|
|
49
|
+
"distributed-ssr": [
|
|
50
|
+
"./dist/types/module-federation/distributed-ssr.d.ts",
|
|
51
|
+
"./src/module-federation/distributed-ssr.tsx"
|
|
52
|
+
],
|
|
53
|
+
"manifest-recovery-runtime-plugin": [
|
|
54
|
+
"./dist/types/module-federation/manifest-recovery-runtime-plugin.d.ts",
|
|
55
|
+
"./src/module-federation/manifest-recovery-runtime-plugin.ts"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
9
58
|
},
|
|
10
59
|
"files": [
|
|
11
|
-
"
|
|
60
|
+
"dist",
|
|
61
|
+
"src"
|
|
12
62
|
],
|
|
63
|
+
"sideEffects": false,
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.0-ultramodern.5"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"@modern-js/runtime": "3.9.0-ultramodern.5",
|
|
69
|
+
"react": "^19.2.8"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.5",
|
|
73
|
+
"@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.5",
|
|
74
|
+
"@rsbuild/core": "2.2.3",
|
|
75
|
+
"@rslib/core": "1.0.0",
|
|
76
|
+
"@scripts/rstest-config": "2.66.0",
|
|
77
|
+
"@types/node": "^26.4.1",
|
|
78
|
+
"@types/react": "^19.2.18",
|
|
79
|
+
"@types/react-dom": "^19.2.7",
|
|
80
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
81
|
+
"esbuild": "^0.28.2",
|
|
82
|
+
"react": "^19.2.8",
|
|
83
|
+
"react-dom": "^19.2.8",
|
|
84
|
+
"typescript": "^7.0.2"
|
|
85
|
+
},
|
|
13
86
|
"publishConfig": {
|
|
14
87
|
"access": "public"
|
|
88
|
+
},
|
|
89
|
+
"scripts": {
|
|
90
|
+
"dev": "rslib build --watch",
|
|
91
|
+
"build": "rslib build",
|
|
92
|
+
"test": "rstest run"
|
|
93
|
+
},
|
|
94
|
+
"homepage": "https://github.com/BleedingDev/ultramodern.js#readme",
|
|
95
|
+
"bugs": {
|
|
96
|
+
"url": "https://github.com/BleedingDev/ultramodern.js/issues"
|
|
15
97
|
}
|
|
16
98
|
}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
// @effect-diagnostics asyncFunction:off strictBooleanExpressions:off
|
|
2
|
+
/**
|
|
3
|
+
* G22 — mandatory degraded consumption API.
|
|
4
|
+
*
|
|
5
|
+
* CONTEXT.md (Degraded State): "consuming a surface obliges the consumer to
|
|
6
|
+
* define a degraded state". This module turns that obligation into a
|
|
7
|
+
* compile-level one: {@link SurfaceConsumerOptions.degraded} is a REQUIRED
|
|
8
|
+
* field, so a call site that forgets its fallback does not type-check.
|
|
9
|
+
*
|
|
10
|
+
* The consumer wraps the loadRemote-ish flow (resolve a SurfaceRef through a
|
|
11
|
+
* provider, then load the execution artifact from the resolved record) and
|
|
12
|
+
* guarantees isolation: any discovery/load failure is caught, classified,
|
|
13
|
+
* reported through the existing MF fallback telemetry, and handed to the
|
|
14
|
+
* required degraded handler.
|
|
15
|
+
*
|
|
16
|
+
* Criticality (owner-default 5: unclassified consumption is `critical`). For a
|
|
17
|
+
* `noncritical` consumption the degraded handler's result is returned and the
|
|
18
|
+
* failure is swallowed, so one failing consumption can never propagate to a
|
|
19
|
+
* sibling (CONTEXT.md: faults isolate at the vertical boundary). For a
|
|
20
|
+
* `critical` consumption the degraded handler still runs — telemetry and
|
|
21
|
+
* fallback-UI obligations hold — but the returned promise then REJECTS with the
|
|
22
|
+
* typed failure so callers and rollout machinery observe it. Critical
|
|
23
|
+
* consumption during a rollout window should be driven by expand/contract, not
|
|
24
|
+
* by silent degrade.
|
|
25
|
+
*/
|
|
26
|
+
import {
|
|
27
|
+
classifyModuleFederationFallback,
|
|
28
|
+
createModuleFederationFallbackTelemetry,
|
|
29
|
+
emitModuleFederationFallbackTelemetry,
|
|
30
|
+
type ModuleFederationFallbackClassification,
|
|
31
|
+
type ModuleFederationFallbackTelemetryEmitOptions,
|
|
32
|
+
type ModuleFederationFallbackTelemetryPayload,
|
|
33
|
+
} from './index';
|
|
34
|
+
import {
|
|
35
|
+
createDiscoveryError,
|
|
36
|
+
type DiscoveryError,
|
|
37
|
+
type DiscoveryErrorCode,
|
|
38
|
+
type DiscoveryResult,
|
|
39
|
+
type EnvironmentId,
|
|
40
|
+
formatSurfaceRef,
|
|
41
|
+
type ParsedSurfaceRef,
|
|
42
|
+
parseSurfaceRef,
|
|
43
|
+
type ResolvedDeliveryUnit,
|
|
44
|
+
type SurfaceResolutionProvider,
|
|
45
|
+
validateSurfaceRef,
|
|
46
|
+
} from './surface-resolution-types';
|
|
47
|
+
|
|
48
|
+
/** Where in the consumption lifecycle a failure was raised. */
|
|
49
|
+
export type SurfaceConsumptionPhase = 'discovery' | 'load' | 'mount';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The typed failure surfaced to the degraded handler. Exactly one of
|
|
53
|
+
* `discoveryError` (resolver returned a typed {@link DiscoveryError}) or
|
|
54
|
+
* `error` (something threw) is populated for a given failure.
|
|
55
|
+
*/
|
|
56
|
+
export type SurfaceConsumptionFailure = {
|
|
57
|
+
ref: ParsedSurfaceRef;
|
|
58
|
+
env: EnvironmentId;
|
|
59
|
+
phase: SurfaceConsumptionPhase;
|
|
60
|
+
classification: ModuleFederationFallbackClassification;
|
|
61
|
+
/** Present when discovery returned a typed error state. */
|
|
62
|
+
discoveryError?: DiscoveryError;
|
|
63
|
+
/** Present when a thrown error caused the failure. */
|
|
64
|
+
error?: unknown;
|
|
65
|
+
/** The resolved record, when discovery succeeded but load/mount failed. */
|
|
66
|
+
resolved?: ResolvedDeliveryUnit;
|
|
67
|
+
/** The fallback telemetry payload emitted for this failure. */
|
|
68
|
+
telemetry: ModuleFederationFallbackTelemetryPayload;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type SurfaceLoadInput = {
|
|
72
|
+
resolved: ResolvedDeliveryUnit;
|
|
73
|
+
ref: ParsedSurfaceRef;
|
|
74
|
+
env: EnvironmentId;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type SurfaceConsumerOptions<T> = {
|
|
78
|
+
/** The logical surface to consume (object or canonical string form). */
|
|
79
|
+
ref: ParsedSurfaceRef | string;
|
|
80
|
+
env: EnvironmentId;
|
|
81
|
+
/** The pluggable resolution seam (RESOLUTION-0001 §2.2). */
|
|
82
|
+
provider: SurfaceResolutionProvider;
|
|
83
|
+
/** Consumer app name, used for telemetry attribution. */
|
|
84
|
+
appName: string;
|
|
85
|
+
/**
|
|
86
|
+
* Loads the execution artifact from a resolved record — the loadRemote-ish
|
|
87
|
+
* flow. Receives the whole {@link ResolvedDeliveryUnit} so the adapter reads
|
|
88
|
+
* locations under one buildMarker (RESOLUTION-0001 §2.3).
|
|
89
|
+
*/
|
|
90
|
+
load: (input: SurfaceLoadInput) => Promise<T> | T;
|
|
91
|
+
/**
|
|
92
|
+
* REQUIRED degraded handler (G22 compile-level obligation). There is no
|
|
93
|
+
* default and no way to opt out at the type level: omitting this field is a
|
|
94
|
+
* compile error. Return the value to serve while the surface is unavailable
|
|
95
|
+
* or incompatible.
|
|
96
|
+
*/
|
|
97
|
+
degraded: (failure: SurfaceConsumptionFailure) => T | Promise<T>;
|
|
98
|
+
/**
|
|
99
|
+
* Consumption criticality (owner-default 5). Defaults to `'critical'` when
|
|
100
|
+
* omitted.
|
|
101
|
+
*
|
|
102
|
+
* - `'noncritical'`: the degraded handler's result is returned and the
|
|
103
|
+
* failure is swallowed — the promise never rejects.
|
|
104
|
+
* - `'critical'`: the degraded handler still runs (telemetry + fallback-UI
|
|
105
|
+
* obligations hold), but the returned promise then REJECTS with the typed
|
|
106
|
+
* failure so callers / rollout machinery observe it. Prefer expand/contract
|
|
107
|
+
* over silent degrade for critical surfaces during rollout windows.
|
|
108
|
+
*/
|
|
109
|
+
classification?: 'critical' | 'noncritical';
|
|
110
|
+
/** Optional telemetry emission options (endpoint/auth/fetch overrides). */
|
|
111
|
+
telemetry?: ModuleFederationFallbackTelemetryEmitOptions;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** Map a typed discovery-error code onto the MF fallback classification space. */
|
|
115
|
+
function classifyDiscoveryError(
|
|
116
|
+
code: DiscoveryErrorCode,
|
|
117
|
+
): ModuleFederationFallbackClassification {
|
|
118
|
+
switch (code) {
|
|
119
|
+
case 'major-not-published':
|
|
120
|
+
case 'stale-record':
|
|
121
|
+
return 'version-skew';
|
|
122
|
+
case 'identity-mismatch':
|
|
123
|
+
return 'contract';
|
|
124
|
+
case 'unknown-unit':
|
|
125
|
+
case 'unknown-surface':
|
|
126
|
+
case 'provider-unavailable':
|
|
127
|
+
return 'remote-unavailable';
|
|
128
|
+
default: {
|
|
129
|
+
// Exhaustiveness guard: a new code must be classified explicitly.
|
|
130
|
+
const _never: never = code;
|
|
131
|
+
return 'remote-unavailable';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Canonical string form of a ref, without the never-throws guarantee of
|
|
138
|
+
* consumeSurface depending on the caller having passed a grammatically valid
|
|
139
|
+
* object ref (canonical formatSurfaceRef throws on invalid refs).
|
|
140
|
+
*/
|
|
141
|
+
function toRemoteString(ref: ParsedSurfaceRef): string {
|
|
142
|
+
return validateSurfaceRef(ref) === undefined
|
|
143
|
+
? formatSurfaceRef(ref)
|
|
144
|
+
: `${ref.unitId}#${ref.surfaceId}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function reportFailure<T>(
|
|
148
|
+
options: SurfaceConsumerOptions<T>,
|
|
149
|
+
ref: ParsedSurfaceRef,
|
|
150
|
+
base: {
|
|
151
|
+
phase: SurfaceConsumptionPhase;
|
|
152
|
+
classification: ModuleFederationFallbackClassification;
|
|
153
|
+
discoveryError?: DiscoveryError;
|
|
154
|
+
error?: unknown;
|
|
155
|
+
resolved?: ResolvedDeliveryUnit;
|
|
156
|
+
},
|
|
157
|
+
): Promise<T> {
|
|
158
|
+
const remote = toRemoteString(ref);
|
|
159
|
+
const telemetry = createModuleFederationFallbackTelemetry({
|
|
160
|
+
appName: options.appName,
|
|
161
|
+
classification: base.classification,
|
|
162
|
+
error: base.error ?? base.discoveryError,
|
|
163
|
+
metadata: {
|
|
164
|
+
env: options.env,
|
|
165
|
+
surfaceRef: remote,
|
|
166
|
+
...(base.discoveryError
|
|
167
|
+
? { discoveryErrorCode: base.discoveryError.code }
|
|
168
|
+
: {}),
|
|
169
|
+
...(base.resolved ? { buildMarker: base.resolved.buildMarker } : {}),
|
|
170
|
+
},
|
|
171
|
+
phase: base.phase,
|
|
172
|
+
remote,
|
|
173
|
+
status: 'degraded',
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Fire-and-forget style, but awaited so tests can assert emission. Emission
|
|
177
|
+
// failures must never mask the degraded path.
|
|
178
|
+
try {
|
|
179
|
+
await emitModuleFederationFallbackTelemetry(
|
|
180
|
+
{
|
|
181
|
+
appName: options.appName,
|
|
182
|
+
classification: base.classification,
|
|
183
|
+
error: base.error ?? base.discoveryError,
|
|
184
|
+
metadata: telemetry.metadata,
|
|
185
|
+
phase: base.phase,
|
|
186
|
+
remote,
|
|
187
|
+
status: 'degraded',
|
|
188
|
+
},
|
|
189
|
+
options.telemetry ?? {},
|
|
190
|
+
);
|
|
191
|
+
} catch {
|
|
192
|
+
// Telemetry sink failure is itself a degraded condition; swallow it.
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const failure: SurfaceConsumptionFailure = {
|
|
196
|
+
ref,
|
|
197
|
+
env: options.env,
|
|
198
|
+
phase: base.phase,
|
|
199
|
+
classification: base.classification,
|
|
200
|
+
telemetry,
|
|
201
|
+
};
|
|
202
|
+
if (base.discoveryError) {
|
|
203
|
+
failure.discoveryError = base.discoveryError;
|
|
204
|
+
}
|
|
205
|
+
if (base.error !== undefined) {
|
|
206
|
+
failure.error = base.error;
|
|
207
|
+
}
|
|
208
|
+
if (base.resolved) {
|
|
209
|
+
failure.resolved = base.resolved;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const critical = (options.classification ?? 'critical') === 'critical';
|
|
213
|
+
|
|
214
|
+
// The ORIGINAL typed failure a critical consumption rejects with: the typed
|
|
215
|
+
// discovery error, else the thrown error, else a synthesized discovery error
|
|
216
|
+
// for the incompatible-verdict path (which carries neither).
|
|
217
|
+
const reason: unknown =
|
|
218
|
+
base.discoveryError ??
|
|
219
|
+
(base.error !== undefined
|
|
220
|
+
? base.error
|
|
221
|
+
: createDiscoveryError(
|
|
222
|
+
'identity-mismatch',
|
|
223
|
+
remote,
|
|
224
|
+
`resolved record for "${remote}" is incompatible`,
|
|
225
|
+
{ env: options.env },
|
|
226
|
+
));
|
|
227
|
+
|
|
228
|
+
let degradedValue: T;
|
|
229
|
+
try {
|
|
230
|
+
degradedValue = await options.degraded(failure);
|
|
231
|
+
} catch (handlerError) {
|
|
232
|
+
// Degraded-handler rejection containment: a throwing/rejecting degraded
|
|
233
|
+
// handler must not propagate unconditionally. Emit handler-failure
|
|
234
|
+
// telemetry, then for noncritical resolve undefined; for critical reject
|
|
235
|
+
// with the ORIGINAL typed error (never the handler's).
|
|
236
|
+
try {
|
|
237
|
+
await emitModuleFederationFallbackTelemetry(
|
|
238
|
+
{
|
|
239
|
+
appName: options.appName,
|
|
240
|
+
classification: base.classification,
|
|
241
|
+
error: handlerError,
|
|
242
|
+
metadata: { ...telemetry.metadata, degradedHandlerFailed: true },
|
|
243
|
+
phase: base.phase,
|
|
244
|
+
remote,
|
|
245
|
+
status: 'failed',
|
|
246
|
+
},
|
|
247
|
+
options.telemetry ?? {},
|
|
248
|
+
);
|
|
249
|
+
} catch {
|
|
250
|
+
// Telemetry sink failure must never mask the containment path.
|
|
251
|
+
}
|
|
252
|
+
if (critical) {
|
|
253
|
+
throw reason;
|
|
254
|
+
}
|
|
255
|
+
return undefined as T;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (critical) {
|
|
259
|
+
throw reason;
|
|
260
|
+
}
|
|
261
|
+
return degradedValue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Consume one surface with a mandatory degraded fallback. On any discovery or
|
|
266
|
+
* load failure the required degraded handler runs (telemetry + fallback UI).
|
|
267
|
+
*
|
|
268
|
+
* For a `noncritical` consumption (see {@link SurfaceConsumerOptions.classification})
|
|
269
|
+
* this resolves to the degraded value and never rejects, so a failing
|
|
270
|
+
* consumption is isolated from any sibling. For a `critical` consumption (the
|
|
271
|
+
* default) it rejects with the typed failure AFTER the degraded handler has
|
|
272
|
+
* run, so callers / rollout machinery observe the failure.
|
|
273
|
+
*/
|
|
274
|
+
export async function consumeSurface<T>(
|
|
275
|
+
options: SurfaceConsumerOptions<T>,
|
|
276
|
+
): Promise<T> {
|
|
277
|
+
let ref: ParsedSurfaceRef;
|
|
278
|
+
if (typeof options.ref === 'string') {
|
|
279
|
+
const parsed = parseSurfaceRef(options.ref);
|
|
280
|
+
if (!parsed.ok) {
|
|
281
|
+
// A malformed ref can never resolve; degrade without calling the
|
|
282
|
+
// provider (providers only accept grammatically valid refs).
|
|
283
|
+
const hashIndex = options.ref.indexOf('#');
|
|
284
|
+
const echoed: ParsedSurfaceRef =
|
|
285
|
+
hashIndex === -1
|
|
286
|
+
? { unitId: options.ref, surfaceId: '' }
|
|
287
|
+
: {
|
|
288
|
+
unitId: options.ref.slice(0, hashIndex),
|
|
289
|
+
surfaceId: options.ref.slice(hashIndex + 1),
|
|
290
|
+
};
|
|
291
|
+
return reportFailure(options, echoed, {
|
|
292
|
+
phase: 'discovery',
|
|
293
|
+
classification: 'remote-unavailable',
|
|
294
|
+
discoveryError: createDiscoveryError(
|
|
295
|
+
'unknown-surface',
|
|
296
|
+
options.ref,
|
|
297
|
+
`SurfaceRef "${options.ref}" is not a valid canonical reference (${parsed.error.code}).`,
|
|
298
|
+
{ parseError: parsed.error, env: options.env },
|
|
299
|
+
),
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
ref = parsed.ref;
|
|
303
|
+
} else {
|
|
304
|
+
ref = options.ref;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let resolution: DiscoveryResult;
|
|
308
|
+
try {
|
|
309
|
+
resolution = await options.provider.resolve(ref, options.env);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
return reportFailure(options, ref, {
|
|
312
|
+
phase: 'discovery',
|
|
313
|
+
classification: classifyModuleFederationFallback(error),
|
|
314
|
+
error,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (!resolution.ok) {
|
|
319
|
+
return reportFailure(options, ref, {
|
|
320
|
+
phase: 'discovery',
|
|
321
|
+
classification: classifyDiscoveryError(resolution.error.code),
|
|
322
|
+
discoveryError: resolution.error,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const resolved = resolution.unit;
|
|
327
|
+
|
|
328
|
+
// A resolver may hand back a record it already judged incompatible; treat it
|
|
329
|
+
// as a degraded consumption rather than loading against a bad contract.
|
|
330
|
+
if (resolved.compatibility.status === 'incompatible') {
|
|
331
|
+
return reportFailure(options, ref, {
|
|
332
|
+
phase: 'discovery',
|
|
333
|
+
classification: 'contract',
|
|
334
|
+
resolved,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
try {
|
|
339
|
+
return await options.load({ resolved, ref, env: options.env });
|
|
340
|
+
} catch (error) {
|
|
341
|
+
return reportFailure(options, ref, {
|
|
342
|
+
phase: 'load',
|
|
343
|
+
classification: classifyModuleFederationFallback(error),
|
|
344
|
+
error,
|
|
345
|
+
resolved,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Build a reusable consumer bound to a provider/env/app, still requiring a
|
|
352
|
+
* degraded handler per call. Per-call options override the bound base.
|
|
353
|
+
*/
|
|
354
|
+
export function createSurfaceConsumer(
|
|
355
|
+
base: Pick<
|
|
356
|
+
SurfaceConsumerOptions<unknown>,
|
|
357
|
+
'provider' | 'env' | 'appName' | 'telemetry'
|
|
358
|
+
>,
|
|
359
|
+
) {
|
|
360
|
+
return function consume<T>(
|
|
361
|
+
options: Omit<SurfaceConsumerOptions<T>, keyof typeof base> &
|
|
362
|
+
Partial<Pick<SurfaceConsumerOptions<T>, keyof typeof base>>,
|
|
363
|
+
): Promise<T> {
|
|
364
|
+
return consumeSurface<T>({
|
|
365
|
+
...base,
|
|
366
|
+
...options,
|
|
367
|
+
} as SurfaceConsumerOptions<T>);
|
|
368
|
+
};
|
|
369
|
+
}
|