controlled-machine 0.4.2 → 0.5.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.
Files changed (2) hide show
  1. package/README.md +148 -5
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,13 +1,132 @@
1
1
  # Controlled Machine
2
2
 
3
- Manage your React UI state cleanly.
3
+ > **⚠️ ARCHIVED**: 프로젝트는 아카이브되었습니다. 아래의 여정과 통찰을 바탕으로 새로운 접근 방식의 프로젝트가 진행될 예정입니다.
4
4
 
5
- - Separate logic from UI
6
- - Declarative state transitions
7
- - Full TypeScript support
5
+ ---
6
+
7
+ ## 문제 정의
8
+
9
+ ### XState와 useReducer가 잘 안 쓰이는 이유
10
+
11
+ XState와 useReducer는 상태 모델링에 강력한 도구다. 선언적으로 상태를 표현하고, 구현과 인터페이스를 분리할 수 있다. **그런데도 실제로는 잘 안 쓰인다.**
12
+
13
+ 이유는 **외부 상태에 닫혀 있기 때문**이다.
14
+
15
+ - 외부 상황을 무시하고 자신의 내부 상태만 철저히 관리한다
16
+ - 오직 이벤트를 통해서만 소통하려 한다
17
+
18
+ React 컴포넌트 생태계에서 가장 강력한 힘을 가진 것은 **props로 전달되는 외부 상태**다. 하지만 reducer나 XState는 이 외부 상태를 다루기 까다롭다.
19
+
20
+ ```
21
+ ┌─────────────────┐ ┌─────────────────┐
22
+ │ 외부 상태 │ ──?──▶ │ 내부 머신 │
23
+ │ (props) │ │ (uncontrolled) │
24
+ │ │ ◀──?─── │ │
25
+ └─────────────────┘ └─────────────────┘
26
+
27
+ 1. 외부 → 내부: 어떤 이벤트를 보낼지?
28
+ 2. 내부 → 외부: 어떻게 동기화할지?
29
+ 3. 충돌 시: 누가 이길지?
30
+ ```
31
+
32
+ **도구의 한계가 개념의 가치까지 묻어버리는 셈이다.**
33
+
34
+ ---
35
+
36
+ ## 여정
37
+
38
+ ```
39
+ 목표: XState/useReducer를 "controlled"처럼 쓰고 싶다
40
+
41
+
42
+ 시도 1: 새로운 상태 머신 라이브러리 만들기 (controlled-machine)
43
+
44
+ ├─ input/internal/computed 분리
45
+ ├─ FSM 상태 추가
46
+ ├─ Discriminated Union 지원
47
+
48
+
49
+ 문제: 결국 XState와 비슷해지면서 복잡해짐
50
+
51
+
52
+ 시도 2: 합성 가능한 구조로 분리
53
+
54
+ ├─ Core는 단순하게
55
+ ├─ 확장은 Wrapper로
56
+
57
+
58
+ 깨달음: 기존 도구(XState, useReducer)를 그대로 쓰고
59
+ "controlled wrapper"만 만들면 되잖아?
60
+
61
+
62
+ 최종 목표: controlled(xstate), controlled(useReducer)
63
+ ```
64
+
65
+ ---
66
+
67
+ ## 통찰
68
+
69
+ ### 1. 문제를 복잡하게 풀지 마라
70
+
71
+ - input/internal/computed 분리 → 복잡한 조합 로직
72
+ - Managed mode/Computed mode → 두 가지 경로 관리
73
+ - state/discriminatedState → 타입 시스템 복잡화
74
+
75
+ **해결책**: 문제 자체를 없애거나, 단일 책임으로 분리
76
+
77
+ ### 2. 기존 도구를 활용하라
78
+
79
+ XState와 useReducer는 이미 검증됨. 새로 만들 필요 없이 **동기화 문제만 해결**하면 됨.
80
+
81
+ ### 3. 진짜 해결해야 할 문제
82
+
83
+ > 외부 상태(props)와 내부 상태(machine/reducer)를 어떻게 안전하게 동기화할 것인가?
84
+
85
+ 이 하나의 문제만 잘 풀면 된다.
86
+
87
+ ---
88
+
89
+ ## 새로운 방향
90
+
91
+ 기존 상태 관리 도구를 그대로 사용하고, **동기화만 해결하는 wrapper**를 만든다:
92
+
93
+ ```typescript
94
+ import { createMachine } from 'xstate'
95
+ import { controlled } from 'controlled-wrapper' // 새 프로젝트
96
+
97
+ const xstateMachine = createMachine({ ... })
98
+ const controlledMachine = controlled(xstateMachine, {
99
+ sync: { ... }, // 외부 → 내부 (이벤트 변환)
100
+ notify: { ... }, // 내부 → 외부 (콜백 호출)
101
+ conflict: { ... } // 충돌 해결
102
+ })
103
+ ```
104
+
105
+ 자세한 내용은 [`docs/`](./docs/) 폴더 참조:
106
+ - [PROBLEM.md](./docs/PROBLEM.md) - 문제 정의
107
+ - [JOURNEY.md](./docs/JOURNEY.md) - 여정
108
+ - [INTERFACE.md](./docs/INTERFACE.md) - 새 인터페이스 설계
109
+ - [REJECTED.md](./docs/REJECTED.md) - 거부된 접근 방식들
110
+
111
+ ---
112
+
113
+ ---
114
+
115
+ # 아래는 기존 문서 (참고용)
116
+
117
+ ---
118
+
119
+ ## Original: Controlled Machine (v0.4.x)
120
+
121
+ A controlled state machine with **internal state management**.
122
+ Machine owns **its own state**. Your component passes **external data**.
8
123
 
9
124
  ```bash
10
125
  npm install controlled-machine
126
+ # or
127
+ yarn add controlled-machine
128
+ # or
129
+ pnpm add controlled-machine
11
130
  ```
12
131
 
13
132
  ---
@@ -435,6 +554,30 @@ createMachine<{
435
554
  { when: ['isEnabled', 'canIncrement', (ctx) => !ctx.isLoading], do: ... }
436
555
  ```
437
556
 
557
+ ### Guard Utilities
558
+
559
+ Compose guards with `not`, `and`, `or`:
560
+
561
+ ```ts
562
+ import { createMachine, not, and, or } from 'controlled-machine'
563
+
564
+ // not() - negate a guard
565
+ { when: not('isDisabled'), do: 'handleClick' }
566
+ { when: not((ctx) => ctx.loading), do: 'submit' }
567
+
568
+ // and() - all guards must pass
569
+ { when: and(['hasValue', 'isValid']), do: 'submit' }
570
+
571
+ // or() - at least one guard must pass
572
+ { when: or(['isAdmin', 'hasPermission']), do: 'delete' }
573
+
574
+ // Nested composition
575
+ { when: not(or(['isLoading', 'isDisabled'])), do: 'handleClick' }
576
+
577
+ // Mixed named and inline guards
578
+ { when: and(['hasValue', (ctx) => ctx.count > 0]), do: 'action' }
579
+ ```
580
+
438
581
  ---
439
582
 
440
583
  ## Computed Values
@@ -695,7 +838,7 @@ createMachine<{
695
838
  ### Exports
696
839
 
697
840
  ```ts
698
- import { createMachine, effect } from 'controlled-machine'
841
+ import { createMachine, effect, not, and, or } from 'controlled-machine'
699
842
  import { useMachine } from 'controlled-machine/react'
700
843
  import type {
701
844
  MachineTypes,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "controlled-machine",
3
- "version": "0.4.2",
4
- "description": "A controlled state machine where state lives outside the machine",
3
+ "version": "0.5.0",
4
+ "description": "[ARCHIVED] A controlled state machine experiment - see README for insights and new direction",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "exports": {