webloom-framework 0.3.0 → 0.4.1

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.
@@ -1,4261 +0,0 @@
1
- // src/contracts/lifecycle.ts
2
- var LifecycleScopeRevokedError = class extends Error {
3
- code = "lifecycle.scope_revoked";
4
- constructor(message = "Lifecycle scope has been revoked") {
5
- super(message);
6
- this.name = "LifecycleScopeRevokedError";
7
- }
8
- };
9
- var PermissionLeaseRevokedError = class extends Error {
10
- code = "permission.lease_revoked";
11
- constructor(message = "Permission lease has been revoked") {
12
- super(message);
13
- this.name = "PermissionLeaseRevokedError";
14
- }
15
- };
16
- var PermissionDeniedError = class extends Error {
17
- code = "permission.denied";
18
- permission;
19
- constructor(permission, message = `Permission denied: ${permission}`) {
20
- super(message);
21
- this.name = "PermissionDeniedError";
22
- this.permission = permission;
23
- }
24
- };
25
- function createRemoteServiceMessageCodec(options = {}) {
26
- const prefix = options.prefix ?? "webloom.remote-service";
27
- const protocolVersion = options.protocolVersion ?? "webloom.remote-service.v2";
28
- const types = /* @__PURE__ */ new Map([
29
- ["call", `${prefix}.call`],
30
- ["result", `${prefix}.result`],
31
- ["error", `${prefix}.error`],
32
- ["cancel", `${prefix}.cancel`]
33
- ]);
34
- return Object.freeze({
35
- protocolVersion,
36
- type(kind) {
37
- const value = types.get(kind);
38
- if (!value) throw new Error(`Unknown remote service message kind: ${kind}`);
39
- return value;
40
- },
41
- encode(message) {
42
- return { ...message };
43
- },
44
- decode(input) {
45
- if (!input || typeof input !== "object") return void 0;
46
- const message = input;
47
- if (typeof message.type !== "string" || ![...types.values()].includes(message.type)) return void 0;
48
- return message;
49
- }
50
- });
51
- }
52
- var RemoteServiceError = class extends Error {
53
- code;
54
- details;
55
- constructor(code, message, details) {
56
- super(message);
57
- this.name = "RemoteServiceError";
58
- this.code = code;
59
- this.details = details;
60
- }
61
- };
62
- var UpgradeGateRejectedError = class extends Error {
63
- code = "upgrade.gate_rejected";
64
- reason;
65
- constructor(reason, message = `Upgrade gate rejected operation: ${reason}`) {
66
- super(message);
67
- this.name = "UpgradeGateRejectedError";
68
- this.reason = reason;
69
- }
70
- };
71
- var SCOPED_TASK_SCHEDULER_CAPABILITY = "runtime.task-scheduler";
72
- var LIFECYCLE_ERROR_TEXT = Object.freeze({
73
- "lifecycle.scope_revoked": "\u8FD0\u884C\u5B9E\u4F8B\u5DF2\u505C\u6B62",
74
- "permission.denied": "\u63D2\u4EF6\u6CA1\u6709\u83B7\u5F97\u8BE5\u64CD\u4F5C\u7684\u6743\u9650",
75
- "permission.lease_revoked": "\u6388\u6743\u79DF\u7EA6\u5DF2\u64A4\u9500",
76
- "lifecycle.cleanup_failed": "\u8D44\u6E90\u6E05\u7406\u5931\u8D25\uFF0C\u7B49\u5F85\u91CD\u8BD5",
77
- "lifecycle.cleanup_timeout": "\u8D44\u6E90\u6E05\u7406\u8D85\u65F6\uFF0C\u4ECD\u5728\u540E\u53F0\u6392\u7A7A",
78
- "upgrade.gate_rejected": "\u7248\u672C\u63A5\u7BA1\u95E8\u7981\u672A\u901A\u8FC7"
79
- });
80
- function lifecycleErrorText(code) {
81
- return LIFECYCLE_ERROR_TEXT[code] ?? "\u751F\u547D\u5468\u671F\u64CD\u4F5C\u672A\u5B8C\u6210";
82
- }
83
-
84
- // src/contracts/messageBus.ts
85
- var RUNTIME_MESSAGE_BUS = "webloom.messageBus";
86
-
87
- // src/contracts/resource.ts
88
- var RESOURCE_OWNER = /* @__PURE__ */ Symbol("webloom.resource.owner");
89
- var RESOURCE_REGISTRY_CAPABILITY = "webloom.resource.registry";
90
-
91
- // src/host/capabilityRegistry.ts
92
- function createCapabilityRegistry() {
93
- const map = /* @__PURE__ */ new Map();
94
- return {
95
- provide(key, value) {
96
- if (map.has(key)) {
97
- throw new Error(`Capability "${key}" is already provided`);
98
- }
99
- map.set(key, value);
100
- },
101
- revoke(key) {
102
- if (!map.has(key)) return;
103
- map.delete(key);
104
- },
105
- get(key) {
106
- if (!map.has(key)) {
107
- throw new Error(`Capability "${key}" is not available`);
108
- }
109
- return map.get(key);
110
- },
111
- has(key) {
112
- return map.has(key);
113
- },
114
- require(key) {
115
- if (!map.has(key)) {
116
- throw new Error(`Required capability "${key}" is missing`);
117
- }
118
- },
119
- keys() {
120
- return [...map.keys()];
121
- }
122
- };
123
- }
124
-
125
- // src/host/pluginGraph.ts
126
- var PluginGraphValidationError = class extends Error {
127
- code = "plugin.graph_invalid";
128
- diagnostics;
129
- constructor(diagnostics) {
130
- super(diagnostics.map((diagnostic) => diagnostic.message).join("; "));
131
- this.name = "PluginGraphValidationError";
132
- this.diagnostics = [...diagnostics];
133
- }
134
- };
135
- function unique(values) {
136
- return [...new Set(values)];
137
- }
138
- function isRuntimeKind(value) {
139
- return value === "window-main" || value === "shared-worker";
140
- }
141
- function isRecord(value) {
142
- return typeof value === "object" && value !== null;
143
- }
144
- function validateRuntimeUnitDependencyContracts(manifests) {
145
- const diagnostics = [];
146
- for (const manifest of manifests) {
147
- const units = manifest.units;
148
- if (units === void 0) continue;
149
- const productLevelFields = [
150
- ["dependencies", manifest.dependencies],
151
- ["permissions", manifest.permissions],
152
- ["contribution", manifest.contribution],
153
- ["config", manifest.config]
154
- ];
155
- for (const [field, value] of productLevelFields) {
156
- if (value === void 0) continue;
157
- diagnostics.push({
158
- code: "plugin.runtime_declaration_at_product_level",
159
- ids: [manifest.id, field],
160
- message: `\u591A\u8FD0\u884C\u5355\u5143\u63D2\u4EF6 "${manifest.id}" \u7684 ${field} \u5FC5\u987B\u58F0\u660E\u5728\u5BF9\u5E94 RuntimeUnitDescriptor \u4E2D\uFF0C\u4E0D\u80FD\u4F7F\u7528\u4EA7\u54C1\u7EA7 fallback`
161
- });
162
- }
163
- if ((manifest.provides?.length ?? 0) > 0) {
164
- diagnostics.push({
165
- code: "plugin.runtime_declaration_at_product_level",
166
- ids: [manifest.id, "provides"],
167
- message: `\u591A\u8FD0\u884C\u5355\u5143\u63D2\u4EF6 "${manifest.id}" \u7684 provides \u5FC5\u987B\u58F0\u660E\u5728\u5BF9\u5E94 RuntimeUnitDescriptor \u4E2D\uFF0C\u4E0D\u80FD\u4F7F\u7528\u4EA7\u54C1\u7EA7 capability \u6458\u8981`
168
- });
169
- }
170
- if (!Array.isArray(units)) {
171
- diagnostics.push({
172
- code: "plugin.dependency_contract_invalid",
173
- ids: [manifest.id, "units"],
174
- message: `\u63D2\u4EF6 "${manifest.id}" \u7684 units \u5FC5\u987B\u662F\u8FD0\u884C\u5355\u5143\u6570\u7EC4`
175
- });
176
- continue;
177
- }
178
- units.forEach((unitValue, unitIndex) => {
179
- if (!isRecord(unitValue)) {
180
- diagnostics.push({
181
- code: "plugin.dependency_contract_invalid",
182
- ids: [manifest.id, `unit:${unitIndex}`],
183
- message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u7B2C ${unitIndex + 1} \u4E2A\u8FD0\u884C\u5355\u5143\u63CF\u8FF0\u65E0\u6548`
184
- });
185
- return;
186
- }
187
- const unitId = typeof unitValue.id === "string" && unitValue.id.length > 0 ? unitValue.id : `unit:${unitIndex}`;
188
- const providedContracts = unitValue.providedContracts;
189
- if (providedContracts !== void 0) {
190
- if (!isRecord(providedContracts)) {
191
- diagnostics.push({
192
- code: "plugin.dependency_contract_invalid",
193
- ids: [manifest.id, unitId],
194
- message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143 "${unitId}" \u7684 providedContracts\uFF08\u63D0\u4F9B\u5951\u7EA6\u7248\u672C\uFF09\u5FC5\u987B\u662F\u5BF9\u8C61`
195
- });
196
- } else {
197
- for (const [capability, version] of Object.entries(providedContracts)) {
198
- if (typeof version !== "string" || version.trim() === "") {
199
- diagnostics.push({
200
- code: "plugin.dependency_contract_invalid",
201
- ids: [manifest.id, unitId, capability],
202
- message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143 "${unitId}" \u4E3A\u80FD\u529B "${capability}" \u58F0\u660E\u7684\u5951\u7EA6\u7248\u672C\u4E0D\u80FD\u4E3A\u7A7A`
203
- });
204
- }
205
- }
206
- }
207
- }
208
- const dependencies = unitValue.dependencies;
209
- if (dependencies === void 0) return;
210
- if (!Array.isArray(dependencies)) {
211
- diagnostics.push({
212
- code: "plugin.dependency_contract_invalid",
213
- ids: [manifest.id, unitId],
214
- message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143 "${unitId}" \u7684 dependencies \u5FC5\u987B\u662F\u6570\u7EC4`
215
- });
216
- return;
217
- }
218
- dependencies.forEach((dependencyValue, dependencyIndex) => {
219
- const dependency = isRecord(dependencyValue) ? dependencyValue : {};
220
- const errors = [];
221
- if (typeof dependency.capability !== "string" || dependency.capability.trim() === "") {
222
- errors.push("capability\uFF08\u80FD\u529B\u6807\u8BC6\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
223
- }
224
- if (typeof dependency.contractVersion !== "string" || dependency.contractVersion.trim() === "") {
225
- errors.push("contractVersion\uFF08\u5951\u7EA6\u7248\u672C\uFF09\u4E0D\u80FD\u4E3A\u7A7A");
226
- }
227
- if (!isRuntimeKind(dependency.sourceRuntime)) {
228
- errors.push("sourceRuntime\uFF08\u63D0\u4F9B\u8005 Runtime\uFF09\u65E0\u6548");
229
- }
230
- if (unitValue.runtime !== void 0 && !isRuntimeKind(unitValue.runtime)) {
231
- errors.push("runtime\uFF08\u76EE\u6807 Runtime\uFF09\u65E0\u6548");
232
- }
233
- if (dependency.reason !== void 0 && typeof dependency.reason !== "string") {
234
- errors.push("reason\uFF08\u4F9D\u8D56\u8BF4\u660E\uFF09\u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
235
- }
236
- if (dependency.optional !== void 0 && typeof dependency.optional !== "boolean") {
237
- errors.push("optional\uFF08\u662F\u5426\u53EF\u9009\uFF09\u5FC5\u987B\u662F\u5E03\u5C14\u503C");
238
- }
239
- if (errors.length === 0) return;
240
- const capability = typeof dependency.capability === "string" && dependency.capability.length > 0 ? dependency.capability : `dependency:${dependencyIndex}`;
241
- diagnostics.push({
242
- code: "plugin.dependency_contract_invalid",
243
- ids: [manifest.id, unitId, capability],
244
- message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143 "${unitId}" \u7B2C ${dependencyIndex + 1} \u6761\u4F9D\u8D56\u5951\u7EA6\u65E0\u6548\uFF1A${errors.join("\u3001")}`
245
- });
246
- });
247
- });
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
- }
260
- return diagnostics;
261
- }
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) {
269
- const units = manifest.units ?? [];
270
- if (units.length === 0) return [];
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]] : [];
275
- }
276
- function dependenciesOfManifest(manifest, runtime) {
277
- const byCapability = /* @__PURE__ */ new Map();
278
- const add = (dependency) => {
279
- const previous = byCapability.get(dependency.capability);
280
- if (!previous || previous.optional === true && dependency.optional !== true) {
281
- byCapability.set(dependency.capability, { ...dependency });
282
- }
283
- };
284
- const units = manifest.units ?? [];
285
- if (units.length > 1 && runtime === void 0) return [];
286
- const selectedUnits = selectedRuntimeUnits(manifest, runtime);
287
- if (units.length > 0 && selectedUnits.length === 0) return [];
288
- if (units.length === 0) {
289
- for (const dependency of manifest.dependencies ?? []) add(dependency);
290
- } else {
291
- for (const unit of selectedUnits) {
292
- for (const dependency of unit.dependencies ?? []) add(dependency);
293
- }
294
- }
295
- return [...byCapability.values()];
296
- }
297
- function providesOfManifest(manifest, runtime) {
298
- const units = manifest.units ?? [];
299
- if (units.length > 1 && runtime === void 0) return [];
300
- return unique([
301
- ...units.length === 0 ? manifest.provides ?? [] : [],
302
- ...selectedRuntimeUnits(manifest, runtime).flatMap((unit) => unit.provides ?? [])
303
- ]);
304
- }
305
- function runtimeUnitProviders(manifests, capability) {
306
- const providers = [];
307
- for (const manifest of manifests) {
308
- for (const unit of manifest.units ?? []) {
309
- if (!unit.provides?.includes(capability)) continue;
310
- if (!isRuntimeUnit(unit)) continue;
311
- providers.push({
312
- pluginId: manifest.id,
313
- unitId: unit.id,
314
- runtime: unit.runtime,
315
- contractVersion: unit.providedContracts?.[capability]
316
- });
317
- }
318
- }
319
- return providers;
320
- }
321
- function matchingRuntimeUnitProviders(manifests, dependency) {
322
- return runtimeUnitProviders(manifests, dependency.capability).filter(
323
- (provider) => provider.runtime === dependency.sourceRuntime && provider.contractVersion === dependency.contractVersion
324
- );
325
- }
326
- function runtimeDependenciesForValidation(manifest, runtime) {
327
- const units = manifest.units ?? [];
328
- const selected = runtime === void 0 ? units : units.filter((unit) => unitRuntime(unit) === runtime);
329
- return selected.flatMap((unit) => unit.dependencies ?? []);
330
- }
331
- function buildPluginGraph(manifests, options = {}) {
332
- const provides = {};
333
- const dependencies = {};
334
- const optionalDependencies = {};
335
- const dependencyDetails = {};
336
- const providers = {};
337
- const unitGraph = {};
338
- const enabled = options.enabledPluginIds;
339
- for (const manifest of manifests) {
340
- const selectedUnits = selectedRuntimeUnits(manifest, options.runtime);
341
- const provided = providesOfManifest(manifest, options.runtime);
342
- const dependencyEntries = dependenciesOfManifest(manifest, options.runtime);
343
- dependencyDetails[manifest.id] = dependencyEntries.map((dependency) => ({ ...dependency }));
344
- const deps = unique(dependencyEntries.map((dependency) => dependency.capability));
345
- optionalDependencies[manifest.id] = unique(
346
- dependencyEntries.filter((dependency) => dependency.optional).map((dependency) => dependency.capability)
347
- );
348
- provides[manifest.id] = provided;
349
- dependencies[manifest.id] = deps;
350
- for (const unit of selectedUnits) {
351
- const unitKey = `${manifest.id}:${unit.id}`;
352
- unitGraph[unitKey] = {
353
- pluginId: manifest.id,
354
- unitId: unit.id,
355
- runtime: unit.runtime,
356
- dependencies: unique((unit.dependencies ?? []).map((dependency) => dependency.capability)),
357
- dependencyDetails: (unit.dependencies ?? []).map((dependency) => ({ ...dependency })),
358
- provides: unique(unit.provides ?? []),
359
- providedContracts: unit.providedContracts ? { ...unit.providedContracts } : void 0
360
- };
361
- }
362
- for (const capability of provided) {
363
- (providers[capability] ??= []).push(manifest.id);
364
- }
365
- }
366
- const providerByCapability = /* @__PURE__ */ new Map();
367
- for (const [capability, pluginIds] of Object.entries(providers)) {
368
- providerByCapability.set(capability, pluginIds);
369
- }
370
- const reverse = {};
371
- for (const manifest of manifests) {
372
- const dependentEnabled = enabled?.has(manifest.id) ?? manifest.meta.defaultEnabled;
373
- for (const capability of dependencies[manifest.id] ?? []) {
374
- if (optionalDependencies[manifest.id]?.includes(capability)) continue;
375
- for (const providerId of providerByCapability.get(capability) ?? []) {
376
- if (providerId === manifest.id) continue;
377
- const entries = reverse[providerId] ??= [];
378
- let entry = entries.find((item) => item.pluginId === manifest.id);
379
- if (!entry) {
380
- entry = { pluginId: manifest.id, enabled: dependentEnabled, capabilities: [] };
381
- entries.push(entry);
382
- }
383
- if (!entry.capabilities.includes(capability)) entry.capabilities.push(capability);
384
- }
385
- }
386
- }
387
- const cycles = [];
388
- const firstProvider = /* @__PURE__ */ new Map();
389
- for (const [capability, pluginIds] of providerByCapability) {
390
- if (pluginIds[0]) firstProvider.set(capability, pluginIds[0]);
391
- }
392
- const visited = /* @__PURE__ */ new Set();
393
- const path = [];
394
- const visit = (pluginId) => {
395
- if (visited.has(pluginId)) return;
396
- const position = path.indexOf(pluginId);
397
- if (position >= 0) {
398
- const cycle = [...path.slice(position), pluginId];
399
- if (!cycles.some((item) => item.join("\0") === cycle.join("\0"))) cycles.push(cycle);
400
- return;
401
- }
402
- path.push(pluginId);
403
- const manifest = manifests.find((item) => item.id === pluginId);
404
- if (!manifest) {
405
- path.pop();
406
- visited.add(pluginId);
407
- return;
408
- }
409
- for (const dependency of dependenciesOfManifest(manifest, options.runtime)) {
410
- if (dependency.optional) continue;
411
- const provider = firstProvider.get(dependency.capability);
412
- if (provider) visit(provider);
413
- }
414
- path.pop();
415
- visited.add(pluginId);
416
- };
417
- for (const manifest of manifests) visit(manifest.id);
418
- return {
419
- plugins: manifests.map((manifest) => manifest.id),
420
- dependencies,
421
- optionalDependencies,
422
- provides,
423
- reverse,
424
- providers,
425
- dependencyDetails,
426
- cycles,
427
- units: unitGraph
428
- };
429
- }
430
- function validatePluginGraph(manifests, options = {}) {
431
- const diagnostics = [];
432
- diagnostics.push(...validateRuntimeUnitDependencyContracts(manifests));
433
- const ids = /* @__PURE__ */ new Set();
434
- for (const manifest of manifests) {
435
- if (ids.has(manifest.id)) {
436
- diagnostics.push({
437
- code: "plugin.duplicate_id",
438
- ids: [manifest.id],
439
- message: `\u63D2\u4EF6\u6807\u8BC6 "${manifest.id}" \u91CD\u590D\uFF0C\u65E0\u6CD5\u786E\u5B9A\u552F\u4E00\u8FD0\u884C\u5B9E\u4F8B`
440
- });
441
- }
442
- ids.add(manifest.id);
443
- }
444
- const graph = buildPluginGraph([...manifests], options);
445
- const builtins = options.builtinCapabilities ?? /* @__PURE__ */ new Set();
446
- const multiProvider = options.multiProviderCapabilities ?? /* @__PURE__ */ new Set();
447
- for (const [capability, pluginIds] of Object.entries(graph.providers ?? {})) {
448
- if (pluginIds.length > 1 && !multiProvider.has(capability)) {
449
- diagnostics.push({
450
- code: "capability.duplicate_provider",
451
- ids: [capability, ...pluginIds],
452
- message: `\u80FD\u529B "${capability}" \u6709\u591A\u4E2A\u63D0\u4F9B\u8005\uFF08${pluginIds.join(", ")}\uFF09\uFF0C\u4F46\u672A\u58F0\u660E\u4E13\u7528 Provider Registry`
453
- });
454
- }
455
- }
456
- for (const manifest of manifests) {
457
- const strictDependencies = runtimeDependenciesForValidation(manifest, options.runtime);
458
- for (const dependency of strictDependencies) {
459
- if (dependency.optional) continue;
460
- if (options.externalRuntimeDependencies && options.runtime !== void 0 && dependency.sourceRuntime !== options.runtime) {
461
- continue;
462
- }
463
- const matches = matchingRuntimeUnitProviders(manifests, dependency);
464
- if (matches.length > 0) {
465
- if (matches.length > 1 && !multiProvider.has(dependency.capability)) {
466
- diagnostics.push({
467
- code: "capability.duplicate_provider",
468
- ids: [dependency.capability, ...matches.map((provider) => `${provider.pluginId}:${provider.unitId}`)],
469
- message: `\u80FD\u529B "${dependency.capability}" \u7684\u8FD0\u884C\u5355\u5143\u5951\u7EA6\u6709\u591A\u4E2A\u5339\u914D Provider\uFF08${matches.map((provider) => `${provider.pluginId}:${provider.unitId}`).join(", ")}\uFF09\uFF0C\u4F46\u672A\u58F0\u660E\u4E13\u7528 Provider Registry`
470
- });
471
- }
472
- continue;
473
- }
474
- const candidates = runtimeUnitProviders(manifests, dependency.capability);
475
- if (builtins.has(dependency.capability) && candidates.length === 0) {
476
- continue;
477
- }
478
- if (candidates.length > 0) {
479
- diagnostics.push({
480
- code: "plugin.dependency_contract_unavailable",
481
- ids: [manifest.id, dependency.capability],
482
- message: `\u63D2\u4EF6 "${manifest.id}" \u7684\u8FD0\u884C\u5355\u5143\u4F9D\u8D56 "${dependency.capability}" \u6CA1\u6709\u5339\u914D\u7684\u5951\u7EA6\u7248\u672C\u3001\u6765\u6E90\u73AF\u5883\u6216\u4F5C\u7528\u57DF`
483
- });
484
- } else if (!options.allowMissingDependencies) {
485
- diagnostics.push({
486
- code: "plugin.missing_dependency",
487
- ids: [manifest.id, dependency.capability],
488
- message: `\u63D2\u4EF6 "${manifest.id}" \u7F3A\u5C11\u786C\u4F9D\u8D56\u80FD\u529B "${dependency.capability}"`
489
- });
490
- }
491
- }
492
- }
493
- for (const manifest of manifests) {
494
- const strictDependencyCapabilities = new Set(
495
- runtimeDependenciesForValidation(manifest, options.runtime).map((dependency) => dependency.capability)
496
- );
497
- for (const dependency of dependenciesOfManifest(manifest, options.runtime)) {
498
- if (dependency.optional) continue;
499
- if (strictDependencyCapabilities.has(dependency.capability)) continue;
500
- const provided = (graph.providers?.[dependency.capability]?.length ?? 0) > 0 || builtins.has(dependency.capability);
501
- if (!provided && !options.allowMissingDependencies) {
502
- diagnostics.push({
503
- code: "plugin.missing_dependency",
504
- ids: [manifest.id, dependency.capability],
505
- message: `\u63D2\u4EF6 "${manifest.id}" \u7F3A\u5C11\u786C\u4F9D\u8D56\u80FD\u529B "${dependency.capability}"`
506
- });
507
- }
508
- }
509
- }
510
- for (const cycle of graph.cycles ?? []) {
511
- diagnostics.push({
512
- code: "plugin.dependency_cycle",
513
- ids: cycle,
514
- message: `\u63D2\u4EF6\u5B58\u5728\u786C\u4F9D\u8D56\u73AF\uFF1A${cycle.join(" -> ")}`
515
- });
516
- }
517
- if (diagnostics.length > 0) throw new PluginGraphValidationError(diagnostics);
518
- return graph;
519
- }
520
- function reverseDependentsOf(graph, pluginId, enabledSet) {
521
- return (graph.reverse[pluginId] ?? []).filter((dependent) => enabledSet.has(dependent.pluginId));
522
- }
523
-
524
- // src/messaging/messageBus.ts
525
- function makeMessageId() {
526
- if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
527
- return crypto.randomUUID();
528
- }
529
- return `m_${Date.now().toString(36)}_${Math.floor(Math.random() * 1e9).toString(36)}`;
530
- }
531
- function errorMessage(err) {
532
- if (err instanceof Error) return err.message;
533
- if (typeof err === "string") return err;
534
- return String(err);
535
- }
536
- var MAX_HANDLER_CONCURRENCY = 128;
537
- function waitForHandler(result, signal) {
538
- if (signal.aborted) {
539
- return Promise.reject(signal.reason ?? new Error("aborted"));
540
- }
541
- return new Promise((resolve, reject) => {
542
- const onAbort = () => {
543
- reject(signal.reason ?? new Error("aborted"));
544
- };
545
- signal.addEventListener("abort", onAbort, { once: true });
546
- result.then(resolve, reject).finally(() => {
547
- signal.removeEventListener("abort", onAbort);
548
- });
549
- });
550
- }
551
- function createMessageBus() {
552
- const subscriptions = /* @__PURE__ */ new Map();
553
- const routedHandlers = /* @__PURE__ */ new Map();
554
- const mailboxes = /* @__PURE__ */ new Map();
555
- const targetHandlerCount = /* @__PURE__ */ new Map();
556
- const targetConcurrency = /* @__PURE__ */ new Map();
557
- const snapshotListeners = /* @__PURE__ */ new Set();
558
- let total = 0;
559
- let completed = 0;
560
- let failed = 0;
561
- let canceled = 0;
562
- let inFlight = 0;
563
- let lastError;
564
- const pumping = /* @__PURE__ */ new Set();
565
- function emitSnapshot() {
566
- const snap = snapshot();
567
- for (const l of snapshotListeners) l(snap);
568
- }
569
- function snapshot() {
570
- const byTarget = {};
571
- let queued = 0;
572
- for (const [target, queue] of mailboxes.entries()) {
573
- byTarget[target] = queue.length;
574
- queued += queue.length;
575
- }
576
- return {
577
- total,
578
- queued,
579
- inFlight,
580
- completed,
581
- failed,
582
- canceled,
583
- lastError,
584
- byTarget
585
- };
586
- }
587
- function makeMessage(type, mode, payload, options) {
588
- return {
589
- id: options.messageId ?? makeMessageId(),
590
- type,
591
- mode,
592
- payload,
593
- target: options.target,
594
- priority: options.priority,
595
- timeoutMs: options.timeoutMs,
596
- causationId: options.causationId,
597
- createdAt: Date.now()
598
- };
599
- }
600
- function publishInternal(message) {
601
- total += 1;
602
- const routed = routedHandlers.get(message.type);
603
- if (routed && !routed.target) {
604
- try {
605
- const result = routed.handler(message);
606
- if (result && typeof result.then === "function") {
607
- result.catch((err) => {
608
- failed += 1;
609
- lastError = errorMessage(err);
610
- emitSnapshot();
611
- });
612
- }
613
- } catch (err) {
614
- failed += 1;
615
- lastError = errorMessage(err);
616
- }
617
- }
618
- const bucket = subscriptions.get(message.type);
619
- if (bucket) {
620
- for (const sub of [...bucket]) {
621
- try {
622
- sub.handler(message.payload);
623
- } catch (err) {
624
- failed += 1;
625
- lastError = errorMessage(err);
626
- }
627
- }
628
- }
629
- emitSnapshot();
630
- return message.id;
631
- }
632
- function settleEntry(entry, state, value) {
633
- if (entry.settled) return;
634
- entry.settled = true;
635
- const wasRunning = entry.state === "running";
636
- entry.state = state;
637
- entry.cleanup();
638
- if (state === "completed") {
639
- completed += 1;
640
- } else if (state === "failed") {
641
- failed += 1;
642
- lastError = errorMessage(value);
643
- } else {
644
- canceled += 1;
645
- lastError = errorMessage(value ?? entry.signal.reason);
646
- }
647
- if (wasRunning) inFlight -= 1;
648
- emitSnapshot();
649
- try {
650
- entry.onSettled?.();
651
- } catch (err) {
652
- lastError = errorMessage(err);
653
- }
654
- if (entry.mode === "request") {
655
- if (state === "completed") entry.resolve(value);
656
- else entry.reject(value);
657
- }
658
- }
659
- function enqueueMessage(message, mode, signal, onSettled) {
660
- total += 1;
661
- const target = message.target;
662
- if (!target) {
663
- failed += 1;
664
- lastError = `MessageBus.${mode === "request" ? "request" : "dispatch"} requires a target`;
665
- emitSnapshot();
666
- try {
667
- onSettled?.();
668
- } catch {
669
- }
670
- return mode === "request" ? Promise.reject(new Error(lastError)) : message.id;
671
- }
672
- const handler = routedHandlers.get(message.type);
673
- if (!handler || handler.target !== target) {
674
- failed += 1;
675
- lastError = `No handler registered for type "${message.type}" at target "${target}"`;
676
- emitSnapshot();
677
- try {
678
- onSettled?.();
679
- } catch {
680
- }
681
- return mode === "request" ? Promise.reject(new Error(lastError)) : message.id;
682
- }
683
- if (signal?.aborted) {
684
- canceled += 1;
685
- lastError = errorMessage(signal.reason ?? new Error("aborted"));
686
- emitSnapshot();
687
- try {
688
- onSettled?.();
689
- } catch {
690
- }
691
- return mode === "request" ? Promise.reject(signal.reason ?? new Error("aborted")) : message.id;
692
- }
693
- const ctl = new AbortController();
694
- let timeoutHandle;
695
- const onUpstreamAbort = () => {
696
- ctl.abort(signal?.reason ?? new Error("aborted"));
697
- };
698
- if (signal) {
699
- signal.addEventListener("abort", onUpstreamAbort, { once: true });
700
- }
701
- if (typeof message.timeoutMs === "number" && message.timeoutMs > 0) {
702
- timeoutHandle = setTimeout(() => {
703
- ctl.abort(new Error("MessageBus.request timeout"));
704
- }, message.timeoutMs);
705
- }
706
- const stamped = { ...message, signal: ctl.signal };
707
- const mailbox = mailboxes.get(target) ?? [];
708
- mailboxes.set(target, mailbox);
709
- return new Promise((resolve, reject) => {
710
- const entry = {
711
- message: stamped,
712
- signal: ctl.signal,
713
- mode,
714
- state: "queued",
715
- settled: false,
716
- resolve: (v) => resolve(v),
717
- reject: (e) => reject(e),
718
- onSettled,
719
- cleanup: () => {
720
- if (timeoutHandle) clearTimeout(timeoutHandle);
721
- if (signal) signal.removeEventListener("abort", onUpstreamAbort);
722
- ctl.signal.removeEventListener("abort", onAbort);
723
- }
724
- };
725
- function onAbort() {
726
- if (entry.state === "queued") {
727
- const idx = mailbox.indexOf(entry);
728
- if (idx >= 0) {
729
- mailbox.splice(idx, 1);
730
- }
731
- }
732
- settleEntry(entry, "canceled", ctl.signal.reason ?? new Error("aborted"));
733
- }
734
- ctl.signal.addEventListener("abort", onAbort, { once: true });
735
- mailbox.push(entry);
736
- emitSnapshot();
737
- schedulePump(target);
738
- });
739
- }
740
- function schedulePump(target) {
741
- if (pumping.has(target)) return;
742
- const mailbox = mailboxes.get(target);
743
- if (!mailbox || mailbox.length === 0) return;
744
- pumping.add(target);
745
- if (typeof queueMicrotask === "function") {
746
- queueMicrotask(() => {
747
- void runPump(target);
748
- });
749
- } else {
750
- Promise.resolve().then(() => {
751
- void runPump(target);
752
- });
753
- }
754
- }
755
- async function runPump(target) {
756
- const concurrency = targetConcurrency.get(target) ?? 1;
757
- const workers = [];
758
- for (let i = 0; i < concurrency; i += 1) {
759
- workers.push(workerLoop(target));
760
- }
761
- try {
762
- await Promise.all(workers);
763
- } finally {
764
- pumping.delete(target);
765
- const mailbox = mailboxes.get(target);
766
- if (mailbox && mailbox.length > 0) {
767
- schedulePump(target);
768
- }
769
- emitSnapshot();
770
- }
771
- }
772
- async function workerLoop(target, workerId) {
773
- while (true) {
774
- const entry = pickBestEntry(target);
775
- if (!entry) return;
776
- await processEntry(target, entry);
777
- }
778
- }
779
- function pickBestEntry(target) {
780
- const mailbox = mailboxes.get(target);
781
- if (!mailbox || mailbox.length === 0) return void 0;
782
- let bestIdx = -1;
783
- let bestEntry;
784
- for (let i = 0; i < mailbox.length; i += 1) {
785
- const cur = mailbox[i];
786
- if (cur.settled) continue;
787
- if (!bestEntry) {
788
- bestEntry = cur;
789
- bestIdx = i;
790
- continue;
791
- }
792
- const bestPriority = bestEntry.message.priority ?? 0;
793
- const curPriority = cur.message.priority ?? 0;
794
- if (curPriority > bestPriority || curPriority === bestPriority && cur.message.createdAt < bestEntry.message.createdAt) {
795
- bestEntry = cur;
796
- bestIdx = i;
797
- }
798
- }
799
- if (bestEntry && bestIdx >= 0) {
800
- mailbox.splice(bestIdx, 1);
801
- }
802
- return bestEntry;
803
- }
804
- async function processEntry(target, entry) {
805
- if (entry.settled) return;
806
- entry.state = "running";
807
- inFlight += 1;
808
- emitSnapshot();
809
- const handler = routedHandlers.get(entry.message.type);
810
- if (!handler) {
811
- settleEntry(entry, "failed", new Error(`No handler for type "${entry.message.type}"`));
812
- return;
813
- }
814
- try {
815
- const result = handler.handler(entry.message);
816
- if (result && typeof result.then === "function") {
817
- const v = await waitForHandler(
818
- result,
819
- entry.signal
820
- );
821
- if (entry.signal.aborted) {
822
- settleEntry(entry, "canceled", entry.signal.reason);
823
- } else {
824
- settleEntry(entry, "completed", v);
825
- }
826
- } else {
827
- if (entry.signal.aborted) {
828
- settleEntry(entry, "canceled", entry.signal.reason);
829
- } else {
830
- settleEntry(entry, "completed", result);
831
- }
832
- }
833
- } catch (err) {
834
- if (entry.signal.aborted) {
835
- settleEntry(entry, "canceled", entry.signal.reason ?? err);
836
- } else {
837
- settleEntry(entry, "failed", err);
838
- }
839
- }
840
- }
841
- const bus = {
842
- publish(type, payload, options) {
843
- const message = makeMessage(type, "event", payload, {
844
- causationId: options?.causationId,
845
- messageId: options?.messageId
846
- });
847
- return publishInternal(message);
848
- },
849
- subscribe(type, handler) {
850
- const record = { type, handler };
851
- let bucket = subscriptions.get(type);
852
- if (!bucket) {
853
- bucket = /* @__PURE__ */ new Set();
854
- subscriptions.set(type, bucket);
855
- }
856
- bucket.add(record);
857
- return () => {
858
- bucket?.delete(record);
859
- };
860
- },
861
- dispatch(type, payload, options) {
862
- const message = makeMessage(type, "command", payload, {
863
- target: options.target,
864
- priority: options.priority,
865
- timeoutMs: options.timeoutMs,
866
- causationId: options.causationId,
867
- messageId: options.messageId
868
- });
869
- const result = enqueueMessage(message, "command", options.signal, options.onSettled);
870
- if (typeof result === "string") return result;
871
- return message.id;
872
- },
873
- request(type, payload, options) {
874
- const message = makeMessage(type, "request", payload, {
875
- target: options.target,
876
- priority: options.priority,
877
- timeoutMs: options.timeoutMs,
878
- causationId: options.causationId,
879
- messageId: options.messageId
880
- });
881
- const result = enqueueMessage(message, "request", options.signal, options.onSettled);
882
- if (typeof result === "string") {
883
- return Promise.reject(new Error("MessageBus.request returned a string id unexpectedly"));
884
- }
885
- return result;
886
- },
887
- handle(type, handler, options) {
888
- const concurrency = options?.concurrency ?? 1;
889
- if (!Number.isFinite(concurrency) || !Number.isInteger(concurrency) || concurrency <= 0) {
890
- throw new Error("Handler concurrency must be a positive integer");
891
- }
892
- if (concurrency > MAX_HANDLER_CONCURRENCY) {
893
- throw new Error(
894
- `Handler concurrency must not exceed ${MAX_HANDLER_CONCURRENCY}`
895
- );
896
- }
897
- const record = {
898
- type,
899
- target: options?.target ?? "",
900
- priority: options?.priority ?? 0,
901
- concurrency,
902
- handler
903
- };
904
- if (routedHandlers.has(type)) {
905
- throw new Error(`Handler for "${type}" is already registered`);
906
- }
907
- if (record.target) {
908
- const existing = targetConcurrency.get(record.target);
909
- if (existing !== void 0 && existing !== concurrency) {
910
- throw new Error(`Conflicting concurrency for target "${record.target}"`);
911
- }
912
- targetConcurrency.set(record.target, concurrency);
913
- targetHandlerCount.set(record.target, (targetHandlerCount.get(record.target) ?? 0) + 1);
914
- if (!mailboxes.has(record.target)) {
915
- mailboxes.set(record.target, []);
916
- }
917
- schedulePump(record.target);
918
- }
919
- routedHandlers.set(type, record);
920
- return () => {
921
- if (routedHandlers.get(type) === record) {
922
- routedHandlers.delete(type);
923
- if (record.target) {
924
- const count = (targetHandlerCount.get(record.target) ?? 1) - 1;
925
- if (count <= 0) {
926
- targetHandlerCount.delete(record.target);
927
- targetConcurrency.delete(record.target);
928
- } else {
929
- targetHandlerCount.set(record.target, count);
930
- }
931
- }
932
- }
933
- };
934
- },
935
- snapshot,
936
- onSnapshot(handler) {
937
- snapshotListeners.add(handler);
938
- handler(snapshot());
939
- return () => snapshotListeners.delete(handler);
940
- }
941
- };
942
- return bus;
943
- }
944
-
945
- // src/lifecycle/resourceScope.ts
946
- function makeId(prefix) {
947
- try {
948
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
949
- return `${prefix}:${crypto.randomUUID()}`;
950
- }
951
- } catch {
952
- }
953
- return `${prefix}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
954
- }
955
- function errorMessage2(error) {
956
- if (error instanceof Error) return error.message;
957
- return typeof error === "string" ? error : String(error);
958
- }
959
- function isPositiveFiniteNumber(value) {
960
- return value !== void 0 && Number.isFinite(value) && value >= 0;
961
- }
962
- function createLifecycleScope(options) {
963
- const identity = {
964
- scopeId: options.scopeId ?? makeId(`scope:${options.kind}`),
965
- instanceId: options.instanceId ?? makeId("instance"),
966
- kind: options.kind,
967
- attributes: {},
968
- ...options.metadata
969
- };
970
- const controller = new AbortController();
971
- const revokeListeners = /* @__PURE__ */ new Set();
972
- const entries = /* @__PURE__ */ new Map();
973
- const usedIds = /* @__PURE__ */ new Set();
974
- let currentState = "active";
975
- let revokeReason = "scope revoked";
976
- let disposalPromise;
977
- let refreshPublishedDisposeResult;
978
- const disposeResultNotifier = options.onDisposeResult;
979
- const notifyChange = (scope) => {
980
- try {
981
- options.onChange?.(scope);
982
- } catch {
983
- }
984
- };
985
- const publicScope = {};
986
- function uniqueResourceId(resourceId) {
987
- const base = resourceId && resourceId.length > 0 ? resourceId : makeId("resource");
988
- if (!usedIds.has(base)) {
989
- usedIds.add(base);
990
- return base;
991
- }
992
- let index = 2;
993
- while (usedIds.has(`${base}#${index}`)) index += 1;
994
- const unique2 = `${base}#${index}`;
995
- usedIds.add(unique2);
996
- return unique2;
997
- }
998
- function assertActive() {
999
- if (currentState !== "active") {
1000
- throw new LifecycleScopeRevokedError(
1001
- `Lifecycle scope "${identity.scopeId}" is ${currentState}`
1002
- );
1003
- }
1004
- }
1005
- function addEntry(resourceId, cleanup, state, removeOnFailure = false, phase = "before-teardown") {
1006
- assertActive();
1007
- const entry = {
1008
- resourceId: uniqueResourceId(resourceId),
1009
- state,
1010
- cleanup,
1011
- phase,
1012
- cleanupStarted: false,
1013
- cleanupFinished: false,
1014
- removeOnFailure
1015
- };
1016
- entries.set(entry.resourceId, entry);
1017
- notifyChange(publicScope);
1018
- return entry;
1019
- }
1020
- function releaseEntry(entry, reason) {
1021
- if (entry.cleanupPromise) return entry.cleanupPromise;
1022
- entry.cleanupStarted = true;
1023
- entry.cleanupPromise = Promise.resolve().then(() => entry.cleanup(reason)).then(() => {
1024
- entry.cleanupFinished = true;
1025
- if (!entry.lateReleaseFailed) {
1026
- entry.state = "released";
1027
- entry.error = void 0;
1028
- }
1029
- notifyChange(publicScope);
1030
- }).catch((error) => {
1031
- entry.cleanupFinished = true;
1032
- entry.state = "pending";
1033
- entry.error = errorMessage2(error);
1034
- notifyChange(publicScope);
1035
- throw error;
1036
- });
1037
- entry.cleanupPromise.catch(() => void 0);
1038
- return entry.cleanupPromise;
1039
- }
1040
- function track(resource, release, resourceId) {
1041
- if (currentState !== "active") {
1042
- void Promise.resolve().then(() => release(resource, revokeReason)).catch(() => void 0);
1043
- throw new LifecycleScopeRevokedError();
1044
- }
1045
- let released = false;
1046
- addEntry(
1047
- resourceId,
1048
- async (reason) => {
1049
- if (released) return;
1050
- released = true;
1051
- await release(resource, reason);
1052
- },
1053
- "active"
1054
- );
1055
- return resource;
1056
- }
1057
- function acquire(resourceId, create, release) {
1058
- assertActive();
1059
- let resource;
1060
- let hasResource = false;
1061
- let released = false;
1062
- let resolveAcquisition;
1063
- let rejectAcquisition;
1064
- let acquisitionSettled = false;
1065
- const acquisitionDone = new Promise((resolve, reject) => {
1066
- resolveAcquisition = () => {
1067
- if (acquisitionSettled) return;
1068
- acquisitionSettled = true;
1069
- resolve();
1070
- };
1071
- rejectAcquisition = (error) => {
1072
- if (acquisitionSettled) return;
1073
- acquisitionSettled = true;
1074
- reject(error);
1075
- };
1076
- });
1077
- acquisitionDone.catch(() => void 0);
1078
- const entry = addEntry(
1079
- resourceId,
1080
- async (reason) => {
1081
- await acquisitionDone;
1082
- if (!hasResource || released) return;
1083
- released = true;
1084
- await release(resource, reason);
1085
- },
1086
- "acquiring",
1087
- true,
1088
- "before-teardown"
1089
- );
1090
- entry.acquisitionDone = acquisitionDone;
1091
- return Promise.resolve().then(() => create(controller.signal)).then(async (created) => {
1092
- resource = created;
1093
- hasResource = true;
1094
- if (currentState !== "active" || controller.signal.aborted || entry.cleanupStarted) {
1095
- if (!released) {
1096
- released = true;
1097
- try {
1098
- await release(created, revokeReason);
1099
- } catch (error) {
1100
- entry.lateReleaseFailed = true;
1101
- entry.state = "pending";
1102
- entry.error = errorMessage2(error);
1103
- notifyChange(publicScope);
1104
- rejectAcquisition(error);
1105
- throw error;
1106
- }
1107
- }
1108
- entry.state = "released";
1109
- resolveAcquisition();
1110
- notifyChange(publicScope);
1111
- throw new LifecycleScopeRevokedError(
1112
- `Resource "${entry.resourceId}" completed after scope revoke`
1113
- );
1114
- }
1115
- entry.state = "active";
1116
- entry.removeOnFailure = false;
1117
- resolveAcquisition();
1118
- notifyChange(publicScope);
1119
- return created;
1120
- }).catch((error) => {
1121
- if (!hasResource) {
1122
- resolveAcquisition();
1123
- entries.delete(entry.resourceId);
1124
- usedIds.delete(entry.resourceId);
1125
- }
1126
- throw error;
1127
- });
1128
- }
1129
- function onDispose(cleanup, resourceId, phase = "before-teardown") {
1130
- if (currentState !== "active") {
1131
- void Promise.resolve().then(() => cleanup(revokeReason)).catch(() => void 0);
1132
- return () => void 0;
1133
- }
1134
- const entry = addEntry(resourceId, cleanup, "active", false, phase);
1135
- return () => {
1136
- if (entry.cleanupStarted || entry.cleanupFinished) return;
1137
- entries.delete(entry.resourceId);
1138
- usedIds.delete(entry.resourceId);
1139
- notifyChange(publicScope);
1140
- };
1141
- }
1142
- function onRevoke(listener) {
1143
- if (currentState === "active") {
1144
- revokeListeners.add(listener);
1145
- return () => revokeListeners.delete(listener);
1146
- }
1147
- try {
1148
- listener(revokeReason);
1149
- } catch {
1150
- }
1151
- return () => void 0;
1152
- }
1153
- function revoke(reason = "scope revoked") {
1154
- if (currentState !== "active") return;
1155
- revokeReason = reason;
1156
- currentState = "stopping";
1157
- try {
1158
- controller.abort(new LifecycleScopeRevokedError(reason));
1159
- } catch {
1160
- controller.abort();
1161
- }
1162
- for (const listener of [...revokeListeners]) {
1163
- try {
1164
- listener(reason);
1165
- } catch {
1166
- }
1167
- }
1168
- notifyChange(publicScope);
1169
- }
1170
- async function runEntry(entry, reason, timeoutMs, issues, onLateSuccess, onLateFailure) {
1171
- if (entry.cleanupFinished && entry.state === "released") return "released";
1172
- const cleanup = releaseEntry(entry, reason);
1173
- if (!isPositiveFiniteNumber(timeoutMs)) {
1174
- try {
1175
- await cleanup;
1176
- return entry.state === "released" ? "released" : "pending";
1177
- } catch (error) {
1178
- issues.push({
1179
- resourceId: entry.resourceId,
1180
- code: "lifecycle.cleanup_failed",
1181
- message: errorMessage2(error)
1182
- });
1183
- return "pending";
1184
- }
1185
- }
1186
- let timeoutHandle;
1187
- const timeout = new Promise((resolve) => {
1188
- timeoutHandle = setTimeout(() => resolve("timeout"), timeoutMs);
1189
- });
1190
- const result = await Promise.race([
1191
- cleanup.then(() => "released", (error) => ({ error })),
1192
- timeout
1193
- ]);
1194
- if (timeoutHandle) clearTimeout(timeoutHandle);
1195
- if (result === "timeout") {
1196
- entry.state = "pending";
1197
- issues.push({
1198
- resourceId: entry.resourceId,
1199
- code: "lifecycle.cleanup_timeout",
1200
- message: `Cleanup timed out after ${timeoutMs}ms`
1201
- });
1202
- void cleanup.then(
1203
- () => {
1204
- try {
1205
- onLateSuccess?.(entry);
1206
- } catch {
1207
- }
1208
- },
1209
- (error) => {
1210
- try {
1211
- onLateFailure?.(entry, error);
1212
- } catch {
1213
- }
1214
- }
1215
- );
1216
- return "pending";
1217
- }
1218
- if (result === "released") return "released";
1219
- entry.state = "pending";
1220
- issues.push({
1221
- resourceId: entry.resourceId,
1222
- code: "lifecycle.cleanup_failed",
1223
- message: errorMessage2(result.error)
1224
- });
1225
- return "pending";
1226
- }
1227
- async function dispose(options2 = {}) {
1228
- if (disposalPromise) return disposalPromise;
1229
- revoke(options2.reason ?? "scope disposed");
1230
- const reason = options2.reason ?? revokeReason;
1231
- const entriesToRelease = [...entries.values()].reverse();
1232
- disposalPromise = (async () => {
1233
- const errors = [];
1234
- const pending = /* @__PURE__ */ new Set();
1235
- const lateReleased = /* @__PURE__ */ new Set();
1236
- let released = 0;
1237
- let attempted = 0;
1238
- let resultSnapshot;
1239
- let resultPublished = false;
1240
- const publishDisposeResult = () => {
1241
- if (!resultPublished || !resultSnapshot) return;
1242
- try {
1243
- disposeResultNotifier?.(resultSnapshot);
1244
- } catch {
1245
- }
1246
- };
1247
- const rebuildResultSnapshot = () => {
1248
- if (!resultSnapshot) return;
1249
- const mergedPending = new Set(pending);
1250
- const mergedErrors = [...errors];
1251
- let mergedAttempted = attempted;
1252
- let mergedReleased = released;
1253
- for (const childEntry of entries.values()) {
1254
- const childScope = childEntry.childScope;
1255
- const childResult = childEntry.childResult;
1256
- if (!childScope || !childResult) continue;
1257
- const prefix = `child:${childScope.identity.scopeId}:`;
1258
- mergedAttempted += childResult.attempted;
1259
- mergedReleased += childResult.released;
1260
- for (const resourceId of childResult.pending) {
1261
- mergedPending.add(`${prefix}${resourceId}`);
1262
- }
1263
- for (const issue of childResult.errors) {
1264
- mergedErrors.push({
1265
- ...issue,
1266
- resourceId: `${prefix}${issue.resourceId}`
1267
- });
1268
- }
1269
- }
1270
- resultSnapshot.attempted = mergedAttempted;
1271
- resultSnapshot.released = mergedReleased;
1272
- resultSnapshot.pending = [...mergedPending];
1273
- resultSnapshot.errors = mergedErrors;
1274
- resultSnapshot.cleanupIncomplete = mergedPending.size > 0 || mergedErrors.length > 0;
1275
- };
1276
- const projectChildResult = (entry, childResult, lateResourceId, lateError) => {
1277
- if (!entry.childScope) return;
1278
- const previous = entry.childResult;
1279
- entry.childResult = childResult;
1280
- entry.state = childResult.cleanupIncomplete ? "pending" : "released";
1281
- entry.error = childResult.cleanupIncomplete ? childResult.errors[0]?.message ?? "Child scope cleanup is still pending" : void 0;
1282
- rebuildResultSnapshot();
1283
- publishDisposeResult();
1284
- if (lateResourceId && resultSnapshot) {
1285
- const resourceId = `child:${entry.childScope.identity.scopeId}:${lateResourceId}`;
1286
- try {
1287
- if (lateError !== void 0) options2.onLateFailure?.(resourceId, lateError, resultSnapshot);
1288
- else options2.onLateSuccess?.(resourceId, resultSnapshot);
1289
- } catch {
1290
- }
1291
- } else if (previous?.cleanupIncomplete && !childResult.cleanupIncomplete && resultSnapshot) {
1292
- try {
1293
- options2.onLateSuccess?.(`child:${entry.childScope.identity.scopeId}`, resultSnapshot);
1294
- } catch {
1295
- }
1296
- }
1297
- notifyChange(publicScope);
1298
- };
1299
- refreshPublishedDisposeResult = () => {
1300
- if (!resultPublished || !resultSnapshot) return;
1301
- rebuildResultSnapshot();
1302
- publishDisposeResult();
1303
- };
1304
- const onLateFailure = (entry, error) => {
1305
- entry.state = "pending";
1306
- entry.error = errorMessage2(error);
1307
- if (!errors.some((issue) => issue.resourceId === entry.resourceId && issue.code === "lifecycle.cleanup_failed")) {
1308
- errors.push({
1309
- resourceId: entry.resourceId,
1310
- code: "lifecycle.cleanup_failed",
1311
- message: errorMessage2(error)
1312
- });
1313
- }
1314
- pending.add(entry.resourceId);
1315
- if (resultSnapshot) {
1316
- rebuildResultSnapshot();
1317
- }
1318
- publishDisposeResult();
1319
- try {
1320
- options2.onLateFailure?.(entry.resourceId, error, resultSnapshot);
1321
- } catch {
1322
- }
1323
- notifyChange(publicScope);
1324
- };
1325
- const onLateSuccess = (entry) => {
1326
- if (lateReleased.has(entry.resourceId)) return;
1327
- lateReleased.add(entry.resourceId);
1328
- pending.delete(entry.resourceId);
1329
- released += 1;
1330
- for (let index = errors.length - 1; index >= 0; index -= 1) {
1331
- const issue = errors[index];
1332
- if (issue?.resourceId === entry.resourceId && issue.code === "lifecycle.cleanup_timeout") {
1333
- errors.splice(index, 1);
1334
- }
1335
- }
1336
- if (resultSnapshot) {
1337
- rebuildResultSnapshot();
1338
- }
1339
- publishDisposeResult();
1340
- try {
1341
- options2.onLateSuccess?.(entry.resourceId, resultSnapshot);
1342
- } catch {
1343
- }
1344
- notifyChange(publicScope);
1345
- };
1346
- const releasePhase = async (phase) => {
1347
- for (const entry of entriesToRelease.filter((item) => item.phase === phase)) {
1348
- if (entry.childScope) {
1349
- entry.cleanupStarted = true;
1350
- try {
1351
- const childResult = await entry.childScope.dispose({
1352
- reason,
1353
- timeoutMs: options2.timeoutMs,
1354
- onLateSuccess: (resourceId, result2) => {
1355
- if (result2) projectChildResult(entry, result2, resourceId);
1356
- },
1357
- onLateFailure: (resourceId, error, result2) => {
1358
- if (result2) projectChildResult(entry, result2, resourceId, error);
1359
- }
1360
- });
1361
- entry.cleanupFinished = true;
1362
- projectChildResult(entry, childResult);
1363
- } catch (error) {
1364
- entry.cleanupFinished = true;
1365
- entry.state = "pending";
1366
- entry.error = errorMessage2(error);
1367
- pending.add(entry.resourceId);
1368
- errors.push({
1369
- resourceId: entry.resourceId,
1370
- code: "lifecycle.cleanup_failed",
1371
- message: errorMessage2(error)
1372
- });
1373
- notifyChange(publicScope);
1374
- }
1375
- continue;
1376
- }
1377
- attempted += 1;
1378
- const result = await runEntry(entry, reason, options2.timeoutMs, errors, onLateSuccess, onLateFailure);
1379
- if (result === "released") released += 1;
1380
- else if (!lateReleased.has(entry.resourceId)) pending.add(entry.resourceId);
1381
- }
1382
- };
1383
- await releasePhase("before-teardown");
1384
- if (options2.teardown) {
1385
- const teardownEntry = {
1386
- resourceId: "scope:teardown",
1387
- state: "active",
1388
- cleanup: options2.teardown,
1389
- phase: "before-teardown",
1390
- cleanupStarted: false,
1391
- cleanupFinished: false,
1392
- removeOnFailure: false
1393
- };
1394
- attempted += 1;
1395
- const result = await runEntry(teardownEntry, reason, options2.timeoutMs, errors, onLateSuccess, onLateFailure);
1396
- if (result === "released") released += 1;
1397
- else if (!lateReleased.has(teardownEntry.resourceId)) pending.add(teardownEntry.resourceId);
1398
- }
1399
- await releasePhase("after-teardown");
1400
- currentState = "stopped";
1401
- notifyChange(publicScope);
1402
- resultSnapshot = {
1403
- scopeId: identity.scopeId,
1404
- state: "stopped",
1405
- attempted,
1406
- released,
1407
- pending: [...pending],
1408
- errors,
1409
- cleanupIncomplete: pending.size > 0 || errors.length > 0
1410
- };
1411
- rebuildResultSnapshot();
1412
- resultPublished = true;
1413
- publishDisposeResult();
1414
- return resultSnapshot;
1415
- })();
1416
- return disposalPromise;
1417
- }
1418
- function resources() {
1419
- return [...entries.values()].map((entry) => ({
1420
- resourceId: entry.resourceId,
1421
- state: entry.state,
1422
- ...entry.error ? { error: entry.error } : {}
1423
- }));
1424
- }
1425
- function child(kind, metadata = {}) {
1426
- assertActive();
1427
- let childEntry;
1428
- const childScope = createLifecycleScope({
1429
- kind,
1430
- metadata: {
1431
- ...metadata,
1432
- parentScopeId: identity.scopeId
1433
- },
1434
- // 父作用域的 Host 订阅者也必须看到子作用域资源的状态变化。
1435
- onChange: () => notifyChange(publicScope),
1436
- // 只有子作用域已经生成最终结果后,父级才允许更新/删除登记。
1437
- onDisposeResult: (result) => {
1438
- const entry2 = childEntry;
1439
- if (!entry2) return;
1440
- entry2.childResult = result;
1441
- entry2.state = result.cleanupIncomplete ? "pending" : "released";
1442
- entry2.error = result.cleanupIncomplete ? result.errors[0]?.message ?? "Child scope cleanup is still pending" : void 0;
1443
- refreshPublishedDisposeResult?.();
1444
- if (!result.cleanupIncomplete && !disposalPromise && !entry2.cleanupStarted) {
1445
- entries.delete(entry2.resourceId);
1446
- usedIds.delete(entry2.resourceId);
1447
- }
1448
- notifyChange(publicScope);
1449
- }
1450
- });
1451
- const removeRevoke = onRevoke((reason) => childScope.revoke(reason));
1452
- const entry = addEntry(`child:${childScope.identity.scopeId}`, async () => void 0, "active");
1453
- childEntry = entry;
1454
- entry.childScope = childScope;
1455
- childScope.onDispose(() => {
1456
- removeRevoke();
1457
- }, `parent-link:${identity.scopeId}`);
1458
- return childScope;
1459
- }
1460
- Object.assign(publicScope, {
1461
- identity,
1462
- signal: controller.signal,
1463
- onRevoke,
1464
- onDispose,
1465
- track,
1466
- acquire,
1467
- child,
1468
- revoke,
1469
- dispose,
1470
- assertActive,
1471
- resources
1472
- });
1473
- Object.defineProperty(publicScope, "state", {
1474
- enumerable: true,
1475
- configurable: false,
1476
- get: () => currentState
1477
- });
1478
- return publicScope;
1479
- }
1480
- var createResourceScope = createLifecycleScope;
1481
-
1482
- // src/lifecycle/scopedMessageBus.ts
1483
- function mergeSignals(scopeSignal, requestSignal) {
1484
- if (!requestSignal) return { signal: scopeSignal, dispose: () => void 0 };
1485
- if (scopeSignal.aborted) {
1486
- const controller2 = new AbortController();
1487
- controller2.abort(scopeSignal.reason);
1488
- return { signal: controller2.signal, dispose: () => void 0 };
1489
- }
1490
- if (requestSignal.aborted) {
1491
- const controller2 = new AbortController();
1492
- controller2.abort(requestSignal.reason);
1493
- return { signal: controller2.signal, dispose: () => void 0 };
1494
- }
1495
- const controller = new AbortController();
1496
- const abortFrom = (source) => {
1497
- try {
1498
- controller.abort(source.reason);
1499
- } catch {
1500
- controller.abort();
1501
- }
1502
- };
1503
- const onScopeAbort = () => abortFrom(scopeSignal);
1504
- const onRequestAbort = () => abortFrom(requestSignal);
1505
- scopeSignal.addEventListener("abort", onScopeAbort, { once: true });
1506
- requestSignal.addEventListener("abort", onRequestAbort, { once: true });
1507
- return {
1508
- signal: controller.signal,
1509
- dispose: () => {
1510
- scopeSignal.removeEventListener("abort", onScopeAbort);
1511
- requestSignal.removeEventListener("abort", onRequestAbort);
1512
- }
1513
- };
1514
- }
1515
- function withScopeCleanup(scope, cleanup) {
1516
- let active = true;
1517
- let removeRevoke = () => void 0;
1518
- let removeDispose = () => void 0;
1519
- const runCleanup = () => {
1520
- if (!active) return;
1521
- active = false;
1522
- removeRevoke();
1523
- removeDispose();
1524
- cleanup();
1525
- };
1526
- removeRevoke = scope.onRevoke(runCleanup);
1527
- removeDispose = scope.onDispose(runCleanup, "message-bus-registration");
1528
- return () => {
1529
- if (!active) return;
1530
- active = false;
1531
- removeRevoke();
1532
- removeDispose();
1533
- cleanup();
1534
- };
1535
- }
1536
- function createScopedMessageBus(base, scope) {
1537
- return {
1538
- publish(type, payload, options) {
1539
- scope.assertActive();
1540
- return base.publish(type, payload, options);
1541
- },
1542
- subscribe(type, handler) {
1543
- scope.assertActive();
1544
- const unsubscribe = base.subscribe(type, (payload) => {
1545
- if (scope.state !== "active") return;
1546
- handler(payload);
1547
- });
1548
- return withScopeCleanup(scope, unsubscribe);
1549
- },
1550
- dispatch(type, payload, options) {
1551
- scope.assertActive();
1552
- const merged = mergeSignals(scope.signal, options.signal);
1553
- const cleanup = withScopeCleanup(scope, merged.dispose);
1554
- try {
1555
- return base.dispatch(type, payload, {
1556
- ...options,
1557
- signal: merged.signal,
1558
- onSettled: () => {
1559
- cleanup();
1560
- options.onSettled?.();
1561
- }
1562
- });
1563
- } catch (error) {
1564
- cleanup();
1565
- throw error;
1566
- }
1567
- },
1568
- request(type, payload, options) {
1569
- scope.assertActive();
1570
- const merged = mergeSignals(scope.signal, options.signal);
1571
- const cleanup = withScopeCleanup(scope, merged.dispose);
1572
- try {
1573
- const request = base.request(type, payload, {
1574
- ...options,
1575
- signal: merged.signal,
1576
- onSettled: () => {
1577
- cleanup();
1578
- options.onSettled?.();
1579
- }
1580
- });
1581
- return request.finally(cleanup);
1582
- } catch (error) {
1583
- cleanup();
1584
- return Promise.reject(error);
1585
- }
1586
- },
1587
- handle(type, handler, options) {
1588
- scope.assertActive();
1589
- const scopedHandler = (message) => {
1590
- if (scope.state !== "active") {
1591
- throw new LifecycleScopeRevokedError(
1592
- `Lifecycle scope "${scope.identity.scopeId}" is ${scope.state}`
1593
- );
1594
- }
1595
- const merged = mergeSignals(scope.signal, message.signal);
1596
- try {
1597
- const result = handler({ ...message, signal: merged.signal });
1598
- if (result && typeof result.then === "function") {
1599
- return Promise.resolve(result).finally(merged.dispose);
1600
- }
1601
- merged.dispose();
1602
- return result;
1603
- } catch (error) {
1604
- merged.dispose();
1605
- throw error;
1606
- }
1607
- };
1608
- const unregister = base.handle(type, scopedHandler, options);
1609
- return withScopeCleanup(scope, unregister);
1610
- },
1611
- snapshot() {
1612
- return base.snapshot();
1613
- },
1614
- onSnapshot(handler) {
1615
- scope.assertActive();
1616
- const unsubscribe = base.onSnapshot(handler);
1617
- return withScopeCleanup(scope, unsubscribe);
1618
- }
1619
- };
1620
- }
1621
-
1622
- // src/lifecycle/permissionLease.ts
1623
- function uniquePermissions(permissions) {
1624
- return [...new Set(permissions ?? [])];
1625
- }
1626
- function sameBindingValue(actual, expected) {
1627
- return expected === void 0 || actual === expected;
1628
- }
1629
- function sameAttributes(actual, expected) {
1630
- if (expected === void 0) return true;
1631
- return Object.keys(expected).every((key) => Object.is(actual[key], expected[key]));
1632
- }
1633
- function normalizedRevision(value, name) {
1634
- if (value === void 0) return void 0;
1635
- if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${name} must be a non-negative safe integer`);
1636
- return value;
1637
- }
1638
- function createPermissionLease(options) {
1639
- const binding = {
1640
- ...options.identity,
1641
- requested: Object.freeze(uniquePermissions(options.requested)),
1642
- approved: Object.freeze(uniquePermissions(options.approved)),
1643
- ...options.sessionConstraints ? { sessionConstraints: Object.freeze(uniquePermissions(options.sessionConstraints)) } : {},
1644
- ...options.policyRevision !== void 0 ? { policyRevision: normalizedRevision(options.policyRevision, "policyRevision") } : {},
1645
- ...options.grantRevision !== void 0 ? { grantRevision: normalizedRevision(options.grantRevision, "grantRevision") } : {},
1646
- ...options.grantId !== void 0 ? { grantId: options.grantId } : {}
1647
- };
1648
- Object.freeze(binding);
1649
- const granted = new Set(
1650
- binding.requested.filter(
1651
- (permission) => binding.approved.includes(permission) && (binding.sessionConstraints === void 0 || binding.sessionConstraints.includes(permission))
1652
- )
1653
- );
1654
- let revoked = false;
1655
- let revokeReason = "permission lease revoked";
1656
- const lease = {
1657
- binding,
1658
- get revoked() {
1659
- return revoked || options.scope !== void 0 && options.scope.state !== "active";
1660
- },
1661
- has(permission) {
1662
- return !lease.revoked && granted.has(permission);
1663
- },
1664
- assert(permission) {
1665
- if (lease.revoked) {
1666
- throw new PermissionLeaseRevokedError(revokeReason);
1667
- }
1668
- if (!granted.has(permission)) {
1669
- throw new PermissionDeniedError(permission);
1670
- }
1671
- },
1672
- assertBinding(expected) {
1673
- if (!sameBindingValue(binding.pluginId, expected.pluginId) || !sameBindingValue(binding.instanceId, expected.instanceId) || !sameAttributes(binding.attributes, expected.attributes) || !sameBindingValue(binding.policyRevision, expected.policyRevision) || !sameBindingValue(binding.grantRevision, expected.grantRevision) || !sameBindingValue(binding.grantId, expected.grantId)) {
1674
- throw new PermissionLeaseRevokedError("Permission lease identity does not match");
1675
- }
1676
- if (lease.revoked) throw new PermissionLeaseRevokedError(revokeReason);
1677
- },
1678
- revoke(reason = "permission lease revoked") {
1679
- if (revoked) return;
1680
- revoked = true;
1681
- revokeReason = reason;
1682
- }
1683
- };
1684
- if (options.scope) {
1685
- options.scope.onRevoke((reason) => lease.revoke(reason));
1686
- options.scope.onDispose((reason) => lease.revoke(reason), "permission-lease");
1687
- }
1688
- return lease;
1689
- }
1690
-
1691
- // src/lifecycle/taskScheduler.ts
1692
- function errorMessage3(error) {
1693
- return error instanceof Error ? error.message : String(error);
1694
- }
1695
- function createScopedTaskScheduler(scope, options = {}) {
1696
- const tasks = /* @__PURE__ */ new Map();
1697
- const listeners = /* @__PURE__ */ new Set();
1698
- let disposed = false;
1699
- const notify = () => {
1700
- const value = [...tasks.values()].map((task) => ({
1701
- id: task.definition.id,
1702
- pluginId: task.definition.pluginId ?? scope.identity.pluginId ?? "unknown",
1703
- label: task.definition.label,
1704
- state: task.state,
1705
- ...task.error ? { error: task.error } : {},
1706
- ...task.lastCompletedAt ? { lastCompletedAt: task.lastCompletedAt } : {},
1707
- ...task.nextRunAt ? { nextRunAt: task.nextRunAt } : {}
1708
- }));
1709
- for (const listener of [...listeners]) {
1710
- try {
1711
- listener(value);
1712
- } catch {
1713
- }
1714
- }
1715
- };
1716
- const clearTimer = (task) => {
1717
- if (task.timer !== void 0) {
1718
- clearTimeout(task.timer);
1719
- task.timer = void 0;
1720
- }
1721
- task.nextRunAt = void 0;
1722
- };
1723
- const schedule = (task) => {
1724
- clearTimer(task);
1725
- const intervalMs = task.definition.intervalMs;
1726
- if (!task.active || disposed || intervalMs === void 0) {
1727
- notify();
1728
- return;
1729
- }
1730
- if (!Number.isFinite(intervalMs) || intervalMs < 0) {
1731
- task.error = "\u4EFB\u52A1 intervalMs \u5FC5\u987B\u662F\u975E\u8D1F\u6709\u9650\u6570";
1732
- task.state = "failed";
1733
- notify();
1734
- return;
1735
- }
1736
- const dueAt = Date.now() + intervalMs;
1737
- task.nextRunAt = new Date(dueAt).toISOString();
1738
- task.timer = setTimeout(() => {
1739
- task.timer = void 0;
1740
- task.nextRunAt = void 0;
1741
- void runTask(task, "interval");
1742
- }, intervalMs);
1743
- notify();
1744
- };
1745
- const runTask = async (task, reason) => {
1746
- if (!task.active || disposed || scope.state !== "active") return;
1747
- if (task.runPromise) {
1748
- task.rerunRequested = true;
1749
- return task.runPromise;
1750
- }
1751
- task.state = "queued";
1752
- task.error = void 0;
1753
- notify();
1754
- const run = (async () => {
1755
- let requestScope;
1756
- let controller;
1757
- let abortFromScope;
1758
- try {
1759
- requestScope = scope.child("request");
1760
- controller = new AbortController();
1761
- task.controller = controller;
1762
- abortFromScope = () => controller?.abort(scope.signal.reason);
1763
- if (scope.signal.aborted) abortFromScope();
1764
- else scope.signal.addEventListener("abort", abortFromScope, { once: true });
1765
- task.state = "running";
1766
- notify();
1767
- await task.definition.run({ signal: controller.signal, reason });
1768
- if (!controller.signal.aborted && !requestScope.signal.aborted) {
1769
- task.state = "idle";
1770
- task.error = void 0;
1771
- task.lastCompletedAt = (/* @__PURE__ */ new Date()).toISOString();
1772
- } else {
1773
- task.state = "idle";
1774
- }
1775
- } catch (error) {
1776
- task.state = "failed";
1777
- task.error = controller?.signal.aborted ? void 0 : errorMessage3(error);
1778
- } finally {
1779
- if (abortFromScope) scope.signal.removeEventListener("abort", abortFromScope);
1780
- task.controller = void 0;
1781
- if (requestScope) {
1782
- await requestScope.dispose({ reason: `task ${task.definition.id} finished` });
1783
- }
1784
- if (task.active && !disposed && scope.state === "active") schedule(task);
1785
- else clearTimer(task);
1786
- notify();
1787
- }
1788
- })();
1789
- task.runPromise = run;
1790
- try {
1791
- await run;
1792
- } finally {
1793
- task.runPromise = void 0;
1794
- notify();
1795
- if (task.rerunRequested && task.active && !disposed && scope.state === "active") {
1796
- task.rerunRequested = false;
1797
- queueMicrotask(() => {
1798
- void runTask(task, "coalesced");
1799
- });
1800
- }
1801
- }
1802
- };
1803
- const releaseTask = async (task, reason) => {
1804
- if (!task.active) return;
1805
- task.active = false;
1806
- task.rerunRequested = false;
1807
- clearTimer(task);
1808
- task.controller?.abort(reason);
1809
- if (task.runPromise) await task.runPromise;
1810
- tasks.delete(task.definition.id);
1811
- notify();
1812
- };
1813
- const scheduler = {
1814
- register(definition) {
1815
- scope.assertActive();
1816
- if (!definition.id || tasks.has(definition.id)) {
1817
- throw new Error(`Scoped task id "${definition.id}" is already registered or empty`);
1818
- }
1819
- const task = {
1820
- definition: { ...definition },
1821
- state: "idle",
1822
- rerunRequested: false,
1823
- active: true
1824
- };
1825
- tasks.set(definition.id, task);
1826
- const removeScopeCleanup = scope.onDispose(
1827
- (reason) => releaseTask(task, reason),
1828
- `task:${definition.id}`
1829
- );
1830
- const unregister = () => {
1831
- if (!task.active) return;
1832
- removeScopeCleanup();
1833
- void releaseTask(task, "task unregistered");
1834
- };
1835
- schedule(task);
1836
- if (options.runOnRegister) void runTask(task, "initial");
1837
- return unregister;
1838
- },
1839
- async runNow(id, reason = "manual") {
1840
- const task = tasks.get(id);
1841
- if (!task) throw new Error(`Scoped task "${id}" is not registered`);
1842
- await runTask(task, reason);
1843
- },
1844
- async cancel(id) {
1845
- const task = tasks.get(id);
1846
- if (!task) return;
1847
- task.rerunRequested = false;
1848
- task.controller?.abort("task canceled");
1849
- if (task.runPromise) await task.runPromise;
1850
- },
1851
- snapshot() {
1852
- return [...tasks.values()].map((task) => ({
1853
- id: task.definition.id,
1854
- pluginId: task.definition.pluginId ?? scope.identity.pluginId ?? "unknown",
1855
- label: task.definition.label,
1856
- state: task.state,
1857
- ...task.error ? { error: task.error } : {},
1858
- ...task.lastCompletedAt ? { lastCompletedAt: task.lastCompletedAt } : {},
1859
- ...task.nextRunAt ? { nextRunAt: task.nextRunAt } : {}
1860
- }));
1861
- },
1862
- subscribe(listener) {
1863
- listeners.add(listener);
1864
- listener(scheduler.snapshot());
1865
- return () => listeners.delete(listener);
1866
- },
1867
- async dispose() {
1868
- if (disposed) return;
1869
- disposed = true;
1870
- for (const task of [...tasks.values()]) {
1871
- task.controller?.abort("task scheduler disposed");
1872
- clearTimer(task);
1873
- }
1874
- await Promise.all([...tasks.values()].map((task) => releaseTask(task, "task scheduler disposed")));
1875
- listeners.clear();
1876
- }
1877
- };
1878
- return scheduler;
1879
- }
1880
-
1881
- // src/resources/resourceRegistry.ts
1882
- function createResourceRegistry() {
1883
- const definitions = /* @__PURE__ */ new Map();
1884
- function registerOwned(ownerId, definition) {
1885
- if (definitions.has(definition.id)) {
1886
- throw new Error(
1887
- `Resource definition "${definition.id}" is already registered`
1888
- );
1889
- }
1890
- const owned = Object.assign({}, definition);
1891
- Object.defineProperty(owned, RESOURCE_OWNER, {
1892
- value: ownerId,
1893
- enumerable: false,
1894
- writable: false,
1895
- configurable: false
1896
- });
1897
- definitions.set(definition.id, owned);
1898
- }
1899
- return {
1900
- _registerOwned: registerOwned,
1901
- register(definition) {
1902
- registerOwned("", definition);
1903
- },
1904
- unregister(id) {
1905
- definitions.delete(id);
1906
- },
1907
- get(id) {
1908
- return definitions.get(id);
1909
- },
1910
- /** 获取所有已注册的资源定义 id(用于 ownership 快照) */
1911
- _ids() {
1912
- return Array.from(definitions.keys());
1913
- }
1914
- };
1915
- }
1916
- function registerOwnedResource(registry, ownerId, definition) {
1917
- const internal = registry;
1918
- if (!internal._registerOwned) {
1919
- registry.register(definition);
1920
- return;
1921
- }
1922
- internal._registerOwned(ownerId, definition);
1923
- }
1924
-
1925
- // src/resources/resourceStore.ts
1926
- function createContext(ownerId, getCapability, getAttributes) {
1927
- return {
1928
- getCapability,
1929
- attributes: getAttributes(ownerId),
1930
- ownerId
1931
- };
1932
- }
1933
- function recordKey(definitionId, key) {
1934
- return `${definitionId}::${key.join("::")}`;
1935
- }
1936
- function defaultEquals(a, b) {
1937
- return Object.is(a, b);
1938
- }
1939
- function createResourceStore(registry, getCapability, getAttributes = () => ({})) {
1940
- const records = /* @__PURE__ */ new Map();
1941
- const microtaskQueue = /* @__PURE__ */ new Map();
1942
- const contextSubscribers = /* @__PURE__ */ new Set();
1943
- let microtaskScheduled = false;
1944
- const notify = (record) => {
1945
- for (const subscriber of [...record.subscribers]) {
1946
- try {
1947
- subscriber();
1948
- } catch {
1949
- }
1950
- }
1951
- };
1952
- const notifyContext = () => {
1953
- for (const subscriber of [...contextSubscribers]) {
1954
- try {
1955
- subscriber();
1956
- } catch {
1957
- }
1958
- }
1959
- };
1960
- const cleanupRecord = (record) => {
1961
- record.abortController?.abort();
1962
- record.providerUnsubscribe?.();
1963
- record.providerUnsubscribe = null;
1964
- };
1965
- const contextFor = (definition) => createContext(
1966
- definition[RESOURCE_OWNER] ?? "__unowned__",
1967
- getCapability,
1968
- getAttributes
1969
- );
1970
- const getOrCreateRecord = (definition, args) => {
1971
- const context = contextFor(definition);
1972
- const key = definition.key(args, context);
1973
- const keyString = recordKey(definition.id, key);
1974
- const current = records.get(keyString);
1975
- if (current) {
1976
- if (definition.subscribe && !current.providerUnsubscribe) {
1977
- current.providerUnsubscribe = definition.subscribe(args, context, () => {
1978
- scheduleInvalidation(definition.id, args);
1979
- });
1980
- }
1981
- return current;
1982
- }
1983
- const record = {
1984
- snapshot: { key, status: "pending", data: void 0, revision: 0 },
1985
- inFlight: null,
1986
- abortController: null,
1987
- subscribers: /* @__PURE__ */ new Set(),
1988
- invalidationScheduled: false,
1989
- loadRevision: 0,
1990
- owner: definition[RESOURCE_OWNER] ?? "__unowned__",
1991
- providerUnsubscribe: null
1992
- };
1993
- records.set(keyString, record);
1994
- if (definition.subscribe) {
1995
- record.providerUnsubscribe = definition.subscribe(args, context, () => {
1996
- scheduleInvalidation(definition.id, args);
1997
- });
1998
- }
1999
- return record;
2000
- };
2001
- const loadResource = (definition, args, record) => {
2002
- record.abortController?.abort();
2003
- const context = contextFor(definition);
2004
- const key = definition.key(args, context);
2005
- if (record.snapshot.key.join("::") !== key.join("::")) return;
2006
- const abortController = new AbortController();
2007
- const loadRevision = ++record.loadRevision;
2008
- record.abortController = abortController;
2009
- try {
2010
- record.inFlight = Promise.resolve(definition.load(args, context, abortController.signal));
2011
- } catch (error) {
2012
- record.inFlight = Promise.reject(error);
2013
- }
2014
- record.snapshot = { ...record.snapshot, status: "pending", revision: record.snapshot.revision + 1 };
2015
- notify(record);
2016
- record.inFlight.then((data) => {
2017
- if (abortController.signal.aborted || record.loadRevision !== loadRevision) return;
2018
- if (record.snapshot.key.join("::") !== key.join("::")) return;
2019
- const equals = definition.equals ?? defaultEquals;
2020
- const changed = !equals(record.snapshot.data, data);
2021
- record.snapshot = {
2022
- key,
2023
- status: "ready",
2024
- data,
2025
- revision: changed ? record.snapshot.revision + 1 : record.snapshot.revision
2026
- };
2027
- record.inFlight = null;
2028
- record.abortController = null;
2029
- if (changed) notify(record);
2030
- }).catch((error) => {
2031
- if (abortController.signal.aborted || record.loadRevision !== loadRevision) return;
2032
- const blocked = error instanceof Error && error.message === "blocked";
2033
- const errorValue = error instanceof Error ? error : new Error(String(error));
2034
- record.snapshot = blocked ? { ...record.snapshot, status: "blocked", revision: record.snapshot.revision + 1 } : {
2035
- ...record.snapshot,
2036
- status: record.snapshot.data === void 0 ? "error" : "stale",
2037
- error: {
2038
- code: typeof errorValue.code === "string" ? String(errorValue.code) : "resource.load_failed",
2039
- message: errorValue.message
2040
- },
2041
- revision: record.snapshot.revision + 1
2042
- };
2043
- record.inFlight = null;
2044
- record.abortController = null;
2045
- notify(record);
2046
- });
2047
- };
2048
- const invalidateNow = (definitionId, args) => {
2049
- const definition = registry.get(definitionId);
2050
- if (!definition) return;
2051
- const context = contextFor(definition);
2052
- const key = definition.key(args, context);
2053
- const record = records.get(recordKey(definitionId, key));
2054
- if (!record) return;
2055
- record.snapshot = { ...record.snapshot, status: "stale", revision: record.snapshot.revision + 1 };
2056
- notify(record);
2057
- loadResource(definition, args, record);
2058
- };
2059
- const flushInvalidations = () => {
2060
- const queue = [...microtaskQueue.values()];
2061
- microtaskQueue.clear();
2062
- microtaskScheduled = false;
2063
- for (const item of queue) invalidateNow(item.definitionId, item.args);
2064
- };
2065
- const scheduleInvalidation = (definitionId, args) => {
2066
- const definition = registry.get(definitionId);
2067
- if (!definition) return;
2068
- if (definition.invalidation === "immediate") {
2069
- invalidateNow(definitionId, args);
2070
- return;
2071
- }
2072
- const key = recordKey(definitionId, definition.key(args, contextFor(definition)));
2073
- if (microtaskQueue.has(key)) return;
2074
- microtaskQueue.set(key, { definitionId, args });
2075
- if (!microtaskScheduled) {
2076
- microtaskScheduled = true;
2077
- queueMicrotask(flushInvalidations);
2078
- }
2079
- };
2080
- const refreshRuntimeBindings = () => {
2081
- for (const record of records.values()) cleanupRecord(record);
2082
- records.clear();
2083
- notifyContext();
2084
- };
2085
- return {
2086
- ensure(definitionId, args) {
2087
- const definition = registry.get(definitionId);
2088
- if (!definition) throw new Error(`Resource definition "${definitionId}" not found`);
2089
- const record = getOrCreateRecord(definition, args);
2090
- if (!record.inFlight && record.snapshot.status === "pending") loadResource(definition, args, record);
2091
- return record.snapshot;
2092
- },
2093
- subscribe(definitionId, args, callback) {
2094
- const definition = registry.get(definitionId);
2095
- if (!definition) return () => void 0;
2096
- let record = getOrCreateRecord(definition, args);
2097
- record.subscribers.add(callback);
2098
- const removeContext = definition.scope === "context" ? (() => {
2099
- const listener = () => {
2100
- record.subscribers.delete(callback);
2101
- record = getOrCreateRecord(definition, args);
2102
- record.subscribers.add(callback);
2103
- callback();
2104
- };
2105
- contextSubscribers.add(listener);
2106
- return () => contextSubscribers.delete(listener);
2107
- })() : void 0;
2108
- return () => {
2109
- removeContext?.();
2110
- record.subscribers.delete(callback);
2111
- if (record.subscribers.size === 0 && record.providerUnsubscribe) {
2112
- record.providerUnsubscribe();
2113
- record.providerUnsubscribe = null;
2114
- }
2115
- if (record.subscribers.size === 0 && record.abortController) {
2116
- const current = record;
2117
- setTimeout(() => {
2118
- if (current.subscribers.size === 0 && current.abortController) {
2119
- current.abortController.abort();
2120
- current.abortController = null;
2121
- current.inFlight = null;
2122
- }
2123
- }, 100);
2124
- }
2125
- };
2126
- },
2127
- read(definitionId, args) {
2128
- const definition = registry.get(definitionId);
2129
- if (!definition) return void 0;
2130
- const key = definition.key(args, contextFor(definition));
2131
- return records.get(recordKey(definitionId, key))?.snapshot;
2132
- },
2133
- invalidate: scheduleInvalidation,
2134
- disposeOwner(ownerId) {
2135
- for (const [key, record] of records) {
2136
- if (record.owner !== ownerId) continue;
2137
- cleanupRecord(record);
2138
- records.delete(key);
2139
- }
2140
- },
2141
- refreshRuntimeBindings,
2142
- subscribeContext(callback) {
2143
- contextSubscribers.add(callback);
2144
- return () => contextSubscribers.delete(callback);
2145
- }
2146
- };
2147
- }
2148
-
2149
- // src/host/createPluginHost.ts
2150
- function createInMemoryPluginConfigStore(initial = {}, readOnly = false) {
2151
- const values = /* @__PURE__ */ new Map();
2152
- for (const [id, value] of Object.entries(initial)) {
2153
- if (typeof value === "boolean") values.set(id, value);
2154
- }
2155
- const listeners = /* @__PURE__ */ new Set();
2156
- const snapshot = () => Object.freeze(Object.fromEntries(values));
2157
- return {
2158
- read: snapshot,
2159
- setEnabled(pluginId, enabled) {
2160
- if (readOnly) return;
2161
- if (values.get(pluginId) === enabled) return;
2162
- values.set(pluginId, enabled);
2163
- const next = snapshot();
2164
- for (const listener of [...listeners]) listener(next);
2165
- },
2166
- subscribe(listener) {
2167
- listeners.add(listener);
2168
- return () => listeners.delete(listener);
2169
- }
2170
- };
2171
- }
2172
- function errorMessage4(error) {
2173
- if (error instanceof Error) return error.message;
2174
- if (error && typeof error === "object" && "message" in error) {
2175
- const message = error.message;
2176
- if (typeof message === "string") return message;
2177
- }
2178
- return String(error);
2179
- }
2180
- function makeInstanceId(pluginId, unitId) {
2181
- try {
2182
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
2183
- return `${pluginId}:${unitId}:${crypto.randomUUID()}`;
2184
- }
2185
- } catch {
2186
- }
2187
- return `${pluginId}:${unitId}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
2188
- }
2189
- function lifecycleStateFor(state, desired) {
2190
- switch (state) {
2191
- case "starting":
2192
- return "starting";
2193
- case "stopping":
2194
- return "stopping";
2195
- case "enabled":
2196
- return "running";
2197
- case "blocked":
2198
- return "waiting";
2199
- case "error-disabled":
2200
- case "cleanup-pending":
2201
- return "failed";
2202
- case "unknown":
2203
- return desired ? "waiting" : "disabled";
2204
- case "disabled":
2205
- return "disabled";
2206
- case "registered":
2207
- return desired ? "waiting" : "disabled";
2208
- }
2209
- }
2210
- function selectedUnit(manifest, runtime) {
2211
- const units = manifest.units ?? [];
2212
- if (units.length > 1) {
2213
- const matches = runtime === void 0 ? [] : units.filter(
2214
- (unit) => unit.runtime === runtime
2215
- );
2216
- return matches.length === 1 ? matches[0] : void 0;
2217
- }
2218
- if (units.length === 1) {
2219
- const unit = units[0];
2220
- return unit && (runtime === void 0 || unit.runtime === runtime) && unit.runtime !== void 0 ? unit : void 0;
2221
- }
2222
- const targetRuntime = runtime ?? "window-main";
2223
- return {
2224
- id: manifest.id,
2225
- runtime: targetRuntime,
2226
- dependencies: manifest.dependencies?.map((dependency) => ({
2227
- capability: dependency.capability,
2228
- contractVersion: dependency.contractVersion ?? `${dependency.capability}.v1`,
2229
- sourceRuntime: dependency.sourceRuntime ?? targetRuntime,
2230
- ...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
2231
- ...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
2232
- })),
2233
- provides: manifest.provides,
2234
- permissions: manifest.permissions,
2235
- config: manifest.config,
2236
- contribution: manifest.contribution
2237
- };
2238
- }
2239
- function dependenciesFor(manifest, runtime) {
2240
- return dependenciesOfManifest(manifest, runtime);
2241
- }
2242
- function setupFor(manifest, unit, options) {
2243
- return options.runtimeUnitImplementationRegistry?.get(manifest.id, unit.id);
2244
- }
2245
- var StartupCapabilityError = class extends Error {
2246
- details;
2247
- constructor(details, phase = "startup") {
2248
- super(`Startup prerequisite unavailable: ${details.map((item) => item.capability).join(", ")} (${phase})`);
2249
- this.name = "StartupCapabilityError";
2250
- this.details = details;
2251
- }
2252
- };
2253
- var StartupPluginError = class extends Error {
2254
- details;
2255
- constructor(details) {
2256
- super(`Startup plugin failed: ${details.pluginId}`);
2257
- this.name = "StartupPluginError";
2258
- this.details = details;
2259
- }
2260
- };
2261
- function createPluginHost(options = {}) {
2262
- const runtimeKind = options.runtime;
2263
- const listeners = /* @__PURE__ */ new Set();
2264
- let versionCounter = 0;
2265
- let hostDisposed = false;
2266
- let disposePromise;
2267
- let runtimeSnapshots = options.runtimeSnapshots;
2268
- const bumpVersion = () => {
2269
- versionCounter += 1;
2270
- for (const listener of [...listeners]) {
2271
- try {
2272
- listener({ version: versionCounter });
2273
- } catch {
2274
- }
2275
- }
2276
- };
2277
- const rootScope = createLifecycleScope({
2278
- kind: "root",
2279
- metadata: { attributes: Object.freeze({ ...options.rootAttributes ?? {} }) },
2280
- onChange: bumpVersion
2281
- });
2282
- const taskScheduler = createScopedTaskScheduler(rootScope);
2283
- const capabilities = createCapabilityRegistry();
2284
- const messageBus = options.messageBus ?? createMessageBus();
2285
- const resourceRegistry = options.resourceRegistry ?? createResourceRegistry();
2286
- let rootAttributes = Object.freeze({ ...options.rootAttributes ?? {} });
2287
- const instanceAttributes = /* @__PURE__ */ new Map();
2288
- const resourceStore = createResourceStore(resourceRegistry, (key) => capabilities.has(key) ? capabilities.get(key) : void 0, (ownerId) => instanceAttributes.get(ownerId ?? "") ?? rootAttributes);
2289
- const injectCapabilities = (source) => {
2290
- if (!source) return;
2291
- const entries = source instanceof Map ? source.entries() : Object.entries(source);
2292
- for (const [key, value] of entries) capabilities.provide(key, value);
2293
- };
2294
- injectCapabilities(options.capabilities);
2295
- injectCapabilities(options.builtinCapabilities);
2296
- if (!capabilities.has(RUNTIME_MESSAGE_BUS)) capabilities.provide(RUNTIME_MESSAGE_BUS, messageBus);
2297
- if (!capabilities.has(RESOURCE_REGISTRY_CAPABILITY)) capabilities.provide(RESOURCE_REGISTRY_CAPABILITY, resourceRegistry);
2298
- if (!capabilities.has(SCOPED_TASK_SCHEDULER_CAPABILITY)) capabilities.provide(SCOPED_TASK_SCHEDULER_CAPABILITY, taskScheduler);
2299
- const configStore = options.configStore ?? createInMemoryPluginConfigStore(options.initialPluginConfig, false);
2300
- const knownManifests = /* @__PURE__ */ new Map();
2301
- const records = /* @__PURE__ */ new Map();
2302
- const enabledSet = /* @__PURE__ */ new Set();
2303
- const starting = /* @__PURE__ */ new Map();
2304
- const stopping = /* @__PURE__ */ new Map();
2305
- const internalConfigWrites = /* @__PURE__ */ new Set();
2306
- let intentSnapshot = options.pluginIntentCoordinator?.snapshot();
2307
- let removeConfigSubscription = () => void 0;
2308
- let removeIntentSubscription = () => void 0;
2309
- let reconcilePromise;
2310
- const desiredEnabledFor = (pluginId, manifest) => {
2311
- if (manifest?.meta.startup === "required" || manifest?.meta.canDisable === false) return true;
2312
- if (intentSnapshot && Object.prototype.hasOwnProperty.call(intentSnapshot.desiredEnabled, pluginId)) {
2313
- return intentSnapshot.desiredEnabled[pluginId] === true;
2314
- }
2315
- return configStore.read()[pluginId] ?? manifest?.meta.defaultEnabled ?? false;
2316
- };
2317
- const desiredRevisionFor = (pluginId) => intentSnapshot?.desiredRevision[pluginId];
2318
- const selected = (manifest) => selectedUnit(manifest, runtimeKind);
2319
- const graph = () => buildPluginGraph([
2320
- ...knownManifests.values()
2321
- ], { runtime: runtimeKind, enabledPluginIds: enabledSet });
2322
- const validateManifest = (manifest) => {
2323
- if (!manifest || typeof manifest.id !== "string" || manifest.id.trim() === "") throw new Error("Plugin id must be a non-empty string");
2324
- if (typeof manifest.name !== "string" || manifest.name.trim() === "") throw new Error(`Plugin "${manifest.id}" name must be a non-empty string`);
2325
- if (!manifest.meta || typeof manifest.meta.defaultEnabled !== "boolean" || typeof manifest.meta.canDisable !== "boolean") {
2326
- throw new Error(`Plugin "${manifest.id}" meta must define defaultEnabled and canDisable`);
2327
- }
2328
- if (manifest.meta.startup === "required" && (manifest.meta.canDisable || !manifest.meta.defaultEnabled)) {
2329
- throw new Error(`Plugin "${manifest.id}" required startup metadata is inconsistent`);
2330
- }
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
- }
2335
- if (manifest.units && manifest.units.length > 0) {
2336
- const ids = /* @__PURE__ */ new Set();
2337
- for (const unit of manifest.units) {
2338
- if (!unit.id || ids.has(unit.id)) throw new Error(`Plugin "${manifest.id}" has duplicate or empty unit id`);
2339
- ids.add(unit.id);
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
- }
2343
- }
2344
- }
2345
- options.manifestValidator?.(manifest);
2346
- };
2347
- const isRequired = (manifest) => manifest.meta.startup === "required" || manifest.meta.canDisable === false;
2348
- const missingDependencies = (manifest) => {
2349
- const missing = [];
2350
- const selectedUnitForDependencies = selected(manifest);
2351
- const localRuntime = selectedUnitForDependencies?.runtime ?? runtimeKind;
2352
- for (const dependency of dependenciesFor(manifest, runtimeKind)) {
2353
- if (dependency.optional) continue;
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
- }
2362
- }
2363
- return [...new Set(missing)];
2364
- };
2365
- const runtimeUnavailable = (manifest) => {
2366
- const unit = selected(manifest);
2367
- if (!unit) {
2368
- const units = manifest.units ?? [];
2369
- if (units.length > 0 && runtimeKind !== void 0 && !units.some((item) => item.runtime === runtimeKind)) {
2370
- const snapshots = runtimeSnapshots?.() ?? [];
2371
- if (snapshots.some((item) => item.pluginId === manifest.id)) return `runtime:${manifest.id}:unknown`;
2372
- }
2373
- return units.length > 0 ? `runtime:${manifest.id}:unit-unavailable` : void 0;
2374
- }
2375
- const unavailable = options.runtimeUnitAvailability?.({
2376
- pluginId: manifest.id,
2377
- unitId: unit.id,
2378
- runtime: unit.runtime
2379
- });
2380
- if (unavailable) return unavailable;
2381
- if (!setupFor(manifest, unit, options)) return `runtime:${manifest.id}:${unit.id}:implementation-unavailable`;
2382
- return void 0;
2383
- };
2384
- const revokeContributions = (record) => {
2385
- for (const contribution of record.contributions) {
2386
- if (!contribution.active) continue;
2387
- contribution.active = false;
2388
- try {
2389
- contribution.revoke();
2390
- } catch {
2391
- }
2392
- }
2393
- };
2394
- const releaseContributions = async (record) => {
2395
- let firstError;
2396
- for (const contribution of [...record.contributions].reverse()) {
2397
- try {
2398
- await contribution.dispose();
2399
- } catch (error) {
2400
- firstError ??= error;
2401
- }
2402
- }
2403
- record.contributions = [];
2404
- if (firstError) throw firstError;
2405
- };
2406
- const registerContribution = async (record, unit, instanceId, scope) => {
2407
- const contribution = unit.contribution;
2408
- if (contribution === void 0 || !options.contributionAdapters) return;
2409
- for (const adapter of options.contributionAdapters) {
2410
- const result = await adapter.register({
2411
- pluginId: record.manifest.id,
2412
- unitId: unit.id,
2413
- instanceId,
2414
- scope,
2415
- contribution,
2416
- manifest: record.manifest
2417
- });
2418
- if (!result) continue;
2419
- if (typeof result === "function") {
2420
- let active = true;
2421
- let cleanupPromise;
2422
- const dispose = async () => {
2423
- if (cleanupPromise) return cleanupPromise;
2424
- if (!active) return;
2425
- active = false;
2426
- cleanupPromise = Promise.resolve(result());
2427
- await cleanupPromise;
2428
- };
2429
- record.contributions.push({ active: true, revoke: () => {
2430
- void dispose().catch(() => void 0);
2431
- }, dispose });
2432
- } else {
2433
- const handle = result;
2434
- let cleanupPromise;
2435
- record.contributions.push({
2436
- active: true,
2437
- revoke: () => {
2438
- try {
2439
- handle.revoke?.();
2440
- } catch {
2441
- }
2442
- if (!cleanupPromise) cleanupPromise = Promise.resolve(handle.dispose?.());
2443
- cleanupPromise.catch(() => void 0);
2444
- },
2445
- dispose: async () => {
2446
- if (cleanupPromise) return cleanupPromise;
2447
- cleanupPromise = Promise.resolve(handle.dispose?.());
2448
- return cleanupPromise;
2449
- }
2450
- });
2451
- }
2452
- }
2453
- };
2454
- const buildContext = (record, unit, scope, instanceId) => {
2455
- const requested = [...new Set(unit.permissions ?? [])];
2456
- const policy = options.permissionPolicy?.({
2457
- pluginId: record.manifest.id,
2458
- unitId: unit.id,
2459
- // Host 的实例令牌和 Scope 原语的内部 instanceId 是两个生成点;
2460
- // PermissionLease 必须绑定 Context 对外暴露的实例令牌。
2461
- identity: { ...scope.identity, instanceId },
2462
- requested
2463
- }) ?? { approved: requested };
2464
- const permissionLease = createPermissionLease({
2465
- identity: { ...scope.identity, instanceId },
2466
- requested,
2467
- approved: policy.approved ?? requested,
2468
- sessionConstraints: policy.sessionConstraints,
2469
- ...policy.binding ?? {},
2470
- scope
2471
- });
2472
- const extension = Object.freeze({
2473
- ...options.contextExtension?.({
2474
- pluginId: record.manifest.id,
2475
- unitId: unit.id,
2476
- instanceId,
2477
- scope,
2478
- manifest: record.manifest
2479
- }) ?? {}
2480
- });
2481
- const config = unit.config === void 0 ? void 0 : Object.freeze({ ...unit.config });
2482
- const contextTaskScheduler = createScopedTaskScheduler(scope);
2483
- const scopedMessageBus = createScopedMessageBus(messageBus, scope);
2484
- const ownedResourceIds = /* @__PURE__ */ new Set();
2485
- let resourceDefinitionsRevoked = false;
2486
- const revokeResourceDefinitions = () => {
2487
- if (resourceDefinitionsRevoked) return;
2488
- resourceDefinitionsRevoked = true;
2489
- resourceStore.disposeOwner(instanceId);
2490
- for (const resourceId of ownedResourceIds) {
2491
- if (resourceRegistry.get(resourceId)) resourceRegistry.unregister(resourceId);
2492
- }
2493
- ownedResourceIds.clear();
2494
- };
2495
- const scopedResourceRegistry = {
2496
- register(definition) {
2497
- scope.assertActive();
2498
- registerOwnedResource(resourceRegistry, instanceId, definition);
2499
- ownedResourceIds.add(definition.id);
2500
- },
2501
- unregister(resourceId) {
2502
- scope.assertActive();
2503
- if (!ownedResourceIds.has(resourceId)) {
2504
- throw new Error(`Resource definition "${resourceId}" is not owned by plugin instance "${instanceId}"`);
2505
- }
2506
- resourceRegistry.unregister(resourceId);
2507
- ownedResourceIds.delete(resourceId);
2508
- },
2509
- get(resourceId) {
2510
- return resourceRegistry.get(resourceId);
2511
- },
2512
- _ids() {
2513
- return [...ownedResourceIds];
2514
- }
2515
- };
2516
- scope.onRevoke(revokeResourceDefinitions);
2517
- scope.onDispose(revokeResourceDefinitions, `resource-definitions:${record.manifest.id}`, "after-teardown");
2518
- scope.onDispose(() => contextTaskScheduler.dispose(), `task-scheduler:${record.manifest.id}`);
2519
- const context = {
2520
- pluginId: record.manifest.id,
2521
- instanceId,
2522
- unitId: unit.id,
2523
- scope,
2524
- signal: scope.signal,
2525
- permissions: Object.freeze([...permissionLease.binding.requested.filter((permission) => permissionLease.has(permission))]),
2526
- permissionLease,
2527
- serviceBridge: options.serviceBridgeForPlugin?.(record.manifest.id, instanceId),
2528
- taskScheduler: contextTaskScheduler,
2529
- extension,
2530
- config,
2531
- onDispose(cleanup) {
2532
- scope.onDispose(cleanup, `plugin-dispose:${record.manifest.id}`);
2533
- record.disposeCallbacks.push(cleanup);
2534
- },
2535
- provide(key, value) {
2536
- scope.assertActive();
2537
- capabilities.provide(key, value);
2538
- record.capabilities.add(key);
2539
- },
2540
- get(key) {
2541
- if (key === RUNTIME_MESSAGE_BUS) return scopedMessageBus;
2542
- if (key === RESOURCE_REGISTRY_CAPABILITY) return scopedResourceRegistry;
2543
- return capabilities.get(key);
2544
- },
2545
- has(key) {
2546
- return capabilities.has(key);
2547
- },
2548
- require(key) {
2549
- capabilities.require(key);
2550
- },
2551
- messageBus: scopedMessageBus
2552
- };
2553
- return context;
2554
- };
2555
- const beginStop = (record, reason, preserveIntent, blockedBy) => {
2556
- if (record.state === "stopping" || record.state === "cleanup-pending") return;
2557
- const wasStarting = record.state === "starting";
2558
- record.state = "stopping";
2559
- record.error = void 0;
2560
- record.blockedBy = void 0;
2561
- record.stopRequested = reason;
2562
- record.preserveIntentOnStop = preserveIntent;
2563
- record.scope?.revoke(reason);
2564
- revokeContributions(record);
2565
- for (const capability of record.capabilities) capabilities.revoke(capability);
2566
- record.capabilities.clear();
2567
- enabledSet.delete(record.manifest.id);
2568
- if (wasStarting) record.pendingDesiredEnabled = desiredEnabledFor(record.manifest.id, record.manifest);
2569
- if (blockedBy) record.blockedBy = [...blockedBy];
2570
- resourceStore.disposeOwner(record.instanceId ?? record.manifest.id);
2571
- bumpVersion();
2572
- };
2573
- const finishStop = async (record, reason) => {
2574
- const scope = record.scope;
2575
- const preserveIntent = preserveIntentOf(record);
2576
- let teardownError;
2577
- const projectLateCleanup = (result, error2) => {
2578
- if (result) record.cleanup = result;
2579
- if (error2 !== void 0) {
2580
- record.error = errorMessage4(error2);
2581
- bumpVersion();
2582
- return;
2583
- }
2584
- if (!result || result.cleanupIncomplete || record.state !== "cleanup-pending") return;
2585
- const desired2 = record.pendingDesiredEnabled ?? desiredEnabledFor(record.manifest.id, record.manifest);
2586
- const missing2 = missingDependencies(record.manifest);
2587
- const unavailable2 = runtimeUnavailable(record.manifest);
2588
- record.state = desired2 && (missing2.length > 0 || unavailable2) ? "blocked" : "disabled";
2589
- record.blockedBy = record.state === "blocked" ? [.../* @__PURE__ */ new Set([...missing2, ...unavailable2 ? [unavailable2] : []])] : void 0;
2590
- record.error = void 0;
2591
- bumpVersion();
2592
- if (desired2 && record.state === "disabled" && !hostDisposed) {
2593
- queueMicrotask(() => {
2594
- void enable(record.manifest.id).catch(() => void 0);
2595
- });
2596
- }
2597
- };
2598
- const cleanup = scope ? await scope.dispose({
2599
- reason,
2600
- timeoutMs: options.lifecycleCleanupTimeoutMs,
2601
- teardown: async () => {
2602
- try {
2603
- if (record.teardown) await record.teardown();
2604
- await releaseContributions(record);
2605
- } catch (error2) {
2606
- teardownError = error2;
2607
- throw error2;
2608
- }
2609
- },
2610
- onLateSuccess: (_resourceId, result) => projectLateCleanup(result),
2611
- onLateFailure: (_resourceId, error2, result) => projectLateCleanup(result, error2)
2612
- }) : void 0;
2613
- record.cleanup = cleanup;
2614
- record.scope = void 0;
2615
- if (record.instanceId) instanceAttributes.delete(record.instanceId);
2616
- record.instanceId = void 0;
2617
- record.unitId = void 0;
2618
- record.teardown = void 0;
2619
- record.stopRequested = void 0;
2620
- record.disposeCallbacks = [];
2621
- const desired = record.pendingDesiredEnabled ?? desiredEnabledFor(record.manifest.id, record.manifest);
2622
- record.pendingDesiredEnabled = void 0;
2623
- const missing = missingDependencies(record.manifest);
2624
- const unavailable = runtimeUnavailable(record.manifest);
2625
- const incomplete = Boolean(cleanup?.pending.length && cleanup.errors.some((item) => item.code === "lifecycle.cleanup_timeout"));
2626
- const error = teardownError ?? cleanup?.errors.find((item) => item.code !== "lifecycle.cleanup_timeout");
2627
- if (incomplete) {
2628
- record.state = "cleanup-pending";
2629
- record.error = errorMessage4(error ?? "Plugin cleanup is still pending");
2630
- } else if (error || cleanup?.cleanupIncomplete) {
2631
- record.state = "error-disabled";
2632
- record.error = errorMessage4(error ?? "Plugin cleanup did not complete");
2633
- } else if (desired && (missing.length > 0 || unavailable)) {
2634
- record.state = "blocked";
2635
- record.blockedBy = [.../* @__PURE__ */ new Set([...missing, ...unavailable ? [unavailable] : []])];
2636
- } else {
2637
- record.state = "disabled";
2638
- record.error = void 0;
2639
- record.blockedBy = void 0;
2640
- }
2641
- if (!preserveIntent && !desired) {
2642
- internalConfigWrites.add(record.manifest.id);
2643
- try {
2644
- configStore.setEnabled(record.manifest.id, false);
2645
- } finally {
2646
- internalConfigWrites.delete(record.manifest.id);
2647
- }
2648
- }
2649
- record.preserveIntentOnStop = void 0;
2650
- bumpVersion();
2651
- if (desired && record.state === "disabled" && !hostDisposed) {
2652
- queueMicrotask(() => {
2653
- void enable(record.manifest.id).catch(() => void 0);
2654
- });
2655
- }
2656
- };
2657
- const preserveIntentOf = (record) => record.preserveIntentOnStop === true;
2658
- const stopPlugin = async (record, reason, preserveIntent, blockedBy) => {
2659
- const existing = stopping.get(record.manifest.id);
2660
- if (existing) return existing;
2661
- beginStop(record, reason, preserveIntent, blockedBy);
2662
- const start = starting.get(record.manifest.id);
2663
- const promise = (start ? start.catch(() => void 0) : Promise.resolve()).then(() => finishStop(record, reason)).finally(() => stopping.delete(record.manifest.id));
2664
- stopping.set(record.manifest.id, promise);
2665
- return promise;
2666
- };
2667
- const enable = async (pluginId) => {
2668
- if (hostDisposed) throw new LifecycleScopeRevokedError("Plugin host is disposed");
2669
- const record = records.get(pluginId);
2670
- if (!record) throw new Error(`Plugin "${pluginId}" is not registered`);
2671
- if (!options.pluginIntentCoordinator) {
2672
- internalConfigWrites.add(pluginId);
2673
- try {
2674
- configStore.setEnabled(pluginId, true);
2675
- } finally {
2676
- internalConfigWrites.delete(pluginId);
2677
- }
2678
- }
2679
- record.pendingDesiredEnabled = true;
2680
- const existingStop = stopping.get(pluginId);
2681
- if (existingStop) {
2682
- await existingStop;
2683
- }
2684
- if (record.state === "enabled") return;
2685
- const existing = starting.get(pluginId);
2686
- if (existing) return existing;
2687
- record.stopRequested = void 0;
2688
- if (record.state === "cleanup-pending") {
2689
- throw new Error(`Plugin "${pluginId}" cannot start while cleanup is pending`);
2690
- }
2691
- if (record.state === "error-disabled") {
2692
- record.error = void 0;
2693
- record.cleanup = void 0;
2694
- }
2695
- const task = (async () => {
2696
- const missing = missingDependencies(record.manifest);
2697
- const unavailable = runtimeUnavailable(record.manifest);
2698
- if (missing.length > 0 || unavailable) {
2699
- record.state = "blocked";
2700
- record.blockedBy = [.../* @__PURE__ */ new Set([...missing, ...unavailable ? [unavailable] : []])];
2701
- bumpVersion();
2702
- return;
2703
- }
2704
- const unit = selected(record.manifest);
2705
- if (!unit) {
2706
- record.state = "blocked";
2707
- record.blockedBy = [`runtime:${record.manifest.id}:unit-unavailable`];
2708
- bumpVersion();
2709
- return;
2710
- }
2711
- const instanceId = makeInstanceId(record.manifest.id, unit.id);
2712
- let scope;
2713
- try {
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 });
2730
- } catch (error) {
2731
- record.state = "blocked";
2732
- record.blockedBy = [errorMessage4(error)];
2733
- bumpVersion();
2734
- return;
2735
- }
2736
- record.state = "starting";
2737
- record.scope = scope;
2738
- record.instanceId = instanceId;
2739
- record.unitId = unit.id;
2740
- instanceAttributes.set(instanceId, scope.identity.attributes);
2741
- record.error = void 0;
2742
- record.blockedBy = void 0;
2743
- bumpVersion();
2744
- try {
2745
- const setup = setupFor(record.manifest, unit, options);
2746
- if (!setup) throw new Error(`Runtime implementation is unavailable for ${record.manifest.id}/${unit.id}`);
2747
- const context = buildContext(record, unit, scope, instanceId);
2748
- const result = await setup(context);
2749
- record.teardown = typeof result === "function" ? result : void 0;
2750
- await registerContribution(record, unit, instanceId, scope);
2751
- const declared = providesOfManifest(record.manifest, runtimeKind);
2752
- const missingDeclarations = declared.filter((key) => !record.capabilities.has(key));
2753
- if (missingDeclarations.length > 0) {
2754
- throw new Error(`Plugin "${record.manifest.id}" did not provide declared capabilities: ${missingDeclarations.join(", ")}`);
2755
- }
2756
- if (record.stopRequested || scope.state !== "active" || !desiredEnabledFor(record.manifest.id, record.manifest)) {
2757
- return;
2758
- }
2759
- enabledSet.add(record.manifest.id);
2760
- record.state = "enabled";
2761
- record.pendingDesiredEnabled = void 0;
2762
- bumpVersion();
2763
- queueMicrotask(() => {
2764
- void reconcile().catch(() => void 0);
2765
- });
2766
- } catch (error) {
2767
- if (record.stopRequested || scope.state !== "active") {
2768
- return;
2769
- }
2770
- record.state = "error-disabled";
2771
- record.error = errorMessage4(error);
2772
- record.scope?.revoke("plugin setup failed");
2773
- revokeContributions(record);
2774
- for (const capability of record.capabilities) capabilities.revoke(capability);
2775
- record.capabilities.clear();
2776
- record.cleanup = await scope.dispose({ reason: "plugin setup failed", timeoutMs: options.lifecycleCleanupTimeoutMs });
2777
- record.scope = void 0;
2778
- instanceAttributes.delete(instanceId);
2779
- record.instanceId = void 0;
2780
- record.unitId = void 0;
2781
- bumpVersion();
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
- });
2789
- }
2790
- })();
2791
- starting.set(pluginId, task);
2792
- try {
2793
- await task;
2794
- } finally {
2795
- starting.delete(pluginId);
2796
- }
2797
- };
2798
- const collectDisablePlan = (pluginId) => {
2799
- const plan = [];
2800
- const visited = /* @__PURE__ */ new Set();
2801
- const currentGraph = graph();
2802
- const activePluginIds = /* @__PURE__ */ new Set([
2803
- ...enabledSet,
2804
- ...[...records.values()].filter((record) => record.state === "starting" || record.state === "stopping").map((record) => record.manifest.id)
2805
- ]);
2806
- const visit = (providerId) => {
2807
- for (const dependent of reverseDependentsOf(currentGraph, providerId, activePluginIds)) {
2808
- if (visited.has(dependent.pluginId)) continue;
2809
- visited.add(dependent.pluginId);
2810
- visit(dependent.pluginId);
2811
- const dependentRecord = records.get(dependent.pluginId);
2812
- if (dependentRecord) plan.push(dependentRecord);
2813
- }
2814
- };
2815
- visit(pluginId);
2816
- const target = records.get(pluginId);
2817
- if (target) plan.push(target);
2818
- return plan;
2819
- };
2820
- const reconcile = async () => {
2821
- if (hostDisposed) return;
2822
- if (reconcilePromise) return reconcilePromise;
2823
- reconcilePromise = (async () => {
2824
- const manifests = [...knownManifests.values()];
2825
- const ordered = orderManifestsByDependencies(manifests, runtimeKind);
2826
- for (const manifest of ordered) {
2827
- const record = records.get(manifest.id);
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
- }
2841
- if (desiredEnabledFor(manifest.id, manifest) && record.state !== "enabled" && record.state !== "starting") {
2842
- try {
2843
- await enable(manifest.id);
2844
- } catch (error) {
2845
- if (!isRequired(manifest)) continue;
2846
- throw error;
2847
- }
2848
- }
2849
- }
2850
- for (const manifest of manifests) {
2851
- const record = records.get(manifest.id);
2852
- if (record && !desiredEnabledFor(manifest.id, manifest) && (record.state === "enabled" || record.state === "starting")) {
2853
- await stopPlugin(record, "desired intent disabled", false);
2854
- }
2855
- }
2856
- })().finally(() => {
2857
- reconcilePromise = void 0;
2858
- });
2859
- return reconcilePromise;
2860
- };
2861
- function orderManifestsByDependencies(manifests, runtime) {
2862
- const byId = new Map(manifests.map((manifest) => [manifest.id, manifest]));
2863
- const providers = /* @__PURE__ */ new Map();
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
- }
2869
- const visited = /* @__PURE__ */ new Set();
2870
- const visiting = /* @__PURE__ */ new Set();
2871
- const ordered = [];
2872
- const visit = (manifest) => {
2873
- if (visited.has(manifest.id)) return;
2874
- if (visiting.has(manifest.id)) return;
2875
- visiting.add(manifest.id);
2876
- for (const dependency of dependenciesFor(manifest, runtime)) {
2877
- if (dependency.optional) continue;
2878
- const provider = providers.get(dependency.capability);
2879
- const providerManifest = provider ? byId.get(provider) : void 0;
2880
- if (providerManifest) visit(providerManifest);
2881
- }
2882
- visiting.delete(manifest.id);
2883
- visited.add(manifest.id);
2884
- ordered.push(manifest);
2885
- };
2886
- for (const manifest of manifests) visit(manifest);
2887
- return ordered;
2888
- }
2889
- const host = {
2890
- capabilities,
2891
- messageBus,
2892
- resourceRegistry,
2893
- resourceStore,
2894
- rootScope,
2895
- taskScheduler,
2896
- installed: () => [...knownManifests.keys()],
2897
- manifests: () => [...knownManifests.keys()],
2898
- state(pluginId) {
2899
- const record = records.get(pluginId);
2900
- if (!record) return { id: pluginId, kind: "disabled", lifecycleState: "disabled" };
2901
- const desired = desiredEnabledFor(pluginId, record.manifest);
2902
- const unit = selected(record.manifest);
2903
- return {
2904
- id: pluginId,
2905
- kind: record.state,
2906
- lifecycleState: lifecycleStateFor(record.state, desired),
2907
- ...record.error ? { error: record.error } : {},
2908
- desiredEnabled: desired,
2909
- ...desiredRevisionFor(pluginId) !== void 0 ? { desiredRevision: desiredRevisionFor(pluginId) } : {},
2910
- ...record.instanceId ? { instanceId: record.instanceId } : {},
2911
- ...record.unitId ? { unitId: record.unitId } : {},
2912
- ...record.blockedBy ? { blockedBy: [...record.blockedBy] } : {},
2913
- ...record.cleanup ? { cleanup: record.cleanup } : {},
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
- }] : []
2922
- };
2923
- },
2924
- scope: (pluginId) => records.get(pluginId)?.scope,
2925
- refreshRuntimeUnitSnapshots() {
2926
- runtimeSnapshots = options.runtimeSnapshots;
2927
- bumpVersion();
2928
- },
2929
- reconcile,
2930
- graph,
2931
- version: () => versionCounter,
2932
- subscribe(listener) {
2933
- listeners.add(listener);
2934
- return () => listeners.delete(listener);
2935
- },
2936
- getManifest: (pluginId) => knownManifests.get(pluginId),
2937
- reverseDeps(pluginId) {
2938
- return graph().reverse[pluginId] ?? [];
2939
- },
2940
- validateManifestSet(manifests) {
2941
- for (const manifest of manifests) validateManifest(manifest);
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
- });
2950
- },
2951
- provide(key, value) {
2952
- if (hostDisposed) throw new LifecycleScopeRevokedError("Plugin host is disposed");
2953
- capabilities.provide(key, value);
2954
- bumpVersion();
2955
- },
2956
- register: async (manifest) => {
2957
- if (hostDisposed) throw new LifecycleScopeRevokedError("Plugin host is disposed");
2958
- validateManifest(manifest);
2959
- if (knownManifests.has(manifest.id)) throw new Error(`Plugin "${manifest.id}" is already registered`);
2960
- knownManifests.set(manifest.id, manifest);
2961
- records.set(manifest.id, { manifest, state: "registered", disposeCallbacks: [], capabilities: /* @__PURE__ */ new Set(), contributions: [] });
2962
- bumpVersion();
2963
- if (desiredEnabledFor(manifest.id, manifest)) await enable(manifest.id);
2964
- },
2965
- registerAll: async (manifests) => {
2966
- if (hostDisposed) throw new LifecycleScopeRevokedError("Plugin host is disposed");
2967
- const current = [...manifests];
2968
- for (const manifest of current) {
2969
- validateManifest(manifest);
2970
- if (knownManifests.has(manifest.id)) throw new Error(`Plugin "${manifest.id}" is already registered`);
2971
- }
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
- });
2981
- for (const manifest of current) {
2982
- knownManifests.set(manifest.id, manifest);
2983
- records.set(manifest.id, { manifest, state: "registered", disposeCallbacks: [], capabilities: /* @__PURE__ */ new Set(), contributions: [] });
2984
- }
2985
- bumpVersion();
2986
- await reconcile();
2987
- },
2988
- enable,
2989
- retry: async (pluginId) => {
2990
- const record = records.get(pluginId);
2991
- if (!record) throw new Error(`Plugin "${pluginId}" is not registered`);
2992
- record.error = void 0;
2993
- record.cleanup = void 0;
2994
- record.state = "registered";
2995
- await enable(pluginId);
2996
- },
2997
- submitIntent: async (pluginId, desired) => {
2998
- if (!knownManifests.has(pluginId)) throw new Error(`Plugin "${pluginId}" is not registered`);
2999
- if (options.pluginIntentCoordinator) {
3000
- const current = options.pluginIntentCoordinator.snapshot();
3001
- const command = {
3002
- commandId: `plugin-intent:${pluginId}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`,
3003
- authorityInstanceId: options.pluginIntentCoordinator.authorityInstanceId,
3004
- expectedRevision: current.revision,
3005
- pluginId,
3006
- desiredEnabled: desired
3007
- };
3008
- return options.pluginIntentCoordinator.submit(command);
3009
- }
3010
- internalConfigWrites.add(pluginId);
3011
- try {
3012
- configStore.setEnabled(pluginId, desired);
3013
- } finally {
3014
- internalConfigWrites.delete(pluginId);
3015
- }
3016
- if (desired) await enable(pluginId);
3017
- else await host.disable(pluginId);
3018
- const next = Object.freeze({
3019
- revision: 0,
3020
- desiredEnabled: { ...configStore.read() },
3021
- desiredRevision: {}
3022
- });
3023
- return { status: "accepted", commandId: `local:${pluginId}`, snapshot: next, persisted: true };
3024
- },
3025
- disable: async (pluginId) => {
3026
- const record = records.get(pluginId);
3027
- if (!record) throw new Error(`Plugin "${pluginId}" is not registered`);
3028
- if (isRequired(record.manifest)) return { ok: false, reason: `Plugin "${pluginId}" cannot be disabled` };
3029
- if (!options.pluginIntentCoordinator) {
3030
- internalConfigWrites.add(pluginId);
3031
- try {
3032
- configStore.setEnabled(pluginId, false);
3033
- } finally {
3034
- internalConfigWrites.delete(pluginId);
3035
- }
3036
- }
3037
- const plan = collectDisablePlan(pluginId);
3038
- for (const item of plan) beginStop(
3039
- item,
3040
- item.manifest.id === pluginId ? "plugin disabled" : `dependency ${pluginId} disabled`,
3041
- item.manifest.id !== pluginId,
3042
- item.manifest.id === pluginId ? void 0 : [pluginId]
3043
- );
3044
- for (const item of plan) await stopPlugin(
3045
- item,
3046
- item.manifest.id === pluginId ? "plugin disabled" : `dependency ${pluginId} disabled`,
3047
- item.manifest.id !== pluginId,
3048
- item.manifest.id === pluginId ? void 0 : [pluginId]
3049
- );
3050
- return { ok: true };
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
- },
3059
- unregister: async (pluginId) => {
3060
- const record = records.get(pluginId);
3061
- if (!record) return;
3062
- if (isRequired(record.manifest)) throw new Error(`Plugin "${pluginId}" cannot be unregistered`);
3063
- await host.disable(pluginId);
3064
- if (record.state === "cleanup-pending") throw new Error(`Plugin "${pluginId}" cleanup is still pending`);
3065
- records.delete(pluginId);
3066
- knownManifests.delete(pluginId);
3067
- bumpVersion();
3068
- },
3069
- dispose: (reason = "plugin host disposed") => {
3070
- if (disposePromise) return disposePromise;
3071
- hostDisposed = true;
3072
- removeConfigSubscription();
3073
- removeIntentSubscription();
3074
- disposePromise = (async () => {
3075
- for (const record of [...records.values()].reverse()) {
3076
- if (record.state === "enabled" || record.state === "starting") {
3077
- await stopPlugin(record, reason, false);
3078
- }
3079
- }
3080
- return rootScope.dispose({ reason, timeoutMs: options.lifecycleCleanupTimeoutMs });
3081
- })();
3082
- return disposePromise;
3083
- },
3084
- assertCapabilities(required, extra = {}) {
3085
- const details = [];
3086
- for (const capability of required) {
3087
- if (capabilities.has(capability)) continue;
3088
- const provider = graph().providers?.[capability]?.[0];
3089
- const providerRecord = provider ? records.get(provider) : void 0;
3090
- details.push({
3091
- capability,
3092
- providerPluginId: provider,
3093
- providerState: providerRecord?.state,
3094
- providerError: providerRecord?.error,
3095
- configuredEnabled: providerRecord ? desiredEnabledFor(providerRecord.manifest.id, providerRecord.manifest) : void 0
3096
- });
3097
- }
3098
- if (details.length > 0) throw new StartupCapabilityError(details, extra.phase ?? "startup");
3099
- }
3100
- };
3101
- if (options.pluginIntentCoordinator) {
3102
- removeIntentSubscription = options.pluginIntentCoordinator.subscribe((snapshot) => {
3103
- intentSnapshot = snapshot;
3104
- void reconcile().catch(() => void 0);
3105
- bumpVersion();
3106
- });
3107
- }
3108
- removeConfigSubscription = configStore.subscribe((snapshot) => {
3109
- if (internalConfigWrites.size > 0) return;
3110
- for (const [pluginId, manifest] of knownManifests) {
3111
- if (manifest.meta.startup !== "required" && manifest.meta.canDisable !== false) continue;
3112
- if (snapshot[pluginId] === true) continue;
3113
- internalConfigWrites.add(pluginId);
3114
- try {
3115
- configStore.setEnabled(pluginId, true);
3116
- } finally {
3117
- internalConfigWrites.delete(pluginId);
3118
- }
3119
- }
3120
- void reconcile().catch(() => void 0);
3121
- bumpVersion();
3122
- });
3123
- return host;
3124
- }
3125
-
3126
- // src/host/runtimeUnitImplementationRegistry.ts
3127
- function implementationKey(pluginId, unitId) {
3128
- return `${pluginId}\0${unitId}`;
3129
- }
3130
- function createRuntimeUnitImplementationRegistry(implementations = []) {
3131
- const entries = /* @__PURE__ */ new Map();
3132
- const register = (implementation) => {
3133
- const key = implementationKey(implementation.pluginId, implementation.unitId);
3134
- if (entries.has(key)) throw new Error(`\u8FD0\u884C\u5355\u5143\u5B9E\u73B0\u91CD\u590D\u6CE8\u518C: ${implementation.pluginId}/${implementation.unitId}`);
3135
- entries.set(key, implementation.setup);
3136
- };
3137
- for (const implementation of implementations) register(implementation);
3138
- return {
3139
- get(pluginId, unitId) {
3140
- return entries.get(implementationKey(pluginId, unitId));
3141
- },
3142
- register,
3143
- unregister(pluginId, unitId) {
3144
- entries.delete(implementationKey(pluginId, unitId));
3145
- }
3146
- };
3147
- }
3148
-
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;
3159
- }
3160
- };
3161
- var RuntimeUnavailableError = class extends Error {
3162
- code = "transport_unavailable";
3163
- constructor(message = "Runtime is unavailable") {
3164
- super(message);
3165
- this.name = "RuntimeUnavailableError";
3166
- }
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;
3175
- }
3176
- function isNonEmptyString(input) {
3177
- return typeof input === "string" && input.length > 0;
3178
- }
3179
- function isRuntimeKind2(input) {
3180
- return input === "window-main" || input === "shared-worker";
3181
- }
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";
3184
- }
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);
3189
- }
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
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) {
3215
- return {
3216
- pluginId: state.pluginId,
3217
- unitId: state.unitId,
3218
- runtime,
3219
- ...state.instanceId !== void 0 ? { instanceId: state.instanceId } : {},
3220
- state: state.kind
3221
- };
3222
- }
3223
-
3224
- // src/transport/serviceBridge.ts
3225
- function stableAttributes(value) {
3226
- const normalize = (input) => {
3227
- if (Array.isArray(input)) return input.map(normalize);
3228
- if (!input || typeof input !== "object") return input;
3229
- return Object.fromEntries(
3230
- Object.entries(input).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, normalize(item)])
3231
- );
3232
- };
3233
- return JSON.stringify(normalize(value));
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
- }
3278
- function referenceKey(reference) {
3279
- return [
3280
- reference.capabilityId,
3281
- reference.contractVersion,
3282
- reference.runtime,
3283
- reference.runtimeInstanceId,
3284
- reference.serviceInstanceId,
3285
- reference.status,
3286
- stableAttributes(reference.attributes),
3287
- reference.grantId ?? "null",
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
3298
- ].join("\0");
3299
- }
3300
- function lookupMatches(reference, lookup) {
3301
- return reference.capabilityId === lookup.capabilityId && reference.contractVersion === lookup.contractVersion && (lookup.runtime === void 0 || reference.runtime === lookup.runtime);
3302
- }
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;
3356
- const proxies = /* @__PURE__ */ new Set();
3357
- const listeners = /* @__PURE__ */ new Set();
3358
- const notify = () => {
3359
- for (const listener of [...listeners]) {
3360
- try {
3361
- listener();
3362
- } catch {
3363
- }
3364
- }
3365
- };
3366
- const revokeProxy = (record, reason, code = "service_revoked") => {
3367
- if (record.revoked) return;
3368
- record.revoked = true;
3369
- record.reason = reason;
3370
- try {
3371
- record.controller.abort(new RemoteServiceError(code, reason));
3372
- } catch {
3373
- record.controller.abort();
3374
- }
3375
- };
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;
3397
- }
3398
- return void 0;
3399
- };
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
- });
3439
- };
3440
- const bridge = {
3441
- get state() {
3442
- return currentState;
3443
- },
3444
- get runtimeInstanceId() {
3445
- return currentRuntimeInstanceId;
3446
- },
3447
- get defaultCallTimeoutMs() {
3448
- return defaultCallTimeoutMs;
3449
- },
3450
- applySnapshot(snapshot) {
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 };
3454
- }
3455
- if (!isValidSnapshot(snapshot)) {
3456
- return { accepted: false, reason: "invalid-snapshot", receivedRevision: snapshot.revision };
3457
- }
3458
- if (currentRuntimeInstanceId === snapshot.runtimeInstanceId && currentRevision !== void 0 && snapshot.revision <= currentRevision) {
3459
- return { accepted: false, reason: "stale-revision", receivedRevision: snapshot.revision };
3460
- }
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
- }
3477
- }
3478
- currentRuntimeInstanceId = snapshot.runtimeInstanceId;
3479
- currentRevision = snapshot.revision;
3480
- currentServices = Object.freeze(nextServices);
3481
- terminalError = void 0;
3482
- if (snapshot.state === "failed") {
3483
- currentState = "stale";
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";
3493
- }
3494
- notify();
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();
3510
- },
3511
- getProxy(lookup, scope) {
3512
- const record = {
3513
- revoked: false,
3514
- reason: "Remote service proxy revoked",
3515
- controller: new AbortController()
3516
- };
3517
- proxies.add(record);
3518
- let removeScopeRevoke;
3519
- let removeScopeDispose;
3520
- const revokeFromScope = (reason) => {
3521
- revokeProxy(record, reason, "service_revoked");
3522
- proxies.delete(record);
3523
- removeScopeRevoke?.();
3524
- removeScopeDispose?.();
3525
- };
3526
- if (scope) {
3527
- try {
3528
- removeScopeRevoke = scope.onRevoke(revokeFromScope);
3529
- removeScopeDispose = scope.onDispose(revokeFromScope, `service-proxy:${lookup.capabilityId}`);
3530
- } catch {
3531
- revokeFromScope("Remote service proxy scope is already revoked");
3532
- }
3533
- }
3534
- const proxy = {
3535
- get reference() {
3536
- return record.boundReference;
3537
- },
3538
- get revoked() {
3539
- return record.revoked;
3540
- },
3541
- async call(request, callOptions = {}) {
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);
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
- }
3571
- const value = await Promise.race([
3572
- result,
3573
- new Promise((_, reject) => {
3574
- if (merged.signal.aborted) {
3575
- reject(abortedError(merged.signal));
3576
- return;
3577
- }
3578
- const onAbort = () => reject(abortedError(merged.signal));
3579
- merged.signal.addEventListener("abort", onAbort, { once: true });
3580
- result.finally(() => merged.signal.removeEventListener("abort", onAbort)).catch(() => void 0);
3581
- })
3582
- ]);
3583
- if (record.revoked) throw new RemoteServiceError("service_revoked", record.reason);
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;
3589
- } finally {
3590
- clearTimeout(timeout);
3591
- merged.dispose();
3592
- if (record.revoked) {
3593
- proxies.delete(record);
3594
- removeScopeRevoke?.();
3595
- removeScopeDispose?.();
3596
- }
3597
- }
3598
- },
3599
- revoke(reason = "Remote service proxy revoked") {
3600
- revokeFromScope(reason);
3601
- }
3602
- };
3603
- return proxy;
3604
- },
3605
- requireProxy(lookup, scope) {
3606
- return bridge.getProxy(lookup, scope);
3607
- },
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");
3614
- currentState = "stale";
3615
- notify();
3616
- },
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";
3635
- notify();
3636
- proxies.clear();
3637
- },
3638
- subscribe(listener) {
3639
- listeners.add(listener);
3640
- return () => listeners.delete(listener);
3641
- },
3642
- services() {
3643
- return currentServices;
3644
- }
3645
- };
3646
- return bridge;
3647
- }
3648
-
3649
- // src/transport/messagePortServiceTransport.ts
3650
- var nextTransportId = 0;
3651
- var nextCallSequence = 0;
3652
- function makeCallId(transportId) {
3653
- nextCallSequence += 1;
3654
- return `remote-call:${transportId}:${nextCallSequence}`;
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
- }
3661
- function errorFromWire(input) {
3662
- return new RemoteServiceError(
3663
- typeof input?.code === "string" && input.code.length > 0 ? input.code : "handler_failed",
3664
- typeof input?.message === "string" ? input.message : "Remote service call failed",
3665
- input?.details
3666
- );
3667
- }
3668
- function abortReason(signal) {
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
- }
3696
- }
3697
- function isResponseMessage(input, codec) {
3698
- if (!input || typeof input !== "object") return false;
3699
- const message = input;
3700
- return (message.type === codec.type("result") || message.type === codec.type("error")) && typeof message.protocolVersion === "string" && typeof message.callId === "string" && typeof message.serviceInstanceId === "string";
3701
- }
3702
- function createMessagePortServiceTransport(options) {
3703
- const pending = /* @__PURE__ */ new Map();
3704
- let disposed = false;
3705
- const transportId = ++nextTransportId;
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
- };
3724
- const onMessage = (event) => {
3725
- const decoded = codec.decode(event.data);
3726
- if (!isResponseMessage(decoded, codec)) return;
3727
- const call = pending.get(decoded.callId);
3728
- if (!call) return;
3729
- if (decoded.serviceInstanceId !== call.serviceInstanceId) return;
3730
- pending.delete(decoded.callId);
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
- }
3737
- if (decoded.type === codec.type("error")) {
3738
- call.reject(errorFromWire(decoded.error));
3739
- } else {
3740
- call.resolve(decoded.result);
3741
- }
3742
- };
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);
3746
- options.port.start();
3747
- const dispose = () => {
3748
- if (disposed) return;
3749
- disposed = true;
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
- }
3758
- }
3759
- };
3760
- const transport = {
3761
- call(request, context) {
3762
- if (disposed) return Promise.reject(new RemoteServiceError("transport_unavailable", "Remote service transport disposed"));
3763
- if (context.signal.aborted) return Promise.reject(abortReason(context.signal));
3764
- const callId = makeCallId(transportId);
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
- })();
3796
- return new Promise((resolve, reject) => {
3797
- let settled = false;
3798
- const finish = (callback) => {
3799
- if (settled) return;
3800
- settled = true;
3801
- callback();
3802
- };
3803
- const sendCancel = () => postBestEffort(options.port, codec.encode({
3804
- type: codec.type("cancel"),
3805
- protocolVersion: codec.protocolVersion,
3806
- callId,
3807
- serviceInstanceId
3808
- }));
3809
- const onAbort = () => {
3810
- if (!pending.delete(callId)) return;
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);
3816
- };
3817
- const pendingCall = {
3818
- protocolVersion: codec.protocolVersion,
3819
- serviceInstanceId,
3820
- resolve: (value) => finish(() => resolve(value)),
3821
- reject: (error) => finish(() => reject(error)),
3822
- removeAbort: () => merged.signal.removeEventListener("abort", onAbort),
3823
- disposeDeadline: () => {
3824
- merged.dispose();
3825
- clearTimeout(deadlineTimer);
3826
- }
3827
- };
3828
- pending.set(callId, pendingCall);
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
- };
3841
- try {
3842
- const transfer = options.transferForRequest?.(request, context) ?? [];
3843
- postRequest(options.port, codec.encode(message), transfer);
3844
- if (merged.signal.aborted) onAbort();
3845
- } catch (error) {
3846
- pending.delete(callId);
3847
- pendingCall.removeAbort();
3848
- pendingCall.disposeDeadline();
3849
- pendingCall.reject(error);
3850
- }
3851
- });
3852
- },
3853
- dispose
3854
- };
3855
- return transport;
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
- }
4258
-
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