@doeixd/machine 0.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.
package/src/solid.ts ADDED
@@ -0,0 +1,502 @@
1
+ /**
2
+ * @file Solid.js integration for @doeixd/machine
3
+ * @description
4
+ * Provides reactive primitives for using state machines with Solid.js, including
5
+ * hooks for both sync and async machines, store integration, and signal-based APIs.
6
+ *
7
+ * Solid.js uses fine-grained reactivity with signals and stores, which pairs
8
+ * beautifully with immutable state machines. This integration provides multiple
9
+ * approaches depending on your needs:
10
+ *
11
+ * - `createMachine()` - Signal-based reactive machine
12
+ * - `createMachineStore()` - Store-based reactive machine (for complex context)
13
+ * - `createAsyncMachine()` - Async machine with signal state
14
+ * - `createMachineResource()` - Resource-based async machine
15
+ */
16
+
17
+ import {
18
+ createSignal,
19
+ createEffect,
20
+ createMemo,
21
+ onCleanup,
22
+ type Accessor,
23
+ type Setter
24
+ } from 'solid-js';
25
+ import { createStore, type SetStoreFunction, type Store, produce } from 'solid-js/store';
26
+ import { Machine, AsyncMachine, Event, Context, runMachine as runMachineCore } from './index';
27
+
28
+ // =============================================================================
29
+ // SIGNAL-BASED MACHINE (for simple state)
30
+ // =============================================================================
31
+
32
+ /**
33
+ * Creates a reactive machine using Solid signals.
34
+ *
35
+ * This is ideal for simple state machines where the entire machine state
36
+ * needs to be tracked reactively. Every transition creates a new machine
37
+ * instance, and the signal updates automatically.
38
+ *
39
+ * @template M - The machine type.
40
+ *
41
+ * @param initialMachine - A function that returns the initial machine state.
42
+ * @returns A tuple of [accessor, transitions] where:
43
+ * - accessor: Reactive accessor for the current machine
44
+ * - transitions: Object with all machine transitions bound to update the signal
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * const [machine, actions] = createMachine(() =>
49
+ * createCounterMachine({ count: 0 })
50
+ * );
51
+ *
52
+ * // In your component
53
+ * <div>
54
+ * <p>Count: {machine().context.count}</p>
55
+ * <button onClick={actions.increment}>Increment</button>
56
+ * </div>
57
+ * ```
58
+ *
59
+ * @example With Type-State
60
+ * ```tsx
61
+ * type LoggedOut = Machine<{ status: "loggedOut" }> & {
62
+ * login: (user: string) => LoggedIn;
63
+ * };
64
+ *
65
+ * type LoggedIn = Machine<{ status: "loggedIn"; user: string }> & {
66
+ * logout: () => LoggedOut;
67
+ * };
68
+ *
69
+ * const [auth, actions] = createMachine<LoggedOut | LoggedIn>(() =>
70
+ * createLoggedOut()
71
+ * );
72
+ *
73
+ * // Conditional rendering based on state type
74
+ * <Show when={auth().context.status === 'loggedIn'}>
75
+ * <p>Welcome, {auth().context.user}</p>
76
+ * <button onClick={actions.logout}>Logout</button>
77
+ * </Show>
78
+ * ```
79
+ */
80
+ export function createMachine<M extends Machine<any>>(
81
+ initialMachine: () => M
82
+ ): [Accessor<M>, TransitionHandlers<M>] {
83
+ const [machine, setMachine] = createSignal<M>(initialMachine());
84
+
85
+ // Extract all transition methods and bind them to update the signal
86
+ const { context, ...transitions } = machine();
87
+
88
+ const handlers = Object.fromEntries(
89
+ Object.entries(transitions).map(([key, fn]) => [
90
+ key,
91
+ (...args: any[]) => {
92
+ const currentMachine = machine();
93
+ const nextMachine = (currentMachine as any)[key](...args);
94
+ setMachine(() => nextMachine);
95
+ return nextMachine;
96
+ }
97
+ ])
98
+ ) as TransitionHandlers<M>;
99
+
100
+ return [machine, handlers];
101
+ }
102
+
103
+ /**
104
+ * Helper type to extract transition handlers from a machine.
105
+ */
106
+ type TransitionHandlers<M extends Machine<any>> = {
107
+ [K in keyof Omit<M, 'context'>]: M[K] extends (...args: infer Args) => infer R
108
+ ? (...args: Args) => R
109
+ : never;
110
+ };
111
+
112
+ // =============================================================================
113
+ // STORE-BASED MACHINE (for complex context with fine-grained reactivity)
114
+ // =============================================================================
115
+
116
+ /**
117
+ * Creates a reactive machine using Solid stores.
118
+ *
119
+ * This is ideal when you have complex nested context and want fine-grained
120
+ * reactivity on individual properties. Instead of replacing the entire machine,
121
+ * transitions update the store, triggering only the affected computations.
122
+ *
123
+ * @template M - The machine type.
124
+ *
125
+ * @param initialMachine - A function that returns the initial machine state.
126
+ * @returns A tuple of [store, actions] where:
127
+ * - store: Reactive store proxy for the machine
128
+ * - actions: Transition handlers that update the store
129
+ *
130
+ * @example
131
+ * ```tsx
132
+ * const [machine, actions] = createMachineStore(() =>
133
+ * createUserMachine({
134
+ * profile: { name: 'Alice', age: 30 },
135
+ * settings: { theme: 'dark', notifications: true }
136
+ * })
137
+ * );
138
+ *
139
+ * // Fine-grained reactivity - only updates when profile.name changes
140
+ * <div>
141
+ * <p>Name: {machine.context.profile.name}</p>
142
+ * <p>Age: {machine.context.profile.age}</p>
143
+ * <button onClick={() => actions.updateName('Bob')}>Change Name</button>
144
+ * </div>
145
+ * ```
146
+ */
147
+ export function createMachineStore<M extends Machine<any>>(
148
+ initialMachine: () => M
149
+ ): [Store<M>, SetStoreFunction<M>, TransitionHandlers<M>] {
150
+ const initial = initialMachine();
151
+ const [store, setStore] = createStore<M>(initial);
152
+
153
+ const { context, ...transitions } = initial;
154
+
155
+ const handlers = Object.fromEntries(
156
+ Object.entries(transitions).map(([key, fn]) => [
157
+ key,
158
+ (...args: any[]) => {
159
+ const nextMachine = (store as any)[key](...args);
160
+ setStore(() => nextMachine);
161
+ return nextMachine;
162
+ }
163
+ ])
164
+ ) as TransitionHandlers<M>;
165
+
166
+ return [store, setStore, handlers];
167
+ }
168
+
169
+ // =============================================================================
170
+ // ASYNC MACHINE WITH SIGNALS
171
+ // =============================================================================
172
+
173
+ /**
174
+ * Creates a reactive async machine with event dispatching.
175
+ *
176
+ * This wraps the core `runMachine` with Solid reactivity, automatically
177
+ * updating a signal whenever the machine state changes. Perfect for async
178
+ * workflows like data fetching, multi-step forms, or any stateful async logic.
179
+ *
180
+ * @template M - The async machine type.
181
+ *
182
+ * @param initialMachine - A function that returns the initial async machine state.
183
+ * @returns A tuple of [accessor, dispatch] where:
184
+ * - accessor: Reactive accessor for current machine state
185
+ * - dispatch: Type-safe event dispatcher
186
+ *
187
+ * @example Basic data fetching
188
+ * ```tsx
189
+ * type FetchMachine = AsyncMachine<{
190
+ * status: 'idle' | 'loading' | 'success' | 'error';
191
+ * data: any;
192
+ * }> & {
193
+ * fetch: () => Promise<FetchMachine>;
194
+ * retry: () => Promise<FetchMachine>;
195
+ * };
196
+ *
197
+ * const [state, dispatch] = createAsyncMachine(() => createFetchMachine());
198
+ *
199
+ * <div>
200
+ * <Switch>
201
+ * <Match when={state().context.status === 'idle'}>
202
+ * <button onClick={() => dispatch({ type: 'fetch', args: [] })}>
203
+ * Load Data
204
+ * </button>
205
+ * </Match>
206
+ * <Match when={state().context.status === 'loading'}>
207
+ * <p>Loading...</p>
208
+ * </Match>
209
+ * <Match when={state().context.status === 'success'}>
210
+ * <p>Data: {JSON.stringify(state().context.data)}</p>
211
+ * </Match>
212
+ * <Match when={state().context.status === 'error'}>
213
+ * <button onClick={() => dispatch({ type: 'retry', args: [] })}>
214
+ * Retry
215
+ * </button>
216
+ * </Match>
217
+ * </Switch>
218
+ * </div>
219
+ * ```
220
+ *
221
+ * @example With effects
222
+ * ```tsx
223
+ * const [state, dispatch] = createAsyncMachine(() => createAuthMachine());
224
+ *
225
+ * // React to state changes
226
+ * createEffect(() => {
227
+ * console.log('Auth state changed:', state().context.status);
228
+ *
229
+ * if (state().context.status === 'loggedIn') {
230
+ * // Navigate, fetch user data, etc.
231
+ * }
232
+ * });
233
+ * ```
234
+ */
235
+ export function createAsyncMachine<M extends AsyncMachine<any>>(
236
+ initialMachine: () => M
237
+ ): [Accessor<M>, (event: Event<M>) => Promise<M>] {
238
+ const [machine, setMachine] = createSignal<M>(initialMachine());
239
+
240
+ // Create the runner with signal update callback
241
+ const runner = runMachineCore(initialMachine(), (nextMachine) => {
242
+ setMachine(() => nextMachine as M);
243
+ });
244
+
245
+ const dispatch = async (event: Event<M>): Promise<M> => {
246
+ const result = await runner.dispatch(event);
247
+ return result as M;
248
+ };
249
+
250
+ return [machine, dispatch];
251
+ }
252
+
253
+ // =============================================================================
254
+ // CONTEXT-ONLY STORE (for just the context data)
255
+ // =============================================================================
256
+
257
+ /**
258
+ * Creates a Solid store for just the machine's context, with actions that
259
+ * transition the machine and sync the context back to the store.
260
+ *
261
+ * This is useful when you want fine-grained reactivity on context properties
262
+ * but don't need to track the machine instance itself.
263
+ *
264
+ * @template C - The context object type.
265
+ * @template M - The machine type.
266
+ *
267
+ * @param initialMachine - A function that returns the initial machine.
268
+ * @returns A tuple of [context store, setContext, actions].
269
+ *
270
+ * @example
271
+ * ```tsx
272
+ * const [context, setContext, actions] = createMachineContext(() =>
273
+ * createCounterMachine({ count: 0, name: 'Counter' })
274
+ * );
275
+ *
276
+ * // Direct access to context with fine-grained reactivity
277
+ * <div>
278
+ * <p>{context.name}: {context.count}</p>
279
+ * <button onClick={actions.increment}>+</button>
280
+ * </div>
281
+ * ```
282
+ */
283
+ export function createMachineContext<C extends object, M extends Machine<C>>(
284
+ initialMachine: () => M
285
+ ): [Store<C>, SetStoreFunction<C>, TransitionHandlers<M>] {
286
+ let currentMachine = initialMachine();
287
+ const [context, setContext] = createStore<C>(currentMachine.context);
288
+
289
+ const { context: _, ...transitions } = currentMachine;
290
+
291
+ const handlers = Object.fromEntries(
292
+ Object.entries(transitions).map(([key, fn]) => [
293
+ key,
294
+ (...args: any[]) => {
295
+ const nextMachine = (currentMachine as any)[key](...args);
296
+ currentMachine = nextMachine;
297
+ setContext(() => nextMachine.context);
298
+ return nextMachine;
299
+ }
300
+ ])
301
+ ) as TransitionHandlers<M>;
302
+
303
+ return [context, setContext, handlers];
304
+ }
305
+
306
+ // =============================================================================
307
+ // MEMOIZED MACHINE DERIVATIONS
308
+ // =============================================================================
309
+
310
+ /**
311
+ * Creates a memoized derivation from a machine's context.
312
+ *
313
+ * This is useful for computed values that depend on the machine state.
314
+ * The computation only re-runs when the accessed context properties change.
315
+ *
316
+ * @template M - The machine type.
317
+ * @template T - The computed value type.
318
+ *
319
+ * @param machine - Machine accessor.
320
+ * @param selector - Function to compute a value from the context.
321
+ * @returns A memoized accessor for the computed value.
322
+ *
323
+ * @example
324
+ * ```tsx
325
+ * const [machine, actions] = createMachine(() => createCart());
326
+ *
327
+ * const total = createMachineSelector(machine, (ctx) =>
328
+ * ctx.items.reduce((sum, item) => sum + item.price, 0)
329
+ * );
330
+ *
331
+ * <div>
332
+ * <p>Total: ${total()}</p>
333
+ * </div>
334
+ * ```
335
+ */
336
+ export function createMachineSelector<M extends Machine<any>, T>(
337
+ machine: Accessor<M>,
338
+ selector: (context: Context<M>) => T
339
+ ): Accessor<T> {
340
+ return createMemo(() => selector(machine().context));
341
+ }
342
+
343
+ // =============================================================================
344
+ // BATCH TRANSITIONS
345
+ // =============================================================================
346
+
347
+ /**
348
+ * Batches multiple transitions into a single reactive update.
349
+ *
350
+ * In Solid, this uses `batch` to group updates, preventing intermediate
351
+ * re-renders and effects from firing.
352
+ *
353
+ * @template M - The machine type.
354
+ *
355
+ * @param machine - The current machine.
356
+ * @param setMachine - The setter function.
357
+ * @param transitions - Array of transition functions to apply.
358
+ * @returns The final machine state.
359
+ *
360
+ * @example
361
+ * ```tsx
362
+ * import { batch } from 'solid-js';
363
+ *
364
+ * const [machine, setMachine] = createSignal(createCounterMachine());
365
+ *
366
+ * const batchUpdate = () => {
367
+ * batch(() => {
368
+ * let m = machine();
369
+ * m = m.increment();
370
+ * m = m.add(5);
371
+ * m = m.increment();
372
+ * setMachine(m);
373
+ * });
374
+ * };
375
+ * ```
376
+ */
377
+ export function batchTransitions<M extends Machine<any>>(
378
+ machine: M,
379
+ setMachine: Setter<M>,
380
+ ...transitions: Array<(m: M) => M>
381
+ ): M {
382
+ const { batch } = require('solid-js');
383
+
384
+ return batch(() => {
385
+ const finalMachine = transitions.reduce((m, transition) => transition(m), machine);
386
+ setMachine(finalMachine);
387
+ return finalMachine;
388
+ });
389
+ }
390
+
391
+ // =============================================================================
392
+ // LIFECYCLE EFFECTS
393
+ // =============================================================================
394
+
395
+ /**
396
+ * Runs an effect when entering or exiting specific machine states.
397
+ *
398
+ * This is useful for side effects that should happen on state transitions,
399
+ * like analytics, logging, or subscriptions.
400
+ *
401
+ * @template M - The machine type.
402
+ *
403
+ * @param machine - Machine accessor.
404
+ * @param statePredicate - Function to determine if we're in the target state.
405
+ * @param onEnter - Effect to run when entering the state.
406
+ * @param onExit - Optional effect to run when exiting the state.
407
+ *
408
+ * @example
409
+ * ```tsx
410
+ * const [machine, actions] = createMachine(() => createAuthMachine());
411
+ *
412
+ * createMachineEffect(
413
+ * machine,
414
+ * (m) => m.context.status === 'loggedIn',
415
+ * (m) => {
416
+ * console.log('User logged in:', m.context.username);
417
+ * // Start session tracking
418
+ * },
419
+ * () => {
420
+ * console.log('User logged out');
421
+ * // Clean up session
422
+ * }
423
+ * );
424
+ * ```
425
+ */
426
+ export function createMachineEffect<M extends Machine<any>>(
427
+ machine: Accessor<M>,
428
+ statePredicate: (m: M) => boolean,
429
+ onEnter: (m: M) => void,
430
+ onExit?: () => void
431
+ ): void {
432
+ let wasInState = false;
433
+
434
+ createEffect(() => {
435
+ const m = machine();
436
+ const isInState = statePredicate(m);
437
+
438
+ if (isInState && !wasInState) {
439
+ // Entering state
440
+ onEnter(m);
441
+ wasInState = true;
442
+ } else if (!isInState && wasInState) {
443
+ // Exiting state
444
+ onExit?.();
445
+ wasInState = false;
446
+ }
447
+ });
448
+
449
+ onCleanup(() => {
450
+ if (wasInState && onExit) {
451
+ onExit();
452
+ }
453
+ });
454
+ }
455
+
456
+ /**
457
+ * Helper to create effects for specific context values.
458
+ *
459
+ * @template M - The machine type.
460
+ * @template T - The selected value type.
461
+ *
462
+ * @param machine - Machine accessor.
463
+ * @param selector - Function to select a value from context.
464
+ * @param effect - Effect to run when the selected value changes.
465
+ *
466
+ * @example
467
+ * ```tsx
468
+ * const [machine, actions] = createMachine(() => createCounterMachine());
469
+ *
470
+ * createMachineValueEffect(
471
+ * machine,
472
+ * (ctx) => ctx.count,
473
+ * (count) => {
474
+ * console.log('Count changed to:', count);
475
+ * if (count > 10) {
476
+ * alert('Count is high!');
477
+ * }
478
+ * }
479
+ * );
480
+ * ```
481
+ */
482
+ export function createMachineValueEffect<M extends Machine<any>, T>(
483
+ machine: Accessor<M>,
484
+ selector: (context: Context<M>) => T,
485
+ effect: (value: T) => void
486
+ ): void {
487
+ createEffect(() => {
488
+ const value = selector(machine().context);
489
+ effect(value);
490
+ });
491
+ }
492
+
493
+ // =============================================================================
494
+ // EXPORT TYPES FOR BETTER DX
495
+ // =============================================================================
496
+
497
+ export type {
498
+ Accessor,
499
+ Setter,
500
+ Store,
501
+ SetStoreFunction
502
+ };
package/src/test.ts ADDED
@@ -0,0 +1,207 @@
1
+ /**
2
+ * A utility type that represents either a value of type T or a Promise that resolves to T.
3
+ * @template T - The value type
4
+ */
5
+ type MaybePromise<T> = T | Promise<T>
6
+
7
+ // /**
8
+ // * A record of synchronous state transition functions.
9
+ // * Each function receives the machine context as `this` and returns a new Machine state.
10
+ // * @template C - The context object type
11
+ // */
12
+ // type Functions<C extends object> =
13
+ // Record<string, (this: C, ...args: any[]) => Machine<C>>
14
+
15
+ /**
16
+ * A record of asynchronous state transition functions.
17
+ * Each function receives the machine context as `this` and returns either a Machine or Promise<Machine>.
18
+ * @template C - The context object type
19
+ */
20
+ type AsyncFunctions<C extends object> =
21
+ Record<string, (this: C, ...args: any[]) => MaybePromise<Machine<C>>>
22
+
23
+ /**
24
+ * A synchronous state machine with a context object and transition functions.
25
+ * @template C - The context object type
26
+ * @example
27
+ * const machine: Machine<{ count: number }> = {
28
+ * context: { count: 0 },
29
+ * increment: function() {
30
+ * return createMachine({ count: this.count + 1 }, this)
31
+ * }
32
+ * }
33
+ */
34
+ export type Machine<C extends object> = { context: C } & Functions<C>
35
+
36
+ /**
37
+ * An asynchronous state machine with a context object and async transition functions.
38
+ * @template C - The context object type
39
+ * @example
40
+ * const machine: AsyncMachine<{ loading: boolean }> = {
41
+ * context: { loading: false },
42
+ * fetch: async function() {
43
+ * return createAsyncMachine({ loading: true }, this)
44
+ * }
45
+ * }
46
+ */
47
+ export type AsyncMachine<C extends object> = { context: C } & AsyncFunctions<C>
48
+
49
+ /**
50
+ * Creates a synchronous state machine from a context and transition functions.
51
+ * @template C - The context object type
52
+ * @param {C} context - The initial state context
53
+ * @param {Functions<C>} fns - Object containing transition function definitions
54
+ * @returns {Machine<C>} A new machine instance
55
+ * @example
56
+ * const counter = createMachine(
57
+ * { count: 0 },
58
+ * {
59
+ * increment: function() {
60
+ * return createMachine({ count: this.count + 1 }, this)
61
+ * },
62
+ * decrement: function() {
63
+ * return createMachine({ count: this.count - 1 }, this)
64
+ * }
65
+ * }
66
+ * )
67
+ * const next = counter.increment() // { context: { count: 1 }, increment, decrement }
68
+ */
69
+ export function createMachine<C extends object>(
70
+ context: C,
71
+ fns: Functions<C>
72
+ ): Machine<C> {
73
+ return Object.assign({ context }, fns)
74
+ }
75
+
76
+ /**
77
+ * Creates an asynchronous state machine from a context and async transition functions.
78
+ * @template C - The context object type
79
+ * @param {C} context - The initial state context
80
+ * @param {AsyncFunctions<C>} fns - Object containing async transition function definitions
81
+ * @returns {AsyncMachine<C>} A new async machine instance
82
+ * @example
83
+ * const user = createAsyncMachine(
84
+ * { id: null, loading: false },
85
+ * {
86
+ * fetchUser: async function(userId) {
87
+ * const data = await fetch(`/api/users/${userId}`)
88
+ * return createAsyncMachine({ id: data.id, loading: false }, this)
89
+ * }
90
+ * }
91
+ * )
92
+ */
93
+ export function createAsyncMachine<C extends object>(
94
+ context: C,
95
+ fns: AsyncFunctions<C>
96
+ ): AsyncMachine<C> {
97
+ return Object.assign({ context }, fns)
98
+ }
99
+
100
+ /**
101
+ * Creates a new machine state by applying an update function to the current machine's context.
102
+ * Preserves all transition functions from the original machine.
103
+ * @template C - The context object type
104
+ * @param {Machine<C>} m - The current machine state
105
+ * @param {Function} update - A function that takes the current read-only context and returns an updated context
106
+ * @returns {Machine<C>} A new machine with updated context but same transition functions
107
+ * @example
108
+ * const counter = createMachine({ count: 0 }, { })
109
+ * const incremented = next(counter, (ctx) => ({ count: ctx.count + 1 }))
110
+ * // incremented.context.count === 1
111
+ */
112
+ export function next<C extends object>(
113
+ m: Machine<C>,
114
+ update: (ctx: Readonly<C>) => C
115
+ ): Machine<C> {
116
+ return createMachine(update(m.context), m)
117
+ }
118
+
119
+ /**
120
+ * A discriminated union type representing an event that can be dispatched to a machine.
121
+ * Each event has a `type` property matching a transition function name and `args` matching that function's parameters.
122
+ * @template M - The machine type
123
+ * @example
124
+ * type CounterEvent = Event<Machine<{ count: number }>& {
125
+ * increment: () => any
126
+ * addValue: (n: number) => any
127
+ * }>
128
+ * // CounterEvent = { type: "increment"; args: [] } | { type: "addValue"; args: [number] }
129
+ */
130
+ export type Event<M> = {
131
+ [K in keyof M & string]: M[K] extends (...args: infer A) => any
132
+ ? { type: K; args: A }
133
+ : never
134
+ }[keyof M & string]
135
+
136
+ /**
137
+ * Runs an asynchronous state machine with event dispatch capability.
138
+ * Provides a managed interface to dispatch events and track state changes.
139
+ * @template C - The context object type
140
+ * @param {AsyncMachine<C>} initial - The initial machine state
141
+ * @param {Function} onChange - Optional callback invoked whenever the machine state changes
142
+ * @returns {Object} An object with a state getter and dispatch function
143
+ * @returns {C} returns.state - The current machine context
144
+ * @returns {Function} returns.dispatch - Async function to dispatch events to the machine
145
+ * @example
146
+ * const machine = createAsyncMachine(
147
+ * { count: 0 },
148
+ * {
149
+ * increment: async function() {
150
+ * return createAsyncMachine({ count: this.count + 1 }, this)
151
+ * }
152
+ * }
153
+ * )
154
+ * const runner = runMachine(machine, (m) => console.log("State changed:", m.context))
155
+ * await runner.dispatch({ type: "increment", args: [] })
156
+ * console.log(runner.state) // { count: 1 }
157
+ */
158
+ export function runMachine<C extends object>(
159
+ initial: AsyncMachine<C>,
160
+ onChange?: (m: AsyncMachine<C>) => void
161
+ ) {
162
+ let current = initial
163
+
164
+ async function dispatch<E extends Event<typeof current>>(event: E) {
165
+ const fn = current[event.type] as any
166
+ if (!fn) throw new Error(`Unknown event: ${event.type}`)
167
+ const next = await fn.apply(current.context, event.args)
168
+ current = next
169
+ onChange?.(current)
170
+ return current
171
+ }
172
+
173
+ return {
174
+ get state() {
175
+ return current.context
176
+ },
177
+ dispatch,
178
+ }
179
+ }
180
+
181
+
182
+
183
+
184
+ type Functions<C extends object> =
185
+ Record<string, (this: C, ...args: any[]) => Machine<any>>
186
+
187
+ // Simple counter example using the functional API
188
+ // Note: In the real library, transition functions receive context as `this`
189
+ const counterFns = {
190
+ increment: function() {
191
+ // `this` is bound to the context by the library
192
+ return createMachine({ count: (this as any).count + 1 }, counterFns);
193
+ },
194
+ decrement: function() {
195
+ // `this` is bound to the context by the library
196
+ return createMachine({ count: (this as any).count - 1 }, counterFns);
197
+ }
198
+ };
199
+
200
+ const counter = createMachine(
201
+ { count: 0 },
202
+ counterFns
203
+ );
204
+
205
+ // Test by calling with proper context binding
206
+ const result = counterFns.increment.call(counter.context);
207
+ console.log('Result:', result.context.count);