@bleedingdev/modern-js-federation-runtime 3.9.0-ultramodern.11 → 3.9.0-ultramodern.13

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.
@@ -0,0 +1,71 @@
1
+ export declare const MODULE_FEDERATION_FALLBACK_SIGNAL_EVENT = "modernjs:mf-runtime-fallback";
2
+ export declare const MODULE_FEDERATION_RECOVERY_SIGNAL_EVENT = "modernjs:mf-runtime-recovery";
3
+ export declare const DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT = "/_modern/contract-gates/runtime-fallback";
4
+ export declare const DEFAULT_RUNTIME_FALLBACK_SIGNAL_AUTH_HEADER = "x-modernjs-runtime-signal-token";
5
+ export type ModuleFederationFallbackClassification = 'timeout' | 'network' | 'contract' | 'version-skew' | 'remote-unavailable';
6
+ export type ModuleFederationFallbackStatus = 'degraded' | 'failed' | 'recovered';
7
+ export type ModuleFederationFallbackTelemetryInput = {
8
+ appName: string;
9
+ classification: ModuleFederationFallbackClassification;
10
+ entry?: string;
11
+ error?: unknown;
12
+ eventName?: string;
13
+ exportName?: string;
14
+ metadata?: Record<string, unknown>;
15
+ phase: 'discovery' | 'load' | 'mount' | 'hydrate' | 'recover';
16
+ remote: string;
17
+ runtimeDigest?: string;
18
+ status?: ModuleFederationFallbackStatus;
19
+ };
20
+ export type ModuleFederationFallbackTelemetryPayload = {
21
+ appName: string;
22
+ entry?: string;
23
+ eventName: string;
24
+ phase: ModuleFederationFallbackTelemetryInput['phase'];
25
+ reason: ModuleFederationFallbackClassification;
26
+ runtimeDigest?: string;
27
+ schemaVersion: 1;
28
+ metadata: {
29
+ classification: ModuleFederationFallbackClassification;
30
+ errorMessage?: string;
31
+ errorName?: string;
32
+ exportName?: string;
33
+ remote: string;
34
+ runtimeDigest?: string;
35
+ status: ModuleFederationFallbackStatus;
36
+ } & Record<string, unknown>;
37
+ };
38
+ export type ModuleFederationFallbackTelemetryEmitOptions = {
39
+ authHeaderName?: string;
40
+ authToken?: string;
41
+ endpoint?: string;
42
+ fetchImpl?: typeof fetch;
43
+ postSignal?: boolean;
44
+ /** Positive reporting deadline; defaults to one second. */
45
+ timeoutMs?: number;
46
+ /** Observe the exact payload and await delivery explicitly in tests. */
47
+ observe?: (payload: ModuleFederationFallbackTelemetryPayload, delivery: Promise<ModuleFederationFallbackTelemetryEmitResult>) => void;
48
+ };
49
+ export type ModuleFederationFallbackTelemetryEmitResult = {
50
+ dispatched: boolean;
51
+ posted: boolean;
52
+ postStatus?: number;
53
+ };
54
+ export declare class ModuleFederationRemoteLoadTimeoutError extends Error {
55
+ constructor(remote: string, timeoutMs: number);
56
+ }
57
+ export declare class ModuleFederationRemoteLoadError extends Error {
58
+ readonly remote: string;
59
+ readonly attempts: number;
60
+ readonly causeError: Error;
61
+ constructor(remote: string, attempts: number, causeError: Error);
62
+ }
63
+ export declare class ModuleFederationRemoteComponentContractError extends Error {
64
+ constructor(remote: string, exportName: string);
65
+ }
66
+ export declare function classifyModuleFederationFallback(error: unknown): ModuleFederationFallbackClassification;
67
+ export declare function createModuleFederationFallbackTelemetry(input: ModuleFederationFallbackTelemetryInput): ModuleFederationFallbackTelemetryPayload;
68
+ export declare function toModuleFederationFallbackAttributes(payload: ModuleFederationFallbackTelemetryPayload): Record<string, string>;
69
+ export declare function emitModuleFederationFallbackTelemetry(input: ModuleFederationFallbackTelemetryInput, options?: ModuleFederationFallbackTelemetryEmitOptions): Promise<ModuleFederationFallbackTelemetryEmitResult>;
70
+ /** Dispatch once locally; remote delivery is bounded and best effort. */
71
+ export declare function emitModuleFederationFallbackPayload(payload: ModuleFederationFallbackTelemetryPayload, options?: ModuleFederationFallbackTelemetryEmitOptions): Promise<ModuleFederationFallbackTelemetryEmitResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bleedingdev/modern-js-federation-runtime",
3
- "version": "3.9.0-ultramodern.11",
3
+ "version": "3.9.0-ultramodern.13",
4
4
  "description": "Federated surface consumption, distributed SSR, and bounded manifest recovery.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -62,25 +62,25 @@
62
62
  ],
63
63
  "sideEffects": false,
64
64
  "dependencies": {
65
- "@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.0-ultramodern.11"
65
+ "@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.0-ultramodern.13"
66
66
  },
67
67
  "peerDependencies": {
68
- "@modern-js/runtime": "3.9.0-ultramodern.11",
68
+ "@modern-js/runtime": "3.9.0-ultramodern.13",
69
69
  "react": "^19.2.8"
70
70
  },
71
71
  "devDependencies": {
72
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.11",
73
- "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.11",
74
- "@rsbuild/core": "2.2.3",
75
- "@rslib/core": "1.0.0",
72
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.13",
73
+ "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.13",
74
+ "@rsbuild/core": "2.2.9",
75
+ "@rslib/core": "1.0.2",
76
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",
77
+ "@types/node": "^26.6.2",
78
+ "@types/react": "^19.3.0",
79
+ "@types/react-dom": "^19.3.0",
80
80
  "@typescript/native-preview": "7.0.0-dev.20260707.2",
81
81
  "esbuild": "^0.28.2",
82
- "react": "^19.2.8",
83
- "react-dom": "^19.2.8",
82
+ "react": "^19.3.0",
83
+ "react-dom": "^19.3.0",
84
84
  "typescript": "^7.0.2"
85
85
  },
86
86
  "publishConfig": {
@@ -23,14 +23,7 @@
23
23
  * consumption during a rollout window should be driven by expand/contract, not
24
24
  * by silent degrade.
25
25
  */
26
- import {
27
- classifyModuleFederationFallback,
28
- createModuleFederationFallbackTelemetry,
29
- emitModuleFederationFallbackTelemetry,
30
- type ModuleFederationFallbackClassification,
31
- type ModuleFederationFallbackTelemetryEmitOptions,
32
- type ModuleFederationFallbackTelemetryPayload,
33
- } from './index';
26
+
34
27
  import {
35
28
  createDiscoveryError,
36
29
  type DiscoveryError,
@@ -44,6 +37,15 @@ import {
44
37
  type SurfaceResolutionProvider,
45
38
  validateSurfaceRef,
46
39
  } from './surface-resolution-types';
40
+ import {
41
+ classifyModuleFederationFallback,
42
+ createModuleFederationFallbackTelemetry,
43
+ emitModuleFederationFallbackPayload,
44
+ emitModuleFederationFallbackTelemetry,
45
+ type ModuleFederationFallbackClassification,
46
+ type ModuleFederationFallbackTelemetryEmitOptions,
47
+ type ModuleFederationFallbackTelemetryPayload,
48
+ } from './telemetry';
47
49
 
48
50
  /** Where in the consumption lifecycle a failure was raised. */
49
51
  export type SurfaceConsumptionPhase = 'discovery' | 'load' | 'mount';
@@ -100,7 +102,7 @@ export type SurfaceConsumerOptions<T> = {
100
102
  * omitted.
101
103
  *
102
104
  * - `'noncritical'`: the degraded handler's result is returned and the
103
- * failure is swallowed — the promise never rejects.
105
+ * failure is swallowed — a failed degraded handler resolves undefined.
104
106
  * - `'critical'`: the degraded handler still runs (telemetry + fallback-UI
105
107
  * obligations hold), but the returned promise then REJECTS with the typed
106
108
  * failure so callers / rollout machinery observe it. Prefer expand/contract
@@ -154,7 +156,7 @@ async function reportFailure<T>(
154
156
  error?: unknown;
155
157
  resolved?: ResolvedDeliveryUnit;
156
158
  },
157
- ): Promise<T> {
159
+ ): Promise<T | undefined> {
158
160
  const remote = toRemoteString(ref);
159
161
  const telemetry = createModuleFederationFallbackTelemetry({
160
162
  appName: options.appName,
@@ -173,24 +175,7 @@ async function reportFailure<T>(
173
175
  status: 'degraded',
174
176
  });
175
177
 
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
- }
178
+ void emitModuleFederationFallbackPayload(telemetry, options.telemetry);
194
179
 
195
180
  const failure: SurfaceConsumptionFailure = {
196
181
  ref,
@@ -234,7 +219,7 @@ async function reportFailure<T>(
234
219
  // telemetry, then for noncritical resolve undefined; for critical reject
235
220
  // with the ORIGINAL typed error (never the handler's).
236
221
  try {
237
- await emitModuleFederationFallbackTelemetry(
222
+ void emitModuleFederationFallbackTelemetry(
238
223
  {
239
224
  appName: options.appName,
240
225
  classification: base.classification,
@@ -252,7 +237,7 @@ async function reportFailure<T>(
252
237
  if (critical) {
253
238
  throw reason;
254
239
  }
255
- return undefined as T;
240
+ return undefined;
256
241
  }
257
242
 
258
243
  if (critical) {
@@ -266,14 +251,14 @@ async function reportFailure<T>(
266
251
  * load failure the required degraded handler runs (telemetry + fallback UI).
267
252
  *
268
253
  * For a `noncritical` consumption (see {@link SurfaceConsumerOptions.classification})
269
- * this resolves to the degraded value and never rejects, so a failing
254
+ * this resolves to the degraded value (or undefined if the handler fails), so a failing
270
255
  * consumption is isolated from any sibling. For a `critical` consumption (the
271
256
  * default) it rejects with the typed failure AFTER the degraded handler has
272
257
  * run, so callers / rollout machinery observe the failure.
273
258
  */
274
259
  export async function consumeSurface<T>(
275
260
  options: SurfaceConsumerOptions<T>,
276
- ): Promise<T> {
261
+ ): Promise<T | undefined> {
277
262
  let ref: ParsedSurfaceRef;
278
263
  if (typeof options.ref === 'string') {
279
264
  const parsed = parseSurfaceRef(options.ref);
@@ -360,7 +345,7 @@ export function createSurfaceConsumer(
360
345
  return function consume<T>(
361
346
  options: Omit<SurfaceConsumerOptions<T>, keyof typeof base> &
362
347
  Partial<Pick<SurfaceConsumerOptions<T>, keyof typeof base>>,
363
- ): Promise<T> {
348
+ ): Promise<T | undefined> {
364
349
  return consumeSurface<T>({
365
350
  ...base,
366
351
  ...options,
@@ -10,265 +10,4 @@ export * from './last-known-good';
10
10
  export * from './manifest-recovery-runtime-plugin';
11
11
  export * from './surface-resolution-types';
12
12
 
13
- export const MODULE_FEDERATION_FALLBACK_SIGNAL_EVENT =
14
- 'modernjs:mf-runtime-fallback';
15
-
16
- export const MODULE_FEDERATION_RECOVERY_SIGNAL_EVENT =
17
- 'modernjs:mf-runtime-recovery';
18
-
19
- export const DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT =
20
- '/_modern/contract-gates/runtime-fallback';
21
-
22
- export const DEFAULT_RUNTIME_FALLBACK_SIGNAL_AUTH_HEADER =
23
- 'x-modernjs-runtime-signal-token';
24
-
25
- export type ModuleFederationFallbackClassification =
26
- | 'timeout'
27
- | 'network'
28
- | 'contract'
29
- | 'version-skew'
30
- | 'remote-unavailable';
31
-
32
- export type ModuleFederationFallbackStatus =
33
- | 'degraded'
34
- | 'failed'
35
- | 'recovered';
36
-
37
- export type ModuleFederationFallbackTelemetryInput = {
38
- appName: string;
39
- classification: ModuleFederationFallbackClassification;
40
- entry?: string;
41
- error?: unknown;
42
- eventName?: string;
43
- exportName?: string;
44
- metadata?: Record<string, unknown>;
45
- phase: 'discovery' | 'load' | 'mount' | 'hydrate' | 'recover';
46
- remote: string;
47
- runtimeDigest?: string;
48
- status?: ModuleFederationFallbackStatus;
49
- };
50
-
51
- export type ModuleFederationFallbackTelemetryPayload = {
52
- appName: string;
53
- entry?: string;
54
- eventName: string;
55
- phase: ModuleFederationFallbackTelemetryInput['phase'];
56
- reason: ModuleFederationFallbackClassification;
57
- runtimeDigest?: string;
58
- schemaVersion: 1;
59
- metadata: {
60
- classification: ModuleFederationFallbackClassification;
61
- errorMessage?: string;
62
- errorName?: string;
63
- exportName?: string;
64
- remote: string;
65
- runtimeDigest?: string;
66
- status: ModuleFederationFallbackStatus;
67
- } & Record<string, unknown>;
68
- };
69
-
70
- export type ModuleFederationFallbackTelemetryEmitOptions = {
71
- authHeaderName?: string;
72
- authToken?: string;
73
- endpoint?: string;
74
- fetchImpl?: typeof fetch;
75
- postSignal?: boolean;
76
- };
77
-
78
- export type ModuleFederationFallbackTelemetryEmitResult = {
79
- dispatched: boolean;
80
- posted: boolean;
81
- postStatus?: number;
82
- };
83
-
84
- export class ModuleFederationRemoteLoadTimeoutError extends Error {
85
- constructor(remote: string, timeoutMs: number) {
86
- super(`Loading remote "${remote}" timed out after ${timeoutMs}ms`);
87
- this.name = 'ModuleFederationRemoteLoadTimeoutError';
88
- }
89
- }
90
-
91
- export class ModuleFederationRemoteLoadError extends Error {
92
- readonly remote: string;
93
- readonly attempts: number;
94
- readonly causeError: Error;
95
-
96
- constructor(remote: string, attempts: number, causeError: Error) {
97
- super(
98
- `Unable to load remote "${remote}" after ${attempts} attempt${
99
- attempts > 1 ? 's' : ''
100
- }: ${causeError.message}`,
101
- );
102
- this.name = 'ModuleFederationRemoteLoadError';
103
- this.remote = remote;
104
- this.attempts = attempts;
105
- this.causeError = causeError;
106
- }
107
- }
108
-
109
- export class ModuleFederationRemoteComponentContractError extends Error {
110
- constructor(remote: string, exportName: string) {
111
- super(
112
- `Remote "${remote}" export "${exportName}" is not a valid React component`,
113
- );
114
- this.name = 'ModuleFederationRemoteComponentContractError';
115
- }
116
- }
117
-
118
- function toError(error: unknown) {
119
- if (error instanceof Error) {
120
- return error;
121
- }
122
-
123
- return new Error(
124
- typeof error === 'string' ? error : 'Unknown remote load error',
125
- );
126
- }
127
-
128
- export function classifyModuleFederationFallback(
129
- error: unknown,
130
- ): ModuleFederationFallbackClassification {
131
- const normalizedError = toError(error);
132
- if (normalizedError instanceof ModuleFederationRemoteLoadError) {
133
- return classifyModuleFederationFallback(normalizedError.causeError);
134
- }
135
- if (normalizedError instanceof ModuleFederationRemoteLoadTimeoutError) {
136
- return 'timeout';
137
- }
138
- if (normalizedError instanceof ModuleFederationRemoteComponentContractError) {
139
- return 'contract';
140
- }
141
-
142
- const message = normalizedError.message;
143
- if (
144
- /version|requiredVersion|singleton|share scope|shared module/i.test(message)
145
- ) {
146
- return 'version-skew';
147
- }
148
- if (/network|fetch|script|timeout|chunk|loading/i.test(message)) {
149
- return 'network';
150
- }
151
-
152
- return 'remote-unavailable';
153
- }
154
-
155
- export function createModuleFederationFallbackTelemetry(
156
- input: ModuleFederationFallbackTelemetryInput,
157
- ): ModuleFederationFallbackTelemetryPayload {
158
- const error = input.error !== undefined ? toError(input.error) : undefined;
159
- const status = input.status ?? 'degraded';
160
- const eventName =
161
- input.eventName ??
162
- (status === 'recovered'
163
- ? MODULE_FEDERATION_RECOVERY_SIGNAL_EVENT
164
- : MODULE_FEDERATION_FALLBACK_SIGNAL_EVENT);
165
- const metadata: ModuleFederationFallbackTelemetryPayload['metadata'] = {
166
- ...(input.metadata ?? {}),
167
- classification: input.classification,
168
- remote: input.remote,
169
- status,
170
- };
171
-
172
- if (input.exportName !== undefined) {
173
- metadata.exportName = input.exportName;
174
- }
175
- if (input.runtimeDigest !== undefined) {
176
- metadata.runtimeDigest = input.runtimeDigest;
177
- }
178
- if (error !== undefined) {
179
- metadata.errorName = error.name;
180
- metadata.errorMessage = error.message;
181
- }
182
-
183
- const payload: ModuleFederationFallbackTelemetryPayload = {
184
- appName: input.appName,
185
- eventName,
186
- metadata,
187
- phase: input.phase,
188
- reason: input.classification,
189
- schemaVersion: 1,
190
- };
191
-
192
- if (input.entry !== undefined) {
193
- payload.entry = input.entry;
194
- }
195
- if (input.runtimeDigest !== undefined) {
196
- payload.runtimeDigest = input.runtimeDigest;
197
- }
198
-
199
- return payload;
200
- }
201
-
202
- export function toModuleFederationFallbackAttributes(
203
- payload: ModuleFederationFallbackTelemetryPayload,
204
- ): Record<string, string> {
205
- return {
206
- 'data-mf-fallback-app': payload.appName,
207
- 'data-mf-fallback-classification': payload.reason,
208
- 'data-mf-fallback-phase': payload.phase,
209
- 'data-mf-fallback-remote': String(payload.metadata.remote),
210
- 'data-mf-fallback-status': String(payload.metadata.status),
211
- 'data-mf-telemetry-event': payload.eventName,
212
- };
213
- }
214
-
215
- export async function emitModuleFederationFallbackTelemetry(
216
- input: ModuleFederationFallbackTelemetryInput,
217
- options: ModuleFederationFallbackTelemetryEmitOptions = {},
218
- ): Promise<ModuleFederationFallbackTelemetryEmitResult> {
219
- const payload = createModuleFederationFallbackTelemetry(input);
220
-
221
- if (
222
- typeof window !== 'undefined' &&
223
- typeof window.dispatchEvent === 'function' &&
224
- typeof CustomEvent !== 'undefined'
225
- ) {
226
- window.dispatchEvent(
227
- new CustomEvent(payload.eventName, {
228
- detail: payload,
229
- }),
230
- );
231
- }
232
-
233
- const shouldPost = options.postSignal === true || Boolean(options.endpoint);
234
- if (!shouldPost) {
235
- return {
236
- dispatched: true,
237
- posted: false,
238
- };
239
- }
240
-
241
- const fetchImpl = options.fetchImpl ?? globalThis.fetch;
242
- if (typeof fetchImpl !== 'function') {
243
- return {
244
- dispatched: true,
245
- posted: false,
246
- };
247
- }
248
-
249
- const headers = new Headers({
250
- 'content-type': 'application/json',
251
- });
252
- if (options.authToken !== undefined && options.authToken.length > 0) {
253
- headers.set(
254
- options.authHeaderName ?? DEFAULT_RUNTIME_FALLBACK_SIGNAL_AUTH_HEADER,
255
- options.authToken,
256
- );
257
- }
258
-
259
- const response = await fetchImpl(
260
- options.endpoint ?? DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT,
261
- {
262
- body: JSON.stringify(payload),
263
- headers,
264
- method: 'POST',
265
- keepalive: true,
266
- },
267
- );
268
-
269
- return {
270
- dispatched: true,
271
- posted: true,
272
- postStatus: response.status,
273
- };
274
- }
13
+ export * from './telemetry';