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.
package/dist/index.js CHANGED
@@ -1,1385 +1,64 @@
1
- import { RuntimeInitializationError, createRuntimeUnitImplementationRegistry, createPluginHost, StartupPluginError, createRemoteServiceMessageCodec, createRuntimeMessageCodec, RemoteServiceError, UpgradeGateRejectedError, unitSnapshotFromState, RuntimeUnavailableError, RUNTIME_PROTOCOL_VERSION, RUNTIME_SNAPSHOT_TYPE, RUNTIME_ERROR_TYPE } from './chunk-76BGPI6M.js';
2
- 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, 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 } from './chunk-76BGPI6M.js';
1
+ export { startSharedWorkerApp } from './chunk-SX46RHDI.js';
2
+ export { connectSharedWorker } from './chunk-ANA6GBEI.js';
3
+ import { capabilityDescriptor } from './chunk-HJKPKWI7.js';
4
+ export { LIFECYCLE_ERROR_TEXT, LifecycleScopeRevokedError, PermissionDeniedError, PermissionLeaseRevokedError, RESOURCE_OWNER, RESOURCE_REGISTRY, RuntimeInitializationError, RuntimeUnavailableError, SCOPED_TASK_SCHEDULER_CAPABILITY, UpgradeGateRejectedError, WebLoomError, assertCapability, capabilityDescriptor, capabilityKey, createLifecycleScope, createMessageBus, createResourceScope, createWindowApp, defineCapability, isCapability, isCapabilityDescriptor, lifecycleErrorText } from './chunk-HJKPKWI7.js';
3
5
 
4
- // src/contracts/plugin.ts
5
- function runtimeCapabilityContractVersion(capability) {
6
- return `${capability}.v1`;
6
+ // src/contracts/messageBus.ts
7
+ var RUNTIME_MESSAGE_BUS = "webloom.messageBus";
8
+
9
+ // src/authoring/definePlugin.ts
10
+ function nonEmpty(value, label) {
11
+ if (value.trim() === "") throw new TypeError(`${label} must be a non-empty string`);
12
+ return value;
7
13
  }
8
- function defineRuntimeUnitDependencies(dependencies, defaults) {
14
+ function normalizeDependencies(dependencies, runtime) {
9
15
  return dependencies.map((dependency) => ({
10
- capability: dependency.capability,
11
- contractVersion: runtimeCapabilityContractVersion(dependency.capability),
12
- sourceRuntime: defaults.sourceRuntime,
16
+ capability: capabilityDescriptor(dependency.capability),
17
+ ...dependency.source === "peer" ? { source: "peer" } : { sourceRuntime: dependency.sourceRuntime ?? runtime },
13
18
  ...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
14
19
  ...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
15
20
  }));
16
21
  }
17
- function defineRuntimeUnitProvidedContracts(capabilities) {
18
- return Object.fromEntries(
19
- capabilities.map((capability) => [capability, runtimeCapabilityContractVersion(capability)])
20
- );
21
- }
22
-
23
- // src/authoring/definePlugin.ts
24
- function normalizeDependency(dependency) {
25
- const contractVersion = dependency.contractVersion ?? runtimeCapabilityContractVersion(dependency.capability);
26
- return {
27
- capability: dependency.capability,
28
- contractVersion,
29
- ...dependency.sourceRuntime !== void 0 ? { sourceRuntime: dependency.sourceRuntime } : {},
30
- ...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
31
- ...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
32
- };
33
- }
34
22
  function definePlugin(options) {
35
- if (!options || typeof options.id !== "string" || options.id.trim() === "") {
36
- throw new Error("Plugin id must be a non-empty string");
37
- }
38
- if (typeof options.setup !== "function") throw new Error(`Plugin "${options.id}" setup must be a function`);
39
- const unitId = options.unitId ?? options.id;
40
- if (unitId.trim() === "") throw new Error(`Plugin "${options.id}" unitId must be a non-empty string`);
41
- const provides = [...new Set(options.provides ?? [])];
42
- const dependencies = (options.dependencies ?? []).map(normalizeDependency);
43
- const providedContracts = {
44
- ...defineRuntimeUnitProvidedContracts(provides),
45
- ...options.providedContracts ?? {}
46
- };
23
+ if (!options || typeof options.id !== "string") throw new TypeError("Plugin id must be a non-empty string");
24
+ nonEmpty(options.id, "Plugin id");
25
+ if (typeof options.setup !== "function") throw new TypeError(`Plugin "${options.id}" setup must be a function`);
26
+ const unitId = nonEmpty(options.unitId ?? options.id, `Plugin "${options.id}" unitId`);
27
+ const startup = options.startup ?? "optional";
28
+ const defaultEnabled = options.defaultEnabled ?? true;
29
+ const canDisable = options.canDisable ?? startup !== "required";
30
+ if (startup === "required" && (!defaultEnabled || canDisable)) {
31
+ throw new TypeError(`Plugin "${options.id}" required startup policy is inconsistent`);
32
+ }
33
+ const provides = [...options.provides ?? []];
34
+ const dependencies = [...options.dependencies ?? []];
35
+ const runtime = options.runtime;
47
36
  const descriptor = {
48
37
  id: unitId,
49
- ...options.runtime !== void 0 ? { runtime: options.runtime } : {},
50
- ...dependencies.length > 0 ? { dependencies } : {},
51
- ...provides.length > 0 ? { provides } : {},
52
- ...Object.keys(providedContracts).length > 0 ? { providedContracts } : {},
53
- ...options.contribution !== void 0 ? { contribution: options.contribution } : {},
38
+ ...runtime !== void 0 ? { runtime } : {},
39
+ ...provides.length > 0 ? { provides: provides.map((capability) => capabilityDescriptor(capability)) } : {},
40
+ ...dependencies.length > 0 ? { dependencies: normalizeDependencies(dependencies, runtime ?? "window-main") } : {},
54
41
  ...options.permissions !== void 0 ? { permissions: [...options.permissions] } : {},
55
- ...options.config !== void 0 ? { config: options.config } : {}
56
- };
57
- const metaInput = options.meta ?? {};
58
- const required = options.required === true || options.startup === "required" || metaInput.startup === "required";
59
- const meta = {
60
- ...metaInput,
61
- defaultEnabled: options.defaultEnabled ?? metaInput.defaultEnabled ?? true,
62
- canDisable: options.canDisable ?? metaInput.canDisable ?? !required,
63
- startup: required ? "required" : options.startup ?? metaInput.startup ?? "optional"
42
+ ...options.config !== void 0 ? { config: options.config } : {},
43
+ ...options.contribution !== void 0 ? { contribution: options.contribution } : {}
64
44
  };
65
- if (meta.startup === "required" && (meta.canDisable || !meta.defaultEnabled)) {
66
- throw new Error(`Plugin "${options.id}" required metadata is inconsistent`);
67
- }
68
45
  const manifest = {
69
46
  id: options.id,
70
47
  name: options.name ?? options.id,
71
48
  ...options.description !== void 0 ? { description: options.description } : {},
72
- meta,
49
+ startup,
50
+ defaultEnabled,
51
+ canDisable,
73
52
  units: [descriptor]
74
53
  };
75
54
  return Object.freeze({
76
55
  manifest: Object.freeze(manifest),
77
56
  descriptor: Object.freeze(descriptor),
78
- setup: options.setup
57
+ setup: options.setup,
58
+ capabilities: Object.freeze([...provides, ...dependencies.map((dependency) => dependency.capability)])
79
59
  });
80
60
  }
81
61
 
82
- // src/runtime/pluginDefinitions.ts
83
- function normalizeProductDependencies(dependencies, runtime) {
84
- return dependencies?.map((dependency) => ({
85
- capability: dependency.capability,
86
- contractVersion: dependency.contractVersion ?? runtimeCapabilityContractVersion(dependency.capability),
87
- sourceRuntime: dependency.sourceRuntime ?? runtime,
88
- ...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
89
- ...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
90
- }));
91
- }
92
- function materializePluginDefinition(input, runtime) {
93
- const manifest = input.manifest;
94
- const setup = input.setup;
95
- if (!manifest || typeof manifest.id !== "string" || manifest.id.trim() === "") {
96
- throw new Error("Plugin manifest id must be a non-empty string");
97
- }
98
- const existingUnits = manifest.units ?? [];
99
- const requestedUnitId = "unitId" in input ? input.unitId : void 0;
100
- const matchingUnits = existingUnits.filter((unit) => unit.runtime === void 0 || unit.runtime === runtime);
101
- let implementationUnitId;
102
- if (existingUnits.length === 0) {
103
- implementationUnitId = manifest.id;
104
- } else if (requestedUnitId !== void 0) {
105
- const selected = existingUnits.find((unit) => unit.id === requestedUnitId);
106
- if (!selected) throw new Error(`Plugin "${manifest.id}" does not declare unit "${requestedUnitId}"`);
107
- if (selected.runtime !== void 0 && selected.runtime !== runtime) {
108
- throw new Error(`Plugin "${manifest.id}" unit "${requestedUnitId}" targets ${selected.runtime}, not ${runtime}`);
109
- }
110
- implementationUnitId = selected.id;
111
- } else if (matchingUnits.length === 1 && matchingUnits[0]) {
112
- implementationUnitId = matchingUnits[0].id;
113
- } else {
114
- throw new Error(
115
- `Plugin "${manifest.id}" has ${matchingUnits.length} implementation units for ${runtime}; specify unitId explicitly`
116
- );
117
- }
118
- const selectedUnit = existingUnits.length > 0 ? existingUnits.find((unit) => unit.id === implementationUnitId) : void 0;
119
- const units = selectedUnit ? [(() => {
120
- const unitRuntime = selectedUnit.runtime ?? runtime;
121
- const provides = [...selectedUnit.provides ?? []];
122
- const dependencies = selectedUnit.dependencies?.map((dependency) => ({
123
- capability: dependency.capability,
124
- contractVersion: dependency.contractVersion ?? runtimeCapabilityContractVersion(dependency.capability),
125
- sourceRuntime: dependency.sourceRuntime ?? unitRuntime,
126
- ...dependency.reason !== void 0 ? { reason: dependency.reason } : {},
127
- ...dependency.optional !== void 0 ? { optional: dependency.optional } : {}
128
- }));
129
- return {
130
- ...selectedUnit,
131
- runtime: unitRuntime,
132
- dependencies,
133
- ...provides.length > 0 ? { providedContracts: { ...defineRuntimeUnitProvidedContracts(provides), ...selectedUnit.providedContracts ?? {} } } : {}
134
- };
135
- })()] : [{
136
- id: manifest.id,
137
- runtime,
138
- dependencies: normalizeProductDependencies(manifest.dependencies, runtime),
139
- ...manifest.provides && manifest.provides.length > 0 ? {
140
- provides: [...manifest.provides],
141
- providedContracts: defineRuntimeUnitProvidedContracts(manifest.provides)
142
- } : {},
143
- ...manifest.permissions !== void 0 ? { permissions: [...manifest.permissions] } : {},
144
- ...manifest.config !== void 0 ? { config: manifest.config } : {},
145
- ...manifest.contribution !== void 0 ? { contribution: manifest.contribution } : {}
146
- }];
147
- const normalized = {
148
- ...manifest,
149
- units
150
- // Product-level declarations are retained only for compatibility with
151
- // low-level manifests; the selected unit is the source of truth in Host.
152
- };
153
- return { manifest: normalized, unitId: implementationUnitId, setup };
154
- }
155
- function materializePluginDefinitions(inputs, runtime) {
156
- return inputs.map((input) => materializePluginDefinition(input, runtime));
157
- }
158
-
159
- // src/runtime/windowRuntime.ts
160
- function makeRuntimeInstanceId(runtimeId) {
161
- try {
162
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
163
- return `${runtimeId}:${crypto.randomUUID()}`;
164
- }
165
- } catch {
166
- }
167
- return `${runtimeId}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
168
- }
169
- function errorMessage(error) {
170
- return error instanceof Error ? error.message : String(error);
171
- }
172
- function isStartupPluginError(error) {
173
- return error instanceof StartupPluginError && typeof error.details?.pluginId === "string";
174
- }
175
- function buildLocalServices(host, manifests, runtimeInstanceId) {
176
- const services = [];
177
- for (const manifest of manifests) {
178
- const state = host.state(manifest.id);
179
- const unit = manifest.units?.find((candidate) => candidate.id === state.unitId) ?? manifest.units?.[0];
180
- if (!unit || unit.runtime === void 0 || !state.instanceId || state.kind !== "enabled") continue;
181
- for (const capability of unit.provides ?? []) {
182
- services.push({
183
- capabilityId: capability,
184
- contractVersion: unit.providedContracts?.[capability] ?? `${capability}.v1`,
185
- runtime: unit.runtime,
186
- runtimeInstanceId,
187
- serviceInstanceId: state.instanceId,
188
- status: "ready",
189
- attributes: Object.freeze({})
190
- });
191
- }
192
- }
193
- return services;
194
- }
195
- async function createWindowApp(options) {
196
- const runtimeId = options.id ?? "window-main";
197
- const remoteRuntime = options.remoteRuntime;
198
- const runtimeInstanceId = makeRuntimeInstanceId(runtimeId);
199
- let currentState = {
200
- runtimeId,
201
- runtimeKind: "window-main",
202
- runtimeInstanceId,
203
- state: "starting",
204
- revision: 0,
205
- units: [],
206
- services: []
207
- };
208
- const listeners = /* @__PURE__ */ new Set();
209
- let disposed = false;
210
- let disposePromise;
211
- const emit = (next) => {
212
- currentState = Object.freeze({
213
- ...next,
214
- units: Object.freeze([...next.units]),
215
- services: Object.freeze([...next.services])
216
- });
217
- for (const listener of [...listeners]) {
218
- try {
219
- listener(currentState);
220
- } catch {
221
- }
222
- }
223
- };
224
- let materialized;
225
- try {
226
- materialized = materializePluginDefinitions(options.plugins, "window-main");
227
- } catch (error) {
228
- throw new RuntimeInitializationError({ phase: "validate", error: errorMessage(error) });
229
- }
230
- const manifests = materialized.map((item) => item.manifest);
231
- let implementations;
232
- try {
233
- const duplicate = manifests.find((manifest, index) => manifests.findIndex((candidate) => candidate.id === manifest.id) !== index);
234
- if (duplicate) throw new Error(`Plugin "${duplicate.id}" is declared more than once`);
235
- implementations = createRuntimeUnitImplementationRegistry(
236
- materialized.map((item) => ({
237
- pluginId: item.manifest.id,
238
- unitId: item.unitId,
239
- setup: item.setup
240
- }))
241
- );
242
- } catch (error) {
243
- throw new RuntimeInitializationError({ phase: "validate", error: errorMessage(error) });
244
- }
245
- let host;
246
- const suppliedHost = options.host;
247
- const remoteServiceBridge = remoteRuntime ? remoteRuntime.serviceBridge : void 0;
248
- try {
249
- const {
250
- id: _id,
251
- plugins: _plugins,
252
- host: _host,
253
- remoteRuntime: _remoteRuntime,
254
- ...hostOptions
255
- } = options;
256
- const suppliedBridgeFactory = hostOptions.serviceBridgeForPlugin;
257
- host = suppliedHost ?? createPluginHost({
258
- ...hostOptions,
259
- runtime: "window-main",
260
- externalRuntimeDependencies: remoteRuntime !== void 0 || hostOptions.externalRuntimeDependencies,
261
- remoteServiceReferences: () => remoteRuntime?.state().services ?? hostOptions.remoteServiceReferences?.() ?? [],
262
- runtimeSnapshots: () => remoteRuntime ? remoteRuntime.state().units.map((unit) => ({
263
- pluginId: unit.pluginId,
264
- unitId: unit.unitId,
265
- runtime: unit.runtime,
266
- ...unit.instanceId !== void 0 ? { instanceId: unit.instanceId } : {},
267
- state: unit.state
268
- })) : hostOptions.runtimeSnapshots?.() ?? [],
269
- serviceBridgeForPlugin: (pluginId, instanceId) => suppliedBridgeFactory?.(pluginId, instanceId) ?? remoteServiceBridge,
270
- rootAttributes: {
271
- ...hostOptions.rootAttributes ?? {},
272
- runtimeId,
273
- runtimeInstanceId
274
- },
275
- runtimeUnitImplementationRegistry: implementations
276
- });
277
- } catch (error) {
278
- throw new RuntimeInitializationError({ phase: "validate", error: errorMessage(error) });
279
- }
280
- let removeRemoteSubscription;
281
- const manifestsForSnapshot = () => {
282
- if (!suppliedHost) return manifests;
283
- return suppliedHost.manifests().map((pluginId) => suppliedHost.getManifest(pluginId)).filter((manifest) => manifest !== void 0);
284
- };
285
- const refresh = () => {
286
- const snapshotManifests = manifestsForSnapshot();
287
- const units = snapshotManifests.flatMap((manifest) => {
288
- const state = host.state(manifest.id);
289
- return (state.units ?? []).map((unit) => unitSnapshotFromState(
290
- unit,
291
- unit.runtime
292
- ));
293
- });
294
- const revision = Math.max(currentState.revision + 1, host.version());
295
- emit({
296
- ...currentState,
297
- revision,
298
- units,
299
- services: [
300
- ...buildLocalServices(host, snapshotManifests, runtimeInstanceId),
301
- ...remoteRuntime?.state().services ?? []
302
- ]
303
- });
304
- };
305
- const removeHostSubscription = host.subscribe(refresh);
306
- try {
307
- if (!suppliedHost) await host.registerAll(manifests);
308
- const failedRequired = suppliedHost ? void 0 : manifests.find((manifest) => {
309
- const required = manifest.meta.startup === "required" || manifest.meta.canDisable === false;
310
- return required && host.state(manifest.id).kind !== "enabled";
311
- });
312
- if (failedRequired) {
313
- const state = host.state(failedRequired.id);
314
- throw new StartupPluginError({
315
- pluginId: failedRequired.id,
316
- unitId: state.unitId ?? failedRequired.units?.[0]?.id ?? failedRequired.id,
317
- capabilities: [],
318
- state: state.kind,
319
- error: state.error ?? `Required plugin is ${state.kind}${state.blockedBy ? `: ${state.blockedBy.join(", ")}` : ""}`
320
- });
321
- }
322
- } catch (error) {
323
- refresh();
324
- const pluginId = isStartupPluginError(error) ? error.details.pluginId : void 0;
325
- const manifest = pluginId ? manifests.find((candidate) => candidate.id === pluginId) : void 0;
326
- const required = manifest?.meta.startup === "required" || manifest?.meta.canDisable === false;
327
- if (required || !isStartupPluginError(error)) {
328
- removeHostSubscription();
329
- removeRemoteSubscription?.();
330
- await host.dispose("window runtime initialization failed").catch(() => void 0);
331
- if (error instanceof RuntimeInitializationError) throw error;
332
- throw new RuntimeInitializationError({
333
- pluginId,
334
- unitId: isStartupPluginError(error) ? error.details.unitId : manifest?.units?.[0]?.id,
335
- phase: "startup",
336
- error: errorMessage(error)
337
- });
338
- }
339
- }
340
- refresh();
341
- emit({ ...currentState, state: "ready" });
342
- removeRemoteSubscription = remoteRuntime?.subscribe(() => {
343
- host.refreshRuntimeUnitSnapshots();
344
- void host.reconcile().catch(() => void 0);
345
- });
346
- const app = {
347
- runtimeKind: "window-main",
348
- runtimeId,
349
- runtimeInstanceId,
350
- host,
351
- state: () => currentState,
352
- capability(capabilityId) {
353
- if (disposed || currentState.state === "disposed" || currentState.state === "stopping") {
354
- throw new RuntimeUnavailableError("Window Runtime has been disposed");
355
- }
356
- return host.capabilities.get(capabilityId);
357
- },
358
- subscribe(listener) {
359
- listeners.add(listener);
360
- listener(currentState);
361
- return () => listeners.delete(listener);
362
- },
363
- dispose(reason = "window runtime disposed") {
364
- if (disposePromise) return disposePromise;
365
- disposed = true;
366
- emit({ ...currentState, state: "stopping" });
367
- removeHostSubscription();
368
- removeRemoteSubscription?.();
369
- disposePromise = host.dispose(reason).then((result) => {
370
- emit({ ...currentState, state: "disposed" });
371
- return result;
372
- }, (error) => {
373
- emit({ ...currentState, state: "failed", error: errorMessage(error) });
374
- throw error;
375
- });
376
- return disposePromise;
377
- }
378
- };
379
- return app;
380
- }
381
-
382
- // src/transport/messagePortServiceProvider.ts
383
- function isCallMessage(input, codec) {
384
- if (!input || typeof input !== "object") return false;
385
- const message = input;
386
- return message.type === codec.type("call") && typeof message.protocolVersion === "string" && typeof message.callId === "string" && message.callId.length > 0 && typeof message.capabilityId === "string" && typeof message.contractVersion === "string" && typeof message.serviceInstanceId === "string";
387
- }
388
- function isCancelMessage(input, codec) {
389
- if (!input || typeof input !== "object") return false;
390
- const message = input;
391
- return message.type === codec.type("cancel") && typeof message.protocolVersion === "string" && typeof message.callId === "string" && typeof message.serviceInstanceId === "string";
392
- }
393
- function errorMessage2(error) {
394
- const candidate = error && typeof error === "object" ? error : void 0;
395
- const code = typeof candidate?.code === "string" && candidate.code.length > 0 ? candidate.code : "handler_failed";
396
- return {
397
- ...typeof candidate?.name === "string" ? { name: candidate.name } : {},
398
- message: typeof candidate?.message === "string" ? candidate.message : String(error),
399
- code,
400
- ...candidate?.details && typeof candidate.details === "object" && !Array.isArray(candidate.details) ? { details: candidate.details } : {}
401
- };
402
- }
403
- function stableValue(value) {
404
- if (Array.isArray(value)) return value.map(stableValue);
405
- if (!value || typeof value !== "object") return value;
406
- return Object.fromEntries(
407
- Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, stableValue(item)])
408
- );
409
- }
410
- function bindingKey(reference) {
411
- return [
412
- reference.capabilityId,
413
- reference.contractVersion,
414
- reference.runtime,
415
- reference.runtimeInstanceId,
416
- reference.serviceInstanceId
417
- ].join("\0");
418
- }
419
- function referenceFingerprint(reference) {
420
- return JSON.stringify({
421
- ...reference,
422
- attributes: stableValue(reference.attributes)
423
- });
424
- }
425
- function postBestEffort(port, message) {
426
- try {
427
- port.postMessage(message);
428
- } catch {
429
- }
430
- }
431
- function createMessagePortServiceProvider(options) {
432
- const pending = /* @__PURE__ */ new Map();
433
- const codec = options.codec ?? createRemoteServiceMessageCodec();
434
- let services = [...options.services?.() ?? []];
435
- let bindings = /* @__PURE__ */ new Map();
436
- let nextBindingEpoch = 0;
437
- let revoked = false;
438
- let disposed = false;
439
- const buildBindings = (nextServices) => {
440
- const nextBindings = /* @__PURE__ */ new Map();
441
- for (const reference of nextServices) {
442
- const key = bindingKey(reference);
443
- const previous = bindings.get(key);
444
- const epoch = previous && referenceFingerprint(previous.reference) === referenceFingerprint(reference) ? previous.epoch : ++nextBindingEpoch;
445
- nextBindings.set(key, { reference, epoch });
446
- }
447
- return nextBindings;
448
- };
449
- const isCurrent = (callId, call) => {
450
- const current = bindings.get(call.bindingKey);
451
- return !disposed && !revoked && pending.get(callId) === call && current?.epoch === call.bindingEpoch && current.reference.serviceInstanceId === call.serviceInstanceId;
452
- };
453
- const revokeReplacedCalls = (nextBindings) => {
454
- for (const call of pending.values()) {
455
- const next = nextBindings.get(call.bindingKey);
456
- if (!next || next.epoch !== call.bindingEpoch) {
457
- sendError(call.message, new RemoteServiceError("service_revoked", "Remote service binding was replaced"));
458
- call.controller.abort(new RemoteServiceError("service_revoked", "Remote service binding was replaced"));
459
- }
460
- }
461
- };
462
- bindings = buildBindings(services);
463
- const sendError = (message, error) => {
464
- const response = {
465
- type: codec.type("error"),
466
- protocolVersion: codec.protocolVersion,
467
- callId: message.callId,
468
- serviceInstanceId: message.serviceInstanceId,
469
- error: errorMessage2(error)
470
- };
471
- postBestEffort(options.port, codec.encode(response));
472
- };
473
- const onMessage = (event) => {
474
- if (disposed) return;
475
- const decoded = codec.decode(event.data);
476
- if (isCancelMessage(decoded, codec)) {
477
- if (decoded.protocolVersion !== codec.protocolVersion) return;
478
- const call2 = pending.get(decoded.callId);
479
- if (call2?.serviceInstanceId === decoded.serviceInstanceId) {
480
- call2.controller.abort(new RemoteServiceError("request_cancelled", "Remote service request cancelled"));
481
- }
482
- return;
483
- }
484
- if (!isCallMessage(decoded, codec)) return;
485
- const message = decoded;
486
- if (message.protocolVersion !== codec.protocolVersion) {
487
- sendError(message, new RemoteServiceError("protocol_mismatch", "Remote service protocol version mismatch"));
488
- return;
489
- }
490
- if (revoked) {
491
- sendError(message, new RemoteServiceError("service_revoked", "Remote service Provider has been revoked"));
492
- return;
493
- }
494
- const reference = services.find((candidate) => candidate.status === "ready" && candidate.capabilityId === message.capabilityId && candidate.contractVersion === message.contractVersion && candidate.serviceInstanceId === message.serviceInstanceId);
495
- if (!reference) {
496
- sendError(message, new RemoteServiceError("service_stale", "Remote service instance is stale or unavailable"));
497
- return;
498
- }
499
- if (pending.has(message.callId)) {
500
- sendError(message, new RemoteServiceError("handler_failed", "Remote service callId is duplicated"));
501
- return;
502
- }
503
- const controller = new AbortController();
504
- const currentBindingKey = bindingKey(reference);
505
- const currentBinding = bindings.get(currentBindingKey);
506
- if (!currentBinding || currentBinding.reference !== reference) {
507
- sendError(message, new RemoteServiceError("service_stale", "Remote service binding changed"));
508
- return;
509
- }
510
- const call = {
511
- message,
512
- controller,
513
- serviceInstanceId: message.serviceInstanceId,
514
- bindingKey: currentBindingKey,
515
- bindingEpoch: currentBinding.epoch
516
- };
517
- pending.set(message.callId, call);
518
- void (async () => {
519
- try {
520
- const result = await options.handleCall({ message, reference, signal: controller.signal });
521
- if (controller.signal.aborted || !isCurrent(message.callId, call)) return;
522
- const response = {
523
- type: codec.type("result"),
524
- protocolVersion: codec.protocolVersion,
525
- callId: message.callId,
526
- serviceInstanceId: message.serviceInstanceId,
527
- result
528
- };
529
- postBestEffort(options.port, codec.encode(response));
530
- } catch (error) {
531
- if (controller.signal.aborted || !isCurrent(message.callId, call)) return;
532
- sendError(message, error);
533
- } finally {
534
- if (pending.get(message.callId) === call) pending.delete(message.callId);
535
- }
536
- })();
537
- };
538
- options.port.addEventListener("message", onMessage);
539
- options.port.start();
540
- const provider = {
541
- setServices(nextServices) {
542
- if (disposed) return;
543
- const next = [...nextServices];
544
- const nextBindings = buildBindings(next);
545
- revoked = true;
546
- revokeReplacedCalls(nextBindings);
547
- services = next;
548
- bindings = nextBindings;
549
- revoked = false;
550
- },
551
- revoke(reason = "Remote service Provider revoked") {
552
- if (disposed) return;
553
- revoked = true;
554
- for (const call of pending.values()) {
555
- sendError(call.message, new RemoteServiceError("service_revoked", reason));
556
- call.controller.abort(new RemoteServiceError("service_revoked", reason));
557
- }
558
- services = [];
559
- bindings = /* @__PURE__ */ new Map();
560
- },
561
- dispose() {
562
- if (disposed) return;
563
- disposed = true;
564
- revoked = true;
565
- options.port.removeEventListener("message", onMessage);
566
- for (const { controller } of pending.values()) {
567
- controller.abort(new RemoteServiceError("transport_unavailable", "Remote service Provider disposed"));
568
- }
569
- pending.clear();
570
- services = [];
571
- bindings = /* @__PURE__ */ new Map();
572
- if (options.closeOnDispose !== false) {
573
- try {
574
- options.port.close();
575
- } catch {
576
- }
577
- }
578
- }
579
- };
580
- return provider;
581
- }
582
-
583
- // src/runtime/sharedWorkerHost.ts
584
- function makeRuntimeInstanceId2(runtimeId) {
585
- try {
586
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
587
- return `${runtimeId}:${crypto.randomUUID()}`;
588
- }
589
- } catch {
590
- }
591
- return `${runtimeId}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
592
- }
593
- function errorMessage3(error) {
594
- return error instanceof Error ? error.message : String(error);
595
- }
596
- function startupDetails(error) {
597
- if (error instanceof StartupPluginError) {
598
- return { pluginId: error.details.pluginId, unitId: error.details.unitId, message: error.details.error ?? error.message };
599
- }
600
- if (error instanceof RuntimeInitializationError) {
601
- return { pluginId: error.details.pluginId, unitId: error.details.unitId, message: error.message };
602
- }
603
- return { message: errorMessage3(error) };
604
- }
605
- function addPortListener(port, listener) {
606
- port.addEventListener("message", listener);
607
- return () => port.removeEventListener("message", listener);
608
- }
609
- function post(port, message) {
610
- try {
611
- port.postMessage(message);
612
- } catch {
613
- }
614
- }
615
- function startSharedWorkerApp(options) {
616
- if (!options || typeof options.id !== "string" || options.id.trim() === "") {
617
- throw new Error("SharedWorker runtime id must be a non-empty string");
618
- }
619
- const workerScope = options.globalScope ?? ("onconnect" in globalThis ? globalThis : void 0);
620
- if (!workerScope) {
621
- throw new RuntimeInitializationError({
622
- phase: "validate",
623
- error: "startSharedWorkerApp must run in a SharedWorkerGlobalScope"
624
- });
625
- }
626
- const runtimeId = options.id;
627
- const runtimeInstanceId = makeRuntimeInstanceId2(runtimeId);
628
- const listeners = /* @__PURE__ */ new Set();
629
- const endpoints = /* @__PURE__ */ new Set();
630
- const codec = createRuntimeMessageCodec();
631
- let runtimeState = "starting";
632
- let revision = 0;
633
- let host;
634
- let manifests = [];
635
- let disposed = false;
636
- let acceptingConnections = true;
637
- let disposePromise;
638
- let startupError;
639
- const serviceReferences = () => {
640
- if (!host || runtimeState !== "ready") return [];
641
- const services = [];
642
- for (const manifest of manifests) {
643
- const state = host.state(manifest.id);
644
- const unit = manifest.units?.find((candidate) => candidate.id === state.unitId) ?? manifest.units?.[0];
645
- if (!unit || unit.runtime === void 0 || !state.instanceId || state.kind !== "enabled") continue;
646
- const scope = host.scope(manifest.id);
647
- const attributes = Object.freeze({ ...scope?.identity.attributes ?? {} });
648
- for (const capability of unit.provides ?? []) {
649
- services.push({
650
- capabilityId: capability,
651
- contractVersion: unit.providedContracts?.[capability] ?? `${capability}.v1`,
652
- runtime: unit.runtime,
653
- runtimeInstanceId,
654
- serviceInstanceId: state.instanceId,
655
- status: "ready",
656
- attributes
657
- });
658
- }
659
- }
660
- return services;
661
- };
662
- const currentSnapshot = () => Object.freeze({
663
- runtimeId,
664
- runtimeKind: "shared-worker",
665
- runtimeInstanceId,
666
- state: runtimeState,
667
- revision,
668
- units: Object.freeze(host && runtimeState !== "failed" ? manifests.flatMap((manifest) => {
669
- const state = host?.state(manifest.id);
670
- return (state?.units ?? []).map((unit) => ({
671
- pluginId: unit.pluginId,
672
- unitId: unit.unitId,
673
- runtime: unit.runtime,
674
- ...unit.instanceId !== void 0 ? { instanceId: unit.instanceId } : {},
675
- state: unit.kind
676
- }));
677
- }) : []),
678
- services: Object.freeze(serviceReferences())
679
- });
680
- const emit = () => {
681
- const snapshot = currentSnapshot();
682
- for (const listener of [...listeners]) {
683
- try {
684
- listener(snapshot);
685
- } catch {
686
- }
687
- }
688
- };
689
- const runtimeSnapshot = () => ({
690
- type: RUNTIME_SNAPSHOT_TYPE,
691
- protocolVersion: RUNTIME_PROTOCOL_VERSION,
692
- runtimeId,
693
- runtimeKind: "shared-worker",
694
- runtimeInstanceId,
695
- revision,
696
- state: runtimeState,
697
- units: currentSnapshot().units,
698
- services: serviceReferences()
699
- });
700
- const publishEndpoint = (endpoint) => {
701
- if (endpoint.closed) return;
702
- endpoint.provider.setServices(serviceReferences());
703
- post(endpoint.port, codec.encode(runtimeSnapshot()));
704
- };
705
- const publishAll = () => {
706
- for (const endpoint of [...endpoints]) publishEndpoint(endpoint);
707
- };
708
- const closeEndpoint = (endpoint, _reason) => {
709
- if (endpoint.closed) return;
710
- endpoint.closed = true;
711
- endpoints.delete(endpoint);
712
- endpoint.removeMessage();
713
- endpoint.provider.dispose();
714
- };
715
- const sendError = (port, code, message, details = {}) => {
716
- const error = {
717
- type: RUNTIME_ERROR_TYPE,
718
- protocolVersion: RUNTIME_PROTOCOL_VERSION,
719
- code,
720
- message,
721
- ...details.pluginId !== void 0 ? { pluginId: details.pluginId } : {},
722
- ...details.unitId !== void 0 ? { unitId: details.unitId } : {},
723
- ...details.phase !== void 0 ? { phase: details.phase } : {}
724
- };
725
- post(port, error);
726
- };
727
- const isAcceptingConnections = () => acceptingConnections && runtimeState !== "stopping" && runtimeState !== "disposed" && !disposed;
728
- const rejectLateConnection = (port) => {
729
- post(port, codec.encode(runtimeSnapshot()));
730
- setTimeout(() => {
731
- try {
732
- port.close();
733
- } catch {
734
- }
735
- }, 0);
736
- };
737
- const handleCall = async (input) => {
738
- if (!host || runtimeState !== "ready") throw new Error("SharedWorker Runtime is not ready");
739
- const { message, reference } = input;
740
- if (reference.runtimeInstanceId !== runtimeInstanceId || reference.runtime !== "shared-worker" || reference.status !== "ready") {
741
- throw new Error("Runtime service reference is stale");
742
- }
743
- const manifest = manifests.find((candidate) => host?.state(candidate.id).instanceId === reference.serviceInstanceId);
744
- if (!manifest) throw new Error("Runtime service provider is no longer active");
745
- const state = host.state(manifest.id);
746
- if (state.kind !== "enabled" || state.instanceId !== reference.serviceInstanceId) {
747
- throw new Error("Runtime service provider is no longer active");
748
- }
749
- const unit = manifest.units?.find((candidate) => candidate.id === state.unitId);
750
- if (!unit || unit.runtime !== "shared-worker" || !unit.provides?.includes(reference.capabilityId)) {
751
- throw new Error("Runtime capability is not declared");
752
- }
753
- const expectedVersion = unit.providedContracts?.[reference.capabilityId] ?? `${reference.capabilityId}.v1`;
754
- if (expectedVersion !== reference.contractVersion) throw new Error("Runtime capability contract mismatch");
755
- const value = host.capabilities.get(reference.capabilityId);
756
- if (input.signal.aborted) throw input.signal.reason ?? new Error("Runtime service request cancelled");
757
- const request = message.request;
758
- if (typeof value === "function") return await value(request, input.signal);
759
- if (value && typeof value === "object") {
760
- const object = value;
761
- if (typeof object.handle === "function") return await object.handle(request, input.signal);
762
- if (request && typeof request === "object" && typeof request.method === "string") {
763
- const method = request.method;
764
- const methodValue = object[method];
765
- if (typeof methodValue === "function") {
766
- const args = request.args;
767
- return await methodValue(...Array.isArray(args) ? args : []);
768
- }
769
- }
770
- }
771
- throw new Error(`Runtime capability "${reference.capabilityId}" does not expose an RPC handler`);
772
- };
773
- const attachPort = (port) => {
774
- let endpoint;
775
- let removeMessage = () => void 0;
776
- const onMessageError = () => {
777
- if (endpoint) closeEndpoint(endpoint);
778
- };
779
- endpoint = {
780
- port,
781
- provider: void 0,
782
- removeMessage: () => void 0,
783
- closed: false
784
- };
785
- endpoint.provider = createMessagePortServiceProvider({
786
- port,
787
- codec,
788
- services: serviceReferences,
789
- handleCall
790
- });
791
- endpoints.add(endpoint);
792
- const onMessage = (event) => {
793
- };
794
- removeMessage = addPortListener(port, onMessage);
795
- endpoint.removeMessage = () => {
796
- removeMessage();
797
- port.removeEventListener("messageerror", onMessageError);
798
- };
799
- port.addEventListener("messageerror", onMessageError);
800
- port.start();
801
- publishEndpoint(endpoint);
802
- };
803
- workerScope.onconnect = (event) => {
804
- const ports = event.ports ?? [];
805
- if (!isAcceptingConnections()) {
806
- for (const port of ports) rejectLateConnection(port);
807
- return;
808
- }
809
- try {
810
- options.onPortConnect?.(event);
811
- } catch (error) {
812
- const detail = startupDetails(error);
813
- for (const port of ports) {
814
- sendError(port, "runtime_initialization_failed", detail.message, {
815
- pluginId: detail.pluginId,
816
- unitId: detail.unitId,
817
- phase: "startup"
818
- });
819
- try {
820
- port.close();
821
- } catch {
822
- }
823
- }
824
- return;
825
- }
826
- if (!isAcceptingConnections()) {
827
- for (const port of ports) rejectLateConnection(port);
828
- return;
829
- }
830
- for (const port of ports) attachPort(port);
831
- };
832
- let materialized = [];
833
- let readyPromise;
834
- try {
835
- materialized = materializePluginDefinitions(options.plugins, "shared-worker");
836
- manifests = materialized.map((item) => item.manifest);
837
- const implementations = createRuntimeUnitImplementationRegistry(materialized.map((item) => ({
838
- pluginId: item.manifest.id,
839
- unitId: item.unitId,
840
- setup: item.setup
841
- })));
842
- const { id: _id, plugins: _plugins, globalScope: _scope, onPortConnect: _onPortConnect, ...hostOptions } = options;
843
- host = createPluginHost({
844
- ...hostOptions,
845
- runtime: "shared-worker",
846
- rootAttributes: { ...hostOptions.rootAttributes ?? {}, runtimeId, runtimeInstanceId },
847
- runtimeUnitImplementationRegistry: implementations
848
- });
849
- host.subscribe(() => {
850
- if (runtimeState !== "ready" || disposed) return;
851
- revision += 1;
852
- publishAll();
853
- emit();
854
- });
855
- readyPromise = host.registerAll(manifests).then(() => {
856
- if (runtimeState === "stopping" || disposed) return;
857
- const runtimeHost = host;
858
- if (!runtimeHost) throw new Error("SharedWorker Plugin Host is unavailable");
859
- const failedRequired = manifests.find((manifest) => {
860
- const required = manifest.meta.startup === "required" || manifest.meta.canDisable === false;
861
- return required && runtimeHost.state(manifest.id).kind !== "enabled";
862
- });
863
- if (failedRequired) {
864
- const state = runtimeHost.state(failedRequired.id);
865
- throw new StartupPluginError({
866
- pluginId: failedRequired.id,
867
- unitId: state.unitId ?? failedRequired.units?.[0]?.id ?? failedRequired.id,
868
- capabilities: [],
869
- state: state.kind,
870
- error: state.error ?? `Required plugin is ${state.kind}`
871
- });
872
- }
873
- runtimeState = "ready";
874
- revision = Math.max(1, revision + 1);
875
- publishAll();
876
- emit();
877
- }).catch((error) => {
878
- if (runtimeState === "stopping" || disposed) return;
879
- startupError = error;
880
- runtimeState = "failed";
881
- emit();
882
- const detail = startupDetails(error);
883
- for (const endpoint of [...endpoints]) {
884
- sendError(endpoint.port, "runtime_initialization_failed", detail.message, {
885
- pluginId: detail.pluginId,
886
- unitId: detail.unitId,
887
- phase: "startup"
888
- });
889
- closeEndpoint(endpoint, "Runtime initialization failed");
890
- }
891
- throw new RuntimeInitializationError({
892
- pluginId: detail.pluginId,
893
- unitId: detail.unitId,
894
- phase: "startup",
895
- error: detail.message
896
- });
897
- });
898
- } catch (error) {
899
- startupError = error;
900
- runtimeState = "failed";
901
- emit();
902
- readyPromise = Promise.reject(error instanceof RuntimeInitializationError ? error : new RuntimeInitializationError({ phase: "validate", error: errorMessage3(error) }));
903
- void readyPromise.catch(() => void 0);
904
- }
905
- const app = {
906
- runtimeKind: "shared-worker",
907
- runtimeId,
908
- runtimeInstanceId,
909
- ready: () => readyPromise,
910
- async reconcile() {
911
- if (!host) {
912
- await readyPromise;
913
- return;
914
- }
915
- if (runtimeState === "failed" || runtimeState === "stopping" || runtimeState === "disposed" || disposed) {
916
- await readyPromise;
917
- return;
918
- }
919
- await host.reconcile();
920
- },
921
- state: currentSnapshot,
922
- subscribe(listener) {
923
- listeners.add(listener);
924
- listener(currentSnapshot());
925
- return () => listeners.delete(listener);
926
- },
927
- dispose(reason = "shared worker runtime disposed") {
928
- if (disposePromise) return disposePromise;
929
- acceptingConnections = false;
930
- runtimeState = "stopping";
931
- revision += 1;
932
- publishAll();
933
- emit();
934
- disposePromise = (async () => {
935
- let result;
936
- let failure;
937
- try {
938
- result = host ? await host.dispose(reason) : { scopeId: `runtime:${runtimeInstanceId}`, state: "stopped", attempted: 0, released: 0, pending: [], errors: [], cleanupIncomplete: false };
939
- } catch (error) {
940
- failure = error;
941
- result = {
942
- scopeId: `runtime:${runtimeInstanceId}`,
943
- state: "stopped",
944
- attempted: 0,
945
- released: 0,
946
- pending: [],
947
- errors: [{ resourceId: "runtime.dispose", code: "lifecycle.cleanup_failed", message: errorMessage3(error) }],
948
- cleanupIncomplete: true
949
- };
950
- }
951
- runtimeState = "disposed";
952
- revision += 1;
953
- publishAll();
954
- emit();
955
- disposed = true;
956
- for (const endpoint of [...endpoints]) closeEndpoint(endpoint);
957
- if (failure) throw failure;
958
- return result;
959
- })();
960
- return disposePromise;
961
- }
962
- };
963
- return app;
964
- }
965
-
966
- // src/lifecycle/permissionVerifier.ts
967
- function verifyPermissionLease(options) {
968
- options.lease.assert(options.permission);
969
- if (options.binding) options.lease.assertBinding(options.binding);
970
- }
971
-
972
- // src/lifecycle/scopedRegistry.ts
973
- function definitionId(value) {
974
- if (!value || typeof value !== "object") return void 0;
975
- const id = value.id;
976
- return typeof id === "string" && id.length > 0 ? id : void 0;
977
- }
978
- function defaultRegistrationRules() {
979
- return [{ method: "register", idArgument: 0, unregisterMethod: "unregister" }];
980
- }
981
- function createScopedRegistryFacade(target, scope, options) {
982
- const rules = options.registrations ?? defaultRegistrationRules();
983
- const byMethod = new Map(rules.map((rule) => [rule.method, rule]));
984
- const unregisterMethods = new Set(
985
- rules.map((rule) => rule.unregisterMethod).filter((method) => Boolean(method))
986
- );
987
- const ownedRegistrations = /* @__PURE__ */ new Set();
988
- const objectTarget = target;
989
- const remember = (rule, id, result, args) => {
990
- const unregisterMethod = rule.unregisterMethod;
991
- if (typeof result !== "function" && (!unregisterMethod || id === void 0)) return result;
992
- const key = rule.method + ":" + (id ?? "returned");
993
- let active = true;
994
- let removeScopeRevoke = () => void 0;
995
- let removeScopeCleanup = () => void 0;
996
- const entry = {};
997
- const clearOwnership = (removeDispose = true) => {
998
- if (!active) return false;
999
- active = false;
1000
- ownedRegistrations.delete(entry);
1001
- removeScopeRevoke();
1002
- if (removeDispose) removeScopeCleanup();
1003
- return true;
1004
- };
1005
- const invokeUnregister = (...offArgs) => {
1006
- if (typeof result === "function") {
1007
- return result.apply(target, offArgs);
1008
- }
1009
- const unregister = objectTarget[unregisterMethod];
1010
- if (typeof unregister !== "function") return void 0;
1011
- const unregisterArgument = rule.unregisterArgument ?? rule.idArgument ?? 0;
1012
- const unregisterArgs = [...args];
1013
- unregisterArgs[unregisterArgument] = id;
1014
- return unregister.apply(target, unregisterArgs);
1015
- };
1016
- const cleanup = async (_reason) => {
1017
- if (!active) {
1018
- if (entry.revokedCleanup) await entry.revokedCleanup;
1019
- return;
1020
- }
1021
- if (!clearOwnership()) return;
1022
- await invokeUnregister();
1023
- };
1024
- const revokeNow = (_reason) => {
1025
- if (!clearOwnership(false)) return;
1026
- try {
1027
- const pending = invokeUnregister();
1028
- if (pending && typeof pending.then === "function") {
1029
- const revokedCleanup = Promise.resolve(pending).then(() => void 0);
1030
- entry.revokedCleanup = revokedCleanup;
1031
- revokedCleanup.catch(() => void 0);
1032
- }
1033
- } catch (error) {
1034
- entry.revokedCleanup = Promise.reject(error);
1035
- entry.revokedCleanup.catch(() => void 0);
1036
- }
1037
- };
1038
- entry.id = id;
1039
- entry.unregisterMethod = unregisterMethod;
1040
- entry.revokeNow = revokeNow;
1041
- Object.defineProperty(entry, "active", {
1042
- enumerable: true,
1043
- configurable: false,
1044
- get: () => active,
1045
- set: (value) => {
1046
- active = value;
1047
- }
1048
- });
1049
- entry.removeScopeCleanup = removeScopeCleanup;
1050
- ownedRegistrations.add(entry);
1051
- removeScopeCleanup = scope.onDispose(
1052
- cleanup,
1053
- options.name + ":" + key,
1054
- "after-teardown"
1055
- );
1056
- entry.removeScopeCleanup = removeScopeCleanup;
1057
- removeScopeRevoke = scope.onRevoke(revokeNow);
1058
- if (typeof result === "function") {
1059
- return (...offArgs) => {
1060
- if (!clearOwnership()) return void 0;
1061
- return result.apply(target, offArgs);
1062
- };
1063
- }
1064
- return result;
1065
- };
1066
- return new Proxy(target, {
1067
- get(current, property, receiver) {
1068
- const value = Reflect.get(current, property, receiver);
1069
- if (typeof value !== "function") return value;
1070
- const rule = typeof property === "string" ? byMethod.get(property) : void 0;
1071
- if (rule) {
1072
- return (...args) => {
1073
- scope.assertActive();
1074
- const callArgs = [...args];
1075
- if (rule.bindPluginIdArgument !== void 0 && scope.identity.pluginId) {
1076
- const claimedPluginId = callArgs[rule.bindPluginIdArgument];
1077
- if (claimedPluginId !== void 0 && claimedPluginId !== scope.identity.pluginId) {
1078
- throw new Error(
1079
- 'Registry owner "' + String(claimedPluginId) + '" does not match plugin instance "' + scope.identity.pluginId + '"'
1080
- );
1081
- }
1082
- callArgs[rule.bindPluginIdArgument] = scope.identity.pluginId;
1083
- }
1084
- if (rule.ownerPluginIdProperty && scope.identity.pluginId) {
1085
- const definition = callArgs[rule.idArgument ?? 0];
1086
- if (definition && typeof definition === "object") {
1087
- const claimedPluginId = definition[rule.ownerPluginIdProperty];
1088
- if (claimedPluginId !== void 0 && claimedPluginId !== scope.identity.pluginId) {
1089
- throw new Error(
1090
- 'Registry owner "' + String(claimedPluginId) + '" does not match plugin instance "' + scope.identity.pluginId + '"'
1091
- );
1092
- }
1093
- }
1094
- }
1095
- const result = value.apply(target, callArgs);
1096
- const id = rule.idArgument === void 0 ? void 0 : definitionId(callArgs[rule.idArgument]);
1097
- return remember(rule, id, result, callArgs);
1098
- };
1099
- }
1100
- if (typeof property === "string" && unregisterMethods.has(property)) {
1101
- return (...args) => {
1102
- scope.assertActive();
1103
- const id = definitionId(args[0]) ?? (typeof args[0] === "string" ? args[0] : void 0);
1104
- const owned = [...ownedRegistrations].find(
1105
- (entry) => entry.active && entry.id === id && entry.unregisterMethod === property
1106
- );
1107
- if (!owned) {
1108
- throw new Error(
1109
- 'Registry resource "' + (id ?? "unknown") + '" is not owned by this plugin instance'
1110
- );
1111
- }
1112
- const result = value.apply(target, args);
1113
- owned.active = false;
1114
- ownedRegistrations.delete(owned);
1115
- owned.removeScopeCleanup();
1116
- return result;
1117
- };
1118
- }
1119
- return value.bind(target);
1120
- }
1121
- });
1122
- }
1123
-
1124
- // src/lifecycle/upgradeGate.ts
1125
- function validGeneration(value) {
1126
- return Number.isSafeInteger(value) && value >= 0;
1127
- }
1128
- function uniqueStrings(values) {
1129
- return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))];
1130
- }
1131
- function makeSessionId() {
1132
- try {
1133
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1134
- return `upgrade-session:${crypto.randomUUID()}`;
1135
- }
1136
- } catch {
1137
- }
1138
- return `upgrade-session:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
1139
- }
1140
- function buildIsCompatible(options, buildId) {
1141
- try {
1142
- if (options.isBuildCompatible) return options.isBuildCompatible(buildId);
1143
- } catch {
1144
- return false;
1145
- }
1146
- return buildId === options.buildId || options.compatibleBuildIds?.has(buildId) === true;
1147
- }
1148
- function createUpgradeGate(options) {
1149
- if (!options.protocolVersion || !options.buildId || !options.authorityInstanceId) {
1150
- throw new Error("\u5347\u7EA7\u95E8\u7981\u7684 protocolVersion\u3001buildId \u548C authorityInstanceId \u5FC5\u987B\u6709\u6548");
1151
- }
1152
- if (!validGeneration(options.handoverGeneration)) {
1153
- throw new Error("\u5347\u7EA7\u95E8\u7981\u7684 handoverGeneration \u5FC5\u987B\u662F\u975E\u8D1F\u5B89\u5168\u6574\u6570");
1154
- }
1155
- const contractVersions = uniqueStrings(options.supportedContractVersions);
1156
- if (contractVersions.length === 0) {
1157
- throw new Error("\u5347\u7EA7\u95E8\u7981\u81F3\u5C11\u9700\u8981\u4E00\u4E2A supportedContractVersions");
1158
- }
1159
- let currentState = "active";
1160
- let closeReason = "upgrade gate closed";
1161
- const leases = /* @__PURE__ */ new Set();
1162
- const sessions = /* @__PURE__ */ new Set();
1163
- const sessionByObject = /* @__PURE__ */ new WeakMap();
1164
- const emptyWaiters = /* @__PURE__ */ new Set();
1165
- const notifyEmpty = () => {
1166
- if (leases.size !== 0) return;
1167
- for (const resolve of [...emptyWaiters]) {
1168
- emptyWaiters.delete(resolve);
1169
- resolve();
1170
- }
1171
- };
1172
- const removeLease = (record) => {
1173
- if (!leases.delete(record)) return;
1174
- record.session.leases.delete(record);
1175
- record.removeExternalAbort?.();
1176
- record.removeExternalAbort = void 0;
1177
- notifyEmpty();
1178
- };
1179
- const revokeSession = (record, reason) => {
1180
- if (record.revoked) return;
1181
- record.revoked = true;
1182
- record.reason = reason;
1183
- try {
1184
- record.controller.abort(new UpgradeGateRejectedError(reason));
1185
- } catch {
1186
- record.controller.abort();
1187
- }
1188
- for (const lease of [...record.leases]) revokeLease(lease, reason);
1189
- sessions.delete(record);
1190
- };
1191
- const revokeLease = (record, reason) => {
1192
- if (record.revoked) return;
1193
- record.revoked = true;
1194
- record.reason = reason;
1195
- try {
1196
- record.controller.abort(new UpgradeGateRejectedError(reason));
1197
- } catch {
1198
- record.controller.abort();
1199
- }
1200
- removeLease(record);
1201
- };
1202
- const assertAccepting = () => {
1203
- if (currentState === "active") return;
1204
- throw new UpgradeGateRejectedError(currentState === "draining" ? "draining" : "closed", closeReason);
1205
- };
1206
- const handshake = (input) => {
1207
- if (currentState !== "active") {
1208
- return { accepted: false, reason: currentState === "draining" ? "draining" : "closed" };
1209
- }
1210
- if (typeof input.connectionId !== "string" || input.connectionId.length === 0 || input.protocolVersion !== options.protocolVersion || typeof input.protocolVersion !== "string" || typeof input.buildId !== "string" || typeof input.authorityInstanceId !== "string" || input.authorityInstanceId.length === 0) {
1211
- return { accepted: false, reason: "protocol-mismatch" };
1212
- }
1213
- if (!buildIsCompatible(options, input.buildId)) {
1214
- return { accepted: false, reason: "build-incompatible" };
1215
- }
1216
- if (!validGeneration(input.handoverGeneration)) {
1217
- return { accepted: false, reason: "stale-generation" };
1218
- }
1219
- if (input.handoverGeneration < options.handoverGeneration) {
1220
- return { accepted: false, reason: "stale-generation" };
1221
- }
1222
- if (input.handoverGeneration > options.handoverGeneration) {
1223
- return { accepted: false, reason: "future-generation" };
1224
- }
1225
- const contractVersion = contractVersions.find((version) => input.supportedContractVersions.includes(version));
1226
- if (!contractVersion) return { accepted: false, reason: "contract-mismatch" };
1227
- const sessionRecord = {
1228
- sessionId: makeSessionId(),
1229
- connectionId: input.connectionId,
1230
- contractVersion,
1231
- controller: new AbortController(),
1232
- revoked: false,
1233
- reason: "upgrade session closed",
1234
- leases: /* @__PURE__ */ new Set()
1235
- };
1236
- const session = {
1237
- connectionId: sessionRecord.connectionId,
1238
- sessionId: sessionRecord.sessionId,
1239
- authorityInstanceId: options.authorityInstanceId,
1240
- handoverGeneration: options.handoverGeneration,
1241
- contractVersion,
1242
- get revoked() {
1243
- return sessionRecord.revoked || !sessions.has(sessionRecord);
1244
- },
1245
- signal: sessionRecord.controller.signal,
1246
- assertActive() {
1247
- if (sessionRecord.revoked || !sessions.has(sessionRecord)) {
1248
- throw new UpgradeGateRejectedError(sessionRecord.reason, "\u5347\u7EA7\u63E1\u624B\u4F1A\u8BDD\u5DF2\u5931\u6548");
1249
- }
1250
- if (currentState === "closed") {
1251
- throw new UpgradeGateRejectedError("closed", closeReason);
1252
- }
1253
- },
1254
- admit(input2) {
1255
- return admitForSession(sessionRecord, input2);
1256
- },
1257
- close(reason = "upgrade session closed") {
1258
- revokeSession(sessionRecord, reason);
1259
- }
1260
- };
1261
- sessionRecord.session = session;
1262
- sessions.add(sessionRecord);
1263
- sessionByObject.set(session, sessionRecord);
1264
- return {
1265
- accepted: true,
1266
- mode: options.mode ?? "cold-switch",
1267
- handoverGeneration: options.handoverGeneration,
1268
- contractVersion,
1269
- connectionId: sessionRecord.connectionId,
1270
- sessionId: sessionRecord.sessionId,
1271
- session
1272
- };
1273
- };
1274
- const admitForSession = (sessionRecord, input) => {
1275
- if (sessionRecord.revoked || !sessions.has(sessionRecord)) {
1276
- throw new UpgradeGateRejectedError(sessionRecord.reason, "\u5347\u7EA7\u63E1\u624B\u4F1A\u8BDD\u5DF2\u5931\u6548");
1277
- }
1278
- assertAccepting();
1279
- if (input.signal?.aborted) {
1280
- throw new UpgradeGateRejectedError("caller-aborted", "I/O \u5728\u53D6\u5F97\u63A5\u7BA1\u79DF\u7EA6\u524D\u5DF2\u53D6\u6D88");
1281
- }
1282
- const record = {
1283
- session: sessionRecord,
1284
- operation: input.operation,
1285
- controller: new AbortController(),
1286
- revoked: false,
1287
- reason: "upgrade I/O lease released"
1288
- };
1289
- leases.add(record);
1290
- sessionRecord.leases.add(record);
1291
- if (input.signal) {
1292
- const onAbort = () => revokeLease(record, "caller-aborted");
1293
- input.signal.addEventListener("abort", onAbort, { once: true });
1294
- record.removeExternalAbort = () => input.signal?.removeEventListener("abort", onAbort);
1295
- }
1296
- const lease = {
1297
- connectionId: sessionRecord.connectionId,
1298
- sessionId: sessionRecord.sessionId,
1299
- authorityInstanceId: options.authorityInstanceId,
1300
- handoverGeneration: options.handoverGeneration,
1301
- contractVersion: sessionRecord.contractVersion,
1302
- operation: record.operation,
1303
- get revoked() {
1304
- return record.revoked || !leases.has(record);
1305
- },
1306
- signal: record.controller.signal,
1307
- assertActive() {
1308
- if (record.revoked || !leases.has(record)) {
1309
- throw new UpgradeGateRejectedError(record.reason, "\u5347\u7EA7 I/O \u79DF\u7EA6\u5DF2\u5931\u6548");
1310
- }
1311
- if (currentState === "closed") {
1312
- throw new UpgradeGateRejectedError("closed", closeReason);
1313
- }
1314
- },
1315
- release() {
1316
- if (record.revoked) return;
1317
- record.revoked = true;
1318
- record.reason = "upgrade I/O lease released";
1319
- removeLease(record);
1320
- }
1321
- };
1322
- return lease;
1323
- };
1324
- const admit = (input) => {
1325
- const sessionRecord = sessionByObject.get(input.session);
1326
- if (!sessionRecord) {
1327
- throw new UpgradeGateRejectedError("invalid-session", "I/O \u5FC5\u987B\u4F7F\u7528\u5F53\u524D\u95E8\u7981\u63E1\u624B\u8FD4\u56DE\u7684\u4F1A\u8BDD");
1328
- }
1329
- return admitForSession(sessionRecord, input);
1330
- };
1331
- const beginDrain = (reason = "upgrade handover draining") => {
1332
- if (currentState !== "active") return;
1333
- closeReason = reason;
1334
- currentState = "draining";
1335
- };
1336
- const drain = async (timeoutMs) => {
1337
- beginDrain();
1338
- if (leases.size === 0) return { state: currentState, drained: true, pending: 0 };
1339
- if (timeoutMs !== void 0 && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
1340
- throw new Error("\u5347\u7EA7\u6392\u7A7A timeoutMs \u5FC5\u987B\u662F\u975E\u8D1F\u6709\u9650\u6570");
1341
- }
1342
- let timeout;
1343
- let timerResolve;
1344
- const empty = new Promise((resolve) => {
1345
- emptyWaiters.add(resolve);
1346
- timerResolve = resolve;
1347
- });
1348
- const timeoutPromise = timeoutMs === void 0 ? void 0 : new Promise((resolve) => {
1349
- timeout = setTimeout(resolve, timeoutMs);
1350
- });
1351
- if (timeoutPromise) await Promise.race([empty, timeoutPromise]);
1352
- else await empty;
1353
- if (timeout !== void 0) clearTimeout(timeout);
1354
- if (timerResolve) emptyWaiters.delete(timerResolve);
1355
- const pending = leases.size;
1356
- return { state: currentState, drained: pending === 0, pending };
1357
- };
1358
- const close = (reason = "upgrade gate closed") => {
1359
- if (currentState === "closed") return;
1360
- closeReason = reason;
1361
- currentState = "closed";
1362
- for (const session of [...sessions]) revokeSession(session, reason);
1363
- for (const lease of [...leases]) revokeLease(lease, reason);
1364
- notifyEmpty();
1365
- };
1366
- return {
1367
- get state() {
1368
- return currentState;
1369
- },
1370
- mode: options.mode ?? "cold-switch",
1371
- authorityInstanceId: options.authorityInstanceId,
1372
- handoverGeneration: options.handoverGeneration,
1373
- handshake,
1374
- assertAccepting,
1375
- admit,
1376
- beginDrain,
1377
- drain,
1378
- close,
1379
- activeIo: () => leases.size
1380
- };
1381
- }
1382
-
1383
- export { createMessagePortServiceProvider, createScopedRegistryFacade, createUpgradeGate, createWindowApp, definePlugin, defineRuntimeUnitDependencies, defineRuntimeUnitProvidedContracts, materializePluginDefinition, materializePluginDefinitions, runtimeCapabilityContractVersion, startSharedWorkerApp, verifyPermissionLease };
62
+ export { RUNTIME_MESSAGE_BUS, definePlugin };
1384
63
  //# sourceMappingURL=index.js.map
1385
64
  //# sourceMappingURL=index.js.map