tinylogs 0.2.0 → 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 +63 -54
- package/dist/cli.js.map +3 -3
- package/dist/server/index.js +37 -30
- package/dist/server/index.js.map +2 -2
- package/package.json +3 -5
package/dist/cli.js
CHANGED
|
@@ -46,7 +46,7 @@ function resolveDbPath(configPath, cfg) {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
// src/storage/db.ts
|
|
49
|
-
import
|
|
49
|
+
import { DatabaseSync } from "node:sqlite";
|
|
50
50
|
var MIGRATIONS = [
|
|
51
51
|
`CREATE TABLE logs (
|
|
52
52
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -72,34 +72,43 @@ var MIGRATIONS = [
|
|
|
72
72
|
created_at INTEGER NOT NULL
|
|
73
73
|
);`
|
|
74
74
|
];
|
|
75
|
+
function tx(db, fn) {
|
|
76
|
+
db.exec("BEGIN");
|
|
77
|
+
try {
|
|
78
|
+
const result = fn();
|
|
79
|
+
db.exec("COMMIT");
|
|
80
|
+
return result;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
db.exec("ROLLBACK");
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
75
86
|
function openDb(path) {
|
|
76
|
-
const db = new
|
|
77
|
-
db.
|
|
78
|
-
db.
|
|
87
|
+
const db = new DatabaseSync(path);
|
|
88
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
89
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
79
90
|
db.exec("CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY)");
|
|
80
91
|
const current = db.prepare("SELECT COALESCE(MAX(version),0) v FROM schema_migrations").get().v;
|
|
81
|
-
|
|
82
|
-
for (let i =
|
|
92
|
+
tx(db, () => {
|
|
93
|
+
for (let i = current; i < MIGRATIONS.length; i++) {
|
|
83
94
|
db.exec(MIGRATIONS[i]);
|
|
84
95
|
db.prepare("INSERT INTO schema_migrations (version) VALUES (?)").run(i + 1);
|
|
85
96
|
}
|
|
86
97
|
});
|
|
87
|
-
apply(current);
|
|
88
98
|
return db;
|
|
89
99
|
}
|
|
90
100
|
function insertLog(db, rec) {
|
|
91
|
-
|
|
92
|
-
const info = db.prepare("INSERT INTO logs (ts, service, message, labels) VALUES (?,?,?,?)").run(
|
|
101
|
+
return tx(db, () => {
|
|
102
|
+
const info = db.prepare("INSERT INTO logs (ts, service, message, labels) VALUES (?,?,?,?)").run(rec.ts, rec.service, rec.message, JSON.stringify(rec.labels ?? {}));
|
|
93
103
|
const logId = Number(info.lastInsertRowid);
|
|
94
104
|
const ins = db.prepare("INSERT INTO log_labels (log_id, key, value) VALUES (?,?,?)");
|
|
95
|
-
for (const [k, v] of Object.entries(
|
|
105
|
+
for (const [k, v] of Object.entries(rec.labels ?? {})) ins.run(logId, k, String(v));
|
|
96
106
|
return logId;
|
|
97
107
|
});
|
|
98
|
-
return txn(rec);
|
|
99
108
|
}
|
|
100
109
|
function dbSizeBytes(db) {
|
|
101
|
-
const pageCount = db.
|
|
102
|
-
const pageSize = db.
|
|
110
|
+
const pageCount = Number(db.prepare("PRAGMA page_count").get().page_count);
|
|
111
|
+
const pageSize = Number(db.prepare("PRAGMA page_size").get().page_size);
|
|
103
112
|
return pageCount * pageSize;
|
|
104
113
|
}
|
|
105
114
|
|
|
@@ -150,6 +159,21 @@ function verifySession(cookieVal, secret) {
|
|
|
150
159
|
return username;
|
|
151
160
|
}
|
|
152
161
|
|
|
162
|
+
// src/style.ts
|
|
163
|
+
function computeSupportsColor() {
|
|
164
|
+
if (process.env.NO_COLOR) return false;
|
|
165
|
+
if (process.env.FORCE_COLOR) return true;
|
|
166
|
+
return Boolean(process.stdout.isTTY);
|
|
167
|
+
}
|
|
168
|
+
var useColor = computeSupportsColor();
|
|
169
|
+
var wrap = (open, close) => (s) => useColor ? `\x1B[${open}m${s}\x1B[${close}m` : s;
|
|
170
|
+
var bold = wrap(1, 22);
|
|
171
|
+
var dim = wrap(2, 22);
|
|
172
|
+
var red = wrap(31, 39);
|
|
173
|
+
var green = wrap(32, 39);
|
|
174
|
+
var yellow = wrap(33, 39);
|
|
175
|
+
var cyan = wrap(36, 39);
|
|
176
|
+
|
|
153
177
|
// src/wizard.ts
|
|
154
178
|
function buildInitConfig(opts) {
|
|
155
179
|
const s = generateSecrets();
|
|
@@ -166,13 +190,13 @@ function buildInitConfig(opts) {
|
|
|
166
190
|
return { config, token: s.token };
|
|
167
191
|
}
|
|
168
192
|
async function runInit(configPath, opts, io) {
|
|
169
|
-
const port = opts.port ?? (Number(await io.prompt(`Port [${DEFAULTS.port}]: `)) || DEFAULTS.port);
|
|
170
|
-
const host = opts.host ?? (await io.prompt(`Host [${DEFAULTS.host}]: `) || DEFAULTS.host);
|
|
171
|
-
const username = opts.username ?? (await io.prompt(
|
|
193
|
+
const port = opts.port ?? (Number(await io.prompt(`Port ${dim(`[${DEFAULTS.port}]`)}: `)) || DEFAULTS.port);
|
|
194
|
+
const host = opts.host ?? (await io.prompt(`Host ${dim(`[${DEFAULTS.host}]`)}: `) || DEFAULTS.host);
|
|
195
|
+
const username = opts.username ?? (await io.prompt(`Admin username ${dim("[admin]")}: `) || "admin");
|
|
172
196
|
const password = opts.password ?? await io.promptHidden("Admin password: ");
|
|
173
197
|
if (!password || password.length === 0) throw new Error("password must not be empty");
|
|
174
|
-
const retentionDays = opts.retentionDays ?? (Number(await io.prompt(`Retention days [${DEFAULTS.retentionDays}]: `)) || DEFAULTS.retentionDays);
|
|
175
|
-
const maxSizeMB = opts.maxSizeMB ?? (Number(await io.prompt(`Max DB size MB [${DEFAULTS.maxSizeMB}]: `)) || DEFAULTS.maxSizeMB);
|
|
198
|
+
const retentionDays = opts.retentionDays ?? (Number(await io.prompt(`Retention days ${dim(`[${DEFAULTS.retentionDays}]`)}: `)) || DEFAULTS.retentionDays);
|
|
199
|
+
const maxSizeMB = opts.maxSizeMB ?? (Number(await io.prompt(`Max DB size MB ${dim(`[${DEFAULTS.maxSizeMB}]`)}: `)) || DEFAULTS.maxSizeMB);
|
|
176
200
|
const dbPath = opts.dbPath ?? DEFAULTS.dbPath;
|
|
177
201
|
const { config, token } = buildInitConfig({ port, host, retentionDays, maxSizeMB, dbPath });
|
|
178
202
|
const db = openDb(resolveDbPath(configPath, config));
|
|
@@ -180,11 +204,13 @@ async function runInit(configPath, opts, io) {
|
|
|
180
204
|
db.close();
|
|
181
205
|
saveConfig(configPath, config);
|
|
182
206
|
io.log("");
|
|
183
|
-
io.log(` tinylogs configured \u2192 ${configPath}`);
|
|
184
|
-
io.log(` Ingest token (shown once, store it now):`);
|
|
185
|
-
io.log(` ${token}`);
|
|
207
|
+
io.log(` ${green("\u2713")} tinylogs configured \u2192 ${dim(configPath)}`);
|
|
186
208
|
io.log("");
|
|
187
|
-
io.log(`
|
|
209
|
+
io.log(` ${yellow("\u26A0")} Ingest token \u2014 shown once, store it now:`);
|
|
210
|
+
io.log("");
|
|
211
|
+
io.log(` ${bold(cyan(token))}`);
|
|
212
|
+
io.log("");
|
|
213
|
+
io.log(` ${dim("Start with:")} ${bold("tinylogs start -d")}`);
|
|
188
214
|
}
|
|
189
215
|
|
|
190
216
|
// src/server/index.ts
|
|
@@ -193,15 +219,15 @@ import { createServer } from "node:http";
|
|
|
193
219
|
// src/storage/retention.ts
|
|
194
220
|
function pruneByAge(db, retentionDays, now) {
|
|
195
221
|
const cutoff = now - retentionDays * 864e5;
|
|
196
|
-
return db.prepare("DELETE FROM logs WHERE ts < ?").run(cutoff).changes;
|
|
222
|
+
return Number(db.prepare("DELETE FROM logs WHERE ts < ?").run(cutoff).changes);
|
|
197
223
|
}
|
|
198
224
|
function pruneBySize(db, maxSizeMB, batch = 1e3) {
|
|
199
225
|
const maxBytes = maxSizeMB * 1024 * 1024;
|
|
200
226
|
let deleted = 0;
|
|
201
227
|
while (dbSizeBytes(db) > maxBytes) {
|
|
202
|
-
const changes = db.prepare(
|
|
228
|
+
const changes = Number(db.prepare(
|
|
203
229
|
"DELETE FROM logs WHERE id IN (SELECT id FROM logs ORDER BY id ASC LIMIT ?)"
|
|
204
|
-
).run(batch).changes;
|
|
230
|
+
).run(batch).changes);
|
|
205
231
|
if (changes === 0) break;
|
|
206
232
|
deleted += changes;
|
|
207
233
|
}
|
|
@@ -339,23 +365,21 @@ function queryLogs(db, params) {
|
|
|
339
365
|
ORDER BY logs.id DESC LIMIT ?`;
|
|
340
366
|
return db.prepare(sql).all(...args, limit).map(rowToRecord);
|
|
341
367
|
}
|
|
368
|
+
function rowToLabel(row) {
|
|
369
|
+
return { key: row.key, value: row.value, count: Number(row.count) };
|
|
370
|
+
}
|
|
342
371
|
function queryLabels(db, opts = {}) {
|
|
343
372
|
const services = db.prepare(
|
|
344
373
|
"SELECT service, COUNT(*) count FROM logs GROUP BY service ORDER BY count DESC"
|
|
345
|
-
).all();
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
labels = db.prepare(
|
|
355
|
-
`SELECT key, value, COUNT(*) count FROM log_labels
|
|
356
|
-
GROUP BY key, value ORDER BY key, count DESC`
|
|
357
|
-
).all();
|
|
358
|
-
}
|
|
374
|
+
).all().map((row) => ({ service: row.service, count: Number(row.count) }));
|
|
375
|
+
const labels = opts.service ? db.prepare(
|
|
376
|
+
`SELECT ll.key, ll.value, COUNT(*) count FROM log_labels ll
|
|
377
|
+
JOIN logs ON logs.id = ll.log_id WHERE logs.service = ?
|
|
378
|
+
GROUP BY ll.key, ll.value ORDER BY ll.key, count DESC`
|
|
379
|
+
).all(opts.service).map(rowToLabel) : db.prepare(
|
|
380
|
+
`SELECT key, value, COUNT(*) count FROM log_labels
|
|
381
|
+
GROUP BY key, value ORDER BY key, count DESC`
|
|
382
|
+
).all().map(rowToLabel);
|
|
359
383
|
return { services, labels };
|
|
360
384
|
}
|
|
361
385
|
|
|
@@ -717,21 +741,6 @@ function runNpmUpdate() {
|
|
|
717
741
|
return r.status ?? 1;
|
|
718
742
|
}
|
|
719
743
|
|
|
720
|
-
// src/style.ts
|
|
721
|
-
function computeSupportsColor() {
|
|
722
|
-
if (process.env.NO_COLOR) return false;
|
|
723
|
-
if (process.env.FORCE_COLOR) return true;
|
|
724
|
-
return Boolean(process.stdout.isTTY);
|
|
725
|
-
}
|
|
726
|
-
var useColor = computeSupportsColor();
|
|
727
|
-
var wrap = (open, close) => (s) => useColor ? `\x1B[${open}m${s}\x1B[${close}m` : s;
|
|
728
|
-
var bold = wrap(1, 22);
|
|
729
|
-
var dim = wrap(2, 22);
|
|
730
|
-
var red = wrap(31, 39);
|
|
731
|
-
var green = wrap(32, 39);
|
|
732
|
-
var yellow = wrap(33, 39);
|
|
733
|
-
var cyan = wrap(36, 39);
|
|
734
|
-
|
|
735
744
|
// src/cli.ts
|
|
736
745
|
function parseFlags(argv) {
|
|
737
746
|
const out = {};
|
package/dist/cli.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/cli.ts", "../src/config.ts", "../src/storage/db.ts", "../src/auth.ts", "../src/wizard.ts", "../src/server/index.ts", "../src/storage/retention.ts", "../src/buffer.ts", "../src/server/app.ts", "../src/server/ingest.ts", "../src/server/queryRoutes.ts", "../src/storage/query.ts", "../src/server/authRoutes.ts", "../src/server/stream.ts", "../src/daemon.ts", "../src/update.ts"
|
|
4
|
-
"sourcesContent": ["import { createInterface } from 'node:readline/promises';\nimport { existsSync } from 'node:fs';\nimport { loadConfig, resolveConfigPath, generateSecrets, saveConfig } from './config.js';\nimport { runInit } from './wizard.js';\nimport { start } from './server/index.js';\nimport { spawnDaemon, stopDaemon, statusInfo } from './daemon.js';\nimport { currentVersion, fetchLatest, detectInstall, runNpmUpdate } from './update.js';\nimport { dim, green, red, yellow } from './style.js';\n\nfunction parseFlags(argv: string[]): Record<string, string | boolean> {\n const out: Record<string, string | boolean> = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a.startsWith('--')) {\n const key = a.slice(2);\n const next = argv[i + 1];\n if (next && !next.startsWith('--')) { out[key] = next; i++; } else out[key] = true;\n }\n }\n return out;\n}\n\nfunction makeIo() {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return {\n io: {\n prompt: (q: string) => rl.question(q),\n // NOTE: not truly hidden; acceptable for a pet tool. Prefer --password / TINYLOGS_PASSWORD in scripts.\n promptHidden: (q: string) => rl.question(q),\n log: (s: string) => console.log(s),\n },\n done: () => rl.close(),\n };\n}\n\nfunction formatUptime(ms: number | null): string {\n if (ms == null) return '\u2014';\n const s = Math.floor(ms / 1000);\n const d = Math.floor(s / 86400);\n const h = Math.floor((s % 86400) / 3600);\n const m = Math.floor((s % 3600) / 60);\n if (d) return `${d}d ${h}h`;\n if (h) return `${h}h ${m}m`;\n if (m) return `${m}m`;\n return `${s}s`;\n}\n\nasync function main() {\n const [cmd, ...rest] = process.argv.slice(2);\n const flags = parseFlags(rest);\n const configPath = resolveConfigPath(typeof flags.config === 'string' ? flags.config : undefined);\n\n if (cmd === 'init') {\n const nonInteractive = flags.yes === true || flags.y === true;\n const opts = {\n port: flags.port ? Number(flags.port) : undefined,\n host: typeof flags.host === 'string' ? flags.host : undefined,\n username: typeof flags.username === 'string' ? flags.username : undefined,\n password: (typeof flags.password === 'string' ? flags.password : undefined) ?? process.env.TINYLOGS_PASSWORD,\n retentionDays: flags['retention-days'] ? Number(flags['retention-days']) : undefined,\n maxSizeMB: flags['max-size-mb'] ? Number(flags['max-size-mb']) : undefined,\n dbPath: typeof flags['db-path'] === 'string' ? flags['db-path'] : undefined,\n };\n if (existsSync(configPath) && !flags.force) {\n console.error(`Config already exists at ${configPath} (use --force to overwrite).`);\n process.exit(1);\n }\n if (nonInteractive) {\n if (!opts.password) { console.error('Non-interactive init requires --password or TINYLOGS_PASSWORD.'); process.exit(1); }\n await runInit(configPath, { ...opts, username: opts.username ?? 'admin' }, { prompt: async () => '', promptHidden: async () => '', log: (s) => console.log(s) });\n } else {\n const { io, done } = makeIo();\n try { await runInit(configPath, opts, io); } finally { done(); }\n }\n return;\n }\n\n if (cmd === 'start') {\n if (!existsSync(configPath)) { console.error(`No config at ${configPath}. Run: tinylogs init`); process.exit(1); }\n const daemon = flags.daemon === true || rest.includes('-d');\n if (daemon) {\n try {\n const { pid, port, logFile } = await spawnDaemon(configPath);\n console.log(`${green('\u2713')} tinylogs started in background`);\n console.log(` ${dim('pid')} ${pid}`);\n console.log(` ${dim('url')} http://127.0.0.1:${port}`);\n console.log(` ${dim('logs')} ${logFile}`);\n console.log(` ${dim('stop')} tinylogs stop`);\n } catch (e) {\n console.error(`${red('\u2717')} ${(e as Error).message}`);\n process.exit(1);\n }\n return;\n }\n const srv = await start(configPath);\n const shutdown = async () => { try { await srv.close(); } finally { process.exit(0); } };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n return; // server keeps the process alive\n }\n\n if (cmd === 'status') {\n if (!existsSync(configPath)) { console.error(`No config at ${configPath}. Run: tinylogs init`); process.exit(1); }\n const s = statusInfo(configPath);\n if (s.running) {\n console.log(`${green('\u25CF')} running`);\n console.log(` ${dim('pid')} ${s.pid}`);\n const shownHost = s.host === '0.0.0.0' || s.host === '::' ? '127.0.0.1' : s.host;\n console.log(` ${dim('url')} http://${shownHost}:${s.port}`);\n console.log(` ${dim('uptime')} ${formatUptime(s.uptimeMs)}`);\n } else {\n console.log(`${dim('\u25CB')} stopped${s.stale ? ' ' + yellow('(cleaned stale pidfile)') : ''}`);\n }\n console.log(` ${dim('version')} ${currentVersion()}`);\n const latest = await fetchLatest();\n if (latest && latest !== currentVersion()) {\n console.log(` ${yellow('\u26A0')} update available: ${latest} \u2014 run: tinylogs update`);\n }\n return;\n }\n\n if (cmd === 'stop') {\n if (!existsSync(configPath)) { console.error(`No config at ${configPath}.`); process.exit(1); }\n const r = await stopDaemon(configPath);\n if (r === 'not-running') console.log(`${dim('\u25CB')} not running`);\n else if (r === 'stopped') console.log(`${green('\u2713')} stopped`);\n else console.log(`${yellow('\u26A0')} force-killed (did not stop gracefully within 5s)`);\n return;\n }\n\n if (cmd === 'restart') {\n if (!existsSync(configPath)) { console.error(`No config at ${configPath}. Run: tinylogs init`); process.exit(1); }\n await stopDaemon(configPath);\n try {\n const { pid, port, logFile } = await spawnDaemon(configPath);\n console.log(`${green('\u2713')} tinylogs restarted`);\n console.log(` ${dim('pid')} ${pid}`);\n console.log(` ${dim('url')} http://127.0.0.1:${port}`);\n console.log(` ${dim('logs')} ${logFile}`);\n } catch (e) {\n console.error(`${red('\u2717')} ${(e as Error).message}`);\n process.exit(1);\n }\n return;\n }\n\n if (cmd === 'version' || cmd === '--version' || cmd === '-v') {\n console.log(`tinylogs ${currentVersion()}`);\n const latest = await fetchLatest();\n if (latest && latest !== currentVersion()) {\n console.log(`${yellow('\u26A0')} update available: ${latest} \u2014 run: tinylogs update`);\n } else if (latest) {\n console.log(dim('up to date'));\n }\n return;\n }\n\n if (cmd === 'update') {\n const cur = currentVersion();\n const latest = await fetchLatest();\n console.log(` ${dim('current')} ${cur}`);\n if (latest) console.log(` ${dim('latest')} ${latest}`);\n if (latest && latest === cur) { console.log(`${green('\u2713')} already up to date`); return; }\n if (detectInstall() === 'source') {\n console.log(`${yellow('\u26A0')} running from a source checkout \u2014 update with:`);\n console.log(` git pull && npm install && npm run build`);\n return;\n }\n console.log(dim('\u2192 npm i -g tinylogs@latest'));\n const code = runNpmUpdate();\n if (code !== 0) { console.error(`${red('\u2717')} npm update failed`); process.exit(code); }\n console.log(`${green('\u2713')} updated to ${latest ?? 'latest'}`);\n if (existsSync(configPath)) {\n const s = statusInfo(configPath);\n if (s.running) console.log(`${yellow('\u26A0')} daemon is running \u2014 run: tinylogs restart`);\n }\n return;\n }\n\n if (cmd === 'rotate-token') {\n if (!existsSync(configPath)) { console.error(`No config at ${configPath}.`); process.exit(1); }\n const cfg = loadConfig(configPath);\n const s = generateSecrets();\n cfg.ingestTokenHash = s.ingestTokenHash;\n saveConfig(configPath, cfg);\n console.log('New ingest token (shown once):');\n console.log(` ${s.token}`);\n return;\n }\n\n console.log(\n [\n 'tinylogs \u2014 usage:',\n ' tinylogs init [--yes --password ...]',\n ' tinylogs start [-d|--daemon] start (foreground, or background with -d)',\n ' tinylogs status is it running? pid, url, uptime, version',\n ' tinylogs stop stop the background server',\n ' tinylogs restart stop + start -d',\n ' tinylogs update update to the latest published version',\n ' tinylogs version show version (and check for updates)',\n ' tinylogs rotate-token issue a new ingest token',\n ].join('\\n'),\n );\n if (cmd && cmd !== 'help') process.exit(1);\n}\n\nmain().catch((err) => { console.error(err); process.exit(1); });\n", "import { createHash, randomBytes } from 'node:crypto';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport type { Config } from './types.js';\n\nexport const DEFAULTS = {\n port: 4700,\n host: '127.0.0.1',\n dbPath: 'tinylogs.db',\n retentionDays: 14,\n maxSizeMB: 500,\n bufferSize: 2000,\n} as const;\n\nexport function resolveConfigPath(flag?: string): string {\n if (flag) return flag;\n if (process.env.TINYLOGS_CONFIG) return process.env.TINYLOGS_CONFIG;\n return join(process.cwd(), 'tinylogs.config.json');\n}\n\nexport function hashToken(token: string): string {\n return createHash('sha256').update(token, 'utf8').digest('hex');\n}\n\nexport function generateSecrets() {\n const sessionSecret = randomBytes(32).toString('hex');\n const token = randomBytes(24).toString('base64url');\n return { sessionSecret, token, ingestTokenHash: hashToken(token) };\n}\n\nexport function saveConfig(path: string, cfg: Config): void {\n writeFileSync(path, JSON.stringify(cfg, null, 2) + '\\n', { mode: 0o600 });\n}\n\nexport function loadConfig(path: string): Config {\n const raw = readFileSync(path, 'utf8'); // throws if missing\n const cfg = JSON.parse(raw) as Config;\n for (const k of ['port', 'host', 'dbPath', 'sessionSecret', 'ingestTokenHash'] as const) {\n if (cfg[k] === undefined) throw new Error(`config missing field: ${k}`);\n }\n return cfg;\n}\n\nexport function resolveDbPath(configPath: string, cfg: Config): string {\n if (isAbsolute(cfg.dbPath)) return cfg.dbPath;\n return resolve(dirname(configPath), cfg.dbPath);\n}\n", "import Database from 'better-sqlite3';\nimport type { LogRecord } from '../types.js';\n\nconst MIGRATIONS: string[] = [\n `CREATE TABLE logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts INTEGER NOT NULL,\n service TEXT NOT NULL,\n message TEXT NOT NULL,\n labels TEXT NOT NULL\n );\n CREATE INDEX idx_logs_ts ON logs(ts);\n CREATE INDEX idx_logs_service ON logs(service);\n CREATE TABLE log_labels (\n log_id INTEGER NOT NULL REFERENCES logs(id) ON DELETE CASCADE,\n key TEXT NOT NULL,\n value TEXT NOT NULL\n );\n CREATE INDEX idx_labels_kv ON log_labels(key, value);\n CREATE INDEX idx_labels_logid ON log_labels(log_id);\n CREATE TABLE users (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n username TEXT UNIQUE NOT NULL,\n password_hash TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'admin',\n created_at INTEGER NOT NULL\n );`,\n];\n\nexport function openDb(path: string): Database.Database {\n const db = new Database(path);\n db.pragma('journal_mode = WAL');\n db.pragma('foreign_keys = ON');\n db.exec('CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY)');\n const current = (db.prepare('SELECT COALESCE(MAX(version),0) v FROM schema_migrations').get() as any).v as number;\n const apply = db.transaction((from: number) => {\n for (let i = from; i < MIGRATIONS.length; i++) {\n db.exec(MIGRATIONS[i]);\n db.prepare('INSERT INTO schema_migrations (version) VALUES (?)').run(i + 1);\n }\n });\n apply(current);\n return db;\n}\n\nexport function insertLog(db: Database.Database, rec: LogRecord): number {\n const txn = db.transaction((r: LogRecord) => {\n const info = db.prepare('INSERT INTO logs (ts, service, message, labels) VALUES (?,?,?,?)')\n .run(r.ts, r.service, r.message, JSON.stringify(r.labels ?? {}));\n const logId = Number(info.lastInsertRowid);\n const ins = db.prepare('INSERT INTO log_labels (log_id, key, value) VALUES (?,?,?)');\n for (const [k, v] of Object.entries(r.labels ?? {})) ins.run(logId, k, String(v));\n return logId;\n });\n return txn(rec);\n}\n\nexport function dbSizeBytes(db: Database.Database): number {\n const pageCount = (db.pragma('page_count', { simple: true }) as number);\n const pageSize = (db.pragma('page_size', { simple: true }) as number);\n return pageCount * pageSize;\n}\n", "import bcrypt from 'bcryptjs';\nimport { createHmac, timingSafeEqual } from 'node:crypto';\nimport type Database from 'better-sqlite3';\nimport { hashToken } from './config.js';\n\nexport function hashPassword(pw: string): string {\n return bcrypt.hashSync(pw, 10);\n}\n\nexport function createUser(db: Database.Database, username: string, pw: string, role = 'admin'): number {\n const info = db.prepare(\n 'INSERT INTO users (username, password_hash, role, created_at) VALUES (?,?,?,?)'\n ).run(username, hashPassword(pw), role, Date.now());\n return Number(info.lastInsertRowid);\n}\n\nexport function verifyUser(db: Database.Database, username: string, pw: string): boolean {\n const row = db.prepare('SELECT password_hash FROM users WHERE username=?').get(username) as any;\n if (!row) { bcrypt.compareSync(pw, '$2a$10$0000000000000000000000000000000000000000000000000000'); return false; }\n return bcrypt.compareSync(pw, row.password_hash);\n}\n\nfunction safeEqualHex(a: string, b: string): boolean {\n const ab = Buffer.from(a, 'hex'); const bb = Buffer.from(b, 'hex');\n if (ab.length !== bb.length || ab.length === 0) return false;\n return timingSafeEqual(ab, bb);\n}\n\nexport function verifyIngestToken(bearer: string | undefined, ingestTokenHash: string): boolean {\n if (!bearer || !bearer.startsWith('Bearer ')) return false;\n const token = bearer.slice('Bearer '.length);\n return safeEqualHex(hashToken(token), ingestTokenHash);\n}\n\nexport function signSession(username: string, secret: string): string {\n const mac = createHmac('sha256', secret).update(username).digest('hex');\n return `${username}.${mac}`;\n}\n\nexport function verifySession(cookieVal: string | undefined, secret: string): string | null {\n if (!cookieVal) return null;\n const dot = cookieVal.lastIndexOf('.');\n if (dot <= 0) return null;\n const username = cookieVal.slice(0, dot);\n const mac = cookieVal.slice(dot + 1);\n const expected = createHmac('sha256', secret).update(username).digest('hex');\n if (mac.length !== expected.length) return null;\n if (!timingSafeEqual(Buffer.from(mac), Buffer.from(expected))) return null;\n return username;\n}\n", "import { openDb } from './storage/db.js';\nimport { createUser } from './auth.js';\nimport { DEFAULTS, generateSecrets, saveConfig, resolveDbPath } from './config.js';\nimport type { Config } from './types.js';\n\nexport interface InitOptions {\n port?: number; host?: string; username?: string; password?: string;\n retentionDays?: number; maxSizeMB?: number; dbPath?: string;\n}\n\nexport function buildInitConfig(opts: {\n port: number; host: string; retentionDays: number; maxSizeMB: number; dbPath: string;\n}): { config: Config; token: string } {\n const s = generateSecrets();\n const config: Config = {\n port: opts.port, host: opts.host, dbPath: opts.dbPath,\n retentionDays: opts.retentionDays, maxSizeMB: opts.maxSizeMB,\n bufferSize: DEFAULTS.bufferSize,\n sessionSecret: s.sessionSecret, ingestTokenHash: s.ingestTokenHash,\n };\n return { config, token: s.token };\n}\n\nexport async function runInit(\n configPath: string,\n opts: InitOptions,\n io: { prompt: (q: string) => Promise<string>; promptHidden: (q: string) => Promise<string>; log: (s: string) => void },\n): Promise<void> {\n const port = opts.port ?? (Number(await io.prompt(`Port [${DEFAULTS.port}]: `)) || DEFAULTS.port);\n const host = opts.host ?? ((await io.prompt(`Host [${DEFAULTS.host}]: `)) || DEFAULTS.host);\n const username = opts.username ?? ((await io.prompt('Admin username [admin]: ')) || 'admin');\n const password = opts.password ?? await io.promptHidden('Admin password: ');\n if (!password || password.length === 0) throw new Error('password must not be empty');\n const retentionDays = opts.retentionDays ?? (Number(await io.prompt(`Retention days [${DEFAULTS.retentionDays}]: `)) || DEFAULTS.retentionDays);\n const maxSizeMB = opts.maxSizeMB ?? (Number(await io.prompt(`Max DB size MB [${DEFAULTS.maxSizeMB}]: `)) || DEFAULTS.maxSizeMB);\n const dbPath = opts.dbPath ?? DEFAULTS.dbPath;\n\n const { config, token } = buildInitConfig({ port, host, retentionDays, maxSizeMB, dbPath });\n const db = openDb(resolveDbPath(configPath, config));\n createUser(db, username, password);\n db.close();\n saveConfig(configPath, config);\n\n io.log('');\n io.log(` tinylogs configured \u2192 ${configPath}`);\n io.log(` Ingest token (shown once, store it now):`);\n io.log(` ${token}`);\n io.log('');\n io.log(` Start with: npx tinylogs start`);\n}\n", "import { createServer } from 'node:http';\nimport { openDb } from '../storage/db.js';\nimport { runRetention } from '../storage/retention.js';\nimport { RingBuffer } from '../buffer.js';\nimport { loadConfig, resolveConfigPath, resolveDbPath } from '../config.js';\nimport { createApp } from './app.js';\nimport { attachWebSocket } from './stream.js';\nimport type { WebSocket } from 'ws';\nimport type { LogRecord } from '../types.js';\n\nexport async function start(configPath = resolveConfigPath()): Promise<{ port: number; close: () => Promise<void> }> {\n const cfg = loadConfig(configPath);\n const db = openDb(resolveDbPath(configPath, cfg));\n const buffer = new RingBuffer(cfg.bufferSize);\n const wsClients = new Set<WebSocket>();\n const broadcast = (r: LogRecord) => {\n const msg = JSON.stringify({ type: 'log', data: r });\n for (const c of wsClients) { try { if (c.readyState === 1) c.send(msg); } catch {} }\n };\n\n const app = createApp({ db, buffer, cfg, broadcast });\n const server = createServer(app);\n attachWebSocket(server, { buffer, cfg, clients: wsClients });\n\n const retentionTimer = setInterval(\n () => runRetention(db, { retentionDays: cfg.retentionDays, maxSizeMB: cfg.maxSizeMB }),\n 60_000,\n );\n retentionTimer.unref();\n\n await new Promise<void>((resolve) => server.listen(cfg.port, cfg.host, resolve));\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : cfg.port;\n console.log(`[tinylogs] listening on http://${cfg.host}:${port}`);\n\n return {\n port,\n close: () => new Promise<void>((resolve) => {\n clearInterval(retentionTimer);\n for (const c of wsClients) { try { c.close(); } catch {} }\n server.close(() => { db.close(); resolve(); });\n server.closeIdleConnections?.();\n }),\n };\n}\n", "import type Database from 'better-sqlite3';\nimport { dbSizeBytes } from './db.js';\n\nexport function pruneByAge(db: Database.Database, retentionDays: number, now: number): number {\n const cutoff = now - retentionDays * 86400000;\n return db.prepare('DELETE FROM logs WHERE ts < ?').run(cutoff).changes;\n}\n\nexport function pruneBySize(db: Database.Database, maxSizeMB: number, batch = 1000): number {\n const maxBytes = maxSizeMB * 1024 * 1024;\n let deleted = 0;\n // Delete oldest rows in batches until file is under the cap or nothing remains.\n while (dbSizeBytes(db) > maxBytes) {\n const changes = db.prepare(\n 'DELETE FROM logs WHERE id IN (SELECT id FROM logs ORDER BY id ASC LIMIT ?)'\n ).run(batch).changes;\n if (changes === 0) break;\n deleted += changes;\n }\n return deleted;\n}\n\nexport function runRetention(\n db: Database.Database,\n opts: { retentionDays: number; maxSizeMB: number; now?: number }\n): void {\n try {\n const now = opts.now ?? Date.now();\n pruneByAge(db, opts.retentionDays, now);\n pruneBySize(db, opts.maxSizeMB);\n db.exec('VACUUM');\n } catch (err) {\n console.error('[tinylogs] retention failed:', (err as Error).message);\n }\n}\n", "import type { LogRecord } from './types.js';\n\nexport class RingBuffer {\n private items: LogRecord[] = [];\n constructor(private capacity: number) {}\n\n push(rec: LogRecord): void {\n this.items.push(rec);\n if (this.items.length > this.capacity) this.items.shift();\n }\n\n snapshot(): LogRecord[] {\n return this.items.slice();\n }\n\n get size(): number {\n return this.items.length;\n }\n}\n", "import express, { type Express } from 'express';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type Database from 'better-sqlite3';\nimport type { Config, LogRecord } from '../types.js';\nimport { RingBuffer } from '../buffer.js';\nimport { registerIngest } from './ingest.js';\nimport { registerQueryRoutes } from './queryRoutes.js';\nimport { registerAuthRoutes } from './authRoutes.js';\n\nexport interface AppDeps {\n db: Database.Database;\n buffer: RingBuffer;\n cfg: Config;\n broadcast: (r: LogRecord) => void;\n}\n\nexport function createApp(deps: AppDeps): Express {\n const app = express();\n app.set('trust proxy', true); // so req.ip reflects X-Forwarded-For behind a reverse proxy\n app.set('deps', deps);\n\n app.get('/api/health', (_req, res) => res.json({ ok: true }));\n\n // /ingest gets its own 5MB JSON parser (Task 9 mounts the handler after this).\n app.use('/ingest', express.json({ limit: '5mb' }));\n app.use('/api', express.json({ limit: '1mb' }));\n\n registerIngest(app);\n registerQueryRoutes(app);\n registerAuthRoutes(app);\n\n // Static UI (built to dist/ui). The running entry may be dist/cli.js (here = dist)\n // or dist/server/index.js (here = dist/server), so probe both depths. In dev (tsx\n // from src) neither resolves, so also fall back to <cwd>/dist/ui. Pick the first\n // candidate that actually contains the built bundle. Absent entirely \u2192 skip.\n const here = dirname(fileURLToPath(import.meta.url));\n const uiDir = [join(here, 'ui'), join(here, '..', 'ui'), join(process.cwd(), 'dist', 'ui')]\n .find((d) => existsSync(join(d, 'bundle.js')));\n if (uiDir) app.use(express.static(uiDir));\n\n return app;\n}\n", "import type { Express } from 'express';\nimport type { AppDeps } from './app.js';\nimport type { LogRecord } from '../types.js';\nimport { verifyIngestToken } from '../auth.js';\nimport { insertLog } from '../storage/db.js';\n\nconst MAX_MESSAGE = 16384;\nconst MAX_LABELS = 50;\nconst MAX_KV = 512;\nconst MAX_BATCH = 1000;\n\nexport function validateRecord(raw: any): { ok: true; rec: LogRecord } | { ok: false; error: string } {\n if (typeof raw !== 'object' || raw === null) return { ok: false, error: 'record must be an object' };\n if (typeof raw.service !== 'string' || raw.service.length === 0) return { ok: false, error: 'service required' };\n if (typeof raw.message !== 'string' || raw.message.length === 0) return { ok: false, error: 'message required' };\n if (raw.message.length > MAX_MESSAGE) return { ok: false, error: 'message too long' };\n if (raw.service.length > MAX_KV) return { ok: false, error: 'service too long' };\n const labels: Record<string, string> = {};\n if (raw.labels !== undefined) {\n if (typeof raw.labels !== 'object' || raw.labels === null || Array.isArray(raw.labels))\n return { ok: false, error: 'labels must be an object' };\n const keys = Object.keys(raw.labels);\n if (keys.length > MAX_LABELS) return { ok: false, error: 'too many labels' };\n for (const k of keys) {\n const v = raw.labels[k];\n if (typeof v !== 'string') return { ok: false, error: `label ${k} must be a string` };\n if (k.length > MAX_KV || v.length > MAX_KV) return { ok: false, error: `label ${k} too long` };\n labels[k] = v;\n }\n }\n const ts = typeof raw.ts === 'number' && Number.isFinite(raw.ts) ? raw.ts : Date.now();\n return { ok: true, rec: { ts, service: raw.service, message: raw.message, labels } };\n}\n\nexport function registerIngest(app: Express): void {\n app.post('/ingest', (req, res) => {\n const deps = app.get('deps') as AppDeps;\n if (!verifyIngestToken(req.header('authorization'), deps.cfg.ingestTokenHash)) {\n return res.status(401).json({ error: 'invalid token' });\n }\n const body = req.body;\n const records = Array.isArray(body) ? body : [body];\n if (records.length > MAX_BATCH) return res.status(400).json({ error: 'batch too large' });\n const validated: LogRecord[] = [];\n for (const raw of records) {\n const v = validateRecord(raw);\n if (!v.ok) return res.status(400).json({ error: v.error });\n validated.push(v.rec);\n }\n try {\n for (const rec of validated) {\n const id = insertLog(deps.db, rec);\n const stored = { ...rec, id };\n deps.buffer.push(stored);\n deps.broadcast(stored);\n }\n return res.json({ accepted: validated.length });\n } catch (err) {\n console.error('[tinylogs] ingest error:', (err as Error).message);\n return res.status(500).json({ error: 'internal error' });\n }\n });\n}\n", "import type { Express, RequestHandler } from 'express';\nimport { parse as parseCookie } from 'cookie';\nimport type { AppDeps } from './app.js';\nimport type { QueryParams } from '../storage/query.js';\nimport { queryLogs, queryLabels } from '../storage/query.js';\nimport { verifySession } from '../auth.js';\n\nexport function requireSession(app: Express): RequestHandler {\n return (req, res, next) => {\n const deps = app.get('deps') as AppDeps;\n const cookies = parseCookie(req.header('cookie') ?? '');\n const user = verifySession(cookies['tl_session'], deps.cfg.sessionSecret);\n if (!user) return res.status(401).json({ error: 'unauthorized' });\n (req as any).user = user;\n next();\n };\n}\n\nexport function parseQuery(query: Record<string, any>): QueryParams {\n const p: QueryParams = {};\n if (typeof query.service === 'string') p.service = query.service;\n if (typeof query.q === 'string') p.q = query.q;\n if (query.from !== undefined) p.from = Number(query.from);\n if (query.to !== undefined) p.to = Number(query.to);\n if (query.limit !== undefined) p.limit = Number(query.limit);\n if (query.before !== undefined) p.before = Number(query.before);\n const rawLabels = query.label === undefined ? [] : Array.isArray(query.label) ? query.label : [query.label];\n p.labels = rawLabels\n .map((s: string) => { const i = s.indexOf('='); return i < 0 ? null : { key: s.slice(0, i), value: s.slice(i + 1) }; })\n .filter(Boolean) as Array<{ key: string; value: string }>;\n return p;\n}\n\nexport function registerQueryRoutes(app: Express): void {\n const guard = requireSession(app);\n app.get('/api/logs', guard, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const logs = queryLogs(deps.db, parseQuery(req.query as any));\n res.json({ logs });\n });\n app.get('/api/labels', guard, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const service = typeof req.query.service === 'string' ? req.query.service : undefined;\n res.json(queryLabels(deps.db, { service }));\n });\n}\n", "import type Database from 'better-sqlite3';\nimport type { LogRecord } from '../types.js';\n\nexport interface QueryParams {\n service?: string;\n labels?: Array<{ key: string; value: string }>;\n q?: string;\n from?: number; to?: number;\n limit?: number;\n before?: number;\n}\n\nfunction rowToRecord(row: any): LogRecord {\n return { id: row.id, ts: row.ts, service: row.service, message: row.message, labels: JSON.parse(row.labels) };\n}\n\nexport function queryLogs(db: Database.Database, params: QueryParams): LogRecord[] {\n const where: string[] = [];\n const args: any[] = [];\n if (params.service) { where.push('logs.service = ?'); args.push(params.service); }\n if (params.q) { where.push('logs.message LIKE ? COLLATE NOCASE'); args.push(`%${params.q}%`); }\n if (params.from !== undefined) { where.push('logs.ts >= ?'); args.push(params.from); }\n if (params.to !== undefined) { where.push('logs.ts < ?'); args.push(params.to); }\n if (params.before !== undefined) { where.push('logs.id < ?'); args.push(params.before); }\n for (const l of params.labels ?? []) {\n where.push('logs.id IN (SELECT log_id FROM log_labels WHERE key = ? AND value = ?)');\n args.push(l.key, l.value);\n }\n const limit = Math.min(Math.max(params.limit ?? 200, 1), 1000);\n const sql = `SELECT id, ts, service, message, labels FROM logs\n ${where.length ? 'WHERE ' + where.join(' AND ') : ''}\n ORDER BY logs.id DESC LIMIT ?`;\n return db.prepare(sql).all(...args, limit).map(rowToRecord);\n}\n\nexport interface LabelCount { key: string; value: string; count: number; }\n\nexport function queryLabels(db: Database.Database, opts: { service?: string } = {}) {\n const services = db.prepare(\n 'SELECT service, COUNT(*) count FROM logs GROUP BY service ORDER BY count DESC'\n ).all() as Array<{ service: string; count: number }>;\n\n let labels: LabelCount[];\n if (opts.service) {\n labels = db.prepare(\n `SELECT ll.key, ll.value, COUNT(*) count FROM log_labels ll\n JOIN logs ON logs.id = ll.log_id WHERE logs.service = ?\n GROUP BY ll.key, ll.value ORDER BY ll.key, count DESC`\n ).all(opts.service) as LabelCount[];\n } else {\n labels = db.prepare(\n `SELECT key, value, COUNT(*) count FROM log_labels\n GROUP BY key, value ORDER BY key, count DESC`\n ).all() as LabelCount[];\n }\n return { services, labels };\n}\n", "import type { Express, RequestHandler } from 'express';\nimport { serialize as serializeCookie } from 'cookie';\nimport type { AppDeps } from './app.js';\nimport { verifyUser, signSession } from '../auth.js';\n\nexport function makeLoginLimiter(maxAttempts = 10, windowMs = 5 * 60_000): RequestHandler {\n const hits = new Map<string, { count: number; reset: number }>();\n return (req, res, next) => {\n const now = Date.now();\n const ip = req.ip ?? 'unknown';\n const rec = hits.get(ip);\n if (!rec || now > rec.reset) { hits.set(ip, { count: 1, reset: now + windowMs }); return next(); }\n rec.count += 1;\n if (rec.count > maxAttempts) return res.status(429).json({ error: 'too many attempts' });\n next();\n };\n}\n\nexport function registerAuthRoutes(app: Express): void {\n const limiter = makeLoginLimiter();\n app.post('/api/login', limiter, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const { username, password } = req.body ?? {};\n if (typeof username !== 'string' || typeof password !== 'string')\n return res.status(400).json({ error: 'username and password required' });\n if (!verifyUser(deps.db, username, password))\n return res.status(401).json({ error: 'invalid credentials' });\n const cookie = serializeCookie('tl_session', signSession(username, deps.cfg.sessionSecret), {\n httpOnly: true, sameSite: 'strict', path: '/', maxAge: 60 * 60 * 24 * 30,\n });\n res.setHeader('Set-Cookie', cookie);\n res.json({ ok: true, username });\n });\n\n app.post('/api/logout', (_req, res) => {\n res.setHeader('Set-Cookie', serializeCookie('tl_session', '', { httpOnly: true, path: '/', maxAge: 0 }));\n res.json({ ok: true });\n });\n}\n", "import type { Server } from 'node:http';\nimport { parse as parseCookie } from 'cookie';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport type { RingBuffer } from '../buffer.js';\nimport type { Config } from '../types.js';\nimport { verifySession } from '../auth.js';\n\nexport function attachWebSocket(\n server: Server,\n deps: { buffer: RingBuffer; cfg: Config; clients: Set<WebSocket> },\n): WebSocketServer {\n const wss = new WebSocketServer({ server, path: '/ws' });\n\n wss.on('connection', (ws, req) => {\n const cookies = parseCookie(req.headers.cookie ?? '');\n const user = verifySession(cookies['tl_session'], deps.cfg.sessionSecret);\n if (!user) { ws.close(4401, 'unauthorized'); return; }\n\n (ws as any).isAlive = true;\n ws.on('pong', () => { (ws as any).isAlive = true; });\n\n ws.send(JSON.stringify({ type: 'buffer', data: deps.buffer.snapshot() }));\n deps.clients.add(ws);\n ws.on('close', () => deps.clients.delete(ws));\n ws.on('error', () => deps.clients.delete(ws));\n });\n\n const interval = setInterval(() => {\n for (const ws of wss.clients) {\n if ((ws as any).isAlive === false) { ws.terminate(); continue; }\n (ws as any).isAlive = false;\n try { ws.ping(); } catch {}\n }\n }, 30_000);\n interval.unref();\n wss.on('close', () => clearInterval(interval));\n\n return wss;\n}\n", "import { spawn } from 'node:child_process';\nimport {\n openSync,\n readFileSync,\n writeFileSync,\n existsSync,\n unlinkSync,\n statSync,\n} from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { loadConfig } from './config.js';\n\nexport function pidPath(configPath: string): string {\n return join(dirname(resolve(configPath)), 'tinylogs.pid');\n}\n\nexport function logPath(configPath: string): string {\n return join(dirname(resolve(configPath)), 'tinylogs.log');\n}\n\nexport function readPid(configPath: string): number | null {\n const p = pidPath(configPath);\n if (!existsSync(p)) return null;\n const n = Number(readFileSync(p, 'utf8').trim());\n return Number.isInteger(n) && n > 0 ? n : null;\n}\n\nexport function isAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface StatusInfo {\n running: boolean;\n stale: boolean;\n pid: number | null;\n port: number;\n host: string;\n uptimeMs: number | null;\n}\n\nexport function statusInfo(configPath: string): StatusInfo {\n const cfg = loadConfig(configPath);\n const pid = readPid(configPath);\n if (pid && isAlive(pid)) {\n let uptimeMs: number | null = null;\n try {\n uptimeMs = Date.now() - statSync(pidPath(configPath)).mtimeMs;\n } catch {}\n return { running: true, stale: false, pid, port: cfg.port, host: cfg.host, uptimeMs };\n }\n if (pid) {\n // stale pidfile \u2014 process is gone; clean it up\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n }\n return {\n running: false,\n stale: pid !== null,\n pid: null,\n port: cfg.port,\n host: cfg.host,\n uptimeMs: null,\n };\n}\n\nconst delay = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\nexport async function spawnDaemon(\n configPath: string,\n): Promise<{ pid: number; port: number; logFile: string }> {\n const existing = readPid(configPath);\n if (existing && isAlive(existing)) {\n throw new Error(`already running (pid ${existing})`);\n }\n if (existing) {\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n }\n\n const cfg = loadConfig(configPath);\n const logFile = logPath(configPath);\n const fd = openSync(logFile, 'a');\n // In the esbuild bundle every module's import.meta.url is dist/cli.js \u2014 the CLI entry.\n const cliEntry = fileURLToPath(import.meta.url);\n const child = spawn(\n process.execPath,\n [cliEntry, 'start', '--config', resolve(configPath)],\n { detached: true, stdio: ['ignore', fd, fd] },\n );\n child.unref();\n const pid = child.pid;\n if (!pid) throw new Error('failed to spawn daemon process');\n writeFileSync(pidPath(configPath), String(pid) + '\\n');\n\n // Confirm it did not crash on startup (e.g. port in use).\n await delay(400);\n if (!isAlive(pid)) {\n let tail = '';\n try {\n tail = readFileSync(logFile, 'utf8').split('\\n').slice(-8).join('\\n');\n } catch {}\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n throw new Error(`daemon exited immediately. Recent log:\\n${tail}`);\n }\n return { pid, port: cfg.port, logFile };\n}\n\nexport async function stopDaemon(\n configPath: string,\n): Promise<'stopped' | 'killed' | 'not-running'> {\n const pid = readPid(configPath);\n if (!pid || !isAlive(pid)) {\n if (pid) {\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n }\n return 'not-running';\n }\n try {\n process.kill(pid, 'SIGTERM');\n } catch {}\n for (let i = 0; i < 50; i++) {\n if (!isAlive(pid)) {\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n return 'stopped';\n }\n await delay(100);\n }\n try {\n process.kill(pid, 'SIGKILL');\n } catch {}\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n return 'killed';\n}\n", "import { spawnSync } from 'node:child_process';\nimport { readFileSync, existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// import.meta.url \u2192 dist/cli.js (bundle) or src/update.ts (dev); one dir up is the package root.\nfunction pkgRoot(): string {\n return dirname(dirname(fileURLToPath(import.meta.url)));\n}\n\nexport function currentVersion(): string {\n try {\n const pkg = JSON.parse(readFileSync(join(pkgRoot(), 'package.json'), 'utf8')) as {\n version?: string;\n };\n return pkg.version ?? '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\nexport async function fetchLatest(timeoutMs = 2500): Promise<string | null> {\n const ctrl = new AbortController();\n const t = setTimeout(() => ctrl.abort(), timeoutMs);\n try {\n const res = await fetch('https://registry.npmjs.org/tinylogs', { signal: ctrl.signal });\n if (!res.ok) return null;\n const data = (await res.json()) as { 'dist-tags'?: { latest?: string } };\n return data['dist-tags']?.latest ?? null;\n } catch {\n return null;\n } finally {\n clearTimeout(t);\n }\n}\n\nexport function detectInstall(): 'source' | 'global' {\n const root = pkgRoot();\n if (existsSync(join(root, '.git')) && existsSync(join(root, 'src'))) return 'source';\n return 'global';\n}\n\nexport function runNpmUpdate(): number {\n const r = spawnSync('npm', ['i', '-g', 'tinylogs@latest'], { stdio: 'inherit' });\n return r.status ?? 1;\n}\n", "function computeSupportsColor(): boolean {\n if (process.env.NO_COLOR) return false;\n if (process.env.FORCE_COLOR) return true;\n return Boolean(process.stdout.isTTY);\n}\n\nconst useColor = computeSupportsColor();\n\nexport function supportsColor(): boolean {\n return useColor;\n}\n\nconst wrap =\n (open: number, close: number) =>\n (s: string): string =>\n useColor ? `\\x1b[${open}m${s}\\x1b[${close}m` : s;\n\nexport const bold = wrap(1, 22);\nexport const dim = wrap(2, 22);\nexport const red = wrap(31, 39);\nexport const green = wrap(32, 39);\nexport const yellow = wrap(33, 39);\nexport const cyan = wrap(36, 39);\n"],
|
|
5
|
-
"mappings": "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",
|
|
3
|
+
"sources": ["../src/cli.ts", "../src/config.ts", "../src/storage/db.ts", "../src/auth.ts", "../src/style.ts", "../src/wizard.ts", "../src/server/index.ts", "../src/storage/retention.ts", "../src/buffer.ts", "../src/server/app.ts", "../src/server/ingest.ts", "../src/server/queryRoutes.ts", "../src/storage/query.ts", "../src/server/authRoutes.ts", "../src/server/stream.ts", "../src/daemon.ts", "../src/update.ts"],
|
|
4
|
+
"sourcesContent": ["import { createInterface } from 'node:readline/promises';\nimport { existsSync } from 'node:fs';\nimport { loadConfig, resolveConfigPath, generateSecrets, saveConfig } from './config.js';\nimport { runInit } from './wizard.js';\nimport { start } from './server/index.js';\nimport { spawnDaemon, stopDaemon, statusInfo } from './daemon.js';\nimport { currentVersion, fetchLatest, detectInstall, runNpmUpdate } from './update.js';\nimport { dim, green, red, yellow } from './style.js';\n\nfunction parseFlags(argv: string[]): Record<string, string | boolean> {\n const out: Record<string, string | boolean> = {};\n for (let i = 0; i < argv.length; i++) {\n const a = argv[i];\n if (a.startsWith('--')) {\n const key = a.slice(2);\n const next = argv[i + 1];\n if (next && !next.startsWith('--')) { out[key] = next; i++; } else out[key] = true;\n }\n }\n return out;\n}\n\nfunction makeIo() {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return {\n io: {\n prompt: (q: string) => rl.question(q),\n // NOTE: not truly hidden; acceptable for a pet tool. Prefer --password / TINYLOGS_PASSWORD in scripts.\n promptHidden: (q: string) => rl.question(q),\n log: (s: string) => console.log(s),\n },\n done: () => rl.close(),\n };\n}\n\nfunction formatUptime(ms: number | null): string {\n if (ms == null) return '\u2014';\n const s = Math.floor(ms / 1000);\n const d = Math.floor(s / 86400);\n const h = Math.floor((s % 86400) / 3600);\n const m = Math.floor((s % 3600) / 60);\n if (d) return `${d}d ${h}h`;\n if (h) return `${h}h ${m}m`;\n if (m) return `${m}m`;\n return `${s}s`;\n}\n\nasync function main() {\n const [cmd, ...rest] = process.argv.slice(2);\n const flags = parseFlags(rest);\n const configPath = resolveConfigPath(typeof flags.config === 'string' ? flags.config : undefined);\n\n if (cmd === 'init') {\n const nonInteractive = flags.yes === true || flags.y === true;\n const opts = {\n port: flags.port ? Number(flags.port) : undefined,\n host: typeof flags.host === 'string' ? flags.host : undefined,\n username: typeof flags.username === 'string' ? flags.username : undefined,\n password: (typeof flags.password === 'string' ? flags.password : undefined) ?? process.env.TINYLOGS_PASSWORD,\n retentionDays: flags['retention-days'] ? Number(flags['retention-days']) : undefined,\n maxSizeMB: flags['max-size-mb'] ? Number(flags['max-size-mb']) : undefined,\n dbPath: typeof flags['db-path'] === 'string' ? flags['db-path'] : undefined,\n };\n if (existsSync(configPath) && !flags.force) {\n console.error(`Config already exists at ${configPath} (use --force to overwrite).`);\n process.exit(1);\n }\n if (nonInteractive) {\n if (!opts.password) { console.error('Non-interactive init requires --password or TINYLOGS_PASSWORD.'); process.exit(1); }\n await runInit(configPath, { ...opts, username: opts.username ?? 'admin' }, { prompt: async () => '', promptHidden: async () => '', log: (s) => console.log(s) });\n } else {\n const { io, done } = makeIo();\n try { await runInit(configPath, opts, io); } finally { done(); }\n }\n return;\n }\n\n if (cmd === 'start') {\n if (!existsSync(configPath)) { console.error(`No config at ${configPath}. Run: tinylogs init`); process.exit(1); }\n const daemon = flags.daemon === true || rest.includes('-d');\n if (daemon) {\n try {\n const { pid, port, logFile } = await spawnDaemon(configPath);\n console.log(`${green('\u2713')} tinylogs started in background`);\n console.log(` ${dim('pid')} ${pid}`);\n console.log(` ${dim('url')} http://127.0.0.1:${port}`);\n console.log(` ${dim('logs')} ${logFile}`);\n console.log(` ${dim('stop')} tinylogs stop`);\n } catch (e) {\n console.error(`${red('\u2717')} ${(e as Error).message}`);\n process.exit(1);\n }\n return;\n }\n const srv = await start(configPath);\n const shutdown = async () => { try { await srv.close(); } finally { process.exit(0); } };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n return; // server keeps the process alive\n }\n\n if (cmd === 'status') {\n if (!existsSync(configPath)) { console.error(`No config at ${configPath}. Run: tinylogs init`); process.exit(1); }\n const s = statusInfo(configPath);\n if (s.running) {\n console.log(`${green('\u25CF')} running`);\n console.log(` ${dim('pid')} ${s.pid}`);\n const shownHost = s.host === '0.0.0.0' || s.host === '::' ? '127.0.0.1' : s.host;\n console.log(` ${dim('url')} http://${shownHost}:${s.port}`);\n console.log(` ${dim('uptime')} ${formatUptime(s.uptimeMs)}`);\n } else {\n console.log(`${dim('\u25CB')} stopped${s.stale ? ' ' + yellow('(cleaned stale pidfile)') : ''}`);\n }\n console.log(` ${dim('version')} ${currentVersion()}`);\n const latest = await fetchLatest();\n if (latest && latest !== currentVersion()) {\n console.log(` ${yellow('\u26A0')} update available: ${latest} \u2014 run: tinylogs update`);\n }\n return;\n }\n\n if (cmd === 'stop') {\n if (!existsSync(configPath)) { console.error(`No config at ${configPath}.`); process.exit(1); }\n const r = await stopDaemon(configPath);\n if (r === 'not-running') console.log(`${dim('\u25CB')} not running`);\n else if (r === 'stopped') console.log(`${green('\u2713')} stopped`);\n else console.log(`${yellow('\u26A0')} force-killed (did not stop gracefully within 5s)`);\n return;\n }\n\n if (cmd === 'restart') {\n if (!existsSync(configPath)) { console.error(`No config at ${configPath}. Run: tinylogs init`); process.exit(1); }\n await stopDaemon(configPath);\n try {\n const { pid, port, logFile } = await spawnDaemon(configPath);\n console.log(`${green('\u2713')} tinylogs restarted`);\n console.log(` ${dim('pid')} ${pid}`);\n console.log(` ${dim('url')} http://127.0.0.1:${port}`);\n console.log(` ${dim('logs')} ${logFile}`);\n } catch (e) {\n console.error(`${red('\u2717')} ${(e as Error).message}`);\n process.exit(1);\n }\n return;\n }\n\n if (cmd === 'version' || cmd === '--version' || cmd === '-v') {\n console.log(`tinylogs ${currentVersion()}`);\n const latest = await fetchLatest();\n if (latest && latest !== currentVersion()) {\n console.log(`${yellow('\u26A0')} update available: ${latest} \u2014 run: tinylogs update`);\n } else if (latest) {\n console.log(dim('up to date'));\n }\n return;\n }\n\n if (cmd === 'update') {\n const cur = currentVersion();\n const latest = await fetchLatest();\n console.log(` ${dim('current')} ${cur}`);\n if (latest) console.log(` ${dim('latest')} ${latest}`);\n if (latest && latest === cur) { console.log(`${green('\u2713')} already up to date`); return; }\n if (detectInstall() === 'source') {\n console.log(`${yellow('\u26A0')} running from a source checkout \u2014 update with:`);\n console.log(` git pull && npm install && npm run build`);\n return;\n }\n console.log(dim('\u2192 npm i -g tinylogs@latest'));\n const code = runNpmUpdate();\n if (code !== 0) { console.error(`${red('\u2717')} npm update failed`); process.exit(code); }\n console.log(`${green('\u2713')} updated to ${latest ?? 'latest'}`);\n if (existsSync(configPath)) {\n const s = statusInfo(configPath);\n if (s.running) console.log(`${yellow('\u26A0')} daemon is running \u2014 run: tinylogs restart`);\n }\n return;\n }\n\n if (cmd === 'rotate-token') {\n if (!existsSync(configPath)) { console.error(`No config at ${configPath}.`); process.exit(1); }\n const cfg = loadConfig(configPath);\n const s = generateSecrets();\n cfg.ingestTokenHash = s.ingestTokenHash;\n saveConfig(configPath, cfg);\n console.log('New ingest token (shown once):');\n console.log(` ${s.token}`);\n return;\n }\n\n console.log(\n [\n 'tinylogs \u2014 usage:',\n ' tinylogs init [--yes --password ...]',\n ' tinylogs start [-d|--daemon] start (foreground, or background with -d)',\n ' tinylogs status is it running? pid, url, uptime, version',\n ' tinylogs stop stop the background server',\n ' tinylogs restart stop + start -d',\n ' tinylogs update update to the latest published version',\n ' tinylogs version show version (and check for updates)',\n ' tinylogs rotate-token issue a new ingest token',\n ].join('\\n'),\n );\n if (cmd && cmd !== 'help') process.exit(1);\n}\n\nmain().catch((err) => { console.error(err); process.exit(1); });\n", "import { createHash, randomBytes } from 'node:crypto';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport type { Config } from './types.js';\n\nexport const DEFAULTS = {\n port: 4700,\n host: '127.0.0.1',\n dbPath: 'tinylogs.db',\n retentionDays: 14,\n maxSizeMB: 500,\n bufferSize: 2000,\n} as const;\n\nexport function resolveConfigPath(flag?: string): string {\n if (flag) return flag;\n if (process.env.TINYLOGS_CONFIG) return process.env.TINYLOGS_CONFIG;\n return join(process.cwd(), 'tinylogs.config.json');\n}\n\nexport function hashToken(token: string): string {\n return createHash('sha256').update(token, 'utf8').digest('hex');\n}\n\nexport function generateSecrets() {\n const sessionSecret = randomBytes(32).toString('hex');\n const token = randomBytes(24).toString('base64url');\n return { sessionSecret, token, ingestTokenHash: hashToken(token) };\n}\n\nexport function saveConfig(path: string, cfg: Config): void {\n writeFileSync(path, JSON.stringify(cfg, null, 2) + '\\n', { mode: 0o600 });\n}\n\nexport function loadConfig(path: string): Config {\n const raw = readFileSync(path, 'utf8'); // throws if missing\n const cfg = JSON.parse(raw) as Config;\n for (const k of ['port', 'host', 'dbPath', 'sessionSecret', 'ingestTokenHash'] as const) {\n if (cfg[k] === undefined) throw new Error(`config missing field: ${k}`);\n }\n return cfg;\n}\n\nexport function resolveDbPath(configPath: string, cfg: Config): string {\n if (isAbsolute(cfg.dbPath)) return cfg.dbPath;\n return resolve(dirname(configPath), cfg.dbPath);\n}\n", "import { DatabaseSync } from 'node:sqlite';\nimport type { LogRecord } from '../types.js';\n\nconst MIGRATIONS: string[] = [\n `CREATE TABLE logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts INTEGER NOT NULL,\n service TEXT NOT NULL,\n message TEXT NOT NULL,\n labels TEXT NOT NULL\n );\n CREATE INDEX idx_logs_ts ON logs(ts);\n CREATE INDEX idx_logs_service ON logs(service);\n CREATE TABLE log_labels (\n log_id INTEGER NOT NULL REFERENCES logs(id) ON DELETE CASCADE,\n key TEXT NOT NULL,\n value TEXT NOT NULL\n );\n CREATE INDEX idx_labels_kv ON log_labels(key, value);\n CREATE INDEX idx_labels_logid ON log_labels(log_id);\n CREATE TABLE users (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n username TEXT UNIQUE NOT NULL,\n password_hash TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'admin',\n created_at INTEGER NOT NULL\n );`,\n];\n\n// node:sqlite has no db.transaction() helper \u2014 wrap a unit of work in an\n// explicit transaction so partial failures roll back.\nfunction tx<T>(db: DatabaseSync, fn: () => T): T {\n db.exec('BEGIN');\n try {\n const result = fn();\n db.exec('COMMIT');\n return result;\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\nexport function openDb(path: string): DatabaseSync {\n const db = new DatabaseSync(path);\n db.exec('PRAGMA journal_mode = WAL');\n db.exec('PRAGMA foreign_keys = ON');\n db.exec('CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY)');\n const current = (db.prepare('SELECT COALESCE(MAX(version),0) v FROM schema_migrations').get() as { v: number }).v;\n tx(db, () => {\n for (let i = current; i < MIGRATIONS.length; i++) {\n db.exec(MIGRATIONS[i]);\n db.prepare('INSERT INTO schema_migrations (version) VALUES (?)').run(i + 1);\n }\n });\n return db;\n}\n\nexport function insertLog(db: DatabaseSync, rec: LogRecord): number {\n return tx(db, () => {\n const info = db.prepare('INSERT INTO logs (ts, service, message, labels) VALUES (?,?,?,?)')\n .run(rec.ts, rec.service, rec.message, JSON.stringify(rec.labels ?? {}));\n const logId = Number(info.lastInsertRowid);\n const ins = db.prepare('INSERT INTO log_labels (log_id, key, value) VALUES (?,?,?)');\n for (const [k, v] of Object.entries(rec.labels ?? {})) ins.run(logId, k, String(v));\n return logId;\n });\n}\n\nexport function dbSizeBytes(db: DatabaseSync): number {\n const pageCount = Number((db.prepare('PRAGMA page_count').get() as { page_count: number }).page_count);\n const pageSize = Number((db.prepare('PRAGMA page_size').get() as { page_size: number }).page_size);\n return pageCount * pageSize;\n}\n", "import bcrypt from 'bcryptjs';\nimport { createHmac, timingSafeEqual } from 'node:crypto';\nimport type { DatabaseSync } from 'node:sqlite';\nimport { hashToken } from './config.js';\n\nexport function hashPassword(pw: string): string {\n return bcrypt.hashSync(pw, 10);\n}\n\nexport function createUser(db: DatabaseSync, username: string, pw: string, role = 'admin'): number {\n const info = db.prepare(\n 'INSERT INTO users (username, password_hash, role, created_at) VALUES (?,?,?,?)'\n ).run(username, hashPassword(pw), role, Date.now());\n return Number(info.lastInsertRowid);\n}\n\nexport function verifyUser(db: DatabaseSync, username: string, pw: string): boolean {\n const row = db.prepare('SELECT password_hash FROM users WHERE username=?').get(username) as any;\n if (!row) { bcrypt.compareSync(pw, '$2a$10$0000000000000000000000000000000000000000000000000000'); return false; }\n return bcrypt.compareSync(pw, row.password_hash);\n}\n\nfunction safeEqualHex(a: string, b: string): boolean {\n const ab = Buffer.from(a, 'hex'); const bb = Buffer.from(b, 'hex');\n if (ab.length !== bb.length || ab.length === 0) return false;\n return timingSafeEqual(ab, bb);\n}\n\nexport function verifyIngestToken(bearer: string | undefined, ingestTokenHash: string): boolean {\n if (!bearer || !bearer.startsWith('Bearer ')) return false;\n const token = bearer.slice('Bearer '.length);\n return safeEqualHex(hashToken(token), ingestTokenHash);\n}\n\nexport function signSession(username: string, secret: string): string {\n const mac = createHmac('sha256', secret).update(username).digest('hex');\n return `${username}.${mac}`;\n}\n\nexport function verifySession(cookieVal: string | undefined, secret: string): string | null {\n if (!cookieVal) return null;\n const dot = cookieVal.lastIndexOf('.');\n if (dot <= 0) return null;\n const username = cookieVal.slice(0, dot);\n const mac = cookieVal.slice(dot + 1);\n const expected = createHmac('sha256', secret).update(username).digest('hex');\n if (mac.length !== expected.length) return null;\n if (!timingSafeEqual(Buffer.from(mac), Buffer.from(expected))) return null;\n return username;\n}\n", "function computeSupportsColor(): boolean {\n if (process.env.NO_COLOR) return false;\n if (process.env.FORCE_COLOR) return true;\n return Boolean(process.stdout.isTTY);\n}\n\nconst useColor = computeSupportsColor();\n\nexport function supportsColor(): boolean {\n return useColor;\n}\n\nconst wrap =\n (open: number, close: number) =>\n (s: string): string =>\n useColor ? `\\x1b[${open}m${s}\\x1b[${close}m` : s;\n\nexport const bold = wrap(1, 22);\nexport const dim = wrap(2, 22);\nexport const red = wrap(31, 39);\nexport const green = wrap(32, 39);\nexport const yellow = wrap(33, 39);\nexport const cyan = wrap(36, 39);\n", "import { openDb } from './storage/db.js';\nimport { createUser } from './auth.js';\nimport { DEFAULTS, generateSecrets, saveConfig, resolveDbPath } from './config.js';\nimport type { Config } from './types.js';\nimport { bold, dim, green, cyan, yellow } from './style.js';\n\nexport interface InitOptions {\n port?: number; host?: string; username?: string; password?: string;\n retentionDays?: number; maxSizeMB?: number; dbPath?: string;\n}\n\nexport function buildInitConfig(opts: {\n port: number; host: string; retentionDays: number; maxSizeMB: number; dbPath: string;\n}): { config: Config; token: string } {\n const s = generateSecrets();\n const config: Config = {\n port: opts.port, host: opts.host, dbPath: opts.dbPath,\n retentionDays: opts.retentionDays, maxSizeMB: opts.maxSizeMB,\n bufferSize: DEFAULTS.bufferSize,\n sessionSecret: s.sessionSecret, ingestTokenHash: s.ingestTokenHash,\n };\n return { config, token: s.token };\n}\n\nexport async function runInit(\n configPath: string,\n opts: InitOptions,\n io: { prompt: (q: string) => Promise<string>; promptHidden: (q: string) => Promise<string>; log: (s: string) => void },\n): Promise<void> {\n const port = opts.port ?? (Number(await io.prompt(`Port ${dim(`[${DEFAULTS.port}]`)}: `)) || DEFAULTS.port);\n const host = opts.host ?? ((await io.prompt(`Host ${dim(`[${DEFAULTS.host}]`)}: `)) || DEFAULTS.host);\n const username = opts.username ?? ((await io.prompt(`Admin username ${dim('[admin]')}: `)) || 'admin');\n const password = opts.password ?? await io.promptHidden('Admin password: ');\n if (!password || password.length === 0) throw new Error('password must not be empty');\n const retentionDays = opts.retentionDays ?? (Number(await io.prompt(`Retention days ${dim(`[${DEFAULTS.retentionDays}]`)}: `)) || DEFAULTS.retentionDays);\n const maxSizeMB = opts.maxSizeMB ?? (Number(await io.prompt(`Max DB size MB ${dim(`[${DEFAULTS.maxSizeMB}]`)}: `)) || DEFAULTS.maxSizeMB);\n const dbPath = opts.dbPath ?? DEFAULTS.dbPath;\n\n const { config, token } = buildInitConfig({ port, host, retentionDays, maxSizeMB, dbPath });\n const db = openDb(resolveDbPath(configPath, config));\n createUser(db, username, password);\n db.close();\n saveConfig(configPath, config);\n\n io.log('');\n io.log(` ${green('\u2713')} tinylogs configured \u2192 ${dim(configPath)}`);\n io.log('');\n io.log(` ${yellow('\u26A0')} Ingest token \u2014 shown once, store it now:`);\n io.log('');\n io.log(` ${bold(cyan(token))}`);\n io.log('');\n io.log(` ${dim('Start with:')} ${bold('tinylogs start -d')}`);\n}\n", "import { createServer } from 'node:http';\nimport { openDb } from '../storage/db.js';\nimport { runRetention } from '../storage/retention.js';\nimport { RingBuffer } from '../buffer.js';\nimport { loadConfig, resolveConfigPath, resolveDbPath } from '../config.js';\nimport { createApp } from './app.js';\nimport { attachWebSocket } from './stream.js';\nimport type { WebSocket } from 'ws';\nimport type { LogRecord } from '../types.js';\n\nexport async function start(configPath = resolveConfigPath()): Promise<{ port: number; close: () => Promise<void> }> {\n const cfg = loadConfig(configPath);\n const db = openDb(resolveDbPath(configPath, cfg));\n const buffer = new RingBuffer(cfg.bufferSize);\n const wsClients = new Set<WebSocket>();\n const broadcast = (r: LogRecord) => {\n const msg = JSON.stringify({ type: 'log', data: r });\n for (const c of wsClients) { try { if (c.readyState === 1) c.send(msg); } catch {} }\n };\n\n const app = createApp({ db, buffer, cfg, broadcast });\n const server = createServer(app);\n attachWebSocket(server, { buffer, cfg, clients: wsClients });\n\n const retentionTimer = setInterval(\n () => runRetention(db, { retentionDays: cfg.retentionDays, maxSizeMB: cfg.maxSizeMB }),\n 60_000,\n );\n retentionTimer.unref();\n\n await new Promise<void>((resolve) => server.listen(cfg.port, cfg.host, resolve));\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : cfg.port;\n console.log(`[tinylogs] listening on http://${cfg.host}:${port}`);\n\n return {\n port,\n close: () => new Promise<void>((resolve) => {\n clearInterval(retentionTimer);\n for (const c of wsClients) { try { c.close(); } catch {} }\n server.close(() => { db.close(); resolve(); });\n server.closeIdleConnections?.();\n }),\n };\n}\n", "import type { DatabaseSync } from 'node:sqlite';\nimport { dbSizeBytes } from './db.js';\n\nexport function pruneByAge(db: DatabaseSync, retentionDays: number, now: number): number {\n const cutoff = now - retentionDays * 86400000;\n return Number(db.prepare('DELETE FROM logs WHERE ts < ?').run(cutoff).changes);\n}\n\nexport function pruneBySize(db: DatabaseSync, maxSizeMB: number, batch = 1000): number {\n const maxBytes = maxSizeMB * 1024 * 1024;\n let deleted = 0;\n // Delete oldest rows in batches until file is under the cap or nothing remains.\n while (dbSizeBytes(db) > maxBytes) {\n const changes = Number(db.prepare(\n 'DELETE FROM logs WHERE id IN (SELECT id FROM logs ORDER BY id ASC LIMIT ?)'\n ).run(batch).changes);\n if (changes === 0) break;\n deleted += changes;\n }\n return deleted;\n}\n\nexport function runRetention(\n db: DatabaseSync,\n opts: { retentionDays: number; maxSizeMB: number; now?: number }\n): void {\n try {\n const now = opts.now ?? Date.now();\n pruneByAge(db, opts.retentionDays, now);\n pruneBySize(db, opts.maxSizeMB);\n db.exec('VACUUM');\n } catch (err) {\n console.error('[tinylogs] retention failed:', (err as Error).message);\n }\n}\n", "import type { LogRecord } from './types.js';\n\nexport class RingBuffer {\n private items: LogRecord[] = [];\n constructor(private capacity: number) {}\n\n push(rec: LogRecord): void {\n this.items.push(rec);\n if (this.items.length > this.capacity) this.items.shift();\n }\n\n snapshot(): LogRecord[] {\n return this.items.slice();\n }\n\n get size(): number {\n return this.items.length;\n }\n}\n", "import express, { type Express } from 'express';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, LogRecord } from '../types.js';\nimport { RingBuffer } from '../buffer.js';\nimport { registerIngest } from './ingest.js';\nimport { registerQueryRoutes } from './queryRoutes.js';\nimport { registerAuthRoutes } from './authRoutes.js';\n\nexport interface AppDeps {\n db: DatabaseSync;\n buffer: RingBuffer;\n cfg: Config;\n broadcast: (r: LogRecord) => void;\n}\n\nexport function createApp(deps: AppDeps): Express {\n const app = express();\n app.set('trust proxy', true); // so req.ip reflects X-Forwarded-For behind a reverse proxy\n app.set('deps', deps);\n\n app.get('/api/health', (_req, res) => res.json({ ok: true }));\n\n // /ingest gets its own 5MB JSON parser (Task 9 mounts the handler after this).\n app.use('/ingest', express.json({ limit: '5mb' }));\n app.use('/api', express.json({ limit: '1mb' }));\n\n registerIngest(app);\n registerQueryRoutes(app);\n registerAuthRoutes(app);\n\n // Static UI (built to dist/ui). The running entry may be dist/cli.js (here = dist)\n // or dist/server/index.js (here = dist/server), so probe both depths. In dev (tsx\n // from src) neither resolves, so also fall back to <cwd>/dist/ui. Pick the first\n // candidate that actually contains the built bundle. Absent entirely \u2192 skip.\n const here = dirname(fileURLToPath(import.meta.url));\n const uiDir = [join(here, 'ui'), join(here, '..', 'ui'), join(process.cwd(), 'dist', 'ui')]\n .find((d) => existsSync(join(d, 'bundle.js')));\n if (uiDir) app.use(express.static(uiDir));\n\n return app;\n}\n", "import type { Express } from 'express';\nimport type { AppDeps } from './app.js';\nimport type { LogRecord } from '../types.js';\nimport { verifyIngestToken } from '../auth.js';\nimport { insertLog } from '../storage/db.js';\n\nconst MAX_MESSAGE = 16384;\nconst MAX_LABELS = 50;\nconst MAX_KV = 512;\nconst MAX_BATCH = 1000;\n\nexport function validateRecord(raw: any): { ok: true; rec: LogRecord } | { ok: false; error: string } {\n if (typeof raw !== 'object' || raw === null) return { ok: false, error: 'record must be an object' };\n if (typeof raw.service !== 'string' || raw.service.length === 0) return { ok: false, error: 'service required' };\n if (typeof raw.message !== 'string' || raw.message.length === 0) return { ok: false, error: 'message required' };\n if (raw.message.length > MAX_MESSAGE) return { ok: false, error: 'message too long' };\n if (raw.service.length > MAX_KV) return { ok: false, error: 'service too long' };\n const labels: Record<string, string> = {};\n if (raw.labels !== undefined) {\n if (typeof raw.labels !== 'object' || raw.labels === null || Array.isArray(raw.labels))\n return { ok: false, error: 'labels must be an object' };\n const keys = Object.keys(raw.labels);\n if (keys.length > MAX_LABELS) return { ok: false, error: 'too many labels' };\n for (const k of keys) {\n const v = raw.labels[k];\n if (typeof v !== 'string') return { ok: false, error: `label ${k} must be a string` };\n if (k.length > MAX_KV || v.length > MAX_KV) return { ok: false, error: `label ${k} too long` };\n labels[k] = v;\n }\n }\n const ts = typeof raw.ts === 'number' && Number.isFinite(raw.ts) ? raw.ts : Date.now();\n return { ok: true, rec: { ts, service: raw.service, message: raw.message, labels } };\n}\n\nexport function registerIngest(app: Express): void {\n app.post('/ingest', (req, res) => {\n const deps = app.get('deps') as AppDeps;\n if (!verifyIngestToken(req.header('authorization'), deps.cfg.ingestTokenHash)) {\n return res.status(401).json({ error: 'invalid token' });\n }\n const body = req.body;\n const records = Array.isArray(body) ? body : [body];\n if (records.length > MAX_BATCH) return res.status(400).json({ error: 'batch too large' });\n const validated: LogRecord[] = [];\n for (const raw of records) {\n const v = validateRecord(raw);\n if (!v.ok) return res.status(400).json({ error: v.error });\n validated.push(v.rec);\n }\n try {\n for (const rec of validated) {\n const id = insertLog(deps.db, rec);\n const stored = { ...rec, id };\n deps.buffer.push(stored);\n deps.broadcast(stored);\n }\n return res.json({ accepted: validated.length });\n } catch (err) {\n console.error('[tinylogs] ingest error:', (err as Error).message);\n return res.status(500).json({ error: 'internal error' });\n }\n });\n}\n", "import type { Express, RequestHandler } from 'express';\nimport { parse as parseCookie } from 'cookie';\nimport type { AppDeps } from './app.js';\nimport type { QueryParams } from '../storage/query.js';\nimport { queryLogs, queryLabels } from '../storage/query.js';\nimport { verifySession } from '../auth.js';\n\nexport function requireSession(app: Express): RequestHandler {\n return (req, res, next) => {\n const deps = app.get('deps') as AppDeps;\n const cookies = parseCookie(req.header('cookie') ?? '');\n const user = verifySession(cookies['tl_session'], deps.cfg.sessionSecret);\n if (!user) return res.status(401).json({ error: 'unauthorized' });\n (req as any).user = user;\n next();\n };\n}\n\nexport function parseQuery(query: Record<string, any>): QueryParams {\n const p: QueryParams = {};\n if (typeof query.service === 'string') p.service = query.service;\n if (typeof query.q === 'string') p.q = query.q;\n if (query.from !== undefined) p.from = Number(query.from);\n if (query.to !== undefined) p.to = Number(query.to);\n if (query.limit !== undefined) p.limit = Number(query.limit);\n if (query.before !== undefined) p.before = Number(query.before);\n const rawLabels = query.label === undefined ? [] : Array.isArray(query.label) ? query.label : [query.label];\n p.labels = rawLabels\n .map((s: string) => { const i = s.indexOf('='); return i < 0 ? null : { key: s.slice(0, i), value: s.slice(i + 1) }; })\n .filter(Boolean) as Array<{ key: string; value: string }>;\n return p;\n}\n\nexport function registerQueryRoutes(app: Express): void {\n const guard = requireSession(app);\n app.get('/api/logs', guard, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const logs = queryLogs(deps.db, parseQuery(req.query as any));\n res.json({ logs });\n });\n app.get('/api/labels', guard, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const service = typeof req.query.service === 'string' ? req.query.service : undefined;\n res.json(queryLabels(deps.db, { service }));\n });\n}\n", "import type { DatabaseSync } from 'node:sqlite';\nimport type { LogRecord } from '../types.js';\n\nexport interface QueryParams {\n service?: string;\n labels?: Array<{ key: string; value: string }>;\n q?: string;\n from?: number; to?: number;\n limit?: number;\n before?: number;\n}\n\nfunction rowToRecord(row: any): LogRecord {\n return { id: row.id, ts: row.ts, service: row.service, message: row.message, labels: JSON.parse(row.labels) };\n}\n\nexport function queryLogs(db: DatabaseSync, params: QueryParams): LogRecord[] {\n const where: string[] = [];\n const args: any[] = [];\n if (params.service) { where.push('logs.service = ?'); args.push(params.service); }\n if (params.q) { where.push('logs.message LIKE ? COLLATE NOCASE'); args.push(`%${params.q}%`); }\n if (params.from !== undefined) { where.push('logs.ts >= ?'); args.push(params.from); }\n if (params.to !== undefined) { where.push('logs.ts < ?'); args.push(params.to); }\n if (params.before !== undefined) { where.push('logs.id < ?'); args.push(params.before); }\n for (const l of params.labels ?? []) {\n where.push('logs.id IN (SELECT log_id FROM log_labels WHERE key = ? AND value = ?)');\n args.push(l.key, l.value);\n }\n const limit = Math.min(Math.max(params.limit ?? 200, 1), 1000);\n const sql = `SELECT id, ts, service, message, labels FROM logs\n ${where.length ? 'WHERE ' + where.join(' AND ') : ''}\n ORDER BY logs.id DESC LIMIT ?`;\n return db.prepare(sql).all(...args, limit).map(rowToRecord);\n}\n\nexport interface LabelCount { key: string; value: string; count: number; }\n\nfunction rowToLabel(row: any): LabelCount {\n return { key: row.key, value: row.value, count: Number(row.count) };\n}\n\nexport function queryLabels(db: DatabaseSync, opts: { service?: string } = {}) {\n const services = db.prepare(\n 'SELECT service, COUNT(*) count FROM logs GROUP BY service ORDER BY count DESC'\n ).all().map((row: any) => ({ service: row.service as string, count: Number(row.count) }));\n\n const labels = opts.service\n ? db.prepare(\n `SELECT ll.key, ll.value, COUNT(*) count FROM log_labels ll\n JOIN logs ON logs.id = ll.log_id WHERE logs.service = ?\n GROUP BY ll.key, ll.value ORDER BY ll.key, count DESC`\n ).all(opts.service).map(rowToLabel)\n : db.prepare(\n `SELECT key, value, COUNT(*) count FROM log_labels\n GROUP BY key, value ORDER BY key, count DESC`\n ).all().map(rowToLabel);\n\n return { services, labels };\n}\n", "import type { Express, RequestHandler } from 'express';\nimport { serialize as serializeCookie } from 'cookie';\nimport type { AppDeps } from './app.js';\nimport { verifyUser, signSession } from '../auth.js';\n\nexport function makeLoginLimiter(maxAttempts = 10, windowMs = 5 * 60_000): RequestHandler {\n const hits = new Map<string, { count: number; reset: number }>();\n return (req, res, next) => {\n const now = Date.now();\n const ip = req.ip ?? 'unknown';\n const rec = hits.get(ip);\n if (!rec || now > rec.reset) { hits.set(ip, { count: 1, reset: now + windowMs }); return next(); }\n rec.count += 1;\n if (rec.count > maxAttempts) return res.status(429).json({ error: 'too many attempts' });\n next();\n };\n}\n\nexport function registerAuthRoutes(app: Express): void {\n const limiter = makeLoginLimiter();\n app.post('/api/login', limiter, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const { username, password } = req.body ?? {};\n if (typeof username !== 'string' || typeof password !== 'string')\n return res.status(400).json({ error: 'username and password required' });\n if (!verifyUser(deps.db, username, password))\n return res.status(401).json({ error: 'invalid credentials' });\n const cookie = serializeCookie('tl_session', signSession(username, deps.cfg.sessionSecret), {\n httpOnly: true, sameSite: 'strict', path: '/', maxAge: 60 * 60 * 24 * 30,\n });\n res.setHeader('Set-Cookie', cookie);\n res.json({ ok: true, username });\n });\n\n app.post('/api/logout', (_req, res) => {\n res.setHeader('Set-Cookie', serializeCookie('tl_session', '', { httpOnly: true, path: '/', maxAge: 0 }));\n res.json({ ok: true });\n });\n}\n", "import type { Server } from 'node:http';\nimport { parse as parseCookie } from 'cookie';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport type { RingBuffer } from '../buffer.js';\nimport type { Config } from '../types.js';\nimport { verifySession } from '../auth.js';\n\nexport function attachWebSocket(\n server: Server,\n deps: { buffer: RingBuffer; cfg: Config; clients: Set<WebSocket> },\n): WebSocketServer {\n const wss = new WebSocketServer({ server, path: '/ws' });\n\n wss.on('connection', (ws, req) => {\n const cookies = parseCookie(req.headers.cookie ?? '');\n const user = verifySession(cookies['tl_session'], deps.cfg.sessionSecret);\n if (!user) { ws.close(4401, 'unauthorized'); return; }\n\n (ws as any).isAlive = true;\n ws.on('pong', () => { (ws as any).isAlive = true; });\n\n ws.send(JSON.stringify({ type: 'buffer', data: deps.buffer.snapshot() }));\n deps.clients.add(ws);\n ws.on('close', () => deps.clients.delete(ws));\n ws.on('error', () => deps.clients.delete(ws));\n });\n\n const interval = setInterval(() => {\n for (const ws of wss.clients) {\n if ((ws as any).isAlive === false) { ws.terminate(); continue; }\n (ws as any).isAlive = false;\n try { ws.ping(); } catch {}\n }\n }, 30_000);\n interval.unref();\n wss.on('close', () => clearInterval(interval));\n\n return wss;\n}\n", "import { spawn } from 'node:child_process';\nimport {\n openSync,\n readFileSync,\n writeFileSync,\n existsSync,\n unlinkSync,\n statSync,\n} from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { loadConfig } from './config.js';\n\nexport function pidPath(configPath: string): string {\n return join(dirname(resolve(configPath)), 'tinylogs.pid');\n}\n\nexport function logPath(configPath: string): string {\n return join(dirname(resolve(configPath)), 'tinylogs.log');\n}\n\nexport function readPid(configPath: string): number | null {\n const p = pidPath(configPath);\n if (!existsSync(p)) return null;\n const n = Number(readFileSync(p, 'utf8').trim());\n return Number.isInteger(n) && n > 0 ? n : null;\n}\n\nexport function isAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport interface StatusInfo {\n running: boolean;\n stale: boolean;\n pid: number | null;\n port: number;\n host: string;\n uptimeMs: number | null;\n}\n\nexport function statusInfo(configPath: string): StatusInfo {\n const cfg = loadConfig(configPath);\n const pid = readPid(configPath);\n if (pid && isAlive(pid)) {\n let uptimeMs: number | null = null;\n try {\n uptimeMs = Date.now() - statSync(pidPath(configPath)).mtimeMs;\n } catch {}\n return { running: true, stale: false, pid, port: cfg.port, host: cfg.host, uptimeMs };\n }\n if (pid) {\n // stale pidfile \u2014 process is gone; clean it up\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n }\n return {\n running: false,\n stale: pid !== null,\n pid: null,\n port: cfg.port,\n host: cfg.host,\n uptimeMs: null,\n };\n}\n\nconst delay = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\nexport async function spawnDaemon(\n configPath: string,\n): Promise<{ pid: number; port: number; logFile: string }> {\n const existing = readPid(configPath);\n if (existing && isAlive(existing)) {\n throw new Error(`already running (pid ${existing})`);\n }\n if (existing) {\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n }\n\n const cfg = loadConfig(configPath);\n const logFile = logPath(configPath);\n const fd = openSync(logFile, 'a');\n // In the esbuild bundle every module's import.meta.url is dist/cli.js \u2014 the CLI entry.\n const cliEntry = fileURLToPath(import.meta.url);\n const child = spawn(\n process.execPath,\n [cliEntry, 'start', '--config', resolve(configPath)],\n { detached: true, stdio: ['ignore', fd, fd] },\n );\n child.unref();\n const pid = child.pid;\n if (!pid) throw new Error('failed to spawn daemon process');\n writeFileSync(pidPath(configPath), String(pid) + '\\n');\n\n // Confirm it did not crash on startup (e.g. port in use).\n await delay(400);\n if (!isAlive(pid)) {\n let tail = '';\n try {\n tail = readFileSync(logFile, 'utf8').split('\\n').slice(-8).join('\\n');\n } catch {}\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n throw new Error(`daemon exited immediately. Recent log:\\n${tail}`);\n }\n return { pid, port: cfg.port, logFile };\n}\n\nexport async function stopDaemon(\n configPath: string,\n): Promise<'stopped' | 'killed' | 'not-running'> {\n const pid = readPid(configPath);\n if (!pid || !isAlive(pid)) {\n if (pid) {\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n }\n return 'not-running';\n }\n try {\n process.kill(pid, 'SIGTERM');\n } catch {}\n for (let i = 0; i < 50; i++) {\n if (!isAlive(pid)) {\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n return 'stopped';\n }\n await delay(100);\n }\n try {\n process.kill(pid, 'SIGKILL');\n } catch {}\n try {\n unlinkSync(pidPath(configPath));\n } catch {}\n return 'killed';\n}\n", "import { spawnSync } from 'node:child_process';\nimport { readFileSync, existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// import.meta.url \u2192 dist/cli.js (bundle) or src/update.ts (dev); one dir up is the package root.\nfunction pkgRoot(): string {\n return dirname(dirname(fileURLToPath(import.meta.url)));\n}\n\nexport function currentVersion(): string {\n try {\n const pkg = JSON.parse(readFileSync(join(pkgRoot(), 'package.json'), 'utf8')) as {\n version?: string;\n };\n return pkg.version ?? '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\nexport async function fetchLatest(timeoutMs = 2500): Promise<string | null> {\n const ctrl = new AbortController();\n const t = setTimeout(() => ctrl.abort(), timeoutMs);\n try {\n const res = await fetch('https://registry.npmjs.org/tinylogs', { signal: ctrl.signal });\n if (!res.ok) return null;\n const data = (await res.json()) as { 'dist-tags'?: { latest?: string } };\n return data['dist-tags']?.latest ?? null;\n } catch {\n return null;\n } finally {\n clearTimeout(t);\n }\n}\n\nexport function detectInstall(): 'source' | 'global' {\n const root = pkgRoot();\n if (existsSync(join(root, '.git')) && existsSync(join(root, 'src'))) return 'source';\n return 'global';\n}\n\nexport function runNpmUpdate(): number {\n const r = spawnSync('npm', ['i', '-g', 'tinylogs@latest'], { stdio: 'inherit' });\n return r.status ?? 1;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["existsSync", "dirname", "join", "dirname", "join", "parseCookie", "parseCookie", "resolve", "readFileSync", "writeFileSync", "existsSync", "dirname", "join", "resolve", "fileURLToPath", "join", "dirname", "resolve", "existsSync", "readFileSync", "fileURLToPath", "writeFileSync", "readFileSync", "existsSync", "dirname", "join", "fileURLToPath", "existsSync"]
|
|
7
7
|
}
|
package/dist/server/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { createServer } from "node:http";
|
|
5
5
|
|
|
6
6
|
// src/storage/db.ts
|
|
7
|
-
import
|
|
7
|
+
import { DatabaseSync } from "node:sqlite";
|
|
8
8
|
var MIGRATIONS = [
|
|
9
9
|
`CREATE TABLE logs (
|
|
10
10
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -30,49 +30,58 @@ var MIGRATIONS = [
|
|
|
30
30
|
created_at INTEGER NOT NULL
|
|
31
31
|
);`
|
|
32
32
|
];
|
|
33
|
+
function tx(db, fn) {
|
|
34
|
+
db.exec("BEGIN");
|
|
35
|
+
try {
|
|
36
|
+
const result = fn();
|
|
37
|
+
db.exec("COMMIT");
|
|
38
|
+
return result;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
db.exec("ROLLBACK");
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
33
44
|
function openDb(path) {
|
|
34
|
-
const db = new
|
|
35
|
-
db.
|
|
36
|
-
db.
|
|
45
|
+
const db = new DatabaseSync(path);
|
|
46
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
47
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
37
48
|
db.exec("CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY)");
|
|
38
49
|
const current = db.prepare("SELECT COALESCE(MAX(version),0) v FROM schema_migrations").get().v;
|
|
39
|
-
|
|
40
|
-
for (let i =
|
|
50
|
+
tx(db, () => {
|
|
51
|
+
for (let i = current; i < MIGRATIONS.length; i++) {
|
|
41
52
|
db.exec(MIGRATIONS[i]);
|
|
42
53
|
db.prepare("INSERT INTO schema_migrations (version) VALUES (?)").run(i + 1);
|
|
43
54
|
}
|
|
44
55
|
});
|
|
45
|
-
apply(current);
|
|
46
56
|
return db;
|
|
47
57
|
}
|
|
48
58
|
function insertLog(db, rec) {
|
|
49
|
-
|
|
50
|
-
const info = db.prepare("INSERT INTO logs (ts, service, message, labels) VALUES (?,?,?,?)").run(
|
|
59
|
+
return tx(db, () => {
|
|
60
|
+
const info = db.prepare("INSERT INTO logs (ts, service, message, labels) VALUES (?,?,?,?)").run(rec.ts, rec.service, rec.message, JSON.stringify(rec.labels ?? {}));
|
|
51
61
|
const logId = Number(info.lastInsertRowid);
|
|
52
62
|
const ins = db.prepare("INSERT INTO log_labels (log_id, key, value) VALUES (?,?,?)");
|
|
53
|
-
for (const [k, v] of Object.entries(
|
|
63
|
+
for (const [k, v] of Object.entries(rec.labels ?? {})) ins.run(logId, k, String(v));
|
|
54
64
|
return logId;
|
|
55
65
|
});
|
|
56
|
-
return txn(rec);
|
|
57
66
|
}
|
|
58
67
|
function dbSizeBytes(db) {
|
|
59
|
-
const pageCount = db.
|
|
60
|
-
const pageSize = db.
|
|
68
|
+
const pageCount = Number(db.prepare("PRAGMA page_count").get().page_count);
|
|
69
|
+
const pageSize = Number(db.prepare("PRAGMA page_size").get().page_size);
|
|
61
70
|
return pageCount * pageSize;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
// src/storage/retention.ts
|
|
65
74
|
function pruneByAge(db, retentionDays, now) {
|
|
66
75
|
const cutoff = now - retentionDays * 864e5;
|
|
67
|
-
return db.prepare("DELETE FROM logs WHERE ts < ?").run(cutoff).changes;
|
|
76
|
+
return Number(db.prepare("DELETE FROM logs WHERE ts < ?").run(cutoff).changes);
|
|
68
77
|
}
|
|
69
78
|
function pruneBySize(db, maxSizeMB, batch = 1e3) {
|
|
70
79
|
const maxBytes = maxSizeMB * 1024 * 1024;
|
|
71
80
|
let deleted = 0;
|
|
72
81
|
while (dbSizeBytes(db) > maxBytes) {
|
|
73
|
-
const changes = db.prepare(
|
|
82
|
+
const changes = Number(db.prepare(
|
|
74
83
|
"DELETE FROM logs WHERE id IN (SELECT id FROM logs ORDER BY id ASC LIMIT ?)"
|
|
75
|
-
).run(batch).changes;
|
|
84
|
+
).run(batch).changes);
|
|
76
85
|
if (changes === 0) break;
|
|
77
86
|
deleted += changes;
|
|
78
87
|
}
|
|
@@ -273,23 +282,21 @@ function queryLogs(db, params) {
|
|
|
273
282
|
ORDER BY logs.id DESC LIMIT ?`;
|
|
274
283
|
return db.prepare(sql).all(...args, limit).map(rowToRecord);
|
|
275
284
|
}
|
|
285
|
+
function rowToLabel(row) {
|
|
286
|
+
return { key: row.key, value: row.value, count: Number(row.count) };
|
|
287
|
+
}
|
|
276
288
|
function queryLabels(db, opts = {}) {
|
|
277
289
|
const services = db.prepare(
|
|
278
290
|
"SELECT service, COUNT(*) count FROM logs GROUP BY service ORDER BY count DESC"
|
|
279
|
-
).all();
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
labels = db.prepare(
|
|
289
|
-
`SELECT key, value, COUNT(*) count FROM log_labels
|
|
290
|
-
GROUP BY key, value ORDER BY key, count DESC`
|
|
291
|
-
).all();
|
|
292
|
-
}
|
|
291
|
+
).all().map((row) => ({ service: row.service, count: Number(row.count) }));
|
|
292
|
+
const labels = opts.service ? db.prepare(
|
|
293
|
+
`SELECT ll.key, ll.value, COUNT(*) count FROM log_labels ll
|
|
294
|
+
JOIN logs ON logs.id = ll.log_id WHERE logs.service = ?
|
|
295
|
+
GROUP BY ll.key, ll.value ORDER BY ll.key, count DESC`
|
|
296
|
+
).all(opts.service).map(rowToLabel) : db.prepare(
|
|
297
|
+
`SELECT key, value, COUNT(*) count FROM log_labels
|
|
298
|
+
GROUP BY key, value ORDER BY key, count DESC`
|
|
299
|
+
).all().map(rowToLabel);
|
|
293
300
|
return { services, labels };
|
|
294
301
|
}
|
|
295
302
|
|
package/dist/server/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/server/index.ts", "../../src/storage/db.ts", "../../src/storage/retention.ts", "../../src/buffer.ts", "../../src/config.ts", "../../src/server/app.ts", "../../src/auth.ts", "../../src/server/ingest.ts", "../../src/server/queryRoutes.ts", "../../src/storage/query.ts", "../../src/server/authRoutes.ts", "../../src/server/stream.ts"],
|
|
4
|
-
"sourcesContent": ["import { createServer } from 'node:http';\nimport { openDb } from '../storage/db.js';\nimport { runRetention } from '../storage/retention.js';\nimport { RingBuffer } from '../buffer.js';\nimport { loadConfig, resolveConfigPath, resolveDbPath } from '../config.js';\nimport { createApp } from './app.js';\nimport { attachWebSocket } from './stream.js';\nimport type { WebSocket } from 'ws';\nimport type { LogRecord } from '../types.js';\n\nexport async function start(configPath = resolveConfigPath()): Promise<{ port: number; close: () => Promise<void> }> {\n const cfg = loadConfig(configPath);\n const db = openDb(resolveDbPath(configPath, cfg));\n const buffer = new RingBuffer(cfg.bufferSize);\n const wsClients = new Set<WebSocket>();\n const broadcast = (r: LogRecord) => {\n const msg = JSON.stringify({ type: 'log', data: r });\n for (const c of wsClients) { try { if (c.readyState === 1) c.send(msg); } catch {} }\n };\n\n const app = createApp({ db, buffer, cfg, broadcast });\n const server = createServer(app);\n attachWebSocket(server, { buffer, cfg, clients: wsClients });\n\n const retentionTimer = setInterval(\n () => runRetention(db, { retentionDays: cfg.retentionDays, maxSizeMB: cfg.maxSizeMB }),\n 60_000,\n );\n retentionTimer.unref();\n\n await new Promise<void>((resolve) => server.listen(cfg.port, cfg.host, resolve));\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : cfg.port;\n console.log(`[tinylogs] listening on http://${cfg.host}:${port}`);\n\n return {\n port,\n close: () => new Promise<void>((resolve) => {\n clearInterval(retentionTimer);\n for (const c of wsClients) { try { c.close(); } catch {} }\n server.close(() => { db.close(); resolve(); });\n server.closeIdleConnections?.();\n }),\n };\n}\n", "import Database from 'better-sqlite3';\nimport type { LogRecord } from '../types.js';\n\nconst MIGRATIONS: string[] = [\n `CREATE TABLE logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts INTEGER NOT NULL,\n service TEXT NOT NULL,\n message TEXT NOT NULL,\n labels TEXT NOT NULL\n );\n CREATE INDEX idx_logs_ts ON logs(ts);\n CREATE INDEX idx_logs_service ON logs(service);\n CREATE TABLE log_labels (\n log_id INTEGER NOT NULL REFERENCES logs(id) ON DELETE CASCADE,\n key TEXT NOT NULL,\n value TEXT NOT NULL\n );\n CREATE INDEX idx_labels_kv ON log_labels(key, value);\n CREATE INDEX idx_labels_logid ON log_labels(log_id);\n CREATE TABLE users (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n username TEXT UNIQUE NOT NULL,\n password_hash TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'admin',\n created_at INTEGER NOT NULL\n );`,\n];\n\nexport function openDb(path: string): Database.Database {\n const db = new Database(path);\n db.pragma('journal_mode = WAL');\n db.pragma('foreign_keys = ON');\n db.exec('CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY)');\n const current = (db.prepare('SELECT COALESCE(MAX(version),0) v FROM schema_migrations').get() as any).v as number;\n const apply = db.transaction((from: number) => {\n for (let i = from; i < MIGRATIONS.length; i++) {\n db.exec(MIGRATIONS[i]);\n db.prepare('INSERT INTO schema_migrations (version) VALUES (?)').run(i + 1);\n }\n });\n apply(current);\n return db;\n}\n\nexport function insertLog(db: Database.Database, rec: LogRecord): number {\n const txn = db.transaction((r: LogRecord) => {\n const info = db.prepare('INSERT INTO logs (ts, service, message, labels) VALUES (?,?,?,?)')\n .run(r.ts, r.service, r.message, JSON.stringify(r.labels ?? {}));\n const logId = Number(info.lastInsertRowid);\n const ins = db.prepare('INSERT INTO log_labels (log_id, key, value) VALUES (?,?,?)');\n for (const [k, v] of Object.entries(r.labels ?? {})) ins.run(logId, k, String(v));\n return logId;\n });\n return txn(rec);\n}\n\nexport function dbSizeBytes(db: Database.Database): number {\n const pageCount = (db.pragma('page_count', { simple: true }) as number);\n const pageSize = (db.pragma('page_size', { simple: true }) as number);\n return pageCount * pageSize;\n}\n", "import type Database from 'better-sqlite3';\nimport { dbSizeBytes } from './db.js';\n\nexport function pruneByAge(db: Database.Database, retentionDays: number, now: number): number {\n const cutoff = now - retentionDays * 86400000;\n return db.prepare('DELETE FROM logs WHERE ts < ?').run(cutoff).changes;\n}\n\nexport function pruneBySize(db: Database.Database, maxSizeMB: number, batch = 1000): number {\n const maxBytes = maxSizeMB * 1024 * 1024;\n let deleted = 0;\n // Delete oldest rows in batches until file is under the cap or nothing remains.\n while (dbSizeBytes(db) > maxBytes) {\n const changes = db.prepare(\n 'DELETE FROM logs WHERE id IN (SELECT id FROM logs ORDER BY id ASC LIMIT ?)'\n ).run(batch).changes;\n if (changes === 0) break;\n deleted += changes;\n }\n return deleted;\n}\n\nexport function runRetention(\n db: Database.Database,\n opts: { retentionDays: number; maxSizeMB: number; now?: number }\n): void {\n try {\n const now = opts.now ?? Date.now();\n pruneByAge(db, opts.retentionDays, now);\n pruneBySize(db, opts.maxSizeMB);\n db.exec('VACUUM');\n } catch (err) {\n console.error('[tinylogs] retention failed:', (err as Error).message);\n }\n}\n", "import type { LogRecord } from './types.js';\n\nexport class RingBuffer {\n private items: LogRecord[] = [];\n constructor(private capacity: number) {}\n\n push(rec: LogRecord): void {\n this.items.push(rec);\n if (this.items.length > this.capacity) this.items.shift();\n }\n\n snapshot(): LogRecord[] {\n return this.items.slice();\n }\n\n get size(): number {\n return this.items.length;\n }\n}\n", "import { createHash, randomBytes } from 'node:crypto';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport type { Config } from './types.js';\n\nexport const DEFAULTS = {\n port: 4700,\n host: '127.0.0.1',\n dbPath: 'tinylogs.db',\n retentionDays: 14,\n maxSizeMB: 500,\n bufferSize: 2000,\n} as const;\n\nexport function resolveConfigPath(flag?: string): string {\n if (flag) return flag;\n if (process.env.TINYLOGS_CONFIG) return process.env.TINYLOGS_CONFIG;\n return join(process.cwd(), 'tinylogs.config.json');\n}\n\nexport function hashToken(token: string): string {\n return createHash('sha256').update(token, 'utf8').digest('hex');\n}\n\nexport function generateSecrets() {\n const sessionSecret = randomBytes(32).toString('hex');\n const token = randomBytes(24).toString('base64url');\n return { sessionSecret, token, ingestTokenHash: hashToken(token) };\n}\n\nexport function saveConfig(path: string, cfg: Config): void {\n writeFileSync(path, JSON.stringify(cfg, null, 2) + '\\n', { mode: 0o600 });\n}\n\nexport function loadConfig(path: string): Config {\n const raw = readFileSync(path, 'utf8'); // throws if missing\n const cfg = JSON.parse(raw) as Config;\n for (const k of ['port', 'host', 'dbPath', 'sessionSecret', 'ingestTokenHash'] as const) {\n if (cfg[k] === undefined) throw new Error(`config missing field: ${k}`);\n }\n return cfg;\n}\n\nexport function resolveDbPath(configPath: string, cfg: Config): string {\n if (isAbsolute(cfg.dbPath)) return cfg.dbPath;\n return resolve(dirname(configPath), cfg.dbPath);\n}\n", "import express, { type Express } from 'express';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type Database from 'better-sqlite3';\nimport type { Config, LogRecord } from '../types.js';\nimport { RingBuffer } from '../buffer.js';\nimport { registerIngest } from './ingest.js';\nimport { registerQueryRoutes } from './queryRoutes.js';\nimport { registerAuthRoutes } from './authRoutes.js';\n\nexport interface AppDeps {\n db: Database.Database;\n buffer: RingBuffer;\n cfg: Config;\n broadcast: (r: LogRecord) => void;\n}\n\nexport function createApp(deps: AppDeps): Express {\n const app = express();\n app.set('trust proxy', true); // so req.ip reflects X-Forwarded-For behind a reverse proxy\n app.set('deps', deps);\n\n app.get('/api/health', (_req, res) => res.json({ ok: true }));\n\n // /ingest gets its own 5MB JSON parser (Task 9 mounts the handler after this).\n app.use('/ingest', express.json({ limit: '5mb' }));\n app.use('/api', express.json({ limit: '1mb' }));\n\n registerIngest(app);\n registerQueryRoutes(app);\n registerAuthRoutes(app);\n\n // Static UI (built to dist/ui). The running entry may be dist/cli.js (here = dist)\n // or dist/server/index.js (here = dist/server), so probe both depths. In dev (tsx\n // from src) neither resolves, so also fall back to <cwd>/dist/ui. Pick the first\n // candidate that actually contains the built bundle. Absent entirely \u2192 skip.\n const here = dirname(fileURLToPath(import.meta.url));\n const uiDir = [join(here, 'ui'), join(here, '..', 'ui'), join(process.cwd(), 'dist', 'ui')]\n .find((d) => existsSync(join(d, 'bundle.js')));\n if (uiDir) app.use(express.static(uiDir));\n\n return app;\n}\n", "import bcrypt from 'bcryptjs';\nimport { createHmac, timingSafeEqual } from 'node:crypto';\nimport type Database from 'better-sqlite3';\nimport { hashToken } from './config.js';\n\nexport function hashPassword(pw: string): string {\n return bcrypt.hashSync(pw, 10);\n}\n\nexport function createUser(db: Database.Database, username: string, pw: string, role = 'admin'): number {\n const info = db.prepare(\n 'INSERT INTO users (username, password_hash, role, created_at) VALUES (?,?,?,?)'\n ).run(username, hashPassword(pw), role, Date.now());\n return Number(info.lastInsertRowid);\n}\n\nexport function verifyUser(db: Database.Database, username: string, pw: string): boolean {\n const row = db.prepare('SELECT password_hash FROM users WHERE username=?').get(username) as any;\n if (!row) { bcrypt.compareSync(pw, '$2a$10$0000000000000000000000000000000000000000000000000000'); return false; }\n return bcrypt.compareSync(pw, row.password_hash);\n}\n\nfunction safeEqualHex(a: string, b: string): boolean {\n const ab = Buffer.from(a, 'hex'); const bb = Buffer.from(b, 'hex');\n if (ab.length !== bb.length || ab.length === 0) return false;\n return timingSafeEqual(ab, bb);\n}\n\nexport function verifyIngestToken(bearer: string | undefined, ingestTokenHash: string): boolean {\n if (!bearer || !bearer.startsWith('Bearer ')) return false;\n const token = bearer.slice('Bearer '.length);\n return safeEqualHex(hashToken(token), ingestTokenHash);\n}\n\nexport function signSession(username: string, secret: string): string {\n const mac = createHmac('sha256', secret).update(username).digest('hex');\n return `${username}.${mac}`;\n}\n\nexport function verifySession(cookieVal: string | undefined, secret: string): string | null {\n if (!cookieVal) return null;\n const dot = cookieVal.lastIndexOf('.');\n if (dot <= 0) return null;\n const username = cookieVal.slice(0, dot);\n const mac = cookieVal.slice(dot + 1);\n const expected = createHmac('sha256', secret).update(username).digest('hex');\n if (mac.length !== expected.length) return null;\n if (!timingSafeEqual(Buffer.from(mac), Buffer.from(expected))) return null;\n return username;\n}\n", "import type { Express } from 'express';\nimport type { AppDeps } from './app.js';\nimport type { LogRecord } from '../types.js';\nimport { verifyIngestToken } from '../auth.js';\nimport { insertLog } from '../storage/db.js';\n\nconst MAX_MESSAGE = 16384;\nconst MAX_LABELS = 50;\nconst MAX_KV = 512;\nconst MAX_BATCH = 1000;\n\nexport function validateRecord(raw: any): { ok: true; rec: LogRecord } | { ok: false; error: string } {\n if (typeof raw !== 'object' || raw === null) return { ok: false, error: 'record must be an object' };\n if (typeof raw.service !== 'string' || raw.service.length === 0) return { ok: false, error: 'service required' };\n if (typeof raw.message !== 'string' || raw.message.length === 0) return { ok: false, error: 'message required' };\n if (raw.message.length > MAX_MESSAGE) return { ok: false, error: 'message too long' };\n if (raw.service.length > MAX_KV) return { ok: false, error: 'service too long' };\n const labels: Record<string, string> = {};\n if (raw.labels !== undefined) {\n if (typeof raw.labels !== 'object' || raw.labels === null || Array.isArray(raw.labels))\n return { ok: false, error: 'labels must be an object' };\n const keys = Object.keys(raw.labels);\n if (keys.length > MAX_LABELS) return { ok: false, error: 'too many labels' };\n for (const k of keys) {\n const v = raw.labels[k];\n if (typeof v !== 'string') return { ok: false, error: `label ${k} must be a string` };\n if (k.length > MAX_KV || v.length > MAX_KV) return { ok: false, error: `label ${k} too long` };\n labels[k] = v;\n }\n }\n const ts = typeof raw.ts === 'number' && Number.isFinite(raw.ts) ? raw.ts : Date.now();\n return { ok: true, rec: { ts, service: raw.service, message: raw.message, labels } };\n}\n\nexport function registerIngest(app: Express): void {\n app.post('/ingest', (req, res) => {\n const deps = app.get('deps') as AppDeps;\n if (!verifyIngestToken(req.header('authorization'), deps.cfg.ingestTokenHash)) {\n return res.status(401).json({ error: 'invalid token' });\n }\n const body = req.body;\n const records = Array.isArray(body) ? body : [body];\n if (records.length > MAX_BATCH) return res.status(400).json({ error: 'batch too large' });\n const validated: LogRecord[] = [];\n for (const raw of records) {\n const v = validateRecord(raw);\n if (!v.ok) return res.status(400).json({ error: v.error });\n validated.push(v.rec);\n }\n try {\n for (const rec of validated) {\n const id = insertLog(deps.db, rec);\n const stored = { ...rec, id };\n deps.buffer.push(stored);\n deps.broadcast(stored);\n }\n return res.json({ accepted: validated.length });\n } catch (err) {\n console.error('[tinylogs] ingest error:', (err as Error).message);\n return res.status(500).json({ error: 'internal error' });\n }\n });\n}\n", "import type { Express, RequestHandler } from 'express';\nimport { parse as parseCookie } from 'cookie';\nimport type { AppDeps } from './app.js';\nimport type { QueryParams } from '../storage/query.js';\nimport { queryLogs, queryLabels } from '../storage/query.js';\nimport { verifySession } from '../auth.js';\n\nexport function requireSession(app: Express): RequestHandler {\n return (req, res, next) => {\n const deps = app.get('deps') as AppDeps;\n const cookies = parseCookie(req.header('cookie') ?? '');\n const user = verifySession(cookies['tl_session'], deps.cfg.sessionSecret);\n if (!user) return res.status(401).json({ error: 'unauthorized' });\n (req as any).user = user;\n next();\n };\n}\n\nexport function parseQuery(query: Record<string, any>): QueryParams {\n const p: QueryParams = {};\n if (typeof query.service === 'string') p.service = query.service;\n if (typeof query.q === 'string') p.q = query.q;\n if (query.from !== undefined) p.from = Number(query.from);\n if (query.to !== undefined) p.to = Number(query.to);\n if (query.limit !== undefined) p.limit = Number(query.limit);\n if (query.before !== undefined) p.before = Number(query.before);\n const rawLabels = query.label === undefined ? [] : Array.isArray(query.label) ? query.label : [query.label];\n p.labels = rawLabels\n .map((s: string) => { const i = s.indexOf('='); return i < 0 ? null : { key: s.slice(0, i), value: s.slice(i + 1) }; })\n .filter(Boolean) as Array<{ key: string; value: string }>;\n return p;\n}\n\nexport function registerQueryRoutes(app: Express): void {\n const guard = requireSession(app);\n app.get('/api/logs', guard, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const logs = queryLogs(deps.db, parseQuery(req.query as any));\n res.json({ logs });\n });\n app.get('/api/labels', guard, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const service = typeof req.query.service === 'string' ? req.query.service : undefined;\n res.json(queryLabels(deps.db, { service }));\n });\n}\n", "import type Database from 'better-sqlite3';\nimport type { LogRecord } from '../types.js';\n\nexport interface QueryParams {\n service?: string;\n labels?: Array<{ key: string; value: string }>;\n q?: string;\n from?: number; to?: number;\n limit?: number;\n before?: number;\n}\n\nfunction rowToRecord(row: any): LogRecord {\n return { id: row.id, ts: row.ts, service: row.service, message: row.message, labels: JSON.parse(row.labels) };\n}\n\nexport function queryLogs(db: Database.Database, params: QueryParams): LogRecord[] {\n const where: string[] = [];\n const args: any[] = [];\n if (params.service) { where.push('logs.service = ?'); args.push(params.service); }\n if (params.q) { where.push('logs.message LIKE ? COLLATE NOCASE'); args.push(`%${params.q}%`); }\n if (params.from !== undefined) { where.push('logs.ts >= ?'); args.push(params.from); }\n if (params.to !== undefined) { where.push('logs.ts < ?'); args.push(params.to); }\n if (params.before !== undefined) { where.push('logs.id < ?'); args.push(params.before); }\n for (const l of params.labels ?? []) {\n where.push('logs.id IN (SELECT log_id FROM log_labels WHERE key = ? AND value = ?)');\n args.push(l.key, l.value);\n }\n const limit = Math.min(Math.max(params.limit ?? 200, 1), 1000);\n const sql = `SELECT id, ts, service, message, labels FROM logs\n ${where.length ? 'WHERE ' + where.join(' AND ') : ''}\n ORDER BY logs.id DESC LIMIT ?`;\n return db.prepare(sql).all(...args, limit).map(rowToRecord);\n}\n\nexport interface LabelCount { key: string; value: string; count: number; }\n\nexport function queryLabels(db: Database.Database, opts: { service?: string } = {}) {\n const services = db.prepare(\n 'SELECT service, COUNT(*) count FROM logs GROUP BY service ORDER BY count DESC'\n ).all() as Array<{ service: string; count: number }>;\n\n let labels: LabelCount[];\n if (opts.service) {\n labels = db.prepare(\n `SELECT ll.key, ll.value, COUNT(*) count FROM log_labels ll\n JOIN logs ON logs.id = ll.log_id WHERE logs.service = ?\n GROUP BY ll.key, ll.value ORDER BY ll.key, count DESC`\n ).all(opts.service) as LabelCount[];\n } else {\n labels = db.prepare(\n `SELECT key, value, COUNT(*) count FROM log_labels\n GROUP BY key, value ORDER BY key, count DESC`\n ).all() as LabelCount[];\n }\n return { services, labels };\n}\n", "import type { Express, RequestHandler } from 'express';\nimport { serialize as serializeCookie } from 'cookie';\nimport type { AppDeps } from './app.js';\nimport { verifyUser, signSession } from '../auth.js';\n\nexport function makeLoginLimiter(maxAttempts = 10, windowMs = 5 * 60_000): RequestHandler {\n const hits = new Map<string, { count: number; reset: number }>();\n return (req, res, next) => {\n const now = Date.now();\n const ip = req.ip ?? 'unknown';\n const rec = hits.get(ip);\n if (!rec || now > rec.reset) { hits.set(ip, { count: 1, reset: now + windowMs }); return next(); }\n rec.count += 1;\n if (rec.count > maxAttempts) return res.status(429).json({ error: 'too many attempts' });\n next();\n };\n}\n\nexport function registerAuthRoutes(app: Express): void {\n const limiter = makeLoginLimiter();\n app.post('/api/login', limiter, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const { username, password } = req.body ?? {};\n if (typeof username !== 'string' || typeof password !== 'string')\n return res.status(400).json({ error: 'username and password required' });\n if (!verifyUser(deps.db, username, password))\n return res.status(401).json({ error: 'invalid credentials' });\n const cookie = serializeCookie('tl_session', signSession(username, deps.cfg.sessionSecret), {\n httpOnly: true, sameSite: 'strict', path: '/', maxAge: 60 * 60 * 24 * 30,\n });\n res.setHeader('Set-Cookie', cookie);\n res.json({ ok: true, username });\n });\n\n app.post('/api/logout', (_req, res) => {\n res.setHeader('Set-Cookie', serializeCookie('tl_session', '', { httpOnly: true, path: '/', maxAge: 0 }));\n res.json({ ok: true });\n });\n}\n", "import type { Server } from 'node:http';\nimport { parse as parseCookie } from 'cookie';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport type { RingBuffer } from '../buffer.js';\nimport type { Config } from '../types.js';\nimport { verifySession } from '../auth.js';\n\nexport function attachWebSocket(\n server: Server,\n deps: { buffer: RingBuffer; cfg: Config; clients: Set<WebSocket> },\n): WebSocketServer {\n const wss = new WebSocketServer({ server, path: '/ws' });\n\n wss.on('connection', (ws, req) => {\n const cookies = parseCookie(req.headers.cookie ?? '');\n const user = verifySession(cookies['tl_session'], deps.cfg.sessionSecret);\n if (!user) { ws.close(4401, 'unauthorized'); return; }\n\n (ws as any).isAlive = true;\n ws.on('pong', () => { (ws as any).isAlive = true; });\n\n ws.send(JSON.stringify({ type: 'buffer', data: deps.buffer.snapshot() }));\n deps.clients.add(ws);\n ws.on('close', () => deps.clients.delete(ws));\n ws.on('error', () => deps.clients.delete(ws));\n });\n\n const interval = setInterval(() => {\n for (const ws of wss.clients) {\n if ((ws as any).isAlive === false) { ws.terminate(); continue; }\n (ws as any).isAlive = false;\n try { ws.ping(); } catch {}\n }\n }, 30_000);\n interval.unref();\n wss.on('close', () => clearInterval(interval));\n\n return wss;\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { createServer } from 'node:http';\nimport { openDb } from '../storage/db.js';\nimport { runRetention } from '../storage/retention.js';\nimport { RingBuffer } from '../buffer.js';\nimport { loadConfig, resolveConfigPath, resolveDbPath } from '../config.js';\nimport { createApp } from './app.js';\nimport { attachWebSocket } from './stream.js';\nimport type { WebSocket } from 'ws';\nimport type { LogRecord } from '../types.js';\n\nexport async function start(configPath = resolveConfigPath()): Promise<{ port: number; close: () => Promise<void> }> {\n const cfg = loadConfig(configPath);\n const db = openDb(resolveDbPath(configPath, cfg));\n const buffer = new RingBuffer(cfg.bufferSize);\n const wsClients = new Set<WebSocket>();\n const broadcast = (r: LogRecord) => {\n const msg = JSON.stringify({ type: 'log', data: r });\n for (const c of wsClients) { try { if (c.readyState === 1) c.send(msg); } catch {} }\n };\n\n const app = createApp({ db, buffer, cfg, broadcast });\n const server = createServer(app);\n attachWebSocket(server, { buffer, cfg, clients: wsClients });\n\n const retentionTimer = setInterval(\n () => runRetention(db, { retentionDays: cfg.retentionDays, maxSizeMB: cfg.maxSizeMB }),\n 60_000,\n );\n retentionTimer.unref();\n\n await new Promise<void>((resolve) => server.listen(cfg.port, cfg.host, resolve));\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : cfg.port;\n console.log(`[tinylogs] listening on http://${cfg.host}:${port}`);\n\n return {\n port,\n close: () => new Promise<void>((resolve) => {\n clearInterval(retentionTimer);\n for (const c of wsClients) { try { c.close(); } catch {} }\n server.close(() => { db.close(); resolve(); });\n server.closeIdleConnections?.();\n }),\n };\n}\n", "import { DatabaseSync } from 'node:sqlite';\nimport type { LogRecord } from '../types.js';\n\nconst MIGRATIONS: string[] = [\n `CREATE TABLE logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n ts INTEGER NOT NULL,\n service TEXT NOT NULL,\n message TEXT NOT NULL,\n labels TEXT NOT NULL\n );\n CREATE INDEX idx_logs_ts ON logs(ts);\n CREATE INDEX idx_logs_service ON logs(service);\n CREATE TABLE log_labels (\n log_id INTEGER NOT NULL REFERENCES logs(id) ON DELETE CASCADE,\n key TEXT NOT NULL,\n value TEXT NOT NULL\n );\n CREATE INDEX idx_labels_kv ON log_labels(key, value);\n CREATE INDEX idx_labels_logid ON log_labels(log_id);\n CREATE TABLE users (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n username TEXT UNIQUE NOT NULL,\n password_hash TEXT NOT NULL,\n role TEXT NOT NULL DEFAULT 'admin',\n created_at INTEGER NOT NULL\n );`,\n];\n\n// node:sqlite has no db.transaction() helper \u2014 wrap a unit of work in an\n// explicit transaction so partial failures roll back.\nfunction tx<T>(db: DatabaseSync, fn: () => T): T {\n db.exec('BEGIN');\n try {\n const result = fn();\n db.exec('COMMIT');\n return result;\n } catch (err) {\n db.exec('ROLLBACK');\n throw err;\n }\n}\n\nexport function openDb(path: string): DatabaseSync {\n const db = new DatabaseSync(path);\n db.exec('PRAGMA journal_mode = WAL');\n db.exec('PRAGMA foreign_keys = ON');\n db.exec('CREATE TABLE IF NOT EXISTS schema_migrations (version INTEGER PRIMARY KEY)');\n const current = (db.prepare('SELECT COALESCE(MAX(version),0) v FROM schema_migrations').get() as { v: number }).v;\n tx(db, () => {\n for (let i = current; i < MIGRATIONS.length; i++) {\n db.exec(MIGRATIONS[i]);\n db.prepare('INSERT INTO schema_migrations (version) VALUES (?)').run(i + 1);\n }\n });\n return db;\n}\n\nexport function insertLog(db: DatabaseSync, rec: LogRecord): number {\n return tx(db, () => {\n const info = db.prepare('INSERT INTO logs (ts, service, message, labels) VALUES (?,?,?,?)')\n .run(rec.ts, rec.service, rec.message, JSON.stringify(rec.labels ?? {}));\n const logId = Number(info.lastInsertRowid);\n const ins = db.prepare('INSERT INTO log_labels (log_id, key, value) VALUES (?,?,?)');\n for (const [k, v] of Object.entries(rec.labels ?? {})) ins.run(logId, k, String(v));\n return logId;\n });\n}\n\nexport function dbSizeBytes(db: DatabaseSync): number {\n const pageCount = Number((db.prepare('PRAGMA page_count').get() as { page_count: number }).page_count);\n const pageSize = Number((db.prepare('PRAGMA page_size').get() as { page_size: number }).page_size);\n return pageCount * pageSize;\n}\n", "import type { DatabaseSync } from 'node:sqlite';\nimport { dbSizeBytes } from './db.js';\n\nexport function pruneByAge(db: DatabaseSync, retentionDays: number, now: number): number {\n const cutoff = now - retentionDays * 86400000;\n return Number(db.prepare('DELETE FROM logs WHERE ts < ?').run(cutoff).changes);\n}\n\nexport function pruneBySize(db: DatabaseSync, maxSizeMB: number, batch = 1000): number {\n const maxBytes = maxSizeMB * 1024 * 1024;\n let deleted = 0;\n // Delete oldest rows in batches until file is under the cap or nothing remains.\n while (dbSizeBytes(db) > maxBytes) {\n const changes = Number(db.prepare(\n 'DELETE FROM logs WHERE id IN (SELECT id FROM logs ORDER BY id ASC LIMIT ?)'\n ).run(batch).changes);\n if (changes === 0) break;\n deleted += changes;\n }\n return deleted;\n}\n\nexport function runRetention(\n db: DatabaseSync,\n opts: { retentionDays: number; maxSizeMB: number; now?: number }\n): void {\n try {\n const now = opts.now ?? Date.now();\n pruneByAge(db, opts.retentionDays, now);\n pruneBySize(db, opts.maxSizeMB);\n db.exec('VACUUM');\n } catch (err) {\n console.error('[tinylogs] retention failed:', (err as Error).message);\n }\n}\n", "import type { LogRecord } from './types.js';\n\nexport class RingBuffer {\n private items: LogRecord[] = [];\n constructor(private capacity: number) {}\n\n push(rec: LogRecord): void {\n this.items.push(rec);\n if (this.items.length > this.capacity) this.items.shift();\n }\n\n snapshot(): LogRecord[] {\n return this.items.slice();\n }\n\n get size(): number {\n return this.items.length;\n }\n}\n", "import { createHash, randomBytes } from 'node:crypto';\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport type { Config } from './types.js';\n\nexport const DEFAULTS = {\n port: 4700,\n host: '127.0.0.1',\n dbPath: 'tinylogs.db',\n retentionDays: 14,\n maxSizeMB: 500,\n bufferSize: 2000,\n} as const;\n\nexport function resolveConfigPath(flag?: string): string {\n if (flag) return flag;\n if (process.env.TINYLOGS_CONFIG) return process.env.TINYLOGS_CONFIG;\n return join(process.cwd(), 'tinylogs.config.json');\n}\n\nexport function hashToken(token: string): string {\n return createHash('sha256').update(token, 'utf8').digest('hex');\n}\n\nexport function generateSecrets() {\n const sessionSecret = randomBytes(32).toString('hex');\n const token = randomBytes(24).toString('base64url');\n return { sessionSecret, token, ingestTokenHash: hashToken(token) };\n}\n\nexport function saveConfig(path: string, cfg: Config): void {\n writeFileSync(path, JSON.stringify(cfg, null, 2) + '\\n', { mode: 0o600 });\n}\n\nexport function loadConfig(path: string): Config {\n const raw = readFileSync(path, 'utf8'); // throws if missing\n const cfg = JSON.parse(raw) as Config;\n for (const k of ['port', 'host', 'dbPath', 'sessionSecret', 'ingestTokenHash'] as const) {\n if (cfg[k] === undefined) throw new Error(`config missing field: ${k}`);\n }\n return cfg;\n}\n\nexport function resolveDbPath(configPath: string, cfg: Config): string {\n if (isAbsolute(cfg.dbPath)) return cfg.dbPath;\n return resolve(dirname(configPath), cfg.dbPath);\n}\n", "import express, { type Express } from 'express';\nimport { existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { DatabaseSync } from 'node:sqlite';\nimport type { Config, LogRecord } from '../types.js';\nimport { RingBuffer } from '../buffer.js';\nimport { registerIngest } from './ingest.js';\nimport { registerQueryRoutes } from './queryRoutes.js';\nimport { registerAuthRoutes } from './authRoutes.js';\n\nexport interface AppDeps {\n db: DatabaseSync;\n buffer: RingBuffer;\n cfg: Config;\n broadcast: (r: LogRecord) => void;\n}\n\nexport function createApp(deps: AppDeps): Express {\n const app = express();\n app.set('trust proxy', true); // so req.ip reflects X-Forwarded-For behind a reverse proxy\n app.set('deps', deps);\n\n app.get('/api/health', (_req, res) => res.json({ ok: true }));\n\n // /ingest gets its own 5MB JSON parser (Task 9 mounts the handler after this).\n app.use('/ingest', express.json({ limit: '5mb' }));\n app.use('/api', express.json({ limit: '1mb' }));\n\n registerIngest(app);\n registerQueryRoutes(app);\n registerAuthRoutes(app);\n\n // Static UI (built to dist/ui). The running entry may be dist/cli.js (here = dist)\n // or dist/server/index.js (here = dist/server), so probe both depths. In dev (tsx\n // from src) neither resolves, so also fall back to <cwd>/dist/ui. Pick the first\n // candidate that actually contains the built bundle. Absent entirely \u2192 skip.\n const here = dirname(fileURLToPath(import.meta.url));\n const uiDir = [join(here, 'ui'), join(here, '..', 'ui'), join(process.cwd(), 'dist', 'ui')]\n .find((d) => existsSync(join(d, 'bundle.js')));\n if (uiDir) app.use(express.static(uiDir));\n\n return app;\n}\n", "import bcrypt from 'bcryptjs';\nimport { createHmac, timingSafeEqual } from 'node:crypto';\nimport type { DatabaseSync } from 'node:sqlite';\nimport { hashToken } from './config.js';\n\nexport function hashPassword(pw: string): string {\n return bcrypt.hashSync(pw, 10);\n}\n\nexport function createUser(db: DatabaseSync, username: string, pw: string, role = 'admin'): number {\n const info = db.prepare(\n 'INSERT INTO users (username, password_hash, role, created_at) VALUES (?,?,?,?)'\n ).run(username, hashPassword(pw), role, Date.now());\n return Number(info.lastInsertRowid);\n}\n\nexport function verifyUser(db: DatabaseSync, username: string, pw: string): boolean {\n const row = db.prepare('SELECT password_hash FROM users WHERE username=?').get(username) as any;\n if (!row) { bcrypt.compareSync(pw, '$2a$10$0000000000000000000000000000000000000000000000000000'); return false; }\n return bcrypt.compareSync(pw, row.password_hash);\n}\n\nfunction safeEqualHex(a: string, b: string): boolean {\n const ab = Buffer.from(a, 'hex'); const bb = Buffer.from(b, 'hex');\n if (ab.length !== bb.length || ab.length === 0) return false;\n return timingSafeEqual(ab, bb);\n}\n\nexport function verifyIngestToken(bearer: string | undefined, ingestTokenHash: string): boolean {\n if (!bearer || !bearer.startsWith('Bearer ')) return false;\n const token = bearer.slice('Bearer '.length);\n return safeEqualHex(hashToken(token), ingestTokenHash);\n}\n\nexport function signSession(username: string, secret: string): string {\n const mac = createHmac('sha256', secret).update(username).digest('hex');\n return `${username}.${mac}`;\n}\n\nexport function verifySession(cookieVal: string | undefined, secret: string): string | null {\n if (!cookieVal) return null;\n const dot = cookieVal.lastIndexOf('.');\n if (dot <= 0) return null;\n const username = cookieVal.slice(0, dot);\n const mac = cookieVal.slice(dot + 1);\n const expected = createHmac('sha256', secret).update(username).digest('hex');\n if (mac.length !== expected.length) return null;\n if (!timingSafeEqual(Buffer.from(mac), Buffer.from(expected))) return null;\n return username;\n}\n", "import type { Express } from 'express';\nimport type { AppDeps } from './app.js';\nimport type { LogRecord } from '../types.js';\nimport { verifyIngestToken } from '../auth.js';\nimport { insertLog } from '../storage/db.js';\n\nconst MAX_MESSAGE = 16384;\nconst MAX_LABELS = 50;\nconst MAX_KV = 512;\nconst MAX_BATCH = 1000;\n\nexport function validateRecord(raw: any): { ok: true; rec: LogRecord } | { ok: false; error: string } {\n if (typeof raw !== 'object' || raw === null) return { ok: false, error: 'record must be an object' };\n if (typeof raw.service !== 'string' || raw.service.length === 0) return { ok: false, error: 'service required' };\n if (typeof raw.message !== 'string' || raw.message.length === 0) return { ok: false, error: 'message required' };\n if (raw.message.length > MAX_MESSAGE) return { ok: false, error: 'message too long' };\n if (raw.service.length > MAX_KV) return { ok: false, error: 'service too long' };\n const labels: Record<string, string> = {};\n if (raw.labels !== undefined) {\n if (typeof raw.labels !== 'object' || raw.labels === null || Array.isArray(raw.labels))\n return { ok: false, error: 'labels must be an object' };\n const keys = Object.keys(raw.labels);\n if (keys.length > MAX_LABELS) return { ok: false, error: 'too many labels' };\n for (const k of keys) {\n const v = raw.labels[k];\n if (typeof v !== 'string') return { ok: false, error: `label ${k} must be a string` };\n if (k.length > MAX_KV || v.length > MAX_KV) return { ok: false, error: `label ${k} too long` };\n labels[k] = v;\n }\n }\n const ts = typeof raw.ts === 'number' && Number.isFinite(raw.ts) ? raw.ts : Date.now();\n return { ok: true, rec: { ts, service: raw.service, message: raw.message, labels } };\n}\n\nexport function registerIngest(app: Express): void {\n app.post('/ingest', (req, res) => {\n const deps = app.get('deps') as AppDeps;\n if (!verifyIngestToken(req.header('authorization'), deps.cfg.ingestTokenHash)) {\n return res.status(401).json({ error: 'invalid token' });\n }\n const body = req.body;\n const records = Array.isArray(body) ? body : [body];\n if (records.length > MAX_BATCH) return res.status(400).json({ error: 'batch too large' });\n const validated: LogRecord[] = [];\n for (const raw of records) {\n const v = validateRecord(raw);\n if (!v.ok) return res.status(400).json({ error: v.error });\n validated.push(v.rec);\n }\n try {\n for (const rec of validated) {\n const id = insertLog(deps.db, rec);\n const stored = { ...rec, id };\n deps.buffer.push(stored);\n deps.broadcast(stored);\n }\n return res.json({ accepted: validated.length });\n } catch (err) {\n console.error('[tinylogs] ingest error:', (err as Error).message);\n return res.status(500).json({ error: 'internal error' });\n }\n });\n}\n", "import type { Express, RequestHandler } from 'express';\nimport { parse as parseCookie } from 'cookie';\nimport type { AppDeps } from './app.js';\nimport type { QueryParams } from '../storage/query.js';\nimport { queryLogs, queryLabels } from '../storage/query.js';\nimport { verifySession } from '../auth.js';\n\nexport function requireSession(app: Express): RequestHandler {\n return (req, res, next) => {\n const deps = app.get('deps') as AppDeps;\n const cookies = parseCookie(req.header('cookie') ?? '');\n const user = verifySession(cookies['tl_session'], deps.cfg.sessionSecret);\n if (!user) return res.status(401).json({ error: 'unauthorized' });\n (req as any).user = user;\n next();\n };\n}\n\nexport function parseQuery(query: Record<string, any>): QueryParams {\n const p: QueryParams = {};\n if (typeof query.service === 'string') p.service = query.service;\n if (typeof query.q === 'string') p.q = query.q;\n if (query.from !== undefined) p.from = Number(query.from);\n if (query.to !== undefined) p.to = Number(query.to);\n if (query.limit !== undefined) p.limit = Number(query.limit);\n if (query.before !== undefined) p.before = Number(query.before);\n const rawLabels = query.label === undefined ? [] : Array.isArray(query.label) ? query.label : [query.label];\n p.labels = rawLabels\n .map((s: string) => { const i = s.indexOf('='); return i < 0 ? null : { key: s.slice(0, i), value: s.slice(i + 1) }; })\n .filter(Boolean) as Array<{ key: string; value: string }>;\n return p;\n}\n\nexport function registerQueryRoutes(app: Express): void {\n const guard = requireSession(app);\n app.get('/api/logs', guard, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const logs = queryLogs(deps.db, parseQuery(req.query as any));\n res.json({ logs });\n });\n app.get('/api/labels', guard, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const service = typeof req.query.service === 'string' ? req.query.service : undefined;\n res.json(queryLabels(deps.db, { service }));\n });\n}\n", "import type { DatabaseSync } from 'node:sqlite';\nimport type { LogRecord } from '../types.js';\n\nexport interface QueryParams {\n service?: string;\n labels?: Array<{ key: string; value: string }>;\n q?: string;\n from?: number; to?: number;\n limit?: number;\n before?: number;\n}\n\nfunction rowToRecord(row: any): LogRecord {\n return { id: row.id, ts: row.ts, service: row.service, message: row.message, labels: JSON.parse(row.labels) };\n}\n\nexport function queryLogs(db: DatabaseSync, params: QueryParams): LogRecord[] {\n const where: string[] = [];\n const args: any[] = [];\n if (params.service) { where.push('logs.service = ?'); args.push(params.service); }\n if (params.q) { where.push('logs.message LIKE ? COLLATE NOCASE'); args.push(`%${params.q}%`); }\n if (params.from !== undefined) { where.push('logs.ts >= ?'); args.push(params.from); }\n if (params.to !== undefined) { where.push('logs.ts < ?'); args.push(params.to); }\n if (params.before !== undefined) { where.push('logs.id < ?'); args.push(params.before); }\n for (const l of params.labels ?? []) {\n where.push('logs.id IN (SELECT log_id FROM log_labels WHERE key = ? AND value = ?)');\n args.push(l.key, l.value);\n }\n const limit = Math.min(Math.max(params.limit ?? 200, 1), 1000);\n const sql = `SELECT id, ts, service, message, labels FROM logs\n ${where.length ? 'WHERE ' + where.join(' AND ') : ''}\n ORDER BY logs.id DESC LIMIT ?`;\n return db.prepare(sql).all(...args, limit).map(rowToRecord);\n}\n\nexport interface LabelCount { key: string; value: string; count: number; }\n\nfunction rowToLabel(row: any): LabelCount {\n return { key: row.key, value: row.value, count: Number(row.count) };\n}\n\nexport function queryLabels(db: DatabaseSync, opts: { service?: string } = {}) {\n const services = db.prepare(\n 'SELECT service, COUNT(*) count FROM logs GROUP BY service ORDER BY count DESC'\n ).all().map((row: any) => ({ service: row.service as string, count: Number(row.count) }));\n\n const labels = opts.service\n ? db.prepare(\n `SELECT ll.key, ll.value, COUNT(*) count FROM log_labels ll\n JOIN logs ON logs.id = ll.log_id WHERE logs.service = ?\n GROUP BY ll.key, ll.value ORDER BY ll.key, count DESC`\n ).all(opts.service).map(rowToLabel)\n : db.prepare(\n `SELECT key, value, COUNT(*) count FROM log_labels\n GROUP BY key, value ORDER BY key, count DESC`\n ).all().map(rowToLabel);\n\n return { services, labels };\n}\n", "import type { Express, RequestHandler } from 'express';\nimport { serialize as serializeCookie } from 'cookie';\nimport type { AppDeps } from './app.js';\nimport { verifyUser, signSession } from '../auth.js';\n\nexport function makeLoginLimiter(maxAttempts = 10, windowMs = 5 * 60_000): RequestHandler {\n const hits = new Map<string, { count: number; reset: number }>();\n return (req, res, next) => {\n const now = Date.now();\n const ip = req.ip ?? 'unknown';\n const rec = hits.get(ip);\n if (!rec || now > rec.reset) { hits.set(ip, { count: 1, reset: now + windowMs }); return next(); }\n rec.count += 1;\n if (rec.count > maxAttempts) return res.status(429).json({ error: 'too many attempts' });\n next();\n };\n}\n\nexport function registerAuthRoutes(app: Express): void {\n const limiter = makeLoginLimiter();\n app.post('/api/login', limiter, (req, res) => {\n const deps = app.get('deps') as AppDeps;\n const { username, password } = req.body ?? {};\n if (typeof username !== 'string' || typeof password !== 'string')\n return res.status(400).json({ error: 'username and password required' });\n if (!verifyUser(deps.db, username, password))\n return res.status(401).json({ error: 'invalid credentials' });\n const cookie = serializeCookie('tl_session', signSession(username, deps.cfg.sessionSecret), {\n httpOnly: true, sameSite: 'strict', path: '/', maxAge: 60 * 60 * 24 * 30,\n });\n res.setHeader('Set-Cookie', cookie);\n res.json({ ok: true, username });\n });\n\n app.post('/api/logout', (_req, res) => {\n res.setHeader('Set-Cookie', serializeCookie('tl_session', '', { httpOnly: true, path: '/', maxAge: 0 }));\n res.json({ ok: true });\n });\n}\n", "import type { Server } from 'node:http';\nimport { parse as parseCookie } from 'cookie';\nimport { WebSocketServer, WebSocket } from 'ws';\nimport type { RingBuffer } from '../buffer.js';\nimport type { Config } from '../types.js';\nimport { verifySession } from '../auth.js';\n\nexport function attachWebSocket(\n server: Server,\n deps: { buffer: RingBuffer; cfg: Config; clients: Set<WebSocket> },\n): WebSocketServer {\n const wss = new WebSocketServer({ server, path: '/ws' });\n\n wss.on('connection', (ws, req) => {\n const cookies = parseCookie(req.headers.cookie ?? '');\n const user = verifySession(cookies['tl_session'], deps.cfg.sessionSecret);\n if (!user) { ws.close(4401, 'unauthorized'); return; }\n\n (ws as any).isAlive = true;\n ws.on('pong', () => { (ws as any).isAlive = true; });\n\n ws.send(JSON.stringify({ type: 'buffer', data: deps.buffer.snapshot() }));\n deps.clients.add(ws);\n ws.on('close', () => deps.clients.delete(ws));\n ws.on('error', () => deps.clients.delete(ws));\n });\n\n const interval = setInterval(() => {\n for (const ws of wss.clients) {\n if ((ws as any).isAlive === false) { ws.terminate(); continue; }\n (ws as any).isAlive = false;\n try { ws.ping(); } catch {}\n }\n }, 30_000);\n interval.unref();\n wss.on('close', () => clearInterval(interval));\n\n return wss;\n}\n"],
|
|
5
|
+
"mappings": ";;;AAAA,SAAS,oBAAoB;;;ACA7B,SAAS,oBAAoB;AAG7B,IAAM,aAAuB;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBF;AAIA,SAAS,GAAM,IAAkB,IAAgB;AAC/C,KAAG,KAAK,OAAO;AACf,MAAI;AACF,UAAM,SAAS,GAAG;AAClB,OAAG,KAAK,QAAQ;AAChB,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,OAAG,KAAK,UAAU;AAClB,UAAM;AAAA,EACR;AACF;AAEO,SAAS,OAAO,MAA4B;AACjD,QAAM,KAAK,IAAI,aAAa,IAAI;AAChC,KAAG,KAAK,2BAA2B;AACnC,KAAG,KAAK,0BAA0B;AAClC,KAAG,KAAK,4EAA4E;AACpF,QAAM,UAAW,GAAG,QAAQ,0DAA0D,EAAE,IAAI,EAAoB;AAChH,KAAG,IAAI,MAAM;AACX,aAAS,IAAI,SAAS,IAAI,WAAW,QAAQ,KAAK;AAChD,SAAG,KAAK,WAAW,CAAC,CAAC;AACrB,SAAG,QAAQ,oDAAoD,EAAE,IAAI,IAAI,CAAC;AAAA,IAC5E;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEO,SAAS,UAAU,IAAkB,KAAwB;AAClE,SAAO,GAAG,IAAI,MAAM;AAClB,UAAM,OAAO,GAAG,QAAQ,kEAAkE,EACvF,IAAI,IAAI,IAAI,IAAI,SAAS,IAAI,SAAS,KAAK,UAAU,IAAI,UAAU,CAAC,CAAC,CAAC;AACzE,UAAM,QAAQ,OAAO,KAAK,eAAe;AACzC,UAAM,MAAM,GAAG,QAAQ,4DAA4D;AACnF,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,UAAU,CAAC,CAAC,EAAG,KAAI,IAAI,OAAO,GAAG,OAAO,CAAC,CAAC;AAClF,WAAO;AAAA,EACT,CAAC;AACH;AAEO,SAAS,YAAY,IAA0B;AACpD,QAAM,YAAY,OAAQ,GAAG,QAAQ,mBAAmB,EAAE,IAAI,EAA6B,UAAU;AACrG,QAAM,WAAW,OAAQ,GAAG,QAAQ,kBAAkB,EAAE,IAAI,EAA4B,SAAS;AACjG,SAAO,YAAY;AACrB;;;ACtEO,SAAS,WAAW,IAAkB,eAAuB,KAAqB;AACvF,QAAM,SAAS,MAAM,gBAAgB;AACrC,SAAO,OAAO,GAAG,QAAQ,+BAA+B,EAAE,IAAI,MAAM,EAAE,OAAO;AAC/E;AAEO,SAAS,YAAY,IAAkB,WAAmB,QAAQ,KAAc;AACrF,QAAM,WAAW,YAAY,OAAO;AACpC,MAAI,UAAU;AAEd,SAAO,YAAY,EAAE,IAAI,UAAU;AACjC,UAAM,UAAU,OAAO,GAAG;AAAA,MACxB;AAAA,IACF,EAAE,IAAI,KAAK,EAAE,OAAO;AACpB,QAAI,YAAY,EAAG;AACnB,eAAW;AAAA,EACb;AACA,SAAO;AACT;AAEO,SAAS,aACd,IACA,MACM;AACN,MAAI;AACF,UAAM,MAAM,KAAK,OAAO,KAAK,IAAI;AACjC,eAAW,IAAI,KAAK,eAAe,GAAG;AACtC,gBAAY,IAAI,KAAK,SAAS;AAC9B,OAAG,KAAK,QAAQ;AAAA,EAClB,SAAS,KAAK;AACZ,YAAQ,MAAM,gCAAiC,IAAc,OAAO;AAAA,EACtE;AACF;;;AChCO,IAAM,aAAN,MAAiB;AAAA,EAEtB,YAAoB,UAAkB;AAAlB;AAAA,EAAmB;AAAA,EAD/B,QAAqB,CAAC;AAAA,EAG9B,KAAK,KAAsB;AACzB,SAAK,MAAM,KAAK,GAAG;AACnB,QAAI,KAAK,MAAM,SAAS,KAAK,SAAU,MAAK,MAAM,MAAM;AAAA,EAC1D;AAAA,EAEA,WAAwB;AACtB,WAAO,KAAK,MAAM,MAAM;AAAA,EAC1B;AAAA,EAEA,IAAI,OAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;;;AClBA,SAAS,YAAY,mBAAmB;AACxC,SAAS,cAAc,qBAAqB;AAC5C,SAAS,SAAS,YAAY,MAAM,eAAe;AAY5C,SAAS,kBAAkB,MAAuB;AACvD,MAAI,KAAM,QAAO;AACjB,MAAI,QAAQ,IAAI,gBAAiB,QAAO,QAAQ,IAAI;AACpD,SAAO,KAAK,QAAQ,IAAI,GAAG,sBAAsB;AACnD;AAEO,SAAS,UAAU,OAAuB;AAC/C,SAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,MAAM,EAAE,OAAO,KAAK;AAChE;AAYO,SAAS,WAAW,MAAsB;AAC/C,QAAM,MAAM,aAAa,MAAM,MAAM;AACrC,QAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,aAAW,KAAK,CAAC,QAAQ,QAAQ,UAAU,iBAAiB,iBAAiB,GAAY;AACvF,QAAI,IAAI,CAAC,MAAM,OAAW,OAAM,IAAI,MAAM,yBAAyB,CAAC,EAAE;AAAA,EACxE;AACA,SAAO;AACT;AAEO,SAAS,cAAc,YAAoB,KAAqB;AACrE,MAAI,WAAW,IAAI,MAAM,EAAG,QAAO,IAAI;AACvC,SAAO,QAAQ,QAAQ,UAAU,GAAG,IAAI,MAAM;AAChD;;;AC9CA,OAAO,aAA+B;AACtC,SAAS,kBAAkB;AAC3B,SAAS,WAAAA,UAAS,QAAAC,aAAY;AAC9B,SAAS,qBAAqB;;;ACH9B,OAAO,YAAY;AACnB,SAAS,YAAY,uBAAuB;AAerC,SAAS,WAAW,IAAkB,UAAkB,IAAqB;AAClF,QAAM,MAAM,GAAG,QAAQ,kDAAkD,EAAE,IAAI,QAAQ;AACvF,MAAI,CAAC,KAAK;AAAE,WAAO,YAAY,IAAI,6DAA6D;AAAG,WAAO;AAAA,EAAO;AACjH,SAAO,OAAO,YAAY,IAAI,IAAI,aAAa;AACjD;AAEA,SAAS,aAAa,GAAW,GAAoB;AACnD,QAAM,KAAK,OAAO,KAAK,GAAG,KAAK;AAAG,QAAM,KAAK,OAAO,KAAK,GAAG,KAAK;AACjE,MAAI,GAAG,WAAW,GAAG,UAAU,GAAG,WAAW,EAAG,QAAO;AACvD,SAAO,gBAAgB,IAAI,EAAE;AAC/B;AAEO,SAAS,kBAAkB,QAA4B,iBAAkC;AAC9F,MAAI,CAAC,UAAU,CAAC,OAAO,WAAW,SAAS,EAAG,QAAO;AACrD,QAAM,QAAQ,OAAO,MAAM,UAAU,MAAM;AAC3C,SAAO,aAAa,UAAU,KAAK,GAAG,eAAe;AACvD;AAEO,SAAS,YAAY,UAAkB,QAAwB;AACpE,QAAM,MAAM,WAAW,UAAU,MAAM,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAK;AACtE,SAAO,GAAG,QAAQ,IAAI,GAAG;AAC3B;AAEO,SAAS,cAAc,WAA+B,QAA+B;AAC1F,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,MAAM,UAAU,YAAY,GAAG;AACrC,MAAI,OAAO,EAAG,QAAO;AACrB,QAAM,WAAW,UAAU,MAAM,GAAG,GAAG;AACvC,QAAM,MAAM,UAAU,MAAM,MAAM,CAAC;AACnC,QAAM,WAAW,WAAW,UAAU,MAAM,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAK;AAC3E,MAAI,IAAI,WAAW,SAAS,OAAQ,QAAO;AAC3C,MAAI,CAAC,gBAAgB,OAAO,KAAK,GAAG,GAAG,OAAO,KAAK,QAAQ,CAAC,EAAG,QAAO;AACtE,SAAO;AACT;;;AC3CA,IAAM,cAAc;AACpB,IAAM,aAAa;AACnB,IAAM,SAAS;AACf,IAAM,YAAY;AAEX,SAAS,eAAe,KAAuE;AACpG,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO,EAAE,IAAI,OAAO,OAAO,2BAA2B;AACnG,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,mBAAmB;AAC/G,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,mBAAmB;AAC/G,MAAI,IAAI,QAAQ,SAAS,YAAa,QAAO,EAAE,IAAI,OAAO,OAAO,mBAAmB;AACpF,MAAI,IAAI,QAAQ,SAAS,OAAQ,QAAO,EAAE,IAAI,OAAO,OAAO,mBAAmB;AAC/E,QAAM,SAAiC,CAAC;AACxC,MAAI,IAAI,WAAW,QAAW;AAC5B,QAAI,OAAO,IAAI,WAAW,YAAY,IAAI,WAAW,QAAQ,MAAM,QAAQ,IAAI,MAAM;AACnF,aAAO,EAAE,IAAI,OAAO,OAAO,2BAA2B;AACxD,UAAM,OAAO,OAAO,KAAK,IAAI,MAAM;AACnC,QAAI,KAAK,SAAS,WAAY,QAAO,EAAE,IAAI,OAAO,OAAO,kBAAkB;AAC3E,eAAW,KAAK,MAAM;AACpB,YAAM,IAAI,IAAI,OAAO,CAAC;AACtB,UAAI,OAAO,MAAM,SAAU,QAAO,EAAE,IAAI,OAAO,OAAO,SAAS,CAAC,oBAAoB;AACpF,UAAI,EAAE,SAAS,UAAU,EAAE,SAAS,OAAQ,QAAO,EAAE,IAAI,OAAO,OAAO,SAAS,CAAC,YAAY;AAC7F,aAAO,CAAC,IAAI;AAAA,IACd;AAAA,EACF;AACA,QAAM,KAAK,OAAO,IAAI,OAAO,YAAY,OAAO,SAAS,IAAI,EAAE,IAAI,IAAI,KAAK,KAAK,IAAI;AACrF,SAAO,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,SAAS,IAAI,SAAS,SAAS,IAAI,SAAS,OAAO,EAAE;AACrF;AAEO,SAAS,eAAe,KAAoB;AACjD,MAAI,KAAK,WAAW,CAAC,KAAK,QAAQ;AAChC,UAAM,OAAO,IAAI,IAAI,MAAM;AAC3B,QAAI,CAAC,kBAAkB,IAAI,OAAO,eAAe,GAAG,KAAK,IAAI,eAAe,GAAG;AAC7E,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,gBAAgB,CAAC;AAAA,IACxD;AACA,UAAM,OAAO,IAAI;AACjB,UAAM,UAAU,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI;AAClD,QAAI,QAAQ,SAAS,UAAW,QAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,kBAAkB,CAAC;AACxF,UAAM,YAAyB,CAAC;AAChC,eAAW,OAAO,SAAS;AACzB,YAAM,IAAI,eAAe,GAAG;AAC5B,UAAI,CAAC,EAAE,GAAI,QAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC;AACzD,gBAAU,KAAK,EAAE,GAAG;AAAA,IACtB;AACA,QAAI;AACF,iBAAW,OAAO,WAAW;AAC3B,cAAM,KAAK,UAAU,KAAK,IAAI,GAAG;AACjC,cAAM,SAAS,EAAE,GAAG,KAAK,GAAG;AAC5B,aAAK,OAAO,KAAK,MAAM;AACvB,aAAK,UAAU,MAAM;AAAA,MACvB;AACA,aAAO,IAAI,KAAK,EAAE,UAAU,UAAU,OAAO,CAAC;AAAA,IAChD,SAAS,KAAK;AACZ,cAAQ,MAAM,4BAA6B,IAAc,OAAO;AAChE,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAAA,IACzD;AAAA,EACF,CAAC;AACH;;;AC7DA,SAAS,SAAS,mBAAmB;;;ACWrC,SAAS,YAAY,KAAqB;AACxC,SAAO,EAAE,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,SAAS,IAAI,SAAS,SAAS,IAAI,SAAS,QAAQ,KAAK,MAAM,IAAI,MAAM,EAAE;AAC9G;AAEO,SAAS,UAAU,IAAkB,QAAkC;AAC5E,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAc,CAAC;AACrB,MAAI,OAAO,SAAS;AAAE,UAAM,KAAK,kBAAkB;AAAG,SAAK,KAAK,OAAO,OAAO;AAAA,EAAG;AACjF,MAAI,OAAO,GAAG;AAAE,UAAM,KAAK,oCAAoC;AAAG,SAAK,KAAK,IAAI,OAAO,CAAC,GAAG;AAAA,EAAG;AAC9F,MAAI,OAAO,SAAS,QAAW;AAAE,UAAM,KAAK,cAAc;AAAG,SAAK,KAAK,OAAO,IAAI;AAAA,EAAG;AACrF,MAAI,OAAO,OAAO,QAAW;AAAE,UAAM,KAAK,aAAa;AAAG,SAAK,KAAK,OAAO,EAAE;AAAA,EAAG;AAChF,MAAI,OAAO,WAAW,QAAW;AAAE,UAAM,KAAK,aAAa;AAAG,SAAK,KAAK,OAAO,MAAM;AAAA,EAAG;AACxF,aAAW,KAAK,OAAO,UAAU,CAAC,GAAG;AACnC,UAAM,KAAK,wEAAwE;AACnF,SAAK,KAAK,EAAE,KAAK,EAAE,KAAK;AAAA,EAC1B;AACA,QAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,OAAO,SAAS,KAAK,CAAC,GAAG,GAAI;AAC7D,QAAM,MAAM;AAAA,iBACG,MAAM,SAAS,WAAW,MAAM,KAAK,OAAO,IAAI,EAAE;AAAA;AAEjE,SAAO,GAAG,QAAQ,GAAG,EAAE,IAAI,GAAG,MAAM,KAAK,EAAE,IAAI,WAAW;AAC5D;AAIA,SAAS,WAAW,KAAsB;AACxC,SAAO,EAAE,KAAK,IAAI,KAAK,OAAO,IAAI,OAAO,OAAO,OAAO,IAAI,KAAK,EAAE;AACpE;AAEO,SAAS,YAAY,IAAkB,OAA6B,CAAC,GAAG;AAC7E,QAAM,WAAW,GAAG;AAAA,IAClB;AAAA,EACF,EAAE,IAAI,EAAE,IAAI,CAAC,SAAc,EAAE,SAAS,IAAI,SAAmB,OAAO,OAAO,IAAI,KAAK,EAAE,EAAE;AAExF,QAAM,SAAS,KAAK,UAChB,GAAG;AAAA,IACD;AAAA;AAAA;AAAA,EAGF,EAAE,IAAI,KAAK,OAAO,EAAE,IAAI,UAAU,IAClC,GAAG;AAAA,IACD;AAAA;AAAA,EAEF,EAAE,IAAI,EAAE,IAAI,UAAU;AAE1B,SAAO,EAAE,UAAU,OAAO;AAC5B;;;ADnDO,SAAS,eAAe,KAA8B;AAC3D,SAAO,CAAC,KAAK,KAAK,SAAS;AACzB,UAAM,OAAO,IAAI,IAAI,MAAM;AAC3B,UAAM,UAAU,YAAY,IAAI,OAAO,QAAQ,KAAK,EAAE;AACtD,UAAM,OAAO,cAAc,QAAQ,YAAY,GAAG,KAAK,IAAI,aAAa;AACxE,QAAI,CAAC,KAAM,QAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,eAAe,CAAC;AAChE,IAAC,IAAY,OAAO;AACpB,SAAK;AAAA,EACP;AACF;AAEO,SAAS,WAAW,OAAyC;AAClE,QAAM,IAAiB,CAAC;AACxB,MAAI,OAAO,MAAM,YAAY,SAAU,GAAE,UAAU,MAAM;AACzD,MAAI,OAAO,MAAM,MAAM,SAAU,GAAE,IAAI,MAAM;AAC7C,MAAI,MAAM,SAAS,OAAW,GAAE,OAAO,OAAO,MAAM,IAAI;AACxD,MAAI,MAAM,OAAO,OAAW,GAAE,KAAK,OAAO,MAAM,EAAE;AAClD,MAAI,MAAM,UAAU,OAAW,GAAE,QAAQ,OAAO,MAAM,KAAK;AAC3D,MAAI,MAAM,WAAW,OAAW,GAAE,SAAS,OAAO,MAAM,MAAM;AAC9D,QAAM,YAAY,MAAM,UAAU,SAAY,CAAC,IAAI,MAAM,QAAQ,MAAM,KAAK,IAAI,MAAM,QAAQ,CAAC,MAAM,KAAK;AAC1G,IAAE,SAAS,UACR,IAAI,CAAC,MAAc;AAAE,UAAM,IAAI,EAAE,QAAQ,GAAG;AAAG,WAAO,IAAI,IAAI,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,GAAG,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE;AAAA,EAAG,CAAC,EACrH,OAAO,OAAO;AACjB,SAAO;AACT;AAEO,SAAS,oBAAoB,KAAoB;AACtD,QAAM,QAAQ,eAAe,GAAG;AAChC,MAAI,IAAI,aAAa,OAAO,CAAC,KAAK,QAAQ;AACxC,UAAM,OAAO,IAAI,IAAI,MAAM;AAC3B,UAAM,OAAO,UAAU,KAAK,IAAI,WAAW,IAAI,KAAY,CAAC;AAC5D,QAAI,KAAK,EAAE,KAAK,CAAC;AAAA,EACnB,CAAC;AACD,MAAI,IAAI,eAAe,OAAO,CAAC,KAAK,QAAQ;AAC1C,UAAM,OAAO,IAAI,IAAI,MAAM;AAC3B,UAAM,UAAU,OAAO,IAAI,MAAM,YAAY,WAAW,IAAI,MAAM,UAAU;AAC5E,QAAI,KAAK,YAAY,KAAK,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,EAC5C,CAAC;AACH;;;AE5CA,SAAS,aAAa,uBAAuB;AAItC,SAAS,iBAAiB,cAAc,IAAI,WAAW,IAAI,KAAwB;AACxF,QAAM,OAAO,oBAAI,IAA8C;AAC/D,SAAO,CAAC,KAAK,KAAK,SAAS;AACzB,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,KAAK,IAAI,MAAM;AACrB,UAAM,MAAM,KAAK,IAAI,EAAE;AACvB,QAAI,CAAC,OAAO,MAAM,IAAI,OAAO;AAAE,WAAK,IAAI,IAAI,EAAE,OAAO,GAAG,OAAO,MAAM,SAAS,CAAC;AAAG,aAAO,KAAK;AAAA,IAAG;AACjG,QAAI,SAAS;AACb,QAAI,IAAI,QAAQ,YAAa,QAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,oBAAoB,CAAC;AACvF,SAAK;AAAA,EACP;AACF;AAEO,SAAS,mBAAmB,KAAoB;AACrD,QAAM,UAAU,iBAAiB;AACjC,MAAI,KAAK,cAAc,SAAS,CAAC,KAAK,QAAQ;AAC5C,UAAM,OAAO,IAAI,IAAI,MAAM;AAC3B,UAAM,EAAE,UAAU,SAAS,IAAI,IAAI,QAAQ,CAAC;AAC5C,QAAI,OAAO,aAAa,YAAY,OAAO,aAAa;AACtD,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iCAAiC,CAAC;AACzE,QAAI,CAAC,WAAW,KAAK,IAAI,UAAU,QAAQ;AACzC,aAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,sBAAsB,CAAC;AAC9D,UAAM,SAAS,gBAAgB,cAAc,YAAY,UAAU,KAAK,IAAI,aAAa,GAAG;AAAA,MAC1F,UAAU;AAAA,MAAM,UAAU;AAAA,MAAU,MAAM;AAAA,MAAK,QAAQ,KAAK,KAAK,KAAK;AAAA,IACxE,CAAC;AACD,QAAI,UAAU,cAAc,MAAM;AAClC,QAAI,KAAK,EAAE,IAAI,MAAM,SAAS,CAAC;AAAA,EACjC,CAAC;AAED,MAAI,KAAK,eAAe,CAAC,MAAM,QAAQ;AACrC,QAAI,UAAU,cAAc,gBAAgB,cAAc,IAAI,EAAE,UAAU,MAAM,MAAM,KAAK,QAAQ,EAAE,CAAC,CAAC;AACvG,QAAI,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,EACvB,CAAC;AACH;;;ALpBO,SAAS,UAAU,MAAwB;AAChD,QAAM,MAAM,QAAQ;AACpB,MAAI,IAAI,eAAe,IAAI;AAC3B,MAAI,IAAI,QAAQ,IAAI;AAEpB,MAAI,IAAI,eAAe,CAAC,MAAM,QAAQ,IAAI,KAAK,EAAE,IAAI,KAAK,CAAC,CAAC;AAG5D,MAAI,IAAI,WAAW,QAAQ,KAAK,EAAE,OAAO,MAAM,CAAC,CAAC;AACjD,MAAI,IAAI,QAAQ,QAAQ,KAAK,EAAE,OAAO,MAAM,CAAC,CAAC;AAE9C,iBAAe,GAAG;AAClB,sBAAoB,GAAG;AACvB,qBAAmB,GAAG;AAMtB,QAAM,OAAOC,SAAQ,cAAc,YAAY,GAAG,CAAC;AACnD,QAAM,QAAQ,CAACC,MAAK,MAAM,IAAI,GAAGA,MAAK,MAAM,MAAM,IAAI,GAAGA,MAAK,QAAQ,IAAI,GAAG,QAAQ,IAAI,CAAC,EACvF,KAAK,CAAC,MAAM,WAAWA,MAAK,GAAG,WAAW,CAAC,CAAC;AAC/C,MAAI,MAAO,KAAI,IAAI,QAAQ,OAAO,KAAK,CAAC;AAExC,SAAO;AACT;;;AM1CA,SAAS,SAASC,oBAAmB;AACrC,SAAS,uBAAkC;AAKpC,SAAS,gBACd,QACA,MACiB;AACjB,QAAM,MAAM,IAAI,gBAAgB,EAAE,QAAQ,MAAM,MAAM,CAAC;AAEvD,MAAI,GAAG,cAAc,CAAC,IAAI,QAAQ;AAChC,UAAM,UAAUC,aAAY,IAAI,QAAQ,UAAU,EAAE;AACpD,UAAM,OAAO,cAAc,QAAQ,YAAY,GAAG,KAAK,IAAI,aAAa;AACxE,QAAI,CAAC,MAAM;AAAE,SAAG,MAAM,MAAM,cAAc;AAAG;AAAA,IAAQ;AAErD,IAAC,GAAW,UAAU;AACtB,OAAG,GAAG,QAAQ,MAAM;AAAE,MAAC,GAAW,UAAU;AAAA,IAAM,CAAC;AAEnD,OAAG,KAAK,KAAK,UAAU,EAAE,MAAM,UAAU,MAAM,KAAK,OAAO,SAAS,EAAE,CAAC,CAAC;AACxE,SAAK,QAAQ,IAAI,EAAE;AACnB,OAAG,GAAG,SAAS,MAAM,KAAK,QAAQ,OAAO,EAAE,CAAC;AAC5C,OAAG,GAAG,SAAS,MAAM,KAAK,QAAQ,OAAO,EAAE,CAAC;AAAA,EAC9C,CAAC;AAED,QAAM,WAAW,YAAY,MAAM;AACjC,eAAW,MAAM,IAAI,SAAS;AAC5B,UAAK,GAAW,YAAY,OAAO;AAAE,WAAG,UAAU;AAAG;AAAA,MAAU;AAC/D,MAAC,GAAW,UAAU;AACtB,UAAI;AAAE,WAAG,KAAK;AAAA,MAAG,QAAQ;AAAA,MAAC;AAAA,IAC5B;AAAA,EACF,GAAG,GAAM;AACT,WAAS,MAAM;AACf,MAAI,GAAG,SAAS,MAAM,cAAc,QAAQ,CAAC;AAE7C,SAAO;AACT;;;AX5BA,eAAsB,MAAM,aAAa,kBAAkB,GAA0D;AACnH,QAAM,MAAM,WAAW,UAAU;AACjC,QAAM,KAAK,OAAO,cAAc,YAAY,GAAG,CAAC;AAChD,QAAM,SAAS,IAAI,WAAW,IAAI,UAAU;AAC5C,QAAM,YAAY,oBAAI,IAAe;AACrC,QAAM,YAAY,CAAC,MAAiB;AAClC,UAAM,MAAM,KAAK,UAAU,EAAE,MAAM,OAAO,MAAM,EAAE,CAAC;AACnD,eAAW,KAAK,WAAW;AAAE,UAAI;AAAE,YAAI,EAAE,eAAe,EAAG,GAAE,KAAK,GAAG;AAAA,MAAG,QAAQ;AAAA,MAAC;AAAA,IAAE;AAAA,EACrF;AAEA,QAAM,MAAM,UAAU,EAAE,IAAI,QAAQ,KAAK,UAAU,CAAC;AACpD,QAAM,SAAS,aAAa,GAAG;AAC/B,kBAAgB,QAAQ,EAAE,QAAQ,KAAK,SAAS,UAAU,CAAC;AAE3D,QAAM,iBAAiB;AAAA,IACrB,MAAM,aAAa,IAAI,EAAE,eAAe,IAAI,eAAe,WAAW,IAAI,UAAU,CAAC;AAAA,IACrF;AAAA,EACF;AACA,iBAAe,MAAM;AAErB,QAAM,IAAI,QAAc,CAACC,aAAY,OAAO,OAAO,IAAI,MAAM,IAAI,MAAMA,QAAO,CAAC;AAC/E,QAAM,OAAO,OAAO,QAAQ;AAC5B,QAAM,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,OAAO,IAAI;AAChE,UAAQ,IAAI,kCAAkC,IAAI,IAAI,IAAI,IAAI,EAAE;AAEhE,SAAO;AAAA,IACL;AAAA,IACA,OAAO,MAAM,IAAI,QAAc,CAACA,aAAY;AAC1C,oBAAc,cAAc;AAC5B,iBAAW,KAAK,WAAW;AAAE,YAAI;AAAE,YAAE,MAAM;AAAA,QAAG,QAAQ;AAAA,QAAC;AAAA,MAAE;AACzD,aAAO,MAAM,MAAM;AAAE,WAAG,MAAM;AAAG,QAAAA,SAAQ;AAAA,MAAG,CAAC;AAC7C,aAAO,uBAAuB;AAAA,IAChC,CAAC;AAAA,EACH;AACF;",
|
|
6
6
|
"names": ["dirname", "join", "dirname", "join", "parseCookie", "parseCookie", "resolve"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tinylogs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A tiny self-hosted central log receiver + live dashboard.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nikita Medvedev",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"packages/client"
|
|
32
32
|
],
|
|
33
33
|
"engines": {
|
|
34
|
-
"node": ">=
|
|
34
|
+
"node": ">=22.13"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"dist",
|
|
@@ -52,7 +52,6 @@
|
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"bcryptjs": "^2.4.3",
|
|
55
|
-
"better-sqlite3": "^11.3.0",
|
|
56
55
|
"cookie": "^0.7.2",
|
|
57
56
|
"express": "^4.19.2",
|
|
58
57
|
"ws": "^8.18.0"
|
|
@@ -60,10 +59,9 @@
|
|
|
60
59
|
"devDependencies": {
|
|
61
60
|
"@testing-library/svelte": "^5.2.0",
|
|
62
61
|
"@types/bcryptjs": "^2.4.6",
|
|
63
|
-
"@types/better-sqlite3": "^7.6.11",
|
|
64
62
|
"@types/cookie": "^0.6.0",
|
|
65
63
|
"@types/express": "^4.17.21",
|
|
66
|
-
"@types/node": "^
|
|
64
|
+
"@types/node": "^22.13.0",
|
|
67
65
|
"@types/supertest": "^6.0.2",
|
|
68
66
|
"@types/ws": "^8.5.12",
|
|
69
67
|
"esbuild": "^0.25.0",
|