mutts 1.0.2 → 1.0.4

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 (104) hide show
  1. package/README.md +14 -6
  2. package/dist/chunks/{_tslib-C-cuVLvZ.js → _tslib-BgjropY9.js} +9 -1
  3. package/dist/chunks/_tslib-BgjropY9.js.map +1 -0
  4. package/dist/chunks/{_tslib-CMEnd0VE.esm.js → _tslib-Mzh1rNsX.esm.js} +9 -2
  5. package/dist/chunks/_tslib-Mzh1rNsX.esm.js.map +1 -0
  6. package/dist/chunks/{decorator-D4DU97Zg.js → decorator-DLvrD0UF.js} +42 -19
  7. package/dist/chunks/decorator-DLvrD0UF.js.map +1 -0
  8. package/dist/chunks/{decorator-GnHw1Az7.esm.js → decorator-DqiszP7i.esm.js} +42 -19
  9. package/dist/chunks/decorator-DqiszP7i.esm.js.map +1 -0
  10. package/dist/chunks/index-79Kk8D6e.esm.js +4857 -0
  11. package/dist/chunks/index-79Kk8D6e.esm.js.map +1 -0
  12. package/dist/chunks/index-GRBSx0mB.js +4908 -0
  13. package/dist/chunks/index-GRBSx0mB.js.map +1 -0
  14. package/dist/decorator.esm.js +1 -1
  15. package/dist/decorator.js +1 -1
  16. package/dist/destroyable.d.ts +1 -1
  17. package/dist/destroyable.esm.js +1 -1
  18. package/dist/destroyable.esm.js.map +1 -1
  19. package/dist/destroyable.js +1 -1
  20. package/dist/destroyable.js.map +1 -1
  21. package/dist/devtools/devtools.html +9 -0
  22. package/dist/devtools/devtools.js +5 -0
  23. package/dist/devtools/devtools.js.map +1 -0
  24. package/dist/devtools/manifest.json +8 -0
  25. package/dist/devtools/panel.css +72 -0
  26. package/dist/devtools/panel.html +31 -0
  27. package/dist/devtools/panel.js +13048 -0
  28. package/dist/devtools/panel.js.map +1 -0
  29. package/dist/eventful.esm.js +1 -1
  30. package/dist/eventful.js +1 -1
  31. package/dist/index.d.ts +18 -63
  32. package/dist/index.esm.js +4 -4
  33. package/dist/index.js +37 -11
  34. package/dist/index.js.map +1 -1
  35. package/dist/indexable.d.ts +187 -1
  36. package/dist/indexable.esm.js +197 -3
  37. package/dist/indexable.esm.js.map +1 -1
  38. package/dist/indexable.js +198 -2
  39. package/dist/indexable.js.map +1 -1
  40. package/dist/mutts.umd.js +1 -1
  41. package/dist/mutts.umd.js.map +1 -1
  42. package/dist/mutts.umd.min.js +1 -1
  43. package/dist/mutts.umd.min.js.map +1 -1
  44. package/dist/promiseChain.esm.js.map +1 -1
  45. package/dist/promiseChain.js.map +1 -1
  46. package/dist/reactive.d.ts +602 -97
  47. package/dist/reactive.esm.js +3 -3
  48. package/dist/reactive.js +32 -10
  49. package/dist/reactive.js.map +1 -1
  50. package/dist/std-decorators.esm.js +1 -1
  51. package/dist/std-decorators.js +1 -1
  52. package/docs/ai/api-reference.md +133 -0
  53. package/docs/ai/manual.md +105 -0
  54. package/docs/iterableWeak.md +646 -0
  55. package/docs/reactive/advanced.md +1280 -0
  56. package/docs/reactive/collections.md +767 -0
  57. package/docs/reactive/core.md +973 -0
  58. package/docs/reactive.md +21 -9545
  59. package/package.json +18 -5
  60. package/src/decorator.ts +266 -0
  61. package/src/destroyable.ts +199 -0
  62. package/src/eventful.ts +77 -0
  63. package/src/index.d.ts +9 -0
  64. package/src/index.ts +9 -0
  65. package/src/indexable.ts +484 -0
  66. package/src/introspection.ts +59 -0
  67. package/src/iterableWeak.ts +233 -0
  68. package/src/mixins.ts +123 -0
  69. package/src/promiseChain.ts +110 -0
  70. package/src/reactive/array.ts +414 -0
  71. package/src/reactive/change.ts +134 -0
  72. package/src/reactive/debug.ts +517 -0
  73. package/src/reactive/deep-touch.ts +268 -0
  74. package/src/reactive/deep-watch-state.ts +82 -0
  75. package/src/reactive/deep-watch.ts +168 -0
  76. package/src/reactive/effect-context.ts +94 -0
  77. package/src/reactive/effects.ts +1345 -0
  78. package/src/reactive/index.ts +76 -0
  79. package/src/reactive/interface.ts +223 -0
  80. package/src/reactive/map.ts +171 -0
  81. package/src/reactive/mapped.ts +130 -0
  82. package/src/reactive/memoize.ts +107 -0
  83. package/src/reactive/non-reactive-state.ts +49 -0
  84. package/src/reactive/non-reactive.ts +43 -0
  85. package/src/reactive/project.project.md +93 -0
  86. package/src/reactive/project.ts +335 -0
  87. package/src/reactive/proxy-state.ts +27 -0
  88. package/src/reactive/proxy.ts +289 -0
  89. package/src/reactive/record.ts +196 -0
  90. package/src/reactive/register.ts +421 -0
  91. package/src/reactive/set.ts +144 -0
  92. package/src/reactive/tracking.ts +101 -0
  93. package/src/reactive/types.ts +358 -0
  94. package/src/reactive/zone.ts +208 -0
  95. package/src/std-decorators.ts +217 -0
  96. package/src/utils.ts +117 -0
  97. package/dist/chunks/_tslib-C-cuVLvZ.js.map +0 -1
  98. package/dist/chunks/_tslib-CMEnd0VE.esm.js.map +0 -1
  99. package/dist/chunks/decorator-D4DU97Zg.js.map +0 -1
  100. package/dist/chunks/decorator-GnHw1Az7.esm.js.map +0 -1
  101. package/dist/chunks/index-DBScoeCX.esm.js +0 -1960
  102. package/dist/chunks/index-DBScoeCX.esm.js.map +0 -1
  103. package/dist/chunks/index-DOTmXL89.js +0 -1983
  104. package/dist/chunks/index-DOTmXL89.js.map +0 -1
@@ -0,0 +1,289 @@
1
+ import { decorator } from '../decorator'
2
+ import { mixin } from '../mixins'
3
+ import { isOwnAccessor, ReflectGet, ReflectSet } 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 { withEffect } from './effect-context'
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
+ prototypeForwarding,
29
+ ReactiveError,
30
+ ReactiveErrorCode,
31
+ unreactiveProperties,
32
+ } from './types'
33
+
34
+ const hasReentry: any[] = []
35
+ const reactiveHandlers = {
36
+ [Symbol.toStringTag]: 'MutTs Reactive',
37
+ get(obj: any, prop: PropertyKey, receiver: any) {
38
+ if (prop === nonReactiveMark) return false
39
+ const unwrappedObj = unwrap(obj)
40
+ // Check if this property is marked as unreactive
41
+ if (unwrappedObj[unreactiveProperties]?.has(prop) || typeof prop === 'symbol')
42
+ return ReflectGet(obj, prop, receiver)
43
+
44
+ // Special-case: array wrappers use prototype forwarding + numeric accessors.
45
+ // With options.instanceMembers=true, inherited reads are normally not tracked, which breaks
46
+ // reactivity for array indices/length (they appear inherited on the proxy).
47
+ const isArrayCase =
48
+ prototypeForwarding in obj &&
49
+ // biome-ignore lint/suspicious/useIsArray: This is the whole point here
50
+ obj[prototypeForwarding] instanceof Array &&
51
+ typeof prop === 'string' &&
52
+ (prop === 'length' || !Number.isNaN(Number(prop)))
53
+ if (isArrayCase) {
54
+ dependant(obj, prop === 'length' ? 'length' : Number(prop))
55
+ }
56
+ // Check if property exists and if it's an own property (cached for later use)
57
+ const hasProp = Reflect.has(receiver, prop)
58
+ const isOwnProp = hasProp && Object.hasOwn(receiver, prop)
59
+ const isInheritedAccess = hasProp && !isOwnProp
60
+
61
+ // For accessor properties, check the unwrapped object to see if it's an accessor
62
+ // This ensures ignoreAccessors works correctly even after operations like Object.setPrototypeOf
63
+ const shouldIgnoreAccessor =
64
+ options.ignoreAccessors &&
65
+ isOwnProp &&
66
+ (isOwnAccessor(receiver, prop) || isOwnAccessor(unwrappedObj, prop))
67
+
68
+ // Depend if...
69
+ if (
70
+ !hasProp ||
71
+ (!(options.instanceMembers && isInheritedAccess && obj instanceof Object) &&
72
+ !shouldIgnoreAccessor)
73
+ )
74
+ dependant(obj, prop)
75
+
76
+ // Watch the whole prototype chain when requested or for null-proto objects
77
+ if (isInheritedAccess && (!options.instanceMembers || !(obj instanceof Object))) {
78
+ let current = reactiveObject(Object.getPrototypeOf(obj))
79
+ while (current && current !== Object.prototype) {
80
+ dependant(current, prop)
81
+ if (Object.hasOwn(current, prop)) break
82
+ let next = reactiveObject(Object.getPrototypeOf(current))
83
+ if (next === current) {
84
+ next = reactiveObject(Object.getPrototypeOf(unwrap(current)))
85
+ }
86
+ current = next
87
+ }
88
+ }
89
+ const value = ReflectGet(obj, prop, receiver)
90
+ if (typeof value === 'object' && value !== null) {
91
+ const reactiveValue = reactiveObject(value)
92
+
93
+ // Only create back-references if this object needs them
94
+ if (needsBackReferences(obj)) {
95
+ addBackReference(reactiveValue, obj, prop)
96
+ }
97
+
98
+ return reactiveValue
99
+ }
100
+ return value
101
+ },
102
+ set(obj: any, prop: PropertyKey, value: any, receiver: any): boolean {
103
+ // Read old value directly from unwrapped object to avoid triggering dependency tracking
104
+ const unwrappedObj = unwrap(obj)
105
+ const unwrappedReceiver = unwrap(receiver)
106
+
107
+ // Check if this property is marked as unreactive
108
+ if (unwrappedObj[unreactiveProperties]?.has(prop) || unwrappedObj !== unwrappedReceiver)
109
+ return ReflectSet(obj, prop, value, receiver)
110
+ // Really specific case for when Array is forwarder, in order to let it manage the reactivity
111
+ const isArrayCase =
112
+ prototypeForwarding in obj &&
113
+ // biome-ignore lint/suspicious/useIsArray: This is the whole point here
114
+ obj[prototypeForwarding] instanceof Array &&
115
+ (!Number.isNaN(Number(prop)) || prop === 'length')
116
+ const newValue = unwrap(value)
117
+
118
+ if (isArrayCase) {
119
+ ;(obj as any)[prop] = newValue
120
+ return true
121
+ }
122
+ // Read old value, using withEffect(undefined, ...) for getter-only accessors to avoid
123
+ // breaking memoization dependency tracking during SET operations
124
+ let oldVal = absent
125
+ if (Reflect.has(unwrappedReceiver, prop)) {
126
+ // Check descriptor on both receiver and target to handle proxy cases
127
+ const receiverDesc = Object.getOwnPropertyDescriptor(unwrappedReceiver, prop)
128
+ const targetDesc = Object.getOwnPropertyDescriptor(unwrappedObj, prop)
129
+ const desc = receiverDesc || targetDesc
130
+ // If it's a getter-only accessor (has getter but no setter), read without tracking
131
+ // to avoid breaking memoization invalidation when the getter calls memoized functions
132
+ if (desc?.get && !desc?.set) {
133
+ oldVal = withEffect(undefined, () => Reflect.get(unwrappedObj, prop, unwrappedReceiver))
134
+ } else {
135
+ oldVal = Reflect.get(unwrappedObj, prop, unwrappedReceiver)
136
+ }
137
+ }
138
+ if (objectsWithDeepWatchers.has(obj)) {
139
+ if (typeof oldVal === 'object' && oldVal !== null) {
140
+ removeBackReference(oldVal, obj, prop)
141
+ }
142
+ if (typeof newValue === 'object' && newValue !== null) {
143
+ const reactiveValue = reactiveObject(newValue)
144
+ addBackReference(reactiveValue, obj, prop)
145
+ }
146
+ }
147
+
148
+ if (oldVal !== newValue) {
149
+ // For getter-only accessors, Reflect.set() may fail, but we still return true
150
+ // to avoid throwing errors. Only proceed with change notifications if set succeeded.
151
+ if (ReflectSet(obj, prop, newValue, receiver)) {
152
+ notifyPropertyChange(obj, prop, oldVal, newValue, oldVal !== absent)
153
+ }
154
+ }
155
+ return true
156
+ },
157
+ has(obj: any, prop: PropertyKey): boolean {
158
+ if (hasReentry.includes(obj))
159
+ throw new ReactiveError(
160
+ `[reactive] Circular dependency detected in 'has' check for property '${String(prop)}'`,
161
+ {
162
+ code: ReactiveErrorCode.CycleDetected,
163
+ cycle: [], // We don't have the full cycle here, but we know it involves obj
164
+ }
165
+ )
166
+ hasReentry.push(obj)
167
+ dependant(obj, prop)
168
+ const rv = Reflect.has(obj, prop)
169
+ hasReentry.pop()
170
+ return rv
171
+ },
172
+ deleteProperty(obj: any, prop: PropertyKey): boolean {
173
+ if (!Object.hasOwn(obj, prop)) return false
174
+
175
+ const oldVal = (obj as any)[prop]
176
+
177
+ // Remove back-references if this object has deep watchers
178
+ if (objectsWithDeepWatchers.has(obj) && typeof oldVal === 'object' && oldVal !== null) {
179
+ removeBackReference(oldVal, obj, prop)
180
+ }
181
+
182
+ delete (obj as any)[prop]
183
+ touched1(obj, { type: 'del', prop }, prop)
184
+
185
+ // Bubble up changes if this object has deep watchers
186
+ if (objectsWithDeepWatchers.has(obj)) {
187
+ bubbleUpChange(obj, { type: 'del', prop })
188
+ }
189
+
190
+ return true
191
+ },
192
+ getPrototypeOf(obj: any): object | null {
193
+ if (prototypeForwarding in obj) return obj[prototypeForwarding]
194
+ return Object.getPrototypeOf(obj)
195
+ },
196
+ setPrototypeOf(obj: any, proto: object | null): boolean {
197
+ if (prototypeForwarding in obj) return false
198
+ Object.setPrototypeOf(obj, proto)
199
+ return true
200
+ },
201
+ ownKeys(obj: any): (string | symbol)[] {
202
+ dependant(obj, allProps)
203
+ return Reflect.ownKeys(obj)
204
+ },
205
+ } as const
206
+
207
+ const reactiveClasses = new WeakSet<Function>()
208
+
209
+ // Create the ReactiveBase mixin
210
+ /**
211
+ * Base mixin for reactive classes that provides proper constructor reactivity
212
+ * Solves constructor reactivity issues in complex inheritance trees
213
+ */
214
+ export const ReactiveBase = mixin((base) => {
215
+ class ReactiveMixin extends base {
216
+ constructor(...args: any[]) {
217
+ super(...args)
218
+ // Only apply reactive transformation if the class is marked with @reactive
219
+ // This allows the mixin to work properly with method inheritance
220
+ // biome-ignore lint/correctness/noConstructorReturn: This is the whole point here
221
+ return reactiveClasses.has(new.target) ? reactive(this) : this
222
+ }
223
+ }
224
+ return ReactiveMixin
225
+ })
226
+ function reactiveObject<T>(anyTarget: T): T {
227
+ if (!anyTarget || typeof anyTarget !== 'object') return anyTarget
228
+ const target = anyTarget as any
229
+ // If target is already a proxy, return it
230
+ if (isNonReactive(target)) return target as T
231
+ const isProxy = proxyToObject.has(target)
232
+ if (isProxy) return target as T
233
+
234
+ // If we already have a proxy for this object, return it (optimized: get returns undefined if not found)
235
+ const existing = getExistingProxy(target)
236
+ if (existing !== undefined) return existing as T
237
+
238
+ const proxied =
239
+ nativeReactive in target && !(target instanceof target[nativeReactive])
240
+ ? new target[nativeReactive](target)
241
+ : target
242
+ if (proxied !== target) trackProxyObject(proxied, target)
243
+ const proxy = new Proxy(proxied, reactiveHandlers)
244
+
245
+ // Store the relationships
246
+ storeProxyRelationship(target, proxy)
247
+ return proxy as T
248
+ }
249
+
250
+ /**
251
+ * Main decorator for making classes reactive
252
+ * Automatically makes class instances reactive when created
253
+ */
254
+ export const reactive = decorator({
255
+ class(original) {
256
+ if (original.prototype instanceof ReactiveBase) {
257
+ reactiveClasses.add(original)
258
+ return original
259
+ }
260
+
261
+ class Reactive extends original {
262
+ constructor(...args: any[]) {
263
+ super(...args)
264
+ if (new.target !== Reactive && !reactiveClasses.has(new.target))
265
+ options.warn(
266
+ `${(original as any).name} has been inherited by ${this.constructor.name} that is not reactive.
267
+ @reactive decorator must be applied to the leaf class OR classes have to extend ReactiveBase.`
268
+ )
269
+ // biome-ignore lint/correctness/noConstructorReturn: This is the whole point here
270
+ return reactive(this)
271
+ }
272
+ }
273
+ Object.defineProperty(Reactive, 'name', {
274
+ value: `Reactive<${original.name}>`,
275
+ })
276
+ return Reactive as any
277
+ },
278
+ get(original: any) {
279
+ return reactiveObject(original)
280
+ },
281
+ default: reactiveObject,
282
+ })
283
+
284
+ /**
285
+ * Gets the original, non-reactive object from a reactive proxy
286
+ * @param proxy - The reactive proxy
287
+ * @returns The original object
288
+ */
289
+ export { isReactive, objectToProxy, proxyToObject, unwrap } from './proxy-state'
@@ -0,0 +1,196 @@
1
+ import { ReflectGet, ReflectSet } 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: () => ReflectGet(observedSource, sourceKey, observedSource),
151
+ set: (value: Source[typeof sourceKey]) =>
152
+ ReflectSet(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
+ }