controlled-machine 0.1.2 → 0.2.0

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 CHANGED
@@ -1,514 +1,314 @@
1
1
  # Controlled Machine
2
2
 
3
- A controlled state machine where state lives outside the machine.
3
+ A controlled state machine where **state lives outside the machine**.
4
4
 
5
- ```ts
6
- import { createMachine } from 'controlled-machine'
7
- import { useMachine } from 'controlled-machine/react'
8
-
9
- const machine = createMachine<{
10
- input: { isOpen: boolean; setIsOpen: (v: boolean) => void }
11
- events: { OPEN: undefined; CLOSE: undefined }
12
- actions: 'open' | 'close'
13
- }>({
14
- on: {
15
- OPEN: 'open',
16
- CLOSE: 'close',
17
- },
18
- actions: {
19
- open: (ctx) => ctx.setIsOpen(true),
20
- close: (ctx) => ctx.setIsOpen(false),
21
- },
22
- })
23
-
24
- function Dropdown() {
25
- const [isOpen, setIsOpen] = useState(false)
26
- const { send } = useMachine(machine, { isOpen, setIsOpen })
5
+ Machine defines **what** happens. Your component owns **the state**.
27
6
 
28
- return <button onClick={() => send('OPEN')}>Open</button>
29
- }
30
- ```
31
-
32
- ---
7
+ ## The Killer Example
33
8
 
34
- ## Introduction
35
-
36
- Controlled Machine maintains the core concepts of state machines (conditional transitions, side effects) while **keeping state external**.
9
+ A reusable dropdown machine — logic lives in the machine, DOM handling in your component:
37
10
 
38
11
  ```ts
39
- // XState: state lives inside the machine
40
- const machine = createMachine({
41
- initial: 'closed',
42
- states: {
43
- closed: { on: { OPEN: 'open' } },
44
- open: { on: { CLOSE: 'closed' } },
45
- },
46
- })
47
-
48
- // Controlled Machine: state is external, machine only defines handlers
49
- const [isOpen, setIsOpen] = useState(false)
50
- const { send } = useMachine(machine, { isOpen, setIsOpen })
51
- ```
52
-
53
- In React, the most powerful pattern is **external state passed via props**. Controlled Machine naturally integrates with this approach.
54
-
55
- ---
56
-
57
- ## Features
58
-
59
- - **Controlled** — State is managed in React state or props
60
- - **Conditional handlers** — Branch logic with `when` conditions
61
- - **State-based structure** — Define different handlers per state
62
- - **Effects** — Watch value changes, cleanup support, `send` access
63
- - **Computed** — Derive values from context
64
- - **Multiple actions** — Execute multiple actions per event
65
-
66
- ---
67
-
68
- ## Installation
69
-
70
- ```bash
71
- npm install controlled-machine
72
- # or
73
- pnpm add controlled-machine
74
- # or
75
- yarn add controlled-machine
76
- ```
77
-
78
- ---
79
-
80
- ## Basic Usage
81
-
82
- ### Define a Machine
83
-
84
- Use `createMachine` to define event handlers.
85
-
86
- ```ts
87
- import { createMachine } from 'controlled-machine'
88
-
89
- type Input = {
90
- isOpen: boolean
91
- setIsOpen: (v: boolean) => void
92
- selectedId: string | null
93
- setSelectedId: (v: string | null) => void
94
- }
95
-
96
- type Events = {
97
- OPEN: undefined
98
- CLOSE: undefined
99
- SELECT: { itemId: string }
100
- }
101
-
102
- const machine = createMachine<{
103
- input: Input
104
- events: Events
105
- actions: 'open' | 'close' | 'select'
12
+ // dropdown-machine.ts Pure logic, no DOM dependencies
13
+ const dropdownMachine = createMachine<{
14
+ input: { isOpen: boolean; onOpenChange: (v: boolean) => void }
15
+ events: { OPEN: undefined; CLOSE: undefined; TOGGLE: undefined }
16
+ actions: 'open' | 'close' | 'focusTrigger'
17
+ guards: 'isOpen'
106
18
  }>({
107
19
  on: {
108
- OPEN: 'open',
109
- CLOSE: 'close',
110
- SELECT: 'select',
20
+ OPEN: [{ when: 'isOpen', do: [] }, { do: 'open' }],
21
+ CLOSE: [{ when: 'isOpen', do: ['close', 'focusTrigger'] }],
22
+ TOGGLE: [{ when: 'isOpen', do: 'close' }, { do: 'open' }],
111
23
  },
112
24
  actions: {
113
- open: (ctx) => ctx.setIsOpen(true),
114
- close: (ctx) => ctx.setIsOpen(false),
115
- select: (ctx, payload) => {
116
- ctx.setSelectedId(payload.itemId)
117
- ctx.setIsOpen(false)
118
- },
25
+ open: (ctx) => ctx.onOpenChange(true),
26
+ close: (ctx) => ctx.onOpenChange(false),
27
+ focusTrigger: () => {}, // Default: noop
28
+ },
29
+ guards: {
30
+ isOpen: (ctx) => ctx.isOpen,
119
31
  },
120
32
  })
121
33
  ```
122
34
 
123
- ### Send Events
124
-
125
- Use `useMachine` in React components.
126
-
127
35
  ```tsx
128
- import { useMachine } from 'controlled-machine/react'
129
-
36
+ // Dropdown.tsx Component provides DOM implementation
130
37
  function Dropdown() {
131
38
  const [isOpen, setIsOpen] = useState(false)
132
- const [selectedId, setSelectedId] = useState<string | null>(null)
39
+ const triggerRef = useRef<HTMLButtonElement>(null)
133
40
 
134
- const { send } = useMachine(machine, {
135
- isOpen,
136
- setIsOpen,
137
- selectedId,
138
- setSelectedId,
41
+ const { send } = useMachine(dropdownMachine, {
42
+ input: { isOpen, onOpenChange: setIsOpen },
43
+ actions: {
44
+ // Override: provide actual DOM implementation
45
+ focusTrigger: () => triggerRef.current?.focus(),
46
+ },
139
47
  })
140
48
 
141
49
  return (
142
- <div>
143
- <button onClick={() => send('OPEN')}>Open</button>
50
+ <>
51
+ <button ref={triggerRef} onClick={() => send('TOGGLE')}>
52
+ Menu
53
+ </button>
144
54
  {isOpen && (
145
55
  <ul>
146
- <li onClick={() => send('SELECT', { itemId: '1' })}>Item 1</li>
56
+ <li onClick={() => send('CLOSE')}>Item 1</li>
147
57
  </ul>
148
58
  )}
149
- </div>
59
+ </>
150
60
  )
151
61
  }
152
62
  ```
153
63
 
154
- ---
155
-
156
- ## API Reference
157
-
158
- ### `createMachine<T>(config)`
64
+ **Why this matters:**
65
+ - Machine is **pure and testable** — no refs, no DOM
66
+ - Component **owns its state** — React's controlled pattern
67
+ - Override **only what you need** — `focusTrigger` gets real implementation
68
+ - Same machine, **different UIs** — reuse logic across components
159
69
 
160
- Creates a machine definition with the given configuration.
70
+ ---
161
71
 
162
- **Type Parameters:**
72
+ ## Installation
163
73
 
164
- ```ts
165
- type MachineTypes = {
166
- input?: unknown // External data passed to the machine
167
- events?: Record<string, unknown> // Event name → payload type
168
- computed?: Record<string, unknown> // Derived values
169
- actions?: string // Union of action names
170
- state?: string // Union of state names (for state-based structure)
171
- }
74
+ ```bash
75
+ npm install controlled-machine
172
76
  ```
173
77
 
174
- **Config:**
175
-
176
- | Property | Description |
177
- |----------|-------------|
178
- | `computed` | Functions that derive values from input |
179
- | `on` | Event → action mappings (global handlers) |
180
- | `states` | State-specific event handlers |
181
- | `always` | Rules evaluated on every context change |
182
- | `effects` | Watch-based side effects |
183
- | `actions` | Named action implementations |
78
+ ---
184
79
 
185
- ### `useMachine(machine, input)`
80
+ ## Core Concepts
186
81
 
187
- React hook that connects a machine to component state.
82
+ ### 1. External State
188
83
 
189
- **Returns:**
84
+ Unlike XState where state lives inside the machine, here **you own the state**:
190
85
 
191
- | Property | Description |
192
- |----------|-------------|
193
- | `send` | Function to dispatch events |
194
- | `computed` | Computed values derived from input |
195
- | `state` | Current state (if using state-based structure) |
86
+ ```tsx
87
+ // Your state, your control
88
+ const [isOpen, setIsOpen] = useState(false)
89
+ const [selectedId, setSelectedId] = useState<string | null>(null)
196
90
 
197
- ---
91
+ // Machine just defines handlers
92
+ const { send } = useMachine(machine, {
93
+ input: { isOpen, onOpenChange: setIsOpen, selectedId, onSelect: setSelectedId },
94
+ })
95
+ ```
198
96
 
199
- ## Conditional Handlers
97
+ ### 2. Declarative Handlers
200
98
 
201
- Use `when` conditions to branch logic. Stops at the first match.
99
+ Define **what** happens on each event with conditional logic:
202
100
 
203
101
  ```ts
204
102
  on: {
205
- TOGGLE: [
206
- { when: (ctx) => ctx.disabled, do: 'noop' },
207
- { when: (ctx) => ctx.isOpen, do: 'close' },
208
- { do: 'open' }, // default case
103
+ SELECT: [
104
+ { when: 'isDisabled', do: [] }, // Guard: skip if disabled
105
+ { when: 'hasSelection', do: 'deselect' }, // Conditional action
106
+ { do: ['select', 'close'] }, // Default: multiple actions
209
107
  ],
210
108
  }
211
109
  ```
212
110
 
213
- ---
214
-
215
- ## Multiple Actions
111
+ ### 3. Actions & Guards Override
216
112
 
217
- Execute multiple actions per event.
113
+ Machine provides defaults. Component can override:
218
114
 
219
115
  ```ts
220
- on: {
221
- // Single action
222
- OPEN: 'open',
223
-
224
- // Multiple actions (array)
225
- CLOSE: ['clearHighlight', 'close'],
116
+ // Machine: default implementations
117
+ const machine = createMachine({
118
+ actions: {
119
+ scrollToItem: () => {}, // noop default
120
+ focusInput: () => {},
121
+ },
122
+ guards: {
123
+ canSelect: (ctx) => !ctx.disabled,
124
+ },
125
+ })
226
126
 
227
- // Conditional with multiple actions
228
- SELECT: [
229
- { when: (ctx) => ctx.disabled, do: 'noop' },
230
- { do: ['highlight', 'select', 'close'] },
231
- ],
232
- }
127
+ // Component: real implementations
128
+ useMachine(machine, {
129
+ input: { ... },
130
+ actions: {
131
+ scrollToItem: (ctx) => itemRefs.get(ctx.highlightedId)?.scrollIntoView(),
132
+ focusInput: () => inputRef.current?.focus(),
133
+ },
134
+ guards: {
135
+ canSelect: (ctx) => !ctx.disabled && ctx.items.length > 0,
136
+ },
137
+ })
233
138
  ```
234
139
 
235
140
  ---
236
141
 
237
- ## State-based Structure
238
-
239
- Define different handlers per state. Undefined events are ignored.
240
-
241
- The `state` value can come from either `computed` (recommended) or `input` directly.
242
-
243
- ### Approach 1: Computed State (Recommended)
142
+ ## API Reference
244
143
 
245
- Derive state from existing values. This aligns with the "controlled" philosophy—state is computed from the source of truth.
144
+ ### `createMachine<T>(config)`
246
145
 
247
146
  ```ts
248
- // Async data fetching example
249
147
  const machine = createMachine<{
250
- input: {
251
- data: Item[] | null
252
- isLoading: boolean
253
- error: Error | null
254
- setData: (data: Item[] | null) => void
255
- setIsLoading: (v: boolean) => void
256
- setError: (e: Error | null) => void
257
- }
258
- events: { FETCH: undefined; RETRY: undefined }
259
- computed: { state: 'idle' | 'loading' | 'error' | 'success' }
260
- actions: 'fetch' | 'retry'
261
- state: 'idle' | 'loading' | 'error' | 'success'
148
+ input: { count: number; setCount: (n: number) => void }
149
+ events: { INCREMENT: undefined; SET: { value: number } }
150
+ computed: { isPositive: boolean }
151
+ actions: 'increment' | 'set'
152
+ guards: 'canIncrement'
153
+ state: 'idle' | 'active' // Optional: for state-based handlers
262
154
  }>({
263
155
  computed: {
264
- state: (input) => {
265
- if (input.isLoading) return 'loading'
266
- if (input.error) return 'error'
267
- if (input.data) return 'success'
268
- return 'idle'
269
- },
156
+ isPositive: (input) => input.count > 0,
270
157
  },
271
- states: {
272
- idle: {
273
- on: { FETCH: 'fetch' },
274
- },
275
- loading: {
276
- // FETCH ignored while loading
277
- },
278
- error: {
279
- on: { RETRY: 'retry' },
280
- },
281
- success: {
282
- on: { FETCH: 'fetch' }, // Allow refetch
283
- },
158
+ on: {
159
+ INCREMENT: [{ when: 'canIncrement', do: 'increment' }],
160
+ SET: 'set',
284
161
  },
285
162
  actions: {
286
- fetch: async (ctx) => {
287
- ctx.setIsLoading(true)
288
- ctx.setError(null)
289
- // fetch logic...
290
- },
291
- retry: (ctx) => {
292
- ctx.setError(null)
293
- ctx.setIsLoading(true)
294
- // retry logic...
295
- },
163
+ increment: (ctx) => ctx.setCount(ctx.count + 1),
164
+ set: (ctx, payload) => ctx.setCount(payload.value),
165
+ },
166
+ guards: {
167
+ canIncrement: (ctx) => ctx.count < 10,
296
168
  },
297
169
  })
170
+ ```
298
171
 
299
- // React: manage individual values, state is derived
300
- function DataList() {
301
- const [data, setData] = useState<Item[] | null>(null)
302
- const [isLoading, setIsLoading] = useState(false)
303
- const [error, setError] = useState<Error | null>(null)
172
+ ### `useMachine(machine, options)`
304
173
 
305
- const { send, state } = useMachine(machine, {
306
- data, setData, isLoading, setIsLoading, error, setError,
307
- })
174
+ ```ts
175
+ const { send, computed, state } = useMachine(machine, {
176
+ input: { count, setCount },
177
+ actions: { /* optional overrides */ },
178
+ guards: { /* optional overrides */ },
179
+ })
308
180
 
309
- return (
310
- <div>
311
- {state === 'idle' && <button onClick={() => send('FETCH')}>Load</button>}
312
- {state === 'loading' && <Spinner />}
313
- {state === 'error' && <button onClick={() => send('RETRY')}>Retry</button>}
314
- {state === 'success' && <List items={data!} />}
315
- </div>
316
- )
317
- }
181
+ send('INCREMENT')
182
+ send('SET', { value: 5 })
318
183
  ```
319
184
 
320
- ### Approach 2: Direct State in Input
185
+ ---
321
186
 
322
- Use when state is explicitly managed as a single value.
187
+ ## Features
323
188
 
324
- ```ts
325
- // Modal with explicit state management
326
- const machine = createMachine<{
327
- input: {
328
- state: 'closed' | 'opening' | 'open' | 'closing'
329
- setState: (s: 'closed' | 'opening' | 'open' | 'closing') => void
330
- }
331
- events: { OPEN: undefined; CLOSE: undefined; ANIMATION_END: undefined }
332
- actions: 'startOpen' | 'completeOpen' | 'startClose' | 'completeClose'
333
- state: 'closed' | 'opening' | 'open' | 'closing'
334
- }>({
335
- states: {
336
- closed: {
337
- on: { OPEN: 'startOpen' },
338
- },
339
- opening: {
340
- on: { ANIMATION_END: 'completeOpen' },
341
- },
342
- open: {
343
- on: { CLOSE: 'startClose' },
344
- },
345
- closing: {
346
- on: { ANIMATION_END: 'completeClose' },
347
- },
348
- },
349
- actions: {
350
- startOpen: (ctx) => ctx.setState('opening'),
351
- completeOpen: (ctx) => ctx.setState('open'),
352
- startClose: (ctx) => ctx.setState('closing'),
353
- completeClose: (ctx) => ctx.setState('closed'),
354
- },
355
- })
189
+ ### Conditional Handlers
356
190
 
357
- // React: manage state directly
358
- function Modal() {
359
- const [state, setState] = useState<'closed' | 'opening' | 'open' | 'closing'>('closed')
360
- const { send } = useMachine(machine, { state, setState })
191
+ Branch logic with `when`. Stops at first match:
361
192
 
362
- return (
363
- <div
364
- className={`modal modal--${state}`}
365
- onAnimationEnd={() => send('ANIMATION_END')}
366
- >
367
- <button onClick={() => send('CLOSE')}>Close</button>
368
- </div>
369
- )
193
+ ```ts
194
+ on: {
195
+ TOGGLE: [
196
+ { when: (ctx) => ctx.disabled, do: [] }, // Function guard
197
+ { when: 'isOpen', do: 'close' }, // String guard (from guards config)
198
+ { do: 'open' }, // Default case
199
+ ],
370
200
  }
371
201
  ```
372
202
 
373
- ### Combining with Global Handlers
203
+ ### Multiple Actions
374
204
 
375
- State handlers run first, then global handlers:
205
+ Execute multiple actions per event:
376
206
 
377
207
  ```ts
378
- {
379
- states: {
380
- idle: { on: { LOG: 'logIdle' } },
381
- active: { on: { LOG: 'logActive' } },
382
- },
383
- on: {
384
- LOG: 'logGlobal', // Always runs after state handler
385
- },
208
+ on: {
209
+ SELECT: ['highlight', 'select', 'close'], // Array of actions
210
+
211
+ CONFIRM: [
212
+ { when: 'isValid', do: ['save', 'close', 'notify'] },
213
+ { do: 'showError' },
214
+ ],
386
215
  }
387
- // idle + LOG → logIdle, then logGlobal
388
216
  ```
389
217
 
390
- ---
391
-
392
- ## Effects
218
+ ### Computed Values
393
219
 
394
- Watch value changes and react to them. Access `send` in callbacks.
220
+ Derive values from input:
395
221
 
396
222
  ```ts
397
- effects: [
398
- {
399
- watch: (ctx) => ctx.hoveredId,
400
- enter: (ctx, { send }) => {
401
- // Called when watch value becomes truthy
402
- const timer = setTimeout(() => send('OPEN'), 300)
403
- return () => clearTimeout(timer) // cleanup
404
- },
405
- exit: (ctx, { send }) => {
406
- // Called when watch value becomes falsy
407
- send('CLOSE')
408
- },
409
- change: (ctx, prev, curr, { send }) => {
410
- // Called on any change
411
- console.log(`Changed from ${prev} to ${curr}`)
412
- },
413
- }
414
- ]
223
+ computed: {
224
+ isEmpty: (input) => input.items.length === 0,
225
+ canSubmit: (input) => input.value.length > 0 && !input.isLoading,
226
+ },
227
+
228
+ // Use in handlers
229
+ on: {
230
+ SUBMIT: [
231
+ { when: (ctx) => !ctx.canSubmit, do: [] },
232
+ { do: 'submit' },
233
+ ],
234
+ }
235
+
236
+ // Access from hook
237
+ const { computed } = useMachine(machine, { input })
238
+ if (computed.isEmpty) { /* ... */ }
415
239
  ```
416
240
 
417
- ### Async Operations with Cleanup
241
+ ### Effects
418
242
 
419
- Handle async requests and race conditions:
243
+ Watch value changes and react:
420
244
 
421
245
  ```ts
422
246
  effects: [
423
247
  {
424
- watch: (ctx) => ctx.searchQuery,
248
+ watch: (ctx) => ctx.highlightedId,
249
+ enter: (ctx, { send }) => {
250
+ // When watch becomes truthy
251
+ const timer = setTimeout(() => send('AUTO_SELECT'), 1000)
252
+ return () => clearTimeout(timer) // Cleanup
253
+ },
254
+ exit: () => {
255
+ // When watch becomes falsy
256
+ },
425
257
  change: (ctx, prev, curr, { send }) => {
426
- const controller = new AbortController()
427
-
428
- fetch(`/api/search?q=${curr}`, { signal: controller.signal })
429
- .then(res => res.json())
430
- .then(data => send('FETCH_SUCCESS', { data }))
431
- .catch(() => {})
432
-
433
- return () => controller.abort() // Cancel previous request
258
+ // On any change
259
+ console.log(`${prev} → ${curr}`)
434
260
  },
435
261
  },
436
262
  ]
437
263
  ```
438
264
 
439
- ### Effect Helper Function
440
-
441
- Use the `effect` helper for better type inference:
442
-
443
- ```ts
444
- import { effect } from 'controlled-machine'
445
-
446
- effects: [
447
- effect<Context, Events, string | null>({
448
- watch: (ctx) => ctx.focusedId,
449
- enter: (ctx, { send }) => { /* ... */ },
450
- }),
451
- ]
452
- ```
453
-
454
- ---
455
-
456
- ## Computed Values
265
+ ### State-based Handlers
457
266
 
458
- Derive values from input. Available in handlers and returned from the hook.
267
+ Different handlers per state:
459
268
 
460
269
  ```ts
461
270
  const machine = createMachine<{
462
- input: Input
463
- events: Events
464
- computed: { isEmpty: boolean; displayValue: string }
271
+ input: { state: 'idle' | 'loading' | 'error'; setState: (s) => void }
272
+ events: { FETCH: undefined; RETRY: undefined }
273
+ state: 'idle' | 'loading' | 'error'
465
274
  }>({
466
- computed: {
467
- isEmpty: (ctx) => ctx.items.length === 0,
468
- displayValue: (ctx) => ctx.selectedItem?.label ?? ctx.inputValue,
469
- },
470
- on: {
471
- CLEAR: [
472
- { when: (ctx) => ctx.isEmpty, do: 'noop' }, // Use computed in handlers
473
- { do: 'clear' },
474
- ],
275
+ states: {
276
+ idle: {
277
+ on: { FETCH: 'startFetch' },
278
+ },
279
+ loading: {
280
+ // FETCH ignored while loading
281
+ },
282
+ error: {
283
+ on: { RETRY: 'startFetch' },
284
+ },
475
285
  },
286
+ actions: { startFetch: (ctx) => ctx.setState('loading') },
476
287
  })
477
-
478
- // Access computed values
479
- const { computed } = useMachine(machine, input)
480
- if (computed.isEmpty) { /* ... */ }
481
288
  ```
482
289
 
483
- ---
484
-
485
- ## Always Rules
290
+ ### Always Rules
486
291
 
487
- Rules evaluated automatically on every context change.
292
+ Auto-evaluated on every context change:
488
293
 
489
294
  ```ts
490
295
  always: [
491
- { when: (ctx) => ctx.value < 0, do: 'resetToZero' },
492
- { when: (ctx) => ctx.value > 100, do: 'capToMax' },
296
+ { when: (ctx) => ctx.value < 0, do: 'clampToMin' },
297
+ { when: (ctx) => ctx.value > 100, do: 'clampToMax' },
493
298
  ]
494
299
  ```
495
300
 
496
301
  ---
497
302
 
498
- ## Vanilla JavaScript Usage
303
+ ## Vanilla Usage
499
304
 
500
305
  Use without React:
501
306
 
502
307
  ```ts
503
- import { createMachine } from 'controlled-machine'
504
-
505
- const machine = createMachine<{ /* types */ }>({
506
- on: { /* handlers */ },
507
- actions: { /* actions */ },
508
- })
308
+ const machine = createMachine({ /* config */ })
509
309
 
510
- // Send events with input
511
- machine.send('OPEN', { isOpen: false, setIsOpen: (v) => { /* ... */ } })
310
+ // Send events
311
+ machine.send('OPEN', { isOpen: false, onOpenChange: (v) => { /* ... */ } })
512
312
 
513
313
  // Evaluate effects
514
314
  machine.evaluate(input)
@@ -516,7 +316,7 @@ machine.evaluate(input)
516
316
  // Get computed values
517
317
  const computed = machine.getComputed(input)
518
318
 
519
- // Cleanup effects on unmount
319
+ // Cleanup
520
320
  machine.cleanup()
521
321
  ```
522
322
 
@@ -524,16 +324,16 @@ machine.cleanup()
524
324
 
525
325
  ## TypeScript
526
326
 
527
- ### Object-based Generic Types
327
+ ### Type Parameters
528
328
 
529
- Specify only the types you need in any order:
329
+ Specify only what you need:
530
330
 
531
331
  ```ts
532
332
  // Minimal
533
333
  createMachine<{
534
334
  input: MyInput
535
335
  events: MyEvents
536
- }>({ /* ... */ })
336
+ }>({ ... })
537
337
 
538
338
  // Full
539
339
  createMachine<{
@@ -541,32 +341,21 @@ createMachine<{
541
341
  events: MyEvents
542
342
  computed: MyComputed
543
343
  actions: 'action1' | 'action2'
544
- state: 'idle' | 'loading' | 'open'
545
- }>({ /* ... */ })
344
+ guards: 'guard1' | 'guard2'
345
+ state: 'idle' | 'active'
346
+ }>({ ... })
546
347
  ```
547
348
 
548
- ### Type Exports
349
+ ### Exports
549
350
 
550
351
  ```ts
551
- import type {
552
- MachineTypes,
553
- Machine,
554
- Send,
555
- Effect,
556
- Rule,
557
- Handler,
558
- } from 'controlled-machine'
352
+ import { createMachine, effect } from 'controlled-machine'
353
+ import { useMachine } from 'controlled-machine/react'
354
+ import type { Machine, Send, Rule, Handler, UseMachineOptions } from 'controlled-machine'
559
355
  ```
560
356
 
561
357
  ---
562
358
 
563
- ## Limitations
564
-
565
- - **No machine-to-machine communication** — Coordinate in parent components
566
- - **No parallel states** — Use separate state variables
567
-
568
- ---
569
-
570
359
  ## License
571
360
 
572
361
  MIT