@better-state/server 0.1.1 → 0.3.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 +32 -11
- 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 +41 -58
- 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 +8 -29
- 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 +100 -0
- package/dist/storage/adapter.d.ts.map +1 -0
- package/dist/storage/adapter.js +10 -0
- package/dist/storage/adapter.js.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +3 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/postgres.d.ts +48 -0
- package/dist/storage/postgres.d.ts.map +1 -0
- package/dist/storage/postgres.js +207 -0
- package/dist/storage/postgres.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 +2 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export class SqliteAdapter {
|
|
5
|
+
db;
|
|
6
|
+
constructor(opts = {}) {
|
|
7
|
+
if (opts.memory) {
|
|
8
|
+
this.db = new Database(":memory:");
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
const dbPath = opts.dbPath ||
|
|
12
|
+
process.env.DATABASE_PATH ||
|
|
13
|
+
path.join(process.cwd(), "data", "state.db");
|
|
14
|
+
mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
15
|
+
this.db = new Database(dbPath);
|
|
16
|
+
}
|
|
17
|
+
this.db.pragma("journal_mode = WAL");
|
|
18
|
+
this.db.pragma("foreign_keys = ON");
|
|
19
|
+
}
|
|
20
|
+
/** Expose the raw database for edge cases (tests, legacy code). */
|
|
21
|
+
get raw() {
|
|
22
|
+
return this.db;
|
|
23
|
+
}
|
|
24
|
+
// ── Lifecycle ───────────────────────────────────────────────────────────
|
|
25
|
+
async init() {
|
|
26
|
+
this.db.exec(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS namespaces (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
name TEXT NOT NULL,
|
|
30
|
+
api_key TEXT NOT NULL UNIQUE,
|
|
31
|
+
created_at INTEGER NOT NULL
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE IF NOT EXISTS states (
|
|
35
|
+
id TEXT PRIMARY KEY,
|
|
36
|
+
namespace TEXT NOT NULL REFERENCES namespaces(id),
|
|
37
|
+
key TEXT NOT NULL,
|
|
38
|
+
initial TEXT NOT NULL,
|
|
39
|
+
snapshot TEXT NOT NULL,
|
|
40
|
+
version INTEGER NOT NULL DEFAULT 0,
|
|
41
|
+
created_at INTEGER NOT NULL,
|
|
42
|
+
updated_at INTEGER NOT NULL,
|
|
43
|
+
UNIQUE(namespace, key)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE TABLE IF NOT EXISTS event_log (
|
|
47
|
+
id TEXT PRIMARY KEY,
|
|
48
|
+
state_id TEXT NOT NULL REFERENCES states(id),
|
|
49
|
+
client_id TEXT NOT NULL,
|
|
50
|
+
client_ts INTEGER NOT NULL,
|
|
51
|
+
server_ts INTEGER NOT NULL,
|
|
52
|
+
seq INTEGER NOT NULL,
|
|
53
|
+
mutation TEXT NOT NULL,
|
|
54
|
+
meta TEXT,
|
|
55
|
+
created_at INTEGER NOT NULL
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_event_log_state_ts
|
|
59
|
+
ON event_log(state_id, server_ts, seq);
|
|
60
|
+
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_states_namespace_key
|
|
62
|
+
ON states(namespace, key);
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
async close() {
|
|
66
|
+
this.db.close();
|
|
67
|
+
}
|
|
68
|
+
// ── Namespaces ──────────────────────────────────────────────────────────
|
|
69
|
+
async listNamespaces() {
|
|
70
|
+
return this.db
|
|
71
|
+
.prepare("SELECT id, name, created_at FROM namespaces")
|
|
72
|
+
.all();
|
|
73
|
+
}
|
|
74
|
+
async findNamespaceByApiKey(apiKeyHash) {
|
|
75
|
+
return (this.db
|
|
76
|
+
.prepare("SELECT id FROM namespaces WHERE api_key = ?")
|
|
77
|
+
.get(apiKeyHash) ?? null);
|
|
78
|
+
}
|
|
79
|
+
async findNamespaceByName(name) {
|
|
80
|
+
return (this.db
|
|
81
|
+
.prepare("SELECT * FROM namespaces WHERE name = ?")
|
|
82
|
+
.get(name) ?? null);
|
|
83
|
+
}
|
|
84
|
+
async createNamespace(ns) {
|
|
85
|
+
this.db
|
|
86
|
+
.prepare("INSERT INTO namespaces (id, name, api_key, created_at) VALUES (?, ?, ?, ?)")
|
|
87
|
+
.run(ns.id, ns.name, ns.api_key, ns.created_at);
|
|
88
|
+
}
|
|
89
|
+
// ── States ──────────────────────────────────────────────────────────────
|
|
90
|
+
async findState(namespace, key) {
|
|
91
|
+
return (this.db
|
|
92
|
+
.prepare("SELECT * FROM states WHERE namespace = ? AND key = ?")
|
|
93
|
+
.get(namespace, key) ?? null);
|
|
94
|
+
}
|
|
95
|
+
async findStateById(id) {
|
|
96
|
+
return (this.db
|
|
97
|
+
.prepare("SELECT * FROM states WHERE id = ?")
|
|
98
|
+
.get(id) ?? null);
|
|
99
|
+
}
|
|
100
|
+
async listStates(namespace) {
|
|
101
|
+
return this.db
|
|
102
|
+
.prepare("SELECT id, key, snapshot, version, created_at, updated_at FROM states WHERE namespace = ?")
|
|
103
|
+
.all(namespace);
|
|
104
|
+
}
|
|
105
|
+
async createState(state) {
|
|
106
|
+
this.db
|
|
107
|
+
.prepare(`INSERT INTO states (id, namespace, key, initial, snapshot, version, created_at, updated_at)
|
|
108
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
109
|
+
.run(state.id, state.namespace, state.key, state.initial, state.snapshot, state.version, state.created_at, state.updated_at);
|
|
110
|
+
}
|
|
111
|
+
async updateSnapshot(stateId, snapshot, version, updatedAt) {
|
|
112
|
+
this.db
|
|
113
|
+
.prepare("UPDATE states SET snapshot = ?, version = ?, updated_at = ? WHERE id = ?")
|
|
114
|
+
.run(snapshot, version, updatedAt, stateId);
|
|
115
|
+
}
|
|
116
|
+
// ── Event Log ───────────────────────────────────────────────────────────
|
|
117
|
+
async getMaxSeq(stateId) {
|
|
118
|
+
const row = this.db
|
|
119
|
+
.prepare("SELECT MAX(seq) as max_seq FROM event_log WHERE state_id = ?")
|
|
120
|
+
.get(stateId);
|
|
121
|
+
return row.max_seq;
|
|
122
|
+
}
|
|
123
|
+
async insertEvents(events) {
|
|
124
|
+
const insert = this.db.prepare(`INSERT INTO event_log (id, state_id, client_id, client_ts, server_ts, seq, mutation, meta, created_at)
|
|
125
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
|
|
126
|
+
const batch = this.db.transaction(() => {
|
|
127
|
+
for (const e of events) {
|
|
128
|
+
insert.run(e.id, e.stateId, e.clientId, e.clientTs, e.serverTs, e.seq, e.mutation, e.meta, e.serverTs);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
batch();
|
|
132
|
+
}
|
|
133
|
+
async getEventsForReplay(stateId) {
|
|
134
|
+
return this.db
|
|
135
|
+
.prepare("SELECT mutation, meta FROM event_log WHERE state_id = ? ORDER BY server_ts ASC, seq ASC")
|
|
136
|
+
.all(stateId);
|
|
137
|
+
}
|
|
138
|
+
async getEventHistory(stateId, limit, cursor) {
|
|
139
|
+
let events;
|
|
140
|
+
if (cursor) {
|
|
141
|
+
events = this.db
|
|
142
|
+
.prepare(`SELECT * FROM event_log
|
|
143
|
+
WHERE state_id = ? AND (server_ts, seq) > (
|
|
144
|
+
SELECT server_ts, seq FROM event_log WHERE id = ?
|
|
145
|
+
)
|
|
146
|
+
ORDER BY server_ts ASC, seq ASC
|
|
147
|
+
LIMIT ?`)
|
|
148
|
+
.all(stateId, cursor, limit + 1);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
events = this.db
|
|
152
|
+
.prepare("SELECT * FROM event_log WHERE state_id = ? ORDER BY server_ts ASC, seq ASC LIMIT ?")
|
|
153
|
+
.all(stateId, limit + 1);
|
|
154
|
+
}
|
|
155
|
+
const hasMore = events.length > limit;
|
|
156
|
+
if (hasMore)
|
|
157
|
+
events.pop();
|
|
158
|
+
return { events, hasMore };
|
|
159
|
+
}
|
|
160
|
+
// ── Studio ──────────────────────────────────────────────────────────────
|
|
161
|
+
async getStats() {
|
|
162
|
+
const ns = this.db
|
|
163
|
+
.prepare("SELECT COUNT(*) as count FROM namespaces")
|
|
164
|
+
.get();
|
|
165
|
+
const st = this.db
|
|
166
|
+
.prepare("SELECT COUNT(*) as count FROM states")
|
|
167
|
+
.get();
|
|
168
|
+
const ev = this.db
|
|
169
|
+
.prepare("SELECT COUNT(*) as count FROM event_log")
|
|
170
|
+
.get();
|
|
171
|
+
return { namespaces: ns.count, states: st.count, events: ev.count };
|
|
172
|
+
}
|
|
173
|
+
async listAllStatesWithNamespace() {
|
|
174
|
+
return this.db
|
|
175
|
+
.prepare(`SELECT s.id, s.namespace, s.key, s.snapshot, s.version, s.created_at, s.updated_at, n.name as namespace_name
|
|
176
|
+
FROM states s
|
|
177
|
+
JOIN namespaces n ON s.namespace = n.id
|
|
178
|
+
ORDER BY s.updated_at DESC`)
|
|
179
|
+
.all();
|
|
180
|
+
}
|
|
181
|
+
async getRecentEvents(limit) {
|
|
182
|
+
const rows = this.db
|
|
183
|
+
.prepare(`SELECT e.id, e.state_id, e.client_id, e.client_ts, e.server_ts, e.mutation, e.meta, s.key as state_key
|
|
184
|
+
FROM event_log e
|
|
185
|
+
JOIN states s ON e.state_id = s.id
|
|
186
|
+
ORDER BY e.server_ts DESC, e.seq DESC
|
|
187
|
+
LIMIT ?`)
|
|
188
|
+
.all(limit);
|
|
189
|
+
return rows.reverse();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/storage/sqlite.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-state/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Better-State server — shared state primitive for developers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"@types/better-sqlite3": "^7.6.0",
|
|
47
47
|
"@types/cors": "^2.8.0",
|
|
48
48
|
"@types/express": "^5.0.0",
|
|
49
|
+
"@types/pg": "^8.16.0",
|
|
49
50
|
"@types/uuid": "^10.0.0",
|
|
50
51
|
"socket.io-client": "^4.8.3",
|
|
51
52
|
"tsx": "^4.19.0",
|