mutts 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/browser.cjs +1030 -4406
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +310 -573
- package/dist/browser.esm.js +18 -24
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{async-browser-CA0jPWIi.cjs → async-browser-lvzLOCgk.cjs} +17 -24
- package/dist/chunks/async-browser-lvzLOCgk.cjs.map +1 -0
- package/dist/chunks/{async-core-UqHzvJ-S.cjs → async-core-CRLKP3l-.cjs} +8 -4
- package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
- package/dist/chunks/{async-node-BYHuGTni.cjs → async-node-C3DeIb0y.cjs} +5 -12
- package/dist/chunks/async-node-C3DeIb0y.cjs.map +1 -0
- package/dist/chunks/index-2vea86wD.esm.js +3011 -0
- package/dist/chunks/index-2vea86wD.esm.js.map +1 -0
- package/dist/chunks/index-CtA2AWl3.esm.js +4376 -0
- package/dist/chunks/index-CtA2AWl3.esm.js.map +1 -0
- package/dist/chunks/index-VTO-b2vR.cjs +4461 -0
- package/dist/chunks/index-VTO-b2vR.cjs.map +1 -0
- package/dist/{reactive → debug}/debug.d.ts +19 -8
- package/dist/debug/debug.d.ts.map +1 -0
- package/dist/debug/index.d.ts +4 -0
- package/dist/debug/index.d.ts.map +1 -0
- package/dist/debug/lineage-panel.d.ts +5 -0
- package/dist/debug/lineage-panel.d.ts.map +1 -0
- package/dist/debug/lineage.d.ts +79 -0
- package/dist/debug/lineage.d.ts.map +1 -0
- package/dist/debug.cjs +35 -0
- package/dist/debug.cjs.map +1 -0
- package/dist/debug.d.ts +209 -0
- package/dist/debug.esm.js +2 -0
- package/dist/debug.esm.js.map +1 -0
- package/dist/index.d.ts +1 -13
- package/dist/mutts.umd.js +1 -1
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +65 -63
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -1
- package/dist/node.esm.js +5 -11
- package/dist/node.esm.js.map +1 -1
- package/dist/{async → src/async}/browser.d.ts.map +1 -1
- package/dist/{async → src/async}/index.d.ts +2 -1
- package/dist/src/async/index.d.ts.map +1 -0
- package/dist/{async → src/async}/node.d.ts.map +1 -1
- package/dist/src/decorator.d.ts.map +1 -0
- package/dist/src/destroyable.d.ts.map +1 -0
- package/dist/src/entry-browser.d.ts.map +1 -0
- package/dist/src/entry-node.d.ts.map +1 -0
- package/dist/src/eventful.d.ts +20 -0
- package/dist/src/eventful.d.ts.map +1 -0
- package/dist/src/flavored.d.ts +33 -0
- package/dist/src/flavored.d.ts.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/indexable.d.ts.map +1 -0
- package/dist/{introspection.d.ts → src/introspection.d.ts} +1 -1
- package/dist/src/introspection.d.ts.map +1 -0
- package/dist/src/iterableWeak.d.ts.map +1 -0
- package/dist/src/mixins.d.ts.map +1 -0
- package/dist/src/promiseChain.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/array.d.ts +9 -10
- package/dist/src/reactive/array.d.ts.map +1 -0
- package/dist/src/reactive/buffer.d.ts +120 -0
- package/dist/src/reactive/buffer.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/change.d.ts +2 -2
- package/dist/src/reactive/change.d.ts.map +1 -0
- package/dist/src/reactive/deep-touch.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/deep-watch-state.d.ts +3 -3
- package/dist/src/reactive/deep-watch-state.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/deep-watch.d.ts +2 -1
- package/dist/src/reactive/deep-watch.d.ts.map +1 -0
- package/dist/src/reactive/describe.d.ts +12 -0
- package/dist/src/reactive/describe.d.ts.map +1 -0
- package/dist/src/reactive/effect-context.d.ts +34 -0
- package/dist/src/reactive/effect-context.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/effects.d.ts +31 -18
- package/dist/src/reactive/effects.d.ts.map +1 -0
- package/dist/src/reactive/index.d.ts +19 -0
- package/dist/src/reactive/index.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/map.d.ts +0 -2
- package/dist/src/reactive/map.d.ts.map +1 -0
- package/dist/src/reactive/memoize.d.ts +28 -0
- package/dist/src/reactive/memoize.d.ts.map +1 -0
- package/dist/src/reactive/non-reactive-state.d.ts.map +1 -0
- package/dist/src/reactive/non-reactive.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/project.d.ts +1 -2
- package/dist/src/reactive/project.d.ts.map +1 -0
- package/dist/src/reactive/proxy-state.d.ts.map +1 -0
- package/dist/src/reactive/proxy.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/record.d.ts +2 -3
- package/dist/src/reactive/record.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/register.d.ts +61 -0
- package/dist/src/reactive/register.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/registry.d.ts +6 -5
- package/dist/src/reactive/registry.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/set.d.ts +0 -2
- package/dist/src/reactive/set.d.ts.map +1 -0
- package/dist/src/reactive/tracking.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/types.d.ts +69 -21
- package/dist/src/reactive/types.d.ts.map +1 -0
- package/dist/{reactive/interface.d.ts → src/reactive/watch.d.ts} +7 -23
- package/dist/src/reactive/watch.d.ts.map +1 -0
- package/dist/{std-decorators.d.ts → src/std-decorators.d.ts} +1 -6
- package/dist/src/std-decorators.d.ts.map +1 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/{zone.d.ts → src/zone.d.ts} +2 -2
- package/dist/src/zone.d.ts.map +1 -0
- package/dist/types-DaHFfhlN.d.ts +414 -0
- package/docs/ai/api-reference.md +1 -1
- package/docs/ai/manual.md +443 -17
- package/docs/events.md +57 -2
- package/docs/flavored.md +210 -0
- package/docs/reactive/advanced.md +2 -3
- package/docs/reactive/attend.md +143 -0
- package/docs/reactive/collections.md +55 -0
- package/docs/reactive/core.md +120 -4
- package/docs/reactive/debugging.md +8 -10
- package/docs/reactive/describe.md +85 -0
- package/docs/reactive/error-handling.md +121 -0
- package/docs/reactive/project.md +1 -1
- package/docs/reactive/scan.md +215 -0
- package/docs/reactive.md +2 -0
- package/docs/std-decorators.md +102 -51
- package/docs/zone.md +3 -7
- package/package.json +6 -1
- package/src/async/browser.ts +110 -106
- package/src/async/index.ts +15 -11
- package/src/async/node.ts +5 -17
- package/src/eventful.ts +82 -49
- package/src/flavored.ts +106 -0
- package/src/index.d.ts +12 -12
- package/src/index.ts +2 -2
- package/src/introspection.ts +1 -1
- package/src/reactive/array.ts +29 -36
- package/src/reactive/buffer.ts +187 -27
- package/src/reactive/change.ts +16 -23
- package/src/reactive/deep-touch.ts +19 -14
- package/src/reactive/deep-watch-state.ts +3 -3
- package/src/reactive/deep-watch.ts +7 -4
- package/src/reactive/describe.ts +39 -0
- package/src/reactive/effect-context.ts +69 -4
- package/src/reactive/effects.ts +445 -393
- package/src/reactive/index.ts +12 -15
- package/src/reactive/map.ts +9 -25
- package/src/reactive/memoize.ts +26 -3
- package/src/reactive/project.md +107 -0
- package/src/reactive/project.ts +48 -71
- package/src/reactive/proxy.ts +21 -3
- package/src/reactive/record.ts +30 -45
- package/src/reactive/register.ts +138 -19
- package/src/reactive/registry.ts +22 -9
- package/src/reactive/set.ts +8 -21
- package/src/reactive/tracking.ts +9 -12
- package/src/reactive/types.ts +104 -24
- package/src/reactive/{interface.ts → watch.ts} +29 -59
- package/src/std-decorators.ts +56 -17
- package/src/utils.ts +2 -3
- package/src/zone.ts +28 -24
- package/dist/async/index.d.ts.map +0 -1
- package/dist/chunks/async-browser-CA0jPWIi.cjs.map +0 -1
- package/dist/chunks/async-core-UqHzvJ-S.cjs.map +0 -1
- package/dist/chunks/async-node-BYHuGTni.cjs.map +0 -1
- package/dist/chunks/index-DhaOVusv.esm.js +0 -6411
- package/dist/chunks/index-DhaOVusv.esm.js.map +0 -1
- package/dist/decorator.d.ts.map +0 -1
- package/dist/destroyable.d.ts.map +0 -1
- package/dist/entry-browser.d.ts.map +0 -1
- package/dist/entry-node.d.ts.map +0 -1
- package/dist/eventful.d.ts +0 -18
- package/dist/eventful.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/indexable.d.ts.map +0 -1
- package/dist/introspection.d.ts.map +0 -1
- package/dist/iterableWeak.d.ts.map +0 -1
- package/dist/mixins.d.ts.map +0 -1
- package/dist/promiseChain.d.ts.map +0 -1
- package/dist/reactive/array.d.ts.map +0 -1
- package/dist/reactive/buffer.d.ts +0 -44
- package/dist/reactive/buffer.d.ts.map +0 -1
- package/dist/reactive/change.d.ts.map +0 -1
- package/dist/reactive/debug.d.ts.map +0 -1
- package/dist/reactive/deep-touch.d.ts.map +0 -1
- package/dist/reactive/deep-watch-state.d.ts.map +0 -1
- package/dist/reactive/deep-watch.d.ts.map +0 -1
- package/dist/reactive/effect-context.d.ts +0 -7
- package/dist/reactive/effect-context.d.ts.map +0 -1
- package/dist/reactive/effects.d.ts.map +0 -1
- package/dist/reactive/index.d.ts +0 -20
- package/dist/reactive/index.d.ts.map +0 -1
- package/dist/reactive/interface.d.ts.map +0 -1
- package/dist/reactive/map.d.ts.map +0 -1
- package/dist/reactive/memoize.d.ts +0 -5
- package/dist/reactive/memoize.d.ts.map +0 -1
- package/dist/reactive/non-reactive-state.d.ts.map +0 -1
- package/dist/reactive/non-reactive.d.ts.map +0 -1
- package/dist/reactive/project.d.ts.map +0 -1
- package/dist/reactive/proxy-state.d.ts.map +0 -1
- package/dist/reactive/proxy.d.ts.map +0 -1
- package/dist/reactive/record.d.ts.map +0 -1
- package/dist/reactive/register.d.ts.map +0 -1
- package/dist/reactive/registry.d.ts.map +0 -1
- package/dist/reactive/set.d.ts.map +0 -1
- package/dist/reactive/tracking.d.ts.map +0 -1
- package/dist/reactive/types.d.ts.map +0 -1
- package/dist/std-decorators.d.ts.map +0 -1
- package/dist/utils.d.ts.map +0 -1
- package/dist/zone.d.ts.map +0 -1
- package/src/reactive/debug.ts +0 -517
- /package/dist/{async → src/async}/browser.d.ts +0 -0
- /package/dist/{async → src/async}/node.d.ts +0 -0
- /package/dist/{decorator.d.ts → src/decorator.d.ts} +0 -0
- /package/dist/{destroyable.d.ts → src/destroyable.d.ts} +0 -0
- /package/dist/{entry-browser.d.ts → src/entry-browser.d.ts} +0 -0
- /package/dist/{entry-node.d.ts → src/entry-node.d.ts} +0 -0
- /package/dist/{indexable.d.ts → src/indexable.d.ts} +0 -0
- /package/dist/{iterableWeak.d.ts → src/iterableWeak.d.ts} +0 -0
- /package/dist/{mixins.d.ts → src/mixins.d.ts} +0 -0
- /package/dist/{promiseChain.d.ts → src/promiseChain.d.ts} +0 -0
- /package/dist/{reactive → src/reactive}/deep-touch.d.ts +0 -0
- /package/dist/{reactive → src/reactive}/non-reactive-state.d.ts +0 -0
- /package/dist/{reactive → src/reactive}/non-reactive.d.ts +0 -0
- /package/dist/{reactive → src/reactive}/proxy-state.d.ts +0 -0
- /package/dist/{reactive → src/reactive}/proxy.d.ts +0 -0
- /package/dist/{reactive → src/reactive}/tracking.d.ts +0 -0
- /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
package/docs/std-decorators.md
CHANGED
|
@@ -189,13 +189,13 @@ When the decorator is applied to a non-getter method:
|
|
|
189
189
|
3. **Memory overhead**: Cached values are stored indefinitely
|
|
190
190
|
4. **Getter-only**: Can only be applied to getter methods, not setters or regular methods
|
|
191
191
|
|
|
192
|
-
##
|
|
192
|
+
## Descriptor
|
|
193
193
|
|
|
194
|
-
The `
|
|
194
|
+
The `descriptor` decorator provides a clean, reusable way to configure property descriptors (enumerable, configurable, writable) for class properties. This decorator uses a functional approach that makes it easy to create reusable descriptor configurations.
|
|
195
195
|
|
|
196
196
|
## API Reference
|
|
197
197
|
|
|
198
|
-
### `
|
|
198
|
+
### `descriptor(descriptor: PropertyDescriptor): <T>(...properties: (keyof T)[]) => GenericClassDecorator<T>`
|
|
199
199
|
|
|
200
200
|
A function that creates a decorator to configure property descriptors for specified properties.
|
|
201
201
|
|
|
@@ -207,29 +207,68 @@ A function that creates a decorator to configure property descriptors for specif
|
|
|
207
207
|
|
|
208
208
|
**Returns:** A function that takes property names and returns a class decorator
|
|
209
209
|
|
|
210
|
+
**Flavors:**
|
|
211
|
+
The `descriptor` function is flavored with the following chainable properties:
|
|
212
|
+
- `.enumerable` - Sets enumerable to true
|
|
213
|
+
- `.hidden` - Sets enumerable to false
|
|
214
|
+
- `.configurable` - Sets configurable to true
|
|
215
|
+
- `.frozen` - Sets configurable to false
|
|
216
|
+
- `.writable` - Sets writable to true
|
|
217
|
+
- `.readonly` - Sets writable to false
|
|
218
|
+
|
|
210
219
|
**Usage Pattern:**
|
|
211
220
|
```typescript
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
221
|
+
// Using flavors for single properties
|
|
222
|
+
@descriptor.readonly('id', 'createdAt')
|
|
223
|
+
@descriptor.hidden('_private')
|
|
224
|
+
class MyClass { }
|
|
215
225
|
|
|
216
|
-
|
|
217
|
-
@
|
|
218
|
-
@locked('critical')
|
|
226
|
+
// For multiple descriptor properties on the same property, use object syntax
|
|
227
|
+
@descriptor({ writable: false, enumerable: false, configurable: false })('secret')
|
|
219
228
|
class MyClass { }
|
|
229
|
+
|
|
230
|
+
// Note: Stacking multiple flavored decorators on the same property is not recommended
|
|
231
|
+
// as each decorator creates a new subclass and only the last applied takes effect
|
|
220
232
|
```
|
|
221
233
|
|
|
222
234
|
## Usage Examples
|
|
223
235
|
|
|
236
|
+
### Using Flavors
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { descriptor } from 'mutts/std-decorators'
|
|
240
|
+
|
|
241
|
+
// Use flavors directly for cleaner syntax
|
|
242
|
+
@descriptor.readonly('id', 'createdAt')
|
|
243
|
+
@descriptor.hidden('_private', '_cache')
|
|
244
|
+
@descriptor.frozen('critical')
|
|
245
|
+
class User {
|
|
246
|
+
id: string = 'user-123'
|
|
247
|
+
name: string = 'John'
|
|
248
|
+
_private: string = 'secret'
|
|
249
|
+
_cache: Map<string, any> = new Map()
|
|
250
|
+
createdAt: Date = new Date()
|
|
251
|
+
critical: string = 'locked'
|
|
252
|
+
|
|
253
|
+
constructor(name: string) {
|
|
254
|
+
this.name = name
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const user = new User('Alice')
|
|
259
|
+
console.log(Object.keys(user)) // ['name', 'createdAt'] - only enumerable properties
|
|
260
|
+
// user.id = 'new-id' // TypeError: Cannot assign to read only property 'id'
|
|
261
|
+
```
|
|
262
|
+
|
|
224
263
|
### Creating Reusable Decorators
|
|
225
264
|
|
|
226
265
|
```typescript
|
|
227
|
-
import {
|
|
266
|
+
import { descriptor } from 'mutts/std-decorators'
|
|
228
267
|
|
|
229
268
|
// Create reusable descriptor configurations
|
|
230
|
-
const readonly =
|
|
231
|
-
const hidden =
|
|
232
|
-
const locked =
|
|
269
|
+
const readonly = descriptor.readonly
|
|
270
|
+
const hidden = descriptor.hidden
|
|
271
|
+
const locked = descriptor.frozen
|
|
233
272
|
|
|
234
273
|
// Use them on classes
|
|
235
274
|
@readonly('id', 'createdAt')
|
|
@@ -255,11 +294,10 @@ console.log(Object.keys(user)) // ['id', 'name', 'createdAt', 'critical'] - only
|
|
|
255
294
|
### Making Properties Non-Enumerable
|
|
256
295
|
|
|
257
296
|
```typescript
|
|
258
|
-
import {
|
|
297
|
+
import { descriptor } from 'mutts/std-decorators'
|
|
259
298
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
@hidden('_internal', '_cache', 'debug')
|
|
299
|
+
// Use the hidden flavor directly
|
|
300
|
+
@descriptor.hidden('_internal', '_cache', 'debug')
|
|
263
301
|
class CacheManager {
|
|
264
302
|
public data: any[] = []
|
|
265
303
|
_internal: Map<string, any> = new Map()
|
|
@@ -280,13 +318,10 @@ console.log(Object.getOwnPropertyNames(cache)) // ['data', '_internal', '_cache'
|
|
|
280
318
|
### Read-Only Properties
|
|
281
319
|
|
|
282
320
|
```typescript
|
|
283
|
-
import {
|
|
284
|
-
|
|
285
|
-
const readonly = describe({ writable: false })
|
|
286
|
-
const readonlyLocked = describe({ writable: false, configurable: false })
|
|
321
|
+
import { descriptor } from 'mutts/std-decorators'
|
|
287
322
|
|
|
288
|
-
|
|
289
|
-
@
|
|
323
|
+
// Use flavors for read-only properties
|
|
324
|
+
@descriptor.readonly('createdAt', 'version')
|
|
290
325
|
class Document {
|
|
291
326
|
id: string
|
|
292
327
|
title: string
|
|
@@ -306,25 +341,41 @@ class Document {
|
|
|
306
341
|
}
|
|
307
342
|
|
|
308
343
|
const doc = new Document('doc-1', 'My Document')
|
|
309
|
-
// doc.id = 'new-id' // TypeError: Cannot assign to read only property 'id'
|
|
310
344
|
// doc.createdAt = new Date() // TypeError: Cannot assign to read only property 'createdAt'
|
|
345
|
+
// doc.version = 2 // TypeError: Cannot assign to read only property 'version'
|
|
311
346
|
doc.updateTitle('Updated Title') // This works
|
|
312
347
|
```
|
|
313
348
|
|
|
314
|
-
###
|
|
349
|
+
### Combining Multiple Flavors
|
|
315
350
|
|
|
316
351
|
```typescript
|
|
317
|
-
import {
|
|
352
|
+
import { descriptor } from 'mutts/std-decorators'
|
|
318
353
|
|
|
319
|
-
|
|
320
|
-
|
|
354
|
+
// Stack multiple flavored decorators
|
|
355
|
+
@descriptor.readonly('secret')
|
|
356
|
+
@descriptor.hidden('secret')
|
|
357
|
+
@descriptor.frozen('secret')
|
|
358
|
+
class SecureData {
|
|
359
|
+
public info: string = 'public'
|
|
360
|
+
secret: string = 'top secret'
|
|
361
|
+
}
|
|
321
362
|
|
|
322
|
-
|
|
323
|
-
|
|
363
|
+
const data = new SecureData()
|
|
364
|
+
// data.secret = 'leaked' // TypeError: Cannot assign to read only property 'secret'
|
|
365
|
+
console.log(Object.keys(data)) // ['info'] - secret is not enumerable
|
|
366
|
+
// Object.defineProperty(data, 'secret', { value: 'new' }) // TypeError: Cannot redefine property
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Configuration Control
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import { descriptor } from 'mutts/std-decorators'
|
|
373
|
+
|
|
374
|
+
// Use flavors for configuration control
|
|
375
|
+
@descriptor.frozen('_sealed')
|
|
324
376
|
class SecureObject {
|
|
325
377
|
public data: any
|
|
326
378
|
_sealed: string = 'cannot be reconfigured'
|
|
327
|
-
_frozen: string = 'cannot be changed or reconfigured'
|
|
328
379
|
|
|
329
380
|
constructor(data: any) {
|
|
330
381
|
this.data = data
|
|
@@ -333,41 +384,41 @@ class SecureObject {
|
|
|
333
384
|
|
|
334
385
|
const obj = new SecureObject({ key: 'value' })
|
|
335
386
|
// Object.defineProperty(obj, '_sealed', { value: 'new' }) // TypeError: Cannot redefine property
|
|
336
|
-
// Object.defineProperty(obj, '_frozen', { value: 'new' }) // TypeError: Cannot redefine property
|
|
337
387
|
```
|
|
338
388
|
|
|
339
389
|
## Implementation Details
|
|
340
390
|
|
|
341
391
|
### Functional Approach
|
|
342
392
|
|
|
343
|
-
The `
|
|
393
|
+
The `descriptor` function uses a flavored approach that provides direct access to common configurations:
|
|
344
394
|
|
|
345
395
|
```typescript
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// Apply to multiple classes with different properties
|
|
351
|
-
@readonly('id', 'createdAt')
|
|
396
|
+
// Use flavors directly for cleaner syntax
|
|
397
|
+
@descriptor.readonly('id', 'createdAt')
|
|
398
|
+
@descriptor.hidden('internal')
|
|
352
399
|
class User { }
|
|
353
400
|
|
|
401
|
+
// Or create reusable aliases
|
|
402
|
+
const readonly = descriptor.readonly
|
|
403
|
+
const hidden = descriptor.hidden
|
|
404
|
+
|
|
354
405
|
@readonly('version', 'buildDate')
|
|
355
406
|
class Package { }
|
|
356
407
|
```
|
|
357
408
|
|
|
358
409
|
### Common Decorator Patterns
|
|
359
410
|
|
|
360
|
-
The
|
|
411
|
+
The flavored approach makes it easy to create common decorator patterns:
|
|
361
412
|
|
|
362
413
|
```typescript
|
|
363
|
-
import {
|
|
414
|
+
import { descriptor } from './std-decorators'
|
|
364
415
|
|
|
365
|
-
// Common reusable decorators
|
|
366
|
-
export const readonly =
|
|
367
|
-
export const hidden =
|
|
368
|
-
export const locked =
|
|
369
|
-
export const frozen =
|
|
370
|
-
export const private =
|
|
416
|
+
// Common reusable decorators using flavors
|
|
417
|
+
export const readonly = descriptor.readonly
|
|
418
|
+
export const hidden = descriptor.hidden
|
|
419
|
+
export const locked = descriptor.frozen
|
|
420
|
+
export const frozen = (() => descriptor.readonly.frozen)()
|
|
421
|
+
export const private = (() => descriptor.hidden.frozen)()
|
|
371
422
|
|
|
372
423
|
// Usage examples
|
|
373
424
|
@readonly('id', 'createdAt')
|
|
@@ -389,7 +440,7 @@ The decorator merges the provided descriptor configuration with the existing pro
|
|
|
389
440
|
|
|
390
441
|
```typescript
|
|
391
442
|
// Original property might have { enumerable: true, writable: true, configurable: true }
|
|
392
|
-
// After @readonly('prop')
|
|
443
|
+
// After @descriptor.readonly('prop')
|
|
393
444
|
// Final descriptor: { enumerable: true, writable: false, configurable: true }
|
|
394
445
|
```
|
|
395
446
|
|
|
@@ -399,9 +450,9 @@ The property descriptor configuration is applied in the constructor after callin
|
|
|
399
450
|
|
|
400
451
|
## Best Practices
|
|
401
452
|
|
|
402
|
-
1. **
|
|
403
|
-
2. **
|
|
404
|
-
3. **Combine multiple decorators**: Stack multiple
|
|
453
|
+
1. **Use flavors directly**: Prefer `descriptor.readonly` over creating aliases for simple cases
|
|
454
|
+
2. **Create reusable aliases**: For complex or frequently used combinations, create aliases
|
|
455
|
+
3. **Combine multiple decorators**: Stack multiple flavored decorators for complex configurations
|
|
405
456
|
4. **Use for encapsulation**: Hide internal properties from enumeration
|
|
406
457
|
5. **Control immutability**: Make critical properties read-only
|
|
407
458
|
6. **Prevent reconfiguration**: Lock important properties from being modified
|
package/docs/zone.md
CHANGED
|
@@ -21,20 +21,17 @@ console.log(myZone.active); // undefined
|
|
|
21
21
|
|
|
22
22
|
## Async Propagation
|
|
23
23
|
|
|
24
|
-
By default, zones are lost when an async operation yields control (e.g., after `await`). To fix this, `
|
|
24
|
+
By default, zones are lost when an async operation yields control (e.g., after `await`). To fix this, register zones in `asyncZone` — the global `ZoneAggregator` — which automatically preserves context across async boundaries via `asyncHooks`.
|
|
25
25
|
|
|
26
26
|
```typescript
|
|
27
|
-
import {
|
|
27
|
+
import { asyncZone, Zone } from 'mutts/zone';
|
|
28
28
|
|
|
29
29
|
const requestId = new Zone<string>();
|
|
30
30
|
|
|
31
31
|
// 1. Tell the global aggregator to track this zone
|
|
32
32
|
asyncZone.add(requestId);
|
|
33
33
|
|
|
34
|
-
// 2.
|
|
35
|
-
configureAsyncZone();
|
|
36
|
-
|
|
37
|
-
// 3. Usage
|
|
34
|
+
// 2. Usage
|
|
38
35
|
requestId.with("req-123", async () => {
|
|
39
36
|
await somePromise();
|
|
40
37
|
// Context is automatically preserved across await!
|
|
@@ -43,7 +40,6 @@ requestId.with("req-123", async () => {
|
|
|
43
40
|
```
|
|
44
41
|
|
|
45
42
|
> [!WARNING]
|
|
46
|
-
> TODO
|
|
47
43
|
> **Browser Limitations**: In browser environments (where `AsyncLocalStorage` is unavailable), `mutts` relies on monkey-patching global async primitives (Promise, setTimeout, etc.) to propagate zones. This is generally less robust than Node.js's `async_hooks` and may fail to track context across:
|
|
48
44
|
> * Native `async/await` boundaries in some modern browsers if not transpiled.
|
|
49
45
|
> * Concurrent modifications to global prototypes by other libraries.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mutts",
|
|
3
3
|
"description": "Modern UTility TS: A collection of TypeScript utilities",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.9",
|
|
5
5
|
"main": "dist/browser.cjs",
|
|
6
6
|
"module": "dist/browser.esm.js",
|
|
7
7
|
"types": "dist/browser.d.ts",
|
|
@@ -51,6 +51,11 @@
|
|
|
51
51
|
"./src/node": {
|
|
52
52
|
"types": "./src/entry-node.ts",
|
|
53
53
|
"import": "./src/entry-node.ts"
|
|
54
|
+
},
|
|
55
|
+
"./debug": {
|
|
56
|
+
"types": "./dist/debug.d.ts",
|
|
57
|
+
"import": "./dist/debug.esm.js",
|
|
58
|
+
"require": "./dist/debug.cjs"
|
|
54
59
|
}
|
|
55
60
|
},
|
|
56
61
|
"files": [
|
package/src/async/browser.ts
CHANGED
|
@@ -1,29 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { asyncHooks, hooks, type Restorer } from '.'
|
|
2
2
|
|
|
3
|
-
const hooks = new Set<Hook>()
|
|
4
3
|
const promiseContexts = new WeakMap<Promise<any>, Set<Restorer>>()
|
|
5
4
|
|
|
6
|
-
asyncHooks.addHook = function (hook: Hook) {
|
|
7
|
-
hooks.add(hook)
|
|
8
|
-
return () => {
|
|
9
|
-
hooks.delete(hook)
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
5
|
// [HACK]: Sanitization
|
|
14
6
|
// If a Promise is created inside the zone, it carries the "Sticky" zone context.
|
|
15
7
|
// If returned to the outer scope, that context leaks. We wrap it in a new Promise
|
|
16
8
|
// created here (in the outer scope) to break the chain and sanitize the return value.
|
|
17
9
|
// See BROWSER_ASYNC_POLYFILL.md for full details.
|
|
18
10
|
asyncHooks.sanitizePromise = (res: any) => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
11
|
+
if (res && typeof (res as any).then === 'function') {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
;(res as any).then(resolve, reject)
|
|
15
|
+
}, 0)
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
return res
|
|
27
19
|
}
|
|
28
20
|
|
|
29
21
|
function captureRestorers() {
|
|
@@ -35,7 +27,10 @@ function captureRestorers() {
|
|
|
35
27
|
return restorers
|
|
36
28
|
}
|
|
37
29
|
|
|
38
|
-
function wrap<Args extends any[], R>(
|
|
30
|
+
function wrap<Args extends any[], R>(
|
|
31
|
+
fn: ((...args: Args) => R) | null | undefined,
|
|
32
|
+
capturedRestorers?: Set<Restorer>
|
|
33
|
+
) {
|
|
39
34
|
if (typeof fn !== 'function') return fn
|
|
40
35
|
const restorers = capturedRestorers || captureRestorers()
|
|
41
36
|
return function (this: any, ...args: Args) {
|
|
@@ -88,7 +83,7 @@ function patchEventTarget(proto: any) {
|
|
|
88
83
|
wrapped = wrap(listener)
|
|
89
84
|
listeners.set(listener, wrapped)
|
|
90
85
|
}
|
|
91
|
-
|
|
86
|
+
|
|
92
87
|
return nativeAdd.call(this, type, wrapped, options)
|
|
93
88
|
}
|
|
94
89
|
|
|
@@ -107,7 +102,7 @@ function patchEventTarget(proto: any) {
|
|
|
107
102
|
}
|
|
108
103
|
}
|
|
109
104
|
}
|
|
110
|
-
|
|
105
|
+
|
|
111
106
|
return nativeRemove.call(this, type, listener, options)
|
|
112
107
|
}
|
|
113
108
|
}
|
|
@@ -123,7 +118,7 @@ function patchOnProperties(proto: any) {
|
|
|
123
118
|
...desc,
|
|
124
119
|
set: function (this: any, fn: any) {
|
|
125
120
|
nativeSet.call(this, wrap(fn))
|
|
126
|
-
}
|
|
121
|
+
},
|
|
127
122
|
})
|
|
128
123
|
}
|
|
129
124
|
}
|
|
@@ -154,35 +149,35 @@ for (const proto of prototypesToPatch) {
|
|
|
154
149
|
}
|
|
155
150
|
}
|
|
156
151
|
|
|
157
|
-
const GLOBAL_ORIGINALS = Symbol.for('mutts.originals')
|
|
158
|
-
const GLOBAL_PROMISE = Symbol.for('mutts.OriginalPromise')
|
|
152
|
+
const GLOBAL_ORIGINALS = Symbol.for('mutts.originals')
|
|
153
|
+
const GLOBAL_PROMISE = Symbol.for('mutts.OriginalPromise')
|
|
159
154
|
|
|
160
|
-
let originals: any
|
|
161
|
-
let OriginalPromise: any
|
|
155
|
+
let originals: any
|
|
156
|
+
let OriginalPromise: any
|
|
162
157
|
|
|
163
158
|
if ((globalThis as any)[GLOBAL_ORIGINALS]) {
|
|
164
|
-
|
|
165
|
-
|
|
159
|
+
originals = (globalThis as any)[GLOBAL_ORIGINALS]
|
|
160
|
+
OriginalPromise = (globalThis as any)[GLOBAL_PROMISE]
|
|
166
161
|
} else {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
162
|
+
OriginalPromise = globalThis.Promise
|
|
163
|
+
originals = {
|
|
164
|
+
then: OriginalPromise.prototype.then,
|
|
165
|
+
catch: OriginalPromise.prototype.catch,
|
|
166
|
+
finally: OriginalPromise.prototype.finally,
|
|
167
|
+
resolve: OriginalPromise.resolve,
|
|
168
|
+
reject: OriginalPromise.reject,
|
|
169
|
+
all: OriginalPromise.all,
|
|
170
|
+
allSettled: (OriginalPromise as any).allSettled,
|
|
171
|
+
race: OriginalPromise.race,
|
|
172
|
+
any: (OriginalPromise as any).any,
|
|
173
|
+
setTimeout: globalThis.setTimeout,
|
|
174
|
+
setInterval: globalThis.setInterval,
|
|
175
|
+
setImmediate: (globalThis as any).setImmediate,
|
|
176
|
+
requestAnimationFrame: (globalThis as any).requestAnimationFrame,
|
|
177
|
+
queueMicrotask: globalThis.queueMicrotask,
|
|
178
|
+
}
|
|
179
|
+
;(globalThis as any)[GLOBAL_ORIGINALS] = originals
|
|
180
|
+
;(globalThis as any)[GLOBAL_PROMISE] = OriginalPromise
|
|
186
181
|
}
|
|
187
182
|
|
|
188
183
|
// Ensure modern statics are captured even if originals was cached from an older version
|
|
@@ -192,7 +187,11 @@ if (!originals.race) originals.race = OriginalPromise.race
|
|
|
192
187
|
|
|
193
188
|
function patchedThen(this: any, onFulfilled: any, onRejected: any) {
|
|
194
189
|
const context = promiseContexts.get(this) || captureRestorers()
|
|
195
|
-
const nextPromise = originals.then.call(
|
|
190
|
+
const nextPromise = originals.then.call(
|
|
191
|
+
this,
|
|
192
|
+
wrap(onFulfilled, context),
|
|
193
|
+
wrap(onRejected, context)
|
|
194
|
+
)
|
|
196
195
|
if (context.size > 0) promiseContexts.set(nextPromise, context)
|
|
197
196
|
return nextPromise
|
|
198
197
|
}
|
|
@@ -211,82 +210,87 @@ function patchedFinally(this: any, onFinally: any) {
|
|
|
211
210
|
return nextPromise
|
|
212
211
|
}
|
|
213
212
|
|
|
214
|
-
function PatchedPromise<T>(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
213
|
+
function PatchedPromise<T>(
|
|
214
|
+
this: any,
|
|
215
|
+
executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void
|
|
216
|
+
) {
|
|
217
|
+
if (typeof executor === 'function') {
|
|
218
|
+
const p = new OriginalPromise((resolve, reject) => {
|
|
219
|
+
const wrappedResolve = wrap(resolve)
|
|
220
|
+
const wrappedReject = wrap(reject)
|
|
221
|
+
executor(wrappedResolve, wrappedReject)
|
|
222
|
+
})
|
|
223
|
+
const context = captureRestorers()
|
|
224
|
+
promiseContexts.set(p, context) // Always set, even if empty (Sticky Root)
|
|
225
|
+
return p
|
|
226
|
+
}
|
|
227
|
+
return new OriginalPromise(executor)
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
// Copy statics
|
|
229
|
-
Object.assign(PatchedPromise,
|
|
231
|
+
Object.assign(PatchedPromise, OriginalPromise as any)
|
|
230
232
|
|
|
231
233
|
// Inherit prototype for instanceof checks
|
|
232
234
|
PatchedPromise.prototype = OriginalPromise.prototype
|
|
233
235
|
|
|
234
|
-
PatchedPromise.resolve =
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
} as any
|
|
241
|
-
|
|
242
|
-
PatchedPromise.reject =
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
} as any
|
|
248
|
-
|
|
249
|
-
PatchedPromise.all =
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
} as any
|
|
255
|
-
|
|
256
|
-
PatchedPromise.allSettled =
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
236
|
+
PatchedPromise.resolve = (<T>(value?: T | PromiseLike<T>): Promise<T> => {
|
|
237
|
+
const p = originals.resolve.call(OriginalPromise, value) as Promise<T>
|
|
238
|
+
const context = captureRestorers()
|
|
239
|
+
// Ensure we don't overwrite if it already has context (e.g. from constructor)
|
|
240
|
+
if (context.size > 0 && !promiseContexts.has(p)) promiseContexts.set(p, context)
|
|
241
|
+
return p
|
|
242
|
+
}) as any
|
|
243
|
+
|
|
244
|
+
PatchedPromise.reject = (<T = never>(reason?: any): Promise<T> => {
|
|
245
|
+
const p = originals.reject.call(OriginalPromise, reason) as Promise<T>
|
|
246
|
+
const context = captureRestorers()
|
|
247
|
+
if (context.size > 0) promiseContexts.set(p, context)
|
|
248
|
+
return p
|
|
249
|
+
}) as any
|
|
250
|
+
|
|
251
|
+
PatchedPromise.all = (<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>[]> => {
|
|
252
|
+
const p = originals.all.call(OriginalPromise, values) as Promise<Awaited<T>[]>
|
|
253
|
+
const context = captureRestorers()
|
|
254
|
+
if (context.size > 0) promiseContexts.set(p, context)
|
|
255
|
+
return p
|
|
256
|
+
}) as any
|
|
257
|
+
|
|
258
|
+
PatchedPromise.allSettled = (<T>(
|
|
259
|
+
values: Iterable<T | PromiseLike<T>>
|
|
260
|
+
): Promise<PromiseSettledResult<Awaited<T>>[]> => {
|
|
261
|
+
const p = (originals.allSettled as any).call(OriginalPromise, values)
|
|
262
|
+
const context = captureRestorers()
|
|
263
|
+
if (context.size > 0) promiseContexts.set(p, context)
|
|
264
|
+
return p
|
|
265
|
+
}) as any
|
|
266
|
+
|
|
267
|
+
PatchedPromise.race = (<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>> => {
|
|
268
|
+
const p = originals.race.call(OriginalPromise, values) as Promise<Awaited<T>>
|
|
269
|
+
const context = captureRestorers()
|
|
270
|
+
if (context.size > 0) promiseContexts.set(p, context)
|
|
271
|
+
return p
|
|
272
|
+
}) as any
|
|
273
|
+
|
|
274
|
+
PatchedPromise.any = (<T>(values: Iterable<T | PromiseLike<T>>): Promise<Awaited<T>> => {
|
|
275
|
+
const p = (originals.any as any).call(OriginalPromise, values)
|
|
276
|
+
const context = captureRestorers()
|
|
277
|
+
if (context.size > 0) promiseContexts.set(p, context)
|
|
278
|
+
return p
|
|
279
|
+
}) as any
|
|
276
280
|
|
|
277
281
|
// Only apply patches if not already applied (or re-apply safely)
|
|
278
282
|
// Note: OriginalPromise.prototype might be shared if we used the global one.
|
|
279
283
|
// We must ensure we don't patch it twice if it's the SAME object.
|
|
280
284
|
if (OriginalPromise.prototype.then !== patchedThen) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
285
|
+
OriginalPromise.prototype.then = patchedThen as any
|
|
286
|
+
OriginalPromise.prototype.catch = patchedCatch as any
|
|
287
|
+
OriginalPromise.prototype.finally = patchedFinally as any
|
|
284
288
|
}
|
|
285
289
|
|
|
286
290
|
try {
|
|
287
291
|
Object.defineProperty(OriginalPromise, Symbol.species, {
|
|
288
292
|
get: () => PatchedPromise,
|
|
289
|
-
configurable: true
|
|
293
|
+
configurable: true,
|
|
290
294
|
})
|
|
291
295
|
} catch (e) {}
|
|
292
296
|
|
package/src/async/index.ts
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
export type Restorer = () => () => void
|
|
2
2
|
export type Hook = () => Restorer
|
|
3
3
|
|
|
4
|
+
// Queue for hooks registered before the environment is ready (circular dependency fix)
|
|
5
|
+
export const hooks = new Set<Hook>()
|
|
6
|
+
|
|
4
7
|
export const asyncHooks = {
|
|
5
|
-
addHook(
|
|
6
|
-
|
|
8
|
+
addHook(hook: Hook): () => void {
|
|
9
|
+
hooks.add(hook)
|
|
10
|
+
return () => hooks.delete(hook)
|
|
11
|
+
},
|
|
12
|
+
/**
|
|
13
|
+
* [Hack] Sanitize a promise (or value) to prevent context leaks.
|
|
14
|
+
* Default: Identity function.
|
|
15
|
+
* Browser: Uses Macrotask wrapping to break microtask chains.
|
|
16
|
+
*/
|
|
17
|
+
sanitizePromise(p: any): any {
|
|
18
|
+
return p
|
|
7
19
|
},
|
|
8
|
-
/**
|
|
9
|
-
* [Hack] Sanitize a promise (or value) to prevent context leaks.
|
|
10
|
-
* Default: Identity function.
|
|
11
|
-
* Browser: Uses Macrotask wrapping to break microtask chains.
|
|
12
|
-
*/
|
|
13
|
-
sanitizePromise(p: any): any {
|
|
14
|
-
return p
|
|
15
|
-
}
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
/**
|
|
@@ -20,4 +24,4 @@ export const asyncHooks = {
|
|
|
20
24
|
* The hook should return a restorer function which will be called just before the async callback runs.
|
|
21
25
|
* That restorer should in turn return an undoer function which will be called just after the async callback finishes.
|
|
22
26
|
*/
|
|
23
|
-
export const asyncHook = (hook: Hook) => asyncHooks.addHook(hook)
|
|
27
|
+
export const asyncHook = (hook: Hook) => asyncHooks.addHook(hook)
|