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