mutts 1.0.2 → 1.0.4
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 +14 -6
- package/dist/chunks/{_tslib-C-cuVLvZ.js → _tslib-BgjropY9.js} +9 -1
- package/dist/chunks/_tslib-BgjropY9.js.map +1 -0
- package/dist/chunks/{_tslib-CMEnd0VE.esm.js → _tslib-Mzh1rNsX.esm.js} +9 -2
- package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +1 -0
- package/dist/chunks/{decorator-D4DU97Zg.js → decorator-DLvrD0UF.js} +42 -19
- package/dist/chunks/decorator-DLvrD0UF.js.map +1 -0
- package/dist/chunks/{decorator-GnHw1Az7.esm.js → decorator-DqiszP7i.esm.js} +42 -19
- package/dist/chunks/decorator-DqiszP7i.esm.js.map +1 -0
- package/dist/chunks/index-79Kk8D6e.esm.js +4857 -0
- package/dist/chunks/index-79Kk8D6e.esm.js.map +1 -0
- package/dist/chunks/index-GRBSx0mB.js +4908 -0
- package/dist/chunks/index-GRBSx0mB.js.map +1 -0
- package/dist/decorator.esm.js +1 -1
- package/dist/decorator.js +1 -1
- package/dist/destroyable.d.ts +1 -1
- package/dist/destroyable.esm.js +1 -1
- package/dist/destroyable.esm.js.map +1 -1
- package/dist/destroyable.js +1 -1
- package/dist/destroyable.js.map +1 -1
- package/dist/devtools/devtools.html +9 -0
- package/dist/devtools/devtools.js +5 -0
- package/dist/devtools/devtools.js.map +1 -0
- package/dist/devtools/manifest.json +8 -0
- package/dist/devtools/panel.css +72 -0
- package/dist/devtools/panel.html +31 -0
- package/dist/devtools/panel.js +13048 -0
- package/dist/devtools/panel.js.map +1 -0
- package/dist/eventful.esm.js +1 -1
- package/dist/eventful.js +1 -1
- package/dist/index.d.ts +18 -63
- package/dist/index.esm.js +4 -4
- package/dist/index.js +37 -11
- package/dist/index.js.map +1 -1
- package/dist/indexable.d.ts +187 -1
- package/dist/indexable.esm.js +197 -3
- package/dist/indexable.esm.js.map +1 -1
- package/dist/indexable.js +198 -2
- package/dist/indexable.js.map +1 -1
- 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/promiseChain.esm.js.map +1 -1
- package/dist/promiseChain.js.map +1 -1
- package/dist/reactive.d.ts +602 -97
- package/dist/reactive.esm.js +3 -3
- package/dist/reactive.js +32 -10
- package/dist/reactive.js.map +1 -1
- package/dist/std-decorators.esm.js +1 -1
- package/dist/std-decorators.js +1 -1
- package/docs/ai/api-reference.md +133 -0
- package/docs/ai/manual.md +105 -0
- package/docs/iterableWeak.md +646 -0
- package/docs/reactive/advanced.md +1280 -0
- package/docs/reactive/collections.md +767 -0
- package/docs/reactive/core.md +973 -0
- package/docs/reactive.md +21 -9545
- package/package.json +18 -5
- package/src/decorator.ts +266 -0
- package/src/destroyable.ts +199 -0
- package/src/eventful.ts +77 -0
- package/src/index.d.ts +9 -0
- package/src/index.ts +9 -0
- package/src/indexable.ts +484 -0
- package/src/introspection.ts +59 -0
- package/src/iterableWeak.ts +233 -0
- package/src/mixins.ts +123 -0
- package/src/promiseChain.ts +110 -0
- package/src/reactive/array.ts +414 -0
- package/src/reactive/change.ts +134 -0
- package/src/reactive/debug.ts +517 -0
- package/src/reactive/deep-touch.ts +268 -0
- package/src/reactive/deep-watch-state.ts +82 -0
- package/src/reactive/deep-watch.ts +168 -0
- package/src/reactive/effect-context.ts +94 -0
- package/src/reactive/effects.ts +1345 -0
- package/src/reactive/index.ts +76 -0
- package/src/reactive/interface.ts +223 -0
- package/src/reactive/map.ts +171 -0
- package/src/reactive/mapped.ts +130 -0
- package/src/reactive/memoize.ts +107 -0
- package/src/reactive/non-reactive-state.ts +49 -0
- package/src/reactive/non-reactive.ts +43 -0
- package/src/reactive/project.project.md +93 -0
- package/src/reactive/project.ts +335 -0
- package/src/reactive/proxy-state.ts +27 -0
- package/src/reactive/proxy.ts +289 -0
- package/src/reactive/record.ts +196 -0
- package/src/reactive/register.ts +421 -0
- package/src/reactive/set.ts +144 -0
- package/src/reactive/tracking.ts +101 -0
- package/src/reactive/types.ts +358 -0
- package/src/reactive/zone.ts +208 -0
- package/src/std-decorators.ts +217 -0
- package/src/utils.ts +117 -0
- package/dist/chunks/_tslib-C-cuVLvZ.js.map +0 -1
- package/dist/chunks/_tslib-CMEnd0VE.esm.js.map +0 -1
- package/dist/chunks/decorator-D4DU97Zg.js.map +0 -1
- package/dist/chunks/decorator-GnHw1Az7.esm.js.map +0 -1
- package/dist/chunks/index-DBScoeCX.esm.js +0 -1960
- package/dist/chunks/index-DBScoeCX.esm.js.map +0 -1
- package/dist/chunks/index-DOTmXL89.js +0 -1983
- package/dist/chunks/index-DOTmXL89.js.map +0 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/// <reference lib="esnext.collection" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Uses weak references but still may iterate through them
|
|
5
|
+
* Note: The behavior is highly dependant on the garbage collector - some entries are perhaps deemed to be collected: don't resuscitate them
|
|
6
|
+
*/
|
|
7
|
+
export class IterableWeakMap<K extends WeakKey, V> implements Map<K, V> {
|
|
8
|
+
private uuids = new WeakMap<K, string>()
|
|
9
|
+
private refs: Record<string, [WeakRef<K>, any]> = {}
|
|
10
|
+
private readonly registry: FinalizationRegistry<string>
|
|
11
|
+
|
|
12
|
+
constructor(entries?: Iterable<[K, V]>) {
|
|
13
|
+
// Create a FinalizationRegistry to clean up refs when keys are garbage collected
|
|
14
|
+
this.registry = new FinalizationRegistry((uuid: string) => {
|
|
15
|
+
delete this.refs[uuid]
|
|
16
|
+
})
|
|
17
|
+
if (entries) for (const [k, v] of entries) this.set(k, v)
|
|
18
|
+
}
|
|
19
|
+
private createIterator<I>(cb: (key: K, value: V) => I): MapIterator<I> {
|
|
20
|
+
const { refs } = this
|
|
21
|
+
return (function* () {
|
|
22
|
+
for (const uuid of Object.keys(refs)) {
|
|
23
|
+
const [keyRef, value] = refs[uuid]
|
|
24
|
+
const key = keyRef.deref()
|
|
25
|
+
if (key) yield cb(key, value)
|
|
26
|
+
else delete refs[uuid]
|
|
27
|
+
}
|
|
28
|
+
return undefined
|
|
29
|
+
})()
|
|
30
|
+
}
|
|
31
|
+
clear(): void {
|
|
32
|
+
// Unregister all keys from the FinalizationRegistry
|
|
33
|
+
for (const uuid of Object.keys(this.refs)) {
|
|
34
|
+
const key = this.refs[uuid][0].deref()
|
|
35
|
+
if (key) this.registry.unregister(key)
|
|
36
|
+
}
|
|
37
|
+
this.uuids = new WeakMap<K, string>()
|
|
38
|
+
this.refs = {}
|
|
39
|
+
}
|
|
40
|
+
delete(key: K): boolean {
|
|
41
|
+
const uuid = this.uuids.get(key)
|
|
42
|
+
if (!uuid) return false
|
|
43
|
+
delete this.refs[uuid]
|
|
44
|
+
this.uuids.delete(key)
|
|
45
|
+
this.registry.unregister(key)
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
|
|
49
|
+
for (const [k, v] of this) callbackfn.call(thisArg ?? this, v, k, thisArg ?? this)
|
|
50
|
+
}
|
|
51
|
+
get(key: K): V | undefined {
|
|
52
|
+
const uuid = this.uuids.get(key)
|
|
53
|
+
if (!uuid) return undefined
|
|
54
|
+
return this.refs[uuid][1]
|
|
55
|
+
}
|
|
56
|
+
has(key: K): boolean {
|
|
57
|
+
return this.uuids.has(key)
|
|
58
|
+
}
|
|
59
|
+
set(key: K, value: V): this {
|
|
60
|
+
let uuid = this.uuids.get(key)
|
|
61
|
+
if (uuid) {
|
|
62
|
+
this.refs[uuid][1] = value
|
|
63
|
+
} else {
|
|
64
|
+
uuid = crypto.randomUUID()
|
|
65
|
+
this.uuids.set(key, uuid)
|
|
66
|
+
this.refs[uuid] = [new WeakRef(key), value]
|
|
67
|
+
// Register key for cleanup when garbage collected
|
|
68
|
+
this.registry.register(key, uuid, key)
|
|
69
|
+
}
|
|
70
|
+
return this
|
|
71
|
+
}
|
|
72
|
+
get size(): number {
|
|
73
|
+
return [...this].length
|
|
74
|
+
}
|
|
75
|
+
entries(): MapIterator<[K, V]> {
|
|
76
|
+
return this.createIterator((key, value) => [key, value] as [K, V])
|
|
77
|
+
}
|
|
78
|
+
keys(): MapIterator<K> {
|
|
79
|
+
return this.createIterator((key, _value) => key)
|
|
80
|
+
}
|
|
81
|
+
values(): MapIterator<V> {
|
|
82
|
+
return this.createIterator((_key, value) => value)
|
|
83
|
+
}
|
|
84
|
+
[Symbol.iterator](): MapIterator<[K, V]> {
|
|
85
|
+
return this.entries()
|
|
86
|
+
}
|
|
87
|
+
readonly [Symbol.toStringTag]: string = 'IterableWeakMap'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Uses weak references but still may iterate through them
|
|
92
|
+
* Note: The behavior is highly dependant on the garbage collector - some entries are perhaps deemed to be collected: don't resuscitate them
|
|
93
|
+
*/
|
|
94
|
+
export class IterableWeakSet<K extends WeakKey> implements Set<K> {
|
|
95
|
+
private uuids = new WeakMap<K, string>()
|
|
96
|
+
private refs: Record<string, WeakRef<K>> = {}
|
|
97
|
+
private readonly registry: FinalizationRegistry<string>
|
|
98
|
+
|
|
99
|
+
constructor(entries?: Iterable<K>) {
|
|
100
|
+
// Create a FinalizationRegistry to clean up refs when values are garbage collected
|
|
101
|
+
this.registry = new FinalizationRegistry((uuid: string) => {
|
|
102
|
+
delete this.refs[uuid]
|
|
103
|
+
})
|
|
104
|
+
if (entries) for (const k of entries) this.add(k)
|
|
105
|
+
}
|
|
106
|
+
private createIterator<I>(cb: (key: K) => I): MapIterator<I> {
|
|
107
|
+
const { refs } = this
|
|
108
|
+
return (function* () {
|
|
109
|
+
for (const uuid of Object.keys(refs)) {
|
|
110
|
+
const key = refs[uuid].deref()
|
|
111
|
+
if (key) yield cb(key)
|
|
112
|
+
else delete refs[uuid]
|
|
113
|
+
}
|
|
114
|
+
return undefined
|
|
115
|
+
})()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
clear(): void {
|
|
119
|
+
// Unregister all values from the FinalizationRegistry
|
|
120
|
+
for (const uuid of Object.keys(this.refs)) {
|
|
121
|
+
const value = this.refs[uuid].deref()
|
|
122
|
+
if (value) this.registry.unregister(value)
|
|
123
|
+
}
|
|
124
|
+
this.uuids = new WeakMap<K, string>()
|
|
125
|
+
this.refs = {}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
add(value: K): this {
|
|
129
|
+
let uuid = this.uuids.get(value)
|
|
130
|
+
if (!uuid) {
|
|
131
|
+
uuid = crypto.randomUUID()
|
|
132
|
+
this.uuids.set(value, uuid)
|
|
133
|
+
this.refs[uuid] = new WeakRef(value)
|
|
134
|
+
// Register value for cleanup when garbage collected
|
|
135
|
+
this.registry.register(value, uuid, value)
|
|
136
|
+
}
|
|
137
|
+
return this
|
|
138
|
+
}
|
|
139
|
+
delete(value: K): boolean {
|
|
140
|
+
const uuid = this.uuids.get(value)
|
|
141
|
+
if (!uuid) return false
|
|
142
|
+
delete this.refs[uuid]
|
|
143
|
+
this.uuids.delete(value)
|
|
144
|
+
this.registry.unregister(value)
|
|
145
|
+
return true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
forEach(callbackfn: (value: K, value2: K, set: Set<K>) => void, thisArg?: any): void {
|
|
149
|
+
for (const value of this) callbackfn.call(thisArg ?? this, value, value, thisArg ?? this)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
has(value: K): boolean {
|
|
153
|
+
return this.uuids.has(value)
|
|
154
|
+
}
|
|
155
|
+
get size(): number {
|
|
156
|
+
return [...this].length
|
|
157
|
+
}
|
|
158
|
+
entries(): SetIterator<[K, K]> {
|
|
159
|
+
return this.createIterator((key) => [key, key] as [K, K])
|
|
160
|
+
}
|
|
161
|
+
keys(): SetIterator<K> {
|
|
162
|
+
return this.createIterator((key) => key)
|
|
163
|
+
}
|
|
164
|
+
values(): SetIterator<K> {
|
|
165
|
+
return this.createIterator((key) => key)
|
|
166
|
+
}
|
|
167
|
+
[Symbol.iterator](): SetIterator<K> {
|
|
168
|
+
return this.keys()
|
|
169
|
+
}
|
|
170
|
+
readonly [Symbol.toStringTag]: string = 'IterableWeakSet'
|
|
171
|
+
|
|
172
|
+
union<U>(other: ReadonlySetLike<U>): Set<K | U> {
|
|
173
|
+
const others = {
|
|
174
|
+
[Symbol.iterator]() {
|
|
175
|
+
return other.keys()
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
const that = this
|
|
179
|
+
return new Set(
|
|
180
|
+
(function* () {
|
|
181
|
+
yield* that
|
|
182
|
+
for (const value of others) if (!that.has(<K>(<unknown>value))) yield value
|
|
183
|
+
})()
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
intersection<U /**/>(other: ReadonlySetLike<U>): Set<K & U> {
|
|
187
|
+
const that = this
|
|
188
|
+
return new Set(
|
|
189
|
+
(function* () {
|
|
190
|
+
for (const value of that) if (other.has(<U>(<unknown>value))) yield <K & U>value
|
|
191
|
+
})()
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
difference<U>(other: ReadonlySetLike<U>): Set<K> {
|
|
195
|
+
const that = this
|
|
196
|
+
return new Set(
|
|
197
|
+
(function* () {
|
|
198
|
+
for (const value of that) if (!other.has(<U>(<unknown>value))) yield <K>value
|
|
199
|
+
})()
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
symmetricDifference<U>(other: ReadonlySetLike<U>): Set<K | U> {
|
|
203
|
+
const others = {
|
|
204
|
+
[Symbol.iterator]() {
|
|
205
|
+
return other.keys()
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
const that = this
|
|
209
|
+
return new Set(
|
|
210
|
+
(function* () {
|
|
211
|
+
for (const value of that) if (!other.has(<U>(<unknown>value))) yield <K | U>value
|
|
212
|
+
for (const value of others) if (!that.has(<K>(<unknown>value))) yield <K | U>value
|
|
213
|
+
})()
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
isSubsetOf(other: ReadonlySetLike<unknown>): boolean {
|
|
217
|
+
for (const value of this) if (!other.has(value)) return false
|
|
218
|
+
return true
|
|
219
|
+
}
|
|
220
|
+
isSupersetOf(other: ReadonlySetLike<unknown>): boolean {
|
|
221
|
+
const others = {
|
|
222
|
+
[Symbol.iterator]() {
|
|
223
|
+
return other.keys()
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
for (const value of others) if (!this.has(<K>value)) return false
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
isDisjointFrom(other: ReadonlySetLike<unknown>): boolean {
|
|
230
|
+
for (const value of this) if (other.has(value)) return false
|
|
231
|
+
return true
|
|
232
|
+
}
|
|
233
|
+
}
|
package/src/mixins.ts
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { isConstructor, ReflectGet } from './utils'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A mixin function that takes a base class and returns a new class with mixed-in functionality
|
|
5
|
+
* @template Mixed - The functionality to be mixed in
|
|
6
|
+
*/
|
|
7
|
+
export type MixinFunction<Mixed> = <Base>(
|
|
8
|
+
base: new (...args: any[]) => Base
|
|
9
|
+
) => new (
|
|
10
|
+
...args: any[]
|
|
11
|
+
) => Base & Mixed
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A mixin class that can be used both as a base class and as a mixin function
|
|
15
|
+
* @template Mixed - The functionality to be mixed in
|
|
16
|
+
*/
|
|
17
|
+
export type MixinClass<Mixed> = new (...args: any[]) => Mixed
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a mixin that can be used both as a class (extends) and as a function (mixin)
|
|
21
|
+
*
|
|
22
|
+
* This function supports:
|
|
23
|
+
* - Using mixins as base classes: `class MyClass extends MyMixin`
|
|
24
|
+
* - Using mixins as functions: `class MyClass extends MyMixin(SomeBase)`
|
|
25
|
+
* - Composing mixins: `const Composed = MixinA(MixinB)`
|
|
26
|
+
* - Type-safe property inference for all patterns
|
|
27
|
+
*
|
|
28
|
+
* @param mixinFunction - The function that creates the mixin
|
|
29
|
+
* @param unwrapFunction - Optional function to unwrap reactive objects for method calls
|
|
30
|
+
* @returns A mixin that can be used both as a class and as a function
|
|
31
|
+
*/
|
|
32
|
+
export function mixin<MixinFn extends (base: any) => new (...args: any[]) => any>(
|
|
33
|
+
mixinFunction: MixinFn,
|
|
34
|
+
unwrapFunction?: (obj: any) => any
|
|
35
|
+
): (new (
|
|
36
|
+
...args: any[]
|
|
37
|
+
) => InstanceType<ReturnType<MixinFn>>) &
|
|
38
|
+
(<Base>(
|
|
39
|
+
base: abstract new (...args: any[]) => Base
|
|
40
|
+
) => new (
|
|
41
|
+
...args: any[]
|
|
42
|
+
) => InstanceType<ReturnType<MixinFn>> & Base) {
|
|
43
|
+
/**
|
|
44
|
+
* Cache for mixin results to ensure the same base class always returns the same mixed class
|
|
45
|
+
*/
|
|
46
|
+
const mixinCache = new WeakMap<new (...args: any[]) => any, new (...args: any[]) => any>()
|
|
47
|
+
|
|
48
|
+
// Apply the mixin to Object as the base class
|
|
49
|
+
const MixedBase = mixinFunction(Object)
|
|
50
|
+
mixinCache.set(Object, MixedBase)
|
|
51
|
+
|
|
52
|
+
// Create the proxy that handles both constructor and function calls
|
|
53
|
+
return new Proxy(MixedBase, {
|
|
54
|
+
// Handle `MixinClass(SomeBase)` - use as mixin function
|
|
55
|
+
apply(_target, _thisArg, args) {
|
|
56
|
+
if (args.length === 0) {
|
|
57
|
+
throw new Error('Mixin requires a base class')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const baseClass = args[0]
|
|
61
|
+
if (typeof baseClass !== 'function') {
|
|
62
|
+
throw new Error('Mixin requires a constructor function')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check if it's a valid constructor or a mixin
|
|
66
|
+
if (
|
|
67
|
+
!isConstructor(baseClass) &&
|
|
68
|
+
!(baseClass && typeof baseClass === 'function' && baseClass.prototype)
|
|
69
|
+
) {
|
|
70
|
+
throw new Error('Mixin requires a valid constructor')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check cache first
|
|
74
|
+
const cached = mixinCache.get(baseClass)
|
|
75
|
+
if (cached) {
|
|
76
|
+
return cached
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let usedBase = baseClass
|
|
80
|
+
if (unwrapFunction) {
|
|
81
|
+
// Create a proxied base class that handles method unwrapping
|
|
82
|
+
const ProxiedBaseClass = class extends baseClass {}
|
|
83
|
+
|
|
84
|
+
// Proxy the prototype methods to handle unwrapping
|
|
85
|
+
const originalPrototype = baseClass.prototype
|
|
86
|
+
const proxiedPrototype = new Proxy(originalPrototype, {
|
|
87
|
+
get(target, prop, receiver) {
|
|
88
|
+
const value = ReflectGet(target, prop, receiver)
|
|
89
|
+
|
|
90
|
+
// Only wrap methods that are likely to access private fields
|
|
91
|
+
// Skip symbols and special properties that the reactive system needs
|
|
92
|
+
if (
|
|
93
|
+
typeof value === 'function' &&
|
|
94
|
+
typeof prop === 'string' &&
|
|
95
|
+
!['constructor', 'toString', 'valueOf'].includes(prop)
|
|
96
|
+
) {
|
|
97
|
+
// Return a wrapped version that uses unwrapped context
|
|
98
|
+
return function (this: any, ...args: any[]) {
|
|
99
|
+
// Use the unwrapping function if provided, otherwise use this
|
|
100
|
+
const context = unwrapFunction(this as any)
|
|
101
|
+
return value.apply(context, args)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return value
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Set the proxied prototype
|
|
110
|
+
Object.setPrototypeOf(ProxiedBaseClass.prototype, proxiedPrototype)
|
|
111
|
+
usedBase = ProxiedBaseClass
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Create the mixed class using the proxied base class
|
|
115
|
+
const mixedClass = mixinFunction(usedBase)
|
|
116
|
+
|
|
117
|
+
// Cache the result
|
|
118
|
+
mixinCache.set(baseClass, mixedClass)
|
|
119
|
+
|
|
120
|
+
return mixedClass
|
|
121
|
+
},
|
|
122
|
+
}) as MixinFn & (new (...args: any[]) => InstanceType<ReturnType<MixinFn>>)
|
|
123
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
type Resolved<T> =
|
|
2
|
+
T extends Promise<infer U>
|
|
3
|
+
? Resolved<U>
|
|
4
|
+
: T extends (...args: infer Args) => infer R
|
|
5
|
+
? (...args: Args) => Resolved<R>
|
|
6
|
+
: T extends object
|
|
7
|
+
? {
|
|
8
|
+
[k in keyof T]: k extends 'then' | 'catch' | 'finally' ? T[k] : Resolved<T[k]>
|
|
9
|
+
}
|
|
10
|
+
: T
|
|
11
|
+
type PromiseAnd<T> = Resolved<T> & Promise<Resolved<T>>
|
|
12
|
+
/**
|
|
13
|
+
* Type that transforms promises into chainable objects
|
|
14
|
+
* Allows calling methods directly on promise results without awaiting them first
|
|
15
|
+
*/
|
|
16
|
+
export type PromiseChain<T> = T extends (...args: infer Args) => infer R
|
|
17
|
+
? PromiseAnd<(...args: Args) => PromiseChain<Resolved<R>>>
|
|
18
|
+
: T extends object
|
|
19
|
+
? PromiseAnd<{
|
|
20
|
+
[k in keyof T]: k extends 'then' | 'catch' | 'finally' ? T[k] : PromiseChain<Resolved<T[k]>>
|
|
21
|
+
}>
|
|
22
|
+
: Promise<Resolved<T>>
|
|
23
|
+
|
|
24
|
+
const forward =
|
|
25
|
+
(name: string, target: any) =>
|
|
26
|
+
(...args: any[]) => {
|
|
27
|
+
return target[name](...args)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const alreadyChained = new WeakMap<any, PromiseChain<any>>()
|
|
31
|
+
const originals = new WeakMap<Promise<any>, any>()
|
|
32
|
+
|
|
33
|
+
function cache(target: any, rv: PromiseChain<any>) {
|
|
34
|
+
originals.set(rv, target)
|
|
35
|
+
alreadyChained.set(target, rv)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
type ChainedFunction<T> = ((...args: any[]) => PromiseChain<T>) & {
|
|
39
|
+
then: Promise<T>['then']
|
|
40
|
+
catch: Promise<T>['catch']
|
|
41
|
+
finally: Promise<T>['finally']
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const promiseProxyHandler: ProxyHandler<ChainedFunction<any>> = {
|
|
45
|
+
//@ts-expect-error
|
|
46
|
+
[Symbol.toStringTag]: 'MutTs PromiseChain function',
|
|
47
|
+
get(target, prop) {
|
|
48
|
+
if (prop === Symbol.toStringTag) return 'PromiseProxy'
|
|
49
|
+
if (typeof prop === 'string' && ['then', 'catch', 'finally'].includes(prop))
|
|
50
|
+
return target[prop as keyof typeof target]
|
|
51
|
+
return chainPromise(target.then((r) => r[prop as keyof typeof r]))
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
const promiseForward = (target: any) => ({
|
|
55
|
+
// biome-ignore lint/suspicious/noThenProperty: This one is the whole point
|
|
56
|
+
then: forward('then', target),
|
|
57
|
+
catch: forward('catch', target),
|
|
58
|
+
finally: forward('finally', target),
|
|
59
|
+
})
|
|
60
|
+
const objectProxyHandler: ProxyHandler<any> = {
|
|
61
|
+
//@ts-expect-error
|
|
62
|
+
[Symbol.toStringTag]: 'MutTs PromiseChain object',
|
|
63
|
+
get(target, prop, receiver) {
|
|
64
|
+
const getter = Object.getOwnPropertyDescriptor(target, prop)?.get
|
|
65
|
+
const rv = getter ? getter.call(receiver) : target[prop]
|
|
66
|
+
// Allows fct.call or fct.apply to bypass the chain system
|
|
67
|
+
if (typeof target === 'function') return rv
|
|
68
|
+
return chainPromise(rv)
|
|
69
|
+
},
|
|
70
|
+
apply(target, thisArg, args) {
|
|
71
|
+
return chainPromise(target.apply(thisArg, args))
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
function chainObject<T extends object | Function>(given: T): PromiseChain<T> {
|
|
75
|
+
const rv = new Proxy(given, objectProxyHandler) as PromiseChain<T>
|
|
76
|
+
cache(given, rv)
|
|
77
|
+
return rv
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function chainable(x: any): x is object | Function {
|
|
81
|
+
return x && ['function', 'object'].includes(typeof x)
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Transforms a promise or value into a chainable object
|
|
85
|
+
* Allows calling methods directly on promise results without awaiting them first
|
|
86
|
+
* @param given - The promise or value to make chainable
|
|
87
|
+
* @returns A chainable version of the input
|
|
88
|
+
*/
|
|
89
|
+
export function chainPromise<T>(given: Promise<T> | T): PromiseChain<T> {
|
|
90
|
+
if (!chainable(given)) return given as PromiseChain<T>
|
|
91
|
+
if (alreadyChained.has(given)) return alreadyChained.get(given) as PromiseChain<T>
|
|
92
|
+
if (!(given instanceof Promise)) return chainObject(given)
|
|
93
|
+
// @ts-expect-error It's ok as we check if it's an object above
|
|
94
|
+
given = given.then((r) => (chainable(r) ? chainObject(r) : r))
|
|
95
|
+
const target = Object.assign(function (this: any, ...args: any[]) {
|
|
96
|
+
return chainPromise(
|
|
97
|
+
given.then((r) => {
|
|
98
|
+
return this?.then
|
|
99
|
+
? this.then((t: any) => (r as any).apply(t, args))
|
|
100
|
+
: (r as any).apply(this, args)
|
|
101
|
+
})
|
|
102
|
+
)
|
|
103
|
+
}, promiseForward(given)) as ChainedFunction<T>
|
|
104
|
+
const chained = new Proxy(
|
|
105
|
+
target,
|
|
106
|
+
promiseProxyHandler as ProxyHandler<ChainedFunction<T>>
|
|
107
|
+
) as PromiseChain<T>
|
|
108
|
+
cache(given, chained as PromiseChain<any>)
|
|
109
|
+
return chained
|
|
110
|
+
}
|