mutts 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -2
- package/dist/browser.cjs +1030 -4406
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.ts +310 -573
- package/dist/browser.esm.js +18 -24
- package/dist/browser.esm.js.map +1 -1
- package/dist/chunks/{async-browser-CA0jPWIi.cjs → async-browser-lvzLOCgk.cjs} +17 -24
- package/dist/chunks/async-browser-lvzLOCgk.cjs.map +1 -0
- package/dist/chunks/{async-core-UqHzvJ-S.cjs → async-core-CRLKP3l-.cjs} +8 -4
- package/dist/chunks/async-core-CRLKP3l-.cjs.map +1 -0
- package/dist/chunks/{async-node-BYHuGTni.cjs → async-node-C3DeIb0y.cjs} +5 -12
- package/dist/chunks/async-node-C3DeIb0y.cjs.map +1 -0
- package/dist/chunks/index-2vea86wD.esm.js +3011 -0
- package/dist/chunks/index-2vea86wD.esm.js.map +1 -0
- package/dist/chunks/index-CtA2AWl3.esm.js +4376 -0
- package/dist/chunks/index-CtA2AWl3.esm.js.map +1 -0
- package/dist/chunks/index-VTO-b2vR.cjs +4461 -0
- package/dist/chunks/index-VTO-b2vR.cjs.map +1 -0
- package/dist/{reactive → debug}/debug.d.ts +19 -8
- package/dist/debug/debug.d.ts.map +1 -0
- package/dist/debug/index.d.ts +4 -0
- package/dist/debug/index.d.ts.map +1 -0
- package/dist/debug/lineage-panel.d.ts +5 -0
- package/dist/debug/lineage-panel.d.ts.map +1 -0
- package/dist/debug/lineage.d.ts +79 -0
- package/dist/debug/lineage.d.ts.map +1 -0
- package/dist/debug.cjs +35 -0
- package/dist/debug.cjs.map +1 -0
- package/dist/debug.d.ts +209 -0
- package/dist/debug.esm.js +2 -0
- package/dist/debug.esm.js.map +1 -0
- package/dist/index.d.ts +1 -13
- package/dist/mutts.umd.js +1 -1
- package/dist/mutts.umd.js.map +1 -1
- package/dist/mutts.umd.min.js +1 -1
- package/dist/mutts.umd.min.js.map +1 -1
- package/dist/node.cjs +65 -63
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.ts +2 -1
- package/dist/node.esm.js +5 -11
- package/dist/node.esm.js.map +1 -1
- package/dist/{async → src/async}/browser.d.ts.map +1 -1
- package/dist/{async → src/async}/index.d.ts +2 -1
- package/dist/src/async/index.d.ts.map +1 -0
- package/dist/{async → src/async}/node.d.ts.map +1 -1
- package/dist/src/decorator.d.ts.map +1 -0
- package/dist/src/destroyable.d.ts.map +1 -0
- package/dist/src/entry-browser.d.ts.map +1 -0
- package/dist/src/entry-node.d.ts.map +1 -0
- package/dist/src/eventful.d.ts +20 -0
- package/dist/src/eventful.d.ts.map +1 -0
- package/dist/src/flavored.d.ts +33 -0
- package/dist/src/flavored.d.ts.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/indexable.d.ts.map +1 -0
- package/dist/{introspection.d.ts → src/introspection.d.ts} +1 -1
- package/dist/src/introspection.d.ts.map +1 -0
- package/dist/src/iterableWeak.d.ts.map +1 -0
- package/dist/src/mixins.d.ts.map +1 -0
- package/dist/src/promiseChain.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/array.d.ts +9 -10
- package/dist/src/reactive/array.d.ts.map +1 -0
- package/dist/src/reactive/buffer.d.ts +120 -0
- package/dist/src/reactive/buffer.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/change.d.ts +2 -2
- package/dist/src/reactive/change.d.ts.map +1 -0
- package/dist/src/reactive/deep-touch.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/deep-watch-state.d.ts +3 -3
- package/dist/src/reactive/deep-watch-state.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/deep-watch.d.ts +2 -1
- package/dist/src/reactive/deep-watch.d.ts.map +1 -0
- package/dist/src/reactive/describe.d.ts +12 -0
- package/dist/src/reactive/describe.d.ts.map +1 -0
- package/dist/src/reactive/effect-context.d.ts +34 -0
- package/dist/src/reactive/effect-context.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/effects.d.ts +31 -18
- package/dist/src/reactive/effects.d.ts.map +1 -0
- package/dist/src/reactive/index.d.ts +19 -0
- package/dist/src/reactive/index.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/map.d.ts +0 -2
- package/dist/src/reactive/map.d.ts.map +1 -0
- package/dist/src/reactive/memoize.d.ts +28 -0
- package/dist/src/reactive/memoize.d.ts.map +1 -0
- package/dist/src/reactive/non-reactive-state.d.ts.map +1 -0
- package/dist/src/reactive/non-reactive.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/project.d.ts +1 -2
- package/dist/src/reactive/project.d.ts.map +1 -0
- package/dist/src/reactive/proxy-state.d.ts.map +1 -0
- package/dist/src/reactive/proxy.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/record.d.ts +2 -3
- package/dist/src/reactive/record.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/register.d.ts +61 -0
- package/dist/src/reactive/register.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/registry.d.ts +6 -5
- package/dist/src/reactive/registry.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/set.d.ts +0 -2
- package/dist/src/reactive/set.d.ts.map +1 -0
- package/dist/src/reactive/tracking.d.ts.map +1 -0
- package/dist/{reactive → src/reactive}/types.d.ts +69 -21
- package/dist/src/reactive/types.d.ts.map +1 -0
- package/dist/{reactive/interface.d.ts → src/reactive/watch.d.ts} +7 -23
- package/dist/src/reactive/watch.d.ts.map +1 -0
- package/dist/{std-decorators.d.ts → src/std-decorators.d.ts} +1 -6
- package/dist/src/std-decorators.d.ts.map +1 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/{zone.d.ts → src/zone.d.ts} +2 -2
- package/dist/src/zone.d.ts.map +1 -0
- package/dist/types-DaHFfhlN.d.ts +414 -0
- package/docs/ai/api-reference.md +1 -1
- package/docs/ai/manual.md +443 -17
- package/docs/events.md +57 -2
- package/docs/flavored.md +210 -0
- package/docs/reactive/advanced.md +2 -3
- package/docs/reactive/attend.md +143 -0
- package/docs/reactive/collections.md +55 -0
- package/docs/reactive/core.md +120 -4
- package/docs/reactive/debugging.md +8 -10
- package/docs/reactive/describe.md +85 -0
- package/docs/reactive/error-handling.md +121 -0
- package/docs/reactive/project.md +1 -1
- package/docs/reactive/scan.md +215 -0
- package/docs/reactive.md +2 -0
- package/docs/std-decorators.md +102 -51
- package/docs/zone.md +3 -7
- package/package.json +6 -1
- package/src/async/browser.ts +110 -106
- package/src/async/index.ts +15 -11
- package/src/async/node.ts +5 -17
- package/src/eventful.ts +82 -49
- package/src/flavored.ts +106 -0
- package/src/index.d.ts +12 -12
- package/src/index.ts +2 -2
- package/src/introspection.ts +1 -1
- package/src/reactive/array.ts +29 -36
- package/src/reactive/buffer.ts +187 -27
- package/src/reactive/change.ts +16 -23
- package/src/reactive/deep-touch.ts +19 -14
- package/src/reactive/deep-watch-state.ts +3 -3
- package/src/reactive/deep-watch.ts +7 -4
- package/src/reactive/describe.ts +39 -0
- package/src/reactive/effect-context.ts +69 -4
- package/src/reactive/effects.ts +445 -393
- package/src/reactive/index.ts +12 -15
- package/src/reactive/map.ts +9 -25
- package/src/reactive/memoize.ts +26 -3
- package/src/reactive/project.md +107 -0
- package/src/reactive/project.ts +48 -71
- package/src/reactive/proxy.ts +21 -3
- package/src/reactive/record.ts +30 -45
- package/src/reactive/register.ts +138 -19
- package/src/reactive/registry.ts +22 -9
- package/src/reactive/set.ts +8 -21
- package/src/reactive/tracking.ts +9 -12
- package/src/reactive/types.ts +104 -24
- package/src/reactive/{interface.ts → watch.ts} +29 -59
- package/src/std-decorators.ts +56 -17
- package/src/utils.ts +2 -3
- package/src/zone.ts +28 -24
- package/dist/async/index.d.ts.map +0 -1
- package/dist/chunks/async-browser-CA0jPWIi.cjs.map +0 -1
- package/dist/chunks/async-core-UqHzvJ-S.cjs.map +0 -1
- package/dist/chunks/async-node-BYHuGTni.cjs.map +0 -1
- package/dist/chunks/index-DhaOVusv.esm.js +0 -6411
- package/dist/chunks/index-DhaOVusv.esm.js.map +0 -1
- package/dist/decorator.d.ts.map +0 -1
- package/dist/destroyable.d.ts.map +0 -1
- package/dist/entry-browser.d.ts.map +0 -1
- package/dist/entry-node.d.ts.map +0 -1
- package/dist/eventful.d.ts +0 -18
- package/dist/eventful.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/indexable.d.ts.map +0 -1
- package/dist/introspection.d.ts.map +0 -1
- package/dist/iterableWeak.d.ts.map +0 -1
- package/dist/mixins.d.ts.map +0 -1
- package/dist/promiseChain.d.ts.map +0 -1
- package/dist/reactive/array.d.ts.map +0 -1
- package/dist/reactive/buffer.d.ts +0 -44
- package/dist/reactive/buffer.d.ts.map +0 -1
- package/dist/reactive/change.d.ts.map +0 -1
- package/dist/reactive/debug.d.ts.map +0 -1
- package/dist/reactive/deep-touch.d.ts.map +0 -1
- package/dist/reactive/deep-watch-state.d.ts.map +0 -1
- package/dist/reactive/deep-watch.d.ts.map +0 -1
- package/dist/reactive/effect-context.d.ts +0 -7
- package/dist/reactive/effect-context.d.ts.map +0 -1
- package/dist/reactive/effects.d.ts.map +0 -1
- package/dist/reactive/index.d.ts +0 -20
- package/dist/reactive/index.d.ts.map +0 -1
- package/dist/reactive/interface.d.ts.map +0 -1
- package/dist/reactive/map.d.ts.map +0 -1
- package/dist/reactive/memoize.d.ts +0 -5
- package/dist/reactive/memoize.d.ts.map +0 -1
- package/dist/reactive/non-reactive-state.d.ts.map +0 -1
- package/dist/reactive/non-reactive.d.ts.map +0 -1
- package/dist/reactive/project.d.ts.map +0 -1
- package/dist/reactive/proxy-state.d.ts.map +0 -1
- package/dist/reactive/proxy.d.ts.map +0 -1
- package/dist/reactive/record.d.ts.map +0 -1
- package/dist/reactive/register.d.ts.map +0 -1
- package/dist/reactive/registry.d.ts.map +0 -1
- package/dist/reactive/set.d.ts.map +0 -1
- package/dist/reactive/tracking.d.ts.map +0 -1
- package/dist/reactive/types.d.ts.map +0 -1
- package/dist/std-decorators.d.ts.map +0 -1
- package/dist/utils.d.ts.map +0 -1
- package/dist/zone.d.ts.map +0 -1
- package/src/reactive/debug.ts +0 -517
- /package/dist/{async → src/async}/browser.d.ts +0 -0
- /package/dist/{async → src/async}/node.d.ts +0 -0
- /package/dist/{decorator.d.ts → src/decorator.d.ts} +0 -0
- /package/dist/{destroyable.d.ts → src/destroyable.d.ts} +0 -0
- /package/dist/{entry-browser.d.ts → src/entry-browser.d.ts} +0 -0
- /package/dist/{entry-node.d.ts → src/entry-node.d.ts} +0 -0
- /package/dist/{indexable.d.ts → src/indexable.d.ts} +0 -0
- /package/dist/{iterableWeak.d.ts → src/iterableWeak.d.ts} +0 -0
- /package/dist/{mixins.d.ts → src/mixins.d.ts} +0 -0
- /package/dist/{promiseChain.d.ts → src/promiseChain.d.ts} +0 -0
- /package/dist/{reactive → src/reactive}/deep-touch.d.ts +0 -0
- /package/dist/{reactive → src/reactive}/non-reactive-state.d.ts +0 -0
- /package/dist/{reactive → src/reactive}/non-reactive.d.ts +0 -0
- /package/dist/{reactive → src/reactive}/proxy-state.d.ts +0 -0
- /package/dist/{reactive → src/reactive}/proxy.d.ts +0 -0
- /package/dist/{reactive → src/reactive}/tracking.d.ts +0 -0
- /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
package/docs/flavored.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Flavored Functions
|
|
2
|
+
|
|
3
|
+
The `flavored` utility creates extensible functions with chainable property modifiers. It enables a fluent API where properties return specialized variants of the base function.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Flavored functions allow you to:
|
|
8
|
+
- Create property-based modifiers that transform how the function is called
|
|
9
|
+
- Chain multiple modifiers together
|
|
10
|
+
- Use either automatic option merging (`flavorOptions`) or custom argument transformation (`createFlavor`)
|
|
11
|
+
- Return hand-made functions for complete control (the generic case)
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
### Creating a Flavored Function
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { flavored, flavorOptions } from 'mutts'
|
|
19
|
+
|
|
20
|
+
const greet = flavored(
|
|
21
|
+
(name: string, options?: { loud?: boolean }) => {
|
|
22
|
+
const greeting = `Hello, ${name}!`
|
|
23
|
+
return options?.loud ? greeting.toUpperCase() : greeting
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
get loud() {
|
|
27
|
+
return flavorOptions(this, { loud: true })
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
greet('World') // "Hello, World!"
|
|
33
|
+
greet.loud('World') // "HELLO, WORLD!"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## API Reference
|
|
37
|
+
|
|
38
|
+
### `flavored(fn, flavors)`
|
|
39
|
+
|
|
40
|
+
Creates a flavored function with chainable property modifiers.
|
|
41
|
+
|
|
42
|
+
**Parameters:**
|
|
43
|
+
- `fn` - The base function to flavor
|
|
44
|
+
- `flavors` - Object defining the flavor properties (getters or methods)
|
|
45
|
+
|
|
46
|
+
**Returns:** A proxy of the function with the flavor properties attached.
|
|
47
|
+
|
|
48
|
+
**Example:**
|
|
49
|
+
```typescript
|
|
50
|
+
const effect = flavored(baseEffect, {
|
|
51
|
+
get opaque() {
|
|
52
|
+
return flavorOptions(this, { opaque: true })
|
|
53
|
+
},
|
|
54
|
+
named(name: string) {
|
|
55
|
+
return flavorOptions(this, { name })
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
effect(fn) // Basic usage
|
|
60
|
+
effect.opaque(fn) // With opaque option
|
|
61
|
+
effect.named('myEffect')(fn) // With name option
|
|
62
|
+
effect.opaque.named('x')(fn) // Chained
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `flavorOptions(fn, defaultOptions)`
|
|
66
|
+
|
|
67
|
+
Creates a flavored variant that merges options with the last argument.
|
|
68
|
+
|
|
69
|
+
**Use when:** The last argument is an options object that should be merged.
|
|
70
|
+
|
|
71
|
+
**Parameters:**
|
|
72
|
+
- `fn` - The base flavored function
|
|
73
|
+
- `defaultOptions` - Options to merge with provided options
|
|
74
|
+
|
|
75
|
+
**Example:**
|
|
76
|
+
```typescript
|
|
77
|
+
const loudGreet = flavorOptions(greet, { loud: true })
|
|
78
|
+
loudGreet('World') // Uses { loud: true }
|
|
79
|
+
loudGreet('World', { prefix: 'Hi' }) // Merges both options
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `createFlavor(fn, transform)`
|
|
83
|
+
|
|
84
|
+
Creates a flavored variant with custom argument transformation.
|
|
85
|
+
|
|
86
|
+
**Use when:** You need to transform arguments before calling the base function.
|
|
87
|
+
|
|
88
|
+
**Parameters:**
|
|
89
|
+
- `fn` - The base flavored function
|
|
90
|
+
- `transform` - Function that receives and returns the arguments tuple
|
|
91
|
+
|
|
92
|
+
**Example:**
|
|
93
|
+
```typescript
|
|
94
|
+
const doubleArgs = createFlavor(add, (a: number, b: number): [number, number] => {
|
|
95
|
+
return [a * 2, b * 2]
|
|
96
|
+
})
|
|
97
|
+
doubleArgs(3, 4) // Returns 14 (6 + 8)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## The Generic Case: Hand-Made Functions
|
|
101
|
+
|
|
102
|
+
When `flavorOptions` or `createFlavor` don't fit your needs, you can return a **hand-made function** directly. This gives you complete control over the behavior.
|
|
103
|
+
|
|
104
|
+
### Basic Hand-Made Function
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
const fetch = flavored(fetchData, {
|
|
108
|
+
// Returns a completely custom function
|
|
109
|
+
withTimeout(timeout: number) {
|
|
110
|
+
return (url: string, options?: { retries?: number }) => {
|
|
111
|
+
return fetchData(url, { ...options, timeout })
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// Usage
|
|
117
|
+
const fetchWith500ms = fetch.withTimeout(500)
|
|
118
|
+
fetchWith500ms('api.com') // Uses timeout: 500
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Re-Flavoring Hand-Made Functions
|
|
122
|
+
|
|
123
|
+
Hand-made functions can be re-flavored to enable further chaining:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const process = flavored(baseProcess, {
|
|
127
|
+
presetMultiplier(multiplier: number) {
|
|
128
|
+
// Create the preset function
|
|
129
|
+
const presetFn = (value: number, options?: { offset?: number }) => {
|
|
130
|
+
return baseProcess(value, { ...options, multiplier })
|
|
131
|
+
}
|
|
132
|
+
// Re-flavor it to enable chaining
|
|
133
|
+
return flavored(presetFn, {
|
|
134
|
+
get withOffset() {
|
|
135
|
+
return flavorOptions(this, { offset: 10 })
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Usage
|
|
142
|
+
const times3 = process.presetMultiplier(3)
|
|
143
|
+
times3(5) // 15 (5 * 3)
|
|
144
|
+
times3.withOffset(5) // 20 (5 * 3 + 5)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### When to Use Each Approach
|
|
148
|
+
|
|
149
|
+
| Approach | Use When |
|
|
150
|
+
|----------|----------|
|
|
151
|
+
| `flavorOptions` | You have an options object as the last argument |
|
|
152
|
+
| `createFlavor` | You need to transform arguments structurally |
|
|
153
|
+
| **Hand-made function** | You need complete control, different signature, or side effects |
|
|
154
|
+
|
|
155
|
+
## Advanced Patterns
|
|
156
|
+
|
|
157
|
+
### Chaining Multiple Modifiers
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const createUser = flavored(createUserBase, {
|
|
161
|
+
get admin() {
|
|
162
|
+
return flavorOptions(this, { admin: true })
|
|
163
|
+
},
|
|
164
|
+
get verified() {
|
|
165
|
+
return flavorOptions(this, { verified: true })
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
createUser.admin.verified('Alice') // Both options applied
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Combining Approaches
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const calculator = flavored(
|
|
176
|
+
(a: number, b: number, opts?: { multiply?: boolean }) => {
|
|
177
|
+
return opts?.multiply ? a * b : a + b
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
get multiply() {
|
|
181
|
+
return flavorOptions(this, { multiply: true })
|
|
182
|
+
},
|
|
183
|
+
double() {
|
|
184
|
+
return createFlavor(this, (a, b, opts?): [number, number, typeof opts] => {
|
|
185
|
+
return [a * 2, b * 2, opts]
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
calculator.multiply(3, 4) // 12
|
|
192
|
+
calculator.double()(3, 4) // 14 (6 + 8)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## TypeScript Considerations
|
|
196
|
+
|
|
197
|
+
Flavored functions use proxies and require type assertions for complex chaining scenarios:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const effectWithModifiers = flavored(effect, {
|
|
201
|
+
get opaque(): EffectWithModifiers {
|
|
202
|
+
return flavorOptions(this, { opaque: true }) as EffectWithModifiers
|
|
203
|
+
},
|
|
204
|
+
named(name: string): EffectWithModifiers {
|
|
205
|
+
return flavorOptions(this, { name }) as EffectWithModifiers
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
The `as EffectWithModifiers` cast enables proper type inference for chained modifiers.
|
|
@@ -1169,8 +1169,7 @@ For a full guide on debugging, including cycle detection and memoization discrep
|
|
|
1169
1169
|
|
|
1170
1170
|
### Quick Summary
|
|
1171
1171
|
|
|
1172
|
-
- **Cycle Detection**: Automatically catch circular dependencies via `reactiveOptions.cycleHandling`. Note: Instant mathematical detection requires choosing
|
|
1173
|
-
- **
|
|
1172
|
+
- **Cycle Detection**: Automatically catch circular dependencies via `reactiveOptions.cycleHandling`. Note: Instant mathematical detection requires choosing `'development'` or `'debug'` mode.
|
|
1173
|
+
- **Production Mode**: The default `reactiveOptions.cycleHandling` is set to `'production'`, providing maximum performance in high-frequency update scenarios by disabling graph maintenance.
|
|
1174
1174
|
- **Memoization Discrepancy**: Detect "missing dependencies" by running computations twice during development using `reactiveOptions.onMemoizationDiscrepancy`.
|
|
1175
1175
|
- **Global Hooks**: Use `reactiveOptions.touched`, `enter`, and `leave` to observe system activity.
|
|
1176
|
-
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Reactive Enumeration (`attend`)
|
|
2
|
+
|
|
3
|
+
The `attend` utility reactively iterates over the entries of a collection, running a per-key effect that is automatically created when a key appears and disposed when it disappears.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`attend` is the reactive equivalent of `forEach`. It:
|
|
8
|
+
- Tracks the **keys** (or indices, or values) of a collection inside an outer effect.
|
|
9
|
+
- **Creates** an inner effect for each key, via `ascend`.
|
|
10
|
+
- **Disposes** the inner effect when the key is removed from the collection.
|
|
11
|
+
- Allows the callback to return a **cleanup function** (like a regular effect closer).
|
|
12
|
+
|
|
13
|
+
This is the foundational lifecycle primitive that `describe` and `organized` are built on.
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// Raw enumeration callback
|
|
19
|
+
function attend<Key>(
|
|
20
|
+
enumerate: () => Iterable<Key>,
|
|
21
|
+
callback: (key: Key) => EffectCloser | void
|
|
22
|
+
): ScopedCallback
|
|
23
|
+
|
|
24
|
+
// Collection shorthands
|
|
25
|
+
function attend<T>(source: readonly T[], callback: (index: number) => EffectCloser | void): ScopedCallback
|
|
26
|
+
function attend<K, V>(source: Map<K, V>, callback: (key: K) => EffectCloser | void): ScopedCallback
|
|
27
|
+
function attend<T>(source: Set<T>, callback: (value: T) => EffectCloser | void): ScopedCallback
|
|
28
|
+
function attend<S extends Record<PropertyKey, any>>(source: S, callback: (key: keyof S & string) => EffectCloser | void): ScopedCallback
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Parameters
|
|
32
|
+
|
|
33
|
+
- **`source`** or **`enumerate`**: Either a collection (array, record, Map, Set) or a callback returning an `Iterable<Key>`. The enumeration runs inside the outer effect, so reactive reads (e.g. `source.length`, `Object.keys(source)`) are tracked automatically.
|
|
34
|
+
- **`callback`**: Called per key inside an inner effect. May return a cleanup function that runs when the key is removed or before the inner effect re-executes.
|
|
35
|
+
|
|
36
|
+
### Returns
|
|
37
|
+
|
|
38
|
+
A `ScopedCallback` that tears down all inner effects and the outer effect.
|
|
39
|
+
|
|
40
|
+
## Basic Usage
|
|
41
|
+
|
|
42
|
+
### Record
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { reactive, attend } from 'mutts'
|
|
46
|
+
|
|
47
|
+
const config = reactive({ theme: 'dark', lang: 'en' })
|
|
48
|
+
|
|
49
|
+
const stop = attend(config, (key) => {
|
|
50
|
+
console.log(`${key} = ${config[key]}`)
|
|
51
|
+
return () => console.log(`cleanup: ${key}`)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// Logs: "theme = dark", "lang = en"
|
|
55
|
+
|
|
56
|
+
config.debug = true
|
|
57
|
+
// Logs: "debug = true"
|
|
58
|
+
|
|
59
|
+
delete config.lang
|
|
60
|
+
// Logs: "cleanup: lang"
|
|
61
|
+
|
|
62
|
+
config.theme = 'light'
|
|
63
|
+
// Logs: "cleanup: theme" then "theme = light"
|
|
64
|
+
// (inner effect re-runs: previous cleanup fires, then new execution)
|
|
65
|
+
|
|
66
|
+
stop()
|
|
67
|
+
// Disposes everything
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Array
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const items = reactive([10, 20, 30])
|
|
74
|
+
|
|
75
|
+
attend(items, (i) => {
|
|
76
|
+
console.log(`[${i}] = ${items[i]}`)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
items.push(40)
|
|
80
|
+
// Logs: "[3] = 40"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Map
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const registry = reactive(new Map([['a', 1]]))
|
|
87
|
+
|
|
88
|
+
attend(registry, (key) => {
|
|
89
|
+
console.log(`${key} -> ${registry.get(key)}`)
|
|
90
|
+
return () => console.log(`removed: ${key}`)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
registry.set('b', 2) // Logs: "b -> 2"
|
|
94
|
+
registry.delete('a') // Logs: "removed: a"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Set
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const tags = reactive(new Set(['alpha', 'beta']))
|
|
101
|
+
|
|
102
|
+
attend(tags, (value) => {
|
|
103
|
+
console.log(`tag: ${value}`)
|
|
104
|
+
return () => console.log(`untagged: ${value}`)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
tags.add('gamma') // Logs: "tag: gamma"
|
|
108
|
+
tags.delete('alpha') // Logs: "untagged: alpha"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Raw enumeration callback
|
|
112
|
+
|
|
113
|
+
For custom iteration logic or non-standard collections:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const source = reactive({ a: 1, b: 2 })
|
|
117
|
+
|
|
118
|
+
attend(
|
|
119
|
+
() => Reflect.ownKeys(source),
|
|
120
|
+
(key) => {
|
|
121
|
+
console.log(key, source[key])
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## How it Works
|
|
127
|
+
|
|
128
|
+
1. An **outer effect** calls `enumerate()` (or derives it from the collection type), collecting the current keys into a `Set`.
|
|
129
|
+
2. For each **new key**, `ascend` creates an inner effect that runs the callback.
|
|
130
|
+
3. For each **removed key**, the corresponding inner effect is stopped (which triggers its cleanup).
|
|
131
|
+
4. The **inner effect** tracks its own reactive dependencies — so if a value changes for an existing key, only that key's effect re-runs.
|
|
132
|
+
|
|
133
|
+
## Relationship to Other Primitives
|
|
134
|
+
|
|
135
|
+
| Primitive | Uses `attend`? | Purpose |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| `attend` | — | Reactive lifecycle per key |
|
|
138
|
+
| `describe` | ✅ | Reactive `Object.defineProperty` per key |
|
|
139
|
+
| `organized` | ✅ | Reactive record mapping with access objects |
|
|
140
|
+
| `project` | ❌ | Reactive collection mapping (manages a target + projection context) |
|
|
141
|
+
| `scan` | ❌ | Reactive accumulation (sequential key dependency) |
|
|
142
|
+
|
|
143
|
+
`project` and `scan` have additional concerns (target management, sequential dependencies) that go beyond `attend`'s independent-key lifecycle model.
|
|
@@ -347,6 +347,61 @@ The `Register` exposes additional methods and behaviors that standard arrays do
|
|
|
347
347
|
- `toArray(): T[]` materializes the current values into a plain array.
|
|
348
348
|
- `toString(): string` returns a concise description like `[Register length=3]`.
|
|
349
349
|
|
|
350
|
+
### Register CRUD Events
|
|
351
|
+
|
|
352
|
+
`Register` emits lifecycle events for add, delete, update, and rekey operations. This enables side effects like logging, syncing with external systems, or triggering derived updates.
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
const list = register(({id}: { id: number }) => id)
|
|
356
|
+
|
|
357
|
+
// Listen to individual events
|
|
358
|
+
list.on('add', (item, key, index) => {
|
|
359
|
+
console.log(`Added ${key} at index ${index}:`, item)
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
list.on('delete', (item, key, index) => {
|
|
363
|
+
console.log(`Removed ${key} from index ${index}:`, item)
|
|
364
|
+
})
|
|
365
|
+
|
|
366
|
+
list.on('update', (oldItem, newItem, key, index) => {
|
|
367
|
+
console.log(`Updated ${key} at index ${index}`)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
list.on('rekey', (item, oldKey, newKey, index) => {
|
|
371
|
+
console.log(`Key changed from ${oldKey} to ${newKey}`)
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
// Bulk event registration
|
|
375
|
+
list.on({
|
|
376
|
+
add: (item) => console.log('Added:', item),
|
|
377
|
+
delete: (item) => console.log('Deleted:', item),
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
// Global hook - receive all events
|
|
381
|
+
const unhook = list.hook((event, ...args) => {
|
|
382
|
+
console.log(`Event: ${String(event)}`, args)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
// Unsubscribe
|
|
386
|
+
const unsubscribe = list.on('add', handler)
|
|
387
|
+
unsubscribe()
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
**Event Types:**
|
|
391
|
+
|
|
392
|
+
| Event | Arguments | Description |
|
|
393
|
+
|-------|-----------|-------------|
|
|
394
|
+
| `add` | `(item, key, index)` | New item added to register |
|
|
395
|
+
| `delete` | `(item, key, index)` | Item removed from register |
|
|
396
|
+
| `update` | `(oldItem, newItem, key, index)` | Item value updated (same key) |
|
|
397
|
+
| `rekey` | `(item, oldKey, newKey, index)` | Item's key changed |
|
|
398
|
+
|
|
399
|
+
**Use Cases:**
|
|
400
|
+
- Audit logging
|
|
401
|
+
- Syncing with databases
|
|
402
|
+
- Triggering notifications
|
|
403
|
+
- Cascading updates to dependent systems
|
|
404
|
+
|
|
350
405
|
Notes:
|
|
351
406
|
- Direct length modification via `list.length = n` is not supported; use `splice` instead.
|
|
352
407
|
- Assigning to an index (`list[i] = value`) uses the key function to bind that slot to `value`’s key.
|
package/docs/reactive/core.md
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
- [5-Minute Quick Start](#5-minute-quick-start)
|
|
8
8
|
- [Core API](#core-api)
|
|
9
9
|
- [Effect System](#effect-system)
|
|
10
|
+
- [Error Handling](./error-handling.md)
|
|
10
11
|
- [Atomic Operations](./advanced.md#atomic-operations)
|
|
11
12
|
- [Advanced Effects](./advanced.md#advanced-effects)
|
|
12
13
|
- [Evolution Tracking](./advanced.md#evolution-tracking)
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
- [Non-Reactive System](./advanced.md#non-reactive-system)
|
|
20
21
|
- [Array Mapping](./advanced.md#array-mapping)
|
|
21
22
|
- [Projection](./advanced.md#projection)
|
|
23
|
+
- [Reactive Property Description](./describe.md)
|
|
22
24
|
- [Record Organization](./advanced.md#record-organization)
|
|
23
25
|
- [Memoization](./advanced.md#memoization)
|
|
24
26
|
- [Debugging and Development](./advanced.md#debugging-and-development)
|
|
@@ -111,6 +113,19 @@ const doubled = project(source, ({ value }) => value * 2)
|
|
|
111
113
|
source.push(4) // doubled automatically becomes [2, 4, 6, 8]
|
|
112
114
|
```
|
|
113
115
|
|
|
116
|
+
**6. Define properties reactively:**
|
|
117
|
+
```typescript
|
|
118
|
+
const target = {}
|
|
119
|
+
const descriptors = reactive({
|
|
120
|
+
foo: { value: 1, enumerable: true }
|
|
121
|
+
})
|
|
122
|
+
describe(descriptors, target)
|
|
123
|
+
// target.foo is 1
|
|
124
|
+
|
|
125
|
+
descriptors.bar = { value: 2, enumerable: true }
|
|
126
|
+
// target.bar is 2 automatically
|
|
127
|
+
```
|
|
128
|
+
|
|
114
129
|
**Ready to go!** Continue reading for advanced features.
|
|
115
130
|
|
|
116
131
|
---
|
|
@@ -204,6 +219,19 @@ reactiveObj.count = 5 // Triggers effect
|
|
|
204
219
|
|
|
205
220
|
See [Prototype Chains and Pure Objects](#prototype-chains-and-pure-objects) for detailed information about prototype chain handling.
|
|
206
221
|
|
|
222
|
+
### `describe()`
|
|
223
|
+
|
|
224
|
+
Reactively defines properties on a target object based on a descriptors record.
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
function describe<T extends object>(
|
|
228
|
+
descriptors: Record<PropertyKey, PropertyDescriptor>,
|
|
229
|
+
target: T = Object.create(null) as T
|
|
230
|
+
): T
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
See the [Reactive Property Description](./describe.md) guide for details.
|
|
234
|
+
|
|
207
235
|
### `effect()`
|
|
208
236
|
|
|
209
237
|
Creates a reactive effect that automatically re-runs when dependencies change.
|
|
@@ -448,7 +476,7 @@ state.c = 15 // Does NOT trigger effect
|
|
|
448
476
|
|
|
449
477
|
The `effect` function provides a special `access` parameter with `tracked` and `ascend` functions that restore the active effect context for dependency tracking in asynchronous operations.
|
|
450
478
|
|
|
451
|
-
In modern `mutts`, this is powered by the **Zone system**. When
|
|
479
|
+
In modern `mutts`, this is powered by the **Zone system**. When zones are registered in `asyncZone`, the active effect context is automatically preserved across `await` points and timers, making manual use of `tracked` optional for these cases.
|
|
452
480
|
|
|
453
481
|
#### The Problem with Async Effects
|
|
454
482
|
|
|
@@ -466,7 +494,7 @@ effect(() => {
|
|
|
466
494
|
const another = state.name // ✅ Tracked (active effect is still set)
|
|
467
495
|
})
|
|
468
496
|
|
|
469
|
-
// Async effect WITHOUT
|
|
497
|
+
// Async effect WITHOUT zone registration - context is lost after await
|
|
470
498
|
effect(async () => {
|
|
471
499
|
const value = state.count // ✅ Tracked
|
|
472
500
|
|
|
@@ -476,7 +504,7 @@ effect(async () => {
|
|
|
476
504
|
const another = state.name // ❌ NOT tracked
|
|
477
505
|
})
|
|
478
506
|
|
|
479
|
-
// Async effect WITH
|
|
507
|
+
// Async effect WITH zone registration - context is preserved
|
|
480
508
|
effect(async () => {
|
|
481
509
|
const value = state.count // ✅ Tracked
|
|
482
510
|
|
|
@@ -497,7 +525,7 @@ effect(async ({ tracked }) => {
|
|
|
497
525
|
|
|
498
526
|
#### Key Benefits of the Zone System
|
|
499
527
|
|
|
500
|
-
1. **Automatic Restoration**: With `
|
|
528
|
+
1. **Automatic Restoration**: With zones registered in `asyncZone`, most native async APIs (Promises, timers) automatically preserve the reactive context.
|
|
501
529
|
2. **Manual Control**: `access.tracked()` allows you to manually "passport" the context into third-party libraries or unmanaged callbacks.
|
|
502
530
|
|
|
503
531
|
### Using `ascend` for Parent Effect Tracking
|
|
@@ -603,6 +631,94 @@ reactiveOptions.leave = (effect) => console.log('Leaving effect:', effect)
|
|
|
603
631
|
reactiveOptions.chain = (caller, target) => console.log('Chaining:', caller, '->', target)
|
|
604
632
|
```
|
|
605
633
|
|
|
634
|
+
### Effect Modifiers
|
|
635
|
+
|
|
636
|
+
The `effect` function provides convenient shortcut modifiers for common options. These can be chained for more concise syntax:
|
|
637
|
+
|
|
638
|
+
#### `.opaque`
|
|
639
|
+
|
|
640
|
+
Creates an opaque effect that tracks object references rather than deep content. This is useful when you want effects to re-run only when the object identity changes, not when its properties change.
|
|
641
|
+
|
|
642
|
+
```typescript
|
|
643
|
+
import { effect, reactive } from 'mutts/reactive'
|
|
644
|
+
|
|
645
|
+
const item = reactive({ id: 1, data: { value: 10 } })
|
|
646
|
+
|
|
647
|
+
// Regular effect - triggers on any property change
|
|
648
|
+
effect(() => {
|
|
649
|
+
console.log('Item data:', item.data.value) // Triggers on item.data.value changes
|
|
650
|
+
})
|
|
651
|
+
|
|
652
|
+
// Opaque effect - only triggers when item.data reference changes
|
|
653
|
+
effect.opaque(() => {
|
|
654
|
+
console.log('Data object:', item.data) // Only triggers when item.data is replaced
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
item.data.value = 20 // Triggers regular effect, NOT opaque effect
|
|
658
|
+
item.data = { value: 30 } // Triggers BOTH effects
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
**Use cases for opaque effects:**
|
|
662
|
+
- When you only care about object identity (e.g., cache keys, memoization)
|
|
663
|
+
- When deep watching would be too expensive
|
|
664
|
+
- When working with external data that shouldn't trigger deep reactivity
|
|
665
|
+
|
|
666
|
+
#### `.named(name)`
|
|
667
|
+
|
|
668
|
+
Creates a named effect for easier debugging and profiling. The name appears in DevTools and debug logs.
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
import { effect, reactive } from 'mutts/reactive'
|
|
672
|
+
|
|
673
|
+
const state = reactive({ count: 0 })
|
|
674
|
+
|
|
675
|
+
// Create a named effect
|
|
676
|
+
effect.named('counter-effect')(() => {
|
|
677
|
+
console.log('Count:', state.count)
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
// Named effects can also be combined with other options
|
|
681
|
+
effect.named('data-loader').opaque(() => {
|
|
682
|
+
console.log('Loading data...')
|
|
683
|
+
})
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
**Benefits of named effects:**
|
|
687
|
+
- Easier identification in DevTools
|
|
688
|
+
- Better stack traces during debugging
|
|
689
|
+
- Helpful for performance profiling
|
|
690
|
+
|
|
691
|
+
#### Combining Modifiers
|
|
692
|
+
|
|
693
|
+
Modifiers can be chained in any order:
|
|
694
|
+
|
|
695
|
+
```typescript
|
|
696
|
+
// Named opaque effect
|
|
697
|
+
effect.named('my-effect').opaque(() => {
|
|
698
|
+
// Effect code
|
|
699
|
+
})
|
|
700
|
+
|
|
701
|
+
// These are equivalent - order doesn't matter for the result
|
|
702
|
+
effect.opaque.named('my-effect')(() => {
|
|
703
|
+
// Effect code
|
|
704
|
+
})
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
Note: The modifiers return new effect functions with the options pre-applied, so they can be stored and reused:
|
|
708
|
+
|
|
709
|
+
```typescript
|
|
710
|
+
// Create a reusable named effect factory
|
|
711
|
+
const createDataEffect = effect.named('data-layer')
|
|
712
|
+
|
|
713
|
+
createDataEffect(() => {
|
|
714
|
+
console.log('Effect 1')
|
|
715
|
+
})
|
|
716
|
+
|
|
717
|
+
createDataEffect(() => {
|
|
718
|
+
console.log('Effect 2')
|
|
719
|
+
})
|
|
720
|
+
```
|
|
721
|
+
|
|
606
722
|
|
|
607
723
|
### `@reactive` Decorator
|
|
608
724
|
|
|
@@ -25,7 +25,7 @@ These hooks are called during the execution of effects and computed values.
|
|
|
25
25
|
|
|
26
26
|
- **`beginChain(targets: Function[]) / endChain()`**: Called when a batch of effects starts and ends its execution.
|
|
27
27
|
- **`maxEffectChain`**: (Default: `100`) Limits the depth of synchronous effect triggering to prevent stack overflows.
|
|
28
|
-
- **`maxTriggerPerBatch`**: (Default: `10`) Limits how many times a single effect can be triggered within the same batch. Useful for detecting aggressive re-computation or infinite cycles in `cycleHandling: '
|
|
28
|
+
- **`maxTriggerPerBatch`**: (Default: `10`) Limits how many times a single effect can be triggered within the same batch. Useful for detecting aggressive re-computation or infinite cycles in `cycleHandling: 'production'` mode.
|
|
29
29
|
|
|
30
30
|
## Cycle Detection
|
|
31
31
|
|
|
@@ -35,18 +35,16 @@ These hooks are called during the execution of effects and computed values.
|
|
|
35
35
|
|
|
36
36
|
You can control how cycles are handled via `reactiveOptions.cycleHandling`:
|
|
37
37
|
|
|
38
|
-
- **`'
|
|
39
|
-
- **`'
|
|
40
|
-
- **`'
|
|
41
|
-
- **`'break'`**: Silently breaks the cycle.
|
|
42
|
-
- **`'strict'`**: Performs a graph check *before* execution to prevent cycles from even starting. This has the highest overhead.
|
|
38
|
+
- **`'production'`**: High-performance FIFO mode. Disables the dependency graph and topological sorting. Uses heuristic detection via `maxEffectChain`.
|
|
39
|
+
- **`'development'`** (Default): Maintains direct dependency graph for early cycle detection during edge creation. Throws immediately with basic path information.
|
|
40
|
+
- **`'debug'`**: Full diagnostic mode with transitive closures and topological sorting. Provides detailed cycle path reporting.
|
|
43
41
|
|
|
44
42
|
### Topological vs. Flat Mode Detection
|
|
45
43
|
|
|
46
44
|
| Mode | `cycleHandling` | Detection Method | Error Code |
|
|
47
45
|
| :--- | :--- | :--- | :--- |
|
|
48
|
-
| **
|
|
49
|
-
| **
|
|
46
|
+
| **Debug** | `'debug'` or `'development'` | **Mathematical**: Analyzes the dependency graph. | `CYCLE_DETECTED` |
|
|
47
|
+
| **Production** | `'production'` (Default) | **Heuristic**: Counts executions per batch. | `MAX_REACTION_EXCEEDED` |
|
|
50
48
|
|
|
51
49
|
In **Topological mode**, the system maintains a transitive closure of all effects, allowing it to know instantly if an effect is its own cause. In **Flat mode**, the system is "blind" to the graph and relies on the execution threshold (`maxTriggerPerBatch`) to interrupt infinite loops.
|
|
52
50
|
|
|
@@ -157,12 +155,12 @@ Since these are runtime options, you can toggle them based on your environment:
|
|
|
157
155
|
|
|
158
156
|
```typescript
|
|
159
157
|
if (process.env.NODE_ENV === 'development') {
|
|
160
|
-
reactiveOptions.cycleHandling = '
|
|
158
|
+
reactiveOptions.cycleHandling = 'debug';
|
|
161
159
|
reactiveOptions.onMemoizationDiscrepancy = myHandler;
|
|
162
160
|
enableIntrospection();
|
|
163
161
|
} else {
|
|
164
162
|
// Ensure they are off in production for performance
|
|
165
163
|
reactiveOptions.onMemoizationDiscrepancy = undefined;
|
|
166
|
-
reactiveOptions.cycleHandling = '
|
|
164
|
+
reactiveOptions.cycleHandling = 'production';
|
|
167
165
|
}
|
|
168
166
|
```
|