mutts 1.0.9 → 1.0.10

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 (204) hide show
  1. package/README.md +58 -33
  2. package/dist/browser.cjs +1371 -1206
  3. package/dist/browser.cjs.map +1 -1
  4. package/dist/browser.d.ts +2 -1392
  5. package/dist/browser.dev.cjs +110 -0
  6. package/dist/browser.dev.cjs.map +1 -0
  7. package/dist/browser.dev.d.ts +2 -0
  8. package/dist/browser.dev.esm.js +5 -0
  9. package/dist/browser.dev.esm.js.map +1 -0
  10. package/dist/browser.esm.js +6 -87
  11. package/dist/browser.esm.js.map +1 -1
  12. package/dist/chunks/{async-browser-lvzLOCgk.cjs → async-browser-BU_IfxYD.cjs} +4 -85
  13. package/dist/chunks/async-browser-BU_IfxYD.cjs.map +1 -0
  14. package/dist/chunks/{async-node-C3DeIb0y.cjs → async-node-3PrbVAbB.cjs} +3 -1
  15. package/dist/chunks/async-node-3PrbVAbB.cjs.map +1 -0
  16. package/dist/chunks/{index-2vea86wD.esm.js → index-CaaQQlPJ.esm.js} +1116 -953
  17. package/dist/chunks/index-CaaQQlPJ.esm.js.map +1 -0
  18. package/dist/chunks/node-nKJBk8iJ.esm.js +96 -0
  19. package/dist/chunks/node-nKJBk8iJ.esm.js.map +1 -0
  20. package/dist/chunks/{index-VTO-b2vR.cjs → proxy-Dtg-bJ3T.cjs} +2285 -2983
  21. package/dist/chunks/proxy-Dtg-bJ3T.cjs.map +1 -0
  22. package/dist/chunks/{index-CtA2AWl3.esm.js → proxy-r7lARftl.esm.js} +2273 -2948
  23. package/dist/chunks/proxy-r7lARftl.esm.js.map +1 -0
  24. package/dist/debug.cjs +973 -28
  25. package/dist/debug.cjs.map +1 -1
  26. package/dist/debug.d.ts +36 -37
  27. package/dist/debug.esm.js +949 -1
  28. package/dist/debug.esm.js.map +1 -1
  29. package/dist/devtools/manifest.json +1 -1
  30. package/dist/devtools/panel.html +1 -1
  31. package/dist/devtools/panel.js +107 -94
  32. package/dist/devtools/panel.js.map +1 -1
  33. package/dist/index.d.ts +1447 -1
  34. package/dist/mutts.umd.js +7167 -1
  35. package/dist/mutts.umd.js.map +1 -1
  36. package/dist/mutts.umd.min.js +1 -1
  37. package/dist/mutts.umd.min.js.map +1 -1
  38. package/dist/node.cjs +62 -60
  39. package/dist/node.cjs.map +1 -1
  40. package/dist/node.d.ts +2 -2
  41. package/dist/node.dev.cjs +110 -0
  42. package/dist/node.dev.cjs.map +1 -0
  43. package/dist/node.dev.d.ts +2 -0
  44. package/dist/node.dev.esm.js +6 -0
  45. package/dist/node.dev.esm.js.map +1 -0
  46. package/dist/node.esm.js +4 -97
  47. package/dist/node.esm.js.map +1 -1
  48. package/dist/{types-DaHFfhlN.d.ts → types-W5vD6m2n.d.ts} +99 -44
  49. package/docs/ai/api-reference.md +11 -18
  50. package/docs/ai/manual.md +308 -230
  51. package/docs/reactive/advanced.md +312 -2
  52. package/docs/reactive/attend.md +1 -2
  53. package/docs/reactive/collections.md +21 -39
  54. package/docs/reactive/core.md +7 -37
  55. package/docs/reactive/debugging.md +117 -10
  56. package/docs/reactive/error-handling.md +10 -10
  57. package/docs/reactive/resource.md +125 -0
  58. package/docs/reactive/scan.md +32 -1
  59. package/docs/reactive.md +3 -2
  60. package/docs/utils.md +70 -0
  61. package/package.json +59 -30
  62. package/dist/chunks/async-browser-lvzLOCgk.cjs.map +0 -1
  63. package/dist/chunks/async-node-C3DeIb0y.cjs.map +0 -1
  64. package/dist/chunks/index-2vea86wD.esm.js.map +0 -1
  65. package/dist/chunks/index-CtA2AWl3.esm.js.map +0 -1
  66. package/dist/chunks/index-VTO-b2vR.cjs.map +0 -1
  67. package/dist/debug/debug.d.ts +0 -122
  68. package/dist/debug/debug.d.ts.map +0 -1
  69. package/dist/debug/index.d.ts +0 -4
  70. package/dist/debug/index.d.ts.map +0 -1
  71. package/dist/debug/lineage-panel.d.ts +0 -5
  72. package/dist/debug/lineage-panel.d.ts.map +0 -1
  73. package/dist/debug/lineage.d.ts +0 -79
  74. package/dist/debug/lineage.d.ts.map +0 -1
  75. package/dist/src/async/browser.d.ts +0 -2
  76. package/dist/src/async/browser.d.ts.map +0 -1
  77. package/dist/src/async/index.d.ts +0 -19
  78. package/dist/src/async/index.d.ts.map +0 -1
  79. package/dist/src/async/node.d.ts +0 -2
  80. package/dist/src/async/node.d.ts.map +0 -1
  81. package/dist/src/decorator.d.ts +0 -106
  82. package/dist/src/decorator.d.ts.map +0 -1
  83. package/dist/src/destroyable.d.ts +0 -87
  84. package/dist/src/destroyable.d.ts.map +0 -1
  85. package/dist/src/entry-browser.d.ts +0 -3
  86. package/dist/src/entry-browser.d.ts.map +0 -1
  87. package/dist/src/entry-node.d.ts +0 -3
  88. package/dist/src/entry-node.d.ts.map +0 -1
  89. package/dist/src/eventful.d.ts +0 -20
  90. package/dist/src/eventful.d.ts.map +0 -1
  91. package/dist/src/flavored.d.ts +0 -33
  92. package/dist/src/flavored.d.ts.map +0 -1
  93. package/dist/src/index.d.ts +0 -14
  94. package/dist/src/index.d.ts.map +0 -1
  95. package/dist/src/indexable.d.ts +0 -243
  96. package/dist/src/indexable.d.ts.map +0 -1
  97. package/dist/src/introspection.d.ts +0 -27
  98. package/dist/src/introspection.d.ts.map +0 -1
  99. package/dist/src/iterableWeak.d.ts +0 -53
  100. package/dist/src/iterableWeak.d.ts.map +0 -1
  101. package/dist/src/mixins.d.ts +0 -25
  102. package/dist/src/mixins.d.ts.map +0 -1
  103. package/dist/src/promiseChain.d.ts +0 -20
  104. package/dist/src/promiseChain.d.ts.map +0 -1
  105. package/dist/src/reactive/array.d.ts +0 -48
  106. package/dist/src/reactive/array.d.ts.map +0 -1
  107. package/dist/src/reactive/buffer.d.ts +0 -120
  108. package/dist/src/reactive/buffer.d.ts.map +0 -1
  109. package/dist/src/reactive/change.d.ts +0 -29
  110. package/dist/src/reactive/change.d.ts.map +0 -1
  111. package/dist/src/reactive/deep-touch.d.ts +0 -28
  112. package/dist/src/reactive/deep-touch.d.ts.map +0 -1
  113. package/dist/src/reactive/deep-watch-state.d.ts +0 -25
  114. package/dist/src/reactive/deep-watch-state.d.ts.map +0 -1
  115. package/dist/src/reactive/deep-watch.d.ts +0 -20
  116. package/dist/src/reactive/deep-watch.d.ts.map +0 -1
  117. package/dist/src/reactive/describe.d.ts +0 -12
  118. package/dist/src/reactive/describe.d.ts.map +0 -1
  119. package/dist/src/reactive/effect-context.d.ts +0 -34
  120. package/dist/src/reactive/effect-context.d.ts.map +0 -1
  121. package/dist/src/reactive/effects.d.ts +0 -164
  122. package/dist/src/reactive/effects.d.ts.map +0 -1
  123. package/dist/src/reactive/index.d.ts +0 -19
  124. package/dist/src/reactive/index.d.ts.map +0 -1
  125. package/dist/src/reactive/map.d.ts +0 -28
  126. package/dist/src/reactive/map.d.ts.map +0 -1
  127. package/dist/src/reactive/memoize.d.ts +0 -28
  128. package/dist/src/reactive/memoize.d.ts.map +0 -1
  129. package/dist/src/reactive/non-reactive-state.d.ts +0 -9
  130. package/dist/src/reactive/non-reactive-state.d.ts.map +0 -1
  131. package/dist/src/reactive/non-reactive.d.ts +0 -11
  132. package/dist/src/reactive/non-reactive.d.ts.map +0 -1
  133. package/dist/src/reactive/project.d.ts +0 -40
  134. package/dist/src/reactive/project.d.ts.map +0 -1
  135. package/dist/src/reactive/proxy-state.d.ts +0 -8
  136. package/dist/src/reactive/proxy-state.d.ts.map +0 -1
  137. package/dist/src/reactive/proxy.d.ts +0 -23
  138. package/dist/src/reactive/proxy.d.ts.map +0 -1
  139. package/dist/src/reactive/record.d.ts +0 -115
  140. package/dist/src/reactive/record.d.ts.map +0 -1
  141. package/dist/src/reactive/register.d.ts +0 -125
  142. package/dist/src/reactive/register.d.ts.map +0 -1
  143. package/dist/src/reactive/registry.d.ts +0 -21
  144. package/dist/src/reactive/registry.d.ts.map +0 -1
  145. package/dist/src/reactive/set.d.ts +0 -26
  146. package/dist/src/reactive/set.d.ts.map +0 -1
  147. package/dist/src/reactive/tracking.d.ts +0 -7
  148. package/dist/src/reactive/tracking.d.ts.map +0 -1
  149. package/dist/src/reactive/types.d.ts +0 -424
  150. package/dist/src/reactive/types.d.ts.map +0 -1
  151. package/dist/src/reactive/watch.d.ts +0 -48
  152. package/dist/src/reactive/watch.d.ts.map +0 -1
  153. package/dist/src/std-decorators.d.ts +0 -45
  154. package/dist/src/std-decorators.d.ts.map +0 -1
  155. package/dist/src/utils.d.ts +0 -49
  156. package/dist/src/utils.d.ts.map +0 -1
  157. package/dist/src/zone.d.ts +0 -40
  158. package/dist/src/zone.d.ts.map +0 -1
  159. package/docs/reactive/describe.md +0 -85
  160. package/docs/reactive/project.md +0 -93
  161. package/src/async/browser.ts +0 -323
  162. package/src/async/index.ts +0 -27
  163. package/src/async/node.ts +0 -92
  164. package/src/decorator.ts +0 -272
  165. package/src/destroyable.ts +0 -199
  166. package/src/entry-browser.ts +0 -5
  167. package/src/entry-node.ts +0 -5
  168. package/src/eventful.ts +0 -110
  169. package/src/flavored.ts +0 -106
  170. package/src/index.d.ts +0 -12
  171. package/src/index.ts +0 -64
  172. package/src/indexable.ts +0 -526
  173. package/src/introspection.ts +0 -59
  174. package/src/iterableWeak.ts +0 -233
  175. package/src/mixins.ts +0 -123
  176. package/src/promiseChain.ts +0 -110
  177. package/src/reactive/array.ts +0 -500
  178. package/src/reactive/buffer.ts +0 -328
  179. package/src/reactive/change.ts +0 -131
  180. package/src/reactive/deep-touch.ts +0 -273
  181. package/src/reactive/deep-watch-state.ts +0 -82
  182. package/src/reactive/deep-watch.ts +0 -171
  183. package/src/reactive/describe.ts +0 -39
  184. package/src/reactive/effect-context.ts +0 -83
  185. package/src/reactive/effects.ts +0 -1434
  186. package/src/reactive/index.ts +0 -72
  187. package/src/reactive/map.ts +0 -142
  188. package/src/reactive/memoize.ts +0 -186
  189. package/src/reactive/non-reactive-state.ts +0 -49
  190. package/src/reactive/non-reactive.ts +0 -43
  191. package/src/reactive/project.md +0 -107
  192. package/src/reactive/project.ts +0 -430
  193. package/src/reactive/proxy-state.ts +0 -27
  194. package/src/reactive/proxy.ts +0 -282
  195. package/src/reactive/record.ts +0 -181
  196. package/src/reactive/register.ts +0 -538
  197. package/src/reactive/registry.ts +0 -72
  198. package/src/reactive/set.ts +0 -117
  199. package/src/reactive/tracking.ts +0 -41
  200. package/src/reactive/types.ts +0 -520
  201. package/src/reactive/watch.ts +0 -180
  202. package/src/std-decorators.ts +0 -256
  203. package/src/utils.ts +0 -300
  204. package/src/zone.ts +0 -142
@@ -1,282 +0,0 @@
1
- import { decorator } from '../decorator'
2
- import { mixin } from '../mixins'
3
- import { FoolProof, isOwnAccessor } from '../utils'
4
- import { touched1 } from './change'
5
- import { notifyPropertyChange } from './deep-touch'
6
- import {
7
- addBackReference,
8
- bubbleUpChange,
9
- needsBackReferences,
10
- objectsWithDeepWatchers,
11
- removeBackReference,
12
- } from './deep-watch-state'
13
- import { untracked } from './effects'
14
- import { absent, isNonReactive } from './non-reactive-state'
15
- import {
16
- getExistingProxy,
17
- proxyToObject,
18
- storeProxyRelationship,
19
- trackProxyObject,
20
- unwrap,
21
- } from './proxy-state'
22
- import { dependant } from './tracking'
23
- import {
24
- allProps,
25
- nativeReactive,
26
- nonReactiveMark,
27
- options,
28
- ReactiveError,
29
- ReactiveErrorCode,
30
- unreactiveProperties,
31
- } from './types'
32
- export const metaProtos = new WeakMap()
33
-
34
- const hasReentry: any[] = []
35
- const reactiveHandlers = {
36
- [Symbol.toStringTag]: 'MutTs Reactive',
37
- get(obj: any, prop: PropertyKey, receiver: any) {
38
- if (obj && typeof obj === 'object' && prop !== Symbol.toStringTag) {
39
- const metaProto = metaProtos.get(obj.constructor)
40
- if (metaProto && Object.hasOwn(metaProto, prop)) {
41
- const desc = Object.getOwnPropertyDescriptor(metaProto, prop)!
42
- if (desc.get) {
43
- if (!Object.hasOwn(obj, prop)) return desc.get.call(obj)
44
- // For own properties (e.g., array length): only override if writable/configurable
45
- const ownDesc = Object.getOwnPropertyDescriptor(obj, prop)!
46
- if (ownDesc.configurable || ownDesc.writable || ownDesc.get)
47
- return desc.get.call(obj)
48
- } else if (!Object.hasOwn(obj, prop))
49
- return (...args: any[]) => desc.value.apply(obj, args)
50
- }
51
- }
52
- if (prop === nonReactiveMark) return false
53
- const unwrappedObj = unwrap(obj)
54
- // Check if this property is marked as unreactive
55
- if (unwrappedObj[unreactiveProperties]?.has(prop) || typeof prop === 'symbol')
56
- return FoolProof.get(obj, prop, receiver)
57
-
58
- // Check if property exists and if it's an own property (cached for later use)
59
- const hasProp = Reflect.has(receiver, prop)
60
- const isOwnProp = hasProp && Object.hasOwn(receiver, prop)
61
- const isInheritedAccess = hasProp && !isOwnProp
62
-
63
- // For accessor properties, check the unwrapped object to see if it's an accessor
64
- // This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
65
- const shouldIgnoreAccessor =
66
- options.ignoreAccessors &&
67
- isOwnProp &&
68
- (isOwnAccessor(receiver, prop) || isOwnAccessor(unwrappedObj, prop))
69
-
70
- // Depend if...
71
- if (
72
- !hasProp ||
73
- (!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
74
- !shouldIgnoreAccessor)
75
- )
76
- dependant(obj, prop)
77
-
78
- // Watch the whole prototype chain when requested or for null-proto objects
79
- if (isInheritedAccess && (!options.instanceMembers || !(obj instanceof Object))) {
80
- let current = reactiveObject(Object.getPrototypeOf(obj))
81
- while (current && current !== Object.prototype) {
82
- dependant(current, prop)
83
- if (Object.hasOwn(current, prop)) break
84
- let next = reactiveObject(Object.getPrototypeOf(current))
85
- if (next === current) {
86
- next = reactiveObject(Object.getPrototypeOf(unwrap(current)))
87
- }
88
- current = next
89
- }
90
- }
91
- const value = FoolProof.get(obj, prop, receiver)
92
- if (typeof value === 'object' && value !== null) {
93
- const reactiveValue = reactiveObject(value)
94
-
95
- // Only create back-references if this object needs them
96
- if (needsBackReferences(obj)) {
97
- addBackReference(reactiveValue, obj, prop)
98
- }
99
-
100
- return reactiveValue
101
- }
102
- return value
103
- },
104
- set(obj: any, prop: PropertyKey, value: any, receiver: any): boolean {
105
- // Read old value directly from unwrapped object to avoid triggering dependency tracking
106
- const unwrappedObj = unwrap(obj)
107
- const unwrappedReceiver = unwrap(receiver)
108
-
109
- // Check if this property is marked as unreactive
110
- if (unwrappedObj[unreactiveProperties]?.has(prop) || unwrappedObj !== unwrappedReceiver)
111
- return FoolProof.set(obj, prop, value, receiver)
112
- const newValue = unwrap(value)
113
- // metaProto setter dispatch (e.g., reactive array length)
114
- if (obj && typeof obj === 'object' && prop !== Symbol.toStringTag) {
115
- const metaProto = obj.constructor && metaProtos.get(obj.constructor)
116
- if (metaProto && Object.hasOwn(metaProto, prop)) {
117
- const desc = Object.getOwnPropertyDescriptor(metaProto, prop)!
118
- if (desc.set) {
119
- desc.set.call(obj, newValue)
120
- return true
121
- }
122
- }
123
- }
124
- // Read old value, using withEffect(undefined, ...) for getter-only accessors to avoid
125
- // breaking memoization dependency tracking during SET operations
126
- let oldVal = absent
127
- if (Reflect.has(unwrappedReceiver, prop)) {
128
- // Check descriptor on both receiver and target to handle proxy cases
129
- const receiverDesc = Object.getOwnPropertyDescriptor(unwrappedReceiver, prop)
130
- const targetDesc = Object.getOwnPropertyDescriptor(unwrappedObj, prop)
131
- const desc = receiverDesc || targetDesc
132
- // We *need* to use `receiver` and not `unwrappedObj` here, otherwise we break
133
- // the dependency tracking for memoized getters
134
- if (desc?.get && !desc?.set) {
135
- oldVal = untracked(() => Reflect.get(unwrappedObj, prop, receiver))
136
- } else {
137
- oldVal = untracked(() => Reflect.get(unwrappedObj, prop, receiver))
138
- }
139
- }
140
- if (objectsWithDeepWatchers.has(obj)) {
141
- if (typeof oldVal === 'object' && oldVal !== null) {
142
- removeBackReference(oldVal, obj, prop)
143
- }
144
- if (typeof newValue === 'object' && newValue !== null) {
145
- const reactiveValue = reactiveObject(newValue)
146
- addBackReference(reactiveValue, obj, prop)
147
- }
148
- }
149
-
150
- if (oldVal !== newValue) {
151
- // For getter-only accessors, Reflect.set() may fail, but we still return true
152
- // to avoid throwing errors. Only proceed with change notifications if set succeeded.
153
- if (FoolProof.set(obj, prop, newValue, receiver)) {
154
- notifyPropertyChange(obj, prop, oldVal, newValue, oldVal !== absent)
155
- }
156
- }
157
- return true
158
- },
159
- has(obj: any, prop: PropertyKey): boolean {
160
- if (hasReentry.includes(obj))
161
- throw new ReactiveError(
162
- `[reactive] Circular dependency detected in 'has' check for property '${String(prop)}'`,
163
- {
164
- code: ReactiveErrorCode.CycleDetected,
165
- cycle: [], // We don't have the full cycle here, but we know it involves obj
166
- }
167
- )
168
- hasReentry.push(obj)
169
- dependant(obj, prop)
170
- const rv = Reflect.has(obj, prop)
171
- hasReentry.pop()
172
- return rv
173
- },
174
- deleteProperty(obj: any, prop: PropertyKey): boolean {
175
- if (!Object.hasOwn(obj, prop)) return false
176
-
177
- const oldVal = (obj as any)[prop]
178
-
179
- // Remove back-references if this object has deep watchers
180
- if (objectsWithDeepWatchers.has(obj) && typeof oldVal === 'object' && oldVal !== null) {
181
- removeBackReference(oldVal, obj, prop)
182
- }
183
-
184
- delete (obj as any)[prop]
185
- touched1(obj, { type: 'del', prop }, prop)
186
-
187
- // Bubble up changes if this object has deep watchers
188
- if (objectsWithDeepWatchers.has(obj)) {
189
- bubbleUpChange(obj, { type: 'del', prop })
190
- }
191
-
192
- return true
193
- },
194
- ownKeys(obj: any): (string | symbol)[] {
195
- dependant(obj, allProps)
196
- return Reflect.ownKeys(obj)
197
- },
198
- } as const
199
-
200
- const reactiveClasses = new WeakSet<Function>()
201
-
202
- // Create the ReactiveBase mixin
203
- /**
204
- * Base mixin for reactive classes that provides proper constructor reactivity
205
- * Solves constructor reactivity issues in complex inheritance trees
206
- */
207
- export const ReactiveBase = mixin((base) => {
208
- class ReactiveMixin extends base {
209
- constructor(...args: any[]) {
210
- super(...args)
211
- // Only apply reactive transformation if the class is marked with @reactive
212
- // This allows the mixin to work properly with method inheritance
213
- // biome-ignore lint/correctness/noConstructorReturn: This is the whole point here
214
- return reactiveClasses.has(new.target) ? reactive(this) : this
215
- }
216
- }
217
- return ReactiveMixin
218
- })
219
- function reactiveObject<T>(anyTarget: T): T {
220
- if (!anyTarget || typeof anyTarget !== 'object') return anyTarget
221
- const target = anyTarget as any
222
- // If target is already a proxy, return it
223
- if (isNonReactive(target)) return target as T
224
- const isProxy = proxyToObject.has(target)
225
- if (isProxy) return target as T
226
-
227
- // If we already have a proxy for this object, return it (optimized: get returns undefined if not found)
228
- const existing = getExistingProxy(target)
229
- if (existing !== undefined) return existing as T
230
-
231
- const proxied =
232
- nativeReactive in target && !(target instanceof target[nativeReactive])
233
- ? new target[nativeReactive](target)
234
- : target
235
- if (proxied !== target) trackProxyObject(proxied, target)
236
- const proxy = new Proxy(proxied, reactiveHandlers)
237
-
238
- // Store the relationships
239
- storeProxyRelationship(target, proxy)
240
- return proxy as T
241
- }
242
-
243
- /**
244
- * Main decorator for making classes reactive
245
- * Automatically makes class instances reactive when created
246
- */
247
- export const reactive = decorator({
248
- class(original) {
249
- if (original.prototype instanceof ReactiveBase) {
250
- reactiveClasses.add(original)
251
- return original
252
- }
253
-
254
- class Reactive extends original {
255
- constructor(...args: any[]) {
256
- super(...args)
257
- if (new.target !== Reactive && !reactiveClasses.has(new.target))
258
- options.warn(
259
- `${(original as any).name} has been inherited by ${this.constructor.name} that is not reactive.
260
- @reactive decorator must be applied to the leaf class OR classes have to extend ReactiveBase.`
261
- )
262
- // biome-ignore lint/correctness/noConstructorReturn: This is the whole point here
263
- return reactive(this)
264
- }
265
- }
266
- Object.defineProperty(Reactive, 'name', {
267
- value: `Reactive<${original.name}>`,
268
- })
269
- return Reactive as any
270
- },
271
- get(original: any) {
272
- return reactiveObject(original)
273
- },
274
- default: reactiveObject,
275
- })
276
-
277
- /**
278
- * Gets the original, non-reactive object from a reactive proxy
279
- * @param proxy - The reactive proxy
280
- * @returns The original object
281
- */
282
- export { isReactive, objectToProxy, proxyToObject, unwrap } from './proxy-state'
@@ -1,181 +0,0 @@
1
- import { FoolProof } from '../utils'
2
- import { attend } from './buffer'
3
- import { touched1 } from './change'
4
- import { cleanedBy } from './effect-context'
5
- import { reactive } from './proxy'
6
- import {
7
- cleanup,
8
- type EffectCloser,
9
- type ScopedCallback,
10
- } from './types'
11
-
12
- /**
13
- * Provides type-safe access to a source object's property within the organized callback.
14
- * @template Source - The type of the source object
15
- * @template Key - The type of the property key in the source object
16
- */
17
- export type OrganizedAccess<Source extends Record<PropertyKey, any>, Key extends keyof Source> = {
18
- /** The property key being accessed */
19
- readonly key: Key
20
-
21
- /**
22
- * Gets the current value of the property from the source object
23
- * @returns The current value of the property
24
- */
25
- get(): Source[Key]
26
-
27
- /**
28
- * Updates the property value in the source object
29
- * @param value - The new value to set
30
- * @returns {boolean} True if the update was successful
31
- */
32
- set(value: Source[Key]): boolean
33
-
34
- /**
35
- * The current value of the property (equivalent to using get()/set() directly)
36
- */
37
- value: Source[Key]
38
- }
39
-
40
- /**
41
- * Callback function type for the organized function that processes each source property.
42
- * @template Source - The type of the source object
43
- * @template Target - The type of the target object
44
- */
45
- export type OrganizedCallback<Source extends Record<PropertyKey, any>, Target extends object> = <
46
- Key extends keyof Source,
47
- >(
48
- /**
49
- * Accessor object for the current source property
50
- */
51
- access: OrganizedAccess<Source, Key>,
52
-
53
- /**
54
- * The target object where organized data will be stored
55
- */
56
- target: Target
57
- ) => EffectCloser | undefined
58
-
59
- /**
60
- * The result type of the organized function, combining the target object with cleanup capability.
61
- * @template Target - The type of the target object
62
- */
63
- export type OrganizedResult<Target extends object> = Target & {
64
- /**
65
- * Cleanup function to dispose of all reactive bindings created by organized().
66
- * This is automatically called when the effect that created the organized binding is disposed.
67
- */
68
- [cleanup]: ScopedCallback
69
- }
70
-
71
- /**
72
- * Organizes a source object's properties into a target object using a callback function.
73
- * This creates a reactive mapping between source properties and a target object,
74
- * automatically handling property additions, updates, and removals.
75
- *
76
- * @template Source - The type of the source object
77
- * @template Target - The type of the target object (defaults to Record<PropertyKey, any>)
78
- *
79
- * @param {Source} source - The source object to organize
80
- * @param {OrganizedCallback<Source, Target>} apply - Callback function that defines how each source property is mapped to the target
81
- * @param {Target} [baseTarget={}] - Optional base target object to use (will be made reactive if not already)
82
- *
83
- * @returns {OrganizedResult<Target>} The target object with cleanup capability
84
- *
85
- * @example
86
- * // Organize user permissions into role-based access
87
- * const user = reactive({ isAdmin: true, canEdit: false });
88
- * const permissions = organized(
89
- * user,
90
- * (access, target) => {
91
- * if (access.key === 'isAdmin') {
92
- * target.hasFullAccess = access.value;
93
- * }
94
- * target[`can${access.key.charAt(0).toUpperCase() + access.key.slice(1)}`] = access.value;
95
- * }
96
- * );
97
- *
98
- * @example
99
- * // Transform object structure with cleanup
100
- * const source = reactive({ firstName: 'John', lastName: 'Doe' });
101
- * const formatted = organized(
102
- * source,
103
- * (access, target) => {
104
- * if (access.key === 'firstName' || access.key === 'lastName') {
105
- * target.fullName = `${source.firstName} ${source.lastName}`.trim();
106
- * }
107
- * }
108
- * );
109
- *
110
- * @example
111
- * // Using with cleanup in a component
112
- * effect(() => {
113
- * const data = fetchData();
114
- * const organizedData = organized(data, (access, target) => {
115
- * // Transform data
116
- * });
117
- *
118
- * // The cleanup will be called automatically when the effect is disposed
119
- * return () => organizedData[cleanup]();
120
- * });
121
- */
122
- export function organized<
123
- Source extends Record<PropertyKey, any>,
124
- Target extends object = Record<PropertyKey, any>,
125
- >(
126
- source: Source,
127
- apply: OrganizedCallback<Source, Target>,
128
- baseTarget: Target = {} as Target
129
- ): OrganizedResult<Target> {
130
- const observedSource = reactive(source) as Source
131
- const target = reactive(baseTarget) as Target
132
-
133
- const stop = attend(
134
- () => {
135
- const keys: PropertyKey[] = []
136
- for (const key in observedSource) keys.push(key)
137
- return keys
138
- },
139
- (key) => {
140
- const sourceKey = key as keyof Source
141
- const accessBase = {
142
- key: sourceKey,
143
- get: () => FoolProof.get(observedSource, sourceKey, observedSource),
144
- set: (value: Source[typeof sourceKey]) =>
145
- FoolProof.set(observedSource, sourceKey, value, observedSource),
146
- }
147
- Object.defineProperty(accessBase, 'value', {
148
- get: accessBase.get,
149
- set: accessBase.set,
150
- configurable: true,
151
- enumerable: true,
152
- })
153
- return apply(accessBase as OrganizedAccess<Source, typeof sourceKey>, target)
154
- }
155
- )
156
-
157
- return cleanedBy(target, stop) as OrganizedResult<Target>
158
- }
159
-
160
- /**
161
- * Organizes a property on a target object
162
- * Shortcut for defineProperty/delete with touched signal
163
- * @param target - The target object
164
- * @param property - The property to organize
165
- * @param access - The access object
166
- * @returns The property descriptor
167
- */
168
- export function organize<T>(
169
- target: object,
170
- property: PropertyKey,
171
- access: { get?(): T; set?(value: T): boolean }
172
- ) {
173
- Object.defineProperty(target, property, {
174
- get: access.get,
175
- set: access.set,
176
- configurable: true,
177
- enumerable: true,
178
- })
179
- touched1(target, { type: 'set', prop: property }, property)
180
- return () => delete (target as any)[property]
181
- }