@better-state/server 0.1.0 → 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/dist/cli.js +7 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +63 -28
- package/dist/index.js.map +1 -1
- package/dist/seed.js +7 -5
- package/dist/seed.js.map +1 -1
- package/dist/state-engine.d.ts +6 -27
- package/dist/state-engine.d.ts.map +1 -1
- package/dist/state-engine.js +40 -54
- package/dist/state-engine.js.map +1 -1
- package/dist/storage/adapter.d.ts +102 -0
- package/dist/storage/adapter.d.ts.map +1 -0
- package/dist/storage/adapter.js +12 -0
- package/dist/storage/adapter.js.map +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/sqlite.d.ts +49 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +192 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/package.json +1 -1
- package/public/playground.html +188 -219
- package/public/studio.html +653 -0
package/public/playground.html
CHANGED
|
@@ -9,293 +9,262 @@
|
|
|
9
9
|
|
|
10
10
|
body {
|
|
11
11
|
font-family: "Inter", ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
12
|
-
background: #
|
|
12
|
+
background: #0a0a12;
|
|
13
13
|
color: #e2e8f0;
|
|
14
14
|
min-height: 100vh;
|
|
15
|
-
padding: 2rem;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
.header {
|
|
19
15
|
display: flex;
|
|
20
16
|
align-items: center;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
.header h1 {
|
|
25
|
-
font-size: 1.5rem;
|
|
26
|
-
font-weight: 700;
|
|
27
|
-
letter-spacing: -0.02em;
|
|
28
|
-
}
|
|
29
|
-
.header h1 span { color: #36adf6; }
|
|
30
|
-
.badge {
|
|
31
|
-
font-size: 0.7rem;
|
|
32
|
-
background: rgba(12, 147, 231, 0.15);
|
|
33
|
-
color: #7cc8fb;
|
|
34
|
-
padding: 0.15rem 0.5rem;
|
|
35
|
-
border-radius: 9999px;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
padding: 1.5rem;
|
|
36
19
|
}
|
|
37
20
|
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
.status
|
|
46
|
-
display:
|
|
47
|
-
|
|
48
|
-
height: 8px;
|
|
49
|
-
border-radius: 50%;
|
|
50
|
-
margin-right: 0.4rem;
|
|
51
|
-
vertical-align: middle;
|
|
21
|
+
.container { width: 100%; max-width: 520px; }
|
|
22
|
+
|
|
23
|
+
.header { margin-bottom: 1.5rem; }
|
|
24
|
+
.header h1 { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; }
|
|
25
|
+
.header h1 span { color: #38bdf8; }
|
|
26
|
+
.subtitle { font-size: 0.8rem; color: #64748b; margin-top: 0.25rem; }
|
|
27
|
+
|
|
28
|
+
.status {
|
|
29
|
+
display: flex; align-items: center; gap: 0.4rem;
|
|
30
|
+
font-size: 0.75rem; color: #94a3b8; margin-bottom: 1.5rem;
|
|
52
31
|
}
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.grid {
|
|
58
|
-
display: grid;
|
|
59
|
-
grid-template-columns: 1fr 1fr;
|
|
60
|
-
gap: 1.5rem;
|
|
61
|
-
margin-bottom: 2rem;
|
|
32
|
+
.dot {
|
|
33
|
+
width: 7px; height: 7px; border-radius: 50%;
|
|
34
|
+
transition: background 0.3s;
|
|
62
35
|
}
|
|
36
|
+
.dot.connecting { background: #fbbf24; animation: pulse 1.2s infinite; }
|
|
37
|
+
.dot.connected { background: #34d399; }
|
|
38
|
+
.dot.disconnected { background: #f87171; }
|
|
39
|
+
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
63
40
|
|
|
64
41
|
.card {
|
|
65
|
-
background: rgba(15, 23, 42, 0.
|
|
42
|
+
background: rgba(15, 23, 42, 0.7);
|
|
66
43
|
border: 1px solid #1e293b;
|
|
67
|
-
border-radius:
|
|
68
|
-
padding: 1.
|
|
69
|
-
}
|
|
70
|
-
.card h2 {
|
|
71
|
-
font-size: 0.75rem;
|
|
72
|
-
font-weight: 600;
|
|
73
|
-
text-transform: uppercase;
|
|
74
|
-
letter-spacing: 0.05em;
|
|
75
|
-
color: #64748b;
|
|
44
|
+
border-radius: 16px;
|
|
45
|
+
padding: 1.5rem;
|
|
76
46
|
margin-bottom: 1rem;
|
|
77
47
|
}
|
|
78
48
|
|
|
79
|
-
.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
color: #a5b4fc;
|
|
90
|
-
margin-bottom: 1rem;
|
|
49
|
+
.card-label {
|
|
50
|
+
font-size: 0.65rem; font-weight: 600; text-transform: uppercase;
|
|
51
|
+
letter-spacing: 0.08em; color: #475569; margin-bottom: 1rem;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Counter */
|
|
55
|
+
.counter-value {
|
|
56
|
+
font-size: 4rem; font-weight: 800; text-align: center;
|
|
57
|
+
color: #38bdf8; padding: 0.5rem 0; transition: transform 0.15s;
|
|
58
|
+
font-variant-numeric: tabular-nums;
|
|
91
59
|
}
|
|
60
|
+
.counter-value.bump { transform: scale(1.08); }
|
|
92
61
|
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
button {
|
|
96
|
-
background: linear-gradient(135deg, #0c93e7, #015da0);
|
|
97
|
-
color: white;
|
|
98
|
-
border: none;
|
|
99
|
-
padding: 0.5rem 1rem;
|
|
100
|
-
border-radius: 8px;
|
|
101
|
-
font-size: 0.8rem;
|
|
102
|
-
font-weight: 500;
|
|
103
|
-
cursor: pointer;
|
|
104
|
-
transition: opacity 0.15s;
|
|
62
|
+
.btn-row {
|
|
63
|
+
display: flex; gap: 0.5rem; margin-top: 1rem;
|
|
105
64
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
border: 1px solid #
|
|
65
|
+
.btn {
|
|
66
|
+
flex: 1; padding: 0.6rem; border-radius: 10px;
|
|
67
|
+
font-size: 0.9rem; font-weight: 600; cursor: pointer;
|
|
68
|
+
border: 1px solid #1e293b; background: #1e293b; color: #e2e8f0;
|
|
69
|
+
transition: background 0.15s, border-color 0.15s;
|
|
110
70
|
}
|
|
111
|
-
|
|
112
|
-
|
|
71
|
+
.btn:hover { background: #334155; border-color: #334155; }
|
|
72
|
+
.btn.primary { background: #2563eb; border-color: #2563eb; }
|
|
73
|
+
.btn.primary:hover { background: #1d4ed8; border-color: #1d4ed8; }
|
|
74
|
+
.btn.danger { color: #f87171; }
|
|
75
|
+
.btn.danger:hover { background: #991b1b22; border-color: #7f1d1d; }
|
|
76
|
+
|
|
77
|
+
/* Todos */
|
|
78
|
+
.todo-input-row { display: flex; gap: 0.5rem; margin-bottom: 0.75rem; }
|
|
79
|
+
.todo-input {
|
|
80
|
+
flex: 1; padding: 0.55rem 0.75rem; border-radius: 10px;
|
|
81
|
+
background: #0f172a; border: 1px solid #334155; color: #e2e8f0;
|
|
82
|
+
font-size: 0.85rem;
|
|
113
83
|
}
|
|
84
|
+
.todo-input:focus { outline: none; border-color: #38bdf8; }
|
|
114
85
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
padding: 0.5rem 0.75rem;
|
|
120
|
-
border-radius: 8px;
|
|
86
|
+
.todo-list { list-style: none; }
|
|
87
|
+
.todo-item {
|
|
88
|
+
display: flex; align-items: center; gap: 0.6rem;
|
|
89
|
+
padding: 0.5rem 0; border-bottom: 1px solid #1e293b;
|
|
121
90
|
font-size: 0.85rem;
|
|
122
|
-
flex: 1;
|
|
123
|
-
min-width: 180px;
|
|
124
91
|
}
|
|
125
|
-
|
|
92
|
+
.todo-item:last-child { border-bottom: none; }
|
|
93
|
+
.todo-check {
|
|
94
|
+
width: 18px; height: 18px; border-radius: 50%;
|
|
95
|
+
border: 2px solid #475569; cursor: pointer;
|
|
96
|
+
display: flex; align-items: center; justify-content: center;
|
|
97
|
+
flex-shrink: 0; transition: all 0.2s;
|
|
98
|
+
}
|
|
99
|
+
.todo-check.done { background: #34d399; border-color: #34d399; }
|
|
100
|
+
.todo-check.done::after { content: "✓"; color: #0a0a12; font-size: 0.7rem; font-weight: 700; }
|
|
101
|
+
.todo-text { flex: 1; }
|
|
102
|
+
.todo-text.done { text-decoration: line-through; color: #64748b; }
|
|
103
|
+
.todo-delete {
|
|
104
|
+
background: none; border: none; color: #475569; cursor: pointer;
|
|
105
|
+
font-size: 1rem; padding: 0 0.25rem; transition: color 0.15s;
|
|
106
|
+
}
|
|
107
|
+
.todo-delete:hover { color: #f87171; }
|
|
108
|
+
.empty { color: #475569; font-size: 0.8rem; text-align: center; padding: 1rem 0; }
|
|
126
109
|
|
|
110
|
+
/* Log */
|
|
127
111
|
.log {
|
|
128
|
-
background: #
|
|
129
|
-
|
|
130
|
-
border-radius: 8px;
|
|
131
|
-
padding: 1rem;
|
|
112
|
+
background: #0a0a12; border: 1px solid #1e293b; border-radius: 10px;
|
|
113
|
+
padding: 0.75rem; max-height: 160px; overflow-y: auto;
|
|
132
114
|
font-family: "JetBrains Mono", "Fira Code", monospace;
|
|
133
|
-
font-size: 0.
|
|
134
|
-
max-height: 300px;
|
|
135
|
-
overflow-y: auto;
|
|
136
|
-
line-height: 1.6;
|
|
137
|
-
}
|
|
138
|
-
.log .entry { margin-bottom: 0.25rem; }
|
|
139
|
-
.log .ts { color: #475569; }
|
|
140
|
-
.log .event { color: #34d399; }
|
|
141
|
-
.log .data { color: #94a3b8; }
|
|
142
|
-
.log .error { color: #f87171; }
|
|
143
|
-
|
|
144
|
-
.hint {
|
|
145
|
-
font-size: 0.8rem;
|
|
146
|
-
color: #475569;
|
|
147
|
-
margin-top: 0.5rem;
|
|
148
|
-
line-height: 1.5;
|
|
115
|
+
font-size: 0.7rem; line-height: 1.7;
|
|
149
116
|
}
|
|
117
|
+
.log-entry .ts { color: #334155; }
|
|
118
|
+
.log-entry .ev { color: #34d399; }
|
|
119
|
+
.log-entry .dt { color: #64748b; }
|
|
150
120
|
|
|
151
|
-
|
|
152
|
-
|
|
121
|
+
.footer {
|
|
122
|
+
text-align: center; font-size: 0.7rem; color: #334155;
|
|
123
|
+
margin-top: 1.5rem; line-height: 1.6;
|
|
153
124
|
}
|
|
125
|
+
.footer code { color: #475569; }
|
|
154
126
|
</style>
|
|
155
127
|
</head>
|
|
156
128
|
<body>
|
|
157
|
-
<div class="
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
|
|
129
|
+
<div class="container">
|
|
130
|
+
<div class="header">
|
|
131
|
+
<h1><span>Better</span>-State</h1>
|
|
132
|
+
<div class="subtitle">Playground — open in multiple tabs to see real-time sync</div>
|
|
133
|
+
</div>
|
|
161
134
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
</div>
|
|
135
|
+
<div class="status">
|
|
136
|
+
<div id="dot" class="dot connecting"></div>
|
|
137
|
+
<span id="status-text">connecting</span>
|
|
138
|
+
</div>
|
|
167
139
|
|
|
168
|
-
|
|
169
|
-
<!-- Counter state -->
|
|
140
|
+
<!-- Counter -->
|
|
170
141
|
<div class="card">
|
|
171
|
-
<
|
|
172
|
-
<div id="counter
|
|
173
|
-
<div class="
|
|
174
|
-
<button onclick="
|
|
175
|
-
<button
|
|
176
|
-
<button onclick="
|
|
142
|
+
<div class="card-label">Synced Counter</div>
|
|
143
|
+
<div id="counter" class="counter-value">0</div>
|
|
144
|
+
<div class="btn-row">
|
|
145
|
+
<button class="btn" onclick="dec()">- 1</button>
|
|
146
|
+
<button class="btn danger" onclick="resetCounter()">Reset</button>
|
|
147
|
+
<button class="btn primary" onclick="inc()">+ 1</button>
|
|
177
148
|
</div>
|
|
178
|
-
<p class="hint">Click buttons in multiple tabs — they all sync in real-time.</p>
|
|
179
149
|
</div>
|
|
180
150
|
|
|
181
|
-
<!-- Todos
|
|
151
|
+
<!-- Todos -->
|
|
182
152
|
<div class="card">
|
|
183
|
-
<
|
|
184
|
-
<div
|
|
185
|
-
|
|
186
|
-
<
|
|
187
|
-
<button onclick="addTodo()">Add</button>
|
|
188
|
-
<button onclick="clearTodos()" class="danger">Clear</button>
|
|
153
|
+
<div class="card-label">Synced Todo List</div>
|
|
154
|
+
<div class="todo-input-row">
|
|
155
|
+
<input id="input" class="todo-input" placeholder="Add a todo..." onkeydown="if(event.key==='Enter')addTodo()" />
|
|
156
|
+
<button class="btn primary" style="flex:none;padding:.55rem .9rem" onclick="addTodo()">Add</button>
|
|
189
157
|
</div>
|
|
158
|
+
<ul id="list" class="todo-list">
|
|
159
|
+
<li class="empty">No todos yet</li>
|
|
160
|
+
</ul>
|
|
190
161
|
</div>
|
|
191
|
-
</div>
|
|
192
162
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
163
|
+
<!-- Log -->
|
|
164
|
+
<div class="card">
|
|
165
|
+
<div class="card-label">Event Log</div>
|
|
166
|
+
<div id="log" class="log"></div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div class="footer">
|
|
170
|
+
State powered by <code>@better-state/client</code><br/>
|
|
171
|
+
Changes sync instantly across every connected tab
|
|
198
172
|
</div>
|
|
199
173
|
</div>
|
|
200
174
|
|
|
201
175
|
<script type="module">
|
|
202
|
-
// --- Config ---
|
|
203
|
-
// The API key is injected by the server into the page.
|
|
204
176
|
const API_KEY = "__API_KEY__";
|
|
205
|
-
const SERVER_URL = window.location.origin;
|
|
206
|
-
|
|
207
|
-
// --- Load SDK ---
|
|
208
177
|
const { createClient } = await import("/sdk/browser.mjs");
|
|
209
178
|
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
179
|
+
const logEl = document.getElementById("log");
|
|
180
|
+
const counterEl = document.getElementById("counter");
|
|
181
|
+
const listEl = document.getElementById("list");
|
|
182
|
+
const dotEl = document.getElementById("dot");
|
|
183
|
+
const statusEl = document.getElementById("status-text");
|
|
184
|
+
const inputEl = document.getElementById("input");
|
|
185
|
+
|
|
186
|
+
function log(ev, data) {
|
|
187
|
+
const t = new Date().toLocaleTimeString("en", { hour12: false });
|
|
188
|
+
const d = document.createElement("div");
|
|
189
|
+
d.className = "log-entry";
|
|
190
|
+
d.innerHTML = `<span class="ts">${t}</span> <span class="ev">${ev}</span> <span class="dt">${data}</span>`;
|
|
191
|
+
logEl.appendChild(d);
|
|
192
|
+
logEl.scrollTop = logEl.scrollHeight;
|
|
223
193
|
}
|
|
224
194
|
|
|
225
|
-
|
|
226
|
-
const client = createClient(SERVER_URL, {
|
|
227
|
-
apiKey: API_KEY,
|
|
228
|
-
debug: true,
|
|
229
|
-
});
|
|
195
|
+
const bs = createClient(location.origin, { apiKey: API_KEY, debug: false });
|
|
230
196
|
|
|
231
|
-
|
|
197
|
+
bs.onStatusChange(s => {
|
|
198
|
+
dotEl.className = "dot " + s;
|
|
199
|
+
statusEl.textContent = s;
|
|
200
|
+
log("status", s);
|
|
201
|
+
});
|
|
232
202
|
|
|
233
|
-
|
|
234
|
-
const counter = client.state("counter", 0);
|
|
203
|
+
bs.onError(e => log("error", e.message));
|
|
235
204
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
205
|
+
// Counter
|
|
206
|
+
const counter = bs.state("counter", 0);
|
|
207
|
+
counter.subscribe(v => {
|
|
208
|
+
counterEl.textContent = v;
|
|
209
|
+
counterEl.classList.add("bump");
|
|
210
|
+
setTimeout(() => counterEl.classList.remove("bump"), 150);
|
|
211
|
+
log("counter", `value=${v}`);
|
|
240
212
|
});
|
|
241
213
|
|
|
242
|
-
|
|
243
|
-
|
|
214
|
+
window.inc = () => counter.update(n => n + 1);
|
|
215
|
+
window.dec = () => counter.update(n => n - 1);
|
|
216
|
+
window.resetCounter = () => counter.set(0);
|
|
244
217
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
addLog("todos:update", `items=${Array.isArray(val) ? val.length : "?"}`);
|
|
248
|
-
});
|
|
218
|
+
// Todos
|
|
219
|
+
const todos = bs.state("todos", []);
|
|
249
220
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (connText.textContent === "Connecting..." && counter.getVersion() > 0) {
|
|
255
|
-
connDot.className = "status-dot green";
|
|
256
|
-
connText.textContent = "Connected";
|
|
257
|
-
clientIdEl.textContent = client._clientId || "active";
|
|
221
|
+
function renderTodos(items) {
|
|
222
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
223
|
+
listEl.innerHTML = '<li class="empty">No todos yet</li>';
|
|
224
|
+
return;
|
|
258
225
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// --- Actions (exposed globally for onclick handlers) ---
|
|
268
|
-
window.increment = () => {
|
|
269
|
-
counter.update((n) => n + 1);
|
|
270
|
-
addLog("action", "increment");
|
|
271
|
-
};
|
|
226
|
+
listEl.innerHTML = items.map(t => `
|
|
227
|
+
<li class="todo-item">
|
|
228
|
+
<div class="todo-check ${t.done ? "done" : ""}" onclick="toggleTodo('${t.id}')"></div>
|
|
229
|
+
<span class="todo-text ${t.done ? "done" : ""}">${esc(t.text)}</span>
|
|
230
|
+
<button class="todo-delete" onclick="deleteTodo('${t.id}')">×</button>
|
|
231
|
+
</li>
|
|
232
|
+
`).join("");
|
|
233
|
+
}
|
|
272
234
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
235
|
+
function esc(s) {
|
|
236
|
+
const d = document.createElement("div");
|
|
237
|
+
d.textContent = s;
|
|
238
|
+
return d.innerHTML;
|
|
239
|
+
}
|
|
277
240
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
};
|
|
241
|
+
todos.subscribe(v => {
|
|
242
|
+
renderTodos(v);
|
|
243
|
+
log("todos", `count=${Array.isArray(v) ? v.length : 0}`);
|
|
244
|
+
});
|
|
282
245
|
|
|
283
246
|
window.addTodo = () => {
|
|
284
|
-
const
|
|
285
|
-
const text = input.value.trim();
|
|
247
|
+
const text = inputEl.value.trim();
|
|
286
248
|
if (!text) return;
|
|
287
249
|
const id = crypto.randomUUID();
|
|
288
|
-
todos.
|
|
289
|
-
|
|
290
|
-
|
|
250
|
+
const current = todos.get();
|
|
251
|
+
todos.set([...(Array.isArray(current) ? current : []), { id, text, done: false }]);
|
|
252
|
+
inputEl.value = "";
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
window.toggleTodo = (id) => {
|
|
256
|
+
const current = todos.get();
|
|
257
|
+
if (!Array.isArray(current)) return;
|
|
258
|
+
todos.set(current.map(t => t.id === id ? { ...t, done: !t.done } : t));
|
|
291
259
|
};
|
|
292
260
|
|
|
293
|
-
window.
|
|
294
|
-
todos.
|
|
295
|
-
|
|
261
|
+
window.deleteTodo = (id) => {
|
|
262
|
+
const current = todos.get();
|
|
263
|
+
if (!Array.isArray(current)) return;
|
|
264
|
+
todos.set(current.filter(t => t.id !== id));
|
|
296
265
|
};
|
|
297
266
|
|
|
298
|
-
|
|
267
|
+
log("init", "SDK loaded");
|
|
299
268
|
</script>
|
|
300
269
|
</body>
|
|
301
270
|
</html>
|