coremachine 1.0.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 +291 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# Coremachine
|
|
2
|
+
|
|
3
|
+
A type-safe state machine built on top of Hypercore for distributed, append-only state management. Coremachine combines the power of state machines with the reliability and synchronization capabilities of Hypercore, making it perfect for building distributed applications that need consistent state across multiple peers.
|
|
4
|
+
|
|
5
|
+
Coremachine extends a Duplex stream — write actions in, read state changes out.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🔒 **Type Safety**: Full TypeScript support with automatic type inference
|
|
10
|
+
- 🌐 **Distributed**: Built on Hypercore for peer-to-peer state synchronization
|
|
11
|
+
- 📝 **Append-Only**: Immutable state history with complete audit trail
|
|
12
|
+
- 🎯 **State Machine**: Predictable state transitions with guards and actions
|
|
13
|
+
- 🔀 **Streamable**: Duplex stream interface — pipe actions in, pipe state out
|
|
14
|
+
- 📦 **Lightweight**: Minimal dependencies, maximum functionality
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install coremachine
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import Corestore from "corestore";
|
|
26
|
+
import { createMachine, Coremachine } from "coremachine";
|
|
27
|
+
|
|
28
|
+
// Define your state machine
|
|
29
|
+
const definition = createMachine({
|
|
30
|
+
initial: "idle",
|
|
31
|
+
context: {
|
|
32
|
+
running: false,
|
|
33
|
+
counter: 0,
|
|
34
|
+
},
|
|
35
|
+
states: {
|
|
36
|
+
idle: {
|
|
37
|
+
on: {
|
|
38
|
+
START: {
|
|
39
|
+
target: "running",
|
|
40
|
+
action: (ctx, counter) => {
|
|
41
|
+
ctx.running = true;
|
|
42
|
+
ctx.counter = counter;
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
running: {
|
|
48
|
+
on: {
|
|
49
|
+
INCREMENT: {
|
|
50
|
+
target: "running",
|
|
51
|
+
action: (ctx) => {
|
|
52
|
+
ctx.counter++;
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
STOP: {
|
|
56
|
+
target: "idle",
|
|
57
|
+
action: (ctx, finalCount) => {
|
|
58
|
+
ctx.running = false;
|
|
59
|
+
if (finalCount !== undefined) {
|
|
60
|
+
ctx.counter = finalCount;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Initialize Coremachine
|
|
70
|
+
const store = new Corestore("./store");
|
|
71
|
+
const cube = new Coremachine(
|
|
72
|
+
store.get({ name: "coremachine", valueEncoding: "json" }),
|
|
73
|
+
definition,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Call action directly
|
|
77
|
+
await cube.action("START", 1);
|
|
78
|
+
console.log(cube.state, cube.context); // "running", { running: true, counter: 1 }
|
|
79
|
+
|
|
80
|
+
// Or write actions into the stream
|
|
81
|
+
cube.write({ event: "INCREMENT" });
|
|
82
|
+
|
|
83
|
+
// Read state changes out
|
|
84
|
+
cube.on("data", ({ state, context }) => {
|
|
85
|
+
console.log(state, context); // "running", { running: true, counter: 2 }
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Core Concepts
|
|
90
|
+
|
|
91
|
+
### State Machine Definition
|
|
92
|
+
|
|
93
|
+
Define your state machine using the `createMachine` function:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
const definition = createMachine({
|
|
97
|
+
initial: "stateName", // Initial state
|
|
98
|
+
context: { // Shared context object
|
|
99
|
+
// your data here
|
|
100
|
+
},
|
|
101
|
+
states: {
|
|
102
|
+
stateName: {
|
|
103
|
+
on: {
|
|
104
|
+
EVENT_NAME: {
|
|
105
|
+
target: "nextState",
|
|
106
|
+
action: (context, payload) => {
|
|
107
|
+
// Modify context here
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Duplex Stream Interface
|
|
117
|
+
|
|
118
|
+
Coremachine is a Duplex stream. The writable side accepts actions, the readable side emits state changes:
|
|
119
|
+
|
|
120
|
+
```javascript
|
|
121
|
+
const cube = new Coremachine(core, definition);
|
|
122
|
+
|
|
123
|
+
// Write side — type-safe action messages
|
|
124
|
+
cube.write({ event: "START", value: 1 });
|
|
125
|
+
cube.write({ event: "INCREMENT" });
|
|
126
|
+
|
|
127
|
+
// Read side — typed state + context
|
|
128
|
+
cube.on("data", ({ state, context }) => {
|
|
129
|
+
console.log(state, context);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Pipe it
|
|
133
|
+
actionSource.pipe(cube).pipe(stateConsumer);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The stream opens lazily on first read or write, restoring the latest state from the underlying Hypercore automatically.
|
|
137
|
+
|
|
138
|
+
### Actions and Transitions
|
|
139
|
+
|
|
140
|
+
Actions are functions that modify the context when transitioning between states:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
action: (context, payload) => {
|
|
144
|
+
// Directly modify the context object
|
|
145
|
+
context.someProperty = payload;
|
|
146
|
+
context.counter++;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## API Reference
|
|
151
|
+
|
|
152
|
+
### `createMachine(config)`
|
|
153
|
+
|
|
154
|
+
Creates a state machine definition.
|
|
155
|
+
|
|
156
|
+
**Parameters:**
|
|
157
|
+
- `config.initial` (string): The initial state name
|
|
158
|
+
- `config.context` (object): The initial context/data
|
|
159
|
+
- `config.states` (object): State definitions with transitions and actions
|
|
160
|
+
|
|
161
|
+
**Returns:** Machine definition object
|
|
162
|
+
|
|
163
|
+
### `new Coremachine(hypercore, definition, opts?)`
|
|
164
|
+
|
|
165
|
+
Creates a new Coremachine instance (a Duplex stream).
|
|
166
|
+
|
|
167
|
+
**Parameters:**
|
|
168
|
+
- `hypercore`: A Hypercore instance with JSON encoding
|
|
169
|
+
- `definition`: Machine definition from `createMachine()`
|
|
170
|
+
- `opts.eager` (boolean): If `true`, push the current state immediately on open
|
|
171
|
+
|
|
172
|
+
### Instance Methods
|
|
173
|
+
|
|
174
|
+
#### `await cube.action(eventName, payload?)`
|
|
175
|
+
|
|
176
|
+
Trigger a state transition directly.
|
|
177
|
+
|
|
178
|
+
**Parameters:**
|
|
179
|
+
- `eventName` (string): The event to trigger
|
|
180
|
+
- `payload` (any, optional): Data to pass to the action function
|
|
181
|
+
|
|
182
|
+
**Returns:** `{ state, context }` after the transition
|
|
183
|
+
|
|
184
|
+
#### `cube.write({ event, value? })`
|
|
185
|
+
|
|
186
|
+
Write an action into the stream. Equivalent to calling `action()` but through the stream interface, with backpressure handled automatically.
|
|
187
|
+
|
|
188
|
+
#### `await cube.forward()`
|
|
189
|
+
|
|
190
|
+
Move forward in the state history.
|
|
191
|
+
|
|
192
|
+
#### `await cube.backward()`
|
|
193
|
+
|
|
194
|
+
Move backward in the state history.
|
|
195
|
+
|
|
196
|
+
#### `cube.truncate(newLength)`
|
|
197
|
+
|
|
198
|
+
Truncate the underlying Hypercore to a new length.
|
|
199
|
+
|
|
200
|
+
### Properties
|
|
201
|
+
|
|
202
|
+
- `cube.state` (string): Current state name
|
|
203
|
+
- `cube.context` (object): Current context data
|
|
204
|
+
- `cube.isEmpty` (boolean): Whether the hypercore is empty
|
|
205
|
+
- `cube.getAvailableActions()` (array): Actions available in the current state
|
|
206
|
+
|
|
207
|
+
### Stream Events
|
|
208
|
+
|
|
209
|
+
#### `data`
|
|
210
|
+
|
|
211
|
+
Emitted when the state changes. Each message contains the new state and context:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
cube.on("data", ({ state, context }) => {
|
|
215
|
+
console.log(`Now in state: ${state}`, context);
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Advanced Usage
|
|
220
|
+
|
|
221
|
+
### Monitoring State Changes
|
|
222
|
+
|
|
223
|
+
Use the `deep-object-diff` library to track specific changes:
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
import { diff } from "deep-object-diff";
|
|
227
|
+
|
|
228
|
+
let prev = null;
|
|
229
|
+
cube.on("data", ({ state, context }) => {
|
|
230
|
+
if (prev) {
|
|
231
|
+
const changes = diff(prev, context);
|
|
232
|
+
console.log("Changes:", changes);
|
|
233
|
+
}
|
|
234
|
+
prev = structuredClone(context);
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Distributed State Synchronization
|
|
239
|
+
|
|
240
|
+
Since Coremachine is built on Hypercore, multiple instances can synchronize automatically:
|
|
241
|
+
|
|
242
|
+
```javascript
|
|
243
|
+
// Peer A
|
|
244
|
+
const storeA = new Corestore("./storeA");
|
|
245
|
+
const cubeA = new Coremachine(
|
|
246
|
+
storeA.get({ name: "coremachine", valueEncoding: "json" }),
|
|
247
|
+
definition
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Peer B
|
|
251
|
+
const storeB = new Corestore("./storeB");
|
|
252
|
+
const cubeB = new Coremachine(
|
|
253
|
+
storeB.get({ key: storeA.key, valueEncoding: "json" }),
|
|
254
|
+
definition
|
|
255
|
+
);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Piping
|
|
259
|
+
|
|
260
|
+
Since Coremachine is a standard Duplex stream, it composes with the rest of the streaming ecosystem:
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
// Pipe actions from one source into the cube
|
|
264
|
+
actionStream.pipe(cube);
|
|
265
|
+
|
|
266
|
+
// Pipe state changes to a consumer
|
|
267
|
+
cube.pipe(renderStream);
|
|
268
|
+
|
|
269
|
+
// Full pipeline
|
|
270
|
+
actionStream.pipe(cube).pipe(renderStream);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Use Cases
|
|
274
|
+
|
|
275
|
+
- **Distributed Applications**: Synchronize application state across multiple peers
|
|
276
|
+
- **Audit Logging**: Maintain immutable history of all state changes
|
|
277
|
+
- **Collaborative Tools**: Build real-time collaborative applications
|
|
278
|
+
- **IoT Networks**: Coordinate state across distributed IoT devices
|
|
279
|
+
- **Blockchain Alternatives**: Create consensus without traditional blockchain overhead
|
|
280
|
+
|
|
281
|
+
## Contributing
|
|
282
|
+
|
|
283
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
Built with ❤️ using [Hypercore](https://github.com/holepunchto/hypercore)
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "coremachine",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Dominic Cassidy",
|
|
7
|
+
"email": "dominic@cassidy.casa",
|
|
8
|
+
"url": "https://dominic.cassidy.casa"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist/*"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"test": "bun run tests/runtime.ts",
|
|
30
|
+
"test:types": "bun tsc --noEmit tests/types.ts",
|
|
31
|
+
"test:all": "bun run test:types && bun run test"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/bun": "latest",
|
|
35
|
+
"@types/streamx": "^2.9.5",
|
|
36
|
+
"corestore": "^7.4.5",
|
|
37
|
+
"deep-object-diff": "^1.1.9",
|
|
38
|
+
"tsd": "^0.33.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"holepunch-types": "github:Drache93/holepunch-types#main",
|
|
42
|
+
"typescript": "^5"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"hypercore": "^11.16.0",
|
|
46
|
+
"ready-resource": "^1.2.0",
|
|
47
|
+
"streamx": "^2.23.0"
|
|
48
|
+
}
|
|
49
|
+
}
|