@antelopejs/interface-core 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/errors.d.ts +12 -0
  2. package/dist/errors.js +21 -1
  3. package/dist/errors.js.map +1 -1
  4. package/dist/index.d.ts +7 -9
  5. package/dist/index.js +91 -25
  6. package/dist/index.js.map +1 -1
  7. package/dist/internal.d.ts +87 -12
  8. package/dist/internal.js +108 -21
  9. package/dist/internal.js.map +1 -1
  10. package/dist/modules.d.ts +6 -0
  11. package/dist/modules.js +67 -28
  12. package/dist/modules.js.map +1 -1
  13. package/dist/proxies.d.ts +64 -129
  14. package/dist/proxies.js +372 -220
  15. package/dist/proxies.js.map +1 -1
  16. package/dist/runtime.js +2 -2
  17. package/dist/runtime.js.map +1 -1
  18. package/dist/tests/generation-cleanup.test.d.ts +1 -0
  19. package/dist/tests/generation-cleanup.test.js +116 -0
  20. package/dist/tests/generation-cleanup.test.js.map +1 -0
  21. package/dist/tests/implement-interface-validation.test.d.ts +1 -0
  22. package/dist/tests/implement-interface-validation.test.js +45 -0
  23. package/dist/tests/implement-interface-validation.test.js.map +1 -0
  24. package/dist/tests/interface-connections.test.d.ts +1 -0
  25. package/dist/tests/interface-connections.test.js +36 -0
  26. package/dist/tests/interface-connections.test.js.map +1 -0
  27. package/dist/tests/module-ownership-context.test.js +18 -0
  28. package/dist/tests/module-ownership-context.test.js.map +1 -1
  29. package/dist/tests/provider-context.test.d.ts +1 -0
  30. package/dist/tests/provider-context.test.js +117 -0
  31. package/dist/tests/provider-context.test.js.map +1 -0
  32. package/dist/tests/provider-routing.test.d.ts +1 -0
  33. package/dist/tests/provider-routing.test.js +78 -0
  34. package/dist/tests/provider-routing.test.js.map +1 -0
  35. package/dist/tests/queue-and-cleanup.test.d.ts +1 -0
  36. package/dist/tests/queue-and-cleanup.test.js +73 -0
  37. package/dist/tests/queue-and-cleanup.test.js.map +1 -0
  38. package/dist/tests/registering-proxy-replay.test.js +9 -0
  39. package/dist/tests/registering-proxy-replay.test.js.map +1 -1
  40. package/dist/tests/runtime-protocol.test.d.ts +1 -0
  41. package/dist/tests/runtime-protocol.test.js +49 -0
  42. package/dist/tests/runtime-protocol.test.js.map +1 -0
  43. package/dist/tests/runtime.test.js +18 -2
  44. package/dist/tests/runtime.test.js.map +1 -1
  45. package/docs/2.proxies.md +18 -1
  46. package/docs/5.modules.md +22 -0
  47. package/package.json +3 -2
package/dist/proxies.js CHANGED
@@ -1,265 +1,426 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EventProxy = exports.RegisteringProxy = exports.AsyncProxy = void 0;
4
+ exports.IsInterfaceProxy = IsInterfaceProxy;
5
+ exports.GetInterfaceProxyIdentity = GetInterfaceProxyIdentity;
4
6
  exports.InvalidateResponsibleModule = InvalidateResponsibleModule;
5
7
  exports.RunWithResponsibleModule = RunWithResponsibleModule;
6
8
  exports.GetResponsibleModule = GetResponsibleModule;
7
- const node_async_hooks_1 = require("node:async_hooks");
8
9
  const errors_1 = require("./errors");
9
10
  const internal_1 = require("./internal");
10
11
  const responsible_module_1 = require("./responsible-module");
11
- const responsibleModuleContext = new node_async_hooks_1.AsyncLocalStorage();
12
- const activeResponsibleModuleTokens = new Map();
13
- function getResponsibleModuleToken(module) {
14
- const activeToken = activeResponsibleModuleTokens.get(module);
15
- if (activeToken) {
16
- return activeToken;
17
- }
18
- const token = Symbol(module);
19
- activeResponsibleModuleTokens.set(module, token);
20
- return token;
12
+ const PROXY_BRAND = Symbol.for("@antelopejs/interface-core/proxy");
13
+ const DEFAULT_PROVIDER = "@antelopejs/interface-core/default-provider";
14
+ function createIdentity(kind, identity) {
15
+ if (identity) {
16
+ return `${kind}:${identity}`;
17
+ }
18
+ const nextIdentity = internal_1.internal.nextProxyIdentity++;
19
+ return `${kind}:anonymous:${nextIdentity}`;
20
+ }
21
+ function getProxyState(brand, create) {
22
+ const existing = internal_1.internal.proxyStates.get(brand.identity);
23
+ if (existing && existing.kind !== brand.kind) {
24
+ throw new Error(`Proxy identity ${brand.identity} has conflicting kinds.`);
25
+ }
26
+ if (existing) {
27
+ return existing.value;
28
+ }
29
+ const value = create();
30
+ internal_1.internal.proxyStates.set(brand.identity, { kind: brand.kind, value });
31
+ return value;
32
+ }
33
+ function createBrand(kind, identity) {
34
+ return Object.freeze({
35
+ protocol: internal_1.RUNTIME_PROTOCOL_VERSION,
36
+ kind,
37
+ identity: createIdentity(kind, identity),
38
+ });
21
39
  }
22
- function assertActiveContext(context) {
23
- if (activeResponsibleModuleTokens.get(context.module) !== context.token) {
24
- throw new errors_1.ModuleContextInvalidatedError(context.module);
40
+ function readBrand(value) {
41
+ if ((typeof value !== "object" && typeof value !== "function") || !value) {
42
+ return;
43
+ }
44
+ const brand = value[PROXY_BRAND];
45
+ if (!brand || brand.protocol !== internal_1.RUNTIME_PROTOCOL_VERSION) {
46
+ return;
25
47
  }
48
+ return brand;
49
+ }
50
+ /** Returns whether a value implements this runtime's stable proxy protocol. */
51
+ function IsInterfaceProxy(value, kind) {
52
+ const brand = readBrand(value);
53
+ return Boolean(brand && (!kind || brand.kind === kind));
54
+ }
55
+ /** Returns the stable identity used to bind a proxy to a provider route. */
56
+ function GetInterfaceProxyIdentity(value) {
57
+ return readBrand(value)?.identity;
58
+ }
59
+ function getAttachmentRoute(manualDetach) {
60
+ const context = (0, internal_1.getModuleContext)();
61
+ const responsible = manualDetach || context?.module ? undefined : GetResponsibleModule();
62
+ const owner = context?.owner ?? context?.module ?? responsible ?? DEFAULT_PROVIDER;
63
+ return { owner, provider: context?.provider ?? owner };
64
+ }
65
+ function getRequestedProvider(proxyIdentity) {
66
+ const context = (0, internal_1.getModuleContext)();
67
+ return context?.providerRoutes?.[proxyIdentity] ?? context?.provider;
68
+ }
69
+ function bindProviderCallback(callback) {
70
+ const context = (0, internal_1.captureModuleContext)();
71
+ if (!context) {
72
+ return callback;
73
+ }
74
+ return ((...args) => (0, internal_1.runWithCapturedModuleContext)(context, () => callback(...args)));
75
+ }
76
+ function getExecutionOwnership() {
77
+ const context = (0, internal_1.getModuleContext)();
78
+ if (context) {
79
+ return { module: context.module, owner: context.owner ?? context.module };
80
+ }
81
+ const module = GetResponsibleModule();
82
+ return { module, owner: module };
83
+ }
84
+ function selectProvider(callbacks, proxyIdentity, requested) {
85
+ if (requested) {
86
+ const callback = callbacks.get(requested);
87
+ if (!callback && callbacks.size > 0) {
88
+ throw new errors_1.MissingProviderError(`Interface proxy ${proxyIdentity} has no provider for route ${requested}.`);
89
+ }
90
+ return callback;
91
+ }
92
+ if (callbacks.size <= 1) {
93
+ return callbacks.values().next().value;
94
+ }
95
+ throw new errors_1.AmbiguousProviderError(proxyIdentity, [...callbacks.keys()]);
96
+ }
97
+ function reportRuntimeError(error, operation, proxyIdentity, module, registrationId) {
98
+ internal_1.internal.runtimeErrorReporter?.(error, {
99
+ operation,
100
+ module,
101
+ proxyIdentity,
102
+ registrationId,
103
+ });
104
+ }
105
+ function matchesLease(attachment, lease) {
106
+ return (attachment?.generation === lease.generation &&
107
+ attachment.owner === lease.owner);
26
108
  }
27
109
  /** @internal */
28
110
  function InvalidateResponsibleModule(module) {
29
- activeResponsibleModuleTokens.delete(module);
111
+ (0, internal_1.invalidateModuleContext)(module);
30
112
  }
31
- /**
32
- * Runs work with an explicit responsible module.
33
- *
34
- * The module remains available to nested synchronous and asynchronous work.
35
- * Calls outside this context continue to use stack-based module resolution.
36
- *
37
- * @param module Module ID responsible for the work
38
- * @param callback Work to run in the module context
39
- * @returns The callback result
40
- */
113
+ /** Runs work with explicit module ownership across asynchronous boundaries. */
41
114
  function RunWithResponsibleModule(module, callback) {
42
- const inheritedContext = responsibleModuleContext.getStore();
43
- if (inheritedContext) {
44
- assertActiveContext(inheritedContext);
45
- }
46
- const context = { module, token: getResponsibleModuleToken(module) };
47
- return responsibleModuleContext.run(context, callback);
115
+ return (0, internal_1.runWithModuleContext)({ module }, callback);
48
116
  }
49
- /**
50
- * Proxy for an asynchronous function.
51
- *
52
- * Queues up calls while unattached, automatically unattaches when the source module is unloaded.
53
- * Provides a mechanism for delayed execution and module-aware function binding.
54
- */
117
+ /** Proxy for an asynchronous interface function. */
55
118
  class AsyncProxy {
56
- callback;
57
- queue = [];
58
- /**
59
- * Attaches a callback to the proxy
60
- *
61
- * Automatically detached if the module calling this function gets unloaded and manualDetach is not set to true.
62
- * When attached, any queued calls will be executed immediately.
63
- *
64
- * @param callback Function to attach
65
- * @param manualDetach Don't detach automatically when module is unloaded
66
- */
119
+ [PROXY_BRAND];
120
+ state;
121
+ constructor(identity) {
122
+ this[PROXY_BRAND] = createBrand("async", identity);
123
+ this.state = getProxyState(this[PROXY_BRAND], () => ({
124
+ callbacks: new Map(),
125
+ queue: [],
126
+ }));
127
+ }
128
+ /** Attaches a provider callback and replays compatible queued calls. */
67
129
  onCall(callback, manualDetach) {
68
- const caller = manualDetach ? undefined : GetResponsibleModule();
69
- this.callback = callback;
70
- if (caller) {
71
- internal_1.internal.addAsyncProxy(caller, this);
72
- }
73
- if (this.queue.length > 0) {
74
- this.queue.forEach(({ args, resolve, reject }) => {
75
- try {
76
- resolve(callback(...args));
77
- }
78
- catch (err) {
79
- reject(err);
80
- }
130
+ const route = getAttachmentRoute(manualDetach);
131
+ const lease = { ...route, generation: internal_1.internal.nextLeaseGeneration++ };
132
+ const providerCallback = bindProviderCallback(callback);
133
+ this.state.callbacks.set(route.provider, {
134
+ callback: providerCallback,
135
+ ...lease,
136
+ });
137
+ if (!manualDetach) {
138
+ internal_1.internal.addAsyncProxy(route.owner, {
139
+ cleanup: () => this.detach(lease),
81
140
  });
82
- this.queue.splice(0, this.queue.length);
141
+ }
142
+ this.replayQueue(route.provider, providerCallback);
143
+ return lease;
144
+ }
145
+ /** Detaches one leased provider, or every provider when called without a lease. */
146
+ detach(lease) {
147
+ if (!lease) {
148
+ this.state.callbacks.clear();
149
+ return;
150
+ }
151
+ const current = this.state.callbacks.get(lease.provider);
152
+ if (current?.generation === lease.generation &&
153
+ current.owner === lease.owner) {
154
+ this.state.callbacks.delete(lease.provider);
83
155
  }
84
156
  }
85
- /**
86
- * Manually detach the callback on this proxy.
87
- */
88
- detach() {
89
- this.callback = undefined;
90
- }
91
- /**
92
- * Call the function attached to this proxy.
93
- *
94
- * If a callback has not been attached yet, the call is queued up for later.
95
- */
157
+ /** Calls the provider selected by the current module execution context. */
96
158
  call(...args) {
97
- if (this.callback) {
98
- try {
99
- return Promise.resolve(this.callback(...args));
100
- }
101
- catch (err) {
102
- return Promise.reject(err);
103
- }
159
+ let requested;
160
+ let attachment;
161
+ try {
162
+ requested = getRequestedProvider(this[PROXY_BRAND].identity);
163
+ attachment = selectProvider(this.state.callbacks, this[PROXY_BRAND].identity, requested);
164
+ }
165
+ catch (error) {
166
+ return Promise.reject(error);
167
+ }
168
+ if (attachment) {
169
+ return this.invoke(attachment.callback, args);
104
170
  }
105
171
  if (internal_1.internal.testStubMode) {
106
172
  return Promise.reject(new errors_1.MissingProviderError());
107
173
  }
108
- return new Promise((resolve, reject) => this.queue.push({ args, resolve, reject }));
174
+ if (this.state.queue.length >= internal_1.internal.maxPendingOperations) {
175
+ return Promise.reject(new errors_1.ProviderQueueFullError(this[PROXY_BRAND].identity, internal_1.internal.maxPendingOperations));
176
+ }
177
+ return new Promise((resolve, reject) => {
178
+ this.state.queue.push({ args, provider: requested, resolve, reject });
179
+ });
180
+ }
181
+ invoke(callback, args) {
182
+ try {
183
+ return Promise.resolve(callback(...args));
184
+ }
185
+ catch (error) {
186
+ return Promise.reject(error);
187
+ }
188
+ }
189
+ replayQueue(provider, callback) {
190
+ const remaining = [];
191
+ for (const pending of this.state.queue) {
192
+ if (pending.provider && pending.provider !== provider) {
193
+ remaining.push(pending);
194
+ continue;
195
+ }
196
+ this.invoke(callback, pending.args).then(pending.resolve, pending.reject);
197
+ }
198
+ this.state.queue = remaining;
109
199
  }
110
200
  }
111
201
  exports.AsyncProxy = AsyncProxy;
112
- /**
113
- * Proxy for a pair of register/unregister functions.
114
- *
115
- * Manages registration of handlers and ensures proper cleanup when modules are unloaded.
116
- * This allows for module-aware event registration with automatic cleanup.
117
- */
202
+ /** Proxy for provider-aware register and unregister handlers. */
118
203
  class RegisteringProxy {
119
- registerCallback;
120
- unregisterCallback;
121
- registered = new Map();
122
- /**
123
- * Attaches a register callback to the proxy
124
- *
125
- * Automatically detached if the module calling this function gets unloaded and manualDetach is not set to true.
126
- *
127
- * @param callback Function to attach as the register callback
128
- * @param manualDetach Don't detach automatically
129
- */
204
+ [PROXY_BRAND];
205
+ state;
206
+ constructor(identity) {
207
+ this[PROXY_BRAND] = createBrand("registering", identity);
208
+ this.state = getProxyState(this[PROXY_BRAND], () => ({
209
+ callbacks: new Map(),
210
+ registered: new Map(),
211
+ }));
212
+ internal_1.internal.registeringProxies.add(this);
213
+ }
214
+ /** Attaches a register callback. */
130
215
  onRegister(callback, manualDetach) {
131
- const caller = manualDetach ? undefined : GetResponsibleModule();
132
- this.registerCallback = callback;
133
- if (caller) {
134
- internal_1.internal.addRegisteringProxy(caller, this);
135
- }
136
- for (const [id, { args }] of this.registered) {
137
- try {
138
- callback(id, ...args);
139
- }
140
- catch (err) {
141
- // Best-effort replay: one throwing consumer must not strand the others.
142
- if (internal_1.internal.replayErrorReporter) {
143
- internal_1.internal.replayErrorReporter(id, err);
144
- }
216
+ const route = getAttachmentRoute(manualDetach);
217
+ return this.attachRegister(bindProviderCallback(callback), route, Boolean(manualDetach));
218
+ }
219
+ /** Attaches an unregister callback to the current provider route. */
220
+ onUnregister(callback) {
221
+ const context = (0, internal_1.getModuleContext)();
222
+ const requested = context?.provider ?? context?.module;
223
+ const current = selectProvider(this.state.callbacks, this[PROXY_BRAND].identity, requested);
224
+ const contextOwner = context?.owner ?? context?.module;
225
+ const attachments = [current?.unregister, current?.register];
226
+ const sibling = contextOwner
227
+ ? attachments.find((attachment) => attachment?.owner === contextOwner)
228
+ : attachments.find((attachment) => Boolean(attachment));
229
+ const canExtendCurrent = current && sibling;
230
+ const options = canExtendCurrent
231
+ ? {
232
+ route: { owner: sibling.owner, provider: current.provider },
233
+ manualDetach: sibling.manualDetach,
145
234
  }
235
+ : { route: getAttachmentRoute(), manualDetach: false };
236
+ return this.attachUnregister(bindProviderCallback(callback), options);
237
+ }
238
+ /** Atomically attaches both registration handlers. */
239
+ onHandlers(register, unregister, manualDetach) {
240
+ const route = getAttachmentRoute(manualDetach);
241
+ const lease = this.createLease(route);
242
+ const boundRegister = bindProviderCallback(register);
243
+ const boundUnregister = bindProviderCallback(unregister);
244
+ this.state.callbacks.set(route.provider, {
245
+ provider: route.provider,
246
+ register: this.createAttachment(boundRegister, lease, manualDetach),
247
+ unregister: this.createAttachment(boundUnregister, lease, manualDetach),
248
+ });
249
+ this.trackAttachment(lease, Boolean(manualDetach));
250
+ this.replayRegistrations(route.provider, boundRegister);
251
+ return lease;
252
+ }
253
+ /** Detaches one leased provider, or every provider when called without a lease. */
254
+ detach(lease) {
255
+ if (!lease) {
256
+ this.state.callbacks.clear();
257
+ return;
258
+ }
259
+ const current = this.state.callbacks.get(lease.provider);
260
+ if (!current) {
261
+ return;
262
+ }
263
+ if (matchesLease(current.register, lease)) {
264
+ current.register = undefined;
265
+ }
266
+ if (matchesLease(current.unregister, lease)) {
267
+ current.unregister = undefined;
268
+ }
269
+ if (!current.register && !current.unregister) {
270
+ this.state.callbacks.delete(lease.provider);
146
271
  }
147
272
  }
148
- /**
149
- * Attaches an unregister callback to the proxy
150
- *
151
- * Detached at the same time as the register callback.
152
- *
153
- * @param callback Function to attach as the unregister callback
154
- */
155
- onUnregister(callback) {
156
- this.unregisterCallback = callback;
157
- }
158
- /**
159
- * Manually detach the callbacks on this proxy.
160
- */
161
- detach() {
162
- this.registerCallback = undefined;
163
- this.unregisterCallback = undefined;
164
- }
165
- /**
166
- * Call the register callback attached to this proxy.
167
- *
168
- * If a callback has not been attached yet, the call is queued up for later.
169
- *
170
- * @param id Unique identifier used to unregister
171
- * @param args Extra arguments
172
- */
273
+ /** Registers an entry with the selected provider or queues it for bootstrap. */
173
274
  register(id, ...args) {
174
- if (!this.registerCallback && internal_1.internal.testStubMode) {
275
+ const requested = getRequestedProvider(this[PROXY_BRAND].identity);
276
+ const callback = selectProvider(this.state.callbacks, this[PROXY_BRAND].identity, requested);
277
+ if (!callback && internal_1.internal.testStubMode) {
175
278
  throw new errors_1.MissingProviderError();
176
279
  }
177
- const module = GetResponsibleModule();
178
- this.registered.set(id, { module, args });
179
- if (this.registerCallback) {
180
- this.registerCallback(id, ...args);
280
+ if (!callback &&
281
+ !this.state.registered.has(id) &&
282
+ this.state.registered.size >= internal_1.internal.maxPendingOperations) {
283
+ throw new errors_1.ProviderQueueFullError(this[PROXY_BRAND].identity, internal_1.internal.maxPendingOperations);
181
284
  }
285
+ const ownership = getExecutionOwnership();
286
+ this.state.registered.set(id, {
287
+ ...ownership,
288
+ provider: requested ?? callback?.provider,
289
+ args,
290
+ });
291
+ callback?.register?.callback(id, ...args);
182
292
  }
183
- /**
184
- * Call the unregister callback attached to this proxy.
185
- *
186
- * @param id Unique identifier to unregister
187
- */
293
+ /** Unregisters an entry from the provider that accepted it. */
188
294
  unregister(id) {
189
- if (this.registered.has(id)) {
190
- if (this.unregisterCallback) {
191
- this.unregisterCallback(id);
295
+ const entry = this.state.registered.get(id);
296
+ if (!entry) {
297
+ return;
298
+ }
299
+ const callback = selectProvider(this.state.callbacks, this[PROXY_BRAND].identity, entry.provider);
300
+ try {
301
+ callback?.unregister?.callback(id);
302
+ }
303
+ finally {
304
+ this.state.registered.delete(id);
305
+ }
306
+ }
307
+ /** Unregisters every entry owned by a destroyed module. */
308
+ unregisterModule(module) {
309
+ this.unregisterMatching((entry) => entry.module === module, module);
310
+ }
311
+ /** Unregisters every entry owned by a destroyed module generation. */
312
+ unregisterOwner(owner) {
313
+ this.unregisterMatching((entry) => entry.owner === owner, owner);
314
+ }
315
+ unregisterMatching(matches, owner) {
316
+ for (const [id, entry] of this.state.registered) {
317
+ if (!matches(entry)) {
318
+ continue;
192
319
  }
193
- this.registered.delete(id);
320
+ try {
321
+ this.unregister(id);
322
+ }
323
+ catch (error) {
324
+ reportRuntimeError(error, "unregister", this[PROXY_BRAND].identity, owner, id);
325
+ }
326
+ finally {
327
+ this.state.registered.delete(id);
328
+ }
329
+ }
330
+ }
331
+ attachRegister(callback, route, manualDetach) {
332
+ const lease = this.createLease(route);
333
+ const current = this.state.callbacks.get(route.provider);
334
+ this.state.callbacks.set(route.provider, {
335
+ provider: route.provider,
336
+ register: this.createAttachment(callback, lease, manualDetach),
337
+ unregister: current?.unregister,
338
+ });
339
+ this.trackAttachment(lease, manualDetach);
340
+ this.replayRegistrations(route.provider, callback);
341
+ return lease;
342
+ }
343
+ attachUnregister(callback, options) {
344
+ const { route, manualDetach } = options;
345
+ const lease = this.createLease(route);
346
+ const current = this.state.callbacks.get(route.provider);
347
+ this.state.callbacks.set(route.provider, {
348
+ provider: route.provider,
349
+ register: current?.register,
350
+ unregister: this.createAttachment(callback, lease, manualDetach),
351
+ });
352
+ this.trackAttachment(lease, manualDetach);
353
+ return lease;
354
+ }
355
+ trackAttachment(lease, manualDetach) {
356
+ if (!manualDetach) {
357
+ internal_1.internal.addRegisteringProxy(lease.owner, {
358
+ cleanup: () => this.detach(lease),
359
+ unregisterModule: (module) => this.unregisterModule(module),
360
+ });
194
361
  }
195
362
  }
196
- /**
197
- * Unregister all entries created by the given module
198
- * @internal
199
- *
200
- * @param mod Module ID
201
- */
202
- unregisterModule(mod) {
203
- for (const [id, { module }] of this.registered) {
204
- if (module === mod) {
205
- if (this.unregisterCallback) {
206
- this.unregisterCallback(id);
207
- }
208
- this.registered.delete(id);
363
+ createLease(route) {
364
+ return { ...route, generation: internal_1.internal.nextLeaseGeneration++ };
365
+ }
366
+ createAttachment(callback, lease, manualDetach) {
367
+ return { callback, ...lease, manualDetach: Boolean(manualDetach) };
368
+ }
369
+ replayRegistrations(provider, callback) {
370
+ for (const [id, entry] of this.state.registered) {
371
+ if (entry.provider && entry.provider !== provider) {
372
+ continue;
373
+ }
374
+ try {
375
+ callback(id, ...entry.args);
376
+ entry.provider = provider;
377
+ }
378
+ catch (error) {
379
+ internal_1.internal.replayErrorReporter?.(id, error);
380
+ reportRuntimeError(error, "register-replay", this[PROXY_BRAND].identity, entry.module, id);
209
381
  }
210
382
  }
211
383
  }
212
384
  }
213
385
  exports.RegisteringProxy = RegisteringProxy;
214
- /**
215
- * Event handler list that automatically removes handlers from unloaded modules.
216
- *
217
- * Provides a module-aware event system that cleans up event handlers when modules are unloaded,
218
- * preventing memory leaks and ensuring proper modularity.
219
- */
386
+ /** Module-aware event handler collection. */
220
387
  class EventProxy {
221
- registered = [];
222
- constructor() {
223
- internal_1.internal.knownEvents.push(this);
224
- }
225
- /**
226
- * Call all the event handlers with the specified arguments.
227
- *
228
- * @param args Arguments
229
- */
388
+ [PROXY_BRAND];
389
+ state;
390
+ constructor(identity) {
391
+ this[PROXY_BRAND] = createBrand("event", identity);
392
+ this.state = getProxyState(this[PROXY_BRAND], () => ({ registered: [] }));
393
+ internal_1.internal.knownEvents.add(this);
394
+ }
395
+ /** Emits to every handler, reporting failures without aborting later handlers. */
230
396
  emit(...args) {
231
- for (const { func } of this.registered) {
232
- func(...args);
397
+ for (const { func, module } of this.state.registered) {
398
+ try {
399
+ func(...args);
400
+ }
401
+ catch (error) {
402
+ reportRuntimeError(error, "event-emit", this[PROXY_BRAND].identity, module);
403
+ }
233
404
  }
234
405
  }
235
- /**
236
- * Register a new handler for this event.
237
- *
238
- * @param func Handler
239
- */
406
+ /** Registers a handler once. */
240
407
  register(func) {
241
- if (this.registered.some((existing) => existing.func === func)) {
408
+ if (this.state.registered.some((existing) => existing.func === func)) {
242
409
  return;
243
410
  }
244
- const module = GetResponsibleModule();
245
- this.registered.push({ module, func });
246
- }
247
- /**
248
- * Unregister a handler on this event.
249
- *
250
- * @param fn The handler that was passed to {@link register}
251
- */
411
+ this.state.registered.push({ ...getExecutionOwnership(), func });
412
+ }
413
+ /** Unregisters a handler. */
252
414
  unregister(fn) {
253
- this.registered = this.registered.filter(({ func }) => func !== fn);
415
+ this.state.registered = this.state.registered.filter(({ func }) => func !== fn);
416
+ }
417
+ /** Unregisters handlers owned by a destroyed module. */
418
+ unregisterModule(module) {
419
+ this.state.registered = this.state.registered.filter((entry) => entry.module !== module);
254
420
  }
255
- /**
256
- * Unregister all handlers created by the given module.
257
- * @internal
258
- *
259
- * @param mod Module ID
260
- */
261
- unregisterModule(mod) {
262
- this.registered = this.registered.filter(({ module }) => module !== mod);
421
+ /** Unregisters handlers owned by a destroyed module generation. */
422
+ unregisterOwner(owner) {
423
+ this.state.registered = this.state.registered.filter((entry) => entry.owner !== owner);
263
424
  }
264
425
  }
265
426
  exports.EventProxy = EventProxy;
@@ -268,27 +429,18 @@ function captureCallStack(startFrame = 0) {
268
429
  const oldLimit = Error.stackTraceLimit;
269
430
  Error.stackTraceLimit = Infinity;
270
431
  Error.prepareStackTrace = (_, trace) => trace;
271
- const errObj = {};
272
- Error.captureStackTrace(errObj, GetResponsibleModule);
273
- const trace = errObj.stack;
432
+ const error = {};
433
+ Error.captureStackTrace(error, GetResponsibleModule);
434
+ const trace = error.stack;
274
435
  Error.prepareStackTrace = oldHandler;
275
436
  Error.stackTraceLimit = oldLimit;
276
437
  return trace.slice(startFrame);
277
438
  }
278
- /**
279
- * Gets the responsible module for the current execution context.
280
- *
281
- * Determines which module is responsible for the current code execution by analyzing the call stack.
282
- * This is used for automatic proxy detachment and event handler cleanup.
283
- *
284
- * @param startFrame The starting frame in the stack trace to analyze
285
- * @returns The module ID or undefined if no module is found
286
- */
439
+ /** Gets the responsible module from explicit async context or the call stack. */
287
440
  function GetResponsibleModule(startFrame = 0) {
288
- const explicitContext = responsibleModuleContext.getStore();
289
- if (explicitContext) {
290
- assertActiveContext(explicitContext);
291
- return explicitContext.module;
441
+ const contextModule = (0, internal_1.getModuleContext)()?.module;
442
+ if (contextModule) {
443
+ return contextModule;
292
444
  }
293
445
  const trace = captureCallStack(startFrame);
294
446
  const responsible = (0, responsible_module_1.findResponsibleFile)(trace);