@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.
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +21 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +7 -9
- package/dist/index.js +91 -25
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +87 -12
- package/dist/internal.js +108 -21
- package/dist/internal.js.map +1 -1
- package/dist/modules.d.ts +6 -0
- package/dist/modules.js +67 -28
- package/dist/modules.js.map +1 -1
- package/dist/proxies.d.ts +64 -129
- package/dist/proxies.js +372 -220
- package/dist/proxies.js.map +1 -1
- package/dist/runtime.js +2 -2
- package/dist/runtime.js.map +1 -1
- package/dist/tests/generation-cleanup.test.d.ts +1 -0
- package/dist/tests/generation-cleanup.test.js +116 -0
- package/dist/tests/generation-cleanup.test.js.map +1 -0
- package/dist/tests/implement-interface-validation.test.d.ts +1 -0
- package/dist/tests/implement-interface-validation.test.js +45 -0
- package/dist/tests/implement-interface-validation.test.js.map +1 -0
- package/dist/tests/interface-connections.test.d.ts +1 -0
- package/dist/tests/interface-connections.test.js +36 -0
- package/dist/tests/interface-connections.test.js.map +1 -0
- package/dist/tests/module-ownership-context.test.js +18 -0
- package/dist/tests/module-ownership-context.test.js.map +1 -1
- package/dist/tests/provider-context.test.d.ts +1 -0
- package/dist/tests/provider-context.test.js +117 -0
- package/dist/tests/provider-context.test.js.map +1 -0
- package/dist/tests/provider-routing.test.d.ts +1 -0
- package/dist/tests/provider-routing.test.js +78 -0
- package/dist/tests/provider-routing.test.js.map +1 -0
- package/dist/tests/queue-and-cleanup.test.d.ts +1 -0
- package/dist/tests/queue-and-cleanup.test.js +73 -0
- package/dist/tests/queue-and-cleanup.test.js.map +1 -0
- package/dist/tests/registering-proxy-replay.test.js +9 -0
- package/dist/tests/registering-proxy-replay.test.js.map +1 -1
- package/dist/tests/runtime-protocol.test.d.ts +1 -0
- package/dist/tests/runtime-protocol.test.js +49 -0
- package/dist/tests/runtime-protocol.test.js.map +1 -0
- package/dist/tests/runtime.test.js +18 -2
- package/dist/tests/runtime.test.js.map +1 -1
- package/docs/2.proxies.md +18 -1
- package/docs/5.modules.md +22 -0
- 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
|
|
12
|
-
const
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
|
23
|
-
if (
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
132
|
-
this.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
for (const [id,
|
|
204
|
-
if (
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
245
|
-
|
|
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
|
-
|
|
257
|
-
|
|
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
|
|
272
|
-
Error.captureStackTrace(
|
|
273
|
-
const trace =
|
|
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
|
|
289
|
-
if (
|
|
290
|
-
|
|
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);
|