webloom-framework 0.1.0 → 0.3.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.
- package/README.md +86 -36
- package/dist/{chunk-KGNF36DZ.js → chunk-76BGPI6M.js} +1094 -569
- package/dist/chunk-76BGPI6M.js.map +1 -0
- package/dist/{createPluginHost-CcW9sPNs.d.ts → createPluginHost-BVsUCDKN.d.ts} +139 -161
- package/dist/index.d.ts +134 -29
- package/dist/index.js +948 -146
- package/dist/index.js.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/resourceRegistry-BFnjmeGE.d.ts +267 -0
- package/dist/testing.d.ts +3 -3
- package/dist/testing.js +2 -2
- package/docs/api.md +157 -14
- package/docs/migration-baseline.md +20 -12
- package/docs/proposals/browser-runtime-v1/implementation-plan.md +533 -0
- package/docs/proposals/browser-runtime-v1/requirements.md +354 -0
- package/docs/proposals/browser-runtime-v1/verification.md +56 -0
- package/docs/proposals/shared-worker-call-first/SWCF-009-typed-transfer-follow-up.md +34 -0
- package/docs/proposals/shared-worker-call-first/implementation-plan.md +567 -0
- package/package.json +4 -1
- package/dist/chunk-KGNF36DZ.js.map +0 -1
- package/dist/resourceRegistry-BAnqKcp7.d.ts +0 -157
|
@@ -24,16 +24,12 @@ var PermissionDeniedError = class extends Error {
|
|
|
24
24
|
};
|
|
25
25
|
function createRemoteServiceMessageCodec(options = {}) {
|
|
26
26
|
const prefix = options.prefix ?? "webloom.remote-service";
|
|
27
|
-
const protocolVersion = options.protocolVersion ?? "
|
|
27
|
+
const protocolVersion = options.protocolVersion ?? "webloom.remote-service.v2";
|
|
28
28
|
const types = /* @__PURE__ */ new Map([
|
|
29
29
|
["call", `${prefix}.call`],
|
|
30
30
|
["result", `${prefix}.result`],
|
|
31
31
|
["error", `${prefix}.error`],
|
|
32
|
-
["cancel", `${prefix}.cancel`]
|
|
33
|
-
["handshake", `${prefix}.handshake`],
|
|
34
|
-
["snapshot", `${prefix}.snapshot`],
|
|
35
|
-
["invalidate", `${prefix}.invalidate`],
|
|
36
|
-
["disconnect", `${prefix}.disconnect`]
|
|
32
|
+
["cancel", `${prefix}.cancel`]
|
|
37
33
|
]);
|
|
38
34
|
return Object.freeze({
|
|
39
35
|
protocolVersion,
|
|
@@ -53,11 +49,14 @@ function createRemoteServiceMessageCodec(options = {}) {
|
|
|
53
49
|
}
|
|
54
50
|
});
|
|
55
51
|
}
|
|
56
|
-
var
|
|
57
|
-
code
|
|
58
|
-
|
|
52
|
+
var RemoteServiceError = class extends Error {
|
|
53
|
+
code;
|
|
54
|
+
details;
|
|
55
|
+
constructor(code, message, details) {
|
|
59
56
|
super(message);
|
|
60
|
-
this.name = "
|
|
57
|
+
this.name = "RemoteServiceError";
|
|
58
|
+
this.code = code;
|
|
59
|
+
this.details = details;
|
|
61
60
|
}
|
|
62
61
|
};
|
|
63
62
|
var UpgradeGateRejectedError = class extends Error {
|
|
@@ -136,11 +135,8 @@ var PluginGraphValidationError = class extends Error {
|
|
|
136
135
|
function unique(values) {
|
|
137
136
|
return [...new Set(values)];
|
|
138
137
|
}
|
|
139
|
-
function
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
function isPluginLifetime(value) {
|
|
143
|
-
return typeof value === "string" && value.trim().length > 0;
|
|
138
|
+
function isRuntimeKind(value) {
|
|
139
|
+
return value === "window-main" || value === "shared-worker";
|
|
144
140
|
}
|
|
145
141
|
function isRecord(value) {
|
|
146
142
|
return typeof value === "object" && value !== null;
|
|
@@ -228,11 +224,11 @@ function validateRuntimeUnitDependencyContracts(manifests) {
|
|
|
228
224
|
if (typeof dependency.contractVersion !== "string" || dependency.contractVersion.trim() === "") {
|
|
229
225
|
errors.push("contractVersion\uFF08\u5951\u7EA6\u7248\u672C\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
|
|
230
226
|
}
|
|
231
|
-
if (!
|
|
232
|
-
errors.push("
|
|
227
|
+
if (!isRuntimeKind(dependency.sourceRuntime)) {
|
|
228
|
+
errors.push("sourceRuntime\uFF08\u63D0\u4F9B\u8005 Runtime\uFF09\u65E0\u6548");
|
|
233
229
|
}
|
|
234
|
-
if (!
|
|
235
|
-
errors.push("
|
|
230
|
+
if (unitValue.runtime !== void 0 && !isRuntimeKind(unitValue.runtime)) {
|
|
231
|
+
errors.push("runtime\uFF08\u76EE\u6807 Runtime\uFF09\u65E0\u6548");
|
|
236
232
|
}
|
|
237
233
|
if (dependency.reason !== void 0 && typeof dependency.reason !== "string") {
|
|
238
234
|
errors.push("reason\uFF08\u4F9D\u8D56\u8BF4\u660E\uFF09\u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
@@ -250,15 +246,34 @@ function validateRuntimeUnitDependencyContracts(manifests) {
|
|
|
250
246
|
});
|
|
251
247
|
});
|
|
252
248
|
}
|
|
249
|
+
for (const manifest of manifests) {
|
|
250
|
+
for (const unit of manifest.units ?? []) {
|
|
251
|
+
if (!isRuntimeKind(unit.runtime)) {
|
|
252
|
+
diagnostics.push({
|
|
253
|
+
code: "plugin.runtime_invalid",
|
|
254
|
+
ids: [manifest.id, unit.id],
|
|
255
|
+
message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143 "${unit.id}" \u58F0\u660E\u4E86\u4E0D\u652F\u6301\u7684 Runtime\uFF1Bv1 \u4EC5\u652F\u6301 window-main/shared-worker`
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
253
260
|
return diagnostics;
|
|
254
261
|
}
|
|
255
|
-
function
|
|
262
|
+
function unitRuntime(unit) {
|
|
263
|
+
return unit.runtime;
|
|
264
|
+
}
|
|
265
|
+
function isRuntimeUnit(unit) {
|
|
266
|
+
return typeof unit.id === "string" && unit.id.length > 0 && isRuntimeKind(unit.runtime);
|
|
267
|
+
}
|
|
268
|
+
function selectedRuntimeUnits(manifest, runtime) {
|
|
256
269
|
const units = manifest.units ?? [];
|
|
257
270
|
if (units.length === 0) return [];
|
|
258
|
-
if (
|
|
259
|
-
|
|
271
|
+
if (runtime !== void 0) return units.filter(
|
|
272
|
+
(unit) => isRuntimeUnit(unit) && unit.runtime === runtime
|
|
273
|
+
);
|
|
274
|
+
return units.length === 1 && isRuntimeUnit(units[0]) ? [units[0]] : [];
|
|
260
275
|
}
|
|
261
|
-
function dependenciesOfManifest(manifest,
|
|
276
|
+
function dependenciesOfManifest(manifest, runtime) {
|
|
262
277
|
const byCapability = /* @__PURE__ */ new Map();
|
|
263
278
|
const add = (dependency) => {
|
|
264
279
|
const previous = byCapability.get(dependency.capability);
|
|
@@ -267,8 +282,8 @@ function dependenciesOfManifest(manifest, execution) {
|
|
|
267
282
|
}
|
|
268
283
|
};
|
|
269
284
|
const units = manifest.units ?? [];
|
|
270
|
-
if (units.length > 1 &&
|
|
271
|
-
const selectedUnits = selectedRuntimeUnits(manifest,
|
|
285
|
+
if (units.length > 1 && runtime === void 0) return [];
|
|
286
|
+
const selectedUnits = selectedRuntimeUnits(manifest, runtime);
|
|
272
287
|
if (units.length > 0 && selectedUnits.length === 0) return [];
|
|
273
288
|
if (units.length === 0) {
|
|
274
289
|
for (const dependency of manifest.dependencies ?? []) add(dependency);
|
|
@@ -279,12 +294,12 @@ function dependenciesOfManifest(manifest, execution) {
|
|
|
279
294
|
}
|
|
280
295
|
return [...byCapability.values()];
|
|
281
296
|
}
|
|
282
|
-
function providesOfManifest(manifest,
|
|
297
|
+
function providesOfManifest(manifest, runtime) {
|
|
283
298
|
const units = manifest.units ?? [];
|
|
284
|
-
if (units.length > 1 &&
|
|
299
|
+
if (units.length > 1 && runtime === void 0) return [];
|
|
285
300
|
return unique([
|
|
286
301
|
...units.length === 0 ? manifest.provides ?? [] : [],
|
|
287
|
-
...selectedRuntimeUnits(manifest,
|
|
302
|
+
...selectedRuntimeUnits(manifest, runtime).flatMap((unit) => unit.provides ?? [])
|
|
288
303
|
]);
|
|
289
304
|
}
|
|
290
305
|
function runtimeUnitProviders(manifests, capability) {
|
|
@@ -292,11 +307,11 @@ function runtimeUnitProviders(manifests, capability) {
|
|
|
292
307
|
for (const manifest of manifests) {
|
|
293
308
|
for (const unit of manifest.units ?? []) {
|
|
294
309
|
if (!unit.provides?.includes(capability)) continue;
|
|
310
|
+
if (!isRuntimeUnit(unit)) continue;
|
|
295
311
|
providers.push({
|
|
296
312
|
pluginId: manifest.id,
|
|
297
313
|
unitId: unit.id,
|
|
298
|
-
|
|
299
|
-
lifetime: unit.lifetime,
|
|
314
|
+
runtime: unit.runtime,
|
|
300
315
|
contractVersion: unit.providedContracts?.[capability]
|
|
301
316
|
});
|
|
302
317
|
}
|
|
@@ -305,12 +320,12 @@ function runtimeUnitProviders(manifests, capability) {
|
|
|
305
320
|
}
|
|
306
321
|
function matchingRuntimeUnitProviders(manifests, dependency) {
|
|
307
322
|
return runtimeUnitProviders(manifests, dependency.capability).filter(
|
|
308
|
-
(provider) => provider.
|
|
323
|
+
(provider) => provider.runtime === dependency.sourceRuntime && provider.contractVersion === dependency.contractVersion
|
|
309
324
|
);
|
|
310
325
|
}
|
|
311
|
-
function runtimeDependenciesForValidation(manifest,
|
|
326
|
+
function runtimeDependenciesForValidation(manifest, runtime) {
|
|
312
327
|
const units = manifest.units ?? [];
|
|
313
|
-
const selected =
|
|
328
|
+
const selected = runtime === void 0 ? units : units.filter((unit) => unitRuntime(unit) === runtime);
|
|
314
329
|
return selected.flatMap((unit) => unit.dependencies ?? []);
|
|
315
330
|
}
|
|
316
331
|
function buildPluginGraph(manifests, options = {}) {
|
|
@@ -322,9 +337,9 @@ function buildPluginGraph(manifests, options = {}) {
|
|
|
322
337
|
const unitGraph = {};
|
|
323
338
|
const enabled = options.enabledPluginIds;
|
|
324
339
|
for (const manifest of manifests) {
|
|
325
|
-
const selectedUnits = selectedRuntimeUnits(manifest, options.
|
|
326
|
-
const provided = providesOfManifest(manifest, options.
|
|
327
|
-
const dependencyEntries = dependenciesOfManifest(manifest, options.
|
|
340
|
+
const selectedUnits = selectedRuntimeUnits(manifest, options.runtime);
|
|
341
|
+
const provided = providesOfManifest(manifest, options.runtime);
|
|
342
|
+
const dependencyEntries = dependenciesOfManifest(manifest, options.runtime);
|
|
328
343
|
dependencyDetails[manifest.id] = dependencyEntries.map((dependency) => ({ ...dependency }));
|
|
329
344
|
const deps = unique(dependencyEntries.map((dependency) => dependency.capability));
|
|
330
345
|
optionalDependencies[manifest.id] = unique(
|
|
@@ -337,7 +352,7 @@ function buildPluginGraph(manifests, options = {}) {
|
|
|
337
352
|
unitGraph[unitKey] = {
|
|
338
353
|
pluginId: manifest.id,
|
|
339
354
|
unitId: unit.id,
|
|
340
|
-
|
|
355
|
+
runtime: unit.runtime,
|
|
341
356
|
dependencies: unique((unit.dependencies ?? []).map((dependency) => dependency.capability)),
|
|
342
357
|
dependencyDetails: (unit.dependencies ?? []).map((dependency) => ({ ...dependency })),
|
|
343
358
|
provides: unique(unit.provides ?? []),
|
|
@@ -391,7 +406,7 @@ function buildPluginGraph(manifests, options = {}) {
|
|
|
391
406
|
visited.add(pluginId);
|
|
392
407
|
return;
|
|
393
408
|
}
|
|
394
|
-
for (const dependency of dependenciesOfManifest(manifest, options.
|
|
409
|
+
for (const dependency of dependenciesOfManifest(manifest, options.runtime)) {
|
|
395
410
|
if (dependency.optional) continue;
|
|
396
411
|
const provider = firstProvider.get(dependency.capability);
|
|
397
412
|
if (provider) visit(provider);
|
|
@@ -439,9 +454,12 @@ function validatePluginGraph(manifests, options = {}) {
|
|
|
439
454
|
}
|
|
440
455
|
}
|
|
441
456
|
for (const manifest of manifests) {
|
|
442
|
-
const strictDependencies = runtimeDependenciesForValidation(manifest, options.
|
|
457
|
+
const strictDependencies = runtimeDependenciesForValidation(manifest, options.runtime);
|
|
443
458
|
for (const dependency of strictDependencies) {
|
|
444
459
|
if (dependency.optional) continue;
|
|
460
|
+
if (options.externalRuntimeDependencies && options.runtime !== void 0 && dependency.sourceRuntime !== options.runtime) {
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
445
463
|
const matches = matchingRuntimeUnitProviders(manifests, dependency);
|
|
446
464
|
if (matches.length > 0) {
|
|
447
465
|
if (matches.length > 1 && !multiProvider.has(dependency.capability)) {
|
|
@@ -474,9 +492,9 @@ function validatePluginGraph(manifests, options = {}) {
|
|
|
474
492
|
}
|
|
475
493
|
for (const manifest of manifests) {
|
|
476
494
|
const strictDependencyCapabilities = new Set(
|
|
477
|
-
runtimeDependenciesForValidation(manifest, options.
|
|
495
|
+
runtimeDependenciesForValidation(manifest, options.runtime).map((dependency) => dependency.capability)
|
|
478
496
|
);
|
|
479
|
-
for (const dependency of dependenciesOfManifest(manifest, options.
|
|
497
|
+
for (const dependency of dependenciesOfManifest(manifest, options.runtime)) {
|
|
480
498
|
if (dependency.optional) continue;
|
|
481
499
|
if (strictDependencyCapabilities.has(dependency.capability)) continue;
|
|
482
500
|
const provided = (graph.providers?.[dependency.capability]?.length ?? 0) > 0 || builtins.has(dependency.capability);
|
|
@@ -2189,24 +2207,26 @@ function lifecycleStateFor(state, desired) {
|
|
|
2189
2207
|
return desired ? "waiting" : "disabled";
|
|
2190
2208
|
}
|
|
2191
2209
|
}
|
|
2192
|
-
function selectedUnit(manifest,
|
|
2210
|
+
function selectedUnit(manifest, runtime) {
|
|
2193
2211
|
const units = manifest.units ?? [];
|
|
2194
2212
|
if (units.length > 1) {
|
|
2195
|
-
const matches =
|
|
2213
|
+
const matches = runtime === void 0 ? [] : units.filter(
|
|
2214
|
+
(unit) => unit.runtime === runtime
|
|
2215
|
+
);
|
|
2196
2216
|
return matches.length === 1 ? matches[0] : void 0;
|
|
2197
2217
|
}
|
|
2198
2218
|
if (units.length === 1) {
|
|
2199
|
-
|
|
2219
|
+
const unit = units[0];
|
|
2220
|
+
return unit && (runtime === void 0 || unit.runtime === runtime) && unit.runtime !== void 0 ? unit : void 0;
|
|
2200
2221
|
}
|
|
2222
|
+
const targetRuntime = runtime ?? "window-main";
|
|
2201
2223
|
return {
|
|
2202
2224
|
id: manifest.id,
|
|
2203
|
-
|
|
2204
|
-
lifetime: manifest.meta.lifetime ?? defaultLifetime,
|
|
2225
|
+
runtime: targetRuntime,
|
|
2205
2226
|
dependencies: manifest.dependencies?.map((dependency) => ({
|
|
2206
2227
|
capability: dependency.capability,
|
|
2207
2228
|
contractVersion: dependency.contractVersion ?? `${dependency.capability}.v1`,
|
|
2208
|
-
|
|
2209
|
-
scope: dependency.scope ?? defaultLifetime,
|
|
2229
|
+
sourceRuntime: dependency.sourceRuntime ?? targetRuntime,
|
|
2210
2230
|
...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
|
|
2211
2231
|
...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
|
|
2212
2232
|
})),
|
|
@@ -2216,17 +2236,12 @@ function selectedUnit(manifest, execution, defaultLifetime) {
|
|
|
2216
2236
|
contribution: manifest.contribution
|
|
2217
2237
|
};
|
|
2218
2238
|
}
|
|
2219
|
-
function dependenciesFor(manifest,
|
|
2220
|
-
return dependenciesOfManifest(manifest,
|
|
2239
|
+
function dependenciesFor(manifest, runtime) {
|
|
2240
|
+
return dependenciesOfManifest(manifest, runtime);
|
|
2221
2241
|
}
|
|
2222
2242
|
function setupFor(manifest, unit, options) {
|
|
2223
2243
|
return options.runtimeUnitImplementationRegistry?.get(manifest.id, unit.id);
|
|
2224
2244
|
}
|
|
2225
|
-
function stableValue(value) {
|
|
2226
|
-
if (Array.isArray(value)) return `[${value.map(stableValue).join(",")}]`;
|
|
2227
|
-
if (!value || typeof value !== "object") return JSON.stringify(value);
|
|
2228
|
-
return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => `${JSON.stringify(key)}:${stableValue(item)}`).join(",")}}`;
|
|
2229
|
-
}
|
|
2230
2245
|
var StartupCapabilityError = class extends Error {
|
|
2231
2246
|
details;
|
|
2232
2247
|
constructor(details, phase = "startup") {
|
|
@@ -2244,6 +2259,7 @@ var StartupPluginError = class extends Error {
|
|
|
2244
2259
|
}
|
|
2245
2260
|
};
|
|
2246
2261
|
function createPluginHost(options = {}) {
|
|
2262
|
+
const runtimeKind = options.runtime;
|
|
2247
2263
|
const listeners = /* @__PURE__ */ new Set();
|
|
2248
2264
|
let versionCounter = 0;
|
|
2249
2265
|
let hostDisposed = false;
|
|
@@ -2290,7 +2306,6 @@ function createPluginHost(options = {}) {
|
|
|
2290
2306
|
let intentSnapshot = options.pluginIntentCoordinator?.snapshot();
|
|
2291
2307
|
let removeConfigSubscription = () => void 0;
|
|
2292
2308
|
let removeIntentSubscription = () => void 0;
|
|
2293
|
-
let removeScopeSubscription = () => void 0;
|
|
2294
2309
|
let reconcilePromise;
|
|
2295
2310
|
const desiredEnabledFor = (pluginId, manifest) => {
|
|
2296
2311
|
if (manifest?.meta.startup === "required" || manifest?.meta.canDisable === false) return true;
|
|
@@ -2300,8 +2315,10 @@ function createPluginHost(options = {}) {
|
|
|
2300
2315
|
return configStore.read()[pluginId] ?? manifest?.meta.defaultEnabled ?? false;
|
|
2301
2316
|
};
|
|
2302
2317
|
const desiredRevisionFor = (pluginId) => intentSnapshot?.desiredRevision[pluginId];
|
|
2303
|
-
const selected = (manifest) => selectedUnit(manifest,
|
|
2304
|
-
const graph = () => buildPluginGraph([
|
|
2318
|
+
const selected = (manifest) => selectedUnit(manifest, runtimeKind);
|
|
2319
|
+
const graph = () => buildPluginGraph([
|
|
2320
|
+
...knownManifests.values()
|
|
2321
|
+
], { runtime: runtimeKind, enabledPluginIds: enabledSet });
|
|
2305
2322
|
const validateManifest = (manifest) => {
|
|
2306
2323
|
if (!manifest || typeof manifest.id !== "string" || manifest.id.trim() === "") throw new Error("Plugin id must be a non-empty string");
|
|
2307
2324
|
if (typeof manifest.name !== "string" || manifest.name.trim() === "") throw new Error(`Plugin "${manifest.id}" name must be a non-empty string`);
|
|
@@ -2312,12 +2329,17 @@ function createPluginHost(options = {}) {
|
|
|
2312
2329
|
throw new Error(`Plugin "${manifest.id}" required startup metadata is inconsistent`);
|
|
2313
2330
|
}
|
|
2314
2331
|
if (manifest.units !== void 0 && !Array.isArray(manifest.units)) throw new Error(`Plugin "${manifest.id}" units must be an array`);
|
|
2332
|
+
if ((manifest.units?.length ?? 0) > 1 && runtimeKind === void 0) {
|
|
2333
|
+
throw new Error(`Plugin "${manifest.id}" runtime must be explicit for multi-unit manifests`);
|
|
2334
|
+
}
|
|
2315
2335
|
if (manifest.units && manifest.units.length > 0) {
|
|
2316
2336
|
const ids = /* @__PURE__ */ new Set();
|
|
2317
2337
|
for (const unit of manifest.units) {
|
|
2318
2338
|
if (!unit.id || ids.has(unit.id)) throw new Error(`Plugin "${manifest.id}" has duplicate or empty unit id`);
|
|
2319
2339
|
ids.add(unit.id);
|
|
2320
|
-
if (
|
|
2340
|
+
if (unit.runtime !== "window-main" && unit.runtime !== "shared-worker") {
|
|
2341
|
+
throw new Error(`Plugin "${manifest.id}" unit "${unit.id}" declares an unsupported Runtime`);
|
|
2342
|
+
}
|
|
2321
2343
|
}
|
|
2322
2344
|
}
|
|
2323
2345
|
options.manifestValidator?.(manifest);
|
|
@@ -2325,9 +2347,18 @@ function createPluginHost(options = {}) {
|
|
|
2325
2347
|
const isRequired = (manifest) => manifest.meta.startup === "required" || manifest.meta.canDisable === false;
|
|
2326
2348
|
const missingDependencies = (manifest) => {
|
|
2327
2349
|
const missing = [];
|
|
2328
|
-
|
|
2350
|
+
const selectedUnitForDependencies = selected(manifest);
|
|
2351
|
+
const localRuntime = selectedUnitForDependencies?.runtime ?? runtimeKind;
|
|
2352
|
+
for (const dependency of dependenciesFor(manifest, runtimeKind)) {
|
|
2329
2353
|
if (dependency.optional) continue;
|
|
2330
|
-
|
|
2354
|
+
const sourceRuntime = dependency.sourceRuntime ?? localRuntime;
|
|
2355
|
+
const contractVersion = dependency.contractVersion ?? `${dependency.capability}.v1`;
|
|
2356
|
+
if (sourceRuntime !== void 0 && sourceRuntime !== localRuntime) {
|
|
2357
|
+
const available = options.remoteServiceReferences?.().some((reference) => reference.status === "ready" && reference.runtime === sourceRuntime && reference.capabilityId === dependency.capability && reference.contractVersion === contractVersion) ?? false;
|
|
2358
|
+
if (!available) missing.push(dependency.capability);
|
|
2359
|
+
} else if (!capabilities.has(dependency.capability)) {
|
|
2360
|
+
missing.push(dependency.capability);
|
|
2361
|
+
}
|
|
2331
2362
|
}
|
|
2332
2363
|
return [...new Set(missing)];
|
|
2333
2364
|
};
|
|
@@ -2335,21 +2366,21 @@ function createPluginHost(options = {}) {
|
|
|
2335
2366
|
const unit = selected(manifest);
|
|
2336
2367
|
if (!unit) {
|
|
2337
2368
|
const units = manifest.units ?? [];
|
|
2338
|
-
if (units.length > 0 &&
|
|
2369
|
+
if (units.length > 0 && runtimeKind !== void 0 && !units.some((item) => item.runtime === runtimeKind)) {
|
|
2339
2370
|
const snapshots = runtimeSnapshots?.() ?? [];
|
|
2340
2371
|
if (snapshots.some((item) => item.pluginId === manifest.id)) return `runtime:${manifest.id}:unknown`;
|
|
2341
2372
|
}
|
|
2342
2373
|
return units.length > 0 ? `runtime:${manifest.id}:unit-unavailable` : void 0;
|
|
2343
2374
|
}
|
|
2375
|
+
const unavailable = options.runtimeUnitAvailability?.({
|
|
2376
|
+
pluginId: manifest.id,
|
|
2377
|
+
unitId: unit.id,
|
|
2378
|
+
runtime: unit.runtime
|
|
2379
|
+
});
|
|
2380
|
+
if (unavailable) return unavailable;
|
|
2344
2381
|
if (!setupFor(manifest, unit, options)) return `runtime:${manifest.id}:${unit.id}:implementation-unavailable`;
|
|
2345
2382
|
return void 0;
|
|
2346
2383
|
};
|
|
2347
|
-
const resolveScope = (record, unit) => {
|
|
2348
|
-
const resolver = options.scopeResolver;
|
|
2349
|
-
if (!resolver) return { available: true, parent: rootScope, attributes: rootAttributes, key: stableValue(rootAttributes) };
|
|
2350
|
-
return resolver.resolve({ pluginId: record.manifest.id, unitId: unit.id, lifetime: unit.lifetime, manifest: record.manifest });
|
|
2351
|
-
};
|
|
2352
|
-
const scopeKeyForResolution = (resolution, unit) => resolution.key ?? `${resolution.parent?.identity.scopeId ?? "root"}:${unit.lifetime}:${stableValue(resolution.attributes ?? rootAttributes)}`;
|
|
2353
2384
|
const revokeContributions = (record) => {
|
|
2354
2385
|
for (const contribution of record.contributions) {
|
|
2355
2386
|
if (!contribution.active) continue;
|
|
@@ -2677,19 +2708,25 @@ function createPluginHost(options = {}) {
|
|
|
2677
2708
|
bumpVersion();
|
|
2678
2709
|
return;
|
|
2679
2710
|
}
|
|
2680
|
-
const resolution = resolveScope(record, unit);
|
|
2681
|
-
if (!resolution.available) {
|
|
2682
|
-
record.state = "blocked";
|
|
2683
|
-
record.blockedBy = [resolution.reason ?? `scope:${unit.lifetime}:unavailable`];
|
|
2684
|
-
bumpVersion();
|
|
2685
|
-
return;
|
|
2686
|
-
}
|
|
2687
2711
|
const instanceId = makeInstanceId(record.manifest.id, unit.id);
|
|
2688
2712
|
let scope;
|
|
2689
2713
|
try {
|
|
2690
|
-
const
|
|
2691
|
-
|
|
2692
|
-
|
|
2714
|
+
const attributes = Object.freeze({
|
|
2715
|
+
...rootAttributes,
|
|
2716
|
+
...options.runtimeUnitAttributes?.({
|
|
2717
|
+
pluginId: record.manifest.id,
|
|
2718
|
+
unitId: unit.id,
|
|
2719
|
+
runtime: unit.runtime,
|
|
2720
|
+
manifest: record.manifest
|
|
2721
|
+
}) ?? {}
|
|
2722
|
+
});
|
|
2723
|
+
const parent = options.runtimeUnitParentScope?.({
|
|
2724
|
+
pluginId: record.manifest.id,
|
|
2725
|
+
unitId: unit.id,
|
|
2726
|
+
runtime: unit.runtime,
|
|
2727
|
+
manifest: record.manifest
|
|
2728
|
+
}) ?? rootScope;
|
|
2729
|
+
scope = parent.child("runtime-unit", { pluginId: record.manifest.id, attributes });
|
|
2693
2730
|
} catch (error) {
|
|
2694
2731
|
record.state = "blocked";
|
|
2695
2732
|
record.blockedBy = [errorMessage4(error)];
|
|
@@ -2701,7 +2738,6 @@ function createPluginHost(options = {}) {
|
|
|
2701
2738
|
record.instanceId = instanceId;
|
|
2702
2739
|
record.unitId = unit.id;
|
|
2703
2740
|
instanceAttributes.set(instanceId, scope.identity.attributes);
|
|
2704
|
-
record.scopeKey = scopeKeyForResolution(resolution, unit);
|
|
2705
2741
|
record.error = void 0;
|
|
2706
2742
|
record.blockedBy = void 0;
|
|
2707
2743
|
bumpVersion();
|
|
@@ -2712,7 +2748,7 @@ function createPluginHost(options = {}) {
|
|
|
2712
2748
|
const result = await setup(context);
|
|
2713
2749
|
record.teardown = typeof result === "function" ? result : void 0;
|
|
2714
2750
|
await registerContribution(record, unit, instanceId, scope);
|
|
2715
|
-
const declared = providesOfManifest(record.manifest,
|
|
2751
|
+
const declared = providesOfManifest(record.manifest, runtimeKind);
|
|
2716
2752
|
const missingDeclarations = declared.filter((key) => !record.capabilities.has(key));
|
|
2717
2753
|
if (missingDeclarations.length > 0) {
|
|
2718
2754
|
throw new Error(`Plugin "${record.manifest.id}" did not provide declared capabilities: ${missingDeclarations.join(", ")}`);
|
|
@@ -2743,7 +2779,13 @@ function createPluginHost(options = {}) {
|
|
|
2743
2779
|
record.instanceId = void 0;
|
|
2744
2780
|
record.unitId = void 0;
|
|
2745
2781
|
bumpVersion();
|
|
2746
|
-
throw new StartupPluginError({
|
|
2782
|
+
throw new StartupPluginError({
|
|
2783
|
+
pluginId: record.manifest.id,
|
|
2784
|
+
unitId: unit.id,
|
|
2785
|
+
capabilities: providesOfManifest(record.manifest, runtimeKind),
|
|
2786
|
+
state: record.state,
|
|
2787
|
+
error: record.error
|
|
2788
|
+
});
|
|
2747
2789
|
}
|
|
2748
2790
|
})();
|
|
2749
2791
|
starting.set(pluginId, task);
|
|
@@ -2780,32 +2822,29 @@ function createPluginHost(options = {}) {
|
|
|
2780
2822
|
if (reconcilePromise) return reconcilePromise;
|
|
2781
2823
|
reconcilePromise = (async () => {
|
|
2782
2824
|
const manifests = [...knownManifests.values()];
|
|
2783
|
-
const ordered = orderManifestsByDependencies(manifests,
|
|
2784
|
-
const rotationTargets = manifests.filter((manifest) => {
|
|
2785
|
-
const record = records.get(manifest.id);
|
|
2786
|
-
if (!record || !desiredEnabledFor(manifest.id, manifest) || record.state !== "enabled" && record.state !== "starting") return false;
|
|
2787
|
-
const unit = selected(record.manifest);
|
|
2788
|
-
if (!unit || !options.scopeResolver) return false;
|
|
2789
|
-
const resolution = resolveScope(record, unit);
|
|
2790
|
-
return !resolution.available || record.scopeKey !== scopeKeyForResolution(resolution, unit);
|
|
2791
|
-
});
|
|
2792
|
-
const rotationPlan = [];
|
|
2793
|
-
const planned = /* @__PURE__ */ new Set();
|
|
2794
|
-
for (const target of rotationTargets) {
|
|
2795
|
-
for (const item of collectDisablePlan(target.id)) {
|
|
2796
|
-
if (planned.has(item.manifest.id)) continue;
|
|
2797
|
-
planned.add(item.manifest.id);
|
|
2798
|
-
rotationPlan.push(item);
|
|
2799
|
-
}
|
|
2800
|
-
}
|
|
2801
|
-
for (const item of rotationPlan) {
|
|
2802
|
-
await stopPlugin(item, "scope resolution changed", true);
|
|
2803
|
-
}
|
|
2825
|
+
const ordered = orderManifestsByDependencies(manifests, runtimeKind);
|
|
2804
2826
|
for (const manifest of ordered) {
|
|
2805
2827
|
const record = records.get(manifest.id);
|
|
2806
2828
|
if (!record) continue;
|
|
2829
|
+
if (record && (record.state === "enabled" || record.state === "starting")) {
|
|
2830
|
+
const missing = missingDependencies(manifest);
|
|
2831
|
+
const unavailable = runtimeUnavailable(manifest);
|
|
2832
|
+
if (missing.length > 0 || unavailable) {
|
|
2833
|
+
await stopPlugin(
|
|
2834
|
+
record,
|
|
2835
|
+
"runtime dependency unavailable",
|
|
2836
|
+
true,
|
|
2837
|
+
[.../* @__PURE__ */ new Set([...missing, ...unavailable ? [unavailable] : []])]
|
|
2838
|
+
);
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2807
2841
|
if (desiredEnabledFor(manifest.id, manifest) && record.state !== "enabled" && record.state !== "starting") {
|
|
2808
|
-
|
|
2842
|
+
try {
|
|
2843
|
+
await enable(manifest.id);
|
|
2844
|
+
} catch (error) {
|
|
2845
|
+
if (!isRequired(manifest)) continue;
|
|
2846
|
+
throw error;
|
|
2847
|
+
}
|
|
2809
2848
|
}
|
|
2810
2849
|
}
|
|
2811
2850
|
for (const manifest of manifests) {
|
|
@@ -2819,10 +2858,14 @@ function createPluginHost(options = {}) {
|
|
|
2819
2858
|
});
|
|
2820
2859
|
return reconcilePromise;
|
|
2821
2860
|
};
|
|
2822
|
-
function orderManifestsByDependencies(manifests,
|
|
2861
|
+
function orderManifestsByDependencies(manifests, runtime) {
|
|
2823
2862
|
const byId = new Map(manifests.map((manifest) => [manifest.id, manifest]));
|
|
2824
2863
|
const providers = /* @__PURE__ */ new Map();
|
|
2825
|
-
for (const manifest of manifests)
|
|
2864
|
+
for (const manifest of manifests) {
|
|
2865
|
+
for (const capability of providesOfManifest(manifest, runtime)) {
|
|
2866
|
+
if (!providers.has(capability)) providers.set(capability, manifest.id);
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2826
2869
|
const visited = /* @__PURE__ */ new Set();
|
|
2827
2870
|
const visiting = /* @__PURE__ */ new Set();
|
|
2828
2871
|
const ordered = [];
|
|
@@ -2830,7 +2873,7 @@ function createPluginHost(options = {}) {
|
|
|
2830
2873
|
if (visited.has(manifest.id)) return;
|
|
2831
2874
|
if (visiting.has(manifest.id)) return;
|
|
2832
2875
|
visiting.add(manifest.id);
|
|
2833
|
-
for (const dependency of dependenciesFor(manifest,
|
|
2876
|
+
for (const dependency of dependenciesFor(manifest, runtime)) {
|
|
2834
2877
|
if (dependency.optional) continue;
|
|
2835
2878
|
const provider = providers.get(dependency.capability);
|
|
2836
2879
|
const providerManifest = provider ? byId.get(provider) : void 0;
|
|
@@ -2868,7 +2911,14 @@ function createPluginHost(options = {}) {
|
|
|
2868
2911
|
...record.unitId ? { unitId: record.unitId } : {},
|
|
2869
2912
|
...record.blockedBy ? { blockedBy: [...record.blockedBy] } : {},
|
|
2870
2913
|
...record.cleanup ? { cleanup: record.cleanup } : {},
|
|
2871
|
-
units: unit ? [{
|
|
2914
|
+
units: unit ? [{
|
|
2915
|
+
pluginId,
|
|
2916
|
+
unitId: unit.id,
|
|
2917
|
+
runtime: unit.runtime,
|
|
2918
|
+
kind: record.state,
|
|
2919
|
+
...record.instanceId ? { instanceId: record.instanceId } : {},
|
|
2920
|
+
...record.error ? { error: record.error } : {}
|
|
2921
|
+
}] : []
|
|
2872
2922
|
};
|
|
2873
2923
|
},
|
|
2874
2924
|
scope: (pluginId) => records.get(pluginId)?.scope,
|
|
@@ -2889,7 +2939,14 @@ function createPluginHost(options = {}) {
|
|
|
2889
2939
|
},
|
|
2890
2940
|
validateManifestSet(manifests) {
|
|
2891
2941
|
for (const manifest of manifests) validateManifest(manifest);
|
|
2892
|
-
validatePluginGraph([...manifests], {
|
|
2942
|
+
validatePluginGraph([...manifests], {
|
|
2943
|
+
runtime: runtimeKind,
|
|
2944
|
+
builtinCapabilities: /* @__PURE__ */ new Set([
|
|
2945
|
+
...capabilities.keys(),
|
|
2946
|
+
...(options.remoteServiceReferences?.() ?? []).map((reference) => reference.capabilityId)
|
|
2947
|
+
]),
|
|
2948
|
+
externalRuntimeDependencies: options.externalRuntimeDependencies
|
|
2949
|
+
});
|
|
2893
2950
|
},
|
|
2894
2951
|
provide(key, value) {
|
|
2895
2952
|
if (hostDisposed) throw new LifecycleScopeRevokedError("Plugin host is disposed");
|
|
@@ -2912,7 +2969,15 @@ function createPluginHost(options = {}) {
|
|
|
2912
2969
|
validateManifest(manifest);
|
|
2913
2970
|
if (knownManifests.has(manifest.id)) throw new Error(`Plugin "${manifest.id}" is already registered`);
|
|
2914
2971
|
}
|
|
2915
|
-
validatePluginGraph(current, {
|
|
2972
|
+
validatePluginGraph(current, {
|
|
2973
|
+
runtime: runtimeKind,
|
|
2974
|
+
builtinCapabilities: /* @__PURE__ */ new Set([
|
|
2975
|
+
...capabilities.keys(),
|
|
2976
|
+
...(options.remoteServiceReferences?.() ?? []).map((reference) => reference.capabilityId)
|
|
2977
|
+
]),
|
|
2978
|
+
allowMissingDependencies: false,
|
|
2979
|
+
externalRuntimeDependencies: options.externalRuntimeDependencies
|
|
2980
|
+
});
|
|
2916
2981
|
for (const manifest of current) {
|
|
2917
2982
|
knownManifests.set(manifest.id, manifest);
|
|
2918
2983
|
records.set(manifest.id, { manifest, state: "registered", disposeCallbacks: [], capabilities: /* @__PURE__ */ new Set(), contributions: [] });
|
|
@@ -2984,6 +3049,13 @@ function createPluginHost(options = {}) {
|
|
|
2984
3049
|
);
|
|
2985
3050
|
return { ok: true };
|
|
2986
3051
|
},
|
|
3052
|
+
suspend: async (pluginId, reason = "runtime identity changed") => {
|
|
3053
|
+
const record = records.get(pluginId);
|
|
3054
|
+
if (!record) return;
|
|
3055
|
+
if (record.state !== "enabled" && record.state !== "starting") return;
|
|
3056
|
+
beginStop(record, reason, true);
|
|
3057
|
+
await stopPlugin(record, reason, true);
|
|
3058
|
+
},
|
|
2987
3059
|
unregister: async (pluginId) => {
|
|
2988
3060
|
const record = records.get(pluginId);
|
|
2989
3061
|
if (!record) return;
|
|
@@ -2999,7 +3071,6 @@ function createPluginHost(options = {}) {
|
|
|
2999
3071
|
hostDisposed = true;
|
|
3000
3072
|
removeConfigSubscription();
|
|
3001
3073
|
removeIntentSubscription();
|
|
3002
|
-
removeScopeSubscription();
|
|
3003
3074
|
disposePromise = (async () => {
|
|
3004
3075
|
for (const record of [...records.values()].reverse()) {
|
|
3005
3076
|
if (record.state === "enabled" || record.state === "starting") {
|
|
@@ -3049,13 +3120,6 @@ function createPluginHost(options = {}) {
|
|
|
3049
3120
|
void reconcile().catch(() => void 0);
|
|
3050
3121
|
bumpVersion();
|
|
3051
3122
|
});
|
|
3052
|
-
if (options.scopeResolver?.subscribe) {
|
|
3053
|
-
removeScopeSubscription = options.scopeResolver.subscribe(() => {
|
|
3054
|
-
void reconcile().catch(() => void 0);
|
|
3055
|
-
resourceStore.refreshRuntimeBindings();
|
|
3056
|
-
bumpVersion();
|
|
3057
|
-
});
|
|
3058
|
-
}
|
|
3059
3123
|
return host;
|
|
3060
3124
|
}
|
|
3061
3125
|
|
|
@@ -3082,206 +3146,82 @@ function createRuntimeUnitImplementationRegistry(implementations = []) {
|
|
|
3082
3146
|
};
|
|
3083
3147
|
}
|
|
3084
3148
|
|
|
3085
|
-
// src/
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
function cloneSnapshot(snapshot) {
|
|
3096
|
-
return {
|
|
3097
|
-
revision: snapshot.revision,
|
|
3098
|
-
desiredEnabled: { ...snapshot.desiredEnabled },
|
|
3099
|
-
desiredRevision: { ...snapshot.desiredRevision }
|
|
3100
|
-
};
|
|
3101
|
-
}
|
|
3102
|
-
function normalizeSnapshot(initial) {
|
|
3103
|
-
const revision = Number.isSafeInteger(initial?.revision) && (initial?.revision ?? 0) >= 0 ? initial.revision : 0;
|
|
3104
|
-
const desiredEnabled = {};
|
|
3105
|
-
for (const [pluginId, value] of Object.entries(initial?.desiredEnabled ?? {})) {
|
|
3106
|
-
if (typeof value === "boolean") desiredEnabled[pluginId] = value;
|
|
3149
|
+
// src/runtime/runtimeTypes.ts
|
|
3150
|
+
var RuntimeInitializationError = class extends Error {
|
|
3151
|
+
code = "runtime_initialization_failed";
|
|
3152
|
+
details;
|
|
3153
|
+
constructor(details) {
|
|
3154
|
+
super(
|
|
3155
|
+
`Runtime initialization failed${details.pluginId ? ` for ${details.pluginId}` : ""} during ${details.phase}: ${details.error}`
|
|
3156
|
+
);
|
|
3157
|
+
this.name = "RuntimeInitializationError";
|
|
3158
|
+
this.details = details;
|
|
3107
3159
|
}
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3160
|
+
};
|
|
3161
|
+
var RuntimeUnavailableError = class extends Error {
|
|
3162
|
+
code = "transport_unavailable";
|
|
3163
|
+
constructor(message = "Runtime is unavailable") {
|
|
3164
|
+
super(message);
|
|
3165
|
+
this.name = "RuntimeUnavailableError";
|
|
3111
3166
|
}
|
|
3112
|
-
|
|
3167
|
+
};
|
|
3168
|
+
|
|
3169
|
+
// src/runtime/runtimeProtocol.ts
|
|
3170
|
+
var RUNTIME_PROTOCOL_VERSION = "webloom.runtime.v2";
|
|
3171
|
+
var RUNTIME_SNAPSHOT_TYPE = "webloom.runtime.snapshot";
|
|
3172
|
+
var RUNTIME_ERROR_TYPE = "webloom.runtime.error";
|
|
3173
|
+
function isRecord2(input) {
|
|
3174
|
+
return typeof input === "object" && input !== null;
|
|
3113
3175
|
}
|
|
3114
|
-
function
|
|
3115
|
-
return
|
|
3116
|
-
command.authorityInstanceId,
|
|
3117
|
-
command.expectedRevision,
|
|
3118
|
-
command.pluginId,
|
|
3119
|
-
command.desiredEnabled ? "true" : "false"
|
|
3120
|
-
].join("\0");
|
|
3176
|
+
function isNonEmptyString(input) {
|
|
3177
|
+
return typeof input === "string" && input.length > 0;
|
|
3121
3178
|
}
|
|
3122
|
-
function
|
|
3123
|
-
return
|
|
3179
|
+
function isRuntimeKind2(input) {
|
|
3180
|
+
return input === "window-main" || input === "shared-worker";
|
|
3124
3181
|
}
|
|
3125
|
-
function
|
|
3126
|
-
|
|
3127
|
-
const maxCommandRecords = Math.max(1, Math.floor(options.maxCommandRecords ?? 256));
|
|
3128
|
-
let current = normalizeSnapshot(options.initial);
|
|
3129
|
-
const commandRecords = /* @__PURE__ */ new Map();
|
|
3130
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
3131
|
-
let queue = Promise.resolve();
|
|
3132
|
-
const notify = () => {
|
|
3133
|
-
const snapshot = cloneSnapshot(current);
|
|
3134
|
-
for (const listener of [...listeners]) {
|
|
3135
|
-
try {
|
|
3136
|
-
listener(snapshot);
|
|
3137
|
-
} catch {
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
};
|
|
3141
|
-
const process = async (command) => {
|
|
3142
|
-
const candidate = command;
|
|
3143
|
-
const commandId = typeof candidate?.commandId === "string" ? candidate.commandId : "";
|
|
3144
|
-
if (!candidate || typeof candidate.authorityInstanceId !== "string" || candidate.authorityInstanceId.length === 0 || typeof candidate.pluginId !== "string" || candidate.pluginId.length === 0 || typeof candidate.desiredEnabled !== "boolean" || !Number.isSafeInteger(candidate.expectedRevision) || (candidate.expectedRevision ?? -1) < 0 || commandId.length === 0) {
|
|
3145
|
-
const result = commandError("commandId\u3001pluginId \u548C expectedRevision \u5FC5\u987B\u662F\u6709\u6548\u503C");
|
|
3146
|
-
return { ...result, commandId };
|
|
3147
|
-
}
|
|
3148
|
-
if (command.authorityInstanceId !== authorityInstanceId) {
|
|
3149
|
-
return {
|
|
3150
|
-
status: "stale-authority",
|
|
3151
|
-
commandId: command.commandId,
|
|
3152
|
-
expectedAuthorityInstanceId: authorityInstanceId
|
|
3153
|
-
};
|
|
3154
|
-
}
|
|
3155
|
-
const fingerprint = commandFingerprint(command);
|
|
3156
|
-
const existing = commandRecords.get(command.commandId);
|
|
3157
|
-
if (existing) {
|
|
3158
|
-
if (existing.fingerprint !== fingerprint) {
|
|
3159
|
-
return {
|
|
3160
|
-
status: "command-conflict",
|
|
3161
|
-
commandId: command.commandId,
|
|
3162
|
-
message: "\u76F8\u540C commandId \u7684\u547D\u4EE4\u5185\u5BB9\u4E0D\u540C\uFF0C\u62D2\u7EDD\u8986\u76D6\u539F\u547D\u4EE4"
|
|
3163
|
-
};
|
|
3164
|
-
}
|
|
3165
|
-
return {
|
|
3166
|
-
status: "duplicate",
|
|
3167
|
-
commandId: command.commandId,
|
|
3168
|
-
snapshot: cloneSnapshot(existing.result.snapshot),
|
|
3169
|
-
persisted: true
|
|
3170
|
-
};
|
|
3171
|
-
}
|
|
3172
|
-
if (command.expectedRevision !== current.revision) {
|
|
3173
|
-
return {
|
|
3174
|
-
status: "revision-conflict",
|
|
3175
|
-
commandId: command.commandId,
|
|
3176
|
-
snapshot: cloneSnapshot(current)
|
|
3177
|
-
};
|
|
3178
|
-
}
|
|
3179
|
-
const next = {
|
|
3180
|
-
revision: current.revision + 1,
|
|
3181
|
-
desiredEnabled: {
|
|
3182
|
-
...current.desiredEnabled,
|
|
3183
|
-
[command.pluginId]: command.desiredEnabled
|
|
3184
|
-
},
|
|
3185
|
-
desiredRevision: {
|
|
3186
|
-
...current.desiredRevision,
|
|
3187
|
-
[command.pluginId]: (current.desiredRevision[command.pluginId] ?? 0) + 1
|
|
3188
|
-
}
|
|
3189
|
-
};
|
|
3190
|
-
try {
|
|
3191
|
-
await options.persist?.(cloneSnapshot(next));
|
|
3192
|
-
} catch (error) {
|
|
3193
|
-
return {
|
|
3194
|
-
status: "persistence-failed",
|
|
3195
|
-
commandId: command.commandId,
|
|
3196
|
-
message: error instanceof Error ? error.message : String(error),
|
|
3197
|
-
snapshot: cloneSnapshot(current)
|
|
3198
|
-
};
|
|
3199
|
-
}
|
|
3200
|
-
current = next;
|
|
3201
|
-
const accepted = {
|
|
3202
|
-
status: "accepted",
|
|
3203
|
-
commandId: command.commandId,
|
|
3204
|
-
snapshot: cloneSnapshot(current),
|
|
3205
|
-
persisted: true
|
|
3206
|
-
};
|
|
3207
|
-
commandRecords.set(command.commandId, {
|
|
3208
|
-
fingerprint,
|
|
3209
|
-
result: accepted
|
|
3210
|
-
});
|
|
3211
|
-
while (commandRecords.size > maxCommandRecords) {
|
|
3212
|
-
const oldest = commandRecords.keys().next().value;
|
|
3213
|
-
if (oldest === void 0) break;
|
|
3214
|
-
commandRecords.delete(oldest);
|
|
3215
|
-
}
|
|
3216
|
-
notify();
|
|
3217
|
-
return {
|
|
3218
|
-
status: "accepted",
|
|
3219
|
-
commandId: accepted.commandId,
|
|
3220
|
-
snapshot: cloneSnapshot(accepted.snapshot),
|
|
3221
|
-
persisted: true
|
|
3222
|
-
};
|
|
3223
|
-
};
|
|
3224
|
-
const controller = {
|
|
3225
|
-
authorityInstanceId,
|
|
3226
|
-
snapshot: () => cloneSnapshot(current),
|
|
3227
|
-
submit(command) {
|
|
3228
|
-
const result = queue.then(() => process(command));
|
|
3229
|
-
queue = result.then(() => void 0, () => void 0);
|
|
3230
|
-
return result;
|
|
3231
|
-
},
|
|
3232
|
-
subscribe(listener) {
|
|
3233
|
-
listeners.add(listener);
|
|
3234
|
-
return () => listeners.delete(listener);
|
|
3235
|
-
}
|
|
3236
|
-
};
|
|
3237
|
-
return controller;
|
|
3182
|
+
function isPluginStateKind(input) {
|
|
3183
|
+
return input === "registered" || input === "starting" || input === "stopping" || input === "enabled" || input === "disabled" || input === "blocked" || input === "error-disabled" || input === "cleanup-pending" || input === "unknown";
|
|
3238
3184
|
}
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
3244
|
-
return `service-request:${crypto.randomUUID()}`;
|
|
3245
|
-
}
|
|
3246
|
-
} catch {
|
|
3247
|
-
}
|
|
3248
|
-
return `service-request:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
3185
|
+
function isRemoteServiceReference(input) {
|
|
3186
|
+
if (!isRecord2(input)) return false;
|
|
3187
|
+
const reference = input;
|
|
3188
|
+
return isNonEmptyString(reference.capabilityId) && isNonEmptyString(reference.contractVersion) && isRuntimeKind2(reference.runtime) && isNonEmptyString(reference.runtimeInstanceId) && isNonEmptyString(reference.serviceInstanceId) && (reference.status === "starting" || reference.status === "ready" || reference.status === "unavailable" || reference.status === "failed") && isRecord2(reference.attributes) && !Array.isArray(reference.attributes) && (reference.grantId === void 0 || isNonEmptyString(reference.grantId)) && (reference.authorizationRevision === void 0 || Number.isSafeInteger(reference.authorizationRevision) && reference.authorizationRevision >= 0);
|
|
3249
3189
|
}
|
|
3250
|
-
function
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
const alreadyAborted = activeSignals.find((signal) => signal.aborted);
|
|
3260
|
-
if (alreadyAborted) {
|
|
3261
|
-
const controller2 = new AbortController();
|
|
3262
|
-
controller2.abort(alreadyAborted.reason);
|
|
3263
|
-
return { signal: controller2.signal, dispose: () => void 0 };
|
|
3264
|
-
}
|
|
3265
|
-
const controller = new AbortController();
|
|
3266
|
-
const abort = (source) => {
|
|
3267
|
-
try {
|
|
3268
|
-
controller.abort(source.reason);
|
|
3269
|
-
} catch {
|
|
3270
|
-
controller.abort();
|
|
3271
|
-
}
|
|
3272
|
-
};
|
|
3273
|
-
const listeners = activeSignals.map((signal) => {
|
|
3274
|
-
const listener = () => abort(signal);
|
|
3275
|
-
signal.addEventListener("abort", listener, { once: true });
|
|
3276
|
-
return { signal, listener };
|
|
3190
|
+
function isRuntimeSnapshotUnit(input) {
|
|
3191
|
+
if (!isRecord2(input)) return false;
|
|
3192
|
+
const unit = input;
|
|
3193
|
+
return isNonEmptyString(unit.pluginId) && isNonEmptyString(unit.unitId) && isRuntimeKind2(unit.runtime) && isPluginStateKind(unit.state) && (unit.instanceId === void 0 || isNonEmptyString(unit.instanceId));
|
|
3194
|
+
}
|
|
3195
|
+
function createRuntimeMessageCodec() {
|
|
3196
|
+
return createRemoteServiceMessageCodec({
|
|
3197
|
+
prefix: "webloom.runtime",
|
|
3198
|
+
protocolVersion: RUNTIME_PROTOCOL_VERSION
|
|
3277
3199
|
});
|
|
3200
|
+
}
|
|
3201
|
+
function isRuntimeSnapshot(input) {
|
|
3202
|
+
if (!isRecord2(input)) return false;
|
|
3203
|
+
const message = input;
|
|
3204
|
+
return message.type === RUNTIME_SNAPSHOT_TYPE && isNonEmptyString(message.protocolVersion) && isNonEmptyString(message.runtimeId) && isRuntimeKind2(message.runtimeKind) && isNonEmptyString(message.runtimeInstanceId) && Number.isSafeInteger(message.revision) && message.revision >= 0 && Array.isArray(message.units) && message.units.every(isRuntimeSnapshotUnit) && Array.isArray(message.services) && message.services.every(isRemoteServiceReference) && (message.state === "starting" || message.state === "ready" || message.state === "stopping" || message.state === "failed" || message.state === "disposed");
|
|
3205
|
+
}
|
|
3206
|
+
function isRuntimeSnapshotProtocol(input) {
|
|
3207
|
+
return input.protocolVersion === RUNTIME_PROTOCOL_VERSION;
|
|
3208
|
+
}
|
|
3209
|
+
function isRuntimeError(input) {
|
|
3210
|
+
if (!isRecord2(input)) return false;
|
|
3211
|
+
const message = input;
|
|
3212
|
+
return message.type === RUNTIME_ERROR_TYPE && isNonEmptyString(message.protocolVersion) && (message.code === "runtime_initialization_failed" || message.code === "protocol_mismatch" || message.code === "transport_unavailable") && isNonEmptyString(message.message) && (message.pluginId === void 0 || isNonEmptyString(message.pluginId)) && (message.unitId === void 0 || isNonEmptyString(message.unitId)) && (message.phase === void 0 || message.phase === "startup" || message.phase === "snapshot");
|
|
3213
|
+
}
|
|
3214
|
+
function unitSnapshotFromState(state, runtime) {
|
|
3278
3215
|
return {
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
}
|
|
3216
|
+
pluginId: state.pluginId,
|
|
3217
|
+
unitId: state.unitId,
|
|
3218
|
+
runtime,
|
|
3219
|
+
...state.instanceId !== void 0 ? { instanceId: state.instanceId } : {},
|
|
3220
|
+
state: state.kind
|
|
3283
3221
|
};
|
|
3284
3222
|
}
|
|
3223
|
+
|
|
3224
|
+
// src/transport/serviceBridge.ts
|
|
3285
3225
|
function stableAttributes(value) {
|
|
3286
3226
|
const normalize = (input) => {
|
|
3287
3227
|
if (Array.isArray(input)) return input.map(normalize);
|
|
@@ -3292,33 +3232,127 @@ function stableAttributes(value) {
|
|
|
3292
3232
|
};
|
|
3293
3233
|
return JSON.stringify(normalize(value));
|
|
3294
3234
|
}
|
|
3235
|
+
function isPlainRecord(value) {
|
|
3236
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
3237
|
+
const prototype = Object.getPrototypeOf(value);
|
|
3238
|
+
return prototype === Object.prototype || prototype === null;
|
|
3239
|
+
}
|
|
3240
|
+
function isSafeAttributeValue(value, seen) {
|
|
3241
|
+
if (value === null || value === void 0 || typeof value === "string" || typeof value === "boolean") return true;
|
|
3242
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
3243
|
+
if (typeof value !== "object") return false;
|
|
3244
|
+
if (seen.has(value)) return false;
|
|
3245
|
+
seen.add(value);
|
|
3246
|
+
const valid = Array.isArray(value) ? value.every((item) => isSafeAttributeValue(item, seen)) : isPlainRecord(value) && !Reflect.ownKeys(value).some((key) => typeof key !== "string") && Object.values(value).every((item) => isSafeAttributeValue(item, seen));
|
|
3247
|
+
seen.delete(value);
|
|
3248
|
+
return valid;
|
|
3249
|
+
}
|
|
3250
|
+
function isValidAttributes(value) {
|
|
3251
|
+
return isPlainRecord(value) && isSafeAttributeValue(value, /* @__PURE__ */ new Set());
|
|
3252
|
+
}
|
|
3253
|
+
function isValidSnapshot(snapshot) {
|
|
3254
|
+
if (!snapshot || typeof snapshot !== "object" || typeof snapshot.protocolVersion !== "string" || snapshot.protocolVersion.length === 0 || typeof snapshot.runtimeInstanceId !== "string" || snapshot.runtimeInstanceId.length === 0 || !Number.isSafeInteger(snapshot.revision) || snapshot.revision < 0 || !Array.isArray(snapshot.services)) return false;
|
|
3255
|
+
if (snapshot.runtimeId !== void 0 && (typeof snapshot.runtimeId !== "string" || snapshot.runtimeId.length === 0)) return false;
|
|
3256
|
+
if (snapshot.runtimeKind !== void 0 && snapshot.runtimeKind !== "window-main" && snapshot.runtimeKind !== "shared-worker") return false;
|
|
3257
|
+
if (snapshot.state !== void 0 && snapshot.state !== "starting" && snapshot.state !== "ready" && snapshot.state !== "stopping" && snapshot.state !== "failed" && snapshot.state !== "disposed") return false;
|
|
3258
|
+
const bindingKeys = /* @__PURE__ */ new Set();
|
|
3259
|
+
const serviceCapabilityKeys = /* @__PURE__ */ new Set();
|
|
3260
|
+
const readyLookupKeys = /* @__PURE__ */ new Set();
|
|
3261
|
+
for (const service of snapshot.services) {
|
|
3262
|
+
if (!service || typeof service !== "object" || typeof service.capabilityId !== "string" || service.capabilityId.length === 0 || typeof service.contractVersion !== "string" || service.contractVersion.length === 0 || service.runtime !== "window-main" && service.runtime !== "shared-worker" || service.runtimeInstanceId !== snapshot.runtimeInstanceId || typeof service.serviceInstanceId !== "string" || service.serviceInstanceId.length === 0 || service.status !== "starting" && service.status !== "ready" && service.status !== "unavailable" && service.status !== "failed" || !isValidAttributes(service.attributes) || service.grantId !== void 0 && (typeof service.grantId !== "string" || service.grantId.length === 0) || service.authorizationRevision !== void 0 && (!Number.isSafeInteger(service.authorizationRevision) || service.authorizationRevision < 0)) return false;
|
|
3263
|
+
if (snapshot.runtimeKind !== void 0 && service.runtime !== snapshot.runtimeKind) return false;
|
|
3264
|
+
const key = bindingKey(service);
|
|
3265
|
+
if (bindingKeys.has(key)) return false;
|
|
3266
|
+
bindingKeys.add(key);
|
|
3267
|
+
const serviceCapabilityKey = `${service.capabilityId}\0${service.serviceInstanceId}`;
|
|
3268
|
+
if (serviceCapabilityKeys.has(serviceCapabilityKey)) return false;
|
|
3269
|
+
serviceCapabilityKeys.add(serviceCapabilityKey);
|
|
3270
|
+
if (service.status === "ready") {
|
|
3271
|
+
const lookupKey = `${service.capabilityId}\0${service.contractVersion}\0${service.runtime}`;
|
|
3272
|
+
if (readyLookupKeys.has(lookupKey)) return false;
|
|
3273
|
+
readyLookupKeys.add(lookupKey);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
return true;
|
|
3277
|
+
}
|
|
3295
3278
|
function referenceKey(reference) {
|
|
3296
3279
|
return [
|
|
3297
3280
|
reference.capabilityId,
|
|
3298
|
-
reference.providerInstanceId,
|
|
3299
|
-
reference.execution,
|
|
3300
3281
|
reference.contractVersion,
|
|
3301
|
-
reference.
|
|
3302
|
-
reference.
|
|
3303
|
-
reference.
|
|
3282
|
+
reference.runtime,
|
|
3283
|
+
reference.runtimeInstanceId,
|
|
3284
|
+
reference.serviceInstanceId,
|
|
3285
|
+
reference.status,
|
|
3304
3286
|
stableAttributes(reference.attributes),
|
|
3305
3287
|
reference.grantId ?? "null",
|
|
3306
|
-
reference.authorizationRevision ?? "null"
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3288
|
+
reference.authorizationRevision ?? "null"
|
|
3289
|
+
].join("\0");
|
|
3290
|
+
}
|
|
3291
|
+
function bindingKey(reference) {
|
|
3292
|
+
return [
|
|
3293
|
+
reference.capabilityId,
|
|
3294
|
+
reference.contractVersion,
|
|
3295
|
+
reference.runtime,
|
|
3296
|
+
reference.runtimeInstanceId,
|
|
3297
|
+
reference.serviceInstanceId
|
|
3310
3298
|
].join("\0");
|
|
3311
3299
|
}
|
|
3312
3300
|
function lookupMatches(reference, lookup) {
|
|
3313
|
-
return reference.
|
|
3301
|
+
return reference.capabilityId === lookup.capabilityId && reference.contractVersion === lookup.contractVersion && (lookup.runtime === void 0 || reference.runtime === lookup.runtime);
|
|
3314
3302
|
}
|
|
3315
|
-
function
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3303
|
+
function ensureTimeout(value, label) {
|
|
3304
|
+
const timeout = value ?? 3e4;
|
|
3305
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
3306
|
+
throw new TypeError(`${label} must be a finite number greater than zero`);
|
|
3307
|
+
}
|
|
3308
|
+
return timeout;
|
|
3309
|
+
}
|
|
3310
|
+
function mergeSignals2(...signals) {
|
|
3311
|
+
const active = signals.filter((signal) => signal !== void 0);
|
|
3312
|
+
if (active.length === 0) return { signal: new AbortController().signal, dispose: () => void 0 };
|
|
3313
|
+
const alreadyAborted = active.find((signal) => signal.aborted);
|
|
3314
|
+
if (alreadyAborted) {
|
|
3315
|
+
const controller2 = new AbortController();
|
|
3316
|
+
controller2.abort(alreadyAborted.reason);
|
|
3317
|
+
return { signal: controller2.signal, dispose: () => void 0 };
|
|
3318
|
+
}
|
|
3319
|
+
const controller = new AbortController();
|
|
3320
|
+
const listeners = active.map((signal) => {
|
|
3321
|
+
const listener = () => {
|
|
3322
|
+
try {
|
|
3323
|
+
controller.abort(signal.reason);
|
|
3324
|
+
} catch {
|
|
3325
|
+
controller.abort();
|
|
3326
|
+
}
|
|
3327
|
+
};
|
|
3328
|
+
signal.addEventListener("abort", listener, { once: true });
|
|
3329
|
+
return { signal, listener };
|
|
3330
|
+
});
|
|
3331
|
+
return {
|
|
3332
|
+
signal: controller.signal,
|
|
3333
|
+
dispose: () => {
|
|
3334
|
+
for (const { signal, listener } of listeners) signal.removeEventListener("abort", listener);
|
|
3335
|
+
}
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
function abortedError(signal, fallback = "request_cancelled") {
|
|
3339
|
+
const reason = signal.reason;
|
|
3340
|
+
if (reason instanceof Error && typeof reason.code === "string") return reason;
|
|
3341
|
+
if (reason instanceof Error && fallback === "request_cancelled") {
|
|
3342
|
+
return new RemoteServiceError("request_cancelled", reason.message);
|
|
3343
|
+
}
|
|
3344
|
+
return new RemoteServiceError(fallback, fallback === "call_timeout" ? "Remote service call timed out" : "Remote service request was cancelled");
|
|
3345
|
+
}
|
|
3346
|
+
function callTimeoutError(timeoutMs) {
|
|
3347
|
+
return new RemoteServiceError("call_timeout", `Remote service call exceeded its ${timeoutMs}ms deadline`, { timeoutMs });
|
|
3348
|
+
}
|
|
3349
|
+
function createServiceBridge(options) {
|
|
3350
|
+
const defaultCallTimeoutMs = ensureTimeout(options.defaultCallTimeoutMs, "defaultCallTimeoutMs");
|
|
3351
|
+
let currentState = "empty";
|
|
3352
|
+
let currentRuntimeInstanceId;
|
|
3353
|
+
let currentRevision;
|
|
3354
|
+
let currentServices = [];
|
|
3355
|
+
let terminalError;
|
|
3322
3356
|
const proxies = /* @__PURE__ */ new Set();
|
|
3323
3357
|
const listeners = /* @__PURE__ */ new Set();
|
|
3324
3358
|
const notify = () => {
|
|
@@ -3329,155 +3363,162 @@ function createServiceBridge(options) {
|
|
|
3329
3363
|
}
|
|
3330
3364
|
}
|
|
3331
3365
|
};
|
|
3332
|
-
const revokeProxy = (
|
|
3333
|
-
if (
|
|
3334
|
-
|
|
3335
|
-
|
|
3366
|
+
const revokeProxy = (record, reason, code = "service_revoked") => {
|
|
3367
|
+
if (record.revoked) return;
|
|
3368
|
+
record.revoked = true;
|
|
3369
|
+
record.reason = reason;
|
|
3336
3370
|
try {
|
|
3337
|
-
|
|
3371
|
+
record.controller.abort(new RemoteServiceError(code, reason));
|
|
3338
3372
|
} catch {
|
|
3339
|
-
|
|
3373
|
+
record.controller.abort();
|
|
3340
3374
|
}
|
|
3341
3375
|
};
|
|
3342
|
-
const
|
|
3343
|
-
for (const
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3376
|
+
const revokeAll = (reason, code = "service_revoked") => {
|
|
3377
|
+
for (const record of proxies) revokeProxy(record, reason, code);
|
|
3378
|
+
};
|
|
3379
|
+
const currentMatches = (lookup) => currentServices.filter((reference) => reference.status === "ready" && lookupMatches(reference, lookup));
|
|
3380
|
+
const findBinding = (record, lookup) => {
|
|
3381
|
+
if (record.boundReference) return record.boundReference;
|
|
3382
|
+
if (terminalError) throw terminalError;
|
|
3383
|
+
const matches = currentMatches(lookup);
|
|
3384
|
+
if (matches.length > 1) {
|
|
3385
|
+
throw new RemoteServiceError(
|
|
3386
|
+
"capability_unavailable",
|
|
3387
|
+
`Service "${lookup.capabilityId}" version "${lookup.contractVersion}" has multiple ready instances`
|
|
3388
|
+
);
|
|
3389
|
+
}
|
|
3390
|
+
const reference = matches[0];
|
|
3391
|
+
if (reference) {
|
|
3392
|
+
record.boundReference = Object.freeze({
|
|
3393
|
+
...reference,
|
|
3394
|
+
attributes: Object.freeze({ ...reference.attributes })
|
|
3395
|
+
});
|
|
3396
|
+
return record.boundReference;
|
|
3348
3397
|
}
|
|
3398
|
+
return void 0;
|
|
3349
3399
|
};
|
|
3350
|
-
const
|
|
3351
|
-
|
|
3352
|
-
|
|
3400
|
+
const waitForBinding = (record, lookup, signal) => {
|
|
3401
|
+
const immediate = findBinding(record, lookup);
|
|
3402
|
+
if (immediate) return Promise.resolve(immediate);
|
|
3403
|
+
if (record.revoked) return Promise.reject(new RemoteServiceError("service_revoked", record.reason));
|
|
3404
|
+
if (signal.aborted) return Promise.reject(abortedError(signal));
|
|
3405
|
+
return new Promise((resolve, reject) => {
|
|
3406
|
+
let settled = false;
|
|
3407
|
+
const finish = (callback) => {
|
|
3408
|
+
if (settled) return;
|
|
3409
|
+
settled = true;
|
|
3410
|
+
removeListener();
|
|
3411
|
+
callback();
|
|
3412
|
+
};
|
|
3413
|
+
const check = () => {
|
|
3414
|
+
if (record.revoked) {
|
|
3415
|
+
finish(() => reject(new RemoteServiceError("service_revoked", record.reason)));
|
|
3416
|
+
return;
|
|
3417
|
+
}
|
|
3418
|
+
if (signal.aborted) {
|
|
3419
|
+
finish(() => reject(abortedError(signal)));
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
try {
|
|
3423
|
+
const reference = findBinding(record, lookup);
|
|
3424
|
+
if (reference) finish(() => resolve(reference));
|
|
3425
|
+
} catch (error) {
|
|
3426
|
+
finish(() => reject(error));
|
|
3427
|
+
}
|
|
3428
|
+
};
|
|
3429
|
+
const onAbort = () => check();
|
|
3430
|
+
const listener = () => check();
|
|
3431
|
+
const removeListener = () => {
|
|
3432
|
+
signal.removeEventListener("abort", onAbort);
|
|
3433
|
+
listeners.delete(listener);
|
|
3434
|
+
};
|
|
3435
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
3436
|
+
listeners.add(listener);
|
|
3437
|
+
check();
|
|
3438
|
+
});
|
|
3353
3439
|
};
|
|
3354
3440
|
const bridge = {
|
|
3355
3441
|
get state() {
|
|
3356
3442
|
return currentState;
|
|
3357
3443
|
},
|
|
3358
|
-
get
|
|
3359
|
-
return
|
|
3444
|
+
get runtimeInstanceId() {
|
|
3445
|
+
return currentRuntimeInstanceId;
|
|
3360
3446
|
},
|
|
3361
|
-
get
|
|
3362
|
-
return
|
|
3363
|
-
},
|
|
3364
|
-
handshake(input) {
|
|
3365
|
-
if (input.protocolVersion !== options.protocolVersion) {
|
|
3366
|
-
clearReferences("service bridge protocol mismatch");
|
|
3367
|
-
currentConnectionId = void 0;
|
|
3368
|
-
currentAuthorityInstanceId = void 0;
|
|
3369
|
-
lastRevision = void 0;
|
|
3370
|
-
hasBaseline = false;
|
|
3371
|
-
currentState = "disconnected";
|
|
3372
|
-
notify();
|
|
3373
|
-
return { accepted: false, reason: "protocol-mismatch" };
|
|
3374
|
-
}
|
|
3375
|
-
const isNewBinding = currentConnectionId !== input.connectionId || currentAuthorityInstanceId !== input.authorityInstanceId;
|
|
3376
|
-
if (isNewBinding) {
|
|
3377
|
-
clearReferences("remote service authority or connection changed");
|
|
3378
|
-
lastRevision = void 0;
|
|
3379
|
-
hasBaseline = false;
|
|
3380
|
-
}
|
|
3381
|
-
currentConnectionId = input.connectionId;
|
|
3382
|
-
currentAuthorityInstanceId = input.authorityInstanceId;
|
|
3383
|
-
currentState = "handshaking";
|
|
3384
|
-
notify();
|
|
3385
|
-
return { accepted: true };
|
|
3447
|
+
get defaultCallTimeoutMs() {
|
|
3448
|
+
return defaultCallTimeoutMs;
|
|
3386
3449
|
},
|
|
3387
3450
|
applySnapshot(snapshot) {
|
|
3388
|
-
if (
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
reason: "wrong-connection",
|
|
3392
|
-
receivedRevision: snapshot.snapshotRevision
|
|
3393
|
-
};
|
|
3394
|
-
}
|
|
3395
|
-
if (snapshot.authorityInstanceId !== currentAuthorityInstanceId) {
|
|
3396
|
-
return {
|
|
3397
|
-
accepted: false,
|
|
3398
|
-
reason: "wrong-authority",
|
|
3399
|
-
receivedRevision: snapshot.snapshotRevision
|
|
3400
|
-
};
|
|
3451
|
+
if (currentState === "disposed") return { accepted: false, reason: "disposed" };
|
|
3452
|
+
if (snapshot.protocolVersion !== options.protocolVersion) {
|
|
3453
|
+
return { accepted: false, reason: "protocol-mismatch", receivedRevision: snapshot.revision };
|
|
3401
3454
|
}
|
|
3402
|
-
if (!
|
|
3403
|
-
return {
|
|
3404
|
-
accepted: false,
|
|
3405
|
-
reason: "stale-revision",
|
|
3406
|
-
receivedRevision: snapshot.snapshotRevision
|
|
3407
|
-
};
|
|
3455
|
+
if (!isValidSnapshot(snapshot)) {
|
|
3456
|
+
return { accepted: false, reason: "invalid-snapshot", receivedRevision: snapshot.revision };
|
|
3408
3457
|
}
|
|
3409
|
-
if (
|
|
3410
|
-
|
|
3411
|
-
clearReferences("service bridge baseline required");
|
|
3412
|
-
notify();
|
|
3413
|
-
return {
|
|
3414
|
-
accepted: false,
|
|
3415
|
-
reason: "baseline-required",
|
|
3416
|
-
receivedRevision: snapshot.snapshotRevision
|
|
3417
|
-
};
|
|
3458
|
+
if (currentRuntimeInstanceId === snapshot.runtimeInstanceId && currentRevision !== void 0 && snapshot.revision <= currentRevision) {
|
|
3459
|
+
return { accepted: false, reason: "stale-revision", receivedRevision: snapshot.revision };
|
|
3418
3460
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3461
|
+
const runtimeChanged = currentRuntimeInstanceId !== void 0 && currentRuntimeInstanceId !== snapshot.runtimeInstanceId;
|
|
3462
|
+
if (runtimeChanged) revokeAll("Runtime instance changed; proxy cannot be rebound", "service_stale");
|
|
3463
|
+
const nextServices = snapshot.services.map((service) => Object.freeze({
|
|
3464
|
+
...service,
|
|
3465
|
+
attributes: Object.freeze({ ...service.attributes })
|
|
3466
|
+
}));
|
|
3467
|
+
const nextReady = new Map(nextServices.filter((service) => service.status === "ready").map((service) => [bindingKey(service), service]));
|
|
3468
|
+
if (!runtimeChanged) {
|
|
3469
|
+
for (const record of proxies) {
|
|
3470
|
+
const bound = record.boundReference;
|
|
3471
|
+
if (!bound) continue;
|
|
3472
|
+
const replacement = nextReady.get(bindingKey(bound));
|
|
3473
|
+
if (!replacement || referenceKey(replacement) !== referenceKey(bound)) {
|
|
3474
|
+
revokeProxy(record, "Bound service instance was replaced or revoked", "service_revoked");
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3425
3477
|
}
|
|
3426
|
-
|
|
3478
|
+
currentRuntimeInstanceId = snapshot.runtimeInstanceId;
|
|
3479
|
+
currentRevision = snapshot.revision;
|
|
3480
|
+
currentServices = Object.freeze(nextServices);
|
|
3481
|
+
terminalError = void 0;
|
|
3482
|
+
if (snapshot.state === "failed") {
|
|
3427
3483
|
currentState = "stale";
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
}
|
|
3438
|
-
const next = /* @__PURE__ */ new Map();
|
|
3439
|
-
for (const reference of snapshot.services) {
|
|
3440
|
-
if (reference.authorityInstanceId !== currentAuthorityInstanceId || reference.snapshotRevision !== snapshot.snapshotRevision) continue;
|
|
3441
|
-
next.set(referenceKey(reference), Object.freeze({ ...reference }));
|
|
3484
|
+
terminalError = new RemoteServiceError("runtime_initialization_failed", "Remote Runtime initialization failed");
|
|
3485
|
+
} else if (snapshot.state === "disposed" || snapshot.state === "stopping") {
|
|
3486
|
+
currentState = "stale";
|
|
3487
|
+
terminalError = new RemoteServiceError("transport_unavailable", "Remote Runtime is no longer accepting calls");
|
|
3488
|
+
revokeAll("Remote Runtime is no longer accepting calls", "transport_unavailable");
|
|
3489
|
+
} else if (snapshot.state === "ready") {
|
|
3490
|
+
currentState = "ready";
|
|
3491
|
+
} else {
|
|
3492
|
+
currentState = "empty";
|
|
3442
3493
|
}
|
|
3443
|
-
references.clear();
|
|
3444
|
-
for (const [key, reference] of next) references.set(key, reference);
|
|
3445
|
-
const validKeys = new Set(
|
|
3446
|
-
[...next.values()].filter((reference) => reference.status === "ready").map(referenceKey)
|
|
3447
|
-
);
|
|
3448
|
-
invalidateProxies("remote service reference is no longer ready", validKeys);
|
|
3449
|
-
lastRevision = snapshot.snapshotRevision;
|
|
3450
|
-
hasBaseline = true;
|
|
3451
|
-
currentState = "ready";
|
|
3452
3494
|
notify();
|
|
3453
|
-
return {
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3495
|
+
return { accepted: true, state: currentState, revision: snapshot.revision };
|
|
3496
|
+
},
|
|
3497
|
+
markProtocolMismatch(reason = "Remote Runtime protocol version mismatch") {
|
|
3498
|
+
if (currentState === "disposed") return;
|
|
3499
|
+
terminalError = new RemoteServiceError("protocol_mismatch", reason);
|
|
3500
|
+
revokeAll(reason, "protocol_mismatch");
|
|
3501
|
+
currentState = "stale";
|
|
3502
|
+
notify();
|
|
3503
|
+
},
|
|
3504
|
+
markInitializationFailed(reason = "Remote Runtime initialization failed") {
|
|
3505
|
+
if (currentState === "disposed") return;
|
|
3506
|
+
terminalError = new RemoteServiceError("runtime_initialization_failed", reason);
|
|
3507
|
+
revokeAll(reason, "runtime_initialization_failed");
|
|
3508
|
+
currentState = "stale";
|
|
3509
|
+
notify();
|
|
3458
3510
|
},
|
|
3459
3511
|
getProxy(lookup, scope) {
|
|
3460
|
-
if (currentState !== "ready") return void 0;
|
|
3461
|
-
const matches = [...references.values()].filter((candidate) => lookupMatches(candidate, lookup));
|
|
3462
|
-
if (matches.length > 1) {
|
|
3463
|
-
throw new RemoteServiceUnavailableError(
|
|
3464
|
-
`Service reference is ambiguous for "${lookup.capabilityId}" version "${lookup.contractVersion}"`
|
|
3465
|
-
);
|
|
3466
|
-
}
|
|
3467
|
-
const reference = matches[0];
|
|
3468
|
-
if (!reference) return void 0;
|
|
3469
3512
|
const record = {
|
|
3470
|
-
reference,
|
|
3471
3513
|
revoked: false,
|
|
3472
|
-
reason: "
|
|
3514
|
+
reason: "Remote service proxy revoked",
|
|
3473
3515
|
controller: new AbortController()
|
|
3474
3516
|
};
|
|
3475
3517
|
proxies.add(record);
|
|
3476
3518
|
let removeScopeRevoke;
|
|
3477
3519
|
let removeScopeDispose;
|
|
3478
|
-
let proxy;
|
|
3479
3520
|
const revokeFromScope = (reason) => {
|
|
3480
|
-
revokeProxy(record, reason);
|
|
3521
|
+
revokeProxy(record, reason, "service_revoked");
|
|
3481
3522
|
proxies.delete(record);
|
|
3482
3523
|
removeScopeRevoke?.();
|
|
3483
3524
|
removeScopeDispose?.();
|
|
@@ -3485,70 +3526,68 @@ function createServiceBridge(options) {
|
|
|
3485
3526
|
if (scope) {
|
|
3486
3527
|
try {
|
|
3487
3528
|
removeScopeRevoke = scope.onRevoke(revokeFromScope);
|
|
3488
|
-
removeScopeDispose = scope.onDispose(revokeFromScope, `service-proxy:${
|
|
3529
|
+
removeScopeDispose = scope.onDispose(revokeFromScope, `service-proxy:${lookup.capabilityId}`);
|
|
3489
3530
|
} catch {
|
|
3490
|
-
|
|
3531
|
+
revokeFromScope("Remote service proxy scope is already revoked");
|
|
3491
3532
|
}
|
|
3492
3533
|
}
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
return void 0;
|
|
3498
|
-
}
|
|
3499
|
-
proxy = {
|
|
3500
|
-
reference,
|
|
3534
|
+
const proxy = {
|
|
3535
|
+
get reference() {
|
|
3536
|
+
return record.boundReference;
|
|
3537
|
+
},
|
|
3501
3538
|
get revoked() {
|
|
3502
3539
|
return record.revoked;
|
|
3503
3540
|
},
|
|
3504
3541
|
async call(request, callOptions = {}) {
|
|
3505
|
-
if (record.revoked) throw new
|
|
3506
|
-
const
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
}
|
|
3518
|
-
const context = {
|
|
3519
|
-
// requestId 只作为旧调用方的业务 operationId 别名;真正的
|
|
3520
|
-
// MessagePort callId 由传输层每次调用单独生成。
|
|
3521
|
-
operationId: callOptions.operationId ?? callOptions.requestId ?? makeRequestId(),
|
|
3522
|
-
connectionId,
|
|
3523
|
-
reference,
|
|
3524
|
-
grantId: reference.grantId,
|
|
3525
|
-
signal: merged.signal
|
|
3526
|
-
};
|
|
3527
|
-
let result;
|
|
3528
|
-
try {
|
|
3529
|
-
result = Promise.resolve(options.transport.call(request, context));
|
|
3530
|
-
} catch (error) {
|
|
3531
|
-
merged.dispose();
|
|
3532
|
-
throw error;
|
|
3533
|
-
}
|
|
3542
|
+
if (record.revoked) throw new RemoteServiceError("service_revoked", record.reason);
|
|
3543
|
+
const timeoutMs = ensureTimeout(callOptions.timeoutMs ?? defaultCallTimeoutMs, "timeoutMs");
|
|
3544
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
3545
|
+
const timeoutController = new AbortController();
|
|
3546
|
+
const timeout = setTimeout(() => {
|
|
3547
|
+
try {
|
|
3548
|
+
timeoutController.abort(callTimeoutError(timeoutMs));
|
|
3549
|
+
} catch {
|
|
3550
|
+
timeoutController.abort();
|
|
3551
|
+
}
|
|
3552
|
+
}, timeoutMs);
|
|
3553
|
+
const merged = mergeSignals2(scope?.signal, record.controller.signal, callOptions.signal, timeoutController.signal);
|
|
3534
3554
|
try {
|
|
3555
|
+
const reference = await waitForBinding(record, lookup, merged.signal);
|
|
3556
|
+
if (merged.signal.aborted) throw abortedError(merged.signal);
|
|
3557
|
+
const context = {
|
|
3558
|
+
operationId: callOptions.operationId ?? callOptions.requestId,
|
|
3559
|
+
reference,
|
|
3560
|
+
grantId: reference.grantId,
|
|
3561
|
+
signal: merged.signal,
|
|
3562
|
+
deadlineAt,
|
|
3563
|
+
timeoutMs
|
|
3564
|
+
};
|
|
3565
|
+
let result;
|
|
3566
|
+
try {
|
|
3567
|
+
result = Promise.resolve(options.transport.call(request, context));
|
|
3568
|
+
} catch (error) {
|
|
3569
|
+
throw error;
|
|
3570
|
+
}
|
|
3535
3571
|
const value = await Promise.race([
|
|
3536
3572
|
result,
|
|
3537
3573
|
new Promise((_, reject) => {
|
|
3538
3574
|
if (merged.signal.aborted) {
|
|
3539
|
-
reject(merged.signal
|
|
3575
|
+
reject(abortedError(merged.signal));
|
|
3540
3576
|
return;
|
|
3541
3577
|
}
|
|
3542
|
-
const onAbort = () => reject(merged.signal
|
|
3578
|
+
const onAbort = () => reject(abortedError(merged.signal));
|
|
3543
3579
|
merged.signal.addEventListener("abort", onAbort, { once: true });
|
|
3544
3580
|
result.finally(() => merged.signal.removeEventListener("abort", onAbort)).catch(() => void 0);
|
|
3545
3581
|
})
|
|
3546
3582
|
]);
|
|
3547
|
-
if (record.revoked
|
|
3548
|
-
throw new RemoteServiceUnavailableError("Remote service request belongs to an invalidated proxy");
|
|
3549
|
-
}
|
|
3583
|
+
if (record.revoked) throw new RemoteServiceError("service_revoked", record.reason);
|
|
3550
3584
|
return value;
|
|
3585
|
+
} catch (error) {
|
|
3586
|
+
if (merged.signal.aborted) throw abortedError(merged.signal);
|
|
3587
|
+
if (error instanceof RemoteServiceError) throw error;
|
|
3588
|
+
throw error;
|
|
3551
3589
|
} finally {
|
|
3590
|
+
clearTimeout(timeout);
|
|
3552
3591
|
merged.dispose();
|
|
3553
3592
|
if (record.revoked) {
|
|
3554
3593
|
proxies.delete(record);
|
|
@@ -3557,124 +3596,203 @@ function createServiceBridge(options) {
|
|
|
3557
3596
|
}
|
|
3558
3597
|
}
|
|
3559
3598
|
},
|
|
3560
|
-
revoke(reason = "
|
|
3561
|
-
|
|
3562
|
-
proxies.delete(record);
|
|
3563
|
-
removeScopeRevoke?.();
|
|
3564
|
-
removeScopeDispose?.();
|
|
3599
|
+
revoke(reason = "Remote service proxy revoked") {
|
|
3600
|
+
revokeFromScope(reason);
|
|
3565
3601
|
}
|
|
3566
3602
|
};
|
|
3567
3603
|
return proxy;
|
|
3568
3604
|
},
|
|
3569
3605
|
requireProxy(lookup, scope) {
|
|
3570
|
-
|
|
3571
|
-
if (!proxy) {
|
|
3572
|
-
throw new RemoteServiceUnavailableError(
|
|
3573
|
-
`Service "${lookup.capabilityId}" version "${lookup.contractVersion}" is not ready`
|
|
3574
|
-
);
|
|
3575
|
-
}
|
|
3576
|
-
return proxy;
|
|
3606
|
+
return bridge.getProxy(lookup, scope);
|
|
3577
3607
|
},
|
|
3578
|
-
invalidate(reason = "
|
|
3579
|
-
|
|
3608
|
+
invalidate(reason = "Remote service directory invalidated") {
|
|
3609
|
+
if (currentState === "disposed") return;
|
|
3610
|
+
currentServices = [];
|
|
3611
|
+
currentRevision = void 0;
|
|
3612
|
+
terminalError = void 0;
|
|
3613
|
+
revokeAll(reason, "service_revoked");
|
|
3580
3614
|
currentState = "stale";
|
|
3581
|
-
lastRevision = void 0;
|
|
3582
|
-
hasBaseline = false;
|
|
3583
3615
|
notify();
|
|
3584
3616
|
},
|
|
3585
|
-
disconnect(reason = "
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3617
|
+
disconnect(reason = "Remote service transport disconnected") {
|
|
3618
|
+
if (currentState === "disposed") return;
|
|
3619
|
+
currentServices = [];
|
|
3620
|
+
currentRevision = void 0;
|
|
3621
|
+
currentRuntimeInstanceId = void 0;
|
|
3622
|
+
terminalError = new RemoteServiceError("transport_unavailable", reason);
|
|
3623
|
+
revokeAll(reason, "transport_unavailable");
|
|
3624
|
+
currentState = "stale";
|
|
3625
|
+
notify();
|
|
3626
|
+
},
|
|
3627
|
+
dispose(reason = "Remote service bridge disposed") {
|
|
3628
|
+
if (currentState === "disposed") return;
|
|
3629
|
+
currentServices = [];
|
|
3630
|
+
currentRevision = void 0;
|
|
3631
|
+
currentRuntimeInstanceId = void 0;
|
|
3632
|
+
terminalError = new RemoteServiceError("transport_unavailable", reason);
|
|
3633
|
+
revokeAll(reason, "transport_unavailable");
|
|
3634
|
+
currentState = "disposed";
|
|
3592
3635
|
notify();
|
|
3636
|
+
proxies.clear();
|
|
3593
3637
|
},
|
|
3594
3638
|
subscribe(listener) {
|
|
3595
3639
|
listeners.add(listener);
|
|
3596
3640
|
return () => listeners.delete(listener);
|
|
3597
3641
|
},
|
|
3598
3642
|
services() {
|
|
3599
|
-
return
|
|
3643
|
+
return currentServices;
|
|
3600
3644
|
}
|
|
3601
3645
|
};
|
|
3602
3646
|
return bridge;
|
|
3603
3647
|
}
|
|
3604
|
-
var createRemoteServiceBridge = createServiceBridge;
|
|
3605
3648
|
|
|
3606
3649
|
// src/transport/messagePortServiceTransport.ts
|
|
3607
3650
|
var nextTransportId = 0;
|
|
3608
3651
|
var nextCallSequence = 0;
|
|
3609
|
-
function makeTransportId() {
|
|
3610
|
-
nextTransportId += 1;
|
|
3611
|
-
return nextTransportId;
|
|
3612
|
-
}
|
|
3613
3652
|
function makeCallId(transportId) {
|
|
3614
3653
|
nextCallSequence += 1;
|
|
3615
3654
|
return `remote-call:${transportId}:${nextCallSequence}`;
|
|
3616
3655
|
}
|
|
3656
|
+
function finiteTimeout(value) {
|
|
3657
|
+
const timeout = value ?? 3e4;
|
|
3658
|
+
if (!Number.isFinite(timeout) || timeout <= 0) throw new TypeError("Remote service timeout must be finite and greater than zero");
|
|
3659
|
+
return timeout;
|
|
3660
|
+
}
|
|
3617
3661
|
function errorFromWire(input) {
|
|
3618
|
-
|
|
3619
|
-
typeof input?.
|
|
3662
|
+
return new RemoteServiceError(
|
|
3663
|
+
typeof input?.code === "string" && input.code.length > 0 ? input.code : "handler_failed",
|
|
3664
|
+
typeof input?.message === "string" ? input.message : "Remote service call failed",
|
|
3665
|
+
input?.details
|
|
3620
3666
|
);
|
|
3621
|
-
if (typeof input?.name === "string" && input.name.length > 0) error.name = input.name;
|
|
3622
|
-
if (typeof input?.code === "string" && input.code.length > 0) {
|
|
3623
|
-
Object.defineProperty(error, "code", {
|
|
3624
|
-
configurable: true,
|
|
3625
|
-
enumerable: true,
|
|
3626
|
-
value: input.code,
|
|
3627
|
-
writable: false
|
|
3628
|
-
});
|
|
3629
|
-
}
|
|
3630
|
-
return error;
|
|
3631
3667
|
}
|
|
3632
3668
|
function abortReason(signal) {
|
|
3633
|
-
|
|
3669
|
+
const reason = signal.reason;
|
|
3670
|
+
if (reason instanceof Error && typeof reason.code === "string") return reason;
|
|
3671
|
+
if (reason instanceof Error) return new RemoteServiceError("request_cancelled", reason.message);
|
|
3672
|
+
return new RemoteServiceError("request_cancelled", "Remote service request was cancelled");
|
|
3673
|
+
}
|
|
3674
|
+
function addMessageListener(port, listener) {
|
|
3675
|
+
port.addEventListener("message", listener);
|
|
3676
|
+
return () => port.removeEventListener("message", listener);
|
|
3677
|
+
}
|
|
3678
|
+
function postBestEffort(port, message, transfer = []) {
|
|
3679
|
+
try {
|
|
3680
|
+
port.postMessage(message, [...transfer]);
|
|
3681
|
+
} catch {
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
function postRequest(port, message, transfer = []) {
|
|
3685
|
+
try {
|
|
3686
|
+
port.postMessage(message, [...transfer]);
|
|
3687
|
+
} catch (error) {
|
|
3688
|
+
const name = error && typeof error === "object" && typeof error.name === "string" ? error.name : "DataCloneError";
|
|
3689
|
+
const messageText = error instanceof Error ? error.message : String(error);
|
|
3690
|
+
throw new RemoteServiceError(
|
|
3691
|
+
"request_clone_failed",
|
|
3692
|
+
`Remote service request could not be posted: ${messageText}`,
|
|
3693
|
+
{ name, message: messageText }
|
|
3694
|
+
);
|
|
3695
|
+
}
|
|
3634
3696
|
}
|
|
3635
3697
|
function isResponseMessage(input, codec) {
|
|
3636
3698
|
if (!input || typeof input !== "object") return false;
|
|
3637
3699
|
const message = input;
|
|
3638
|
-
return (message.type === codec.type("result") || message.type === codec.type("error")) && typeof message.
|
|
3700
|
+
return (message.type === codec.type("result") || message.type === codec.type("error")) && typeof message.protocolVersion === "string" && typeof message.callId === "string" && typeof message.serviceInstanceId === "string";
|
|
3639
3701
|
}
|
|
3640
3702
|
function createMessagePortServiceTransport(options) {
|
|
3641
3703
|
const pending = /* @__PURE__ */ new Map();
|
|
3642
3704
|
let disposed = false;
|
|
3643
|
-
const transportId =
|
|
3705
|
+
const transportId = ++nextTransportId;
|
|
3644
3706
|
const codec = options.codec ?? createRemoteServiceMessageCodec();
|
|
3707
|
+
const defaultCallTimeoutMs = finiteTimeout(options.defaultCallTimeoutMs);
|
|
3708
|
+
const rejectPending = (error, sendCancel) => {
|
|
3709
|
+
for (const [callId, call] of pending) {
|
|
3710
|
+
pending.delete(callId);
|
|
3711
|
+
call.removeAbort();
|
|
3712
|
+
call.disposeDeadline();
|
|
3713
|
+
if (sendCancel) {
|
|
3714
|
+
postBestEffort(options.port, codec.encode({
|
|
3715
|
+
type: codec.type("cancel"),
|
|
3716
|
+
protocolVersion: codec.protocolVersion,
|
|
3717
|
+
callId,
|
|
3718
|
+
serviceInstanceId: call.serviceInstanceId
|
|
3719
|
+
}));
|
|
3720
|
+
}
|
|
3721
|
+
call.reject(error);
|
|
3722
|
+
}
|
|
3723
|
+
};
|
|
3645
3724
|
const onMessage = (event) => {
|
|
3646
3725
|
const decoded = codec.decode(event.data);
|
|
3647
3726
|
if (!isResponseMessage(decoded, codec)) return;
|
|
3648
3727
|
const call = pending.get(decoded.callId);
|
|
3649
3728
|
if (!call) return;
|
|
3650
|
-
if (decoded.
|
|
3729
|
+
if (decoded.serviceInstanceId !== call.serviceInstanceId) return;
|
|
3651
3730
|
pending.delete(decoded.callId);
|
|
3652
3731
|
call.removeAbort();
|
|
3732
|
+
call.disposeDeadline();
|
|
3733
|
+
if (decoded.protocolVersion !== codec.protocolVersion) {
|
|
3734
|
+
call.reject(new RemoteServiceError("protocol_mismatch", "Remote service protocol version mismatch"));
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3653
3737
|
if (decoded.type === codec.type("error")) {
|
|
3654
3738
|
call.reject(errorFromWire(decoded.error));
|
|
3655
3739
|
} else {
|
|
3656
3740
|
call.resolve(decoded.result);
|
|
3657
3741
|
}
|
|
3658
3742
|
};
|
|
3659
|
-
|
|
3743
|
+
const onMessageError = () => rejectPending(new RemoteServiceError("transport_unavailable", "Remote service message could not be decoded"), false);
|
|
3744
|
+
const removeMessage = addMessageListener(options.port, onMessage);
|
|
3745
|
+
options.port.addEventListener("messageerror", onMessageError);
|
|
3660
3746
|
options.port.start();
|
|
3661
3747
|
const dispose = () => {
|
|
3662
3748
|
if (disposed) return;
|
|
3663
3749
|
disposed = true;
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3750
|
+
removeMessage();
|
|
3751
|
+
options.port.removeEventListener("messageerror", onMessageError);
|
|
3752
|
+
rejectPending(new RemoteServiceError("transport_unavailable", "Remote service transport disposed"), true);
|
|
3753
|
+
if (options.closeOnDispose) {
|
|
3754
|
+
try {
|
|
3755
|
+
options.port.close();
|
|
3756
|
+
} catch {
|
|
3757
|
+
}
|
|
3669
3758
|
}
|
|
3670
|
-
if (options.closeOnDispose) options.port.close();
|
|
3671
3759
|
};
|
|
3672
3760
|
const transport = {
|
|
3673
3761
|
call(request, context) {
|
|
3674
|
-
if (disposed) return Promise.reject(new
|
|
3762
|
+
if (disposed) return Promise.reject(new RemoteServiceError("transport_unavailable", "Remote service transport disposed"));
|
|
3675
3763
|
if (context.signal.aborted) return Promise.reject(abortReason(context.signal));
|
|
3676
3764
|
const callId = makeCallId(transportId);
|
|
3677
|
-
const
|
|
3765
|
+
const serviceInstanceId = context.reference.serviceInstanceId;
|
|
3766
|
+
const timeoutMs = finiteTimeout(context.timeoutMs ?? defaultCallTimeoutMs);
|
|
3767
|
+
const deadlineAt = context.deadlineAt ?? Date.now() + timeoutMs;
|
|
3768
|
+
const timeoutController = new AbortController();
|
|
3769
|
+
const remaining = Math.max(0, deadlineAt - Date.now());
|
|
3770
|
+
const deadlineTimer = setTimeout(() => {
|
|
3771
|
+
try {
|
|
3772
|
+
timeoutController.abort(new RemoteServiceError("call_timeout", "Remote service call timed out"));
|
|
3773
|
+
} catch {
|
|
3774
|
+
timeoutController.abort();
|
|
3775
|
+
}
|
|
3776
|
+
}, remaining);
|
|
3777
|
+
const merged = (() => {
|
|
3778
|
+
const controller = new AbortController();
|
|
3779
|
+
const signals = [context.signal, timeoutController.signal];
|
|
3780
|
+
const listeners = signals.map((signal) => {
|
|
3781
|
+
const listener = () => {
|
|
3782
|
+
try {
|
|
3783
|
+
controller.abort(signal.reason);
|
|
3784
|
+
} catch {
|
|
3785
|
+
controller.abort();
|
|
3786
|
+
}
|
|
3787
|
+
};
|
|
3788
|
+
signal.addEventListener("abort", listener, { once: true });
|
|
3789
|
+
return { signal, listener };
|
|
3790
|
+
});
|
|
3791
|
+
return {
|
|
3792
|
+
signal: controller.signal,
|
|
3793
|
+
dispose: () => listeners.forEach(({ signal, listener }) => signal.removeEventListener("abort", listener))
|
|
3794
|
+
};
|
|
3795
|
+
})();
|
|
3678
3796
|
return new Promise((resolve, reject) => {
|
|
3679
3797
|
let settled = false;
|
|
3680
3798
|
const finish = (callback) => {
|
|
@@ -3682,46 +3800,52 @@ function createMessagePortServiceTransport(options) {
|
|
|
3682
3800
|
settled = true;
|
|
3683
3801
|
callback();
|
|
3684
3802
|
};
|
|
3803
|
+
const sendCancel = () => postBestEffort(options.port, codec.encode({
|
|
3804
|
+
type: codec.type("cancel"),
|
|
3805
|
+
protocolVersion: codec.protocolVersion,
|
|
3806
|
+
callId,
|
|
3807
|
+
serviceInstanceId
|
|
3808
|
+
}));
|
|
3685
3809
|
const onAbort = () => {
|
|
3686
3810
|
if (!pending.delete(callId)) return;
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
connectionId: context.connectionId,
|
|
3693
|
-
providerInstanceId
|
|
3694
|
-
};
|
|
3695
|
-
options.port.postMessage(codec.encode(cancel));
|
|
3696
|
-
} catch {
|
|
3697
|
-
}
|
|
3811
|
+
sendCancel();
|
|
3812
|
+
const reason = merged.signal.reason instanceof RemoteServiceError ? merged.signal.reason : merged.signal.reason?.code === "call_timeout" ? merged.signal.reason : abortReason(merged.signal);
|
|
3813
|
+
finish(() => reject(reason));
|
|
3814
|
+
merged.dispose();
|
|
3815
|
+
clearTimeout(deadlineTimer);
|
|
3698
3816
|
};
|
|
3699
3817
|
const pendingCall = {
|
|
3700
|
-
|
|
3701
|
-
|
|
3818
|
+
protocolVersion: codec.protocolVersion,
|
|
3819
|
+
serviceInstanceId,
|
|
3702
3820
|
resolve: (value) => finish(() => resolve(value)),
|
|
3703
3821
|
reject: (error) => finish(() => reject(error)),
|
|
3704
|
-
removeAbort: () =>
|
|
3822
|
+
removeAbort: () => merged.signal.removeEventListener("abort", onAbort),
|
|
3823
|
+
disposeDeadline: () => {
|
|
3824
|
+
merged.dispose();
|
|
3825
|
+
clearTimeout(deadlineTimer);
|
|
3826
|
+
}
|
|
3705
3827
|
};
|
|
3706
3828
|
pending.set(callId, pendingCall);
|
|
3707
|
-
|
|
3829
|
+
merged.signal.addEventListener("abort", onAbort, { once: true });
|
|
3830
|
+
const message = {
|
|
3831
|
+
type: codec.type("call"),
|
|
3832
|
+
protocolVersion: codec.protocolVersion,
|
|
3833
|
+
callId,
|
|
3834
|
+
capabilityId: context.reference.capabilityId,
|
|
3835
|
+
contractVersion: context.reference.contractVersion,
|
|
3836
|
+
serviceInstanceId,
|
|
3837
|
+
...context.operationId ? { operationId: context.operationId } : {},
|
|
3838
|
+
...context.grantId ?? context.reference.grantId ? { grantId: context.grantId ?? context.reference.grantId } : {},
|
|
3839
|
+
request
|
|
3840
|
+
};
|
|
3708
3841
|
try {
|
|
3709
|
-
const message = {
|
|
3710
|
-
type: codec.type("call"),
|
|
3711
|
-
callId,
|
|
3712
|
-
connectionId: context.connectionId,
|
|
3713
|
-
providerInstanceId,
|
|
3714
|
-
...context.operationId ? { operationId: context.operationId } : {},
|
|
3715
|
-
...context.grantId ?? context.reference.grantId ? { grantId: context.grantId ?? context.reference.grantId } : {},
|
|
3716
|
-
reference: context.reference,
|
|
3717
|
-
request
|
|
3718
|
-
};
|
|
3719
3842
|
const transfer = options.transferForRequest?.(request, context) ?? [];
|
|
3720
|
-
options.port
|
|
3721
|
-
if (
|
|
3843
|
+
postRequest(options.port, codec.encode(message), transfer);
|
|
3844
|
+
if (merged.signal.aborted) onAbort();
|
|
3722
3845
|
} catch (error) {
|
|
3723
3846
|
pending.delete(callId);
|
|
3724
3847
|
pendingCall.removeAbort();
|
|
3848
|
+
pendingCall.disposeDeadline();
|
|
3725
3849
|
pendingCall.reject(error);
|
|
3726
3850
|
}
|
|
3727
3851
|
});
|
|
@@ -3730,7 +3854,408 @@ function createMessagePortServiceTransport(options) {
|
|
|
3730
3854
|
};
|
|
3731
3855
|
return transport;
|
|
3732
3856
|
}
|
|
3857
|
+
function ensureTimeout2(value) {
|
|
3858
|
+
const timeout = value ?? 3e4;
|
|
3859
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
3860
|
+
throw new TypeError("defaultCallTimeoutMs must be a finite number greater than zero");
|
|
3861
|
+
}
|
|
3862
|
+
return timeout;
|
|
3863
|
+
}
|
|
3864
|
+
function addMessageListener2(port, listener) {
|
|
3865
|
+
port.addEventListener("message", listener);
|
|
3866
|
+
return () => port.removeEventListener("message", listener);
|
|
3867
|
+
}
|
|
3868
|
+
function ensureMessageEventTarget(port) {
|
|
3869
|
+
const target = port;
|
|
3870
|
+
if (target.addEventListener && target.removeEventListener) return;
|
|
3871
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3872
|
+
const original = target.onmessage;
|
|
3873
|
+
target.addEventListener = function add(type, listener) {
|
|
3874
|
+
if (type === "message") listeners.add(listener);
|
|
3875
|
+
};
|
|
3876
|
+
target.removeEventListener = function remove(type, listener) {
|
|
3877
|
+
if (type === "message") listeners.delete(listener);
|
|
3878
|
+
};
|
|
3879
|
+
target.onmessage = (event) => {
|
|
3880
|
+
original?.call(port, event);
|
|
3881
|
+
for (const listener of [...listeners]) listener(event);
|
|
3882
|
+
};
|
|
3883
|
+
}
|
|
3884
|
+
function makeWorker(options) {
|
|
3885
|
+
const workerOptions = {
|
|
3886
|
+
type: "module",
|
|
3887
|
+
...options.name ? { name: options.name } : {},
|
|
3888
|
+
...options.credentials ? { credentials: options.credentials } : {}
|
|
3889
|
+
};
|
|
3890
|
+
if (options.workerFactory) return options.workerFactory(options.url, workerOptions);
|
|
3891
|
+
const WorkerConstructor = globalThis.SharedWorker;
|
|
3892
|
+
if (!WorkerConstructor) throw new RuntimeUnavailableError("SharedWorker is not supported by this browser");
|
|
3893
|
+
return new WorkerConstructor(options.url, workerOptions);
|
|
3894
|
+
}
|
|
3895
|
+
function connectSharedWorkerInternal(options) {
|
|
3896
|
+
if (!options || typeof options.id !== "string" || options.id.trim() === "") {
|
|
3897
|
+
throw new Error("SharedWorker runtime id must be a non-empty string");
|
|
3898
|
+
}
|
|
3899
|
+
const defaultCallTimeoutMs = ensureTimeout2(options.defaultCallTimeoutMs);
|
|
3900
|
+
const worker = makeWorker(options);
|
|
3901
|
+
const port = worker.port;
|
|
3902
|
+
if (!port) throw new RuntimeUnavailableError("SharedWorker did not expose a MessagePort");
|
|
3903
|
+
ensureMessageEventTarget(port);
|
|
3904
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3905
|
+
const codec = createRuntimeMessageCodec();
|
|
3906
|
+
let disposed = false;
|
|
3907
|
+
let runtimeInstanceId;
|
|
3908
|
+
let removeRuntimeMessage;
|
|
3909
|
+
let transport;
|
|
3910
|
+
let restoreWorkerError = () => void 0;
|
|
3911
|
+
let currentSnapshot = {
|
|
3912
|
+
runtimeId: options.id,
|
|
3913
|
+
runtimeKind: "shared-worker",
|
|
3914
|
+
runtimeInstanceId: "",
|
|
3915
|
+
state: "starting",
|
|
3916
|
+
revision: 0,
|
|
3917
|
+
units: [],
|
|
3918
|
+
services: []
|
|
3919
|
+
};
|
|
3920
|
+
const emit = (next) => {
|
|
3921
|
+
currentSnapshot = Object.freeze({
|
|
3922
|
+
...next,
|
|
3923
|
+
units: Object.freeze([...next.units]),
|
|
3924
|
+
services: Object.freeze([...next.services])
|
|
3925
|
+
});
|
|
3926
|
+
for (const listener of [...listeners]) {
|
|
3927
|
+
try {
|
|
3928
|
+
listener(currentSnapshot);
|
|
3929
|
+
} catch {
|
|
3930
|
+
}
|
|
3931
|
+
}
|
|
3932
|
+
};
|
|
3933
|
+
const bridgeTransport = {
|
|
3934
|
+
call(request, context) {
|
|
3935
|
+
if (!transport) {
|
|
3936
|
+
return Promise.reject(new RuntimeUnavailableError("SharedWorker connection is unavailable"));
|
|
3937
|
+
}
|
|
3938
|
+
return transport.call(request, context);
|
|
3939
|
+
}
|
|
3940
|
+
};
|
|
3941
|
+
const bridge = createServiceBridge({
|
|
3942
|
+
protocolVersion: RUNTIME_PROTOCOL_VERSION,
|
|
3943
|
+
transport: bridgeTransport,
|
|
3944
|
+
defaultCallTimeoutMs
|
|
3945
|
+
});
|
|
3946
|
+
const cleanup = () => {
|
|
3947
|
+
removeRuntimeMessage?.();
|
|
3948
|
+
removeRuntimeMessage = void 0;
|
|
3949
|
+
port.removeEventListener("messageerror", onWorkerError);
|
|
3950
|
+
transport?.dispose();
|
|
3951
|
+
transport = void 0;
|
|
3952
|
+
try {
|
|
3953
|
+
port.close();
|
|
3954
|
+
} catch {
|
|
3955
|
+
}
|
|
3956
|
+
if (worker.removeEventListener) worker.removeEventListener("error", onWorkerError);
|
|
3957
|
+
restoreWorkerError();
|
|
3958
|
+
restoreWorkerError = () => void 0;
|
|
3959
|
+
};
|
|
3960
|
+
const disconnect = (reason, failure) => {
|
|
3961
|
+
if (disposed) return;
|
|
3962
|
+
cleanup();
|
|
3963
|
+
bridge.disconnect(reason);
|
|
3964
|
+
emit({
|
|
3965
|
+
...currentSnapshot,
|
|
3966
|
+
state: "disconnected",
|
|
3967
|
+
runtimeInstanceId: "",
|
|
3968
|
+
revision: 0,
|
|
3969
|
+
units: [],
|
|
3970
|
+
services: [],
|
|
3971
|
+
error: reason
|
|
3972
|
+
});
|
|
3973
|
+
runtimeInstanceId = void 0;
|
|
3974
|
+
};
|
|
3975
|
+
const onRuntimeError = (message) => {
|
|
3976
|
+
const error = new RuntimeInitializationError({
|
|
3977
|
+
pluginId: message.pluginId,
|
|
3978
|
+
unitId: message.unitId,
|
|
3979
|
+
phase: message.phase === "snapshot" ? "snapshot" : "startup",
|
|
3980
|
+
error: message.message
|
|
3981
|
+
});
|
|
3982
|
+
if (message.code === "protocol_mismatch") bridge.markProtocolMismatch(message.message);
|
|
3983
|
+
else if (message.code === "runtime_initialization_failed") bridge.markInitializationFailed(message.message);
|
|
3984
|
+
else bridge.disconnect(message.message);
|
|
3985
|
+
emit({ ...currentSnapshot, state: "failed", error: error.message, units: [], services: [] });
|
|
3986
|
+
};
|
|
3987
|
+
const onSnapshot = (snapshot) => {
|
|
3988
|
+
if (snapshot.runtimeId !== options.id || snapshot.runtimeKind !== "shared-worker") return;
|
|
3989
|
+
if (!isRuntimeSnapshotProtocol(snapshot)) {
|
|
3990
|
+
bridge.markProtocolMismatch(`Runtime protocol ${snapshot.protocolVersion} is not supported`);
|
|
3991
|
+
emit({
|
|
3992
|
+
...currentSnapshot,
|
|
3993
|
+
state: "failed",
|
|
3994
|
+
error: `Runtime protocol ${snapshot.protocolVersion} is not supported`,
|
|
3995
|
+
units: [],
|
|
3996
|
+
services: []
|
|
3997
|
+
});
|
|
3998
|
+
return;
|
|
3999
|
+
}
|
|
4000
|
+
const applied = bridge.applySnapshot(snapshot);
|
|
4001
|
+
if (!applied.accepted) return;
|
|
4002
|
+
runtimeInstanceId = snapshot.runtimeInstanceId;
|
|
4003
|
+
emit({
|
|
4004
|
+
runtimeId: snapshot.runtimeId,
|
|
4005
|
+
runtimeKind: snapshot.runtimeKind,
|
|
4006
|
+
runtimeInstanceId: snapshot.runtimeInstanceId,
|
|
4007
|
+
state: snapshot.state === "ready" ? "ready" : snapshot.state,
|
|
4008
|
+
revision: snapshot.revision,
|
|
4009
|
+
units: snapshot.units,
|
|
4010
|
+
services: snapshot.services
|
|
4011
|
+
});
|
|
4012
|
+
};
|
|
4013
|
+
const onWorkerError = (event) => {
|
|
4014
|
+
const detail = event.type ? `SharedWorker error: ${event.type}` : "SharedWorker error";
|
|
4015
|
+
disconnect(detail);
|
|
4016
|
+
};
|
|
4017
|
+
const onMessage = (event) => {
|
|
4018
|
+
if (disposed) return;
|
|
4019
|
+
if (isRuntimeError(event.data)) {
|
|
4020
|
+
onRuntimeError(event.data);
|
|
4021
|
+
return;
|
|
4022
|
+
}
|
|
4023
|
+
if (isRuntimeSnapshot(event.data)) {
|
|
4024
|
+
onSnapshot(event.data);
|
|
4025
|
+
return;
|
|
4026
|
+
}
|
|
4027
|
+
const decoded = codec.decode(event.data);
|
|
4028
|
+
if (decoded?.type === codec.type("error")) ;
|
|
4029
|
+
};
|
|
4030
|
+
try {
|
|
4031
|
+
options.onConnection?.({ worker, port });
|
|
4032
|
+
transport = createMessagePortServiceTransport({
|
|
4033
|
+
port,
|
|
4034
|
+
codec,
|
|
4035
|
+
defaultCallTimeoutMs,
|
|
4036
|
+
closeOnDispose: false
|
|
4037
|
+
});
|
|
4038
|
+
removeRuntimeMessage = addMessageListener2(port, onMessage);
|
|
4039
|
+
port.addEventListener("messageerror", onWorkerError);
|
|
4040
|
+
if (worker.addEventListener) worker.addEventListener("error", onWorkerError);
|
|
4041
|
+
else {
|
|
4042
|
+
const previous = worker.onerror;
|
|
4043
|
+
const fallbackHandler = (event) => {
|
|
4044
|
+
previous?.(event);
|
|
4045
|
+
onWorkerError(event);
|
|
4046
|
+
};
|
|
4047
|
+
worker.onerror = fallbackHandler;
|
|
4048
|
+
restoreWorkerError = () => {
|
|
4049
|
+
if (worker.onerror === fallbackHandler) worker.onerror = previous;
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
4052
|
+
port.start();
|
|
4053
|
+
} catch (error) {
|
|
4054
|
+
cleanup();
|
|
4055
|
+
throw error;
|
|
4056
|
+
}
|
|
4057
|
+
const handle = {
|
|
4058
|
+
runtimeKind: "shared-worker",
|
|
4059
|
+
runtimeId: options.id,
|
|
4060
|
+
get runtimeInstanceId() {
|
|
4061
|
+
return runtimeInstanceId;
|
|
4062
|
+
},
|
|
4063
|
+
serviceBridge: bridge,
|
|
4064
|
+
state: () => currentSnapshot,
|
|
4065
|
+
capability(capabilityId, capabilityOptions = {}) {
|
|
4066
|
+
const proxy = bridge.requireProxy({
|
|
4067
|
+
capabilityId,
|
|
4068
|
+
contractVersion: capabilityOptions.contractVersion ?? `${capabilityId}.v1`
|
|
4069
|
+
});
|
|
4070
|
+
return proxy;
|
|
4071
|
+
},
|
|
4072
|
+
subscribe(listener) {
|
|
4073
|
+
listeners.add(listener);
|
|
4074
|
+
listener(currentSnapshot);
|
|
4075
|
+
return () => listeners.delete(listener);
|
|
4076
|
+
},
|
|
4077
|
+
dispose(reason = "SharedWorker connection disposed") {
|
|
4078
|
+
if (disposed) return Promise.resolve();
|
|
4079
|
+
disposed = true;
|
|
4080
|
+
emit({ ...currentSnapshot, state: "stopping" });
|
|
4081
|
+
bridge.dispose(reason);
|
|
4082
|
+
cleanup();
|
|
4083
|
+
runtimeInstanceId = void 0;
|
|
4084
|
+
emit({
|
|
4085
|
+
...currentSnapshot,
|
|
4086
|
+
state: "disposed",
|
|
4087
|
+
runtimeInstanceId: "",
|
|
4088
|
+
revision: 0,
|
|
4089
|
+
units: [],
|
|
4090
|
+
services: []
|
|
4091
|
+
});
|
|
4092
|
+
return Promise.resolve();
|
|
4093
|
+
}
|
|
4094
|
+
};
|
|
4095
|
+
return handle;
|
|
4096
|
+
}
|
|
4097
|
+
function connectSharedWorker(options) {
|
|
4098
|
+
return connectSharedWorkerInternal(options);
|
|
4099
|
+
}
|
|
4100
|
+
function connectSharedWorkerForTesting(options, workerFactory) {
|
|
4101
|
+
return connectSharedWorkerInternal({ ...options, workerFactory });
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
// src/lifecycle/pluginIntentController.ts
|
|
4105
|
+
function makeAuthorityInstanceId() {
|
|
4106
|
+
try {
|
|
4107
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
|
4108
|
+
return `authority:${crypto.randomUUID()}`;
|
|
4109
|
+
}
|
|
4110
|
+
} catch {
|
|
4111
|
+
}
|
|
4112
|
+
return `authority:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
4113
|
+
}
|
|
4114
|
+
function cloneSnapshot(snapshot) {
|
|
4115
|
+
return {
|
|
4116
|
+
revision: snapshot.revision,
|
|
4117
|
+
desiredEnabled: { ...snapshot.desiredEnabled },
|
|
4118
|
+
desiredRevision: { ...snapshot.desiredRevision }
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
function normalizeSnapshot(initial) {
|
|
4122
|
+
const revision = Number.isSafeInteger(initial?.revision) && (initial?.revision ?? 0) >= 0 ? initial.revision : 0;
|
|
4123
|
+
const desiredEnabled = {};
|
|
4124
|
+
for (const [pluginId, value] of Object.entries(initial?.desiredEnabled ?? {})) {
|
|
4125
|
+
if (typeof value === "boolean") desiredEnabled[pluginId] = value;
|
|
4126
|
+
}
|
|
4127
|
+
const desiredRevision = {};
|
|
4128
|
+
for (const [pluginId, value] of Object.entries(initial?.desiredRevision ?? {})) {
|
|
4129
|
+
if (Number.isSafeInteger(value) && value >= 0) desiredRevision[pluginId] = value;
|
|
4130
|
+
}
|
|
4131
|
+
return { revision, desiredEnabled, desiredRevision };
|
|
4132
|
+
}
|
|
4133
|
+
function commandFingerprint(command) {
|
|
4134
|
+
return [
|
|
4135
|
+
command.authorityInstanceId,
|
|
4136
|
+
command.expectedRevision,
|
|
4137
|
+
command.pluginId,
|
|
4138
|
+
command.desiredEnabled ? "true" : "false"
|
|
4139
|
+
].join("\0");
|
|
4140
|
+
}
|
|
4141
|
+
function commandError(message) {
|
|
4142
|
+
return { status: "command-conflict", commandId: "", message };
|
|
4143
|
+
}
|
|
4144
|
+
function createPluginIntentController(options = {}) {
|
|
4145
|
+
const authorityInstanceId = options.authorityInstanceId ?? makeAuthorityInstanceId();
|
|
4146
|
+
const maxCommandRecords = Math.max(1, Math.floor(options.maxCommandRecords ?? 256));
|
|
4147
|
+
let current = normalizeSnapshot(options.initial);
|
|
4148
|
+
const commandRecords = /* @__PURE__ */ new Map();
|
|
4149
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4150
|
+
let queue = Promise.resolve();
|
|
4151
|
+
const notify = () => {
|
|
4152
|
+
const snapshot = cloneSnapshot(current);
|
|
4153
|
+
for (const listener of [...listeners]) {
|
|
4154
|
+
try {
|
|
4155
|
+
listener(snapshot);
|
|
4156
|
+
} catch {
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
};
|
|
4160
|
+
const process = async (command) => {
|
|
4161
|
+
const candidate = command;
|
|
4162
|
+
const commandId = typeof candidate?.commandId === "string" ? candidate.commandId : "";
|
|
4163
|
+
if (!candidate || typeof candidate.authorityInstanceId !== "string" || candidate.authorityInstanceId.length === 0 || typeof candidate.pluginId !== "string" || candidate.pluginId.length === 0 || typeof candidate.desiredEnabled !== "boolean" || !Number.isSafeInteger(candidate.expectedRevision) || (candidate.expectedRevision ?? -1) < 0 || commandId.length === 0) {
|
|
4164
|
+
const result = commandError("commandId\u3001pluginId \u548C expectedRevision \u5FC5\u987B\u662F\u6709\u6548\u503C");
|
|
4165
|
+
return { ...result, commandId };
|
|
4166
|
+
}
|
|
4167
|
+
if (command.authorityInstanceId !== authorityInstanceId) {
|
|
4168
|
+
return {
|
|
4169
|
+
status: "stale-authority",
|
|
4170
|
+
commandId: command.commandId,
|
|
4171
|
+
expectedAuthorityInstanceId: authorityInstanceId
|
|
4172
|
+
};
|
|
4173
|
+
}
|
|
4174
|
+
const fingerprint = commandFingerprint(command);
|
|
4175
|
+
const existing = commandRecords.get(command.commandId);
|
|
4176
|
+
if (existing) {
|
|
4177
|
+
if (existing.fingerprint !== fingerprint) {
|
|
4178
|
+
return {
|
|
4179
|
+
status: "command-conflict",
|
|
4180
|
+
commandId: command.commandId,
|
|
4181
|
+
message: "\u76F8\u540C commandId \u7684\u547D\u4EE4\u5185\u5BB9\u4E0D\u540C\uFF0C\u62D2\u7EDD\u8986\u76D6\u539F\u547D\u4EE4"
|
|
4182
|
+
};
|
|
4183
|
+
}
|
|
4184
|
+
return {
|
|
4185
|
+
status: "duplicate",
|
|
4186
|
+
commandId: command.commandId,
|
|
4187
|
+
snapshot: cloneSnapshot(existing.result.snapshot),
|
|
4188
|
+
persisted: true
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
if (command.expectedRevision !== current.revision) {
|
|
4192
|
+
return {
|
|
4193
|
+
status: "revision-conflict",
|
|
4194
|
+
commandId: command.commandId,
|
|
4195
|
+
snapshot: cloneSnapshot(current)
|
|
4196
|
+
};
|
|
4197
|
+
}
|
|
4198
|
+
const next = {
|
|
4199
|
+
revision: current.revision + 1,
|
|
4200
|
+
desiredEnabled: {
|
|
4201
|
+
...current.desiredEnabled,
|
|
4202
|
+
[command.pluginId]: command.desiredEnabled
|
|
4203
|
+
},
|
|
4204
|
+
desiredRevision: {
|
|
4205
|
+
...current.desiredRevision,
|
|
4206
|
+
[command.pluginId]: (current.desiredRevision[command.pluginId] ?? 0) + 1
|
|
4207
|
+
}
|
|
4208
|
+
};
|
|
4209
|
+
try {
|
|
4210
|
+
await options.persist?.(cloneSnapshot(next));
|
|
4211
|
+
} catch (error) {
|
|
4212
|
+
return {
|
|
4213
|
+
status: "persistence-failed",
|
|
4214
|
+
commandId: command.commandId,
|
|
4215
|
+
message: error instanceof Error ? error.message : String(error),
|
|
4216
|
+
snapshot: cloneSnapshot(current)
|
|
4217
|
+
};
|
|
4218
|
+
}
|
|
4219
|
+
current = next;
|
|
4220
|
+
const accepted = {
|
|
4221
|
+
status: "accepted",
|
|
4222
|
+
commandId: command.commandId,
|
|
4223
|
+
snapshot: cloneSnapshot(current),
|
|
4224
|
+
persisted: true
|
|
4225
|
+
};
|
|
4226
|
+
commandRecords.set(command.commandId, {
|
|
4227
|
+
fingerprint,
|
|
4228
|
+
result: accepted
|
|
4229
|
+
});
|
|
4230
|
+
while (commandRecords.size > maxCommandRecords) {
|
|
4231
|
+
const oldest = commandRecords.keys().next().value;
|
|
4232
|
+
if (oldest === void 0) break;
|
|
4233
|
+
commandRecords.delete(oldest);
|
|
4234
|
+
}
|
|
4235
|
+
notify();
|
|
4236
|
+
return {
|
|
4237
|
+
status: "accepted",
|
|
4238
|
+
commandId: accepted.commandId,
|
|
4239
|
+
snapshot: cloneSnapshot(accepted.snapshot),
|
|
4240
|
+
persisted: true
|
|
4241
|
+
};
|
|
4242
|
+
};
|
|
4243
|
+
const controller = {
|
|
4244
|
+
authorityInstanceId,
|
|
4245
|
+
snapshot: () => cloneSnapshot(current),
|
|
4246
|
+
submit(command) {
|
|
4247
|
+
const result = queue.then(() => process(command));
|
|
4248
|
+
queue = result.then(() => void 0, () => void 0);
|
|
4249
|
+
return result;
|
|
4250
|
+
},
|
|
4251
|
+
subscribe(listener) {
|
|
4252
|
+
listeners.add(listener);
|
|
4253
|
+
return () => listeners.delete(listener);
|
|
4254
|
+
}
|
|
4255
|
+
};
|
|
4256
|
+
return controller;
|
|
4257
|
+
}
|
|
3733
4258
|
|
|
3734
|
-
export { LIFECYCLE_ERROR_TEXT, LifecycleScopeRevokedError, PermissionDeniedError, PermissionLeaseRevokedError, PluginGraphValidationError, RESOURCE_OWNER, RESOURCE_REGISTRY_CAPABILITY, RUNTIME_MESSAGE_BUS,
|
|
3735
|
-
//# sourceMappingURL=chunk-
|
|
3736
|
-
//# sourceMappingURL=chunk-
|
|
4259
|
+
export { LIFECYCLE_ERROR_TEXT, LifecycleScopeRevokedError, PermissionDeniedError, PermissionLeaseRevokedError, PluginGraphValidationError, RESOURCE_OWNER, RESOURCE_REGISTRY_CAPABILITY, RUNTIME_ERROR_TYPE, RUNTIME_MESSAGE_BUS, RUNTIME_PROTOCOL_VERSION, RUNTIME_SNAPSHOT_TYPE, RemoteServiceError, RuntimeInitializationError, RuntimeUnavailableError, SCOPED_TASK_SCHEDULER_CAPABILITY, StartupCapabilityError, StartupPluginError, UpgradeGateRejectedError, buildPluginGraph, connectSharedWorker, connectSharedWorkerForTesting, createCapabilityRegistry, createInMemoryPluginConfigStore, createLifecycleScope, createMessageBus, createMessagePortServiceTransport, createPermissionLease, createPluginHost, createPluginIntentController, createRemoteServiceMessageCodec, createResourceRegistry, createResourceScope, createResourceStore, createRuntimeMessageCodec, createRuntimeUnitImplementationRegistry, createScopedMessageBus, createScopedTaskScheduler, createServiceBridge, dependenciesOfManifest, isRuntimeError, isRuntimeSnapshot, isRuntimeSnapshotProtocol, lifecycleErrorText, providesOfManifest, registerOwnedResource, reverseDependentsOf, unitSnapshotFromState, validatePluginGraph, validateRuntimeUnitDependencyContracts };
|
|
4260
|
+
//# sourceMappingURL=chunk-76BGPI6M.js.map
|
|
4261
|
+
//# sourceMappingURL=chunk-76BGPI6M.js.map
|