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.
@@ -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 ?? "1";
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 RemoteServiceUnavailableError = class extends Error {
57
- code = "service.unavailable";
58
- constructor(message = "Remote service is unavailable") {
52
+ var RemoteServiceError = class extends Error {
53
+ code;
54
+ details;
55
+ constructor(code, message, details) {
59
56
  super(message);
60
- this.name = "RemoteServiceUnavailableError";
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 isPluginExecution(value) {
140
- return typeof value === "string" && value.trim().length > 0;
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 (!isPluginExecution(dependency.sourceExecution)) {
232
- errors.push("sourceExecution\uFF08\u63D0\u4F9B\u8005\u8FD0\u884C\u73AF\u5883\uFF09\u65E0\u6548");
227
+ if (!isRuntimeKind(dependency.sourceRuntime)) {
228
+ errors.push("sourceRuntime\uFF08\u63D0\u4F9B\u8005 Runtime\uFF09\u65E0\u6548");
233
229
  }
234
- if (!isPluginLifetime(dependency.scope)) {
235
- errors.push("scope\uFF08\u63D0\u4F9B\u8005\u4F5C\u7528\u57DF\uFF09\u65E0\u6548");
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 selectedRuntimeUnits(manifest, execution) {
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 (execution !== void 0) return units.filter((unit) => unit.execution === execution);
259
- return units.length === 1 ? [...units] : [];
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, execution) {
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 && execution === void 0) return [];
271
- const selectedUnits = selectedRuntimeUnits(manifest, execution);
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, execution) {
297
+ function providesOfManifest(manifest, runtime) {
283
298
  const units = manifest.units ?? [];
284
- if (units.length > 1 && execution === void 0) return [];
299
+ if (units.length > 1 && runtime === void 0) return [];
285
300
  return unique([
286
301
  ...units.length === 0 ? manifest.provides ?? [] : [],
287
- ...selectedRuntimeUnits(manifest, execution).flatMap((unit) => unit.provides ?? [])
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
- execution: unit.execution,
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.execution === dependency.sourceExecution && provider.lifetime === dependency.scope && provider.contractVersion === dependency.contractVersion
323
+ (provider) => provider.runtime === dependency.sourceRuntime && provider.contractVersion === dependency.contractVersion
309
324
  );
310
325
  }
311
- function runtimeDependenciesForValidation(manifest, execution) {
326
+ function runtimeDependenciesForValidation(manifest, runtime) {
312
327
  const units = manifest.units ?? [];
313
- const selected = execution === void 0 ? units : units.filter((unit) => unit.execution === execution);
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.execution);
326
- const provided = providesOfManifest(manifest, options.execution);
327
- const dependencyEntries = dependenciesOfManifest(manifest, options.execution);
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
- execution: unit.execution,
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.execution)) {
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.execution);
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.execution).map((dependency) => dependency.capability)
495
+ runtimeDependenciesForValidation(manifest, options.runtime).map((dependency) => dependency.capability)
478
496
  );
479
- for (const dependency of dependenciesOfManifest(manifest, options.execution)) {
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, execution, defaultLifetime) {
2210
+ function selectedUnit(manifest, runtime) {
2193
2211
  const units = manifest.units ?? [];
2194
2212
  if (units.length > 1) {
2195
- const matches = execution === void 0 ? [] : units.filter((unit) => unit.execution === execution);
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
- return execution === void 0 || units[0]?.execution === execution ? units[0] : void 0;
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
- execution: manifest.meta.execution ?? execution ?? "default",
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
- sourceExecution: dependency.sourceExecution ?? "default",
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, execution) {
2220
- return dependenciesOfManifest(manifest, execution);
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, options.execution, options.defaultLifetime ?? "plugin-instance");
2304
- const graph = () => buildPluginGraph([...knownManifests.values()], { execution: options.execution, enabledPluginIds: enabledSet });
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 (!unit.execution || !unit.lifetime) throw new Error(`Plugin "${manifest.id}" unit "${unit.id}" must define execution and lifetime`);
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
- for (const dependency of dependenciesFor(manifest, options.execution)) {
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
- if (!capabilities.has(dependency.capability)) missing.push(dependency.capability);
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 && options.execution !== void 0 && !units.some((item) => item.execution === options.execution)) {
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 parent = resolution.parent ?? rootScope;
2691
- const attributes = Object.freeze({ ...resolution.attributes ?? rootAttributes });
2692
- scope = parent.child(unit.lifetime || "plugin-instance", { pluginId: record.manifest.id, attributes });
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, options.execution);
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({ pluginId: record.manifest.id, capabilities: providesOfManifest(record.manifest, options.execution), state: record.state, error: record.error });
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, options.execution);
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
- await enable(manifest.id);
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, execution) {
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) for (const capability of providesOfManifest(manifest, execution)) if (!providers.has(capability)) providers.set(capability, manifest.id);
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, execution)) {
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 ? [{ pluginId, unitId: unit.id, execution: unit.execution, kind: record.state, ...record.instanceId ? { instanceId: record.instanceId } : {}, ...record.error ? { error: record.error } : {} }] : []
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], { execution: options.execution, builtinCapabilities: new Set(capabilities.keys()) });
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, { execution: options.execution, builtinCapabilities: new Set(capabilities.keys()), allowMissingDependencies: false });
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/lifecycle/pluginIntentController.ts
3086
- function makeAuthorityInstanceId() {
3087
- try {
3088
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
3089
- return `authority:${crypto.randomUUID()}`;
3090
- }
3091
- } catch {
3092
- }
3093
- return `authority:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
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
- const desiredRevision = {};
3109
- for (const [pluginId, value] of Object.entries(initial?.desiredRevision ?? {})) {
3110
- if (Number.isSafeInteger(value) && value >= 0) desiredRevision[pluginId] = value;
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
- return { revision, desiredEnabled, desiredRevision };
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 commandFingerprint(command) {
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 commandError(message) {
3123
- return { status: "command-conflict", commandId: "", message };
3179
+ function isRuntimeKind2(input) {
3180
+ return input === "window-main" || input === "shared-worker";
3124
3181
  }
3125
- function createPluginIntentController(options = {}) {
3126
- const authorityInstanceId = options.authorityInstanceId ?? makeAuthorityInstanceId();
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
- // src/transport/serviceBridge.ts
3241
- function makeRequestId() {
3242
- try {
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 mergeSignals2(...signals) {
3251
- const activeSignals = signals.filter((signal) => signal !== void 0);
3252
- if (activeSignals.length === 0) {
3253
- return { signal: new AbortController().signal, dispose: () => void 0 };
3254
- }
3255
- if (activeSignals.length === 1) {
3256
- const [signal] = activeSignals;
3257
- if (signal) return { signal, dispose: () => void 0 };
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
- signal: controller.signal,
3280
- dispose: () => {
3281
- for (const item of listeners) item.signal.removeEventListener("abort", item.listener);
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.authorityInstanceId,
3302
- reference.scopeId,
3303
- reference.handoverGeneration,
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
- // 代理绑定的是某一份权威目录快照;目录修订后即使 Provider 实例未变,
3308
- // 旧代理也不能继续使用旧授权视图,必须重新取得当前引用。
3309
- reference.snapshotRevision
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.status === "ready" && reference.capabilityId === lookup.capabilityId && reference.contractVersion === lookup.contractVersion && (lookup.execution === void 0 || reference.execution === lookup.execution) && (lookup.scopeId === void 0 || reference.scopeId === lookup.scopeId);
3301
+ return reference.capabilityId === lookup.capabilityId && reference.contractVersion === lookup.contractVersion && (lookup.runtime === void 0 || reference.runtime === lookup.runtime);
3314
3302
  }
3315
- function createServiceBridge(options) {
3316
- let currentState = "disconnected";
3317
- let currentConnectionId;
3318
- let currentAuthorityInstanceId;
3319
- let lastRevision;
3320
- let hasBaseline = false;
3321
- const references = /* @__PURE__ */ new Map();
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 = (proxy, reason) => {
3333
- if (proxy.revoked) return;
3334
- proxy.revoked = true;
3335
- proxy.reason = reason;
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
- proxy.controller.abort(new RemoteServiceUnavailableError(reason));
3371
+ record.controller.abort(new RemoteServiceError(code, reason));
3338
3372
  } catch {
3339
- proxy.controller.abort();
3373
+ record.controller.abort();
3340
3374
  }
3341
3375
  };
3342
- const invalidateProxies = (reason, keep) => {
3343
- for (const proxy of proxies) {
3344
- if (!keep?.has(referenceKey(proxy.reference))) {
3345
- revokeProxy(proxy, reason);
3346
- proxies.delete(proxy);
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 clearReferences = (reason) => {
3351
- references.clear();
3352
- invalidateProxies(reason);
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 connectionId() {
3359
- return currentConnectionId;
3444
+ get runtimeInstanceId() {
3445
+ return currentRuntimeInstanceId;
3360
3446
  },
3361
- get authorityInstanceId() {
3362
- return currentAuthorityInstanceId;
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 (snapshot.connectionId !== currentConnectionId) {
3389
- return {
3390
- accepted: false,
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 (!Number.isSafeInteger(snapshot.snapshotRevision) || snapshot.snapshotRevision < 0) {
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 (!snapshot.baseline && !hasBaseline) {
3410
- currentState = "stale";
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
- if (lastRevision !== void 0 && snapshot.snapshotRevision <= lastRevision) {
3420
- return {
3421
- accepted: false,
3422
- reason: "stale-revision",
3423
- receivedRevision: snapshot.snapshotRevision
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
- if (!snapshot.baseline && lastRevision !== void 0 && snapshot.snapshotRevision !== lastRevision + 1) {
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
- clearReferences("service bridge revision gap");
3429
- hasBaseline = false;
3430
- notify();
3431
- return {
3432
- accepted: false,
3433
- reason: "revision-gap",
3434
- expectedRevision: lastRevision + 1,
3435
- receivedRevision: snapshot.snapshotRevision
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
- accepted: true,
3455
- state: currentState,
3456
- snapshotRevision: snapshot.snapshotRevision
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: "remote service proxy revoked",
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:${reference.capabilityId}`);
3529
+ removeScopeDispose = scope.onDispose(revokeFromScope, `service-proxy:${lookup.capabilityId}`);
3489
3530
  } catch {
3490
- record.revoked = true;
3531
+ revokeFromScope("Remote service proxy scope is already revoked");
3491
3532
  }
3492
3533
  }
3493
- if (record.revoked) {
3494
- proxies.delete(record);
3495
- removeScopeRevoke?.();
3496
- removeScopeDispose?.();
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 RemoteServiceUnavailableError(record.reason);
3506
- const current = references.get(referenceKey(reference));
3507
- if (currentState !== "ready" || !current || current.status !== "ready") {
3508
- record.revoked = true;
3509
- record.reason = "Remote service reference is no longer ready";
3510
- throw new RemoteServiceUnavailableError(record.reason);
3511
- }
3512
- const merged = mergeSignals2(scope?.signal, record.controller.signal, callOptions.signal);
3513
- const connectionId = currentConnectionId;
3514
- if (!connectionId) {
3515
- merged.dispose();
3516
- throw new RemoteServiceUnavailableError("Remote service connection is no longer active");
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.reason ?? new RemoteServiceUnavailableError("Remote service request aborted"));
3575
+ reject(abortedError(merged.signal));
3540
3576
  return;
3541
3577
  }
3542
- const onAbort = () => reject(merged.signal.reason ?? new RemoteServiceUnavailableError("Remote service request aborted"));
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 || currentState !== "ready" || references.get(referenceKey(reference))?.status !== "ready") {
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 = "remote service proxy revoked") {
3561
- revokeProxy(record, reason);
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
- const proxy = bridge.getProxy(lookup, scope);
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 = "remote service bridge invalidated") {
3579
- clearReferences(reason);
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 = "remote service connection disconnected") {
3586
- clearReferences(reason);
3587
- currentConnectionId = void 0;
3588
- currentAuthorityInstanceId = void 0;
3589
- lastRevision = void 0;
3590
- hasBaseline = false;
3591
- currentState = "disconnected";
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 [...references.values()];
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
- const error = new Error(
3619
- typeof input?.message === "string" ? input.message : "Remote service call failed"
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
- return signal.reason ?? new RemoteServiceUnavailableError("Remote service request aborted");
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.callId === "string" && typeof message.connectionId === "string" && typeof message.providerInstanceId === "string";
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 = makeTransportId();
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.connectionId !== call.connectionId || decoded.providerInstanceId !== call.providerInstanceId) return;
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
- options.port.addEventListener("message", onMessage);
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
- options.port.removeEventListener("message", onMessage);
3665
- for (const [callId, call] of pending) {
3666
- pending.delete(callId);
3667
- call.removeAbort();
3668
- call.reject(new RemoteServiceUnavailableError("Remote service transport disposed"));
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 RemoteServiceUnavailableError("Remote service transport disposed"));
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 providerInstanceId = context.reference.providerInstanceId;
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
- finish(() => reject(abortReason(context.signal)));
3688
- try {
3689
- const cancel = {
3690
- type: codec.type("cancel"),
3691
- callId,
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
- connectionId: context.connectionId,
3701
- providerInstanceId,
3818
+ protocolVersion: codec.protocolVersion,
3819
+ serviceInstanceId,
3702
3820
  resolve: (value) => finish(() => resolve(value)),
3703
3821
  reject: (error) => finish(() => reject(error)),
3704
- removeAbort: () => context.signal.removeEventListener("abort", onAbort)
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
- context.signal.addEventListener("abort", onAbort, { once: true });
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.postMessage(codec.encode(message), [...transfer]);
3721
- if (context.signal.aborted) onAbort();
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, RemoteServiceUnavailableError, SCOPED_TASK_SCHEDULER_CAPABILITY, StartupCapabilityError, StartupPluginError, UpgradeGateRejectedError, buildPluginGraph, createCapabilityRegistry, createInMemoryPluginConfigStore, createLifecycleScope, createMessageBus, createMessagePortServiceTransport, createPermissionLease, createPluginHost, createPluginIntentController, createRemoteServiceBridge, createRemoteServiceMessageCodec, createResourceRegistry, createResourceScope, createResourceStore, createRuntimeUnitImplementationRegistry, createScopedMessageBus, createScopedTaskScheduler, createServiceBridge, dependenciesOfManifest, lifecycleErrorText, providesOfManifest, registerOwnedResource, reverseDependentsOf, validatePluginGraph, validateRuntimeUnitDependencyContracts };
3735
- //# sourceMappingURL=chunk-KGNF36DZ.js.map
3736
- //# sourceMappingURL=chunk-KGNF36DZ.js.map
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