@mark1russell7/client-sqlite 0.0.0-stage → 0.1.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/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +747 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +16 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +287 -0
- package/dist/module.js.map +1 -0
- package/dist/register.d.ts +8 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +14 -0
- package/dist/register.js.map +1 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/dist/types.js.map +1 -0
- package/package.json +74 -3
- package/src/index.ts +88 -0
- package/src/module.ts +385 -0
- package/src/register.ts +17 -0
- package/src/types.ts +135 -0
package/src/module.ts
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module (no side effects): the procedures of this package as data, for `createRegistry()`.
|
|
3
|
+
*
|
|
4
|
+
* Procedure Registration for SQLite
|
|
5
|
+
*
|
|
6
|
+
* Registers database and logging procedures with the client system.
|
|
7
|
+
* This file is referenced by package.json's client.procedures field.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createProcedure, zodAdapter, defineModule, type ProcedureModule } from "@mark1russell7/client";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import {
|
|
13
|
+
withConnection,
|
|
14
|
+
query,
|
|
15
|
+
execute,
|
|
16
|
+
lastInsertRowId,
|
|
17
|
+
runMigrations,
|
|
18
|
+
type Migration,
|
|
19
|
+
} from "@mark1russell7/docker-sqlite";
|
|
20
|
+
import {
|
|
21
|
+
DEFAULT_DB_PATH,
|
|
22
|
+
LOG_LEVELS,
|
|
23
|
+
type DbQueryInput,
|
|
24
|
+
type DbQueryOutput,
|
|
25
|
+
type DbExecuteInput,
|
|
26
|
+
type DbExecuteOutput,
|
|
27
|
+
type LogsStoreInput,
|
|
28
|
+
type LogsStoreOutput,
|
|
29
|
+
type LogsQueryInput,
|
|
30
|
+
type LogsQueryOutput,
|
|
31
|
+
type LogEntry,
|
|
32
|
+
type LogLevel,
|
|
33
|
+
} from "./types.js";
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Schemas
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
// Real schemas (roadmap 2.1): the input is validated before the handler, and the MCP tool list
|
|
40
|
+
// and the help of mark describe each field. zodAdapter<T> checks each schema against its type.
|
|
41
|
+
|
|
42
|
+
const dbPathField = z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe(`The SQLite database file (default: ${DEFAULT_DB_PATH}). A query does not make a missing file.`);
|
|
46
|
+
const paramsField = z
|
|
47
|
+
.array(z.unknown())
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("The values of the ? placeholders of the SQL, in order");
|
|
50
|
+
const logLevel = z.enum(LOG_LEVELS as [LogLevel, ...LogLevel[]], { error: (issue) => `Invalid log level: ${JSON.stringify(issue.input)}` });
|
|
51
|
+
/** A non-negative integer: it goes into the SQL text, so the schema is strict. */
|
|
52
|
+
const nonNegativeInteger = (field: string) =>
|
|
53
|
+
z
|
|
54
|
+
.number({ error: (issue) => `Invalid ${field}: expected a non-negative integer, got ${JSON.stringify(issue.input)}` })
|
|
55
|
+
.int({ error: `Invalid ${field}: expected a non-negative integer` })
|
|
56
|
+
.min(0, { error: `Invalid ${field}: expected a non-negative integer` });
|
|
57
|
+
|
|
58
|
+
const dbQueryInputSchema = zodAdapter<DbQueryInput>(
|
|
59
|
+
z.object({
|
|
60
|
+
sql: z.string().describe("The SQL query (SELECT)"),
|
|
61
|
+
params: paramsField,
|
|
62
|
+
dbPath: dbPathField,
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
const dbQueryOutputSchema = zodAdapter<DbQueryOutput>(
|
|
66
|
+
z.looseObject({
|
|
67
|
+
columns: z.array(z.string()).describe("The column names"),
|
|
68
|
+
rows: z.array(z.record(z.string(), z.unknown())).describe("The rows, one object for each row"),
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
const dbExecuteInputSchema = zodAdapter<DbExecuteInput>(
|
|
72
|
+
z.object({
|
|
73
|
+
sql: z.string().describe("The SQL statement or script (INSERT, UPDATE, DELETE, CREATE TABLE, ...)"),
|
|
74
|
+
params: paramsField,
|
|
75
|
+
dbPath: dbPathField,
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
const dbExecuteOutputSchema = zodAdapter<DbExecuteOutput>(
|
|
79
|
+
z.looseObject({ changes: z.number().describe("The number of rows that the statements changed") })
|
|
80
|
+
);
|
|
81
|
+
const logsStoreInputSchema = zodAdapter<LogsStoreInput>(
|
|
82
|
+
z.object({
|
|
83
|
+
level: logLevel.describe("The level: trace, debug, info, warn or error"),
|
|
84
|
+
message: z.string().describe("The log message"),
|
|
85
|
+
sessionId: z.string().optional().describe("The session that the entry belongs to (to group entries)"),
|
|
86
|
+
command: z.string().optional().describe("The command that made the entry"),
|
|
87
|
+
context: z.string().optional().describe("More context, for example a package name"),
|
|
88
|
+
data: z.record(z.string(), z.unknown()).optional().describe("Structured data of the entry"),
|
|
89
|
+
errorStack: z.string().optional().describe("The stack trace of an error"),
|
|
90
|
+
dbPath: dbPathField,
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
const logsStoreOutputSchema = zodAdapter<LogsStoreOutput>(
|
|
94
|
+
z.looseObject({ id: z.number().describe("The id of the new log row") })
|
|
95
|
+
);
|
|
96
|
+
const logsQueryInputSchema = zodAdapter<LogsQueryInput>(
|
|
97
|
+
z.object({
|
|
98
|
+
sessionId: z.string().optional().describe("Only the entries of this session"),
|
|
99
|
+
command: z.string().optional().describe("Only the entries of this command"),
|
|
100
|
+
level: z.union([logLevel, z.array(logLevel)]).optional().describe("Only the entries of this level, or of these levels"),
|
|
101
|
+
limit: nonNegativeInteger("limit").optional().describe("The most entries to give"),
|
|
102
|
+
offset: nonNegativeInteger("offset").optional().describe("The number of entries to skip (for pages)"),
|
|
103
|
+
orderBy: z.enum(["asc", "desc"]).optional().describe("The order by time: desc (the default, newest first) or asc"),
|
|
104
|
+
dbPath: dbPathField,
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
const logsQueryOutputSchema = zodAdapter<LogsQueryOutput>(
|
|
108
|
+
z.looseObject({
|
|
109
|
+
logs: z
|
|
110
|
+
.array(
|
|
111
|
+
z.looseObject({
|
|
112
|
+
id: z.number().describe("The id of the log row"),
|
|
113
|
+
timestamp: z.string().describe("The time of the entry (SQLite datetime text, UTC)"),
|
|
114
|
+
level: logLevel.describe("The level of the entry"),
|
|
115
|
+
message: z.string().describe("The log message"),
|
|
116
|
+
data: z.record(z.string(), z.unknown()).nullable().describe("The structured data of the entry, or null"),
|
|
117
|
+
sessionId: z.string().nullable().describe("The session of the entry, or null"),
|
|
118
|
+
command: z.string().nullable().describe("The command that made the entry, or null"),
|
|
119
|
+
context: z.string().nullable().describe("More context, or null"),
|
|
120
|
+
errorStack: z.string().nullable().describe("The stack trace of an error, or null"),
|
|
121
|
+
})
|
|
122
|
+
)
|
|
123
|
+
.describe("The entries that match the filters, in the order of orderBy"),
|
|
124
|
+
total: z.number().optional().describe("The number of entries that match the filters, without limit and offset"),
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
// =============================================================================
|
|
129
|
+
// Log Table Migrations
|
|
130
|
+
// =============================================================================
|
|
131
|
+
|
|
132
|
+
const LOG_MIGRATIONS: Migration[] = [
|
|
133
|
+
{
|
|
134
|
+
version: 1,
|
|
135
|
+
description: "Create logs table",
|
|
136
|
+
up: `
|
|
137
|
+
CREATE TABLE IF NOT EXISTS logs (
|
|
138
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
139
|
+
timestamp TEXT NOT NULL DEFAULT (datetime('now')),
|
|
140
|
+
level TEXT NOT NULL CHECK (level IN ('trace', 'debug', 'info', 'warn', 'error')),
|
|
141
|
+
message TEXT NOT NULL,
|
|
142
|
+
data TEXT,
|
|
143
|
+
session_id TEXT,
|
|
144
|
+
command TEXT,
|
|
145
|
+
context TEXT,
|
|
146
|
+
error_stack TEXT,
|
|
147
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
148
|
+
);
|
|
149
|
+
CREATE INDEX IF NOT EXISTS idx_logs_session ON logs(session_id);
|
|
150
|
+
CREATE INDEX IF NOT EXISTS idx_logs_level ON logs(level);
|
|
151
|
+
CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp);
|
|
152
|
+
CREATE INDEX IF NOT EXISTS idx_logs_command ON logs(command);
|
|
153
|
+
`,
|
|
154
|
+
down: "DROP TABLE IF EXISTS logs;",
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Ensure the logs table exists
|
|
160
|
+
*/
|
|
161
|
+
async function ensureLogsTable(dbPath: string): Promise<void> {
|
|
162
|
+
await withConnection({ dbPath }, (db) => {
|
|
163
|
+
runMigrations(db, LOG_MIGRATIONS);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// =============================================================================
|
|
168
|
+
// db.query Procedure
|
|
169
|
+
// =============================================================================
|
|
170
|
+
|
|
171
|
+
const dbQueryProcedure = createProcedure()
|
|
172
|
+
.path(["db", "query"])
|
|
173
|
+
.input(dbQueryInputSchema)
|
|
174
|
+
.output(dbQueryOutputSchema)
|
|
175
|
+
.meta({ description: "Execute a SQL SELECT query" })
|
|
176
|
+
.handler(async (input: DbQueryInput): Promise<DbQueryOutput> => {
|
|
177
|
+
const dbPath = input.dbPath ?? DEFAULT_DB_PATH;
|
|
178
|
+
const params = input.params ?? [];
|
|
179
|
+
|
|
180
|
+
return withConnection({ dbPath }, (db) => {
|
|
181
|
+
const result = query(db, input.sql, params);
|
|
182
|
+
return {
|
|
183
|
+
columns: result.columns,
|
|
184
|
+
rows: result.rows,
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
})
|
|
188
|
+
.build();
|
|
189
|
+
|
|
190
|
+
// =============================================================================
|
|
191
|
+
// db.execute Procedure
|
|
192
|
+
// =============================================================================
|
|
193
|
+
|
|
194
|
+
const dbExecuteProcedure = createProcedure()
|
|
195
|
+
.path(["db", "execute"])
|
|
196
|
+
.input(dbExecuteInputSchema)
|
|
197
|
+
.output(dbExecuteOutputSchema)
|
|
198
|
+
.meta({ description: "Execute a SQL statement (INSERT, UPDATE, DELETE)" })
|
|
199
|
+
.handler(async (input: DbExecuteInput): Promise<DbExecuteOutput> => {
|
|
200
|
+
const dbPath = input.dbPath ?? DEFAULT_DB_PATH;
|
|
201
|
+
const params = input.params ?? [];
|
|
202
|
+
|
|
203
|
+
return withConnection({ dbPath }, (db) => {
|
|
204
|
+
const result = execute(db, input.sql, params);
|
|
205
|
+
return { changes: result.changes };
|
|
206
|
+
});
|
|
207
|
+
})
|
|
208
|
+
.build();
|
|
209
|
+
|
|
210
|
+
// =============================================================================
|
|
211
|
+
// logs.store Procedure
|
|
212
|
+
// =============================================================================
|
|
213
|
+
|
|
214
|
+
const logsStoreProcedure = createProcedure()
|
|
215
|
+
.path(["logs", "store"])
|
|
216
|
+
.input(logsStoreInputSchema)
|
|
217
|
+
.output(logsStoreOutputSchema)
|
|
218
|
+
.meta({ description: "Store a log entry in the database" })
|
|
219
|
+
.handler(async (input: LogsStoreInput): Promise<LogsStoreOutput> => {
|
|
220
|
+
const dbPath = input.dbPath ?? DEFAULT_DB_PATH;
|
|
221
|
+
|
|
222
|
+
// Validate log level
|
|
223
|
+
if (!LOG_LEVELS.includes(input.level)) {
|
|
224
|
+
throw new Error(`Invalid log level: ${input.level}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Ensure table exists
|
|
228
|
+
await ensureLogsTable(dbPath);
|
|
229
|
+
|
|
230
|
+
return withConnection({ dbPath }, (db) => {
|
|
231
|
+
const dataJson = input.data ? JSON.stringify(input.data) : null;
|
|
232
|
+
|
|
233
|
+
execute(
|
|
234
|
+
db,
|
|
235
|
+
`INSERT INTO logs (level, message, data, session_id, command, context, error_stack)
|
|
236
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
237
|
+
[
|
|
238
|
+
input.level,
|
|
239
|
+
input.message,
|
|
240
|
+
dataJson,
|
|
241
|
+
input.sessionId ?? null,
|
|
242
|
+
input.command ?? null,
|
|
243
|
+
input.context ?? null,
|
|
244
|
+
input.errorStack ?? null,
|
|
245
|
+
]
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const id = lastInsertRowId(db);
|
|
249
|
+
return { id };
|
|
250
|
+
});
|
|
251
|
+
})
|
|
252
|
+
.build();
|
|
253
|
+
|
|
254
|
+
// =============================================================================
|
|
255
|
+
// logs.query Procedure
|
|
256
|
+
// =============================================================================
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Validate a value as a non-negative integer before it is interpolated into
|
|
260
|
+
* SQL. The input schema is a pass-through, so `limit`/`offset` can arrive as
|
|
261
|
+
* arbitrary values; interpolating them directly is a SQL injection / syntax
|
|
262
|
+
* hazard. Throws on anything that is not a finite, non-negative integer.
|
|
263
|
+
*/
|
|
264
|
+
function toNonNegativeInt(value: unknown, field: string): number {
|
|
265
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
`Invalid ${field}: expected a non-negative integer, got ${JSON.stringify(value)}`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const logsQueryProcedure = createProcedure()
|
|
274
|
+
.path(["logs", "query"])
|
|
275
|
+
.input(logsQueryInputSchema)
|
|
276
|
+
.output(logsQueryOutputSchema)
|
|
277
|
+
.meta({ description: "Query log entries from the database" })
|
|
278
|
+
.handler(async (input: LogsQueryInput): Promise<LogsQueryOutput> => {
|
|
279
|
+
const dbPath = input.dbPath ?? DEFAULT_DB_PATH;
|
|
280
|
+
|
|
281
|
+
// Ensure table exists
|
|
282
|
+
await ensureLogsTable(dbPath);
|
|
283
|
+
|
|
284
|
+
return withConnection({ dbPath }, (db) => {
|
|
285
|
+
const conditions: string[] = [];
|
|
286
|
+
const params: unknown[] = [];
|
|
287
|
+
|
|
288
|
+
if (input.sessionId !== undefined) {
|
|
289
|
+
conditions.push("session_id = ?");
|
|
290
|
+
params.push(input.sessionId);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (input.command !== undefined) {
|
|
294
|
+
conditions.push("command = ?");
|
|
295
|
+
params.push(input.command);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (input.level !== undefined) {
|
|
299
|
+
const levels = Array.isArray(input.level) ? input.level : [input.level];
|
|
300
|
+
const placeholders = levels.map(() => "?").join(", ");
|
|
301
|
+
conditions.push(`level IN (${placeholders})`);
|
|
302
|
+
params.push(...levels);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const whereClause =
|
|
306
|
+
conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
307
|
+
const orderDir = input.orderBy === "asc" ? "ASC" : "DESC";
|
|
308
|
+
let limitClause =
|
|
309
|
+
input.limit !== undefined
|
|
310
|
+
? `LIMIT ${toNonNegativeInt(input.limit, "limit")}`
|
|
311
|
+
: "";
|
|
312
|
+
let offsetClause = "";
|
|
313
|
+
if (input.offset !== undefined) {
|
|
314
|
+
offsetClause = `OFFSET ${toNonNegativeInt(input.offset, "offset")}`;
|
|
315
|
+
// SQLite's grammar only allows OFFSET as part of a LIMIT clause; an
|
|
316
|
+
// OFFSET with no LIMIT is a syntax error. Use "LIMIT -1" (unbounded)
|
|
317
|
+
// so an offset-only query remains valid.
|
|
318
|
+
if (input.limit === undefined) {
|
|
319
|
+
limitClause = "LIMIT -1";
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const sql = `
|
|
324
|
+
SELECT id, timestamp, level, message, data, session_id, command, context, error_stack
|
|
325
|
+
FROM logs
|
|
326
|
+
${whereClause}
|
|
327
|
+
ORDER BY timestamp ${orderDir}
|
|
328
|
+
${limitClause}
|
|
329
|
+
${offsetClause}
|
|
330
|
+
`;
|
|
331
|
+
|
|
332
|
+
const result = query<{
|
|
333
|
+
id: number;
|
|
334
|
+
timestamp: string;
|
|
335
|
+
level: string;
|
|
336
|
+
message: string;
|
|
337
|
+
data: string | null;
|
|
338
|
+
session_id: string | null;
|
|
339
|
+
command: string | null;
|
|
340
|
+
context: string | null;
|
|
341
|
+
error_stack: string | null;
|
|
342
|
+
}>(db, sql, params);
|
|
343
|
+
|
|
344
|
+
const logs: LogEntry[] = result.rows.map((row) => ({
|
|
345
|
+
id: row.id,
|
|
346
|
+
timestamp: row.timestamp,
|
|
347
|
+
level: row.level as LogLevel,
|
|
348
|
+
message: row.message,
|
|
349
|
+
data: row.data ? (JSON.parse(row.data) as Record<string, unknown>) : null,
|
|
350
|
+
sessionId: row.session_id,
|
|
351
|
+
command: row.command,
|
|
352
|
+
context: row.context,
|
|
353
|
+
errorStack: row.error_stack,
|
|
354
|
+
}));
|
|
355
|
+
|
|
356
|
+
// Total number of rows matching the filters, ignoring LIMIT/OFFSET, so
|
|
357
|
+
// callers can paginate. `params` holds only the WHERE-clause bindings.
|
|
358
|
+
const countResult = query<{ total: number }>(
|
|
359
|
+
db,
|
|
360
|
+
`SELECT COUNT(*) AS total FROM logs ${whereClause}`,
|
|
361
|
+
params
|
|
362
|
+
);
|
|
363
|
+
const total = countResult.rows[0]?.total ?? 0;
|
|
364
|
+
|
|
365
|
+
return { logs, total };
|
|
366
|
+
});
|
|
367
|
+
})
|
|
368
|
+
.build();
|
|
369
|
+
|
|
370
|
+
// =============================================================================
|
|
371
|
+
// Registration
|
|
372
|
+
// =============================================================================
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* The module of this package, for `createRegistry()`: its procedures as data. Importing this
|
|
376
|
+
* file registers nothing. (`register.ts` registers the same procedures in the global registry.)
|
|
377
|
+
*/
|
|
378
|
+
export const sqliteModule: ProcedureModule = defineModule("@mark1russell7/client-sqlite", [
|
|
379
|
+
dbQueryProcedure,
|
|
380
|
+
dbExecuteProcedure,
|
|
381
|
+
logsStoreProcedure,
|
|
382
|
+
logsQueryProcedure,
|
|
383
|
+
]);
|
|
384
|
+
|
|
385
|
+
export default sqliteModule;
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing this file registers the procedures of @mark1russell7/client-sqlite in the global registry
|
|
3
|
+
* (DECISIONS-2026-10, "Every package root registers its procedures"). The procedures as data
|
|
4
|
+
* are in `module.ts`, which registers nothing: a host with its own registry uses that one.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { registerProcedures } from "@mark1russell7/client";
|
|
8
|
+
import { sqliteModule } from "./module.js";
|
|
9
|
+
|
|
10
|
+
export * from "./module.js";
|
|
11
|
+
|
|
12
|
+
export function registerSqliteProcedures(): void {
|
|
13
|
+
registerProcedures(sqliteModule.procedures);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Auto-register when this module is loaded
|
|
17
|
+
registerSqliteProcedures();
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for client-sqlite procedures
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Configuration
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Default database path for CLI logs
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_DB_PATH: string = join(homedir(), "logs", "cli", "cli.db");
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Log Levels
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error";
|
|
22
|
+
|
|
23
|
+
export const LOG_LEVELS: readonly LogLevel[] = [
|
|
24
|
+
"trace",
|
|
25
|
+
"debug",
|
|
26
|
+
"info",
|
|
27
|
+
"warn",
|
|
28
|
+
"error",
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// db.query types
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
export interface DbQueryInput {
|
|
36
|
+
/** SQL query to execute */
|
|
37
|
+
sql: string;
|
|
38
|
+
/** Query parameters */
|
|
39
|
+
params?: unknown[] | undefined;
|
|
40
|
+
/** Database path (defaults to CLI log database) */
|
|
41
|
+
dbPath?: string | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DbQueryOutput<T = Record<string, unknown>> {
|
|
45
|
+
/** Column names */
|
|
46
|
+
columns: string[];
|
|
47
|
+
/** Query results */
|
|
48
|
+
rows: T[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// db.execute types
|
|
53
|
+
// =============================================================================
|
|
54
|
+
|
|
55
|
+
export interface DbExecuteInput {
|
|
56
|
+
/** SQL statement to execute (INSERT, UPDATE, DELETE, etc.) */
|
|
57
|
+
sql: string;
|
|
58
|
+
/** Statement parameters */
|
|
59
|
+
params?: unknown[] | undefined;
|
|
60
|
+
/** Database path (defaults to CLI log database) */
|
|
61
|
+
dbPath?: string | undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface DbExecuteOutput {
|
|
65
|
+
/** Number of rows affected */
|
|
66
|
+
changes: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// logs.store types
|
|
71
|
+
// =============================================================================
|
|
72
|
+
|
|
73
|
+
export interface LogsStoreInput {
|
|
74
|
+
/** Log level */
|
|
75
|
+
level: LogLevel;
|
|
76
|
+
/** Log message */
|
|
77
|
+
message: string;
|
|
78
|
+
/** Session ID for grouping logs */
|
|
79
|
+
sessionId?: string | undefined;
|
|
80
|
+
/** Command that generated this log */
|
|
81
|
+
command?: string | undefined;
|
|
82
|
+
/** Additional context (e.g., package name) */
|
|
83
|
+
context?: string | undefined;
|
|
84
|
+
/** Additional structured data */
|
|
85
|
+
data?: Record<string, unknown> | undefined;
|
|
86
|
+
/** Error stack trace if applicable */
|
|
87
|
+
errorStack?: string | undefined;
|
|
88
|
+
/** Database path (defaults to CLI log database) */
|
|
89
|
+
dbPath?: string | undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface LogsStoreOutput {
|
|
93
|
+
/** ID of the inserted log row */
|
|
94
|
+
id: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// logs.query types
|
|
99
|
+
// =============================================================================
|
|
100
|
+
|
|
101
|
+
export interface LogsQueryInput {
|
|
102
|
+
/** Filter by session ID */
|
|
103
|
+
sessionId?: string | undefined;
|
|
104
|
+
/** Filter by command */
|
|
105
|
+
command?: string | undefined;
|
|
106
|
+
/** Filter by log level(s) */
|
|
107
|
+
level?: LogLevel | LogLevel[] | undefined;
|
|
108
|
+
/** Maximum number of logs to return */
|
|
109
|
+
limit?: number | undefined;
|
|
110
|
+
/** Offset for pagination */
|
|
111
|
+
offset?: number | undefined;
|
|
112
|
+
/** Order by (default: timestamp DESC) */
|
|
113
|
+
orderBy?: "asc" | "desc" | undefined;
|
|
114
|
+
/** Database path (defaults to CLI log database) */
|
|
115
|
+
dbPath?: string | undefined;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface LogEntry {
|
|
119
|
+
id: number;
|
|
120
|
+
timestamp: string;
|
|
121
|
+
level: LogLevel;
|
|
122
|
+
message: string;
|
|
123
|
+
data: Record<string, unknown> | null;
|
|
124
|
+
sessionId: string | null;
|
|
125
|
+
command: string | null;
|
|
126
|
+
context: string | null;
|
|
127
|
+
errorStack: string | null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface LogsQueryOutput {
|
|
131
|
+
/** Log entries matching the query */
|
|
132
|
+
logs: LogEntry[];
|
|
133
|
+
/** Total count (if limit was applied) */
|
|
134
|
+
total?: number | undefined;
|
|
135
|
+
}
|