db4ai 0.2.0 → 0.3.1
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/chunk-3DWAMVV5.js +305 -0
- package/dist/chunk-3DWAMVV5.js.map +1 -0
- package/dist/chunk-COTPYBYM.js +618 -0
- package/dist/chunk-COTPYBYM.js.map +1 -0
- package/dist/chunk-EERD6CDF.js +735 -0
- package/dist/chunk-EERD6CDF.js.map +1 -0
- package/dist/chunk-FUF4HJTC.js +758 -0
- package/dist/chunk-FUF4HJTC.js.map +1 -0
- package/dist/chunk-JLL6FH5L.js +16 -0
- package/dist/chunk-JLL6FH5L.js.map +1 -0
- package/dist/chunk-JXFW6AIT.js +192 -0
- package/dist/chunk-JXFW6AIT.js.map +1 -0
- package/dist/chunk-XLSYCQPG.js +854 -0
- package/dist/chunk-XLSYCQPG.js.map +1 -0
- package/dist/chunk-Y5IXAS7F.js +569 -0
- package/dist/chunk-Y5IXAS7F.js.map +1 -0
- package/dist/cli/bin.d.ts +13 -12
- package/dist/cli/bin.js +277 -307
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/dashboard/index.d.ts +295 -14
- package/dist/cli/dashboard/index.js +60 -15
- package/dist/cli/dashboard/index.js.map +1 -1
- package/dist/cli/index.d.ts +10 -16
- package/dist/cli/index.js +94 -47
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/runtime/index.d.ts +52 -23
- package/dist/cli/runtime/index.js +10 -704
- package/dist/cli/runtime/index.js.map +1 -1
- package/dist/cli/scanner/index.d.ts +17 -15
- package/dist/cli/scanner/index.js +8 -639
- package/dist/cli/scanner/index.js.map +1 -1
- package/dist/cli/seed/index.d.ts +16 -12
- package/dist/cli/seed/index.js +12 -773
- package/dist/cli/seed/index.js.map +1 -1
- package/dist/cli/sync/index.d.ts +54 -53
- package/dist/cli/sync/index.js +23 -704
- package/dist/cli/sync/index.js.map +1 -1
- package/dist/cli/terminal.d.ts +9 -8
- package/dist/cli/terminal.js +6 -209
- package/dist/cli/terminal.js.map +1 -1
- package/dist/cli/workflow/index.d.ts +18 -10
- package/dist/cli/workflow/index.js +6 -307
- package/dist/cli/workflow/index.js.map +1 -1
- package/dist/handlers.d.ts +10 -9
- package/dist/handlers.js +6 -38
- package/dist/handlers.js.map +1 -1
- package/dist/index.d.ts +134 -117
- package/dist/index.js +1963 -3067
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/dist/cli/bin.d.ts.map +0 -1
- package/dist/cli/dashboard/App.d.ts +0 -16
- package/dist/cli/dashboard/App.d.ts.map +0 -1
- package/dist/cli/dashboard/App.js +0 -116
- package/dist/cli/dashboard/App.js.map +0 -1
- package/dist/cli/dashboard/components/index.d.ts +0 -70
- package/dist/cli/dashboard/components/index.d.ts.map +0 -1
- package/dist/cli/dashboard/components/index.js +0 -192
- package/dist/cli/dashboard/components/index.js.map +0 -1
- package/dist/cli/dashboard/hooks/index.d.ts +0 -76
- package/dist/cli/dashboard/hooks/index.d.ts.map +0 -1
- package/dist/cli/dashboard/hooks/index.js +0 -201
- package/dist/cli/dashboard/hooks/index.js.map +0 -1
- package/dist/cli/dashboard/index.d.ts.map +0 -1
- package/dist/cli/dashboard/types.d.ts +0 -84
- package/dist/cli/dashboard/types.d.ts.map +0 -1
- package/dist/cli/dashboard/types.js +0 -5
- package/dist/cli/dashboard/types.js.map +0 -1
- package/dist/cli/dashboard/views/index.d.ts +0 -51
- package/dist/cli/dashboard/views/index.d.ts.map +0 -1
- package/dist/cli/dashboard/views/index.js +0 -72
- package/dist/cli/dashboard/views/index.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/runtime/index.d.ts.map +0 -1
- package/dist/cli/scanner/index.d.ts.map +0 -1
- package/dist/cli/seed/index.d.ts.map +0 -1
- package/dist/cli/sync/index.d.ts.map +0 -1
- package/dist/cli/terminal.d.ts.map +0 -1
- package/dist/cli/workflow/index.d.ts.map +0 -1
- package/dist/errors.d.ts +0 -43
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -47
- package/dist/errors.js.map +0 -1
- package/dist/handlers.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/types.d.ts +0 -276
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -12
- package/dist/types.js.map +0 -1
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
// cli/runtime/index.ts
|
|
2
|
+
import Database from "better-sqlite3";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import * as crypto from "crypto";
|
|
6
|
+
function computeHash(obj) {
|
|
7
|
+
const sortedJson = stableStringify(obj);
|
|
8
|
+
return crypto.createHash("sha256").update(sortedJson).digest("hex").slice(0, 16);
|
|
9
|
+
}
|
|
10
|
+
function stableStringify(obj) {
|
|
11
|
+
if (obj === null || typeof obj !== "object") {
|
|
12
|
+
return JSON.stringify(obj);
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(obj)) {
|
|
15
|
+
return "[" + obj.map(stableStringify).join(",") + "]";
|
|
16
|
+
}
|
|
17
|
+
const keys = Object.keys(obj).sort();
|
|
18
|
+
const pairs = keys.map(
|
|
19
|
+
(key) => JSON.stringify(key) + ":" + stableStringify(obj[key])
|
|
20
|
+
);
|
|
21
|
+
return "{" + pairs.join(",") + "}";
|
|
22
|
+
}
|
|
23
|
+
function ensureDirectoryExists(filePath) {
|
|
24
|
+
const dir = path.dirname(filePath);
|
|
25
|
+
try {
|
|
26
|
+
if (!fs.existsSync(dir)) {
|
|
27
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
var ConnectionPool = class {
|
|
33
|
+
static instances = /* @__PURE__ */ new Map();
|
|
34
|
+
/**
|
|
35
|
+
* Acquire a database connection. Creates a new one or returns existing.
|
|
36
|
+
*/
|
|
37
|
+
static acquire(dbPath) {
|
|
38
|
+
const normalizedPath = path.resolve(dbPath);
|
|
39
|
+
const existing = this.instances.get(normalizedPath);
|
|
40
|
+
if (existing) {
|
|
41
|
+
existing.refCount++;
|
|
42
|
+
return existing.db;
|
|
43
|
+
}
|
|
44
|
+
ensureDirectoryExists(normalizedPath);
|
|
45
|
+
let db;
|
|
46
|
+
try {
|
|
47
|
+
db = new Database(normalizedPath);
|
|
48
|
+
} catch {
|
|
49
|
+
db = new Database(":memory:");
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
db.pragma("journal_mode = WAL");
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
this.instances.set(normalizedPath, { db, refCount: 1 });
|
|
56
|
+
return db;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Release a database connection. Closes when ref count reaches 0.
|
|
60
|
+
*/
|
|
61
|
+
static release(dbPath) {
|
|
62
|
+
const normalizedPath = path.resolve(dbPath);
|
|
63
|
+
const entry = this.instances.get(normalizedPath);
|
|
64
|
+
if (!entry) return;
|
|
65
|
+
entry.refCount--;
|
|
66
|
+
if (entry.refCount <= 0) {
|
|
67
|
+
entry.db.close();
|
|
68
|
+
this.instances.delete(normalizedPath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Force close all connections (for testing cleanup).
|
|
73
|
+
*/
|
|
74
|
+
static closeAll() {
|
|
75
|
+
for (const [path2, entry] of this.instances) {
|
|
76
|
+
entry.db.close();
|
|
77
|
+
this.instances.delete(path2);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var SCHEMA_VERSION = 2;
|
|
82
|
+
var SCHEMA_SQL = `
|
|
83
|
+
-- Things table (entities)
|
|
84
|
+
CREATE TABLE IF NOT EXISTS things (
|
|
85
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
86
|
+
type TEXT NOT NULL,
|
|
87
|
+
id TEXT NOT NULL,
|
|
88
|
+
data TEXT NOT NULL,
|
|
89
|
+
content_hash TEXT NOT NULL,
|
|
90
|
+
created_at INTEGER NOT NULL,
|
|
91
|
+
created_by_action INTEGER,
|
|
92
|
+
FOREIGN KEY (created_by_action) REFERENCES actions(rowid)
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
-- Indexes for things table
|
|
96
|
+
CREATE INDEX IF NOT EXISTS idx_things_type_id ON things(type, id);
|
|
97
|
+
CREATE INDEX IF NOT EXISTS idx_things_content_hash ON things(content_hash);
|
|
98
|
+
CREATE INDEX IF NOT EXISTS idx_things_type ON things(type);
|
|
99
|
+
-- Optimized index for latest version lookups (covering index)
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_things_type_id_rowid ON things(type, id, rowid DESC);
|
|
101
|
+
-- Index for content hash deduplication queries
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_things_type_id_hash ON things(type, id, content_hash);
|
|
103
|
+
|
|
104
|
+
-- Relationships table (edges)
|
|
105
|
+
CREATE TABLE IF NOT EXISTS relationships (
|
|
106
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
107
|
+
from_rowid INTEGER NOT NULL,
|
|
108
|
+
to_rowid INTEGER NOT NULL,
|
|
109
|
+
type TEXT NOT NULL,
|
|
110
|
+
path TEXT NOT NULL,
|
|
111
|
+
label TEXT NOT NULL,
|
|
112
|
+
created_at INTEGER NOT NULL,
|
|
113
|
+
created_by_action INTEGER,
|
|
114
|
+
FOREIGN KEY (from_rowid) REFERENCES things(rowid),
|
|
115
|
+
FOREIGN KEY (to_rowid) REFERENCES things(rowid),
|
|
116
|
+
FOREIGN KEY (created_by_action) REFERENCES actions(rowid),
|
|
117
|
+
UNIQUE (from_rowid, to_rowid, path)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
-- Indexes for relationships table
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_from ON relationships(from_rowid);
|
|
122
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_to ON relationships(to_rowid);
|
|
123
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(type);
|
|
124
|
+
-- Optimized composite index for filtered traversals
|
|
125
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_from_type ON relationships(from_rowid, type);
|
|
126
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_to_type ON relationships(to_rowid, type);
|
|
127
|
+
|
|
128
|
+
-- Actions table (for caching and tracking)
|
|
129
|
+
CREATE TABLE IF NOT EXISTS actions (
|
|
130
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
131
|
+
type TEXT NOT NULL,
|
|
132
|
+
input_hash TEXT NOT NULL,
|
|
133
|
+
input TEXT NOT NULL,
|
|
134
|
+
output TEXT,
|
|
135
|
+
model TEXT,
|
|
136
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
137
|
+
error TEXT,
|
|
138
|
+
duration_ms INTEGER,
|
|
139
|
+
cost_tokens INTEGER,
|
|
140
|
+
created_at INTEGER NOT NULL
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
-- Indexes for actions table
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_actions_input_hash ON actions(input_hash);
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_actions_status ON actions(status);
|
|
146
|
+
-- Optimized composite index for cache lookups (input_hash + status)
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_actions_hash_status ON actions(input_hash, status);
|
|
148
|
+
|
|
149
|
+
-- Events table (audit log)
|
|
150
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
151
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
152
|
+
type TEXT NOT NULL,
|
|
153
|
+
action TEXT NOT NULL,
|
|
154
|
+
entity_type TEXT NOT NULL,
|
|
155
|
+
entity_id TEXT NOT NULL,
|
|
156
|
+
entity_rowid INTEGER NOT NULL,
|
|
157
|
+
data TEXT NOT NULL,
|
|
158
|
+
changes TEXT,
|
|
159
|
+
timestamp INTEGER NOT NULL,
|
|
160
|
+
action_id INTEGER,
|
|
161
|
+
correlation_id TEXT,
|
|
162
|
+
FOREIGN KEY (action_id) REFERENCES actions(rowid)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
-- Indexes for events table
|
|
166
|
+
CREATE INDEX IF NOT EXISTS idx_events_type ON events(type);
|
|
167
|
+
CREATE INDEX IF NOT EXISTS idx_events_action ON events(action);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
169
|
+
CREATE INDEX IF NOT EXISTS idx_events_correlation_id ON events(correlation_id);
|
|
170
|
+
-- Optimized composite index for entity-based event lookups
|
|
171
|
+
CREATE INDEX IF NOT EXISTS idx_events_entity ON events(entity_type, entity_id);
|
|
172
|
+
-- Composite index for filtered event queries
|
|
173
|
+
CREATE INDEX IF NOT EXISTS idx_events_type_action ON events(type, action);
|
|
174
|
+
|
|
175
|
+
-- Schema version tracking
|
|
176
|
+
CREATE TABLE IF NOT EXISTS schema_version (
|
|
177
|
+
version INTEGER NOT NULL
|
|
178
|
+
);
|
|
179
|
+
`;
|
|
180
|
+
var MIGRATION_V1_TO_V2 = `
|
|
181
|
+
-- Add new optimized indexes for version 2
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_things_type_id_rowid ON things(type, id, rowid DESC);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_things_type_id_hash ON things(type, id, content_hash);
|
|
184
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_from_type ON relationships(from_rowid, type);
|
|
185
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_to_type ON relationships(to_rowid, type);
|
|
186
|
+
CREATE INDEX IF NOT EXISTS idx_actions_hash_status ON actions(input_hash, status);
|
|
187
|
+
CREATE INDEX IF NOT EXISTS idx_events_entity ON events(entity_type, entity_id);
|
|
188
|
+
CREATE INDEX IF NOT EXISTS idx_events_type_action ON events(type, action);
|
|
189
|
+
`;
|
|
190
|
+
var LocalDB = class {
|
|
191
|
+
db;
|
|
192
|
+
usePool;
|
|
193
|
+
path;
|
|
194
|
+
version = SCHEMA_VERSION;
|
|
195
|
+
things;
|
|
196
|
+
relationships;
|
|
197
|
+
actions;
|
|
198
|
+
events;
|
|
199
|
+
constructor(dbPath, options) {
|
|
200
|
+
this.path = dbPath || path.join(process.cwd(), ".db4", "local.db");
|
|
201
|
+
this.usePool = options?.usePool ?? false;
|
|
202
|
+
if (this.usePool) {
|
|
203
|
+
this.db = ConnectionPool.acquire(this.path);
|
|
204
|
+
} else {
|
|
205
|
+
ensureDirectoryExists(this.path);
|
|
206
|
+
try {
|
|
207
|
+
this.db = new Database(this.path);
|
|
208
|
+
} catch {
|
|
209
|
+
this.db = new Database(":memory:");
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
this.db.pragma("journal_mode = WAL");
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this.initSchema();
|
|
217
|
+
this.things = new ThingsAPI(this.db);
|
|
218
|
+
this.relationships = new RelationshipsAPI(this.db);
|
|
219
|
+
this.actions = new ActionsAPI(this.db);
|
|
220
|
+
this.events = new EventsAPI(this.db);
|
|
221
|
+
}
|
|
222
|
+
initSchema() {
|
|
223
|
+
const tableCheck = this.db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='schema_version'`).get();
|
|
224
|
+
if (!tableCheck) {
|
|
225
|
+
this.db.exec(SCHEMA_SQL);
|
|
226
|
+
this.db.prepare("INSERT INTO schema_version (version) VALUES (?)").run(SCHEMA_VERSION);
|
|
227
|
+
} else {
|
|
228
|
+
this.runMigrations();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Run any pending database migrations.
|
|
233
|
+
*/
|
|
234
|
+
runMigrations() {
|
|
235
|
+
const row = this.db.prepare("SELECT version FROM schema_version LIMIT 1").get();
|
|
236
|
+
const currentVersion = row?.version ?? 1;
|
|
237
|
+
if (currentVersion < SCHEMA_VERSION) {
|
|
238
|
+
if (currentVersion < 2) {
|
|
239
|
+
this.db.exec(MIGRATION_V1_TO_V2);
|
|
240
|
+
}
|
|
241
|
+
this.db.prepare("UPDATE schema_version SET version = ?").run(SCHEMA_VERSION);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Closes the database connection.
|
|
246
|
+
*/
|
|
247
|
+
close() {
|
|
248
|
+
if (this.usePool) {
|
|
249
|
+
ConnectionPool.release(this.path);
|
|
250
|
+
} else {
|
|
251
|
+
this.db.close();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Runs VACUUM to reclaim disk space.
|
|
256
|
+
*/
|
|
257
|
+
vacuum() {
|
|
258
|
+
this.db.exec("VACUUM");
|
|
259
|
+
return Promise.resolve();
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
var ThingsAPI = class {
|
|
263
|
+
constructor(db) {
|
|
264
|
+
this.db = db;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Upsert a thing. Returns existing rowid if content_hash matches,
|
|
268
|
+
* otherwise creates a new version.
|
|
269
|
+
*/
|
|
270
|
+
async upsert(params) {
|
|
271
|
+
const { type, id, data, created_by_action } = params;
|
|
272
|
+
const dataJson = JSON.stringify(data);
|
|
273
|
+
const content_hash = computeHash({ type, id, data });
|
|
274
|
+
const created_at = Date.now();
|
|
275
|
+
const existing = this.db.prepare("SELECT rowid FROM things WHERE type = ? AND id = ? AND content_hash = ?").get(type, id, content_hash);
|
|
276
|
+
if (existing) {
|
|
277
|
+
return { rowid: existing.rowid, created: false };
|
|
278
|
+
}
|
|
279
|
+
const result = this.db.prepare(
|
|
280
|
+
`INSERT INTO things (type, id, data, content_hash, created_at, created_by_action)
|
|
281
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
282
|
+
).run(type, id, dataJson, content_hash, created_at, created_by_action || null);
|
|
283
|
+
return { rowid: result.lastInsertRowid, created: true };
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get the latest version of a thing by type and id.
|
|
287
|
+
*/
|
|
288
|
+
async get(params) {
|
|
289
|
+
const { type, id } = params;
|
|
290
|
+
const row = this.db.prepare(
|
|
291
|
+
`SELECT rowid, type, id, data, content_hash, created_at, created_by_action
|
|
292
|
+
FROM things
|
|
293
|
+
WHERE type = ? AND id = ?
|
|
294
|
+
ORDER BY rowid DESC
|
|
295
|
+
LIMIT 1`
|
|
296
|
+
).get(type, id);
|
|
297
|
+
if (!row) return null;
|
|
298
|
+
return {
|
|
299
|
+
rowid: row.rowid,
|
|
300
|
+
type: row.type,
|
|
301
|
+
id: row.id,
|
|
302
|
+
data: JSON.parse(row.data),
|
|
303
|
+
content_hash: row.content_hash,
|
|
304
|
+
created_at: row.created_at,
|
|
305
|
+
created_by_action: row.created_by_action ?? void 0
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Get a specific version by rowid.
|
|
310
|
+
*/
|
|
311
|
+
async getByRowid(rowid) {
|
|
312
|
+
const row = this.db.prepare(
|
|
313
|
+
`SELECT rowid, type, id, data, content_hash, created_at, created_by_action
|
|
314
|
+
FROM things
|
|
315
|
+
WHERE rowid = ?`
|
|
316
|
+
).get(rowid);
|
|
317
|
+
if (!row) return null;
|
|
318
|
+
return {
|
|
319
|
+
rowid: row.rowid,
|
|
320
|
+
type: row.type,
|
|
321
|
+
id: row.id,
|
|
322
|
+
data: JSON.parse(row.data),
|
|
323
|
+
content_hash: row.content_hash,
|
|
324
|
+
created_at: row.created_at,
|
|
325
|
+
created_by_action: row.created_by_action ?? void 0
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* List things with optional filters.
|
|
330
|
+
*/
|
|
331
|
+
async list(params = {}) {
|
|
332
|
+
const { type, limit, offset, where, order = "asc" } = params;
|
|
333
|
+
let sql = `
|
|
334
|
+
SELECT t.rowid, t.type, t.id, t.data, t.content_hash, t.created_at, t.created_by_action
|
|
335
|
+
FROM things t
|
|
336
|
+
INNER JOIN (
|
|
337
|
+
SELECT type, id, MAX(rowid) as max_rowid
|
|
338
|
+
FROM things
|
|
339
|
+
${type ? "WHERE type = ?" : ""}
|
|
340
|
+
GROUP BY type, id
|
|
341
|
+
) latest ON t.rowid = latest.max_rowid
|
|
342
|
+
`;
|
|
343
|
+
const queryParams = [];
|
|
344
|
+
if (type) {
|
|
345
|
+
queryParams.push(type);
|
|
346
|
+
}
|
|
347
|
+
if (where && Object.keys(where).length > 0) {
|
|
348
|
+
const whereConditions = Object.keys(where).map((key) => {
|
|
349
|
+
const value = where[key];
|
|
350
|
+
queryParams.push(value);
|
|
351
|
+
return `json_extract(t.data, '$.${key}') = ?`;
|
|
352
|
+
});
|
|
353
|
+
sql += ` WHERE ${whereConditions.join(" AND ")}`;
|
|
354
|
+
}
|
|
355
|
+
sql += ` ORDER BY t.rowid ${order === "desc" ? "DESC" : "ASC"}`;
|
|
356
|
+
if (limit !== void 0) {
|
|
357
|
+
sql += ` LIMIT ?`;
|
|
358
|
+
queryParams.push(limit);
|
|
359
|
+
}
|
|
360
|
+
if (offset !== void 0) {
|
|
361
|
+
sql += ` OFFSET ?`;
|
|
362
|
+
queryParams.push(offset);
|
|
363
|
+
}
|
|
364
|
+
const rows = this.db.prepare(sql).all(...queryParams);
|
|
365
|
+
return rows.map((row) => ({
|
|
366
|
+
rowid: row.rowid,
|
|
367
|
+
type: row.type,
|
|
368
|
+
id: row.id,
|
|
369
|
+
data: JSON.parse(row.data),
|
|
370
|
+
content_hash: row.content_hash,
|
|
371
|
+
created_at: row.created_at,
|
|
372
|
+
created_by_action: row.created_by_action ?? void 0
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Delete a thing by rowid.
|
|
377
|
+
*/
|
|
378
|
+
async delete(rowid) {
|
|
379
|
+
this.db.prepare("DELETE FROM things WHERE rowid = ?").run(rowid);
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
var RelationshipsAPI = class {
|
|
383
|
+
constructor(db) {
|
|
384
|
+
this.db = db;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Create or update a relationship edge.
|
|
388
|
+
* If (from_rowid, to_rowid, path) already exists, updates it.
|
|
389
|
+
*/
|
|
390
|
+
async create(params) {
|
|
391
|
+
const { from_rowid, to_rowid, type, path: path2, label, created_by_action } = params;
|
|
392
|
+
const created_at = Date.now();
|
|
393
|
+
const existing = this.db.prepare(
|
|
394
|
+
`SELECT rowid FROM relationships
|
|
395
|
+
WHERE from_rowid = ? AND to_rowid = ? AND path = ?`
|
|
396
|
+
).get(from_rowid, to_rowid, path2);
|
|
397
|
+
if (existing) {
|
|
398
|
+
this.db.prepare(
|
|
399
|
+
`UPDATE relationships
|
|
400
|
+
SET type = ?, label = ?, created_by_action = ?
|
|
401
|
+
WHERE rowid = ?`
|
|
402
|
+
).run(type, label, created_by_action || null, existing.rowid);
|
|
403
|
+
const row = this.db.prepare(
|
|
404
|
+
`SELECT rowid, from_rowid, to_rowid, type, path, label, created_at, created_by_action
|
|
405
|
+
FROM relationships WHERE rowid = ?`
|
|
406
|
+
).get(existing.rowid);
|
|
407
|
+
return {
|
|
408
|
+
rowid: row.rowid,
|
|
409
|
+
from_rowid: row.from_rowid,
|
|
410
|
+
to_rowid: row.to_rowid,
|
|
411
|
+
type: row.type,
|
|
412
|
+
path: row.path,
|
|
413
|
+
label: row.label,
|
|
414
|
+
created_at: row.created_at,
|
|
415
|
+
created_by_action: row.created_by_action ?? void 0
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
const result = this.db.prepare(
|
|
419
|
+
`INSERT INTO relationships (from_rowid, to_rowid, type, path, label, created_at, created_by_action)
|
|
420
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
421
|
+
).run(from_rowid, to_rowid, type, path2, label, created_at, created_by_action || null);
|
|
422
|
+
return {
|
|
423
|
+
rowid: result.lastInsertRowid,
|
|
424
|
+
from_rowid,
|
|
425
|
+
to_rowid,
|
|
426
|
+
type,
|
|
427
|
+
path: path2,
|
|
428
|
+
label,
|
|
429
|
+
created_at,
|
|
430
|
+
created_by_action
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* List all relationships.
|
|
435
|
+
*/
|
|
436
|
+
async list() {
|
|
437
|
+
const rows = this.db.prepare(
|
|
438
|
+
`SELECT rowid, from_rowid, to_rowid, type, path, label, created_at, created_by_action
|
|
439
|
+
FROM relationships`
|
|
440
|
+
).all();
|
|
441
|
+
return rows.map((row) => ({
|
|
442
|
+
rowid: row.rowid,
|
|
443
|
+
from_rowid: row.from_rowid,
|
|
444
|
+
to_rowid: row.to_rowid,
|
|
445
|
+
type: row.type,
|
|
446
|
+
path: row.path,
|
|
447
|
+
label: row.label,
|
|
448
|
+
created_at: row.created_at,
|
|
449
|
+
created_by_action: row.created_by_action ?? void 0
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Traverse forward from an entity.
|
|
454
|
+
*/
|
|
455
|
+
async traverse(params) {
|
|
456
|
+
const { from_rowid, type } = params;
|
|
457
|
+
let sql = `
|
|
458
|
+
SELECT rowid, from_rowid, to_rowid, type, path, label, created_at, created_by_action
|
|
459
|
+
FROM relationships
|
|
460
|
+
WHERE from_rowid = ?
|
|
461
|
+
`;
|
|
462
|
+
const queryParams = [from_rowid];
|
|
463
|
+
if (type) {
|
|
464
|
+
sql += " AND type = ?";
|
|
465
|
+
queryParams.push(type);
|
|
466
|
+
}
|
|
467
|
+
const rows = this.db.prepare(sql).all(...queryParams);
|
|
468
|
+
return rows.map((row) => ({
|
|
469
|
+
rowid: row.rowid,
|
|
470
|
+
from_rowid: row.from_rowid,
|
|
471
|
+
to_rowid: row.to_rowid,
|
|
472
|
+
type: row.type,
|
|
473
|
+
path: row.path,
|
|
474
|
+
label: row.label,
|
|
475
|
+
created_at: row.created_at,
|
|
476
|
+
created_by_action: row.created_by_action ?? void 0
|
|
477
|
+
}));
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Find incoming edges to an entity.
|
|
481
|
+
*/
|
|
482
|
+
async incoming(params) {
|
|
483
|
+
const { to_rowid, type } = params;
|
|
484
|
+
let sql = `
|
|
485
|
+
SELECT rowid, from_rowid, to_rowid, type, path, label, created_at, created_by_action
|
|
486
|
+
FROM relationships
|
|
487
|
+
WHERE to_rowid = ?
|
|
488
|
+
`;
|
|
489
|
+
const queryParams = [to_rowid];
|
|
490
|
+
if (type) {
|
|
491
|
+
sql += " AND type = ?";
|
|
492
|
+
queryParams.push(type);
|
|
493
|
+
}
|
|
494
|
+
const rows = this.db.prepare(sql).all(...queryParams);
|
|
495
|
+
return rows.map((row) => ({
|
|
496
|
+
rowid: row.rowid,
|
|
497
|
+
from_rowid: row.from_rowid,
|
|
498
|
+
to_rowid: row.to_rowid,
|
|
499
|
+
type: row.type,
|
|
500
|
+
path: row.path,
|
|
501
|
+
label: row.label,
|
|
502
|
+
created_at: row.created_at,
|
|
503
|
+
created_by_action: row.created_by_action ?? void 0
|
|
504
|
+
}));
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Traverse multiple hops from an entity (BFS).
|
|
508
|
+
*/
|
|
509
|
+
async traverseMultiHop(params) {
|
|
510
|
+
const { from_rowid, depth, type } = params;
|
|
511
|
+
const visited = /* @__PURE__ */ new Set([from_rowid]);
|
|
512
|
+
const results = [];
|
|
513
|
+
let currentLevel = [from_rowid];
|
|
514
|
+
for (let d = 1; d <= depth && currentLevel.length > 0; d++) {
|
|
515
|
+
const nextLevel = [];
|
|
516
|
+
for (const nodeId of currentLevel) {
|
|
517
|
+
const edges = await this.traverse({ from_rowid: nodeId, type });
|
|
518
|
+
for (const edge of edges) {
|
|
519
|
+
if (!visited.has(edge.to_rowid)) {
|
|
520
|
+
visited.add(edge.to_rowid);
|
|
521
|
+
results.push({ to_rowid: edge.to_rowid, depth: d });
|
|
522
|
+
nextLevel.push(edge.to_rowid);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
currentLevel = nextLevel;
|
|
527
|
+
}
|
|
528
|
+
return results;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
var ActionsAPI = class {
|
|
532
|
+
constructor(db) {
|
|
533
|
+
this.db = db;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Create a new action in pending status.
|
|
537
|
+
*/
|
|
538
|
+
async create(params) {
|
|
539
|
+
const { type, input, model } = params;
|
|
540
|
+
const inputJson = JSON.stringify(input);
|
|
541
|
+
const input_hash = computeHash(input);
|
|
542
|
+
const created_at = Date.now();
|
|
543
|
+
const result = this.db.prepare(
|
|
544
|
+
`INSERT INTO actions (type, input_hash, input, model, status, created_at)
|
|
545
|
+
VALUES (?, ?, ?, ?, 'pending', ?)`
|
|
546
|
+
).run(type, input_hash, inputJson, model || null, created_at);
|
|
547
|
+
return {
|
|
548
|
+
rowid: result.lastInsertRowid,
|
|
549
|
+
type,
|
|
550
|
+
input_hash,
|
|
551
|
+
input,
|
|
552
|
+
model,
|
|
553
|
+
status: "pending",
|
|
554
|
+
created_at
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Find a completed action by input hash (cache lookup).
|
|
559
|
+
* Only returns actions with status='complete'.
|
|
560
|
+
*/
|
|
561
|
+
async findByHash(input_hash) {
|
|
562
|
+
const row = this.db.prepare(
|
|
563
|
+
`SELECT rowid, type, input_hash, input, output, model, status, error, duration_ms, cost_tokens, created_at
|
|
564
|
+
FROM actions
|
|
565
|
+
WHERE input_hash = ? AND status = 'complete'
|
|
566
|
+
ORDER BY rowid DESC
|
|
567
|
+
LIMIT 1`
|
|
568
|
+
).get(input_hash);
|
|
569
|
+
if (!row) return null;
|
|
570
|
+
return {
|
|
571
|
+
rowid: row.rowid,
|
|
572
|
+
type: row.type,
|
|
573
|
+
input_hash: row.input_hash,
|
|
574
|
+
input: JSON.parse(row.input),
|
|
575
|
+
output: row.output ? JSON.parse(row.output) : void 0,
|
|
576
|
+
model: row.model ?? void 0,
|
|
577
|
+
status: row.status,
|
|
578
|
+
error: row.error ?? void 0,
|
|
579
|
+
duration_ms: row.duration_ms ?? void 0,
|
|
580
|
+
cost_tokens: row.cost_tokens ?? void 0,
|
|
581
|
+
created_at: row.created_at
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Complete an action with output.
|
|
586
|
+
*/
|
|
587
|
+
async complete(params) {
|
|
588
|
+
const { rowid, output, duration_ms, cost_tokens } = params;
|
|
589
|
+
const outputJson = JSON.stringify(output);
|
|
590
|
+
const existing = this.db.prepare("SELECT status FROM actions WHERE rowid = ?").get(rowid);
|
|
591
|
+
if (!existing) {
|
|
592
|
+
throw new Error(`Action with rowid ${rowid} not found`);
|
|
593
|
+
}
|
|
594
|
+
if (existing.status !== "pending") {
|
|
595
|
+
throw new Error(`Cannot complete action with status '${existing.status}'`);
|
|
596
|
+
}
|
|
597
|
+
this.db.prepare(
|
|
598
|
+
`UPDATE actions
|
|
599
|
+
SET status = 'complete', output = ?, duration_ms = ?, cost_tokens = ?
|
|
600
|
+
WHERE rowid = ?`
|
|
601
|
+
).run(outputJson, duration_ms || null, cost_tokens || null, rowid);
|
|
602
|
+
const row = this.db.prepare(
|
|
603
|
+
`SELECT rowid, type, input_hash, input, output, model, status, error, duration_ms, cost_tokens, created_at
|
|
604
|
+
FROM actions WHERE rowid = ?`
|
|
605
|
+
).get(rowid);
|
|
606
|
+
return {
|
|
607
|
+
rowid: row.rowid,
|
|
608
|
+
type: row.type,
|
|
609
|
+
input_hash: row.input_hash,
|
|
610
|
+
input: JSON.parse(row.input),
|
|
611
|
+
output: row.output ? JSON.parse(row.output) : void 0,
|
|
612
|
+
model: row.model ?? void 0,
|
|
613
|
+
status: row.status,
|
|
614
|
+
error: row.error ?? void 0,
|
|
615
|
+
duration_ms: row.duration_ms ?? void 0,
|
|
616
|
+
cost_tokens: row.cost_tokens ?? void 0,
|
|
617
|
+
created_at: row.created_at
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Mark an action as failed.
|
|
622
|
+
*/
|
|
623
|
+
async fail(params) {
|
|
624
|
+
const { rowid, error } = params;
|
|
625
|
+
const existing = this.db.prepare("SELECT status FROM actions WHERE rowid = ?").get(rowid);
|
|
626
|
+
if (!existing) {
|
|
627
|
+
throw new Error(`Action with rowid ${rowid} not found`);
|
|
628
|
+
}
|
|
629
|
+
if (existing.status !== "pending") {
|
|
630
|
+
throw new Error(`Cannot fail action with status '${existing.status}'`);
|
|
631
|
+
}
|
|
632
|
+
this.db.prepare(`UPDATE actions SET status = 'error', error = ? WHERE rowid = ?`).run(error, rowid);
|
|
633
|
+
const row = this.db.prepare(
|
|
634
|
+
`SELECT rowid, type, input_hash, input, output, model, status, error, duration_ms, cost_tokens, created_at
|
|
635
|
+
FROM actions WHERE rowid = ?`
|
|
636
|
+
).get(rowid);
|
|
637
|
+
return {
|
|
638
|
+
rowid: row.rowid,
|
|
639
|
+
type: row.type,
|
|
640
|
+
input_hash: row.input_hash,
|
|
641
|
+
input: JSON.parse(row.input),
|
|
642
|
+
output: row.output ? JSON.parse(row.output) : void 0,
|
|
643
|
+
model: row.model ?? void 0,
|
|
644
|
+
status: row.status,
|
|
645
|
+
error: row.error ?? void 0,
|
|
646
|
+
duration_ms: row.duration_ms ?? void 0,
|
|
647
|
+
cost_tokens: row.cost_tokens ?? void 0,
|
|
648
|
+
created_at: row.created_at
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
var EventsAPI = class {
|
|
653
|
+
constructor(db) {
|
|
654
|
+
this.db = db;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Emit an event.
|
|
658
|
+
*/
|
|
659
|
+
async emit(params) {
|
|
660
|
+
const {
|
|
661
|
+
type,
|
|
662
|
+
action,
|
|
663
|
+
entity_type,
|
|
664
|
+
entity_id,
|
|
665
|
+
entity_rowid,
|
|
666
|
+
data,
|
|
667
|
+
changes,
|
|
668
|
+
action_id,
|
|
669
|
+
correlation_id
|
|
670
|
+
} = params;
|
|
671
|
+
const timestamp = Date.now();
|
|
672
|
+
const dataJson = JSON.stringify(data);
|
|
673
|
+
const changesJson = changes ? JSON.stringify(changes) : null;
|
|
674
|
+
const result = this.db.prepare(
|
|
675
|
+
`INSERT INTO events (type, action, entity_type, entity_id, entity_rowid, data, changes, timestamp, action_id, correlation_id)
|
|
676
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
677
|
+
).run(
|
|
678
|
+
type,
|
|
679
|
+
action,
|
|
680
|
+
entity_type,
|
|
681
|
+
entity_id,
|
|
682
|
+
entity_rowid,
|
|
683
|
+
dataJson,
|
|
684
|
+
changesJson,
|
|
685
|
+
timestamp,
|
|
686
|
+
action_id || null,
|
|
687
|
+
correlation_id || null
|
|
688
|
+
);
|
|
689
|
+
return {
|
|
690
|
+
rowid: result.lastInsertRowid,
|
|
691
|
+
type,
|
|
692
|
+
action,
|
|
693
|
+
entity_type,
|
|
694
|
+
entity_id,
|
|
695
|
+
entity_rowid,
|
|
696
|
+
data,
|
|
697
|
+
changes,
|
|
698
|
+
timestamp,
|
|
699
|
+
action_id,
|
|
700
|
+
correlation_id
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* List events with optional filters.
|
|
705
|
+
*/
|
|
706
|
+
async list(params = {}) {
|
|
707
|
+
const { type, action, since, correlation_id, limit } = params;
|
|
708
|
+
let sql = `
|
|
709
|
+
SELECT rowid, type, action, entity_type, entity_id, entity_rowid, data, changes, timestamp, action_id, correlation_id
|
|
710
|
+
FROM events
|
|
711
|
+
WHERE 1=1
|
|
712
|
+
`;
|
|
713
|
+
const queryParams = [];
|
|
714
|
+
if (type) {
|
|
715
|
+
sql += " AND type = ?";
|
|
716
|
+
queryParams.push(type);
|
|
717
|
+
}
|
|
718
|
+
if (action) {
|
|
719
|
+
sql += " AND action = ?";
|
|
720
|
+
queryParams.push(action);
|
|
721
|
+
}
|
|
722
|
+
if (since !== void 0) {
|
|
723
|
+
sql += " AND timestamp > ?";
|
|
724
|
+
queryParams.push(since);
|
|
725
|
+
}
|
|
726
|
+
if (correlation_id) {
|
|
727
|
+
sql += " AND correlation_id = ?";
|
|
728
|
+
queryParams.push(correlation_id);
|
|
729
|
+
}
|
|
730
|
+
sql += " ORDER BY timestamp ASC";
|
|
731
|
+
if (limit !== void 0) {
|
|
732
|
+
sql += " LIMIT ?";
|
|
733
|
+
queryParams.push(limit);
|
|
734
|
+
}
|
|
735
|
+
const rows = this.db.prepare(sql).all(...queryParams);
|
|
736
|
+
return rows.map((row) => ({
|
|
737
|
+
rowid: row.rowid,
|
|
738
|
+
type: row.type,
|
|
739
|
+
action: row.action,
|
|
740
|
+
entity_type: row.entity_type,
|
|
741
|
+
entity_id: row.entity_id,
|
|
742
|
+
entity_rowid: row.entity_rowid,
|
|
743
|
+
data: JSON.parse(row.data),
|
|
744
|
+
changes: row.changes ? JSON.parse(row.changes) : void 0,
|
|
745
|
+
timestamp: row.timestamp,
|
|
746
|
+
action_id: row.action_id ?? void 0,
|
|
747
|
+
correlation_id: row.correlation_id ?? void 0
|
|
748
|
+
}));
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
var runtime_default = LocalDB;
|
|
752
|
+
|
|
753
|
+
export {
|
|
754
|
+
ConnectionPool,
|
|
755
|
+
LocalDB,
|
|
756
|
+
runtime_default
|
|
757
|
+
};
|
|
758
|
+
//# sourceMappingURL=chunk-FUF4HJTC.js.map
|