mutts 1.0.0 → 1.0.2
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 +24 -2
- package/dist/chunks/_tslib-C-cuVLvZ.js +73 -0
- package/dist/chunks/_tslib-C-cuVLvZ.js.map +1 -0
- package/dist/chunks/_tslib-CMEnd0VE.esm.js +68 -0
- package/dist/chunks/_tslib-CMEnd0VE.esm.js.map +1 -0
- package/dist/chunks/{decorator-BXsign4Z.js → decorator-D4DU97Zg.js} +70 -4
- package/dist/chunks/decorator-D4DU97Zg.js.map +1 -0
- package/dist/chunks/{decorator-CPbZNnsX.esm.js → decorator-GnHw1Az7.esm.js} +67 -5
- package/dist/chunks/decorator-GnHw1Az7.esm.js.map +1 -0
- package/dist/chunks/index-DBScoeCX.esm.js +1960 -0
- package/dist/chunks/index-DBScoeCX.esm.js.map +1 -0
- package/dist/chunks/index-DOTmXL89.js +1983 -0
- package/dist/chunks/index-DOTmXL89.js.map +1 -0
- package/dist/decorator.d.ts +58 -1
- package/dist/decorator.esm.js +1 -1
- package/dist/decorator.js +1 -1
- package/dist/destroyable.d.ts +42 -0
- package/dist/destroyable.esm.js +19 -1
- package/dist/destroyable.esm.js.map +1 -1
- package/dist/destroyable.js +19 -1
- package/dist/destroyable.js.map +1 -1
- package/dist/eventful.d.ts +10 -1
- package/dist/eventful.esm.js +5 -27
- package/dist/eventful.esm.js.map +1 -1
- package/dist/eventful.js +15 -37
- package/dist/eventful.js.map +1 -1
- package/dist/index.d.ts +52 -3
- package/dist/index.esm.js +3 -2
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +18 -3
- package/dist/index.js.map +1 -1
- package/dist/indexable.d.ts +26 -0
- package/dist/indexable.esm.js +6 -0
- package/dist/indexable.esm.js.map +1 -1
- package/dist/indexable.js +6 -0
- 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.d.ts +10 -0
- package/dist/promiseChain.esm.js +6 -0
- package/dist/promiseChain.esm.js.map +1 -1
- package/dist/promiseChain.js +6 -0
- package/dist/promiseChain.js.map +1 -1
- package/dist/reactive.d.ts +258 -20
- package/dist/reactive.esm.js +4 -1454
- package/dist/reactive.esm.js.map +1 -1
- package/dist/reactive.js +29 -1466
- package/dist/reactive.js.map +1 -1
- package/dist/std-decorators.d.ts +35 -0
- package/dist/std-decorators.esm.js +36 -1
- package/dist/std-decorators.esm.js.map +1 -1
- package/dist/std-decorators.js +36 -1
- package/dist/std-decorators.js.map +1 -1
- package/docs/mixin.md +229 -0
- package/docs/reactive.md +7931 -458
- package/package.json +1 -2
- package/dist/chunks/decorator-BXsign4Z.js.map +0 -1
- package/dist/chunks/decorator-CPbZNnsX.esm.js.map +0 -1
- package/src/decorator.test.ts +0 -495
- package/src/decorator.ts +0 -205
- package/src/destroyable.test.ts +0 -155
- package/src/destroyable.ts +0 -158
- package/src/eventful.test.ts +0 -380
- package/src/eventful.ts +0 -69
- package/src/index.ts +0 -7
- package/src/indexable.test.ts +0 -388
- package/src/indexable.ts +0 -124
- package/src/promiseChain.test.ts +0 -201
- package/src/promiseChain.ts +0 -99
- package/src/reactive/array.test.ts +0 -923
- package/src/reactive/array.ts +0 -352
- package/src/reactive/core.test.ts +0 -1663
- package/src/reactive/core.ts +0 -866
- package/src/reactive/index.ts +0 -28
- package/src/reactive/interface.test.ts +0 -1477
- package/src/reactive/interface.ts +0 -231
- package/src/reactive/map.test.ts +0 -866
- package/src/reactive/map.ts +0 -162
- package/src/reactive/set.test.ts +0 -289
- package/src/reactive/set.ts +0 -142
- package/src/std-decorators.test.ts +0 -679
- package/src/std-decorators.ts +0 -182
- package/src/utils.ts +0 -52
package/src/decorator.ts
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
// biome-ignore-all lint/suspicious/noConfusingVoidType: We *love* voids
|
|
2
|
-
// Standardized decorator system that works with both Legacy and Modern decorators
|
|
3
|
-
|
|
4
|
-
import { isConstructor } from './utils'
|
|
5
|
-
|
|
6
|
-
export class DecoratorError extends Error {
|
|
7
|
-
constructor(message: string) {
|
|
8
|
-
super(message)
|
|
9
|
-
this.name = 'DecoratorException'
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
//#region all decorator types
|
|
13
|
-
|
|
14
|
-
// Used for get/set and method decorators
|
|
15
|
-
export type LegacyPropertyDecorator<T> = (
|
|
16
|
-
target: T,
|
|
17
|
-
name: string | symbol,
|
|
18
|
-
descriptor: PropertyDescriptor
|
|
19
|
-
) => any
|
|
20
|
-
|
|
21
|
-
export type LegacyClassDecorator<T> = (target: T) => any
|
|
22
|
-
|
|
23
|
-
export type ModernMethodDecorator<T> = (target: T, context: ClassMethodDecoratorContext) => any
|
|
24
|
-
|
|
25
|
-
export type ModernGetterDecorator<T> = (target: T, context: ClassGetterDecoratorContext) => any
|
|
26
|
-
|
|
27
|
-
export type ModernSetterDecorator<T> = (target: T, context: ClassSetterDecoratorContext) => any
|
|
28
|
-
|
|
29
|
-
export type ModernAccessorDecorator<T> = (target: T, context: ClassAccessorDecoratorContext) => any
|
|
30
|
-
|
|
31
|
-
export type ModernClassDecorator<T> = (target: T, context: ClassDecoratorContext) => any
|
|
32
|
-
|
|
33
|
-
//#endregion
|
|
34
|
-
|
|
35
|
-
type DDMethod<T> = (
|
|
36
|
-
original: (this: T, ...args: any[]) => any,
|
|
37
|
-
name: PropertyKey
|
|
38
|
-
) => ((this: T, ...args: any[]) => any) | void
|
|
39
|
-
|
|
40
|
-
type DDGetter<T> = (original: (this: T) => any, name: PropertyKey) => ((this: T) => any) | void
|
|
41
|
-
|
|
42
|
-
type DDSetter<T> = (
|
|
43
|
-
original: (this: T, value: any) => void,
|
|
44
|
-
name: PropertyKey
|
|
45
|
-
) => ((this: T, value: any) => void) | void
|
|
46
|
-
|
|
47
|
-
type DDClass<T> = <Ctor extends new (...args: any[]) => T = new (...args: any[]) => T>(
|
|
48
|
-
target: Ctor
|
|
49
|
-
) => Ctor | void
|
|
50
|
-
export interface DecoratorDescription<T> {
|
|
51
|
-
method?: DDMethod<T>
|
|
52
|
-
class?: DDClass<T>
|
|
53
|
-
getter?: DDGetter<T>
|
|
54
|
-
setter?: DDSetter<T>
|
|
55
|
-
default?: (...args: any[]) => any
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export type Decorator<T, Description extends DecoratorDescription<T>> = (Description extends {
|
|
59
|
-
method: DDMethod<T>
|
|
60
|
-
}
|
|
61
|
-
? LegacyPropertyDecorator<T> & ModernMethodDecorator<T>
|
|
62
|
-
: unknown) &
|
|
63
|
-
(Description extends { class: DDClass<new (...args: any[]) => T> }
|
|
64
|
-
? LegacyClassDecorator<new (...args: any[]) => T> &
|
|
65
|
-
ModernClassDecorator<new (...args: any[]) => T>
|
|
66
|
-
: unknown) &
|
|
67
|
-
(Description extends { getter: DDGetter<T> }
|
|
68
|
-
? LegacyPropertyDecorator<T> & ModernGetterDecorator<T> & ModernAccessorDecorator<T>
|
|
69
|
-
: unknown) &
|
|
70
|
-
(Description extends { setter: DDSetter<T> }
|
|
71
|
-
? LegacyPropertyDecorator<T> & ModernSetterDecorator<T> & ModernAccessorDecorator<T>
|
|
72
|
-
: unknown) &
|
|
73
|
-
(Description extends { default: infer Signature } ? Signature : unknown)
|
|
74
|
-
|
|
75
|
-
export type DecoratorFactory<T> = <Description extends DecoratorDescription<T>>(
|
|
76
|
-
description: Description
|
|
77
|
-
) => (Description extends { method: DDMethod<T> }
|
|
78
|
-
? LegacyPropertyDecorator<T> & ModernMethodDecorator<T>
|
|
79
|
-
: unknown) &
|
|
80
|
-
(Description extends { class: DDClass<new (...args: any[]) => T> }
|
|
81
|
-
? LegacyClassDecorator<new (...args: any[]) => T> &
|
|
82
|
-
ModernClassDecorator<new (...args: any[]) => T>
|
|
83
|
-
: unknown) &
|
|
84
|
-
(Description extends { getter: DDGetter<T> }
|
|
85
|
-
? LegacyPropertyDecorator<T> & ModernGetterDecorator<T> & ModernAccessorDecorator<T>
|
|
86
|
-
: unknown) &
|
|
87
|
-
(Description extends { setter: DDSetter<T> }
|
|
88
|
-
? LegacyPropertyDecorator<T> & ModernSetterDecorator<T> & ModernAccessorDecorator<T>
|
|
89
|
-
: unknown) &
|
|
90
|
-
(Description extends { default: infer Signature } ? Signature : unknown)
|
|
91
|
-
|
|
92
|
-
export function legacyDecorator<T = any>(description: DecoratorDescription<T>): any {
|
|
93
|
-
return function (
|
|
94
|
-
target: any,
|
|
95
|
-
propertyKey?: PropertyKey,
|
|
96
|
-
descriptor?: PropertyDescriptor,
|
|
97
|
-
...args: any[]
|
|
98
|
-
) {
|
|
99
|
-
if (propertyKey === undefined) {
|
|
100
|
-
if (isConstructor(target)) {
|
|
101
|
-
if (!('class' in description)) throw new Error('Decorator cannot be applied to a class')
|
|
102
|
-
return description.class?.(target)
|
|
103
|
-
}
|
|
104
|
-
} else if (typeof target === 'object' && ['string', 'symbol'].includes(typeof propertyKey)) {
|
|
105
|
-
if (!descriptor) throw new Error('Decorator cannot be applied to a field')
|
|
106
|
-
else if (typeof descriptor === 'object' && 'configurable' in descriptor) {
|
|
107
|
-
if ('get' in descriptor || 'set' in descriptor) {
|
|
108
|
-
if (!('getter' in description || 'setter' in description))
|
|
109
|
-
throw new Error('Decorator cannot be applied to a getter or setter')
|
|
110
|
-
if ('getter' in description) {
|
|
111
|
-
const newGetter = description.getter?.(descriptor.get, propertyKey)
|
|
112
|
-
if (newGetter) descriptor.get = newGetter
|
|
113
|
-
}
|
|
114
|
-
if ('setter' in description) {
|
|
115
|
-
const newSetter = description.setter?.(descriptor.set, propertyKey)
|
|
116
|
-
if (newSetter) descriptor.set = newSetter
|
|
117
|
-
}
|
|
118
|
-
return descriptor
|
|
119
|
-
} else if (typeof descriptor.value === 'function') {
|
|
120
|
-
if (!('method' in description)) throw new Error('Decorator cannot be applied to a method')
|
|
121
|
-
const newMethod = description.method?.(descriptor.value, propertyKey)
|
|
122
|
-
if (newMethod) descriptor.value = newMethod
|
|
123
|
-
return descriptor
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (!('default' in description))
|
|
128
|
-
throw new Error('Decorator do not have a default implementation')
|
|
129
|
-
return description.default.call(this, target, propertyKey, descriptor, ...args)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function modernDecorator<T = any>(description: DecoratorDescription<T>): any {
|
|
134
|
-
return function (target: any, context?: DecoratorContext, ...args: any[]) {
|
|
135
|
-
if (!context?.kind || typeof context.kind !== 'string') {
|
|
136
|
-
if (!('default' in description))
|
|
137
|
-
throw new Error('Decorator do not have a default implementation')
|
|
138
|
-
return description.default.call(this, target, context, ...args)
|
|
139
|
-
}
|
|
140
|
-
switch (context.kind) {
|
|
141
|
-
case 'class':
|
|
142
|
-
if (!('class' in description)) throw new Error('Decorator cannot be applied to a class')
|
|
143
|
-
return description.class?.(target)
|
|
144
|
-
case 'field':
|
|
145
|
-
throw new Error('Decorator cannot be applied to a field')
|
|
146
|
-
case 'getter':
|
|
147
|
-
if (!('getter' in description)) throw new Error('Decorator cannot be applied to a getter')
|
|
148
|
-
return description.getter?.(target, context.name)
|
|
149
|
-
case 'setter':
|
|
150
|
-
if (!('setter' in description)) throw new Error('Decorator cannot be applied to a setter')
|
|
151
|
-
return description.setter?.(target, context.name)
|
|
152
|
-
case 'method':
|
|
153
|
-
if (!('method' in description)) throw new Error('Decorator cannot be applied to a method')
|
|
154
|
-
return description.method?.(target, context.name)
|
|
155
|
-
case 'accessor': {
|
|
156
|
-
if (!('getter' in description || 'setter' in description))
|
|
157
|
-
throw new Error('Decorator cannot be applied to a getter or setter')
|
|
158
|
-
const rv: Partial<ClassAccessorDecoratorResult<any, any>> = {}
|
|
159
|
-
if ('getter' in description) {
|
|
160
|
-
const newGetter = description.getter?.(target.get, context.name)
|
|
161
|
-
if (newGetter) rv.get = newGetter
|
|
162
|
-
}
|
|
163
|
-
if ('setter' in description) {
|
|
164
|
-
const newSetter = description.setter?.(target.set, context.name)
|
|
165
|
-
if (newSetter) rv.set = newSetter
|
|
166
|
-
}
|
|
167
|
-
return rv
|
|
168
|
-
}
|
|
169
|
-
//return description.accessor?.(target, context.name, target)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Detects if the decorator is being called in modern (Modern) or legacy (Legacy) mode
|
|
176
|
-
* based on the arguments passed to the decorator function
|
|
177
|
-
*/
|
|
178
|
-
function detectDecoratorMode(
|
|
179
|
-
_target: any,
|
|
180
|
-
contextOrKey?: any,
|
|
181
|
-
_descriptor?: any
|
|
182
|
-
): 'modern' | 'legacy' {
|
|
183
|
-
// Modern decorators have a context object as the second parameter
|
|
184
|
-
// Legacy decorators have a string/symbol key as the second parameter
|
|
185
|
-
if (
|
|
186
|
-
typeof contextOrKey === 'object' &&
|
|
187
|
-
contextOrKey !== null &&
|
|
188
|
-
typeof contextOrKey.kind === 'string'
|
|
189
|
-
) {
|
|
190
|
-
return 'modern'
|
|
191
|
-
}
|
|
192
|
-
return 'legacy'
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export const decorator: DecoratorFactory<any> = (description: DecoratorDescription<any>) => {
|
|
196
|
-
return ((target: any, contextOrKey?: any, ...args: any[]) => {
|
|
197
|
-
const mode = detectDecoratorMode(target, contextOrKey, args[0])
|
|
198
|
-
return mode === 'modern'
|
|
199
|
-
? modernDecorator(description)(target, contextOrKey, ...args)
|
|
200
|
-
: legacyDecorator(description)(target, contextOrKey, ...args)
|
|
201
|
-
}) as any
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export type GenericClassDecorator<T> = LegacyClassDecorator<new (...args: any[]) => T> &
|
|
205
|
-
ModernClassDecorator<new (...args: any[]) => T>
|
package/src/destroyable.test.ts
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
allocated,
|
|
3
|
-
allocatedValues,
|
|
4
|
-
Destroyable,
|
|
5
|
-
DestructionError,
|
|
6
|
-
destructor,
|
|
7
|
-
} from './destroyable'
|
|
8
|
-
|
|
9
|
-
function tick(ms: number = 0) {
|
|
10
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const gc = global.gc
|
|
14
|
-
|
|
15
|
-
async function collectGarbages() {
|
|
16
|
-
await tick()
|
|
17
|
-
gc!()
|
|
18
|
-
await tick()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
describe('Destroyable', () => {
|
|
22
|
-
describe('with base class and destructor object', () => {
|
|
23
|
-
it('should create destroyable class with custom destructor', async () => {
|
|
24
|
-
let receivedAllocated: any = null
|
|
25
|
-
|
|
26
|
-
class MyClass extends Destroyable({
|
|
27
|
-
destructor(allocated) {
|
|
28
|
-
receivedAllocated = allocated
|
|
29
|
-
},
|
|
30
|
-
}) {
|
|
31
|
-
constructor(public name: string) {
|
|
32
|
-
super()
|
|
33
|
-
this[allocatedValues].name = name
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
;(() => {
|
|
38
|
-
const obj = new MyClass('test')
|
|
39
|
-
expect(obj.name).toBe('test')
|
|
40
|
-
})()
|
|
41
|
-
await collectGarbages()
|
|
42
|
-
expect(receivedAllocated.name).toBe('test')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('should pass constructor arguments to base class', () => {
|
|
46
|
-
class BaseClass {
|
|
47
|
-
constructor(public value: number) {}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const DestroyableBaseClass = Destroyable(BaseClass, {
|
|
51
|
-
destructor: () => {},
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
const obj = new DestroyableBaseClass(42)
|
|
55
|
-
expect(obj.value).toBe(42)
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('should throw error when accessing destroyed object', () => {
|
|
59
|
-
class MyClass {
|
|
60
|
-
constructor(public name: string) {}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const DestroyableMyClass = Destroyable(MyClass, {
|
|
64
|
-
destructor: () => {},
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
const obj = new DestroyableMyClass('test')
|
|
68
|
-
DestroyableMyClass.destroy(obj)
|
|
69
|
-
|
|
70
|
-
expect(() => obj.name).toThrow(DestructionError)
|
|
71
|
-
expect(() => {
|
|
72
|
-
obj.name = 'value'
|
|
73
|
-
}).toThrow(DestructionError)
|
|
74
|
-
})
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
describe('with destructor object only', () => {
|
|
78
|
-
it('should create destroyable class from scratch', () => {
|
|
79
|
-
let destructorCalled = false
|
|
80
|
-
|
|
81
|
-
const DestroyableClass = Destroyable({
|
|
82
|
-
destructor: () => {
|
|
83
|
-
destructorCalled = true
|
|
84
|
-
},
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const obj = new DestroyableClass()
|
|
88
|
-
expect(DestroyableClass.isDestroyable(obj)).toBe(true)
|
|
89
|
-
expect(destructorCalled).toBe(false)
|
|
90
|
-
|
|
91
|
-
const result = DestroyableClass.destroy(obj)
|
|
92
|
-
expect(result).toBe(true)
|
|
93
|
-
expect(destructorCalled).toBe(true)
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
describe('with base class only', () => {
|
|
98
|
-
it('should create destroyable class with default destructor', () => {
|
|
99
|
-
class MyClass {
|
|
100
|
-
constructor(public name: string) {}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const DestroyableMyClass = Destroyable(MyClass)
|
|
104
|
-
|
|
105
|
-
expect(() => new DestroyableMyClass('test')).toThrow(DestructionError)
|
|
106
|
-
})
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
describe('class with [destructor] method', () => {
|
|
110
|
-
it('should call [destructor] method with allocated values', async () => {
|
|
111
|
-
let receivedAllocated: any = null
|
|
112
|
-
|
|
113
|
-
class MyClass extends Destroyable() {
|
|
114
|
-
constructor(public name: string) {
|
|
115
|
-
super()
|
|
116
|
-
this[allocatedValues].name = name
|
|
117
|
-
}
|
|
118
|
-
[destructor](allocated: any) {
|
|
119
|
-
receivedAllocated = allocated
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
;(() => {
|
|
124
|
-
const obj = new MyClass('test')
|
|
125
|
-
expect(obj.name).toBe('test')
|
|
126
|
-
})()
|
|
127
|
-
await collectGarbages()
|
|
128
|
-
expect(receivedAllocated.name).toBe('test')
|
|
129
|
-
})
|
|
130
|
-
})
|
|
131
|
-
describe('decorators usage', () => {
|
|
132
|
-
it('should collect allocated from decorators', async () => {
|
|
133
|
-
let receivedAllocated: any = null
|
|
134
|
-
|
|
135
|
-
class MyClass extends Destroyable() {
|
|
136
|
-
@allocated
|
|
137
|
-
accessor name: string
|
|
138
|
-
constructor(name: string) {
|
|
139
|
-
super()
|
|
140
|
-
this.name = name
|
|
141
|
-
}
|
|
142
|
-
[destructor](allocated: any) {
|
|
143
|
-
receivedAllocated = allocated
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
;(() => {
|
|
148
|
-
const obj = new MyClass('test')
|
|
149
|
-
expect(obj.name).toBe('test')
|
|
150
|
-
})()
|
|
151
|
-
await collectGarbages()
|
|
152
|
-
expect(receivedAllocated.name).toBe('test')
|
|
153
|
-
})
|
|
154
|
-
})
|
|
155
|
-
})
|
package/src/destroyable.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { decorator } from './decorator'
|
|
2
|
-
|
|
3
|
-
// Integrated with `using` statement via Symbol.dispose
|
|
4
|
-
const fr = new FinalizationRegistry<() => void>((f) => f())
|
|
5
|
-
export const destructor = Symbol('destructor')
|
|
6
|
-
export const allocatedValues = Symbol('allocated')
|
|
7
|
-
export class DestructionError extends Error {
|
|
8
|
-
static throw<_T = void>(msg: string) {
|
|
9
|
-
return () => {
|
|
10
|
-
throw new DestructionError(msg)
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
constructor(msg: string) {
|
|
14
|
-
super(`Object is destroyed. ${msg}`)
|
|
15
|
-
this.name = 'DestroyedAccessError'
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
const destroyedHandler = {
|
|
19
|
-
[Symbol.toStringTag]: 'MutTs Destroyable',
|
|
20
|
-
get: DestructionError.throw('Cannot access destroyed object'),
|
|
21
|
-
set: DestructionError.throw('Cannot access destroyed object'),
|
|
22
|
-
} as const
|
|
23
|
-
|
|
24
|
-
abstract class AbstractDestroyable<Allocated> {
|
|
25
|
-
abstract [destructor](allocated: Allocated): void
|
|
26
|
-
[Symbol.dispose](): void {
|
|
27
|
-
this[destructor](this as unknown as Allocated)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface Destructor<Allocated> {
|
|
32
|
-
destructor(allocated: Allocated): void
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function Destroyable<
|
|
36
|
-
T extends new (
|
|
37
|
-
...args: any[]
|
|
38
|
-
) => any,
|
|
39
|
-
Allocated extends Partial<typeof this>,
|
|
40
|
-
>(
|
|
41
|
-
base: T,
|
|
42
|
-
destructorObj: Destructor<Allocated>
|
|
43
|
-
): (new (
|
|
44
|
-
...args: ConstructorParameters<T>
|
|
45
|
-
) => InstanceType<T> & { [allocatedValues]: Allocated }) & {
|
|
46
|
-
destroy(obj: InstanceType<T>): boolean
|
|
47
|
-
isDestroyable(obj: InstanceType<T>): boolean
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function Destroyable<Allocated extends Record<PropertyKey, any> = Record<PropertyKey, any>>(
|
|
51
|
-
destructorObj: Destructor<Allocated>
|
|
52
|
-
): (new () => { [allocatedValues]: Allocated }) & {
|
|
53
|
-
destroy(obj: any): boolean
|
|
54
|
-
isDestroyable(obj: any): boolean
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function Destroyable<
|
|
58
|
-
T extends new (
|
|
59
|
-
...args: any[]
|
|
60
|
-
) => any,
|
|
61
|
-
Allocated extends Record<PropertyKey, any> = Record<PropertyKey, any>,
|
|
62
|
-
>(
|
|
63
|
-
base: T
|
|
64
|
-
): (new (
|
|
65
|
-
...args: ConstructorParameters<T>
|
|
66
|
-
) => AbstractDestroyable<Allocated> & InstanceType<T> & { [allocatedValues]: Allocated }) & {
|
|
67
|
-
destroy(obj: InstanceType<T>): boolean
|
|
68
|
-
isDestroyable(obj: InstanceType<T>): boolean
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function Destroyable<
|
|
72
|
-
Allocated extends Record<PropertyKey, any> = Record<PropertyKey, any>,
|
|
73
|
-
>(): abstract new () => (AbstractDestroyable<Allocated> & {
|
|
74
|
-
[allocatedValues]: Allocated
|
|
75
|
-
}) & {
|
|
76
|
-
destroy(obj: any): boolean
|
|
77
|
-
isDestroyable(obj: any): boolean
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function Destroyable<
|
|
81
|
-
T extends new (
|
|
82
|
-
...args: any[]
|
|
83
|
-
) => any,
|
|
84
|
-
Allocated extends Record<PropertyKey, any> = Record<PropertyKey, any>,
|
|
85
|
-
>(base?: T | Destructor<Allocated>, destructorObj?: Destructor<Allocated>) {
|
|
86
|
-
if (base && typeof base !== 'function') {
|
|
87
|
-
destructorObj = base as Destructor<Allocated>
|
|
88
|
-
base = undefined
|
|
89
|
-
}
|
|
90
|
-
if (!base) {
|
|
91
|
-
base = class {} as T
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return class Destroyable extends (base as T) {
|
|
95
|
-
static readonly destructors = new WeakMap<any, () => void>()
|
|
96
|
-
static destroy(obj: Destroyable) {
|
|
97
|
-
const destructor = Destroyable.destructors.get(obj)
|
|
98
|
-
if (!destructor) return false
|
|
99
|
-
fr.unregister(obj)
|
|
100
|
-
Destroyable.destructors.delete(obj)
|
|
101
|
-
Object.setPrototypeOf(obj, new Proxy({}, destroyedHandler))
|
|
102
|
-
// Clear all own properties
|
|
103
|
-
for (const key of Object.getOwnPropertyNames(obj)) {
|
|
104
|
-
delete (obj as any)[key]
|
|
105
|
-
}
|
|
106
|
-
destructor()
|
|
107
|
-
return true
|
|
108
|
-
}
|
|
109
|
-
static isDestroyable(obj: Destroyable) {
|
|
110
|
-
return Destroyable.destructors.has(obj)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
declare [forwardProperties]: PropertyKey[]
|
|
114
|
-
readonly [allocatedValues]: Allocated
|
|
115
|
-
constructor(...args: any[]) {
|
|
116
|
-
super(...args)
|
|
117
|
-
const allocated = {} as Allocated
|
|
118
|
-
this[allocatedValues] = allocated
|
|
119
|
-
// @ts-expect-error `this` is an AbstractDestroyable
|
|
120
|
-
const myDestructor = destructorObj?.destructor ?? this[destructor]
|
|
121
|
-
if (!myDestructor) {
|
|
122
|
-
throw new DestructionError('Destructor is not defined')
|
|
123
|
-
}
|
|
124
|
-
function destruction() {
|
|
125
|
-
myDestructor(allocated)
|
|
126
|
-
}
|
|
127
|
-
Destroyable.destructors.set(this, destruction)
|
|
128
|
-
fr.register(this, destruction, this)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const forwardProperties = Symbol('forwardProperties')
|
|
134
|
-
export const allocated = decorator({
|
|
135
|
-
setter(original, propertyKey) {
|
|
136
|
-
return function (value) {
|
|
137
|
-
this[allocatedValues][propertyKey] = value
|
|
138
|
-
return original.call(this, value)
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
export function callOnGC(cb: () => void) {
|
|
144
|
-
let called = false
|
|
145
|
-
const forward = () => {
|
|
146
|
-
if (called) return
|
|
147
|
-
called = true
|
|
148
|
-
cb()
|
|
149
|
-
}
|
|
150
|
-
fr.register(forward, cb, cb)
|
|
151
|
-
return forward
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Context Manager Protocol for `with` statement integration
|
|
155
|
-
export interface ContextManager<T = any> {
|
|
156
|
-
[Symbol.dispose](): void
|
|
157
|
-
value?: T
|
|
158
|
-
}
|