@kuindji/reactive 1.0.24 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -14
- package/dist/action.d.ts +31 -10
- package/dist/action.js +156 -23
- package/dist/actionBus.d.ts +13 -4
- package/dist/actionBus.js +201 -5
- package/dist/actionMap.d.ts +26 -19
- package/dist/actionMap.js +10 -4
- package/dist/event.d.ts +37 -3
- package/dist/event.js +345 -78
- package/dist/eventBus.d.ts +7 -3
- package/dist/eventBus.js +194 -34
- package/dist/index.d.ts +7 -7
- package/dist/index.js +7 -7
- package/dist/lib/actionMapInternal.d.ts +8 -0
- package/dist/lib/actionMapInternal.js +8 -0
- package/dist/lib/isPromiseLike.d.ts +1 -0
- package/dist/lib/isPromiseLike.js +5 -0
- package/dist/lib/normalizeEventOptions.d.ts +13 -0
- package/dist/lib/normalizeEventOptions.js +21 -0
- package/dist/lib/types.d.ts +1 -1
- package/dist/react/ErrorBoundary.d.ts +1 -1
- package/dist/react/listenerOptionsEqual.d.ts +27 -0
- package/dist/react/listenerOptionsEqual.js +121 -0
- package/dist/react/useAction.d.ts +3 -3
- package/dist/react/useAction.js +10 -7
- package/dist/react/useActionBus.d.ts +4 -4
- package/dist/react/useActionBus.js +32 -2
- package/dist/react/useActionBusStatus.d.ts +13 -0
- package/dist/react/useActionBusStatus.js +26 -0
- package/dist/react/useActionMap.d.ts +4 -4
- package/dist/react/useActionMap.js +40 -7
- package/dist/react/useAsyncAction.d.ts +20 -0
- package/dist/react/useAsyncAction.js +53 -0
- package/dist/react/useEvent.d.ts +2 -2
- package/dist/react/useEvent.js +18 -2
- package/dist/react/useEventBus.d.ts +2 -2
- package/dist/react/useEventBus.js +14 -10
- package/dist/react/useListenToAction.d.ts +1 -1
- package/dist/react/useListenToAction.js +17 -38
- package/dist/react/useListenToActionBus.d.ts +3 -3
- package/dist/react/useListenToActionBus.js +15 -9
- package/dist/react/useListenToEvent.d.ts +2 -2
- package/dist/react/useListenToEvent.js +8 -6
- package/dist/react/useListenToEventBus.d.ts +3 -3
- package/dist/react/useListenToEventBus.js +9 -7
- package/dist/react/useListenToStoreChanges.d.ts +3 -3
- package/dist/react/useListenToStoreChanges.js +9 -7
- package/dist/react/useReconciledListener.d.ts +33 -0
- package/dist/react/useReconciledListener.js +44 -0
- package/dist/react/useStore.d.ts +2 -2
- package/dist/react/useStore.js +71 -19
- package/dist/react/useStoreSelector.d.ts +35 -0
- package/dist/react/useStoreSelector.js +144 -0
- package/dist/react/useStoreState.d.ts +2 -2
- package/dist/react/useStoreState.js +26 -21
- package/dist/react.d.ts +16 -13
- package/dist/react.js +16 -13
- package/dist/store.d.ts +12 -8
- package/dist/store.js +473 -39
- package/package.json +13 -3
package/dist/event.js
CHANGED
|
@@ -1,29 +1,45 @@
|
|
|
1
|
-
import asyncCall from "./lib/asyncCall";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import asyncCall from "./lib/asyncCall.js";
|
|
2
|
+
import isPromiseLike from "./lib/isPromiseLike.js";
|
|
3
|
+
import listenerSorter from "./lib/listenerSorter.js";
|
|
4
|
+
import tagsIntersect from "./lib/tagsIntersect.js";
|
|
5
|
+
import { TriggerReturnType } from "./lib/types.js";
|
|
5
6
|
export function createEvent(eventOptions = {}) {
|
|
6
7
|
let listeners = [];
|
|
7
8
|
const errorListeners = [];
|
|
8
9
|
let queue = [];
|
|
9
10
|
let suspended = false;
|
|
10
11
|
let queued = false;
|
|
12
|
+
let destroyed = false;
|
|
11
13
|
let triggered = 0;
|
|
12
14
|
let lastTrigger = null;
|
|
15
|
+
// The args replayed to a late autoTrigger listener. Recorded only while
|
|
16
|
+
// autoTrigger is enabled, so enabling it *after* a trigger does not replay
|
|
17
|
+
// that earlier (pre-enablement) trigger. Kept separate from `lastTrigger`,
|
|
18
|
+
// which is recorded on every trigger purely for `lastTriggerArgs`.
|
|
19
|
+
let autoTriggerArgs = null;
|
|
13
20
|
let sortListeners = false;
|
|
14
21
|
let currentTagsFilter = null;
|
|
15
22
|
const options = Object.assign({ async: null, limit: null, autoTrigger: null, filter: null, filterContext: null, maxListeners: 0 }, eventOptions);
|
|
16
23
|
const addListener = (handler, listenerOptions = {}) => {
|
|
24
|
+
var _a, _b;
|
|
25
|
+
if (destroyed) {
|
|
26
|
+
throw new Error("Event is destroyed");
|
|
27
|
+
}
|
|
17
28
|
if (!handler) {
|
|
18
29
|
return;
|
|
19
30
|
}
|
|
20
|
-
|
|
31
|
+
const signal = (_a = listenerOptions.signal) !== null && _a !== void 0 ? _a : null;
|
|
32
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const listenerContext = (_b = listenerOptions.context) !== null && _b !== void 0 ? _b : null;
|
|
36
|
+
if (listeners.find((l) => l.handler === handler && l.context === listenerContext)) {
|
|
21
37
|
return;
|
|
22
38
|
}
|
|
23
39
|
if (options.maxListeners && listeners.length >= options.maxListeners) {
|
|
24
40
|
throw new Error(`Max listeners (${options.maxListeners}) reached`);
|
|
25
41
|
}
|
|
26
|
-
const listener = Object.assign({ handler, called: 0, count: 0, index: listeners.length, start: 1, context: null, tags: [], extraData: null, first: false, alwaysFirst: false, alwaysLast: false, limit: 0, async: null }, listenerOptions);
|
|
42
|
+
const listener = Object.assign(Object.assign({ handler, called: 0, count: 0, index: listeners.length, start: 1, context: null, tags: [], extraData: null, first: false, alwaysFirst: false, alwaysLast: false, limit: 0, async: null }, listenerOptions), { abortCleanup: null });
|
|
27
43
|
if (listener.async === true) {
|
|
28
44
|
listener.async = 1;
|
|
29
45
|
}
|
|
@@ -44,21 +60,32 @@ export function createEvent(eventOptions = {}) {
|
|
|
44
60
|
|| (listenerOptions === null || listenerOptions === void 0 ? void 0 : listenerOptions.alwaysLast) === true) {
|
|
45
61
|
sortListeners = true;
|
|
46
62
|
}
|
|
63
|
+
if (signal) {
|
|
64
|
+
const onAbort = () => {
|
|
65
|
+
removeListener(handler, listenerContext);
|
|
66
|
+
};
|
|
67
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
68
|
+
listener.abortCleanup = () => {
|
|
69
|
+
signal.removeEventListener("abort", onAbort);
|
|
70
|
+
};
|
|
71
|
+
}
|
|
47
72
|
if (options.autoTrigger
|
|
48
|
-
&&
|
|
73
|
+
&& autoTriggerArgs !== null
|
|
49
74
|
&& !suspended) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
// Replay the last enabled trigger, but only into the listener just
|
|
76
|
+
// added. The replay target is passed explicitly to `_trigger` (not
|
|
77
|
+
// via shared closure/`options.filter` state) so that a real trigger
|
|
78
|
+
// fired synchronously by the replayed handler is an ordinary real
|
|
79
|
+
// trigger — full bookkeeping, limit enforcement, and delivery to all
|
|
80
|
+
// listeners — rather than inheriting this replay's suppression.
|
|
81
|
+
_trigger(autoTriggerArgs, null, null, {
|
|
82
|
+
handler,
|
|
83
|
+
context: listenerContext !== null && listenerContext !== void 0 ? listenerContext : null,
|
|
84
|
+
});
|
|
59
85
|
}
|
|
60
86
|
};
|
|
61
87
|
const removeListener = (handler, context, tag) => {
|
|
88
|
+
var _a;
|
|
62
89
|
const inx = listeners.findIndex((l) => {
|
|
63
90
|
if (l.handler !== handler) {
|
|
64
91
|
return false;
|
|
@@ -77,7 +104,92 @@ export function createEvent(eventOptions = {}) {
|
|
|
77
104
|
if (inx === -1) {
|
|
78
105
|
return false;
|
|
79
106
|
}
|
|
80
|
-
listeners.splice(inx, 1);
|
|
107
|
+
const [removed] = listeners.splice(inx, 1);
|
|
108
|
+
(_a = removed === null || removed === void 0 ? void 0 : removed.abortCleanup) === null || _a === void 0 ? void 0 : _a.call(removed);
|
|
109
|
+
return true;
|
|
110
|
+
};
|
|
111
|
+
const updateListenerOptions = (handler, context = null, nextOptions = {}) => {
|
|
112
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
113
|
+
const listenerContext = context !== null && context !== void 0 ? context : null;
|
|
114
|
+
const listener = listeners.find((l) => l.handler === handler && l.context === listenerContext);
|
|
115
|
+
if (!listener) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const prevAlwaysFirst = listener.alwaysFirst;
|
|
119
|
+
const prevAlwaysLast = listener.alwaysLast;
|
|
120
|
+
// Partial update: only fields explicitly present in nextOptions change;
|
|
121
|
+
// any omitted field keeps its current value (a caller changing one
|
|
122
|
+
// option does not silently reset the others). Pass a field explicitly
|
|
123
|
+
// (e.g. limit: 0, signal: null) to clear it.
|
|
124
|
+
if ("limit" in nextOptions) {
|
|
125
|
+
listener.limit = (_a = nextOptions.limit) !== null && _a !== void 0 ? _a : 0;
|
|
126
|
+
}
|
|
127
|
+
if ("start" in nextOptions) {
|
|
128
|
+
listener.start = (_b = nextOptions.start) !== null && _b !== void 0 ? _b : 1;
|
|
129
|
+
}
|
|
130
|
+
if ("tags" in nextOptions) {
|
|
131
|
+
listener.tags = (_c = nextOptions.tags) !== null && _c !== void 0 ? _c : [];
|
|
132
|
+
}
|
|
133
|
+
if ("extraData" in nextOptions) {
|
|
134
|
+
listener.extraData = (_d = nextOptions.extraData) !== null && _d !== void 0 ? _d : null;
|
|
135
|
+
}
|
|
136
|
+
if ("alwaysFirst" in nextOptions) {
|
|
137
|
+
listener.alwaysFirst = (_e = nextOptions.alwaysFirst) !== null && _e !== void 0 ? _e : false;
|
|
138
|
+
}
|
|
139
|
+
if ("alwaysLast" in nextOptions) {
|
|
140
|
+
listener.alwaysLast = (_f = nextOptions.alwaysLast) !== null && _f !== void 0 ? _f : false;
|
|
141
|
+
}
|
|
142
|
+
if ("async" in nextOptions) {
|
|
143
|
+
let nextAsync = (_g = nextOptions.async) !== null && _g !== void 0 ? _g : null;
|
|
144
|
+
if (nextAsync === true) {
|
|
145
|
+
nextAsync = 1;
|
|
146
|
+
}
|
|
147
|
+
listener.async = nextAsync;
|
|
148
|
+
}
|
|
149
|
+
// Re-sort if ordering hints changed. Unlike addListener we do NOT
|
|
150
|
+
// rewrite each listener's index here: the existing indices hold the
|
|
151
|
+
// original insertion order, and preserving them lets sorting restore
|
|
152
|
+
// that order when alwaysFirst/alwaysLast is cleared.
|
|
153
|
+
if (listener.alwaysFirst !== prevAlwaysFirst
|
|
154
|
+
|| listener.alwaysLast !== prevAlwaysLast) {
|
|
155
|
+
if (listener.alwaysFirst === true || listener.alwaysLast === true) {
|
|
156
|
+
sortListeners = true;
|
|
157
|
+
}
|
|
158
|
+
if (sortListeners) {
|
|
159
|
+
listeners.sort((l1, l2) => listenerSorter(l1, l2));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Rebind the AbortSignal only when `signal` is explicitly present:
|
|
163
|
+
// detach any previous wiring so the old controller can no longer remove
|
|
164
|
+
// this listener, then attach the new signal. Omitting the field leaves
|
|
165
|
+
// the existing binding intact (partial-update convention); pass
|
|
166
|
+
// signal: null to clear it. An already-aborted new signal removes the
|
|
167
|
+
// listener now, mirroring addListener's "do not keep an aborted-signal
|
|
168
|
+
// listener".
|
|
169
|
+
if ("signal" in nextOptions) {
|
|
170
|
+
(_h = listener.abortCleanup) === null || _h === void 0 ? void 0 : _h.call(listener);
|
|
171
|
+
listener.abortCleanup = null;
|
|
172
|
+
const nextSignal = (_j = nextOptions.signal) !== null && _j !== void 0 ? _j : null;
|
|
173
|
+
if (nextSignal) {
|
|
174
|
+
if (nextSignal.aborted) {
|
|
175
|
+
removeListener(listener.handler, listenerContext);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
const onAbort = () => {
|
|
179
|
+
removeListener(listener.handler, listenerContext);
|
|
180
|
+
};
|
|
181
|
+
nextSignal.addEventListener("abort", onAbort, { once: true });
|
|
182
|
+
listener.abortCleanup = () => {
|
|
183
|
+
nextSignal.removeEventListener("abort", onAbort);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// The core auto-remove check is a strict `called === limit`, so a
|
|
188
|
+
// listener whose `called` already exceeds the new limit would never
|
|
189
|
+
// auto-remove. Remove it immediately in that case.
|
|
190
|
+
if (listener.limit !== 0 && listener.called >= listener.limit) {
|
|
191
|
+
removeListener(listener.handler, listener.context);
|
|
192
|
+
}
|
|
81
193
|
return true;
|
|
82
194
|
};
|
|
83
195
|
const hasListener = (handler, context, tag) => {
|
|
@@ -105,10 +217,16 @@ export function createEvent(eventOptions = {}) {
|
|
|
105
217
|
const removeAllListeners = (tag) => {
|
|
106
218
|
if (tag) {
|
|
107
219
|
listeners = listeners.filter((l) => {
|
|
108
|
-
|
|
220
|
+
var _a;
|
|
221
|
+
const keep = !l.tags || l.tags.indexOf(tag) === -1;
|
|
222
|
+
if (!keep) {
|
|
223
|
+
(_a = l.abortCleanup) === null || _a === void 0 ? void 0 : _a.call(l);
|
|
224
|
+
}
|
|
225
|
+
return keep;
|
|
109
226
|
});
|
|
110
227
|
}
|
|
111
228
|
else {
|
|
229
|
+
listeners.forEach((l) => { var _a; return (_a = l.abortCleanup) === null || _a === void 0 ? void 0 : _a.call(l); });
|
|
112
230
|
listeners = [];
|
|
113
231
|
}
|
|
114
232
|
};
|
|
@@ -137,7 +255,7 @@ export function createEvent(eventOptions = {}) {
|
|
|
137
255
|
queued = false;
|
|
138
256
|
if (queue.length > 0) {
|
|
139
257
|
for (let i = 0, l = queue.length; i < l; i++) {
|
|
140
|
-
_trigger(queue[i][0], queue[i][1]);
|
|
258
|
+
_trigger(queue[i][0], queue[i][1], queue[i][2]);
|
|
141
259
|
}
|
|
142
260
|
queue = [];
|
|
143
261
|
}
|
|
@@ -147,7 +265,98 @@ export function createEvent(eventOptions = {}) {
|
|
|
147
265
|
};
|
|
148
266
|
const isSuspended = () => suspended;
|
|
149
267
|
const isQueued = () => queued;
|
|
268
|
+
// One-call teardown: drop all listeners (unwinding their abort handlers via
|
|
269
|
+
// reset) and mark the event dead. Post-destroy trigger/addListener throw
|
|
270
|
+
// rather than silently no-op, surfacing use-after-free.
|
|
271
|
+
const destroy = () => {
|
|
272
|
+
reset();
|
|
273
|
+
destroyed = true;
|
|
274
|
+
};
|
|
275
|
+
const isDestroyed = () => destroyed;
|
|
276
|
+
const listenerCount = (tag) => {
|
|
277
|
+
if (tag) {
|
|
278
|
+
return listeners.filter((l) => l.tags && l.tags.indexOf(tag) !== -1).length;
|
|
279
|
+
}
|
|
280
|
+
return listeners.length;
|
|
281
|
+
};
|
|
282
|
+
const triggeredCount = () => triggered;
|
|
283
|
+
// Return a copy: handing back the internal `lastTrigger` reference would let
|
|
284
|
+
// a caller mutate it, corrupting both the recorded snapshot and the values
|
|
285
|
+
// replayed to autoTrigger listeners.
|
|
286
|
+
const lastTriggerArgs = () => lastTrigger ? lastTrigger.slice() : null;
|
|
287
|
+
// Deep-copy extraData so the read-only projection cannot mutate internal
|
|
288
|
+
// listener metadata (which filters can read) at any depth; a shallow copy
|
|
289
|
+
// still shares nested containers by reference. `seen` carries already-cloned
|
|
290
|
+
// containers so a cyclic graph reuses its clone instead of recursing forever
|
|
291
|
+
// (a plain recursive clone throws RangeError on cycles). Arrays, plain
|
|
292
|
+
// objects, Date, Map and Set are cloned. Truly opaque values (functions,
|
|
293
|
+
// class instances) are returned as-is — copying their enumerable keys would
|
|
294
|
+
// not faithfully reproduce them — as are primitives.
|
|
295
|
+
const projectExtraDataDeep = (value, seen) => {
|
|
296
|
+
if (value === null || typeof value !== "object") {
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
const existing = seen.get(value);
|
|
300
|
+
if (existing !== undefined) {
|
|
301
|
+
return existing;
|
|
302
|
+
}
|
|
303
|
+
if (value instanceof Date) {
|
|
304
|
+
return new Date(value.getTime());
|
|
305
|
+
}
|
|
306
|
+
if (Array.isArray(value)) {
|
|
307
|
+
const copy = [];
|
|
308
|
+
seen.set(value, copy);
|
|
309
|
+
for (const v of value) {
|
|
310
|
+
copy.push(projectExtraDataDeep(v, seen));
|
|
311
|
+
}
|
|
312
|
+
return copy;
|
|
313
|
+
}
|
|
314
|
+
if (value instanceof Map) {
|
|
315
|
+
const copy = new Map();
|
|
316
|
+
seen.set(value, copy);
|
|
317
|
+
value.forEach((v, k) => {
|
|
318
|
+
copy.set(projectExtraDataDeep(k, seen), projectExtraDataDeep(v, seen));
|
|
319
|
+
});
|
|
320
|
+
return copy;
|
|
321
|
+
}
|
|
322
|
+
if (value instanceof Set) {
|
|
323
|
+
const copy = new Set();
|
|
324
|
+
seen.set(value, copy);
|
|
325
|
+
value.forEach((v) => {
|
|
326
|
+
copy.add(projectExtraDataDeep(v, seen));
|
|
327
|
+
});
|
|
328
|
+
return copy;
|
|
329
|
+
}
|
|
330
|
+
const proto = Object.getPrototypeOf(value);
|
|
331
|
+
if (proto === Object.prototype || proto === null) {
|
|
332
|
+
const copy = {};
|
|
333
|
+
seen.set(value, copy);
|
|
334
|
+
for (const k of Object.keys(value)) {
|
|
335
|
+
copy[k] = projectExtraDataDeep(value[k], seen);
|
|
336
|
+
}
|
|
337
|
+
return copy;
|
|
338
|
+
}
|
|
339
|
+
return value;
|
|
340
|
+
};
|
|
341
|
+
const projectExtraData = (value) => projectExtraDataDeep(value, new WeakMap());
|
|
342
|
+
const getListeners = () => {
|
|
343
|
+
return listeners.map((l) => ({
|
|
344
|
+
handler: l.handler,
|
|
345
|
+
context: l.context,
|
|
346
|
+
tags: l.tags ? l.tags.slice() : [],
|
|
347
|
+
limit: l.limit,
|
|
348
|
+
start: l.start,
|
|
349
|
+
called: l.called,
|
|
350
|
+
count: l.count,
|
|
351
|
+
async: l.async,
|
|
352
|
+
first: l.first,
|
|
353
|
+
alwaysFirst: l.alwaysFirst,
|
|
354
|
+
alwaysLast: l.alwaysLast,
|
|
355
|
+
extraData: projectExtraData(l.extraData),
|
|
356
|
+
}));
|
|
357
|
+
};
|
|
150
358
|
const reset = () => {
|
|
359
|
+
listeners.forEach((l) => { var _a; return (_a = l.abortCleanup) === null || _a === void 0 ? void 0 : _a.call(l); });
|
|
151
360
|
listeners.length = 0;
|
|
152
361
|
errorListeners.length = 0;
|
|
153
362
|
queue.length = 0;
|
|
@@ -155,8 +364,8 @@ export function createEvent(eventOptions = {}) {
|
|
|
155
364
|
queued = false;
|
|
156
365
|
triggered = 0;
|
|
157
366
|
lastTrigger = null;
|
|
367
|
+
autoTriggerArgs = null;
|
|
158
368
|
sortListeners = false;
|
|
159
|
-
cachedPromise = null;
|
|
160
369
|
};
|
|
161
370
|
const _listenerCall = (listener, args, resolve = null) => {
|
|
162
371
|
let isAsync = listener.async;
|
|
@@ -173,26 +382,29 @@ export function createEvent(eventOptions = {}) {
|
|
|
173
382
|
const result = isAsync !== false
|
|
174
383
|
? asyncCall(listener.handler, listener.context, args, isAsync)
|
|
175
384
|
: listener.handler.bind(listener.context)(...args);
|
|
176
|
-
if (
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
resolve(result);
|
|
385
|
+
if (isPromiseLike(result)) {
|
|
386
|
+
const handledResult = Promise.resolve(result).catch((error) => {
|
|
387
|
+
for (const errorListener of errorListeners) {
|
|
388
|
+
errorListener.handler({
|
|
389
|
+
error: error instanceof Error
|
|
390
|
+
? error
|
|
391
|
+
: new Error(error),
|
|
392
|
+
args: args,
|
|
393
|
+
type: "event",
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
if (errorListeners.length === 0) {
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
return undefined;
|
|
400
|
+
});
|
|
401
|
+
if (resolve !== null) {
|
|
402
|
+
void handledResult.then(resolve);
|
|
195
403
|
}
|
|
404
|
+
return handledResult;
|
|
405
|
+
}
|
|
406
|
+
if (resolve !== null) {
|
|
407
|
+
resolve(result);
|
|
196
408
|
}
|
|
197
409
|
return result;
|
|
198
410
|
}
|
|
@@ -214,6 +426,12 @@ export function createEvent(eventOptions = {}) {
|
|
|
214
426
|
};
|
|
215
427
|
const _listenerCallWPrev = (listener, args, prevValue, returnType) => {
|
|
216
428
|
if (returnType === TriggerReturnType.PIPE) {
|
|
429
|
+
// Copy-on-write: preserve the pre-pipe lastTrigger snapshot before
|
|
430
|
+
// mutating args[0] in place for the pipe chain (lastTrigger stores
|
|
431
|
+
// the args reference rather than an eager per-trigger copy).
|
|
432
|
+
if (lastTrigger === args) {
|
|
433
|
+
lastTrigger = args.slice();
|
|
434
|
+
}
|
|
217
435
|
args[0] = prevValue;
|
|
218
436
|
// since we don't user listener's arg transformer,
|
|
219
437
|
// we don't need to prepare args
|
|
@@ -235,20 +453,49 @@ export function createEvent(eventOptions = {}) {
|
|
|
235
453
|
}
|
|
236
454
|
return _listenerCall(listener, args);
|
|
237
455
|
};
|
|
238
|
-
const _trigger = (args, returnType = null, tags
|
|
456
|
+
const _trigger = (args, returnType = null, tags,
|
|
457
|
+
// When set, this call is an autoTrigger replay: it must not bump
|
|
458
|
+
// `triggered`/`lastTrigger` or be gated by the trigger `limit`, and it is
|
|
459
|
+
// delivered only to the listener identified here.
|
|
460
|
+
replayTo) => {
|
|
461
|
+
var _a, _b, _c;
|
|
462
|
+
const replaying = !!replayTo;
|
|
463
|
+
if (destroyed) {
|
|
464
|
+
throw new Error("Event is destroyed");
|
|
465
|
+
}
|
|
239
466
|
if (queued) {
|
|
240
|
-
queue.push([
|
|
467
|
+
queue.push([
|
|
468
|
+
args,
|
|
469
|
+
returnType,
|
|
470
|
+
(_b = (_a = (tags || currentTagsFilter)) === null || _a === void 0 ? void 0 : _a.slice()) !== null && _b !== void 0 ? _b : null,
|
|
471
|
+
]);
|
|
241
472
|
return;
|
|
242
473
|
}
|
|
243
474
|
if (suspended) {
|
|
244
475
|
return;
|
|
245
476
|
}
|
|
246
|
-
|
|
477
|
+
// The trigger `limit` bounds real triggers; an autoTrigger replay is an
|
|
478
|
+
// internal redelivery and must always reach the new listener.
|
|
479
|
+
if (options.limit && triggered >= options.limit && !replaying) {
|
|
247
480
|
return;
|
|
248
481
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
482
|
+
if (!replaying) {
|
|
483
|
+
triggered++;
|
|
484
|
+
// Record the last trigger arguments for introspection
|
|
485
|
+
// (`lastTriggerArgs`). Store the reference rather than eagerly
|
|
486
|
+
// copying on every trigger (a hot path even when nothing ever reads
|
|
487
|
+
// it): `args` is a fresh per-call array the caller cannot reach, and
|
|
488
|
+
// listeners receive it spread (never the array itself). The snapshot
|
|
489
|
+
// is copied lazily — when handed out by lastTriggerArgs(), and
|
|
490
|
+
// copy-on-write before PIPE mode mutates args[0] in place — so the
|
|
491
|
+
// recorded snapshot stays the pre-pipe arguments.
|
|
492
|
+
lastTrigger = args;
|
|
493
|
+
// Record the replay source only while autoTrigger is enabled, so a
|
|
494
|
+
// late listener added after autoTrigger is turned on replays the
|
|
495
|
+
// most recent *enabled* trigger, never an earlier disabled one.
|
|
496
|
+
if (options.autoTrigger) {
|
|
497
|
+
autoTriggerArgs = args.slice();
|
|
498
|
+
}
|
|
252
499
|
}
|
|
253
500
|
// in pipe mode if there is no listeners,
|
|
254
501
|
// we just return piped value
|
|
@@ -279,6 +526,13 @@ export function createEvent(eventOptions = {}) {
|
|
|
279
526
|
if (!listener) {
|
|
280
527
|
continue;
|
|
281
528
|
}
|
|
529
|
+
// An autoTrigger replay is delivered only to the newly added
|
|
530
|
+
// listener identified by `replayTo`.
|
|
531
|
+
if (replayTo
|
|
532
|
+
&& (listener.handler !== replayTo.handler
|
|
533
|
+
|| ((_c = listener.context) !== null && _c !== void 0 ? _c : null) !== replayTo.context)) {
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
282
536
|
if (options.filter
|
|
283
537
|
&& options.filter.call(options.filterContext, args, listener)
|
|
284
538
|
=== false) {
|
|
@@ -300,12 +554,20 @@ export function createEvent(eventOptions = {}) {
|
|
|
300
554
|
&& listener.count < listener.start) {
|
|
301
555
|
continue;
|
|
302
556
|
}
|
|
557
|
+
// Count the call and exhaust the limit BEFORE invoking the handler.
|
|
558
|
+
// If the handler re-triggers this same event, the nested _trigger
|
|
559
|
+
// snapshots `listeners` AFTER the removal below, so an exhausted
|
|
560
|
+
// (e.g. once()) listener is not invoked a second time. Doing this
|
|
561
|
+
// after the call would let a re-entrant trigger see the still-live
|
|
562
|
+
// listener and run it again past its limit.
|
|
563
|
+
listener.called++;
|
|
564
|
+
if (listener.called === listener.limit) {
|
|
565
|
+
removeListener(listener.handler, listener.context);
|
|
566
|
+
}
|
|
303
567
|
if (isConsequent && results.length > 0) {
|
|
304
568
|
const prev = results[results.length - 1];
|
|
305
569
|
if (hasPromises) {
|
|
306
|
-
const prevPromise = prev
|
|
307
|
-
? prev
|
|
308
|
-
: Promise.resolve(prev);
|
|
570
|
+
const prevPromise = Promise.resolve(prev);
|
|
309
571
|
listenerResult = prevPromise.then(((listener, args, returnType) => (value) => {
|
|
310
572
|
return _listenerCallWPrev(listener, args, value, returnType);
|
|
311
573
|
})(listener, args, returnType));
|
|
@@ -319,10 +581,6 @@ export function createEvent(eventOptions = {}) {
|
|
|
319
581
|
else {
|
|
320
582
|
listenerResult = _listenerCall(listener, args);
|
|
321
583
|
}
|
|
322
|
-
listener.called++;
|
|
323
|
-
if (listener.called === listener.limit) {
|
|
324
|
-
removeListener(listener.handler, listener.context);
|
|
325
|
-
}
|
|
326
584
|
if (returnType === TriggerReturnType.FIRST) {
|
|
327
585
|
return listenerResult;
|
|
328
586
|
}
|
|
@@ -342,7 +600,7 @@ export function createEvent(eventOptions = {}) {
|
|
|
342
600
|
}
|
|
343
601
|
case TriggerReturnType.FIRST_NON_EMPTY: {
|
|
344
602
|
if (!hasPromises
|
|
345
|
-
&& !(listenerResult
|
|
603
|
+
&& !isPromiseLike(listenerResult)
|
|
346
604
|
&& listenerResult !== null
|
|
347
605
|
&& listenerResult !== undefined) {
|
|
348
606
|
return listenerResult;
|
|
@@ -351,7 +609,7 @@ export function createEvent(eventOptions = {}) {
|
|
|
351
609
|
}
|
|
352
610
|
}
|
|
353
611
|
}
|
|
354
|
-
if (!hasPromises && listenerResult
|
|
612
|
+
if (!hasPromises && isPromiseLike(listenerResult)) {
|
|
355
613
|
hasPromises = true;
|
|
356
614
|
}
|
|
357
615
|
results.push(listenerResult);
|
|
@@ -405,33 +663,34 @@ export function createEvent(eventOptions = {}) {
|
|
|
405
663
|
_trigger(args);
|
|
406
664
|
};
|
|
407
665
|
const withTags = (tags, callback) => {
|
|
666
|
+
const prevTagsFilter = currentTagsFilter;
|
|
408
667
|
currentTagsFilter = tags;
|
|
409
668
|
try {
|
|
410
669
|
return callback();
|
|
411
670
|
}
|
|
412
671
|
finally {
|
|
413
|
-
currentTagsFilter =
|
|
672
|
+
currentTagsFilter = prevTagsFilter;
|
|
414
673
|
}
|
|
415
674
|
};
|
|
416
|
-
|
|
675
|
+
const once = (handler, listenerOptions = {}) => {
|
|
676
|
+
return addListener(handler, Object.assign(Object.assign({}, listenerOptions), { limit: 1 }));
|
|
677
|
+
};
|
|
417
678
|
const promise = (options) => {
|
|
418
|
-
return
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
resolve(args);
|
|
423
|
-
cachedPromise = null;
|
|
424
|
-
});
|
|
425
|
-
addListener(l, options);
|
|
679
|
+
return new Promise((resolve) => {
|
|
680
|
+
options = Object.assign(Object.assign({}, (options || {})), { limit: 1 });
|
|
681
|
+
const l = ((...args) => {
|
|
682
|
+
resolve(args);
|
|
426
683
|
});
|
|
684
|
+
addListener(l, options);
|
|
685
|
+
});
|
|
427
686
|
};
|
|
428
687
|
const first = (...args) => {
|
|
429
688
|
return _trigger(args, TriggerReturnType.FIRST);
|
|
430
689
|
};
|
|
431
690
|
const resolveFirst = (...args) => {
|
|
432
691
|
const response = _trigger(args, TriggerReturnType.FIRST);
|
|
433
|
-
if (response
|
|
434
|
-
return response;
|
|
692
|
+
if (isPromiseLike(response)) {
|
|
693
|
+
return Promise.resolve(response);
|
|
435
694
|
}
|
|
436
695
|
return Promise.resolve(response);
|
|
437
696
|
};
|
|
@@ -440,8 +699,8 @@ export function createEvent(eventOptions = {}) {
|
|
|
440
699
|
};
|
|
441
700
|
const resolveAll = (...args) => {
|
|
442
701
|
const response = _trigger(args, TriggerReturnType.ALL);
|
|
443
|
-
if (response
|
|
444
|
-
return response;
|
|
702
|
+
if (isPromiseLike(response)) {
|
|
703
|
+
return Promise.resolve(response);
|
|
445
704
|
}
|
|
446
705
|
return Promise.resolve(response);
|
|
447
706
|
};
|
|
@@ -450,8 +709,8 @@ export function createEvent(eventOptions = {}) {
|
|
|
450
709
|
};
|
|
451
710
|
const resolveLast = (...args) => {
|
|
452
711
|
const response = _trigger(args, TriggerReturnType.LAST);
|
|
453
|
-
if (response
|
|
454
|
-
return response;
|
|
712
|
+
if (isPromiseLike(response)) {
|
|
713
|
+
return Promise.resolve(response);
|
|
455
714
|
}
|
|
456
715
|
return Promise.resolve(response);
|
|
457
716
|
};
|
|
@@ -460,8 +719,8 @@ export function createEvent(eventOptions = {}) {
|
|
|
460
719
|
};
|
|
461
720
|
const resolveMerge = (...args) => {
|
|
462
721
|
const response = _trigger(args, TriggerReturnType.MERGE);
|
|
463
|
-
if (response
|
|
464
|
-
return response;
|
|
722
|
+
if (isPromiseLike(response)) {
|
|
723
|
+
return Promise.resolve(response);
|
|
465
724
|
}
|
|
466
725
|
return Promise.resolve(response);
|
|
467
726
|
};
|
|
@@ -470,8 +729,8 @@ export function createEvent(eventOptions = {}) {
|
|
|
470
729
|
};
|
|
471
730
|
const resolveConcat = (...args) => {
|
|
472
731
|
const response = _trigger(args, TriggerReturnType.CONCAT);
|
|
473
|
-
if (response
|
|
474
|
-
return response;
|
|
732
|
+
if (isPromiseLike(response)) {
|
|
733
|
+
return Promise.resolve(response);
|
|
475
734
|
}
|
|
476
735
|
return Promise.resolve(response);
|
|
477
736
|
};
|
|
@@ -480,8 +739,8 @@ export function createEvent(eventOptions = {}) {
|
|
|
480
739
|
};
|
|
481
740
|
const resolveFirstNonEmpty = (...args) => {
|
|
482
741
|
const response = _trigger(args, TriggerReturnType.FIRST_NON_EMPTY);
|
|
483
|
-
if (response
|
|
484
|
-
return response;
|
|
742
|
+
if (isPromiseLike(response)) {
|
|
743
|
+
return Promise.resolve(response);
|
|
485
744
|
}
|
|
486
745
|
return Promise.resolve(response);
|
|
487
746
|
};
|
|
@@ -496,8 +755,8 @@ export function createEvent(eventOptions = {}) {
|
|
|
496
755
|
};
|
|
497
756
|
const resolvePipe = (...args) => {
|
|
498
757
|
const response = _trigger(args, TriggerReturnType.PIPE);
|
|
499
|
-
if (response
|
|
500
|
-
return response;
|
|
758
|
+
if (isPromiseLike(response)) {
|
|
759
|
+
return Promise.resolve(response);
|
|
501
760
|
}
|
|
502
761
|
return Promise.resolve(response);
|
|
503
762
|
};
|
|
@@ -512,7 +771,9 @@ export function createEvent(eventOptions = {}) {
|
|
|
512
771
|
listen: addListener,
|
|
513
772
|
/** @alias addListener */
|
|
514
773
|
subscribe: addListener,
|
|
774
|
+
once,
|
|
515
775
|
removeListener,
|
|
776
|
+
updateListenerOptions,
|
|
516
777
|
/** @alias removeListener */
|
|
517
778
|
un: removeListener,
|
|
518
779
|
/** @alias removeListener */
|
|
@@ -536,8 +797,14 @@ export function createEvent(eventOptions = {}) {
|
|
|
536
797
|
resume,
|
|
537
798
|
setOptions,
|
|
538
799
|
reset,
|
|
800
|
+
destroy,
|
|
801
|
+
isDestroyed,
|
|
539
802
|
isSuspended,
|
|
540
803
|
isQueued,
|
|
804
|
+
listenerCount,
|
|
805
|
+
triggeredCount,
|
|
806
|
+
lastTriggerArgs,
|
|
807
|
+
getListeners,
|
|
541
808
|
withTags,
|
|
542
809
|
promise,
|
|
543
810
|
first,
|