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,49 @@
1
+ import { nativeReactive, nonReactiveMark } from './types'
2
+
3
+ export const nonReactiveObjects = new WeakSet<object>()
4
+ export const immutables = new Set<(tested: any) => boolean>()
5
+ export const absent = Symbol('absent')
6
+
7
+ function markNonReactive<T extends object[]>(...obj: T): T[0] {
8
+ for (const o of obj) {
9
+ try {
10
+ Object.defineProperty(o, nonReactiveMark, {
11
+ value: true,
12
+ writable: false,
13
+ enumerable: false,
14
+ configurable: false,
15
+ })
16
+ } catch {}
17
+ if (!(nonReactiveMark in (o as object))) nonReactiveObjects.add(o as object)
18
+ }
19
+ return obj[0]
20
+ }
21
+
22
+ export function nonReactiveClass<T extends (new (...args: any[]) => any)[]>(...cls: T): T[0] {
23
+ for (const c of cls) if (c) (c.prototype as any)[nonReactiveMark] = true
24
+ return cls[0]
25
+ }
26
+
27
+ export function isNonReactive(obj: any): boolean {
28
+ if (obj === null || typeof obj !== 'object') return true
29
+ if (nonReactiveObjects.has(obj)) return true
30
+ if ((obj as any)[nonReactiveMark]) return true
31
+ for (const fn of immutables) if (fn(obj)) return true
32
+ return false
33
+ }
34
+
35
+ export function registerNativeReactivity(
36
+ originalClass: new (...args: any[]) => any,
37
+ reactiveClass: new (...args: any[]) => any
38
+ ) {
39
+ originalClass.prototype[nativeReactive] = reactiveClass
40
+ nonReactiveClass(reactiveClass)
41
+ }
42
+
43
+ nonReactiveClass(Date, RegExp, Error, Promise, Function)
44
+ if (typeof window !== 'undefined') {
45
+ markNonReactive(window, document)
46
+ nonReactiveClass(Node, Element, HTMLElement, EventTarget)
47
+ }
48
+
49
+ export { markNonReactive as nonReactive }
@@ -0,0 +1,43 @@
1
+ import {
2
+ absent,
3
+ immutables,
4
+ isNonReactive,
5
+ nonReactive,
6
+ nonReactiveClass,
7
+ nonReactiveObjects,
8
+ registerNativeReactivity,
9
+ } from './non-reactive-state'
10
+ import { reactive } from './proxy'
11
+
12
+ /**
13
+ * Converts an iterator to a generator that yields reactive values
14
+ */
15
+ export function* makeReactiveIterator<T>(iterator: Iterator<T>): Generator<T> {
16
+ let result = iterator.next()
17
+ while (!result.done) {
18
+ yield reactive(result.value)
19
+ result = iterator.next()
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Converts an iterator of key-value pairs to a generator that yields reactive key-value pairs
25
+ */
26
+ export function* makeReactiveEntriesIterator<K, V>(iterator: Iterator<[K, V]>): Generator<[K, V]> {
27
+ let result = iterator.next()
28
+ while (!result.done) {
29
+ const [key, value] = result.value
30
+ yield [reactive(key), reactive(value)]
31
+ result = iterator.next()
32
+ }
33
+ }
34
+
35
+ export {
36
+ absent,
37
+ immutables,
38
+ isNonReactive,
39
+ nonReactive,
40
+ nonReactiveClass,
41
+ nonReactiveObjects,
42
+ registerNativeReactivity,
43
+ }
@@ -0,0 +1,93 @@
1
+ # Reactive Register Memoization Notes
2
+
3
+ ## Background
4
+
5
+ - Register entries are stored in a reactive `Map`. Updating an entry via `Map.set` marks the entire value as changed.
6
+ - Memoized computations that read through `map.get(key)` re-execute fully when the entry changes, even if only a nested property is touched.
7
+ - The goal for rendering lists in a JSX/HTML engine is to avoid rebuilding DOM nodes; only the affected properties should update.
8
+
9
+ ## Evolution: From `organized` to `project`
10
+
11
+ ### Initial State
12
+
13
+ - `memoize` caches results but invalidates on `Map.set`, so large effects still re-run.
14
+ - `organized` (designed for `Record` sources) creates per-key effects so downstream work reruns only for the touched key; this matches the desired behaviour.
15
+ - **Gap:** `organized` operates on plain objects: key enumeration relies on property iteration and `ReflectGet/Set`. Registers and other keyed collections (`Map`, `Register`, custom stores) need the same per-entry orchestration without converting to records.
16
+
17
+ ### Completed Evolution: `project` Implementation
18
+
19
+ We implemented `project` as a generalized transformation helper that works across arrays, records, and maps:
20
+
21
+ **Key Design Decisions:**
22
+ - **Unified API:** Single `project` function with runtime dispatch to `project.array`, `project.record`, or `project.map` based on source type.
23
+ - **Access Pattern:** Callback receives a `ProjectAccess` object with `get()`, `set()`, `key`, `source`, and `value` (computed property) - similar to `organized` but returning a value instead of an effect.
24
+ - **Automatic Target Creation:** The function always creates its own reactive target container (array, record, or map) - no `baseTarget` parameter needed.
25
+ - **Per-Key Effects:** Each source key/index gets its own reactive effect that recomputes only when that specific entry changes, enabling granular updates for rendering pipelines.
26
+
27
+ **Current API:**
28
+ ```typescript
29
+ project.array(source: readonly T[], apply: (access, target) => U): ProjectResult<U[]>
30
+ project.record(source: Record<K, T>, apply: (access, target) => U): ProjectResult<Record<K, U>>
31
+ project.map(source: Map<K, T>, apply: (access, target) => U): ProjectResult<Map<K, U>>
32
+ ```
33
+
34
+ **Current Behavior:**
35
+ - Eager computation: all entries are computed immediately when keys are present.
36
+ - One-way transformation: callback only handles "get" (read) operations; no write-back support.
37
+ - Mutable results: returned arrays/records/maps are fully mutable.
38
+ - `ProjectAccess.old` exposes the previously computed result for each entry, enabling incremental updates and state preservation.
39
+
40
+ ## Future Evolutions
41
+
42
+ ### Bidirectional Transformation (Set Callback)
43
+
44
+ **Goal:** Support writing back to the source through the projected object.
45
+
46
+ **Implementation:**
47
+ - Add optional second callback parameter: `set: (access, newValue, target) => void | boolean`
48
+ - When `set` is provided, mutations to the projected object trigger the set callback.
49
+ - The callback receives the same `ProjectAccess` object plus the new value, allowing it to update the source.
50
+ - If `set` returns `false` or throws, the mutation is rejected.
51
+
52
+ **API Impact:**
53
+ ```typescript
54
+ project.array(
55
+ source: readonly T[],
56
+ apply: (access, target) => U,
57
+ set?: (access, newValue: U, target) => void | boolean
58
+ ): ProjectResult<U[]>
59
+ ```
60
+
61
+ ### Readonly Results When No Set Callback
62
+
63
+ **Goal:** Make projected objects readonly when no write-back is supported.
64
+
65
+ **Implementation:**
66
+ - When `set` callback is not provided, wrap the result in a readonly proxy or use read-only array/record types.
67
+ - Prevents accidental mutations that would have no effect on the source.
68
+ - Type system should reflect readonly nature in return types.
69
+
70
+ **API Impact:**
71
+ - Return type becomes `ProjectResult<readonly U[]>` or similar when `set` is omitted.
72
+ - Arrays use `ReactiveReadOnlyArray` (already exists in `mapped.ts`).
73
+ - Records and maps need readonly wrappers or proxy-based protection.
74
+
75
+ ## Implementation Notes for AI Agents
76
+
77
+ **Current State:**
78
+ - `project` is fully functional for arrays, records, maps, and registers with eager, one-way transformation.
79
+ - Tests cover per-key reactivity, key addition/removal, automatic helper selection, and `access.old` value propagation.
80
+ - The implementation follows the same per-key effect pattern as `organized` but returns computed values.
81
+
82
+ **Future Work:**
83
+ - Lazy computing requires careful effect lifecycle management to avoid memory leaks.
84
+ - Bidirectional support needs to handle edge cases (concurrent reads/writes, validation).
85
+ - Readonly enforcement should align with existing `ReactiveReadOnlyArray` patterns where possible.
86
+ - Consider whether these features should be opt-in via options object or separate function variants.
87
+
88
+ **Related Files:**
89
+ - `src/reactive/project.ts` - Main implementation
90
+ - `src/reactive/mapped.ts` - Reference for `ReactiveReadOnlyArray` pattern
91
+ - `src/reactive/record.ts` - Reference for `organized` pattern (different use case)
92
+ - `tests/reactive/project.test.ts` - Test coverage
93
+
@@ -0,0 +1,335 @@
1
+ import { ReflectGet, ReflectSet } from '../utils'
2
+ import { effect, untracked } from './effects'
3
+ import { cleanedBy, cleanup } from './interface'
4
+ import { reactive } from './proxy'
5
+ import { Register } from './register'
6
+ import { type ScopedCallback } from './types'
7
+
8
+ type ProjectOldValue<Target> = Target extends readonly (infer Item)[]
9
+ ? Item
10
+ : Target extends Map<any, infer Item>
11
+ ? Item
12
+ : Target extends Record<PropertyKey, infer Item>
13
+ ? Item
14
+ : unknown
15
+
16
+ export type ProjectAccess<SourceValue, Key, SourceType, Target> = {
17
+ readonly key: Key
18
+ get(): SourceValue
19
+ set(value: SourceValue): boolean
20
+ readonly source: SourceType
21
+ readonly old?: ProjectOldValue<Target>
22
+ value: SourceValue
23
+ }
24
+
25
+ type BivariantProjectCallback<Args extends any[], Return> = {
26
+ bivarianceHack(...args: Args): Return
27
+ }['bivarianceHack']
28
+
29
+ export type ProjectCallback<
30
+ SourceValue,
31
+ Key,
32
+ Target extends object,
33
+ SourceType,
34
+ Result,
35
+ > = BivariantProjectCallback<[ProjectAccess<SourceValue, Key, SourceType, Target>, Target], Result>
36
+
37
+ export type ProjectResult<Target extends object> = Target & { [cleanup]: ScopedCallback }
38
+
39
+ function defineAccessValue<Access extends { get(): unknown; set(value: unknown): boolean }>(
40
+ access: Access
41
+ ) {
42
+ Object.defineProperty(access, 'value', {
43
+ get: access.get,
44
+ set: access.set,
45
+ configurable: true,
46
+ enumerable: true,
47
+ })
48
+ }
49
+
50
+ function makeCleanup<Result extends object>(
51
+ target: Result,
52
+ effectMap: Map<unknown, ScopedCallback>,
53
+ onDispose: () => void
54
+ ): ProjectResult<Result> {
55
+ return cleanedBy(target, () => {
56
+ onDispose()
57
+ for (const stop of effectMap.values()) stop?.()
58
+ effectMap.clear()
59
+ }) as ProjectResult<Result>
60
+ }
61
+
62
+ function projectArray<SourceValue, ResultValue>(
63
+ source: readonly SourceValue[],
64
+ apply: ProjectCallback<SourceValue, number, ResultValue[], readonly SourceValue[], ResultValue>
65
+ ): ProjectResult<ResultValue[]> {
66
+ const observedSource = reactive(source) as readonly SourceValue[]
67
+ const target = reactive([] as ResultValue[])
68
+ const indexEffects = new Map<number, ScopedCallback>()
69
+
70
+ function normalizeTargetLength(length: number) {
71
+ ReflectSet(target as unknown as object, 'length', length, target)
72
+ }
73
+
74
+ function disposeIndex(index: number) {
75
+ const stopEffect = indexEffects.get(index)
76
+ if (stopEffect) {
77
+ indexEffects.delete(index)
78
+ stopEffect()
79
+ Reflect.deleteProperty(target as unknown as object, index)
80
+ }
81
+ }
82
+
83
+ const cleanupLength = effect(function projectArrayLengthEffect({ ascend }) {
84
+ const length = observedSource.length
85
+ normalizeTargetLength(length)
86
+ const existing = Array.from(indexEffects.keys())
87
+ for (let i = 0; i < length; i++) {
88
+ if (indexEffects.has(i)) continue
89
+ ascend(() => {
90
+ const index = i
91
+ const stop = effect(function projectArrayIndexEffect() {
92
+ const previous = untracked(() => target[index])
93
+ const accessBase = {
94
+ key: index,
95
+ source: observedSource,
96
+ get: () => ReflectGet(observedSource as any, index, observedSource),
97
+ set: (value: SourceValue) =>
98
+ ReflectSet(observedSource as any, index, value, observedSource),
99
+ old: previous,
100
+ } as ProjectAccess<SourceValue, number, readonly SourceValue[], ResultValue[]>
101
+ defineAccessValue(accessBase)
102
+ const produced = apply(accessBase, target)
103
+ target[index] = produced
104
+ })
105
+ indexEffects.set(i, stop)
106
+ })
107
+ }
108
+ for (const index of existing) if (index >= length) disposeIndex(index)
109
+ })
110
+
111
+ return makeCleanup(target, indexEffects, () => cleanupLength())
112
+ }
113
+
114
+ function projectRegister<Key extends PropertyKey, SourceValue, ResultValue>(
115
+ source: Register<SourceValue, Key>,
116
+ apply: ProjectCallback<
117
+ SourceValue,
118
+ Key,
119
+ Map<Key, ResultValue>,
120
+ Register<SourceValue, Key>,
121
+ ResultValue
122
+ >
123
+ ): ProjectResult<Map<Key, ResultValue>> {
124
+ const observedSource = reactive(source) as Register<SourceValue, Key>
125
+ const rawTarget = new Map<Key, ResultValue>()
126
+ const target = reactive(rawTarget) as Map<Key, ResultValue>
127
+ const keyEffects = new Map<Key, ScopedCallback>()
128
+
129
+ function disposeKey(key: Key) {
130
+ const stopEffect = keyEffects.get(key)
131
+ if (stopEffect) {
132
+ stopEffect()
133
+ keyEffects.delete(key)
134
+ target.delete(key)
135
+ }
136
+ }
137
+
138
+ const cleanupKeys = effect(function projectRegisterEffect({ ascend }) {
139
+ const keys = new Set<Key>()
140
+ for (const key of observedSource.mapKeys()) keys.add(key)
141
+
142
+ for (const key of keys) {
143
+ if (keyEffects.has(key)) continue
144
+ ascend(() => {
145
+ const stop = effect(function projectRegisterKeyEffect() {
146
+ const previous = untracked(() => target.get(key))
147
+ const accessBase = {
148
+ key,
149
+ source: observedSource,
150
+ get: () => observedSource.get(key) as SourceValue,
151
+ set: (value: SourceValue) => {
152
+ observedSource.set(key, value)
153
+ return true
154
+ },
155
+ old: previous,
156
+ } as ProjectAccess<SourceValue, Key, Register<SourceValue, Key>, Map<Key, ResultValue>>
157
+ defineAccessValue(accessBase)
158
+ const produced = apply(accessBase, target)
159
+ target.set(key, produced)
160
+ })
161
+ keyEffects.set(key, stop)
162
+ })
163
+ }
164
+
165
+ for (const key of Array.from(keyEffects.keys())) if (!keys.has(key)) disposeKey(key)
166
+ })
167
+
168
+ return makeCleanup(target, keyEffects, () => cleanupKeys())
169
+ }
170
+
171
+ function projectRecord<Source extends Record<PropertyKey, any>, ResultValue>(
172
+ source: Source,
173
+ apply: ProjectCallback<
174
+ Source[keyof Source],
175
+ keyof Source,
176
+ Record<keyof Source, ResultValue>,
177
+ Source,
178
+ ResultValue
179
+ >
180
+ ): ProjectResult<Record<keyof Source, ResultValue>> {
181
+ const observedSource = reactive(source) as Source
182
+ const target = reactive({} as Record<keyof Source, ResultValue>)
183
+ const keyEffects = new Map<PropertyKey, ScopedCallback>()
184
+
185
+ function disposeKey(key: PropertyKey) {
186
+ const stopEffect = keyEffects.get(key)
187
+ if (stopEffect) {
188
+ stopEffect()
189
+ keyEffects.delete(key)
190
+ Reflect.deleteProperty(target as Record<PropertyKey, unknown>, key)
191
+ }
192
+ }
193
+
194
+ const cleanupKeys = effect(function projectRecordEffect({ ascend }) {
195
+ const keys = new Set<PropertyKey>()
196
+ for (const key in observedSource) keys.add(key)
197
+ const observed = Reflect.ownKeys(observedSource)
198
+ for (const key of observed) keys.add(key)
199
+
200
+ for (const key of keys) {
201
+ if (keyEffects.has(key)) continue
202
+ ascend(() => {
203
+ const stop = effect(function projectRecordKeyEffect() {
204
+ const sourceKey = key as keyof Source
205
+ const previous = untracked(
206
+ () => (target as Record<PropertyKey, ResultValue | undefined>)[key]
207
+ )
208
+ const accessBase = {
209
+ key: sourceKey,
210
+ source: observedSource,
211
+ get: () => ReflectGet(observedSource, sourceKey, observedSource),
212
+ set: (value: Source[typeof sourceKey]) =>
213
+ ReflectSet(observedSource, sourceKey, value, observedSource),
214
+ old: previous,
215
+ } as ProjectAccess<
216
+ Source[typeof sourceKey],
217
+ keyof Source,
218
+ Source,
219
+ Record<keyof Source, ResultValue>
220
+ >
221
+ defineAccessValue(accessBase)
222
+ const produced = apply(accessBase, target)
223
+ ;(target as any)[sourceKey] = produced
224
+ })
225
+ keyEffects.set(key, stop)
226
+ })
227
+ }
228
+
229
+ for (const key of Array.from(keyEffects.keys())) if (!keys.has(key)) disposeKey(key)
230
+ })
231
+
232
+ return makeCleanup(target, keyEffects, () => cleanupKeys())
233
+ }
234
+
235
+ function projectMap<Key, Value, ResultValue>(
236
+ source: Map<Key, Value>,
237
+ apply: ProjectCallback<Value, Key, Map<Key, ResultValue>, Map<Key, Value>, ResultValue>
238
+ ): ProjectResult<Map<Key, ResultValue>> {
239
+ const observedSource = reactive(source) as Map<Key, Value>
240
+ const rawTarget = new Map<Key, ResultValue>()
241
+ const target = reactive(rawTarget) as Map<Key, ResultValue>
242
+ const keyEffects = new Map<Key, ScopedCallback>()
243
+
244
+ function disposeKey(key: Key) {
245
+ const stopEffect = keyEffects.get(key)
246
+ if (stopEffect) {
247
+ stopEffect()
248
+ keyEffects.delete(key)
249
+ target.delete(key)
250
+ }
251
+ }
252
+
253
+ const cleanupKeys = effect(function projectMapEffect({ ascend }) {
254
+ const keys = new Set<Key>()
255
+ for (const key of observedSource.keys()) keys.add(key)
256
+
257
+ for (const key of keys) {
258
+ if (keyEffects.has(key)) continue
259
+ ascend(() => {
260
+ const stop = effect(function projectMapKeyEffect() {
261
+ const previous = untracked(() => target.get(key))
262
+ const accessBase = {
263
+ key,
264
+ source: observedSource,
265
+ get: () => observedSource.get(key) as Value,
266
+ set: (value: Value) => {
267
+ observedSource.set(key, value)
268
+ return true
269
+ },
270
+ old: previous,
271
+ } as ProjectAccess<Value, Key, Map<Key, Value>, Map<Key, ResultValue>>
272
+ defineAccessValue(accessBase)
273
+ const produced = apply(accessBase, target)
274
+ target.set(key, produced)
275
+ })
276
+ keyEffects.set(key, stop)
277
+ })
278
+ }
279
+
280
+ for (const key of Array.from(keyEffects.keys())) if (!keys.has(key)) disposeKey(key)
281
+ })
282
+
283
+ return makeCleanup(target, keyEffects, () => cleanupKeys())
284
+ }
285
+
286
+ type ProjectOverload = {
287
+ <SourceValue, ResultValue>(
288
+ source: readonly SourceValue[],
289
+ apply: ProjectCallback<SourceValue, number, ResultValue[], readonly SourceValue[], ResultValue>
290
+ ): ProjectResult<ResultValue[]>
291
+ <Key extends PropertyKey, SourceValue, ResultValue>(
292
+ source: Register<SourceValue, Key>,
293
+ apply: ProjectCallback<
294
+ SourceValue,
295
+ Key,
296
+ Map<Key, ResultValue>,
297
+ Register<SourceValue, Key>,
298
+ ResultValue
299
+ >
300
+ ): ProjectResult<Map<Key, ResultValue>>
301
+ <Source extends Record<PropertyKey, any>, ResultValue>(
302
+ source: Source,
303
+ apply: ProjectCallback<
304
+ Source[keyof Source],
305
+ keyof Source,
306
+ Record<keyof Source, ResultValue>,
307
+ Source,
308
+ ResultValue
309
+ >
310
+ ): ProjectResult<Record<keyof Source, ResultValue>>
311
+ <Key, Value, ResultValue>(
312
+ source: Map<Key, Value>,
313
+ apply: ProjectCallback<Value, Key, Map<Key, ResultValue>, Map<Key, Value>, ResultValue>
314
+ ): ProjectResult<Map<Key, ResultValue>>
315
+ array: typeof projectArray
316
+ register: typeof projectRegister
317
+ record: typeof projectRecord
318
+ map: typeof projectMap
319
+ }
320
+
321
+ function projectCore(source: any, apply: any): ProjectResult<any> {
322
+ if (Array.isArray(source)) return projectArray(source, apply)
323
+ if (source instanceof Map) return projectMap(source, apply)
324
+ if (source instanceof Register) return projectRegister(source, apply)
325
+ if (source && (source.constructor === Object || source.constructor === undefined))
326
+ return projectRecord(source, apply)
327
+ throw new Error('Unsupported source type')
328
+ }
329
+
330
+ export const project: ProjectOverload = Object.assign(projectCore, {
331
+ array: projectArray,
332
+ register: projectRegister,
333
+ record: projectRecord,
334
+ map: projectMap,
335
+ })
@@ -0,0 +1,27 @@
1
+ export const objectToProxy = new WeakMap<object, object>()
2
+ export const proxyToObject = new WeakMap<object, object>()
3
+
4
+ export function storeProxyRelationship(target: object, proxy: object) {
5
+ objectToProxy.set(target, proxy)
6
+ proxyToObject.set(proxy, target)
7
+ }
8
+
9
+ export function getExistingProxy<T extends object>(target: T): T | undefined {
10
+ return objectToProxy.get(target) as T | undefined
11
+ }
12
+
13
+ export function trackProxyObject(proxy: object, target: object) {
14
+ proxyToObject.set(proxy, target)
15
+ }
16
+
17
+ export function unwrap<T>(obj: T): T {
18
+ let current = obj
19
+ while (current && typeof current === 'object' && current !== null && proxyToObject.has(current)) {
20
+ current = proxyToObject.get(current) as T
21
+ }
22
+ return current
23
+ }
24
+
25
+ export function isReactive(obj: any): boolean {
26
+ return proxyToObject.has(obj)
27
+ }