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.
Files changed (82) hide show
  1. package/README.md +150 -0
  2. package/dist/chunks/decorator-BXsign4Z.js +176 -0
  3. package/dist/chunks/decorator-BXsign4Z.js.map +1 -0
  4. package/dist/chunks/decorator-CPbZNnsX.esm.js +168 -0
  5. package/dist/chunks/decorator-CPbZNnsX.esm.js.map +1 -0
  6. package/dist/decorator.d.ts +50 -0
  7. package/dist/decorator.esm.js +2 -0
  8. package/dist/decorator.esm.js.map +1 -0
  9. package/dist/decorator.js +11 -0
  10. package/dist/decorator.js.map +1 -0
  11. package/dist/destroyable.d.ts +48 -0
  12. package/dist/destroyable.esm.js +91 -0
  13. package/dist/destroyable.esm.js.map +1 -0
  14. package/dist/destroyable.js +98 -0
  15. package/dist/destroyable.js.map +1 -0
  16. package/dist/eventful.d.ts +11 -0
  17. package/dist/eventful.esm.js +88 -0
  18. package/dist/eventful.esm.js.map +1 -0
  19. package/dist/eventful.js +90 -0
  20. package/dist/eventful.js.map +1 -0
  21. package/dist/index.d.ts +15 -0
  22. package/dist/index.esm.js +7 -0
  23. package/dist/index.esm.js.map +1 -0
  24. package/dist/index.js +52 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/indexable.d.ts +31 -0
  27. package/dist/indexable.esm.js +85 -0
  28. package/dist/indexable.esm.js.map +1 -0
  29. package/dist/indexable.js +89 -0
  30. package/dist/indexable.js.map +1 -0
  31. package/dist/mutts.umd.js +2 -0
  32. package/dist/mutts.umd.js.map +1 -0
  33. package/dist/mutts.umd.min.js +2 -0
  34. package/dist/mutts.umd.min.js.map +1 -0
  35. package/dist/promiseChain.d.ts +11 -0
  36. package/dist/promiseChain.esm.js +72 -0
  37. package/dist/promiseChain.esm.js.map +1 -0
  38. package/dist/promiseChain.js +74 -0
  39. package/dist/promiseChain.js.map +1 -0
  40. package/dist/reactive.d.ts +114 -0
  41. package/dist/reactive.esm.js +1455 -0
  42. package/dist/reactive.esm.js.map +1 -0
  43. package/dist/reactive.js +1472 -0
  44. package/dist/reactive.js.map +1 -0
  45. package/dist/std-decorators.d.ts +17 -0
  46. package/dist/std-decorators.esm.js +161 -0
  47. package/dist/std-decorators.esm.js.map +1 -0
  48. package/dist/std-decorators.js +169 -0
  49. package/dist/std-decorators.js.map +1 -0
  50. package/docs/decorator.md +300 -0
  51. package/docs/destroyable.md +294 -0
  52. package/docs/events.md +225 -0
  53. package/docs/indexable.md +561 -0
  54. package/docs/promiseChain.md +218 -0
  55. package/docs/reactive.md +2072 -0
  56. package/docs/std-decorators.md +558 -0
  57. package/package.json +132 -0
  58. package/src/decorator.test.ts +495 -0
  59. package/src/decorator.ts +205 -0
  60. package/src/destroyable.test.ts +155 -0
  61. package/src/destroyable.ts +158 -0
  62. package/src/eventful.test.ts +380 -0
  63. package/src/eventful.ts +69 -0
  64. package/src/index.ts +7 -0
  65. package/src/indexable.test.ts +388 -0
  66. package/src/indexable.ts +124 -0
  67. package/src/promiseChain.test.ts +201 -0
  68. package/src/promiseChain.ts +99 -0
  69. package/src/reactive/array.test.ts +923 -0
  70. package/src/reactive/array.ts +352 -0
  71. package/src/reactive/core.test.ts +1663 -0
  72. package/src/reactive/core.ts +866 -0
  73. package/src/reactive/index.ts +28 -0
  74. package/src/reactive/interface.test.ts +1477 -0
  75. package/src/reactive/interface.ts +231 -0
  76. package/src/reactive/map.test.ts +866 -0
  77. package/src/reactive/map.ts +162 -0
  78. package/src/reactive/set.test.ts +289 -0
  79. package/src/reactive/set.ts +142 -0
  80. package/src/std-decorators.test.ts +679 -0
  81. package/src/std-decorators.ts +182 -0
  82. 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