@bleedingdev/modern-js-plugin-bff-extensions 3.9.0-ultramodern.10 → 3.9.0-ultramodern.11
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/README.md
CHANGED
|
@@ -21,6 +21,13 @@ upstream Modern.js project.
|
|
|
21
21
|
- `/backend-federation-manifest` exposes manifest contracts and resolution,
|
|
22
22
|
while
|
|
23
23
|
`/backend-federation-manifest/node` enables its hardened Node evaluator.
|
|
24
|
+
`loadBackendFederatedEffectApiFromManifest` enforces one shared
|
|
25
|
+
manifest-plus-entry load deadline (default 10s, override via `timeoutMs`,
|
|
26
|
+
0 to opt out) instead of resetting a fresh timeout per network hop. The
|
|
27
|
+
load settles on that shared deadline/abort even when a caller-pinned
|
|
28
|
+
`loadEntry` plugin, its `init`/`get`, or a custom runtime's `loadRemote`
|
|
29
|
+
never settles; it cannot stop that uncooperative work from still running,
|
|
30
|
+
only stop waiting on it.
|
|
24
31
|
|
|
25
32
|
Effect and its OpenTelemetry integration are exact-cohort optional peers. A
|
|
26
33
|
Hono-only consumer can import `/hono` without installing Effect.
|
|
@@ -37,6 +37,7 @@ const external_metadata_js_namespaceObject = require("./metadata.js");
|
|
|
37
37
|
const external_reference_js_namespaceObject = require("./reference.js");
|
|
38
38
|
const external_remote_js_namespaceObject = require("./remote.js");
|
|
39
39
|
const external_validation_js_namespaceObject = require("./validation.js");
|
|
40
|
+
const DEFAULT_SHARED_MANIFEST_LOAD_TIMEOUT_MS = 10000;
|
|
40
41
|
function createManifestLoadScope(options) {
|
|
41
42
|
const controller = new AbortController();
|
|
42
43
|
const policyTimeouts = [
|
|
@@ -48,7 +49,8 @@ function createManifestLoadScope(options) {
|
|
|
48
49
|
...policyTimeouts
|
|
49
50
|
].filter((timeout)=>void 0 !== timeout);
|
|
50
51
|
for (const timeout of configuredTimeouts)if (!Number.isSafeInteger(timeout) || timeout < 0) throw new external_errors_js_namespaceObject.BackendFederationManifestAdapterError('manifest_invalid', '[BFF][Effect] Backend federation timeoutMs must be a non-negative safe integer.');
|
|
51
|
-
const
|
|
52
|
+
const positivePolicyTimeouts = policyTimeouts.filter((timeout)=>timeout > 0);
|
|
53
|
+
const timeoutMs = options.timeoutMs ?? (0 === policyTimeouts.length ? DEFAULT_SHARED_MANIFEST_LOAD_TIMEOUT_MS : positivePolicyTimeouts.length > 0 ? Math.min(...positivePolicyTimeouts) : 0);
|
|
52
54
|
const inputSignals = [
|
|
53
55
|
options.signal,
|
|
54
56
|
options.manifestPolicy?.signal,
|
|
@@ -102,6 +104,27 @@ function isCallerPinnedStaticRemote(options, remote) {
|
|
|
102
104
|
function throwIfManifestLoadAborted(signal) {
|
|
103
105
|
if (signal.aborted) throw new external_errors_js_namespaceObject.BackendFederationManifestAdapterError('remote_unavailable', '[BFF][Effect] Backend federation load was aborted before remote execution.', signal.reason);
|
|
104
106
|
}
|
|
107
|
+
function settleOnSharedAbort(promise, signal) {
|
|
108
|
+
if (signal.aborted) {
|
|
109
|
+
promise.catch(()=>{});
|
|
110
|
+
throwIfManifestLoadAborted(signal);
|
|
111
|
+
}
|
|
112
|
+
let onAbort;
|
|
113
|
+
const aborted = new Promise((_resolve, reject)=>{
|
|
114
|
+
onAbort = ()=>{
|
|
115
|
+
reject(new external_errors_js_namespaceObject.BackendFederationManifestAdapterError('remote_unavailable', '[BFF][Effect] Backend federation load was aborted before remote execution.', signal.reason));
|
|
116
|
+
};
|
|
117
|
+
signal.addEventListener('abort', onAbort, {
|
|
118
|
+
once: true
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
return Promise.race([
|
|
122
|
+
promise,
|
|
123
|
+
aborted
|
|
124
|
+
]).finally(()=>{
|
|
125
|
+
if (onAbort) signal.removeEventListener('abort', onAbort);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
105
128
|
async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
106
129
|
let manifest;
|
|
107
130
|
let remote;
|
|
@@ -109,7 +132,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
109
132
|
try {
|
|
110
133
|
scope = createManifestLoadScope(options);
|
|
111
134
|
throwIfManifestLoadAborted(scope.signal);
|
|
112
|
-
manifest = await (0, external_reference_js_namespaceObject.loadBackendFederationManifest)({
|
|
135
|
+
manifest = await settleOnSharedAbort((0, external_reference_js_namespaceObject.loadBackendFederationManifest)({
|
|
113
136
|
...options,
|
|
114
137
|
manifestPolicy: {
|
|
115
138
|
...options.manifestPolicy,
|
|
@@ -118,7 +141,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
118
141
|
},
|
|
119
142
|
signal: scope.signal,
|
|
120
143
|
timeoutMs: 0
|
|
121
|
-
});
|
|
144
|
+
}), scope.signal);
|
|
122
145
|
(0, external_validation_js_namespaceObject.validateBackendFederationManifest)(manifest, options.expected);
|
|
123
146
|
remote = (0, external_remote_js_namespaceObject.resolveBackendFederationRemoteFromManifest)(manifest, options.remote);
|
|
124
147
|
throwIfManifestLoadAborted(scope.signal);
|
|
@@ -132,7 +155,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
132
155
|
const unitId = (0, external_metadata_js_namespaceObject.stringValue)(deliveryUnit.boundary?.unitId) ?? (0, external_metadata_js_namespaceObject.stringValue)(deliveryUnit.top?.unitId);
|
|
133
156
|
const buildMarker = (0, external_metadata_js_namespaceObject.stringValue)(deliveryUnit.boundary?.buildMarker);
|
|
134
157
|
if (!unitId || !buildMarker) throw new external_errors_js_namespaceObject.BackendFederationManifestAdapterError('manifest_invalid', 'Backend federation manifest requires delivery-unit identity.');
|
|
135
|
-
const loaded = await (0, load_js_namespaceObject.loadBackendFederatedEffectApi)({
|
|
158
|
+
const loaded = await settleOnSharedAbort((0, load_js_namespaceObject.loadBackendFederatedEffectApi)({
|
|
136
159
|
hostName: options.hostName,
|
|
137
160
|
remote,
|
|
138
161
|
...options.plugins ? {
|
|
@@ -152,7 +175,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
152
175
|
}
|
|
153
176
|
}).catch((error)=>{
|
|
154
177
|
throw new external_errors_js_namespaceObject.BackendFederationManifestAdapterError(scope?.timedOut() === true ? 'timeout' : (0, external_validation_js_namespaceObject.classifyLoadError)(error), `[BFF][Effect] Backend federation remote ${remote?.name ?? 'unknown'} could not load ${remote?.expose ?? index_js_namespaceObject.BACKEND_FEDERATION_EFFECT_EXPOSE}.`, error);
|
|
155
|
-
});
|
|
178
|
+
}), scope.signal);
|
|
156
179
|
(0, external_validation_js_namespaceObject.validateLoadedBackendFederationContract)(loaded, manifest, remote);
|
|
157
180
|
return loaded;
|
|
158
181
|
} catch (error) {
|
|
@@ -5,6 +5,7 @@ import { backendFederationMetadata, manifestDeliveryUnit, stringValue, versionBo
|
|
|
5
5
|
import { loadBackendFederationManifest } from "./reference.mjs";
|
|
6
6
|
import { resolveBackendFederationRemoteFromManifest } from "./remote.mjs";
|
|
7
7
|
import { classifyLoadError, validateBackendFederationManifest, validateLoadedBackendFederationContract } from "./validation.mjs";
|
|
8
|
+
const DEFAULT_SHARED_MANIFEST_LOAD_TIMEOUT_MS = 10000;
|
|
8
9
|
function createManifestLoadScope(options) {
|
|
9
10
|
const controller = new AbortController();
|
|
10
11
|
const policyTimeouts = [
|
|
@@ -16,7 +17,8 @@ function createManifestLoadScope(options) {
|
|
|
16
17
|
...policyTimeouts
|
|
17
18
|
].filter((timeout)=>void 0 !== timeout);
|
|
18
19
|
for (const timeout of configuredTimeouts)if (!Number.isSafeInteger(timeout) || timeout < 0) throw new BackendFederationManifestAdapterError('manifest_invalid', '[BFF][Effect] Backend federation timeoutMs must be a non-negative safe integer.');
|
|
19
|
-
const
|
|
20
|
+
const positivePolicyTimeouts = policyTimeouts.filter((timeout)=>timeout > 0);
|
|
21
|
+
const timeoutMs = options.timeoutMs ?? (0 === policyTimeouts.length ? DEFAULT_SHARED_MANIFEST_LOAD_TIMEOUT_MS : positivePolicyTimeouts.length > 0 ? Math.min(...positivePolicyTimeouts) : 0);
|
|
20
22
|
const inputSignals = [
|
|
21
23
|
options.signal,
|
|
22
24
|
options.manifestPolicy?.signal,
|
|
@@ -70,6 +72,27 @@ function isCallerPinnedStaticRemote(options, remote) {
|
|
|
70
72
|
function throwIfManifestLoadAborted(signal) {
|
|
71
73
|
if (signal.aborted) throw new BackendFederationManifestAdapterError('remote_unavailable', '[BFF][Effect] Backend federation load was aborted before remote execution.', signal.reason);
|
|
72
74
|
}
|
|
75
|
+
function settleOnSharedAbort(promise, signal) {
|
|
76
|
+
if (signal.aborted) {
|
|
77
|
+
promise.catch(()=>{});
|
|
78
|
+
throwIfManifestLoadAborted(signal);
|
|
79
|
+
}
|
|
80
|
+
let onAbort;
|
|
81
|
+
const aborted = new Promise((_resolve, reject)=>{
|
|
82
|
+
onAbort = ()=>{
|
|
83
|
+
reject(new BackendFederationManifestAdapterError('remote_unavailable', '[BFF][Effect] Backend federation load was aborted before remote execution.', signal.reason));
|
|
84
|
+
};
|
|
85
|
+
signal.addEventListener('abort', onAbort, {
|
|
86
|
+
once: true
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
return Promise.race([
|
|
90
|
+
promise,
|
|
91
|
+
aborted
|
|
92
|
+
]).finally(()=>{
|
|
93
|
+
if (onAbort) signal.removeEventListener('abort', onAbort);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
73
96
|
async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
74
97
|
let manifest;
|
|
75
98
|
let remote;
|
|
@@ -77,7 +100,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
77
100
|
try {
|
|
78
101
|
scope = createManifestLoadScope(options);
|
|
79
102
|
throwIfManifestLoadAborted(scope.signal);
|
|
80
|
-
manifest = await loadBackendFederationManifest({
|
|
103
|
+
manifest = await settleOnSharedAbort(loadBackendFederationManifest({
|
|
81
104
|
...options,
|
|
82
105
|
manifestPolicy: {
|
|
83
106
|
...options.manifestPolicy,
|
|
@@ -86,7 +109,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
86
109
|
},
|
|
87
110
|
signal: scope.signal,
|
|
88
111
|
timeoutMs: 0
|
|
89
|
-
});
|
|
112
|
+
}), scope.signal);
|
|
90
113
|
validateBackendFederationManifest(manifest, options.expected);
|
|
91
114
|
remote = resolveBackendFederationRemoteFromManifest(manifest, options.remote);
|
|
92
115
|
throwIfManifestLoadAborted(scope.signal);
|
|
@@ -100,7 +123,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
100
123
|
const unitId = stringValue(deliveryUnit.boundary?.unitId) ?? stringValue(deliveryUnit.top?.unitId);
|
|
101
124
|
const buildMarker = stringValue(deliveryUnit.boundary?.buildMarker);
|
|
102
125
|
if (!unitId || !buildMarker) throw new BackendFederationManifestAdapterError('manifest_invalid', 'Backend federation manifest requires delivery-unit identity.');
|
|
103
|
-
const loaded = await loadBackendFederatedEffectApi({
|
|
126
|
+
const loaded = await settleOnSharedAbort(loadBackendFederatedEffectApi({
|
|
104
127
|
hostName: options.hostName,
|
|
105
128
|
remote,
|
|
106
129
|
...options.plugins ? {
|
|
@@ -120,7 +143,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
120
143
|
}
|
|
121
144
|
}).catch((error)=>{
|
|
122
145
|
throw new BackendFederationManifestAdapterError(scope?.timedOut() === true ? 'timeout' : classifyLoadError(error), `[BFF][Effect] Backend federation remote ${remote?.name ?? 'unknown'} could not load ${remote?.expose ?? BACKEND_FEDERATION_EFFECT_EXPOSE}.`, error);
|
|
123
|
-
});
|
|
146
|
+
}), scope.signal);
|
|
124
147
|
validateLoadedBackendFederationContract(loaded, manifest, remote);
|
|
125
148
|
return loaded;
|
|
126
149
|
} catch (error) {
|
|
@@ -6,6 +6,7 @@ import { backendFederationMetadata, manifestDeliveryUnit, stringValue, versionBo
|
|
|
6
6
|
import { loadBackendFederationManifest } from "./reference.mjs";
|
|
7
7
|
import { resolveBackendFederationRemoteFromManifest } from "./remote.mjs";
|
|
8
8
|
import { classifyLoadError, validateBackendFederationManifest, validateLoadedBackendFederationContract } from "./validation.mjs";
|
|
9
|
+
const DEFAULT_SHARED_MANIFEST_LOAD_TIMEOUT_MS = 10000;
|
|
9
10
|
function createManifestLoadScope(options) {
|
|
10
11
|
const controller = new AbortController();
|
|
11
12
|
const policyTimeouts = [
|
|
@@ -17,7 +18,8 @@ function createManifestLoadScope(options) {
|
|
|
17
18
|
...policyTimeouts
|
|
18
19
|
].filter((timeout)=>void 0 !== timeout);
|
|
19
20
|
for (const timeout of configuredTimeouts)if (!Number.isSafeInteger(timeout) || timeout < 0) throw new BackendFederationManifestAdapterError('manifest_invalid', '[BFF][Effect] Backend federation timeoutMs must be a non-negative safe integer.');
|
|
20
|
-
const
|
|
21
|
+
const positivePolicyTimeouts = policyTimeouts.filter((timeout)=>timeout > 0);
|
|
22
|
+
const timeoutMs = options.timeoutMs ?? (0 === policyTimeouts.length ? DEFAULT_SHARED_MANIFEST_LOAD_TIMEOUT_MS : positivePolicyTimeouts.length > 0 ? Math.min(...positivePolicyTimeouts) : 0);
|
|
21
23
|
const inputSignals = [
|
|
22
24
|
options.signal,
|
|
23
25
|
options.manifestPolicy?.signal,
|
|
@@ -71,6 +73,27 @@ function isCallerPinnedStaticRemote(options, remote) {
|
|
|
71
73
|
function throwIfManifestLoadAborted(signal) {
|
|
72
74
|
if (signal.aborted) throw new BackendFederationManifestAdapterError('remote_unavailable', '[BFF][Effect] Backend federation load was aborted before remote execution.', signal.reason);
|
|
73
75
|
}
|
|
76
|
+
function settleOnSharedAbort(promise, signal) {
|
|
77
|
+
if (signal.aborted) {
|
|
78
|
+
promise.catch(()=>{});
|
|
79
|
+
throwIfManifestLoadAborted(signal);
|
|
80
|
+
}
|
|
81
|
+
let onAbort;
|
|
82
|
+
const aborted = new Promise((_resolve, reject)=>{
|
|
83
|
+
onAbort = ()=>{
|
|
84
|
+
reject(new BackendFederationManifestAdapterError('remote_unavailable', '[BFF][Effect] Backend federation load was aborted before remote execution.', signal.reason));
|
|
85
|
+
};
|
|
86
|
+
signal.addEventListener('abort', onAbort, {
|
|
87
|
+
once: true
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
return Promise.race([
|
|
91
|
+
promise,
|
|
92
|
+
aborted
|
|
93
|
+
]).finally(()=>{
|
|
94
|
+
if (onAbort) signal.removeEventListener('abort', onAbort);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
74
97
|
async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
75
98
|
let manifest;
|
|
76
99
|
let remote;
|
|
@@ -78,7 +101,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
78
101
|
try {
|
|
79
102
|
scope = createManifestLoadScope(options);
|
|
80
103
|
throwIfManifestLoadAborted(scope.signal);
|
|
81
|
-
manifest = await loadBackendFederationManifest({
|
|
104
|
+
manifest = await settleOnSharedAbort(loadBackendFederationManifest({
|
|
82
105
|
...options,
|
|
83
106
|
manifestPolicy: {
|
|
84
107
|
...options.manifestPolicy,
|
|
@@ -87,7 +110,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
87
110
|
},
|
|
88
111
|
signal: scope.signal,
|
|
89
112
|
timeoutMs: 0
|
|
90
|
-
});
|
|
113
|
+
}), scope.signal);
|
|
91
114
|
validateBackendFederationManifest(manifest, options.expected);
|
|
92
115
|
remote = resolveBackendFederationRemoteFromManifest(manifest, options.remote);
|
|
93
116
|
throwIfManifestLoadAborted(scope.signal);
|
|
@@ -101,7 +124,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
101
124
|
const unitId = stringValue(deliveryUnit.boundary?.unitId) ?? stringValue(deliveryUnit.top?.unitId);
|
|
102
125
|
const buildMarker = stringValue(deliveryUnit.boundary?.buildMarker);
|
|
103
126
|
if (!unitId || !buildMarker) throw new BackendFederationManifestAdapterError('manifest_invalid', 'Backend federation manifest requires delivery-unit identity.');
|
|
104
|
-
const loaded = await loadBackendFederatedEffectApi({
|
|
127
|
+
const loaded = await settleOnSharedAbort(loadBackendFederatedEffectApi({
|
|
105
128
|
hostName: options.hostName,
|
|
106
129
|
remote,
|
|
107
130
|
...options.plugins ? {
|
|
@@ -121,7 +144,7 @@ async function loadBackendFederatedEffectApiFromManifest(options) {
|
|
|
121
144
|
}
|
|
122
145
|
}).catch((error)=>{
|
|
123
146
|
throw new BackendFederationManifestAdapterError(scope?.timedOut() === true ? 'timeout' : classifyLoadError(error), `[BFF][Effect] Backend federation remote ${remote?.name ?? 'unknown'} could not load ${remote?.expose ?? BACKEND_FEDERATION_EFFECT_EXPOSE}.`, error);
|
|
124
|
-
});
|
|
147
|
+
}), scope.signal);
|
|
125
148
|
validateLoadedBackendFederationContract(loaded, manifest, remote);
|
|
126
149
|
return loaded;
|
|
127
150
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"modern.js",
|
|
19
19
|
"ultramodern.js"
|
|
20
20
|
],
|
|
21
|
-
"version": "3.9.0-ultramodern.
|
|
21
|
+
"version": "3.9.0-ultramodern.11",
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
24
|
"src"
|
|
@@ -193,14 +193,14 @@
|
|
|
193
193
|
"node": ">=26.7.0"
|
|
194
194
|
},
|
|
195
195
|
"dependencies": {
|
|
196
|
-
"@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.
|
|
197
|
-
"@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.9.0-ultramodern.
|
|
198
|
-
"@modern-js/bff-effect": "npm:@bleedingdev/modern-js-bff-effect@3.9.0-ultramodern.
|
|
199
|
-
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.
|
|
200
|
-
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.
|
|
201
|
-
"@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.
|
|
202
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.
|
|
203
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.
|
|
196
|
+
"@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.11",
|
|
197
|
+
"@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.9.0-ultramodern.11",
|
|
198
|
+
"@modern-js/bff-effect": "npm:@bleedingdev/modern-js-bff-effect@3.9.0-ultramodern.11",
|
|
199
|
+
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.11",
|
|
200
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.11",
|
|
201
|
+
"@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.11",
|
|
202
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.11",
|
|
203
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.11",
|
|
204
204
|
"@module-federation/runtime": "2.9.0",
|
|
205
205
|
"@swc/core": "1.16.2",
|
|
206
206
|
"esbuild": "^0.28.2"
|
|
@@ -219,7 +219,7 @@
|
|
|
219
219
|
},
|
|
220
220
|
"devDependencies": {
|
|
221
221
|
"@effect/opentelemetry": "4.0.0-rc.112",
|
|
222
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.
|
|
222
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.11",
|
|
223
223
|
"@rsbuild/core": "2.2.3",
|
|
224
224
|
"@rslib/core": "1.0.0",
|
|
225
225
|
"@scripts/rstest-config": "2.66.0",
|
|
@@ -25,6 +25,10 @@ import {
|
|
|
25
25
|
validateLoadedBackendFederationContract,
|
|
26
26
|
} from './validation';
|
|
27
27
|
|
|
28
|
+
// One manifest+entry budget. Explicit outer 0 opts out; nested 0 cannot
|
|
29
|
+
// disable a positive nested deadline.
|
|
30
|
+
const DEFAULT_SHARED_MANIFEST_LOAD_TIMEOUT_MS = 10_000;
|
|
31
|
+
|
|
28
32
|
function createManifestLoadScope(
|
|
29
33
|
options: BackendFederationManifestAdapterOptions,
|
|
30
34
|
) {
|
|
@@ -44,9 +48,14 @@ function createManifestLoadScope(
|
|
|
44
48
|
);
|
|
45
49
|
}
|
|
46
50
|
}
|
|
51
|
+
const positivePolicyTimeouts = policyTimeouts.filter(timeout => timeout > 0);
|
|
47
52
|
const timeoutMs =
|
|
48
53
|
options.timeoutMs ??
|
|
49
|
-
(policyTimeouts.length
|
|
54
|
+
(policyTimeouts.length === 0
|
|
55
|
+
? DEFAULT_SHARED_MANIFEST_LOAD_TIMEOUT_MS
|
|
56
|
+
: positivePolicyTimeouts.length > 0
|
|
57
|
+
? Math.min(...positivePolicyTimeouts)
|
|
58
|
+
: 0);
|
|
50
59
|
const inputSignals = [
|
|
51
60
|
options.signal,
|
|
52
61
|
options.manifestPolicy?.signal,
|
|
@@ -140,6 +149,39 @@ function throwIfManifestLoadAborted(signal: AbortSignal) {
|
|
|
140
149
|
}
|
|
141
150
|
}
|
|
142
151
|
|
|
152
|
+
// Stop waiting even when trusted plugin work ignores cancellation. The race
|
|
153
|
+
// observes late rejections; it cannot stop that work from running.
|
|
154
|
+
function settleOnSharedAbort<T>(
|
|
155
|
+
promise: Promise<T>,
|
|
156
|
+
signal: AbortSignal,
|
|
157
|
+
): Promise<T> {
|
|
158
|
+
if (signal.aborted) {
|
|
159
|
+
// Observe work even when the pre-aborted path never reaches Promise.race.
|
|
160
|
+
promise.catch(() => {});
|
|
161
|
+
throwIfManifestLoadAborted(signal);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let onAbort: (() => void) | undefined;
|
|
165
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
166
|
+
onAbort = () => {
|
|
167
|
+
reject(
|
|
168
|
+
new BackendFederationManifestAdapterError(
|
|
169
|
+
'remote_unavailable',
|
|
170
|
+
'[BFF][Effect] Backend federation load was aborted before remote execution.',
|
|
171
|
+
signal.reason,
|
|
172
|
+
),
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return Promise.race([promise, aborted]).finally(() => {
|
|
179
|
+
if (onAbort) {
|
|
180
|
+
signal.removeEventListener('abort', onAbort);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
143
185
|
export async function loadBackendFederatedEffectApiFromManifest(
|
|
144
186
|
options: BackendFederationManifestAdapterOptions,
|
|
145
187
|
): Promise<BackendFederatedEffectApiModule> {
|
|
@@ -150,16 +192,19 @@ export async function loadBackendFederatedEffectApiFromManifest(
|
|
|
150
192
|
try {
|
|
151
193
|
scope = createManifestLoadScope(options);
|
|
152
194
|
throwIfManifestLoadAborted(scope.signal);
|
|
153
|
-
manifest = await
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
195
|
+
manifest = await settleOnSharedAbort(
|
|
196
|
+
loadBackendFederationManifest({
|
|
197
|
+
...options,
|
|
198
|
+
manifestPolicy: {
|
|
199
|
+
...options.manifestPolicy,
|
|
200
|
+
signal: scope.signal,
|
|
201
|
+
timeoutMs: 0,
|
|
202
|
+
},
|
|
157
203
|
signal: scope.signal,
|
|
158
204
|
timeoutMs: 0,
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
});
|
|
205
|
+
}),
|
|
206
|
+
scope.signal,
|
|
207
|
+
);
|
|
163
208
|
validateBackendFederationManifest(manifest, options.expected);
|
|
164
209
|
remote = resolveBackendFederationRemoteFromManifest(
|
|
165
210
|
manifest,
|
|
@@ -223,24 +268,27 @@ export async function loadBackendFederatedEffectApiFromManifest(
|
|
|
223
268
|
'manifest_invalid',
|
|
224
269
|
'Backend federation manifest requires delivery-unit identity.',
|
|
225
270
|
);
|
|
226
|
-
const loaded = await
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
271
|
+
const loaded = await settleOnSharedAbort(
|
|
272
|
+
loadBackendFederatedEffectApi({
|
|
273
|
+
hostName: options.hostName,
|
|
274
|
+
remote,
|
|
275
|
+
...(options.plugins ? { plugins: options.plugins } : {}),
|
|
276
|
+
entryPolicy: {
|
|
277
|
+
...options.entryPolicy,
|
|
278
|
+
signal: scope.signal,
|
|
279
|
+
timeoutMs: 0,
|
|
280
|
+
},
|
|
281
|
+
...(options.runtime ? { runtime: options.runtime } : {}),
|
|
282
|
+
expected: { unitId, buildMarker },
|
|
283
|
+
}).catch((error: unknown) => {
|
|
284
|
+
throw new BackendFederationManifestAdapterError(
|
|
285
|
+
scope?.timedOut() === true ? 'timeout' : classifyLoadError(error),
|
|
286
|
+
`[BFF][Effect] Backend federation remote ${remote?.name ?? 'unknown'} could not load ${remote?.expose ?? BACKEND_FEDERATION_EFFECT_EXPOSE}.`,
|
|
287
|
+
error,
|
|
288
|
+
);
|
|
289
|
+
}),
|
|
290
|
+
scope.signal,
|
|
291
|
+
);
|
|
244
292
|
|
|
245
293
|
validateLoadedBackendFederationContract(loaded, manifest, remote);
|
|
246
294
|
|