mutts 1.0.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 +150 -0
- package/dist/chunks/decorator-BXsign4Z.js +176 -0
- package/dist/chunks/decorator-BXsign4Z.js.map +1 -0
- package/dist/chunks/decorator-CPbZNnsX.esm.js +168 -0
- package/dist/chunks/decorator-CPbZNnsX.esm.js.map +1 -0
- package/dist/decorator.d.ts +50 -0
- package/dist/decorator.esm.js +2 -0
- package/dist/decorator.esm.js.map +1 -0
- package/dist/decorator.js +11 -0
- package/dist/decorator.js.map +1 -0
- package/dist/destroyable.d.ts +48 -0
- package/dist/destroyable.esm.js +91 -0
- package/dist/destroyable.esm.js.map +1 -0
- package/dist/destroyable.js +98 -0
- package/dist/destroyable.js.map +1 -0
- package/dist/eventful.d.ts +11 -0
- package/dist/eventful.esm.js +88 -0
- package/dist/eventful.esm.js.map +1 -0
- package/dist/eventful.js +90 -0
- package/dist/eventful.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.esm.js +7 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/indexable.d.ts +31 -0
- package/dist/indexable.esm.js +85 -0
- package/dist/indexable.esm.js.map +1 -0
- package/dist/indexable.js +89 -0
- package/dist/indexable.js.map +1 -0
- package/dist/mutts.umd.js +2 -0
- package/dist/mutts.umd.js.map +1 -0
- package/dist/mutts.umd.min.js +2 -0
- package/dist/mutts.umd.min.js.map +1 -0
- package/dist/promiseChain.d.ts +11 -0
- package/dist/promiseChain.esm.js +72 -0
- package/dist/promiseChain.esm.js.map +1 -0
- package/dist/promiseChain.js +74 -0
- package/dist/promiseChain.js.map +1 -0
- package/dist/reactive.d.ts +114 -0
- package/dist/reactive.esm.js +1455 -0
- package/dist/reactive.esm.js.map +1 -0
- package/dist/reactive.js +1472 -0
- package/dist/reactive.js.map +1 -0
- package/dist/std-decorators.d.ts +17 -0
- package/dist/std-decorators.esm.js +161 -0
- package/dist/std-decorators.esm.js.map +1 -0
- package/dist/std-decorators.js +169 -0
- package/dist/std-decorators.js.map +1 -0
- package/docs/decorator.md +300 -0
- package/docs/destroyable.md +294 -0
- package/docs/events.md +225 -0
- package/docs/indexable.md +561 -0
- package/docs/promiseChain.md +218 -0
- package/docs/reactive.md +2072 -0
- package/docs/std-decorators.md +558 -0
- package/package.json +132 -0
- package/src/decorator.test.ts +495 -0
- package/src/decorator.ts +205 -0
- package/src/destroyable.test.ts +155 -0
- package/src/destroyable.ts +158 -0
- package/src/eventful.test.ts +380 -0
- package/src/eventful.ts +69 -0
- package/src/index.ts +7 -0
- package/src/indexable.test.ts +388 -0
- package/src/indexable.ts +124 -0
- package/src/promiseChain.test.ts +201 -0
- package/src/promiseChain.ts +99 -0
- package/src/reactive/array.test.ts +923 -0
- package/src/reactive/array.ts +352 -0
- package/src/reactive/core.test.ts +1663 -0
- package/src/reactive/core.ts +866 -0
- package/src/reactive/index.ts +28 -0
- package/src/reactive/interface.test.ts +1477 -0
- package/src/reactive/interface.ts +231 -0
- package/src/reactive/map.test.ts +866 -0
- package/src/reactive/map.ts +162 -0
- package/src/reactive/set.test.ts +289 -0
- package/src/reactive/set.ts +142 -0
- package/src/std-decorators.test.ts +679 -0
- package/src/std-decorators.ts +182 -0
- package/src/utils.ts +52 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Decorator System
|
|
2
|
+
|
|
3
|
+
A unified decorator system that works seamlessly across both Legacy (legacy) and Modern (standard) decorator environments in TypeScript.
|
|
4
|
+
|
|
5
|
+
Hopefully this library will soon become obsolete, but it is still a struggle when writing a library to choose which version of decorators to use. Here is a way for a library to provide decorators that can be used however the using application is configured.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
The `decorator` function provides a single API that automatically adapts to your environment's decorator system, ensuring consistent behavior whether you're using legacy decorators or the new standard decorators.
|
|
10
|
+
|
|
11
|
+
## Core Function
|
|
12
|
+
|
|
13
|
+
### `decorator(description: DecoratorDescription): Decorator`
|
|
14
|
+
|
|
15
|
+
Creates a decorator from a description object that defines how different decorator types should behave.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { decorator } from 'mutts'
|
|
19
|
+
|
|
20
|
+
const myDecorator = decorator({
|
|
21
|
+
class(target) {
|
|
22
|
+
// Handle class decoration
|
|
23
|
+
return target
|
|
24
|
+
},
|
|
25
|
+
method(original, name) {
|
|
26
|
+
// Handle method decoration
|
|
27
|
+
return function(...args) {
|
|
28
|
+
return original.apply(this, args)
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
getter(original, name) {
|
|
32
|
+
// Handle getter decoration
|
|
33
|
+
return function() {
|
|
34
|
+
return original.call(this)
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
setter(original, name) {
|
|
38
|
+
// Handle setter decoration
|
|
39
|
+
return function(value) {
|
|
40
|
+
return original.call(this, value)
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
default(...args) {
|
|
44
|
+
// Handle any other decorator type or fallback
|
|
45
|
+
console.log('Default handler called with:', args)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Decorator Types
|
|
51
|
+
|
|
52
|
+
### Class Decorators
|
|
53
|
+
|
|
54
|
+
Modify or extend classes:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const enhanced = decorator({
|
|
58
|
+
class(original) {
|
|
59
|
+
return class extends original {
|
|
60
|
+
static version = '1.0.0'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
@enhanced
|
|
66
|
+
class MyClass {
|
|
67
|
+
value = 'test'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log((MyClass as any).version) // '1.0.0'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Method Decorators
|
|
74
|
+
|
|
75
|
+
Wrap method calls:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
const logged = decorator({
|
|
79
|
+
method(original, name) {
|
|
80
|
+
return function(...args) {
|
|
81
|
+
console.log(`Calling ${String(name)} with:`, args)
|
|
82
|
+
return original.apply(this, args)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
class Calculator {
|
|
88
|
+
@logged
|
|
89
|
+
add(a: number, b: number) {
|
|
90
|
+
return a + b
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const calc = new Calculator()
|
|
95
|
+
calc.add(2, 3) // Logs: "Calling add with: [2, 3]"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Getter/Setter Decorators
|
|
99
|
+
|
|
100
|
+
Wrap accessor calls:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const tracked = decorator({
|
|
104
|
+
getter(original, name) {
|
|
105
|
+
return function() {
|
|
106
|
+
console.log(`Getting ${String(name)}`)
|
|
107
|
+
return original.call(this)
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
setter(original, name) {
|
|
111
|
+
return function(value) {
|
|
112
|
+
console.log(`Setting ${String(name)} to:`, value)
|
|
113
|
+
return original.call(this, value)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
class Person {
|
|
119
|
+
private _name = ''
|
|
120
|
+
|
|
121
|
+
@tracked
|
|
122
|
+
get name() {
|
|
123
|
+
return this._name
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@tracked
|
|
127
|
+
set name(value: string) {
|
|
128
|
+
this._name = value
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const person = new Person()
|
|
133
|
+
person.name = 'John' // Logs: "Setting name to: John"
|
|
134
|
+
console.log(person.name) // Logs: "Getting name" then "John"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Combined Decorators
|
|
138
|
+
|
|
139
|
+
You can combine multiple decorator types in a single decorator:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const fullDecorator = decorator({
|
|
143
|
+
class(target) {
|
|
144
|
+
return class extends target {
|
|
145
|
+
static decorated = true
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
method(original, name) {
|
|
149
|
+
return function(...args) {
|
|
150
|
+
return `method: ${original.apply(this, args)}`
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
getter(original, name) {
|
|
154
|
+
return function() {
|
|
155
|
+
return `get: ${original.call(this)}`
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
@fullDecorator
|
|
161
|
+
class TestClass {
|
|
162
|
+
@fullDecorator
|
|
163
|
+
greet(name: string) {
|
|
164
|
+
return `Hello ${name}`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
@fullDecorator
|
|
168
|
+
get data() {
|
|
169
|
+
return 'test'
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const instance = new TestClass()
|
|
174
|
+
console.log(instance.greet('World')) // "method: Hello World"
|
|
175
|
+
console.log(instance.data) // "get: test"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Default Handler
|
|
179
|
+
|
|
180
|
+
The `default` handler is called for any decorator type that doesn't have a specific handler defined, or as a fallback:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const myDecorator = decorator({
|
|
184
|
+
class(target) {
|
|
185
|
+
return target
|
|
186
|
+
},
|
|
187
|
+
default(...args) {
|
|
188
|
+
console.log('Default handler called with:', args)
|
|
189
|
+
// Handle any decorator type not explicitly defined
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
When defining that handler, there is no type verification for a type conflict with other decorators signature, even if the types are strictly checked at runtime, the handler will never receive `(object, string)` as an argument in a legacy environment for example (field decorator)
|
|
195
|
+
|
|
196
|
+
## Fields
|
|
197
|
+
|
|
198
|
+
Weirdly enough, there is no standard ways to decorate standard instance value fields. Legacy and Modern have no intersection of data beside the name of the decorated field - and they both lack something important:
|
|
199
|
+
- Legacy decorators give access to the prototype but not to the instances
|
|
200
|
+
- Modern does the opposite: gives access to the instances (through `addInitializer`) but not to the class.
|
|
201
|
+
|
|
202
|
+
The best ways to go through with fields are :
|
|
203
|
+
|
|
204
|
+
### Auto-accessors
|
|
205
|
+
|
|
206
|
+
JS/TS Have a so called auto-accessors available with the `accessor` keyword
|
|
207
|
+
```ts
|
|
208
|
+
class MClass {
|
|
209
|
+
accessor myValue = 5
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
This will create a get/set pair automatically accessing an internal value. When this is decorated, the setter and the getter will be decorated separately.
|
|
214
|
+
|
|
215
|
+
### Class decorator
|
|
216
|
+
|
|
217
|
+
Giving the properties, as a list or an object can solve all this, with the help of the helper type `GenericClassDecorator`.
|
|
218
|
+
```ts
|
|
219
|
+
const myDecorator = decorator({
|
|
220
|
+
getter(key, original) {
|
|
221
|
+
//...
|
|
222
|
+
},
|
|
223
|
+
setter(key, original) {
|
|
224
|
+
//...
|
|
225
|
+
},
|
|
226
|
+
default<T>(...args: (keyof T)[]): GenericClassDecorator<T> {
|
|
227
|
+
//default<T>(_description: { [k in keyof T]?: string }): GenericClassDecorator<T> {
|
|
228
|
+
return (classOriginal) => {
|
|
229
|
+
//...
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
This example would allow the following code *with type checking*
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
@myDecorator('field1', 'method')
|
|
239
|
+
//@myDecorator({ field1: 'always', method: 'no' })
|
|
240
|
+
class Test {
|
|
241
|
+
field1 = 'value1'
|
|
242
|
+
field2 = 42
|
|
243
|
+
@myDecorator
|
|
244
|
+
accessor field3
|
|
245
|
+
method() { /* ... */ }
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Environment Detection
|
|
250
|
+
|
|
251
|
+
The system automatically detects whether you're using Legacy or Modern decorators:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { detectDecoratorSupport } from 'mutts'
|
|
255
|
+
|
|
256
|
+
const support = detectDecoratorSupport()
|
|
257
|
+
console.log(support) // 'modern' | 'legacy' | false
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Type Safety
|
|
261
|
+
|
|
262
|
+
The decorator function provides full TypeScript support with proper type inference:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// TypeScript will validate property names
|
|
266
|
+
function createDecorator<T extends new (...args: any[]) => any>(
|
|
267
|
+
...properties: (keyof InstanceType<T>)[]
|
|
268
|
+
) {
|
|
269
|
+
return decorator({
|
|
270
|
+
class(original) {
|
|
271
|
+
console.log('Decorating class with properties:', properties)
|
|
272
|
+
return original
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
@createDecorator('method', 'value') // TypeScript validates these exist on the class
|
|
278
|
+
class MyClass {
|
|
279
|
+
method() {}
|
|
280
|
+
value = 'test'
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Key Features
|
|
285
|
+
|
|
286
|
+
- **Environment Agnostic**: Works with both Legacy and Modern decorators
|
|
287
|
+
- **Type Safe**: Full TypeScript support with proper inference
|
|
288
|
+
- **Unified API**: Single API surface regardless of environment
|
|
289
|
+
- **Consistent Behavior**: Same results across all environments
|
|
290
|
+
- **Future Proof**: Will work when Modern becomes standard
|
|
291
|
+
|
|
292
|
+
## Implementation Details
|
|
293
|
+
|
|
294
|
+
The decorator system uses different underlying implementations based on your environment:
|
|
295
|
+
|
|
296
|
+
- **Legacy**: Uses `experimentalDecorators` with `PropertyDescriptor` manipulation
|
|
297
|
+
- **Modern**: Uses the new decorator standard with `DecoratorContext` objects
|
|
298
|
+
|
|
299
|
+
The `decorator` function automatically routes to the appropriate implementation and normalizes the differences between the two systems, providing a consistent API regardless of which environment you're using.
|
|
300
|
+
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# Destroyable
|
|
2
|
+
|
|
3
|
+
A comprehensive resource management system that provides automatic cleanup for objects with proper destructor handling. The destroyable system integrates with JavaScript's `FinalizationRegistry` for garbage collection-based cleanup and supports modern resource management patterns including the upcoming `using` statement.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Automatic Resource Management**: Objects are automatically cleaned up when garbage collected
|
|
8
|
+
- **Manual Destruction**: Explicit destruction with immediate cleanup
|
|
9
|
+
- **Resource Tracking**: Properties can be marked with `@allocated` to be tracked in a separate allocation object
|
|
10
|
+
- **Context Manager Integration**: Support for `Symbol.dispose` and context manager patterns
|
|
11
|
+
- **Type Safety**: Full TypeScript support with proper type inference
|
|
12
|
+
- **Destruction Safety**: Destroyed objects throw errors when accessed to prevent use-after-free bugs
|
|
13
|
+
|
|
14
|
+
## Basic Usage
|
|
15
|
+
|
|
16
|
+
### Creating Destroyable Classes
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { Destroyable, allocated, destructor } from 'mutts/destroyable'
|
|
20
|
+
|
|
21
|
+
// Method 1: With destructor object
|
|
22
|
+
class DatabaseConnection {
|
|
23
|
+
constructor(public host: string, public port: number) {
|
|
24
|
+
console.log(`Connecting to ${host}:${port}`)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DestroyableDB = Destroyable(DatabaseConnection, {
|
|
29
|
+
destructor(allocated) {
|
|
30
|
+
console.log('Database connection destroyed')
|
|
31
|
+
// Cleanup logic here
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
// Method 2: With destructor method
|
|
36
|
+
class FileHandler extends Destroyable() {
|
|
37
|
+
@allocated
|
|
38
|
+
accessor filePath: string
|
|
39
|
+
|
|
40
|
+
constructor(path: string) {
|
|
41
|
+
super()
|
|
42
|
+
this.filePath = path
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[destructor](allocated) {
|
|
46
|
+
console.log(`Closing file: ${allocated.filePath}`)
|
|
47
|
+
// Cleanup logic here
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Using Destroyable Objects
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
// Create and use
|
|
56
|
+
const db = new DestroyableDB('localhost', 5432)
|
|
57
|
+
const result = db.query('SELECT * FROM users')
|
|
58
|
+
|
|
59
|
+
// Manual destruction
|
|
60
|
+
DestroyableDB.destroy(db) // Returns true if destroyed successfully
|
|
61
|
+
|
|
62
|
+
// Check if object is destroyable
|
|
63
|
+
if (DestroyableDB.isDestroyable(db)) {
|
|
64
|
+
// Object can be destroyed
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Resource Tracking with @allocated
|
|
69
|
+
|
|
70
|
+
The `@allocated` decorator automatically stores property values in a separate allocation object that gets passed to the destructor:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
class ResourceManager extends Destroyable() {
|
|
74
|
+
// Modern decorators: use auto-accessors or explicit accessors
|
|
75
|
+
@allocated accessor connection: DatabaseConnection
|
|
76
|
+
@allocated accessor cache: Map<string, any>
|
|
77
|
+
|
|
78
|
+
constructor() {
|
|
79
|
+
super()
|
|
80
|
+
this.connection = new DatabaseConnection('localhost', 5432)
|
|
81
|
+
this.cache = new Map()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
[destructor](allocated) {
|
|
85
|
+
// allocated.connection and allocated.cache are available
|
|
86
|
+
allocated.connection.close()
|
|
87
|
+
allocated.cache.clear()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Important notes about `@allocated` support:
|
|
93
|
+
- Legacy decorators: `@allocated` decorates both getter and setter paths on accessors. Plain data fields are not supported (legacy field decorators do not provide instance access needed here).
|
|
94
|
+
- Modern decorators: `@allocated` is applied to the accessor and only modifies the setter side of the accessor. Plain data fields are not supported. Prefer `accessor` properties or explicit get/set pairs.
|
|
95
|
+
|
|
96
|
+
## Future using Statement Support
|
|
97
|
+
|
|
98
|
+
When JavaScript's `using` statement becomes available, destroyable objects will work seamlessly:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// This will work when `using` statement is available
|
|
102
|
+
using file = disposable(new FileHandler('example.txt'))
|
|
103
|
+
const content = file.read()
|
|
104
|
+
// Automatic cleanup at end of block
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## API Reference
|
|
108
|
+
|
|
109
|
+
### Destroyable()
|
|
110
|
+
|
|
111
|
+
Creates a destroyable class with automatic resource management.
|
|
112
|
+
|
|
113
|
+
**Signatures:**
|
|
114
|
+
```typescript
|
|
115
|
+
// With base class and destructor object
|
|
116
|
+
Destroyable<T, Allocated>(base: T, destructorObj: Destructor<Allocated>)
|
|
117
|
+
|
|
118
|
+
// With destructor object only
|
|
119
|
+
Destroyable<Allocated>(destructorObj: Destructor<Allocated>)
|
|
120
|
+
|
|
121
|
+
// With base class only (requires [destructor] method)
|
|
122
|
+
Destroyable<T, Allocated>(base: T)
|
|
123
|
+
|
|
124
|
+
// Abstract base class
|
|
125
|
+
Destroyable<Allocated>()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### @allocated
|
|
129
|
+
|
|
130
|
+
Decorator that marks properties to be stored in the allocated object and passed to the destructor.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
@allocated
|
|
134
|
+
accessor propertyName: Type
|
|
135
|
+
// or with explicit accessor pair
|
|
136
|
+
@allocated
|
|
137
|
+
set propertyName(value: Type) { /* store */ }
|
|
138
|
+
get propertyName(): Type { /* read */ }
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### callOnGC()
|
|
142
|
+
|
|
143
|
+
Registers a callback to be called when an object is garbage collected. Returns the object whose reference can be collected.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
callOnGC(cb: () => void): () => void
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Types
|
|
150
|
+
|
|
151
|
+
This module exposes the `ContextManager` interface and `DestructionError` class. Additional type utilities referenced in earlier drafts (e.g., `AllocatedProperties`, `AllocatedKeys`) are not part of the current API.
|
|
152
|
+
|
|
153
|
+
## Error Handling
|
|
154
|
+
|
|
155
|
+
### DestructionError
|
|
156
|
+
|
|
157
|
+
Thrown when attempting to access a destroyed object:
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { DestructionError } from 'mutts/destroyable'
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
destroyedObject.someProperty
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error instanceof DestructionError) {
|
|
166
|
+
console.log('Object has been destroyed')
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Type-Safe Allocated Properties
|
|
172
|
+
|
|
173
|
+
### Manual Type Mapping
|
|
174
|
+
|
|
175
|
+
Define explicit interfaces for your allocated properties:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
interface DatabaseAllocated {
|
|
179
|
+
connection: DatabaseConnection
|
|
180
|
+
pool: ConnectionPool[]
|
|
181
|
+
cache: Map<string, any>
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
class DatabaseManager extends Destroyable() {
|
|
185
|
+
@allocated
|
|
186
|
+
accessor connection: DatabaseConnection
|
|
187
|
+
|
|
188
|
+
@allocated
|
|
189
|
+
accessor pool: ConnectionPool[]
|
|
190
|
+
|
|
191
|
+
@allocated
|
|
192
|
+
accessor cache: Map<string, any>
|
|
193
|
+
|
|
194
|
+
constructor() {
|
|
195
|
+
super()
|
|
196
|
+
this.connection = new DatabaseConnection()
|
|
197
|
+
this.pool = []
|
|
198
|
+
this.cache = new Map()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
[destructor](allocated: DatabaseAllocated) {
|
|
202
|
+
// Type-safe access with full IntelliSense
|
|
203
|
+
allocated.connection.close()
|
|
204
|
+
allocated.pool.forEach(pool => pool.destroy())
|
|
205
|
+
allocated.cache.clear()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Type-safe method to get allocated values
|
|
209
|
+
getAllocatedValues(): DatabaseAllocated {
|
|
210
|
+
return this[allocatedValues] as DatabaseAllocated
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Advanced Patterns
|
|
216
|
+
|
|
217
|
+
### Resource Pools
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
class ConnectionPool extends Destroyable() {
|
|
221
|
+
@allocated
|
|
222
|
+
accessor connections: DatabaseConnection[]
|
|
223
|
+
|
|
224
|
+
constructor(size: number) {
|
|
225
|
+
super()
|
|
226
|
+
this.connections = Array.from({ length: size }, () =>
|
|
227
|
+
new DatabaseConnection('localhost', 5432)
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
[destructor](allocated) {
|
|
232
|
+
// Close all connections
|
|
233
|
+
allocated.connections.forEach(conn => conn.close())
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
getConnection(): DatabaseConnection {
|
|
237
|
+
return this.connections[Math.floor(Math.random() * this.connections.length)]
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Lazy Resource Loading
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
class LazyResource extends Destroyable() {
|
|
246
|
+
@allocated
|
|
247
|
+
accessor _resource: any
|
|
248
|
+
|
|
249
|
+
get resource() {
|
|
250
|
+
if (!this._resource) {
|
|
251
|
+
this._resource = this.loadResource()
|
|
252
|
+
}
|
|
253
|
+
return this._resource
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private loadResource() {
|
|
257
|
+
// Expensive resource loading
|
|
258
|
+
return new ExpensiveResource()
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
[destructor](allocated) {
|
|
262
|
+
if (allocated._resource) {
|
|
263
|
+
allocated._resource.cleanup()
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Use Cases
|
|
270
|
+
|
|
271
|
+
- **Database Connections**: Automatic connection cleanup
|
|
272
|
+
- **File Handles**: Ensure files are properly closed
|
|
273
|
+
- **Network Resources**: Cleanup network connections and streams
|
|
274
|
+
- **Memory Management**: Automatic cleanup of large objects
|
|
275
|
+
- **Plugin Systems**: Cleanup when plugins are unloaded
|
|
276
|
+
- **Temporary Resources**: Automatic cleanup of temporary files or data
|
|
277
|
+
- **Event Listeners**: Remove event listeners when objects are destroyed
|
|
278
|
+
- **Timers and Intervals**: Clear timers and intervals automatically
|
|
279
|
+
|
|
280
|
+
## Performance Considerations
|
|
281
|
+
|
|
282
|
+
- **FinalizationRegistry**: Uses native JavaScript FinalizationRegistry for efficient cleanup
|
|
283
|
+
- **WeakMap Storage**: Internal storage uses WeakMaps to avoid memory leaks
|
|
284
|
+
- **Lazy Cleanup**: Resources are only cleaned up when objects are garbage collected or explicitly destroyed
|
|
285
|
+
- **Proxy Overhead**: Destroyed objects use Proxy for access protection (minimal overhead)
|
|
286
|
+
|
|
287
|
+
## Best Practices
|
|
288
|
+
|
|
289
|
+
1. **Always provide destructors**: Either via destructor object or `[destructor]` method
|
|
290
|
+
2. **Use @allocated for resources**: Mark properties that need cleanup with `@allocated`
|
|
291
|
+
3. **Prefer explicit destruction**: Use `destroy()` for immediate cleanup when possible
|
|
292
|
+
4. **Handle destruction errors**: Check for `DestructionError` when accessing objects
|
|
293
|
+
5. Consider using the upcoming `using` statement when available; otherwise prefer explicit `destroy()`
|
|
294
|
+
6. **Test cleanup logic**: Ensure destructors are called and resources are properly cleaned up
|