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.
Files changed (226) hide show
  1. package/README.md +19 -2
  2. package/dist/browser.cjs +1030 -4406
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +310 -573
  5. package/dist/browser.esm.js +18 -24
  6. package/dist/browser.esm.js.map +1 -1
  7. package/dist/chunks/{async-browser-CA0jPWIi.cjs → async-browser-lvzLOCgk.cjs} +17 -24
  8. package/dist/chunks/async-browser-lvzLOCgk.cjs.map +1 -0
  9. package/dist/chunks/{async-core-UqHzvJ-S.cjs → async-core-CRLKP3l-.cjs} +8 -4
  10. package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
  11. package/dist/chunks/{async-node-BYHuGTni.cjs → async-node-C3DeIb0y.cjs} +5 -12
  12. package/dist/chunks/async-node-C3DeIb0y.cjs.map +1 -0
  13. package/dist/chunks/index-2vea86wD.esm.js +3011 -0
  14. package/dist/chunks/index-2vea86wD.esm.js.map +1 -0
  15. package/dist/chunks/index-CtA2AWl3.esm.js +4376 -0
  16. package/dist/chunks/index-CtA2AWl3.esm.js.map +1 -0
  17. package/dist/chunks/index-VTO-b2vR.cjs +4461 -0
  18. package/dist/chunks/index-VTO-b2vR.cjs.map +1 -0
  19. package/dist/{reactive → debug}/debug.d.ts +19 -8
  20. package/dist/debug/debug.d.ts.map +1 -0
  21. package/dist/debug/index.d.ts +4 -0
  22. package/dist/debug/index.d.ts.map +1 -0
  23. package/dist/debug/lineage-panel.d.ts +5 -0
  24. package/dist/debug/lineage-panel.d.ts.map +1 -0
  25. package/dist/debug/lineage.d.ts +79 -0
  26. package/dist/debug/lineage.d.ts.map +1 -0
  27. package/dist/debug.cjs +35 -0
  28. package/dist/debug.cjs.map +1 -0
  29. package/dist/debug.d.ts +209 -0
  30. package/dist/debug.esm.js +2 -0
  31. package/dist/debug.esm.js.map +1 -0
  32. package/dist/index.d.ts +1 -13
  33. package/dist/mutts.umd.js +1 -1
  34. package/dist/mutts.umd.js.map +1 -1
  35. package/dist/mutts.umd.min.js +1 -1
  36. package/dist/mutts.umd.min.js.map +1 -1
  37. package/dist/node.cjs +65 -63
  38. package/dist/node.cjs.map +1 -1
  39. package/dist/node.d.ts +2 -1
  40. package/dist/node.esm.js +5 -11
  41. package/dist/node.esm.js.map +1 -1
  42. package/dist/{async → src/async}/browser.d.ts.map +1 -1
  43. package/dist/{async → src/async}/index.d.ts +2 -1
  44. package/dist/src/async/index.d.ts.map +1 -0
  45. package/dist/{async → src/async}/node.d.ts.map +1 -1
  46. package/dist/src/decorator.d.ts.map +1 -0
  47. package/dist/src/destroyable.d.ts.map +1 -0
  48. package/dist/src/entry-browser.d.ts.map +1 -0
  49. package/dist/src/entry-node.d.ts.map +1 -0
  50. package/dist/src/eventful.d.ts +20 -0
  51. package/dist/src/eventful.d.ts.map +1 -0
  52. package/dist/src/flavored.d.ts +33 -0
  53. package/dist/src/flavored.d.ts.map +1 -0
  54. package/dist/src/index.d.ts +14 -0
  55. package/dist/src/index.d.ts.map +1 -0
  56. package/dist/src/indexable.d.ts.map +1 -0
  57. package/dist/{introspection.d.ts → src/introspection.d.ts} +1 -1
  58. package/dist/src/introspection.d.ts.map +1 -0
  59. package/dist/src/iterableWeak.d.ts.map +1 -0
  60. package/dist/src/mixins.d.ts.map +1 -0
  61. package/dist/src/promiseChain.d.ts.map +1 -0
  62. package/dist/{reactive → src/reactive}/array.d.ts +9 -10
  63. package/dist/src/reactive/array.d.ts.map +1 -0
  64. package/dist/src/reactive/buffer.d.ts +120 -0
  65. package/dist/src/reactive/buffer.d.ts.map +1 -0
  66. package/dist/{reactive → src/reactive}/change.d.ts +2 -2
  67. package/dist/src/reactive/change.d.ts.map +1 -0
  68. package/dist/src/reactive/deep-touch.d.ts.map +1 -0
  69. package/dist/{reactive → src/reactive}/deep-watch-state.d.ts +3 -3
  70. package/dist/src/reactive/deep-watch-state.d.ts.map +1 -0
  71. package/dist/{reactive → src/reactive}/deep-watch.d.ts +2 -1
  72. package/dist/src/reactive/deep-watch.d.ts.map +1 -0
  73. package/dist/src/reactive/describe.d.ts +12 -0
  74. package/dist/src/reactive/describe.d.ts.map +1 -0
  75. package/dist/src/reactive/effect-context.d.ts +34 -0
  76. package/dist/src/reactive/effect-context.d.ts.map +1 -0
  77. package/dist/{reactive → src/reactive}/effects.d.ts +31 -18
  78. package/dist/src/reactive/effects.d.ts.map +1 -0
  79. package/dist/src/reactive/index.d.ts +19 -0
  80. package/dist/src/reactive/index.d.ts.map +1 -0
  81. package/dist/{reactive → src/reactive}/map.d.ts +0 -2
  82. package/dist/src/reactive/map.d.ts.map +1 -0
  83. package/dist/src/reactive/memoize.d.ts +28 -0
  84. package/dist/src/reactive/memoize.d.ts.map +1 -0
  85. package/dist/src/reactive/non-reactive-state.d.ts.map +1 -0
  86. package/dist/src/reactive/non-reactive.d.ts.map +1 -0
  87. package/dist/{reactive → src/reactive}/project.d.ts +1 -2
  88. package/dist/src/reactive/project.d.ts.map +1 -0
  89. package/dist/src/reactive/proxy-state.d.ts.map +1 -0
  90. package/dist/src/reactive/proxy.d.ts.map +1 -0
  91. package/dist/{reactive → src/reactive}/record.d.ts +2 -3
  92. package/dist/src/reactive/record.d.ts.map +1 -0
  93. package/dist/{reactive → src/reactive}/register.d.ts +61 -0
  94. package/dist/src/reactive/register.d.ts.map +1 -0
  95. package/dist/{reactive → src/reactive}/registry.d.ts +6 -5
  96. package/dist/src/reactive/registry.d.ts.map +1 -0
  97. package/dist/{reactive → src/reactive}/set.d.ts +0 -2
  98. package/dist/src/reactive/set.d.ts.map +1 -0
  99. package/dist/src/reactive/tracking.d.ts.map +1 -0
  100. package/dist/{reactive → src/reactive}/types.d.ts +69 -21
  101. package/dist/src/reactive/types.d.ts.map +1 -0
  102. package/dist/{reactive/interface.d.ts → src/reactive/watch.d.ts} +7 -23
  103. package/dist/src/reactive/watch.d.ts.map +1 -0
  104. package/dist/{std-decorators.d.ts → src/std-decorators.d.ts} +1 -6
  105. package/dist/src/std-decorators.d.ts.map +1 -0
  106. package/dist/src/utils.d.ts.map +1 -0
  107. package/dist/{zone.d.ts → src/zone.d.ts} +2 -2
  108. package/dist/src/zone.d.ts.map +1 -0
  109. package/dist/types-DaHFfhlN.d.ts +414 -0
  110. package/docs/ai/api-reference.md +1 -1
  111. package/docs/ai/manual.md +443 -17
  112. package/docs/events.md +57 -2
  113. package/docs/flavored.md +210 -0
  114. package/docs/reactive/advanced.md +2 -3
  115. package/docs/reactive/attend.md +143 -0
  116. package/docs/reactive/collections.md +55 -0
  117. package/docs/reactive/core.md +120 -4
  118. package/docs/reactive/debugging.md +8 -10
  119. package/docs/reactive/describe.md +85 -0
  120. package/docs/reactive/error-handling.md +121 -0
  121. package/docs/reactive/project.md +1 -1
  122. package/docs/reactive/scan.md +215 -0
  123. package/docs/reactive.md +2 -0
  124. package/docs/std-decorators.md +102 -51
  125. package/docs/zone.md +3 -7
  126. package/package.json +6 -1
  127. package/src/async/browser.ts +110 -106
  128. package/src/async/index.ts +15 -11
  129. package/src/async/node.ts +5 -17
  130. package/src/eventful.ts +82 -49
  131. package/src/flavored.ts +106 -0
  132. package/src/index.d.ts +12 -12
  133. package/src/index.ts +2 -2
  134. package/src/introspection.ts +1 -1
  135. package/src/reactive/array.ts +29 -36
  136. package/src/reactive/buffer.ts +187 -27
  137. package/src/reactive/change.ts +16 -23
  138. package/src/reactive/deep-touch.ts +19 -14
  139. package/src/reactive/deep-watch-state.ts +3 -3
  140. package/src/reactive/deep-watch.ts +7 -4
  141. package/src/reactive/describe.ts +39 -0
  142. package/src/reactive/effect-context.ts +69 -4
  143. package/src/reactive/effects.ts +445 -393
  144. package/src/reactive/index.ts +12 -15
  145. package/src/reactive/map.ts +9 -25
  146. package/src/reactive/memoize.ts +26 -3
  147. package/src/reactive/project.md +107 -0
  148. package/src/reactive/project.ts +48 -71
  149. package/src/reactive/proxy.ts +21 -3
  150. package/src/reactive/record.ts +30 -45
  151. package/src/reactive/register.ts +138 -19
  152. package/src/reactive/registry.ts +22 -9
  153. package/src/reactive/set.ts +8 -21
  154. package/src/reactive/tracking.ts +9 -12
  155. package/src/reactive/types.ts +104 -24
  156. package/src/reactive/{interface.ts → watch.ts} +29 -59
  157. package/src/std-decorators.ts +56 -17
  158. package/src/utils.ts +2 -3
  159. package/src/zone.ts +28 -24
  160. package/dist/async/index.d.ts.map +0 -1
  161. package/dist/chunks/async-browser-CA0jPWIi.cjs.map +0 -1
  162. package/dist/chunks/async-core-UqHzvJ-S.cjs.map +0 -1
  163. package/dist/chunks/async-node-BYHuGTni.cjs.map +0 -1
  164. package/dist/chunks/index-DhaOVusv.esm.js +0 -6411
  165. package/dist/chunks/index-DhaOVusv.esm.js.map +0 -1
  166. package/dist/decorator.d.ts.map +0 -1
  167. package/dist/destroyable.d.ts.map +0 -1
  168. package/dist/entry-browser.d.ts.map +0 -1
  169. package/dist/entry-node.d.ts.map +0 -1
  170. package/dist/eventful.d.ts +0 -18
  171. package/dist/eventful.d.ts.map +0 -1
  172. package/dist/index.d.ts.map +0 -1
  173. package/dist/indexable.d.ts.map +0 -1
  174. package/dist/introspection.d.ts.map +0 -1
  175. package/dist/iterableWeak.d.ts.map +0 -1
  176. package/dist/mixins.d.ts.map +0 -1
  177. package/dist/promiseChain.d.ts.map +0 -1
  178. package/dist/reactive/array.d.ts.map +0 -1
  179. package/dist/reactive/buffer.d.ts +0 -44
  180. package/dist/reactive/buffer.d.ts.map +0 -1
  181. package/dist/reactive/change.d.ts.map +0 -1
  182. package/dist/reactive/debug.d.ts.map +0 -1
  183. package/dist/reactive/deep-touch.d.ts.map +0 -1
  184. package/dist/reactive/deep-watch-state.d.ts.map +0 -1
  185. package/dist/reactive/deep-watch.d.ts.map +0 -1
  186. package/dist/reactive/effect-context.d.ts +0 -7
  187. package/dist/reactive/effect-context.d.ts.map +0 -1
  188. package/dist/reactive/effects.d.ts.map +0 -1
  189. package/dist/reactive/index.d.ts +0 -20
  190. package/dist/reactive/index.d.ts.map +0 -1
  191. package/dist/reactive/interface.d.ts.map +0 -1
  192. package/dist/reactive/map.d.ts.map +0 -1
  193. package/dist/reactive/memoize.d.ts +0 -5
  194. package/dist/reactive/memoize.d.ts.map +0 -1
  195. package/dist/reactive/non-reactive-state.d.ts.map +0 -1
  196. package/dist/reactive/non-reactive.d.ts.map +0 -1
  197. package/dist/reactive/project.d.ts.map +0 -1
  198. package/dist/reactive/proxy-state.d.ts.map +0 -1
  199. package/dist/reactive/proxy.d.ts.map +0 -1
  200. package/dist/reactive/record.d.ts.map +0 -1
  201. package/dist/reactive/register.d.ts.map +0 -1
  202. package/dist/reactive/registry.d.ts.map +0 -1
  203. package/dist/reactive/set.d.ts.map +0 -1
  204. package/dist/reactive/tracking.d.ts.map +0 -1
  205. package/dist/reactive/types.d.ts.map +0 -1
  206. package/dist/std-decorators.d.ts.map +0 -1
  207. package/dist/utils.d.ts.map +0 -1
  208. package/dist/zone.d.ts.map +0 -1
  209. package/src/reactive/debug.ts +0 -517
  210. /package/dist/{async → src/async}/browser.d.ts +0 -0
  211. /package/dist/{async → src/async}/node.d.ts +0 -0
  212. /package/dist/{decorator.d.ts → src/decorator.d.ts} +0 -0
  213. /package/dist/{destroyable.d.ts → src/destroyable.d.ts} +0 -0
  214. /package/dist/{entry-browser.d.ts → src/entry-browser.d.ts} +0 -0
  215. /package/dist/{entry-node.d.ts → src/entry-node.d.ts} +0 -0
  216. /package/dist/{indexable.d.ts → src/indexable.d.ts} +0 -0
  217. /package/dist/{iterableWeak.d.ts → src/iterableWeak.d.ts} +0 -0
  218. /package/dist/{mixins.d.ts → src/mixins.d.ts} +0 -0
  219. /package/dist/{promiseChain.d.ts → src/promiseChain.d.ts} +0 -0
  220. /package/dist/{reactive → src/reactive}/deep-touch.d.ts +0 -0
  221. /package/dist/{reactive → src/reactive}/non-reactive-state.d.ts +0 -0
  222. /package/dist/{reactive → src/reactive}/non-reactive.d.ts +0 -0
  223. /package/dist/{reactive → src/reactive}/proxy-state.d.ts +0 -0
  224. /package/dist/{reactive → src/reactive}/proxy.d.ts +0 -0
  225. /package/dist/{reactive → src/reactive}/tracking.d.ts +0 -0
  226. /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
package/src/async/node.ts CHANGED
@@ -1,17 +1,9 @@
1
1
  import { createHook } from 'node:async_hooks'
2
- import { Hook, Restorer, asyncHooks } from '.'
2
+ import { hooks, type Restorer } from '.'
3
3
 
4
4
  // 1. Generic async_hooks implementation for Hooks
5
5
  // This maintains support for 'asyncHooks.addHook' for generic use cases.
6
6
 
7
- const hooks = new Set<Hook>()
8
- asyncHooks.addHook = function (hook: Hook) {
9
- hooks.add(hook)
10
- return () => {
11
- hooks.delete(hook)
12
- }
13
- }
14
-
15
7
  const contexts = new Map<number, Restorer[]>()
16
8
  const activeUndoers = new Map<number, (() => void)[]>()
17
9
 
@@ -47,7 +39,7 @@ function wrap<Args extends any[], R>(fn: ((...args: Args) => R) | null | undefin
47
39
  }
48
40
 
49
41
  const hook = createHook({
50
- init(asyncId, type, triggerId, resource) {
42
+ init(asyncId, _type, _triggerId, _resource) {
51
43
  // Used for native resources like Timers
52
44
  const restorers = captureRestorers()
53
45
  if (restorers.length > 0) contexts.set(asyncId, restorers)
@@ -88,17 +80,13 @@ const originalMethods = {
88
80
  all: OriginalPromise.all,
89
81
  }
90
82
 
91
-
92
-
93
83
  // Patch prototype
94
- OriginalPromise.prototype.then = function(onFulfilled, onRejected) {
84
+ OriginalPromise.prototype.then = function (onFulfilled, onRejected) {
95
85
  return originalMethods.then.call(this, wrap(onFulfilled), wrap(onRejected))
96
86
  } as any
97
- OriginalPromise.prototype.catch = function(onRejected) {
87
+ OriginalPromise.prototype.catch = function (onRejected) {
98
88
  return originalMethods.catch.call(this, wrap(onRejected))
99
89
  } as any
100
- OriginalPromise.prototype.finally = function(onFinally) {
90
+ OriginalPromise.prototype.finally = function (onFinally) {
101
91
  return originalMethods.finally.call(this, wrap(onFinally))
102
92
  } as any
103
-
104
-
package/src/eventful.ts CHANGED
@@ -2,76 +2,109 @@
2
2
  * Base type for event maps - all event handlers must be functions
3
3
  */
4
4
  export type EventsBase = Record<string, (...args: any[]) => void>
5
- /**
6
- * A type-safe event system that provides a clean API for event handling
7
- * @template Events - The event map defining event names and their handler signatures
8
- */
9
- export class Eventful<Events extends EventsBase> {
10
- readonly #events = new Map<keyof Events, ((...args: any[]) => void)[]>()
11
- readonly #hooks = [] as ((...args: any[]) => void)[]
12
5
 
13
- public hook(
14
- cb: <EventType extends keyof Events>(
15
- event: EventType,
16
- ...args: Parameters<Events[EventType]>
17
- ) => void
18
- ): () => void {
19
- if (!this.#hooks.includes(cb)) this.#hooks.push(cb)
20
- return () => {
21
- this.#hooks.splice(this.#hooks.indexOf(cb), 1)
22
- }
23
- }
6
+ const events = Symbol('events')
7
+ const hooks = Symbol('hooks')
24
8
 
25
- public on(events: Partial<Events>): void
26
- public on<EventType extends keyof Events>(event: EventType, cb: Events[EventType]): () => void
27
- public on<EventType extends keyof Events>(
28
- eventOrEvents: EventType | Partial<Events>,
29
- cb?: Events[EventType]
9
+ const eventBehavior = {
10
+ on<EventType extends keyof EventsBase>(
11
+ eventOrEvents: EventType | Partial<EventsBase>,
12
+ cb?: EventsBase[EventType]
30
13
  ): () => void {
31
14
  if (typeof eventOrEvents === 'object') {
32
- for (const e of Object.keys(eventOrEvents) as (keyof Events)[]) {
15
+ for (const e of Object.keys(eventOrEvents) as (keyof EventsBase)[]) {
33
16
  this.on(e, eventOrEvents[e]!)
34
17
  }
35
18
  } else if (cb !== undefined) {
36
- let callbacks = this.#events.get(eventOrEvents)
37
- if (!callbacks) {
38
- callbacks = []
39
- this.#events.set(eventOrEvents, callbacks)
40
- }
41
- callbacks.push(cb)
19
+ const callbacks = this[events].get(eventOrEvents) ?? new Set<EventsBase[EventType]>()
20
+ if (!callbacks.has(cb)) callbacks.add(cb)
21
+ this[events].set(eventOrEvents, callbacks)
42
22
  }
43
- // @ts-expect-error Generic case leads to generic case
44
23
  return () => this.off(eventOrEvents, cb)
45
- }
46
- public off(events: Partial<Events>): void
47
- public off<EventType extends keyof Events>(event: EventType, cb?: Events[EventType]): void
48
- public off<EventType extends keyof Events>(
49
- eventOrEvents: EventType | Partial<Events>,
50
- cb?: Events[EventType]
24
+ },
25
+ off<EventType extends keyof EventsBase>(
26
+ eventOrEvents: EventType | Partial<EventsBase>,
27
+ cb?: EventsBase[EventType]
51
28
  ): void {
52
29
  if (typeof eventOrEvents === 'object') {
53
- for (const e of Object.keys(eventOrEvents) as (keyof Events)[]) {
30
+ for (const e of Object.keys(eventOrEvents) as (keyof EventsBase)[]) {
54
31
  this.off(e, eventOrEvents[e])
55
32
  }
56
33
  } else if (cb !== null && cb !== undefined) {
57
- const callbacks = this.#events.get(eventOrEvents)
34
+ const callbacks = this[events].get(eventOrEvents)
58
35
  if (callbacks) {
59
- this.#events.set(
60
- eventOrEvents,
61
- callbacks.filter((c) => c !== cb)
62
- )
36
+ callbacks.delete(cb)
37
+ if (!callbacks.size) this[events].delete(eventOrEvents)
63
38
  }
64
39
  } else {
65
40
  // Remove all listeners for this event
66
- this.#events.delete(eventOrEvents)
41
+ this[events].delete(eventOrEvents)
67
42
  }
68
- }
69
- public emit<EventType extends keyof Events>(
43
+ },
44
+ emit<EventType extends keyof EventsBase>(
70
45
  event: EventType,
71
- ...args: Parameters<Events[EventType]>
46
+ ...args: Parameters<EventsBase[EventType]>
72
47
  ) {
73
- const callbacks = this.#events.get(event)
48
+ const callbacks = this[events].get(event)
74
49
  if (callbacks) for (const cb of callbacks) cb.apply(this, args)
75
- for (const cb of this.#hooks) cb.call(this, event, ...args)
50
+ for (const cb of this[hooks]) cb.call(this, event, ...args)
51
+ },
52
+ }
53
+
54
+ function perEvent(
55
+ eventful: Eventful<any>,
56
+ fct: (event: string, ...args: any[]) => void,
57
+ use?: 'use'
58
+ ) {
59
+ const cache = new Map<string, (...args: any[]) => any>()
60
+ return new Proxy(fct, {
61
+ get(target, prop: PropertyKey) {
62
+ if (typeof prop !== 'string') return target[prop]
63
+ if (use && !eventful[events].has(prop) && !eventful[hooks].size) return () => {}
64
+
65
+ // Return cached function or create and cache
66
+ let cached = cache.get(prop)
67
+ if (!cached) {
68
+ cached = (...args: any[]) => fct.apply(eventful, [prop, ...args])
69
+ cache.set(prop, cached)
70
+ }
71
+ return cached
72
+ },
73
+ })
74
+ }
75
+
76
+ /**
77
+ * A type-safe event system that provides a clean API for event handling
78
+ * @template Events - The event map defining event names and their handler signatures
79
+ */
80
+ export class Eventful<Events extends EventsBase> {
81
+ private readonly [events] = new Map<keyof Events, Set<(...args: any[]) => void>>()
82
+ private readonly [hooks] = new Set<(...args: any[]) => void>()
83
+
84
+ public hook(
85
+ cb: <EventType extends keyof Events>(
86
+ event: EventType,
87
+ ...args: Parameters<Events[EventType]>
88
+ ) => void
89
+ ): () => void {
90
+ this[hooks].add(cb)
91
+ return () => {
92
+ this[hooks].delete(cb)
93
+ }
76
94
  }
95
+
96
+ public on = perEvent(this, eventBehavior.on) as ((events: Partial<Events>) => void) &
97
+ (<EventType extends keyof Events>(event: EventType, cb: Events[EventType]) => () => void) & {
98
+ [event in keyof Events]: (cb: Events[event]) => () => void
99
+ }
100
+ public off = perEvent(this, eventBehavior.off) as ((events: Partial<Events>) => void) &
101
+ (<EventType extends keyof Events>(event: EventType, cb?: Events[EventType]) => void) & {
102
+ [event in keyof Events]: (cb?: Events[event]) => void
103
+ }
104
+
105
+ public emit = perEvent(this, eventBehavior.emit, 'use') as (<EventType extends keyof Events>(
106
+ event: EventType,
107
+ ...args: Parameters<Events[EventType]>
108
+ ) => void) &
109
+ Events
77
110
  }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Creates a flavored (extensible) version of a function with chainable property modifiers.
3
+ *
4
+ * Each property defined in `flavors` returns a new flavored function that transforms
5
+ * how the original function is called. This enables a fluent API where properties
6
+ * create specialized variants of the base function.
7
+ *
8
+ * @param fn - The base function to flavor
9
+ * @param flavors - Object defining the flavor properties (getters or methods)
10
+ * @returns A proxy of the function with the flavor properties attached
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * function greet(name: string, options?: { loud?: boolean }) {
15
+ * const greeting = `Hello, ${name}!`
16
+ * return options?.loud ? greeting.toUpperCase() : greeting
17
+ * }
18
+ *
19
+ * const flavoredGreet = flavored(greet, {
20
+ * get loud() {
21
+ * return createFlavor(this, (name, opts) => [name, { ...opts, loud: true }])
22
+ * }
23
+ * })
24
+ *
25
+ * flavoredGreet('World') // "Hello, World!"
26
+ * flavoredGreet.loud('World') // "HELLO, WORLD!"
27
+ * ```
28
+ */
29
+ import { named } from './utils'
30
+
31
+ /**
32
+ * Creates a flavored (extensible) version of a function with chainable property modifiers.
33
+ */
34
+ export function flavored<T extends (...args: any[]) => any, F extends Record<string, any>>(
35
+ fn: T,
36
+ flavors: F
37
+ ): T & F {
38
+ // Store flavors for recursive flavoring
39
+ ;(fn as any).flavors = flavors
40
+
41
+ return new Proxy(fn, {
42
+ get(target, prop, receiver) {
43
+ if (prop in flavors) {
44
+ return Reflect.get(flavors, prop, receiver)
45
+ }
46
+ return (target as any)[prop]
47
+ },
48
+ }) as T & F
49
+ }
50
+
51
+ /**
52
+ * Creates a new flavored function that transforms arguments before calling the base.
53
+ *
54
+ * @param fn - The base flavored function
55
+ * @param transform - Function that receives the original arguments and returns transformed arguments
56
+ * @returns A new flavored function with the transformation applied
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const loudGreet = createFlavor(greet, (name, opts) => [name, { ...opts, loud: true }])
61
+ * ```
62
+ */
63
+ export function createFlavor<T extends (...args: any[]) => any>(
64
+ fn: T,
65
+ transform: (...args: Parameters<T>) => Parameters<T>,
66
+ name?: string
67
+ ): T {
68
+ const fct = function flavorWrapper(this: any, ...args: Parameters<T>) {
69
+ return fn.apply(this, transform(...args))
70
+ }
71
+ if (name) named(name, fct)
72
+
73
+ return flavored(fct as any as T, (fn as any).flavors || {})
74
+ }
75
+
76
+ /**
77
+ * Creates a new flavored function that merges options objects.
78
+ * Useful when the last argument is an options object that should be merged.
79
+ *
80
+ * @param fn - The base flavored function
81
+ * @param defaultOptions - Options to merge with the provided options
82
+ * @returns A new flavored function with merged options
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const opaqueEffect = flavorOptions(effect, { opaque: true })
87
+ * const namedEffect = flavorOptions(effect, { name: 'myEffect' })
88
+ * ```
89
+ */
90
+ export function flavorOptions<T extends (...args: any[]) => any>(
91
+ fn: T,
92
+ defaultOptions: Record<string, any>,
93
+ name?: string
94
+ ): T {
95
+ const fct = function flavorOptionsWrapper(this: any, ...args: any[]) {
96
+ const lastArg = args[args.length - 1]
97
+ const mergedArgs =
98
+ lastArg !== undefined && typeof lastArg === 'object'
99
+ ? [...args.slice(0, -1), { ...defaultOptions, ...lastArg }]
100
+ : [...args, defaultOptions]
101
+ return fn.apply(this, mergedArgs)
102
+ }
103
+ if (name) named(name, fct)
104
+
105
+ return flavored(fct as any as T, (fn as any).flavors || {})
106
+ }
package/src/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- export * from './decorator';
2
- export * from './destroyable';
3
- export * from './eventful';
4
- export * from './indexable';
5
- export * from './iterableWeak';
6
- export * from './mixins';
7
- export * from './promiseChain';
8
- export * from './reactive';
9
- export * from './std-decorators';
10
- export * from './utils';
11
- export * from './zone';
12
- export * from './async';
1
+ export * from './decorator'
2
+ export * from './destroyable'
3
+ export * from './eventful'
4
+ export * from './indexable'
5
+ export * from './iterableWeak'
6
+ export * from './mixins'
7
+ export * from './promiseChain'
8
+ export * from './reactive'
9
+ export * from './std-decorators'
10
+ export * from './utils'
11
+ export * from './zone'
12
+ export * from './async'
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
+ export * from './async'
1
2
  export * from './decorator'
2
3
  export * from './destroyable'
3
4
  export * from './eventful'
5
+ export * from './flavored'
4
6
  export * from './indexable'
5
7
  export * from './iterableWeak'
6
8
  export * from './mixins'
@@ -9,7 +11,6 @@ export * from './reactive'
9
11
  export * from './std-decorators'
10
12
  export * from './utils'
11
13
  export * from './zone'
12
- export * from './async'
13
14
 
14
15
  import pkg from '../package.json'
15
16
 
@@ -31,7 +32,6 @@ if (globalScope) {
31
32
  let source = 'mutts/index'
32
33
  const viteEval = eval
33
34
  try {
34
- // @ts-ignore
35
35
  if (typeof __filename !== 'undefined') source = __filename
36
36
  else {
37
37
  // Using eval to avoid SyntaxError in CJS environments where import.meta is not allowed
@@ -14,7 +14,7 @@ import {
14
14
  type MutationRecord,
15
15
  type ObjectNode,
16
16
  type ReactivityGraph,
17
- } from './reactive/debug'
17
+ } from '../debug/debug'
18
18
  import { options, type ReactiveDebugInfo, ReactiveErrorCode } from './reactive/types'
19
19
 
20
20
  export {
@@ -25,29 +25,18 @@ export abstract class Indexer extends Array {
25
25
  dependant(this, i)
26
26
  return reactive(this[i])
27
27
  }
28
+ // Returns undefined intentionally: signals the proxy handler that notifications
29
+ // were already dispatched via touched(), preventing double notification
28
30
  set(i: number, value: any) {
29
31
  const added = i >= this.length
30
32
  this[i] = value
31
33
  touched(this, { type: 'set', prop: i }, index(i, { length: added }))
32
34
  }
33
- getLength() {
34
- dependant(this, 'length')
35
- return this.length
36
- }
37
- setLength(value: number) {
38
- const oldLength = this.length
39
- try {
40
- this.length = value
41
- } finally {
42
- touched(this, { type: 'set', prop: 'length' }, range(oldLength, value, { length: true }))
43
- }
44
- }
45
35
  }
46
36
  const indexLess = { get: FoolProof.get, set: FoolProof.set }
47
37
  Object.assign(FoolProof, {
48
38
  get(obj: any, prop: any, receiver: any) {
49
39
  if (obj instanceof Array && typeof prop === 'string') {
50
- if (prop === 'length') return Indexer.prototype.getLength.call(obj)
51
40
  const index = parseInt(prop)
52
41
  if (!Number.isNaN(index)) return Indexer.prototype.get.call(obj, index)
53
42
  }
@@ -55,7 +44,6 @@ Object.assign(FoolProof, {
55
44
  },
56
45
  set(obj: any, prop: any, value: any, receiver: any) {
57
46
  if (obj instanceof Array && typeof prop === 'string') {
58
- if (prop === 'length') return Indexer.prototype.setLength.call(obj, value)
59
47
  const index = parseInt(prop)
60
48
  if (!Number.isNaN(index)) return Indexer.prototype.set.call(obj, index, value)
61
49
  }
@@ -67,8 +55,18 @@ export abstract class ReactiveArray extends Array {
67
55
  toJSON() {
68
56
  return this
69
57
  }
70
- get [Symbol.toStringTag]() {
71
- return 'ReactiveArray'
58
+ get length(): number {
59
+ dependant(this, 'length')
60
+ return this.length
61
+ }
62
+ set length(value: number) {
63
+ const oldLength = this.length
64
+ if (oldLength === value) return
65
+ try {
66
+ this.length = value
67
+ } finally {
68
+ touched(this, { type: 'set', prop: 'length' }, range(oldLength, value, { length: true }))
69
+ }
72
70
  }
73
71
  // Safe array access with negative indices
74
72
  at(index: number): any {
@@ -103,17 +101,17 @@ export abstract class ReactiveArray extends Array {
103
101
  }
104
102
 
105
103
  // Iterator methods with reactivity tracking
106
- entries() {
104
+ entries(): any {
107
105
  dependant(this)
108
106
  return makeReactiveEntriesIterator(this.entries())
109
107
  }
110
108
 
111
- keys() {
109
+ keys(): any {
112
110
  dependant(this, 'length')
113
111
  return this.keys()
114
112
  }
115
113
 
116
- values() {
114
+ values(): any {
117
115
  dependant(this)
118
116
  return makeReactiveIterator(this.values())
119
117
  }
@@ -140,7 +138,7 @@ export abstract class ReactiveArray extends Array {
140
138
  const length = this.length
141
139
  let i = fromIndex === undefined ? 0 : fromIndex
142
140
  if (i < 0) i = Math.max(length + i, 0)
143
-
141
+
144
142
  const unwrappedSearch = unwrap(searchElement)
145
143
 
146
144
  for (; i < length; i++) {
@@ -171,7 +169,7 @@ export abstract class ReactiveArray extends Array {
171
169
  }
172
170
  }
173
171
 
174
- // If we scanned the whole relevant part and didn't find it, we depend on length
172
+ // If we scanned the whole relevant part and didn't find it, we depend on length
175
173
  // (because adding elements might shift indices or add the element)
176
174
  // Actually for lastIndexOf, if we start from end, length dependency is implicit in the start index calculation?
177
175
  // But if we return -1, it means we didn't find it.
@@ -360,7 +358,7 @@ export abstract class ReactiveArray extends Array {
360
358
  return false
361
359
  }
362
360
  // Side-effectful
363
- push(...items: any[]) {
361
+ push(...items: any[]): number {
364
362
  const oldLength = this.length
365
363
  try {
366
364
  return this.push(...items)
@@ -373,7 +371,7 @@ export abstract class ReactiveArray extends Array {
373
371
  }
374
372
  }
375
373
 
376
- pop() {
374
+ pop(): any {
377
375
  if (this.length === 0) return undefined
378
376
  try {
379
377
  return reactive(this.pop())
@@ -382,7 +380,7 @@ export abstract class ReactiveArray extends Array {
382
380
  }
383
381
  }
384
382
 
385
- shift() {
383
+ shift(): any {
386
384
  if (this.length === 0) return undefined
387
385
  try {
388
386
  return reactive(this.shift())
@@ -391,7 +389,7 @@ export abstract class ReactiveArray extends Array {
391
389
  }
392
390
  }
393
391
 
394
- unshift(...items: any[]) {
392
+ unshift(...items: any[]): number {
395
393
  try {
396
394
  return this.unshift(...items)
397
395
  } finally {
@@ -403,7 +401,7 @@ export abstract class ReactiveArray extends Array {
403
401
  }
404
402
  }
405
403
 
406
- splice(start: number, deleteCount?: number, ...items: any[]) {
404
+ splice(start: number, deleteCount?: number, ...items: any[]): any[] {
407
405
  const oldLength = this.length
408
406
 
409
407
  // Normalize start index
@@ -435,7 +433,7 @@ export abstract class ReactiveArray extends Array {
435
433
  }
436
434
  }
437
435
 
438
- reverse() {
436
+ reverse(): any[] {
439
437
  try {
440
438
  return this.reverse()
441
439
  } finally {
@@ -443,7 +441,7 @@ export abstract class ReactiveArray extends Array {
443
441
  }
444
442
  }
445
443
 
446
- sort(compareFn?: (a: any, b: any) => number) {
444
+ sort(compareFn?: (a: any, b: any) => number): this {
447
445
  compareFn = compareFn || ((a, b) => a.toString().localeCompare(b.toString()))
448
446
  try {
449
447
  return this.sort((a, b) => compareFn(reactive(a), reactive(b))) as any
@@ -452,7 +450,7 @@ export abstract class ReactiveArray extends Array {
452
450
  }
453
451
  }
454
452
 
455
- fill(value: any, start?: number, end?: number) {
453
+ fill(value: any, start?: number, end?: number): this {
456
454
  const len = this.length
457
455
  let k = start === undefined ? 0 : start
458
456
  if (k < 0) k = Math.max(len + k, 0)
@@ -473,7 +471,7 @@ export abstract class ReactiveArray extends Array {
473
471
  }
474
472
  }
475
473
 
476
- copyWithin(target: number, start: number, end?: number) {
474
+ copyWithin(target: number, start: number, end?: number): this {
477
475
  try {
478
476
  if (end === undefined) return this.copyWithin(target, start) as any
479
477
  return this.copyWithin(target, start, end) as any
@@ -495,13 +493,8 @@ export abstract class ReactiveArray extends Array {
495
493
  const count = Math.min(final - from, len - to)
496
494
 
497
495
  if (count > 0) {
498
- touched(
499
- this,
500
- { type: 'bunch', method: 'copyWithin' },
501
- range(to, to + count - 1)
502
- )
496
+ touched(this, { type: 'bunch', method: 'copyWithin' }, range(to, to + count - 1))
503
497
  }
504
-
505
498
  }
506
499
  }
507
500
  }