mutts 1.0.7 → 1.0.9
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 +72 -17
- package/dist/browser.cjs +3111 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.ts +1391 -1
- package/dist/browser.esm.js +264 -35
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/async-browser-lvzLOCgk.cjs +297 -0
- package/dist/chunks/async-browser-lvzLOCgk.cjs.map +1 -0
- package/dist/chunks/async-core-CRLKP3l-.cjs +29 -0
- package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
- package/dist/chunks/async-node-C3DeIb0y.cjs +96 -0
- package/dist/chunks/async-node-C3DeIb0y.cjs.map +1 -0
- package/dist/chunks/index-2vea86wD.esm.js +3011 -0
- package/dist/chunks/index-2vea86wD.esm.js.map +1 -0
- package/dist/chunks/index-CtA2AWl3.esm.js +4376 -0
- package/dist/chunks/index-CtA2AWl3.esm.js.map +1 -0
- package/dist/chunks/index-VTO-b2vR.cjs +4461 -0
- package/dist/chunks/index-VTO-b2vR.cjs.map +1 -0
- package/dist/debug/debug.d.ts +122 -0
- package/dist/debug/debug.d.ts.map +1 -0
- package/dist/debug/index.d.ts +4 -0
- package/dist/debug/index.d.ts.map +1 -0
- package/dist/debug/lineage-panel.d.ts +5 -0
- package/dist/debug/lineage-panel.d.ts.map +1 -0
- package/dist/debug/lineage.d.ts +79 -0
- package/dist/debug/lineage.d.ts.map +1 -0
- package/dist/debug.cjs +35 -0
- package/dist/debug.cjs.map +1 -0
- package/dist/debug.d.ts +209 -0
- package/dist/debug.esm.js +2 -0
- package/dist/debug.esm.js.map +1 -0
- package/dist/devtools/devtool/devtools.d.ts +1 -0
- package/dist/devtools/devtool/devtools.d.ts.map +1 -0
- package/dist/devtools/devtool/panel.d.ts +2 -0
- package/dist/devtools/devtool/panel.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/mutts.umd.js +1 -1
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +107 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.ts +2 -2
- package/dist/node.esm.js +86 -33
- package/dist/node.esm.js.map +1 -1
- package/dist/src/async/browser.d.ts +2 -0
- package/dist/src/async/browser.d.ts.map +1 -0
- package/dist/src/async/index.d.ts +19 -0
- package/dist/src/async/index.d.ts.map +1 -0
- package/dist/src/async/node.d.ts +2 -0
- package/dist/src/async/node.d.ts.map +1 -0
- package/dist/src/decorator.d.ts +106 -0
- package/dist/src/decorator.d.ts.map +1 -0
- package/dist/src/destroyable.d.ts +87 -0
- package/dist/src/destroyable.d.ts.map +1 -0
- package/dist/src/entry-browser.d.ts +3 -0
- package/dist/src/entry-browser.d.ts.map +1 -0
- package/dist/src/entry-node.d.ts +3 -0
- package/dist/src/entry-node.d.ts.map +1 -0
- package/dist/src/eventful.d.ts +20 -0
- package/dist/src/eventful.d.ts.map +1 -0
- package/dist/src/flavored.d.ts +33 -0
- package/dist/src/flavored.d.ts.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/indexable.d.ts +243 -0
- package/dist/src/indexable.d.ts.map +1 -0
- package/dist/src/introspection.d.ts +27 -0
- package/dist/src/introspection.d.ts.map +1 -0
- package/dist/src/iterableWeak.d.ts +53 -0
- package/dist/src/iterableWeak.d.ts.map +1 -0
- package/dist/src/mixins.d.ts +25 -0
- package/dist/src/mixins.d.ts.map +1 -0
- package/dist/src/promiseChain.d.ts +20 -0
- package/dist/src/promiseChain.d.ts.map +1 -0
- package/dist/src/reactive/array.d.ts +48 -0
- package/dist/src/reactive/array.d.ts.map +1 -0
- package/dist/src/reactive/buffer.d.ts +120 -0
- package/dist/src/reactive/buffer.d.ts.map +1 -0
- package/dist/src/reactive/change.d.ts +29 -0
- package/dist/src/reactive/change.d.ts.map +1 -0
- package/dist/src/reactive/deep-touch.d.ts +28 -0
- package/dist/src/reactive/deep-touch.d.ts.map +1 -0
- package/dist/src/reactive/deep-watch-state.d.ts +25 -0
- package/dist/src/reactive/deep-watch-state.d.ts.map +1 -0
- package/dist/src/reactive/deep-watch.d.ts +20 -0
- package/dist/src/reactive/deep-watch.d.ts.map +1 -0
- package/dist/src/reactive/describe.d.ts +12 -0
- package/dist/src/reactive/describe.d.ts.map +1 -0
- package/dist/src/reactive/effect-context.d.ts +34 -0
- package/dist/src/reactive/effect-context.d.ts.map +1 -0
- package/dist/src/reactive/effects.d.ts +164 -0
- package/dist/src/reactive/effects.d.ts.map +1 -0
- package/dist/src/reactive/index.d.ts +19 -0
- package/dist/src/reactive/index.d.ts.map +1 -0
- package/dist/src/reactive/map.d.ts +28 -0
- package/dist/src/reactive/map.d.ts.map +1 -0
- package/dist/src/reactive/memoize.d.ts +28 -0
- package/dist/src/reactive/memoize.d.ts.map +1 -0
- package/dist/src/reactive/non-reactive-state.d.ts +9 -0
- package/dist/src/reactive/non-reactive-state.d.ts.map +1 -0
- package/dist/src/reactive/non-reactive.d.ts +11 -0
- package/dist/src/reactive/non-reactive.d.ts.map +1 -0
- package/dist/src/reactive/project.d.ts +40 -0
- package/dist/src/reactive/project.d.ts.map +1 -0
- package/dist/src/reactive/proxy-state.d.ts +8 -0
- package/dist/src/reactive/proxy-state.d.ts.map +1 -0
- package/dist/src/reactive/proxy.d.ts +23 -0
- package/dist/src/reactive/proxy.d.ts.map +1 -0
- package/dist/src/reactive/record.d.ts +115 -0
- package/dist/src/reactive/record.d.ts.map +1 -0
- package/dist/src/reactive/register.d.ts +125 -0
- package/dist/src/reactive/register.d.ts.map +1 -0
- package/dist/src/reactive/registry.d.ts +21 -0
- package/dist/src/reactive/registry.d.ts.map +1 -0
- package/dist/src/reactive/set.d.ts +26 -0
- package/dist/src/reactive/set.d.ts.map +1 -0
- package/dist/src/reactive/tracking.d.ts +7 -0
- package/dist/src/reactive/tracking.d.ts.map +1 -0
- package/dist/src/reactive/types.d.ts +424 -0
- package/dist/src/reactive/types.d.ts.map +1 -0
- package/dist/src/reactive/watch.d.ts +48 -0
- package/dist/src/reactive/watch.d.ts.map +1 -0
- package/dist/src/std-decorators.d.ts +45 -0
- package/dist/src/std-decorators.d.ts.map +1 -0
- package/dist/src/utils.d.ts +49 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/zone.d.ts +40 -0
- package/dist/src/zone.d.ts.map +1 -0
- package/dist/types-DaHFfhlN.d.ts +414 -0
- package/docs/ai/api-reference.md +1 -1
- package/docs/ai/manual.md +443 -17
- package/docs/events.md +57 -2
- package/docs/flavored.md +210 -0
- package/docs/reactive/advanced.md +2 -3
- package/docs/reactive/attend.md +143 -0
- package/docs/reactive/collections.md +55 -0
- package/docs/reactive/core.md +120 -4
- package/docs/reactive/debugging.md +8 -10
- package/docs/reactive/describe.md +85 -0
- package/docs/reactive/error-handling.md +121 -0
- package/docs/reactive/project.md +1 -1
- package/docs/reactive/scan.md +215 -0
- package/docs/reactive.md +2 -0
- package/docs/std-decorators.md +171 -52
- package/docs/zone.md +9 -6
- package/package.json +44 -27
- package/src/async/browser.ts +277 -41
- package/src/async/index.ts +23 -4
- package/src/async/node.ts +78 -32
- package/src/entry-browser.ts +5 -0
- package/src/entry-node.ts +5 -0
- package/src/eventful.ts +82 -49
- package/src/flavored.ts +106 -0
- package/src/index.d.ts +12 -9
- package/src/index.ts +2 -1
- package/src/introspection.ts +1 -1
- package/src/reactive/array.ts +160 -80
- package/src/reactive/buffer.ts +187 -27
- package/src/reactive/change.ts +16 -23
- package/src/reactive/deep-touch.ts +19 -14
- package/src/reactive/deep-watch-state.ts +3 -3
- package/src/reactive/deep-watch.ts +7 -4
- package/src/reactive/describe.ts +39 -0
- package/src/reactive/effect-context.ts +71 -6
- package/src/reactive/effects.ts +445 -393
- package/src/reactive/index.ts +12 -14
- package/src/reactive/map.ts +8 -24
- package/src/reactive/memoize.ts +26 -3
- package/src/reactive/project.md +107 -0
- package/src/reactive/project.ts +48 -71
- package/src/reactive/proxy.ts +21 -3
- package/src/reactive/record.ts +30 -45
- package/src/reactive/register.ts +138 -19
- package/src/reactive/registry.ts +22 -9
- package/src/reactive/set.ts +7 -20
- package/src/reactive/tracking.ts +9 -12
- package/src/reactive/types.ts +104 -24
- package/src/reactive/{interface.ts → watch.ts} +29 -59
- package/src/std-decorators.ts +56 -17
- package/src/utils.ts +3 -4
- package/src/zone.ts +43 -28
- package/dist/browser.js +0 -161
- package/dist/browser.js.map +0 -1
- package/dist/chunks/index-BFYK02LG.js +0 -6378
- package/dist/chunks/index-BFYK02LG.js.map +0 -1
- package/dist/chunks/index-CNR6QRUl.esm.js +0 -6291
- package/dist/chunks/index-CNR6QRUl.esm.js.map +0 -1
- package/dist/node.js +0 -136
- package/dist/node.js.map +0 -1
- package/src/reactive/debug.ts +0 -517
package/src/async/browser.ts
CHANGED
|
@@ -1,65 +1,301 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { asyncHooks, hooks, type Restorer } from '.'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const promiseContexts = new WeakMap<Promise<any>, Set<Restorer>>()
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// [HACK]: Sanitization
|
|
6
|
+
// If a Promise is created inside the zone, it carries the "Sticky" zone context.
|
|
7
|
+
// If returned to the outer scope, that context leaks. We wrap it in a new Promise
|
|
8
|
+
// created here (in the outer scope) to break the chain and sanitize the return value.
|
|
9
|
+
// See BROWSER_ASYNC_POLYFILL.md for full details.
|
|
10
|
+
asyncHooks.sanitizePromise = (res: any) => {
|
|
11
|
+
if (res && typeof (res as any).then === 'function') {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
;(res as any).then(resolve, reject)
|
|
15
|
+
}, 0)
|
|
16
|
+
})
|
|
9
17
|
}
|
|
18
|
+
return res
|
|
10
19
|
}
|
|
11
20
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
function wrap<Args extends any[], R>(fn: ((...args: Args) => R) | null | undefined) {
|
|
15
|
-
if (typeof fn !== 'function') return fn
|
|
21
|
+
function captureRestorers() {
|
|
16
22
|
const restorers = new Set<Restorer>()
|
|
17
|
-
for (const hook of hooks)
|
|
23
|
+
for (const hook of hooks) {
|
|
24
|
+
const restorer = hook()
|
|
25
|
+
if (restorer) restorers.add(restorer)
|
|
26
|
+
}
|
|
27
|
+
return restorers
|
|
28
|
+
}
|
|
18
29
|
|
|
30
|
+
function wrap<Args extends any[], R>(
|
|
31
|
+
fn: ((...args: Args) => R) | null | undefined,
|
|
32
|
+
capturedRestorers?: Set<Restorer>
|
|
33
|
+
) {
|
|
34
|
+
if (typeof fn !== 'function') return fn
|
|
35
|
+
const restorers = capturedRestorers || captureRestorers()
|
|
19
36
|
return function (this: any, ...args: Args) {
|
|
20
|
-
const undoers
|
|
21
|
-
for (const restore of restorers) undoers.
|
|
37
|
+
const undoers: (() => void)[] = []
|
|
38
|
+
for (const restore of restorers) undoers.push(restore())
|
|
22
39
|
try {
|
|
23
40
|
return fn.apply(this, args)
|
|
24
41
|
} finally {
|
|
25
|
-
|
|
42
|
+
if (originals.queueMicrotask) {
|
|
43
|
+
// Double microtask ensures we run after the first await resumption microtask
|
|
44
|
+
originals.queueMicrotask.call(globalThis, () => {
|
|
45
|
+
originals.queueMicrotask.call(globalThis, () => {
|
|
46
|
+
originals.queueMicrotask.call(globalThis, () => {
|
|
47
|
+
for (let i = undoers.length - 1; i >= 0; i--) undoers[i]()
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
} else {
|
|
52
|
+
for (let i = undoers.length - 1; i >= 0; i--) undoers[i]()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const targetWrappers = new WeakMap<any, Map<string, WeakMap<Function, Function>>>()
|
|
59
|
+
|
|
60
|
+
function patchEventTarget(proto: any) {
|
|
61
|
+
if (!proto || !proto.addEventListener || !proto.removeEventListener) return
|
|
62
|
+
const nativeAdd = proto.addEventListener
|
|
63
|
+
const nativeRemove = proto.removeEventListener
|
|
64
|
+
|
|
65
|
+
proto.addEventListener = function (this: any, type: string, listener: any, options: any) {
|
|
66
|
+
if (typeof listener !== 'function') {
|
|
67
|
+
return nativeAdd.call(this, type, listener, options)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let types = targetWrappers.get(this)
|
|
71
|
+
if (!types) {
|
|
72
|
+
types = new Map()
|
|
73
|
+
targetWrappers.set(this, types)
|
|
74
|
+
}
|
|
75
|
+
let listeners = types.get(type)
|
|
76
|
+
if (!listeners) {
|
|
77
|
+
listeners = new WeakMap()
|
|
78
|
+
types.set(type, listeners)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let wrapped = listeners.get(listener)
|
|
82
|
+
if (!wrapped) {
|
|
83
|
+
wrapped = wrap(listener)
|
|
84
|
+
listeners.set(listener, wrapped)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return nativeAdd.call(this, type, wrapped, options)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
proto.removeEventListener = function (this: any, type: string, listener: any, options: any) {
|
|
91
|
+
if (typeof listener !== 'function') {
|
|
92
|
+
return nativeRemove.call(this, type, listener, options)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const types = targetWrappers.get(this)
|
|
96
|
+
if (types) {
|
|
97
|
+
const listeners = types.get(type)
|
|
98
|
+
if (listeners) {
|
|
99
|
+
const wrapped = listeners.get(listener)
|
|
100
|
+
if (wrapped) {
|
|
101
|
+
return nativeRemove.call(this, type, wrapped, options)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
26
104
|
}
|
|
105
|
+
|
|
106
|
+
return nativeRemove.call(this, type, listener, options)
|
|
27
107
|
}
|
|
28
108
|
}
|
|
29
109
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
110
|
+
function patchOnProperties(proto: any) {
|
|
111
|
+
if (!proto) return
|
|
112
|
+
for (const prop of Object.getOwnPropertyNames(proto)) {
|
|
113
|
+
if (prop.startsWith('on')) {
|
|
114
|
+
const desc = Object.getOwnPropertyDescriptor(proto, prop)
|
|
115
|
+
if (desc && desc.set && desc.configurable) {
|
|
116
|
+
const nativeSet = desc.set
|
|
117
|
+
Object.defineProperty(proto, prop, {
|
|
118
|
+
...desc,
|
|
119
|
+
set: function (this: any, fn: any) {
|
|
120
|
+
nativeSet.call(this, wrap(fn))
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
39
126
|
}
|
|
40
127
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
onFulfilled?: ((value: T) => R1 | PromiseLike<R1>) | null,
|
|
44
|
-
onRejected?: ((reason: any) => R2 | PromiseLike<R2>) | null
|
|
45
|
-
): Promise<R1 | R2> {
|
|
46
|
-
return originals.then.call(this, wrap(onFulfilled), wrap(onRejected))
|
|
128
|
+
if (typeof EventTarget !== 'undefined') {
|
|
129
|
+
patchEventTarget(EventTarget.prototype)
|
|
47
130
|
}
|
|
48
131
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
132
|
+
const prototypesToPatch = [
|
|
133
|
+
typeof EventTarget !== 'undefined' && EventTarget.prototype,
|
|
134
|
+
typeof HTMLElement !== 'undefined' && HTMLElement.prototype,
|
|
135
|
+
typeof Window !== 'undefined' && Window.prototype,
|
|
136
|
+
typeof Document !== 'undefined' && Document.prototype,
|
|
137
|
+
typeof MessagePort !== 'undefined' && MessagePort.prototype,
|
|
138
|
+
typeof XMLHttpRequest !== 'undefined' && XMLHttpRequest.prototype,
|
|
139
|
+
typeof IDBRequest !== 'undefined' && IDBRequest.prototype,
|
|
140
|
+
typeof IDBTransaction !== 'undefined' && IDBTransaction.prototype,
|
|
141
|
+
typeof IDBDatabase !== 'undefined' && IDBDatabase.prototype,
|
|
142
|
+
typeof FileReader !== 'undefined' && FileReader.prototype,
|
|
143
|
+
typeof AbortSignal !== 'undefined' && AbortSignal.prototype,
|
|
144
|
+
]
|
|
145
|
+
|
|
146
|
+
for (const proto of prototypesToPatch) {
|
|
147
|
+
if (proto) {
|
|
148
|
+
patchOnProperties(proto)
|
|
149
|
+
}
|
|
54
150
|
}
|
|
55
151
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
152
|
+
const GLOBAL_ORIGINALS = Symbol.for('mutts.originals')
|
|
153
|
+
const GLOBAL_PROMISE = Symbol.for('mutts.OriginalPromise')
|
|
154
|
+
|
|
155
|
+
let originals: any
|
|
156
|
+
let OriginalPromise: any
|
|
157
|
+
|
|
158
|
+
if ((globalThis as any)[GLOBAL_ORIGINALS]) {
|
|
159
|
+
originals = (globalThis as any)[GLOBAL_ORIGINALS]
|
|
160
|
+
OriginalPromise = (globalThis as any)[GLOBAL_PROMISE]
|
|
161
|
+
} else {
|
|
162
|
+
OriginalPromise = globalThis.Promise
|
|
163
|
+
originals = {
|
|
164
|
+
then: OriginalPromise.prototype.then,
|
|
165
|
+
catch: OriginalPromise.prototype.catch,
|
|
166
|
+
finally: OriginalPromise.prototype.finally,
|
|
167
|
+
resolve: OriginalPromise.resolve,
|
|
168
|
+
reject: OriginalPromise.reject,
|
|
169
|
+
all: OriginalPromise.all,
|
|
170
|
+
allSettled: (OriginalPromise as any).allSettled,
|
|
171
|
+
race: OriginalPromise.race,
|
|
172
|
+
any: (OriginalPromise as any).any,
|
|
173
|
+
setTimeout: globalThis.setTimeout,
|
|
174
|
+
setInterval: globalThis.setInterval,
|
|
175
|
+
setImmediate: (globalThis as any).setImmediate,
|
|
176
|
+
requestAnimationFrame: (globalThis as any).requestAnimationFrame,
|
|
177
|
+
queueMicrotask: globalThis.queueMicrotask,
|
|
178
|
+
}
|
|
179
|
+
;(globalThis as any)[GLOBAL_ORIGINALS] = originals
|
|
180
|
+
;(globalThis as any)[GLOBAL_PROMISE] = OriginalPromise
|
|
61
181
|
}
|
|
62
182
|
|
|
183
|
+
// Ensure modern statics are captured even if originals was cached from an older version
|
|
184
|
+
if (!originals.allSettled) originals.allSettled = (OriginalPromise as any).allSettled
|
|
185
|
+
if (!originals.any) originals.any = (OriginalPromise as any).any
|
|
186
|
+
if (!originals.race) originals.race = OriginalPromise.race
|
|
187
|
+
|
|
188
|
+
function patchedThen(this: any, onFulfilled: any, onRejected: any) {
|
|
189
|
+
const context = promiseContexts.get(this) || captureRestorers()
|
|
190
|
+
const nextPromise = originals.then.call(
|
|
191
|
+
this,
|
|
192
|
+
wrap(onFulfilled, context),
|
|
193
|
+
wrap(onRejected, context)
|
|
194
|
+
)
|
|
195
|
+
if (context.size > 0) promiseContexts.set(nextPromise, context)
|
|
196
|
+
return nextPromise
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function patchedCatch(this: any, onRejected: any) {
|
|
200
|
+
const context = promiseContexts.get(this) || captureRestorers()
|
|
201
|
+
const nextPromise = originals.catch.call(this, wrap(onRejected, context))
|
|
202
|
+
if (context.size > 0) promiseContexts.set(nextPromise, context)
|
|
203
|
+
return nextPromise
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function patchedFinally(this: any, onFinally: any) {
|
|
207
|
+
const context = promiseContexts.get(this) || captureRestorers()
|
|
208
|
+
const nextPromise = originals.finally.call(this, wrap(onFinally, context))
|
|
209
|
+
if (context.size > 0) promiseContexts.set(nextPromise, context)
|
|
210
|
+
return nextPromise
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function PatchedPromise<T>(
|
|
214
|
+
this: any,
|
|
215
|
+
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void
|
|
216
|
+
) {
|
|
217
|
+
if (typeof executor === 'function') {
|
|
218
|
+
const p = new OriginalPromise((resolve, reject) => {
|
|
219
|
+
const wrappedResolve = wrap(resolve)
|
|
220
|
+
const wrappedReject = wrap(reject)
|
|
221
|
+
executor(wrappedResolve, wrappedReject)
|
|
222
|
+
})
|
|
223
|
+
const context = captureRestorers()
|
|
224
|
+
promiseContexts.set(p, context) // Always set, even if empty (Sticky Root)
|
|
225
|
+
return p
|
|
226
|
+
}
|
|
227
|
+
return new OriginalPromise(executor)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Copy statics
|
|
231
|
+
Object.assign(PatchedPromise, OriginalPromise as any)
|
|
232
|
+
|
|
233
|
+
// Inherit prototype for instanceof checks
|
|
234
|
+
PatchedPromise.prototype = OriginalPromise.prototype
|
|
235
|
+
|
|
236
|
+
PatchedPromise.resolve = (<T>(value?: T | PromiseLike<T>): Promise<T> => {
|
|
237
|
+
const p = originals.resolve.call(OriginalPromise, value) as Promise<T>
|
|
238
|
+
const context = captureRestorers()
|
|
239
|
+
// Ensure we don't overwrite if it already has context (e.g. from constructor)
|
|
240
|
+
if (context.size > 0 && !promiseContexts.has(p)) promiseContexts.set(p, context)
|
|
241
|
+
return p
|
|
242
|
+
}) as any
|
|
243
|
+
|
|
244
|
+
PatchedPromise.reject = (<T = never>(reason?: any): Promise<T> => {
|
|
245
|
+
const p = originals.reject.call(OriginalPromise, reason) as Promise<T>
|
|
246
|
+
const context = captureRestorers()
|
|
247
|
+
if (context.size > 0) promiseContexts.set(p, context)
|
|
248
|
+
return p
|
|
249
|
+
}) as any
|
|
250
|
+
|
|
251
|
+
PatchedPromise.all = (<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>[]> => {
|
|
252
|
+
const p = originals.all.call(OriginalPromise, values) as Promise<Awaited<T>[]>
|
|
253
|
+
const context = captureRestorers()
|
|
254
|
+
if (context.size > 0) promiseContexts.set(p, context)
|
|
255
|
+
return p
|
|
256
|
+
}) as any
|
|
257
|
+
|
|
258
|
+
PatchedPromise.allSettled = (<T>(
|
|
259
|
+
values: Iterable<T | PromiseLike<T>>
|
|
260
|
+
): Promise<PromiseSettledResult<Awaited<T>>[]> => {
|
|
261
|
+
const p = (originals.allSettled as any).call(OriginalPromise, values)
|
|
262
|
+
const context = captureRestorers()
|
|
263
|
+
if (context.size > 0) promiseContexts.set(p, context)
|
|
264
|
+
return p
|
|
265
|
+
}) as any
|
|
266
|
+
|
|
267
|
+
PatchedPromise.race = (<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>> => {
|
|
268
|
+
const p = originals.race.call(OriginalPromise, values) as Promise<Awaited<T>>
|
|
269
|
+
const context = captureRestorers()
|
|
270
|
+
if (context.size > 0) promiseContexts.set(p, context)
|
|
271
|
+
return p
|
|
272
|
+
}) as any
|
|
273
|
+
|
|
274
|
+
PatchedPromise.any = (<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>> => {
|
|
275
|
+
const p = (originals.any as any).call(OriginalPromise, values)
|
|
276
|
+
const context = captureRestorers()
|
|
277
|
+
if (context.size > 0) promiseContexts.set(p, context)
|
|
278
|
+
return p
|
|
279
|
+
}) as any
|
|
280
|
+
|
|
281
|
+
// Only apply patches if not already applied (or re-apply safely)
|
|
282
|
+
// Note: OriginalPromise.prototype might be shared if we used the global one.
|
|
283
|
+
// We must ensure we don't patch it twice if it's the SAME object.
|
|
284
|
+
if (OriginalPromise.prototype.then !== patchedThen) {
|
|
285
|
+
OriginalPromise.prototype.then = patchedThen as any
|
|
286
|
+
OriginalPromise.prototype.catch = patchedCatch as any
|
|
287
|
+
OriginalPromise.prototype.finally = patchedFinally as any
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
Object.defineProperty(OriginalPromise, Symbol.species, {
|
|
292
|
+
get: () => PatchedPromise,
|
|
293
|
+
configurable: true,
|
|
294
|
+
})
|
|
295
|
+
} catch (e) {}
|
|
296
|
+
|
|
297
|
+
;(globalThis as any).Promise = PatchedPromise
|
|
298
|
+
|
|
63
299
|
globalThis.setTimeout = ((callback: Function, ...args: any[]) => {
|
|
64
300
|
return originals.setTimeout.call(globalThis, wrap(callback as any), ...args)
|
|
65
301
|
}) as any
|
|
@@ -69,7 +305,7 @@ globalThis.setInterval = ((callback: Function, ...args: any[]) => {
|
|
|
69
305
|
}) as any
|
|
70
306
|
|
|
71
307
|
if (originals.setImmediate) {
|
|
72
|
-
globalThis.setImmediate = ((callback: Function, ...args: any[]) => {
|
|
308
|
+
;(globalThis as any).setImmediate = ((callback: Function, ...args: any[]) => {
|
|
73
309
|
return originals.setImmediate.call(globalThis, wrap(callback as any), ...args)
|
|
74
310
|
}) as any
|
|
75
311
|
}
|
|
@@ -84,4 +320,4 @@ if (originals.queueMicrotask) {
|
|
|
84
320
|
globalThis.queueMicrotask = (callback: VoidFunction): void => {
|
|
85
321
|
originals.queueMicrotask.call(globalThis, wrap(callback))
|
|
86
322
|
}
|
|
87
|
-
}
|
|
323
|
+
}
|
package/src/async/index.ts
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
export type Restorer = () => () => void
|
|
2
2
|
export type Hook = () => Restorer
|
|
3
3
|
|
|
4
|
+
// Queue for hooks registered before the environment is ready (circular dependency fix)
|
|
5
|
+
export const hooks = new Set<Hook>()
|
|
6
|
+
|
|
4
7
|
export const asyncHooks = {
|
|
5
|
-
addHook(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
8
|
+
addHook(hook: Hook): () => void {
|
|
9
|
+
hooks.add(hook)
|
|
10
|
+
return () => hooks.delete(hook)
|
|
11
|
+
},
|
|
12
|
+
/**
|
|
13
|
+
* [Hack] Sanitize a promise (or value) to prevent context leaks.
|
|
14
|
+
* Default: Identity function.
|
|
15
|
+
* Browser: Uses Macrotask wrapping to break microtask chains.
|
|
16
|
+
*/
|
|
17
|
+
sanitizePromise(p: any): any {
|
|
18
|
+
return p
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register a hook that will be called whenever an asynchronous operation is initiated.
|
|
24
|
+
* The hook should return a restorer function which will be called just before the async callback runs.
|
|
25
|
+
* That restorer should in turn return an undoer function which will be called just after the async callback finishes.
|
|
26
|
+
*/
|
|
27
|
+
export const asyncHook = (hook: Hook) => asyncHooks.addHook(hook)
|
package/src/async/node.ts
CHANGED
|
@@ -1,46 +1,92 @@
|
|
|
1
1
|
import { createHook } from 'node:async_hooks'
|
|
2
|
-
import {
|
|
2
|
+
import { hooks, type Restorer } from '.'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const undoersPerAsyncId = new Map<number, Set<() => void>>()
|
|
4
|
+
// 1. Generic async_hooks implementation for Hooks
|
|
5
|
+
// This maintains support for 'asyncHooks.addHook' for generic use cases.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
const contexts = new Map<number, Restorer[]>()
|
|
8
|
+
const activeUndoers = new Map<number, (() => void)[]>()
|
|
9
|
+
|
|
10
|
+
// Helper to capture current hooks state
|
|
11
|
+
function captureRestorers() {
|
|
12
|
+
if (hooks.size === 0) return []
|
|
13
|
+
const restorers: Restorer[] = []
|
|
14
|
+
for (const h of hooks) {
|
|
15
|
+
const r = h()
|
|
16
|
+
if (r) restorers.push(r)
|
|
17
|
+
}
|
|
18
|
+
return restorers
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Manual Wrap function to handle Promise callbacks
|
|
22
|
+
function wrap<Args extends any[], R>(fn: ((...args: Args) => R) | null | undefined) {
|
|
23
|
+
if (typeof fn !== 'function') return fn
|
|
24
|
+
const restorers = captureRestorers()
|
|
25
|
+
if (restorers.length === 0) return fn
|
|
26
|
+
|
|
27
|
+
return function (this: any, ...args: Args) {
|
|
28
|
+
const undoers: (() => void)[] = []
|
|
29
|
+
for (const restore of restorers) {
|
|
30
|
+
const u = restore()
|
|
31
|
+
if (u) undoers.push(u)
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return fn.apply(this, args)
|
|
35
|
+
} finally {
|
|
36
|
+
for (let i = undoers.length - 1; i >= 0; i--) undoers[i]()
|
|
13
37
|
}
|
|
14
|
-
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const hook = createHook({
|
|
42
|
+
init(asyncId, _type, _triggerId, _resource) {
|
|
43
|
+
// Used for native resources like Timers
|
|
44
|
+
const restorers = captureRestorers()
|
|
45
|
+
if (restorers.length > 0) contexts.set(asyncId, restorers)
|
|
15
46
|
},
|
|
16
47
|
before(asyncId) {
|
|
17
|
-
const restorers =
|
|
18
|
-
if (restorers)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
undoersPerAsyncId.set(asyncId, undoers)
|
|
48
|
+
const restorers = contexts.get(asyncId)
|
|
49
|
+
if (!restorers) return
|
|
50
|
+
const undoers: (() => void)[] = []
|
|
51
|
+
for (const restore of restorers) {
|
|
52
|
+
const u = restore()
|
|
53
|
+
if (u) undoers.push(u)
|
|
24
54
|
}
|
|
55
|
+
if (undoers.length > 0) activeUndoers.set(asyncId, undoers)
|
|
25
56
|
},
|
|
26
57
|
after(asyncId) {
|
|
27
|
-
const undoers =
|
|
28
|
-
if (undoers)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
58
|
+
const undoers = activeUndoers.get(asyncId)
|
|
59
|
+
if (!undoers) return
|
|
60
|
+
for (let i = undoers.length - 1; i >= 0; i--) undoers[i]()
|
|
61
|
+
activeUndoers.delete(asyncId)
|
|
32
62
|
},
|
|
33
63
|
destroy(asyncId) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
})
|
|
64
|
+
contexts.delete(asyncId)
|
|
65
|
+
activeUndoers.delete(asyncId)
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
hook.enable()
|
|
38
69
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
70
|
+
// 2. Shadow Promise Implementation
|
|
71
|
+
// Ensures V8 await resumptions are visible as .then callbacks, wrapping them to restore context.
|
|
72
|
+
|
|
73
|
+
const OriginalPromise = globalThis.Promise
|
|
74
|
+
const originalMethods = {
|
|
75
|
+
then: OriginalPromise.prototype.then,
|
|
76
|
+
catch: OriginalPromise.prototype.catch,
|
|
77
|
+
finally: OriginalPromise.prototype.finally,
|
|
78
|
+
resolve: OriginalPromise.resolve,
|
|
79
|
+
reject: OriginalPromise.reject,
|
|
80
|
+
all: OriginalPromise.all,
|
|
44
81
|
}
|
|
45
82
|
|
|
46
|
-
|
|
83
|
+
// Patch prototype
|
|
84
|
+
OriginalPromise.prototype.then = function (onFulfilled, onRejected) {
|
|
85
|
+
return originalMethods.then.call(this, wrap(onFulfilled), wrap(onRejected))
|
|
86
|
+
} as any
|
|
87
|
+
OriginalPromise.prototype.catch = function (onRejected) {
|
|
88
|
+
return originalMethods.catch.call(this, wrap(onRejected))
|
|
89
|
+
} as any
|
|
90
|
+
OriginalPromise.prototype.finally = function (onFinally) {
|
|
91
|
+
return originalMethods.finally.call(this, wrap(onFinally))
|
|
92
|
+
} as any
|
package/src/eventful.ts
CHANGED
|
@@ -2,76 +2,109 @@
|
|
|
2
2
|
* Base type for event maps - all event handlers must be functions
|
|
3
3
|
*/
|
|
4
4
|
export type EventsBase = Record<string, (...args: any[]) => void>
|
|
5
|
-
/**
|
|
6
|
-
* A type-safe event system that provides a clean API for event handling
|
|
7
|
-
* @template Events - The event map defining event names and their handler signatures
|
|
8
|
-
*/
|
|
9
|
-
export class Eventful<Events extends EventsBase> {
|
|
10
|
-
readonly #events = new Map<keyof Events, ((...args: any[]) => void)[]>()
|
|
11
|
-
readonly #hooks = [] as ((...args: any[]) => void)[]
|
|
12
5
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
event: EventType,
|
|
16
|
-
...args: Parameters<Events[EventType]>
|
|
17
|
-
) => void
|
|
18
|
-
): () => void {
|
|
19
|
-
if (!this.#hooks.includes(cb)) this.#hooks.push(cb)
|
|
20
|
-
return () => {
|
|
21
|
-
this.#hooks.splice(this.#hooks.indexOf(cb), 1)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
6
|
+
const events = Symbol('events')
|
|
7
|
+
const hooks = Symbol('hooks')
|
|
24
8
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
cb?: Events[EventType]
|
|
9
|
+
const eventBehavior = {
|
|
10
|
+
on<EventType extends keyof EventsBase>(
|
|
11
|
+
eventOrEvents: EventType | Partial<EventsBase>,
|
|
12
|
+
cb?: EventsBase[EventType]
|
|
30
13
|
): () => void {
|
|
31
14
|
if (typeof eventOrEvents === 'object') {
|
|
32
|
-
for (const e of Object.keys(eventOrEvents) as (keyof
|
|
15
|
+
for (const e of Object.keys(eventOrEvents) as (keyof EventsBase)[]) {
|
|
33
16
|
this.on(e, eventOrEvents[e]!)
|
|
34
17
|
}
|
|
35
18
|
} else if (cb !== undefined) {
|
|
36
|
-
|
|
37
|
-
if (!callbacks)
|
|
38
|
-
|
|
39
|
-
this.#events.set(eventOrEvents, callbacks)
|
|
40
|
-
}
|
|
41
|
-
callbacks.push(cb)
|
|
19
|
+
const callbacks = this[events].get(eventOrEvents) ?? new Set<EventsBase[EventType]>()
|
|
20
|
+
if (!callbacks.has(cb)) callbacks.add(cb)
|
|
21
|
+
this[events].set(eventOrEvents, callbacks)
|
|
42
22
|
}
|
|
43
|
-
// @ts-expect-error Generic case leads to generic case
|
|
44
23
|
return () => this.off(eventOrEvents, cb)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
eventOrEvents: EventType | Partial<Events>,
|
|
50
|
-
cb?: Events[EventType]
|
|
24
|
+
},
|
|
25
|
+
off<EventType extends keyof EventsBase>(
|
|
26
|
+
eventOrEvents: EventType | Partial<EventsBase>,
|
|
27
|
+
cb?: EventsBase[EventType]
|
|
51
28
|
): void {
|
|
52
29
|
if (typeof eventOrEvents === 'object') {
|
|
53
|
-
for (const e of Object.keys(eventOrEvents) as (keyof
|
|
30
|
+
for (const e of Object.keys(eventOrEvents) as (keyof EventsBase)[]) {
|
|
54
31
|
this.off(e, eventOrEvents[e])
|
|
55
32
|
}
|
|
56
33
|
} else if (cb !== null && cb !== undefined) {
|
|
57
|
-
const callbacks = this
|
|
34
|
+
const callbacks = this[events].get(eventOrEvents)
|
|
58
35
|
if (callbacks) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
callbacks.filter((c) => c !== cb)
|
|
62
|
-
)
|
|
36
|
+
callbacks.delete(cb)
|
|
37
|
+
if (!callbacks.size) this[events].delete(eventOrEvents)
|
|
63
38
|
}
|
|
64
39
|
} else {
|
|
65
40
|
// Remove all listeners for this event
|
|
66
|
-
this
|
|
41
|
+
this[events].delete(eventOrEvents)
|
|
67
42
|
}
|
|
68
|
-
}
|
|
69
|
-
|
|
43
|
+
},
|
|
44
|
+
emit<EventType extends keyof EventsBase>(
|
|
70
45
|
event: EventType,
|
|
71
|
-
...args: Parameters<
|
|
46
|
+
...args: Parameters<EventsBase[EventType]>
|
|
72
47
|
) {
|
|
73
|
-
const callbacks = this
|
|
48
|
+
const callbacks = this[events].get(event)
|
|
74
49
|
if (callbacks) for (const cb of callbacks) cb.apply(this, args)
|
|
75
|
-
for (const cb of this
|
|
50
|
+
for (const cb of this[hooks]) cb.call(this, event, ...args)
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function perEvent(
|
|
55
|
+
eventful: Eventful<any>,
|
|
56
|
+
fct: (event: string, ...args: any[]) => void,
|
|
57
|
+
use?: 'use'
|
|
58
|
+
) {
|
|
59
|
+
const cache = new Map<string, (...args: any[]) => any>()
|
|
60
|
+
return new Proxy(fct, {
|
|
61
|
+
get(target, prop: PropertyKey) {
|
|
62
|
+
if (typeof prop !== 'string') return target[prop]
|
|
63
|
+
if (use && !eventful[events].has(prop) && !eventful[hooks].size) return () => {}
|
|
64
|
+
|
|
65
|
+
// Return cached function or create and cache
|
|
66
|
+
let cached = cache.get(prop)
|
|
67
|
+
if (!cached) {
|
|
68
|
+
cached = (...args: any[]) => fct.apply(eventful, [prop, ...args])
|
|
69
|
+
cache.set(prop, cached)
|
|
70
|
+
}
|
|
71
|
+
return cached
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A type-safe event system that provides a clean API for event handling
|
|
78
|
+
* @template Events - The event map defining event names and their handler signatures
|
|
79
|
+
*/
|
|
80
|
+
export class Eventful<Events extends EventsBase> {
|
|
81
|
+
private readonly [events] = new Map<keyof Events, Set<(...args: any[]) => void>>()
|
|
82
|
+
private readonly [hooks] = new Set<(...args: any[]) => void>()
|
|
83
|
+
|
|
84
|
+
public hook(
|
|
85
|
+
cb: <EventType extends keyof Events>(
|
|
86
|
+
event: EventType,
|
|
87
|
+
...args: Parameters<Events[EventType]>
|
|
88
|
+
) => void
|
|
89
|
+
): () => void {
|
|
90
|
+
this[hooks].add(cb)
|
|
91
|
+
return () => {
|
|
92
|
+
this[hooks].delete(cb)
|
|
93
|
+
}
|
|
76
94
|
}
|
|
95
|
+
|
|
96
|
+
public on = perEvent(this, eventBehavior.on) as ((events: Partial<Events>) => void) &
|
|
97
|
+
(<EventType extends keyof Events>(event: EventType, cb: Events[EventType]) => () => void) & {
|
|
98
|
+
[event in keyof Events]: (cb: Events[event]) => () => void
|
|
99
|
+
}
|
|
100
|
+
public off = perEvent(this, eventBehavior.off) as ((events: Partial<Events>) => void) &
|
|
101
|
+
(<EventType extends keyof Events>(event: EventType, cb?: Events[EventType]) => void) & {
|
|
102
|
+
[event in keyof Events]: (cb?: Events[event]) => void
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public emit = perEvent(this, eventBehavior.emit, 'use') as (<EventType extends keyof Events>(
|
|
106
|
+
event: EventType,
|
|
107
|
+
...args: Parameters<Events[EventType]>
|
|
108
|
+
) => void) &
|
|
109
|
+
Events
|
|
77
110
|
}
|