feat(storage): add resilient SQLite foundation
This commit is contained in:
@@ -5,13 +5,16 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./src/index.ts",
|
"exports": "./src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run --root ../.. apps/api/src/app.test.ts apps/api/src/start.test.ts",
|
"test": "vitest run --root ../.. apps/api/src/app.test.ts apps/api/src/start.test.ts apps/api/src/infrastructure/database/database.test.ts",
|
||||||
"typecheck": "tsc -p tsconfig.json"
|
"typecheck": "tsc -p tsconfig.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"better-sqlite3": "12.11.1",
|
||||||
|
"drizzle-orm": "0.45.2",
|
||||||
"fastify": "5.10.0"
|
"fastify": "5.10.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/better-sqlite3": "7.6.13",
|
||||||
"@types/node": "24.13.3"
|
"@types/node": "24.13.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,3 +8,7 @@ export {
|
|||||||
export type { BuildAppOptions, ListenEnvironment, ListenOptions, ReadinessResult } from './app.js';
|
export type { BuildAppOptions, ListenEnvironment, ListenOptions, ReadinessResult } from './app.js';
|
||||||
export { startApi } from './start.js';
|
export { startApi } from './start.js';
|
||||||
export type { StartApiOptions } from './start.js';
|
export type { StartApiOptions } from './start.js';
|
||||||
|
export { backupDatabase, restoreDatabase } from './infrastructure/database/backup.js';
|
||||||
|
export { openDatabase } from './infrastructure/database/database.js';
|
||||||
|
export { MIGRATIONS, migrateDatabase } from './infrastructure/database/migrations.js';
|
||||||
|
export * as databaseSchema from './infrastructure/database/schema.js';
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import Database from 'better-sqlite3';
|
||||||
|
import { chmod, copyFile, lstat, mkdir, open, rename, rm, stat } from 'node:fs/promises';
|
||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import { dirname, resolve } from 'node:path';
|
||||||
|
import { beginRestore, getManagedDatabaseIdentity, type SqliteDatabase } from './database.js';
|
||||||
|
|
||||||
|
async function assertRegularFile(path: string, label: string): Promise<void> {
|
||||||
|
const details = await lstat(path);
|
||||||
|
if (details.isSymbolicLink()) throw new Error(`${label} must not be a symbolic link`);
|
||||||
|
if (!details.isFile()) throw new Error(`${label} must be a regular file`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertDistinctFiles(databasePath: string, backupPath: string): Promise<void> {
|
||||||
|
if (resolve(databasePath) === resolve(backupPath))
|
||||||
|
throw new Error('Live and backup paths are the same');
|
||||||
|
await assertRegularFile(backupPath, 'Backup');
|
||||||
|
try {
|
||||||
|
await assertRegularFile(databasePath, 'Live database');
|
||||||
|
const [live, backup] = await Promise.all([stat(databasePath), stat(backupPath)]);
|
||||||
|
if (live.dev === backup.dev && live.ino === backup.ino)
|
||||||
|
throw new Error('Live and backup paths are aliases of the same file');
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reservePrivateFile(path: string): Promise<void> {
|
||||||
|
const handle = await open(path, 'wx', 0o600);
|
||||||
|
await handle.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertValidDatabase(path: string): Promise<void> {
|
||||||
|
let database: Database.Database | undefined;
|
||||||
|
try {
|
||||||
|
database = new Database(path, { fileMustExist: true, readonly: true });
|
||||||
|
const integrity = database.pragma('integrity_check', { simple: true });
|
||||||
|
if (integrity !== 'ok') throw new Error(`Backup integrity check failed: ${String(integrity)}`);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Backup is not a valid SQLite database', { cause: error });
|
||||||
|
} finally {
|
||||||
|
database?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncFile(path: string): Promise<void> {
|
||||||
|
const handle = await open(path, 'r');
|
||||||
|
try {
|
||||||
|
await handle.sync();
|
||||||
|
} finally {
|
||||||
|
await handle.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncDirectory(path: string): Promise<void> {
|
||||||
|
const handle = await open(path, 'r');
|
||||||
|
try {
|
||||||
|
await handle.sync();
|
||||||
|
} finally {
|
||||||
|
await handle.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function backupDatabase(database: SqliteDatabase, backupPath: string): Promise<void> {
|
||||||
|
const source = getManagedDatabaseIdentity(database);
|
||||||
|
const directory = dirname(backupPath);
|
||||||
|
await mkdir(directory, { recursive: true });
|
||||||
|
try {
|
||||||
|
const destination = await stat(backupPath);
|
||||||
|
if (`${destination.dev}:${destination.ino}` === source.deviceInode) {
|
||||||
|
throw new Error('Backup destination aliases the live database identity');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
||||||
|
}
|
||||||
|
const temporaryPath = `${backupPath}.${randomUUID()}.tmp`;
|
||||||
|
try {
|
||||||
|
await reservePrivateFile(temporaryPath);
|
||||||
|
await database.backup(temporaryPath);
|
||||||
|
await chmod(temporaryPath, 0o600);
|
||||||
|
await assertValidDatabase(temporaryPath);
|
||||||
|
await syncFile(temporaryPath);
|
||||||
|
await rename(temporaryPath, backupPath);
|
||||||
|
await chmod(backupPath, 0o600);
|
||||||
|
await syncDirectory(directory);
|
||||||
|
} finally {
|
||||||
|
await rm(temporaryPath, { force: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function restoreDatabase(databasePath: string, backupPath: string): Promise<void> {
|
||||||
|
const endRestore = beginRestore(databasePath);
|
||||||
|
const directory = dirname(databasePath);
|
||||||
|
const temporaryPath = `${databasePath}.${randomUUID()}.restore`;
|
||||||
|
try {
|
||||||
|
await assertDistinctFiles(databasePath, backupPath);
|
||||||
|
await assertValidDatabase(backupPath);
|
||||||
|
await mkdir(directory, { recursive: true });
|
||||||
|
await reservePrivateFile(temporaryPath);
|
||||||
|
await copyFile(backupPath, temporaryPath);
|
||||||
|
await chmod(temporaryPath, 0o600);
|
||||||
|
await assertValidDatabase(temporaryPath);
|
||||||
|
await syncFile(temporaryPath);
|
||||||
|
|
||||||
|
// Atomic replacement comes first: any failure before this point leaves live and sidecars intact.
|
||||||
|
await rename(temporaryPath, databasePath);
|
||||||
|
await chmod(databasePath, 0o600);
|
||||||
|
await syncDirectory(directory);
|
||||||
|
// No managed connection can exist under the exclusive gate; stale sidecars are now safe to remove.
|
||||||
|
await rm(`${databasePath}-wal`, { force: true });
|
||||||
|
await rm(`${databasePath}-shm`, { force: true });
|
||||||
|
await syncDirectory(directory);
|
||||||
|
} finally {
|
||||||
|
await rm(temporaryPath, { force: true });
|
||||||
|
endRestore();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
import { link, lstat, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { afterEach, describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { backupDatabase, restoreDatabase } from './backup.js';
|
||||||
|
import { openDatabase } from './database.js';
|
||||||
|
import { MIGRATIONS, migrateDatabase } from './migrations.js';
|
||||||
|
|
||||||
|
const directories: string[] = [];
|
||||||
|
|
||||||
|
async function temporaryDirectory(): Promise<string> {
|
||||||
|
const directory = await mkdtemp(join(tmpdir(), 'multi-simadmin-database-'));
|
||||||
|
directories.push(directory);
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await Promise.all(
|
||||||
|
directories.splice(0).map((directory) => rm(directory, { force: true, recursive: true })),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('database migrations', () => {
|
||||||
|
it('rejects duplicate, unordered, backfilled, and drifted migration catalogs', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const database = openDatabase(join(directory, 'app.sqlite'));
|
||||||
|
const one = { id: 1, name: 'one', statements: ['CREATE TABLE one (id TEXT)'] };
|
||||||
|
migrateDatabase(database, [one]);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
migrateDatabase(database, [{ ...one, statements: ['CREATE TABLE changed (id TEXT)'] }]),
|
||||||
|
).toThrow(/checksum|drift/i);
|
||||||
|
expect(() => migrateDatabase(database, [one, { ...one, name: 'two' }])).toThrow(
|
||||||
|
/duplicate.*id/i,
|
||||||
|
);
|
||||||
|
expect(() => migrateDatabase(database, [one, { id: 2, name: 'one', statements: [] }])).toThrow(
|
||||||
|
/duplicate.*name/i,
|
||||||
|
);
|
||||||
|
expect(() => migrateDatabase(database, [{ id: 2, name: 'two', statements: [] }, one])).toThrow(
|
||||||
|
/increasing/i,
|
||||||
|
);
|
||||||
|
migrateDatabase(database, [{ id: 2, name: 'two', statements: [] }]);
|
||||||
|
expect(() => migrateDatabase(database, [{ id: 0, name: 'backfill', statements: [] }])).toThrow(
|
||||||
|
/backfill/i,
|
||||||
|
);
|
||||||
|
database.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates all business tables, migration metadata, indexes, foreign keys, and UTC timestamp fields', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const database = openDatabase(join(directory, 'app.sqlite'));
|
||||||
|
migrateDatabase(database);
|
||||||
|
|
||||||
|
const tables = database
|
||||||
|
.prepare(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
.map((row) => (row as { name: string }).name);
|
||||||
|
expect(tables).toEqual([
|
||||||
|
'app_settings',
|
||||||
|
'audit_events',
|
||||||
|
'capabilities',
|
||||||
|
'instance_tags',
|
||||||
|
'instances',
|
||||||
|
'job_items',
|
||||||
|
'jobs',
|
||||||
|
'schema_migrations',
|
||||||
|
'secret_references',
|
||||||
|
'status_snapshots',
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(database.pragma('foreign_keys', { simple: true })).toBe(1);
|
||||||
|
const instanceColumns = database.pragma('table_info(instances)') as Array<{
|
||||||
|
name: string;
|
||||||
|
notnull: number;
|
||||||
|
}>;
|
||||||
|
expect(instanceColumns).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({ name: 'config_revision', notnull: 1 }),
|
||||||
|
expect.objectContaining({ name: 'created_at', notnull: 1 }),
|
||||||
|
expect.objectContaining({ name: 'updated_at', notnull: 1 }),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
const foreignKeys = database.pragma('foreign_key_list(job_items)') as Array<{
|
||||||
|
from: string;
|
||||||
|
on_delete: string;
|
||||||
|
table: string;
|
||||||
|
}>;
|
||||||
|
expect(foreignKeys).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({ from: 'job_id', on_delete: 'CASCADE', table: 'jobs' }),
|
||||||
|
expect.objectContaining({ from: 'instance_id', on_delete: 'RESTRICT', table: 'instances' }),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
const indexes = database
|
||||||
|
.prepare(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type = 'index' AND name LIKE 'idx_%' ORDER BY name",
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
.map((row) => (row as { name: string }).name);
|
||||||
|
expect(indexes).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
'idx_audit_events_created_at',
|
||||||
|
'idx_jobs_status_created_at',
|
||||||
|
'idx_status_snapshots_instance_observed_at',
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
database.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('replays without resetting persisted data', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const database = openDatabase(join(directory, 'app.sqlite'));
|
||||||
|
migrateDatabase(database);
|
||||||
|
database
|
||||||
|
.prepare(
|
||||||
|
'INSERT INTO instances (id, name, base_url, config_revision, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)',
|
||||||
|
)
|
||||||
|
.run(
|
||||||
|
'one',
|
||||||
|
'One',
|
||||||
|
'http://example.invalid',
|
||||||
|
7,
|
||||||
|
'2026-01-01T00:00:00.000Z',
|
||||||
|
'2026-01-01T00:00:00.000Z',
|
||||||
|
);
|
||||||
|
|
||||||
|
migrateDatabase(database);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
database.prepare('SELECT name, config_revision FROM instances WHERE id = ?').get('one'),
|
||||||
|
).toEqual({
|
||||||
|
config_revision: 7,
|
||||||
|
name: 'One',
|
||||||
|
});
|
||||||
|
expect(database.prepare('SELECT COUNT(*) AS count FROM schema_migrations').get()).toEqual({
|
||||||
|
count: MIGRATIONS.length,
|
||||||
|
});
|
||||||
|
database.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rolls back the entire migration when a statement fails', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const database = openDatabase(join(directory, 'app.sqlite'));
|
||||||
|
expect(() =>
|
||||||
|
migrateDatabase(database, [
|
||||||
|
{
|
||||||
|
id: 999,
|
||||||
|
name: 'deliberately-broken',
|
||||||
|
statements: ['CREATE TABLE should_rollback (id TEXT PRIMARY KEY)', 'THIS IS NOT SQL'],
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
).toThrow();
|
||||||
|
expect(
|
||||||
|
database.prepare("SELECT name FROM sqlite_master WHERE name = 'should_rollback'").get(),
|
||||||
|
).toBeUndefined();
|
||||||
|
expect(
|
||||||
|
database.prepare("SELECT name FROM sqlite_master WHERE name = 'schema_migrations'").get(),
|
||||||
|
).toBeUndefined();
|
||||||
|
database.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('database backup and restore', () => {
|
||||||
|
it('refuses restore while a managed live connection is open', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const databasePath = join(directory, 'app.sqlite');
|
||||||
|
const backupPath = join(directory, 'backup.sqlite');
|
||||||
|
const database = openDatabase(databasePath);
|
||||||
|
migrateDatabase(database);
|
||||||
|
await backupDatabase(database, backupPath);
|
||||||
|
await expect(restoreDatabase(databasePath, backupPath)).rejects.toThrow(/open|connection/i);
|
||||||
|
database.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('refuses restore through hardlink and symlinked-parent aliases while a managed connection is open', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const realDirectory = join(directory, 'real');
|
||||||
|
const aliasDirectory = join(directory, 'alias');
|
||||||
|
await mkdir(realDirectory);
|
||||||
|
await symlink(realDirectory, aliasDirectory);
|
||||||
|
const databasePath = join(realDirectory, 'app.sqlite');
|
||||||
|
const hardlinkPath = join(directory, 'hardlink.sqlite');
|
||||||
|
const backupPath = join(directory, 'backup.sqlite');
|
||||||
|
const database = openDatabase(databasePath);
|
||||||
|
migrateDatabase(database);
|
||||||
|
await backupDatabase(database, backupPath);
|
||||||
|
await link(databasePath, hardlinkPath);
|
||||||
|
|
||||||
|
await expect(restoreDatabase(hardlinkPath, backupPath)).rejects.toThrow(
|
||||||
|
/open|connection|identity/i,
|
||||||
|
);
|
||||||
|
await expect(restoreDatabase(join(aliasDirectory, 'app.sqlite'), backupPath)).rejects.toThrow(
|
||||||
|
/open|connection|identity/i,
|
||||||
|
);
|
||||||
|
database.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects backup destinations that alias the managed live database', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const databasePath = join(directory, 'app.sqlite');
|
||||||
|
const hardlinkPath = join(directory, 'hardlink.sqlite');
|
||||||
|
const database = openDatabase(databasePath);
|
||||||
|
migrateDatabase(database);
|
||||||
|
await link(databasePath, hardlinkPath);
|
||||||
|
|
||||||
|
await expect(backupDatabase(database, hardlinkPath)).rejects.toThrow(/same|alias|identity/i);
|
||||||
|
database.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates backups with owner-only permissions', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const database = openDatabase(join(directory, 'app.sqlite'));
|
||||||
|
migrateDatabase(database);
|
||||||
|
const backupPath = join(directory, 'backup.sqlite');
|
||||||
|
await backupDatabase(database, backupPath);
|
||||||
|
expect((await lstat(backupPath)).mode & 0o777).toBe(0o600);
|
||||||
|
database.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects same-path, symlink, and hardlink restore aliases', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const databasePath = join(directory, 'app.sqlite');
|
||||||
|
const backupPath = join(directory, 'backup.sqlite');
|
||||||
|
const database = openDatabase(databasePath);
|
||||||
|
migrateDatabase(database);
|
||||||
|
await backupDatabase(database, backupPath);
|
||||||
|
database.close();
|
||||||
|
await expect(restoreDatabase(databasePath, databasePath)).rejects.toThrow(/same|alias/i);
|
||||||
|
const symlinkPath = join(directory, 'backup-link.sqlite');
|
||||||
|
await symlink(backupPath, symlinkPath);
|
||||||
|
await expect(restoreDatabase(databasePath, symlinkPath)).rejects.toThrow(/symbolic|symlink/i);
|
||||||
|
const hardlinkPath = join(directory, 'live-hardlink.sqlite');
|
||||||
|
await link(databasePath, hardlinkPath);
|
||||||
|
await expect(restoreDatabase(databasePath, hardlinkPath)).rejects.toThrow(/same|alias/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('backs up an active WAL database, then atomically restores its earlier state', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const databasePath = join(directory, 'app.sqlite');
|
||||||
|
const backupPath = join(directory, 'backup.sqlite');
|
||||||
|
const database = openDatabase(databasePath);
|
||||||
|
migrateDatabase(database);
|
||||||
|
database
|
||||||
|
.prepare(
|
||||||
|
'INSERT INTO app_settings (key, value_json, created_at, updated_at) VALUES (?, ?, ?, ?)',
|
||||||
|
)
|
||||||
|
.run('colour', '"blue"', '2026-01-01T00:00:00.000Z', '2026-01-01T00:00:00.000Z');
|
||||||
|
await backupDatabase(database, backupPath);
|
||||||
|
database.prepare('UPDATE app_settings SET value_json = ? WHERE key = ?').run('"red"', 'colour');
|
||||||
|
database.close();
|
||||||
|
|
||||||
|
await restoreDatabase(databasePath, backupPath);
|
||||||
|
|
||||||
|
const restored = openDatabase(databasePath);
|
||||||
|
expect(
|
||||||
|
restored.prepare('SELECT value_json FROM app_settings WHERE key = ?').get('colour'),
|
||||||
|
).toEqual({ value_json: '"blue"' });
|
||||||
|
expect(restored.pragma('integrity_check', { simple: true })).toBe('ok');
|
||||||
|
restored.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects a corrupt backup without changing the live database', async () => {
|
||||||
|
const directory = await temporaryDirectory();
|
||||||
|
const databasePath = join(directory, 'app.sqlite');
|
||||||
|
const corruptPath = join(directory, 'corrupt.sqlite');
|
||||||
|
const database = openDatabase(databasePath);
|
||||||
|
migrateDatabase(database);
|
||||||
|
database
|
||||||
|
.prepare(
|
||||||
|
'INSERT INTO app_settings (key, value_json, created_at, updated_at) VALUES (?, ?, ?, ?)',
|
||||||
|
)
|
||||||
|
.run('safe', 'true', '2026-01-01T00:00:00.000Z', '2026-01-01T00:00:00.000Z');
|
||||||
|
database.close();
|
||||||
|
const before = await readFile(databasePath);
|
||||||
|
await writeFile(corruptPath, 'not a sqlite database');
|
||||||
|
|
||||||
|
await expect(restoreDatabase(databasePath, corruptPath)).rejects.toThrow(
|
||||||
|
/backup|integrity|database/i,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await readFile(databasePath)).toEqual(before);
|
||||||
|
const unchanged = openDatabase(databasePath);
|
||||||
|
expect(
|
||||||
|
unchanged.prepare('SELECT value_json FROM app_settings WHERE key = ?').get('safe'),
|
||||||
|
).toEqual({ value_json: 'true' });
|
||||||
|
unchanged.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import Database from 'better-sqlite3';
|
||||||
|
import { mkdirSync, realpathSync, statSync } from 'node:fs';
|
||||||
|
import { basename, dirname, join, resolve } from 'node:path';
|
||||||
|
|
||||||
|
export type SqliteDatabase = Database.Database;
|
||||||
|
|
||||||
|
export interface DatabaseIdentity {
|
||||||
|
readonly canonicalPath: string;
|
||||||
|
readonly deviceInode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const openPaths = new Map<string, number>();
|
||||||
|
const openIdentities = new Map<string, number>();
|
||||||
|
const restorePaths = new Set<string>();
|
||||||
|
const restoreIdentities = new Set<string>();
|
||||||
|
const managedIdentities = new WeakMap<SqliteDatabase, DatabaseIdentity>();
|
||||||
|
|
||||||
|
function increment(map: Map<string, number>, key: string): void {
|
||||||
|
map.set(key, (map.get(key) ?? 0) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrement(map: Map<string, number>, key: string): void {
|
||||||
|
const remaining = (map.get(key) ?? 1) - 1;
|
||||||
|
if (remaining === 0) map.delete(key);
|
||||||
|
else map.set(key, remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalPath(path: string): string {
|
||||||
|
const absolute = resolve(path);
|
||||||
|
return join(realpathSync(dirname(absolute)), basename(absolute));
|
||||||
|
}
|
||||||
|
|
||||||
|
function identityForExistingPath(path: string): DatabaseIdentity {
|
||||||
|
const canonical = realpathSync(path);
|
||||||
|
const details = statSync(canonical);
|
||||||
|
return {
|
||||||
|
canonicalPath: canonical,
|
||||||
|
deviceInode: `${details.dev}:${details.ino}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getManagedDatabaseIdentity(database: SqliteDatabase): DatabaseIdentity {
|
||||||
|
const identity = managedIdentities.get(database);
|
||||||
|
if (!identity) throw new Error('Database connection is not managed by openDatabase');
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasOpenDatabase(path: string): boolean {
|
||||||
|
const canonical = canonicalPath(path);
|
||||||
|
if ((openPaths.get(canonical) ?? 0) > 0) return true;
|
||||||
|
try {
|
||||||
|
return (openIdentities.get(identityForExistingPath(path).deviceInode) ?? 0) > 0;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function beginRestore(path: string): () => void {
|
||||||
|
const identity = identityForExistingPath(path);
|
||||||
|
if (restorePaths.has(identity.canonicalPath) || restoreIdentities.has(identity.deviceInode)) {
|
||||||
|
throw new Error('A restore is already in progress for this database identity');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(openPaths.get(identity.canonicalPath) ?? 0) > 0 ||
|
||||||
|
(openIdentities.get(identity.deviceInode) ?? 0) > 0
|
||||||
|
) {
|
||||||
|
throw new Error('Cannot restore database while a managed connection is open for this identity');
|
||||||
|
}
|
||||||
|
restorePaths.add(identity.canonicalPath);
|
||||||
|
restoreIdentities.add(identity.deviceInode);
|
||||||
|
return () => {
|
||||||
|
restorePaths.delete(identity.canonicalPath);
|
||||||
|
restoreIdentities.delete(identity.deviceInode);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openDatabase(path: string): SqliteDatabase {
|
||||||
|
const absolute = resolve(path);
|
||||||
|
mkdirSync(dirname(absolute), { recursive: true });
|
||||||
|
const preOpenPath = canonicalPath(absolute);
|
||||||
|
if (restorePaths.has(preOpenPath))
|
||||||
|
throw new Error('Cannot open database while restore is in progress');
|
||||||
|
|
||||||
|
const database = new Database(absolute);
|
||||||
|
const identity = identityForExistingPath(absolute);
|
||||||
|
if (restorePaths.has(identity.canonicalPath) || restoreIdentities.has(identity.deviceInode)) {
|
||||||
|
database.close();
|
||||||
|
throw new Error('Cannot open database while restore is in progress for this identity');
|
||||||
|
}
|
||||||
|
|
||||||
|
increment(openPaths, identity.canonicalPath);
|
||||||
|
increment(openIdentities, identity.deviceInode);
|
||||||
|
managedIdentities.set(database, identity);
|
||||||
|
const close = database.close.bind(database);
|
||||||
|
let closed = false;
|
||||||
|
database.close = () => {
|
||||||
|
if (!closed) {
|
||||||
|
closed = true;
|
||||||
|
decrement(openPaths, identity.canonicalPath);
|
||||||
|
decrement(openIdentities, identity.deviceInode);
|
||||||
|
managedIdentities.delete(database);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
return database;
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
database.pragma('journal_mode = WAL');
|
||||||
|
database.pragma('foreign_keys = ON');
|
||||||
|
database.pragma('busy_timeout = 5000');
|
||||||
|
return database;
|
||||||
|
} catch (error) {
|
||||||
|
database.close();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
import type { SqliteDatabase } from './database.js';
|
||||||
|
import { createHash } from 'node:crypto';
|
||||||
|
|
||||||
|
export interface Migration {
|
||||||
|
readonly id: number;
|
||||||
|
readonly name: string;
|
||||||
|
readonly statements: readonly string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MIGRATIONS: readonly Migration[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'initial-control-plane-schema',
|
||||||
|
statements: [
|
||||||
|
`CREATE TABLE instances (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
base_url TEXT NOT NULL UNIQUE,
|
||||||
|
auth_mode TEXT NOT NULL DEFAULT 'password',
|
||||||
|
enabled INTEGER NOT NULL DEFAULT 1 CHECK (enabled IN (0, 1)),
|
||||||
|
config_revision INTEGER NOT NULL DEFAULT 1 CHECK (config_revision > 0),
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE instance_tags (
|
||||||
|
instance_id TEXT NOT NULL REFERENCES instances(id) ON DELETE CASCADE,
|
||||||
|
tag TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (instance_id, tag)
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE capabilities (
|
||||||
|
instance_id TEXT NOT NULL REFERENCES instances(id) ON DELETE CASCADE,
|
||||||
|
operation_id TEXT NOT NULL,
|
||||||
|
state TEXT NOT NULL CHECK (state IN ('supported', 'unsupported', 'auth-required', 'degraded', 'unknown')),
|
||||||
|
upstream_version TEXT,
|
||||||
|
detail_code TEXT,
|
||||||
|
observed_at TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (instance_id, operation_id)
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE status_snapshots (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
instance_id TEXT NOT NULL REFERENCES instances(id) ON DELETE CASCADE,
|
||||||
|
category TEXT NOT NULL,
|
||||||
|
state TEXT NOT NULL CHECK (state IN ('fresh', 'stale', 'expired', 'unknown')),
|
||||||
|
payload_json TEXT NOT NULL,
|
||||||
|
observed_at TEXT NOT NULL,
|
||||||
|
expires_at TEXT,
|
||||||
|
created_at TEXT NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE jobs (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
parent_job_id TEXT REFERENCES jobs(id) ON DELETE RESTRICT,
|
||||||
|
operation_id TEXT NOT NULL,
|
||||||
|
risk_level TEXT NOT NULL CHECK (risk_level IN ('R0', 'R1', 'R2', 'R3')),
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
requested_by TEXT NOT NULL,
|
||||||
|
request_id TEXT NOT NULL,
|
||||||
|
parameters_digest TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
started_at TEXT,
|
||||||
|
finished_at TEXT,
|
||||||
|
updated_at TEXT NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE job_items (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
|
||||||
|
instance_id TEXT NOT NULL REFERENCES instances(id) ON DELETE RESTRICT,
|
||||||
|
attempt_number INTEGER NOT NULL DEFAULT 1 CHECK (attempt_number > 0),
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
result_code TEXT,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
started_at TEXT,
|
||||||
|
finished_at TEXT,
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
UNIQUE (job_id, instance_id, attempt_number)
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE audit_events (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
instance_id TEXT REFERENCES instances(id) ON DELETE SET NULL,
|
||||||
|
job_id TEXT REFERENCES jobs(id) ON DELETE SET NULL,
|
||||||
|
actor TEXT NOT NULL,
|
||||||
|
operation_id TEXT NOT NULL,
|
||||||
|
risk_level TEXT NOT NULL CHECK (risk_level IN ('R0', 'R1', 'R2', 'R3')),
|
||||||
|
request_id TEXT NOT NULL,
|
||||||
|
parameters_summary_json TEXT NOT NULL,
|
||||||
|
body_digest TEXT,
|
||||||
|
result_code TEXT NOT NULL,
|
||||||
|
duration_ms INTEGER NOT NULL CHECK (duration_ms >= 0),
|
||||||
|
created_at TEXT NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE app_settings (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value_json TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE secret_references (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
instance_id TEXT REFERENCES instances(id) ON DELETE CASCADE,
|
||||||
|
purpose TEXT NOT NULL,
|
||||||
|
provider TEXT NOT NULL,
|
||||||
|
external_reference TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
UNIQUE (provider, external_reference)
|
||||||
|
)`,
|
||||||
|
'CREATE INDEX idx_status_snapshots_instance_observed_at ON status_snapshots(instance_id, observed_at DESC)',
|
||||||
|
'CREATE INDEX idx_jobs_status_created_at ON jobs(status, created_at DESC)',
|
||||||
|
'CREATE INDEX idx_audit_events_created_at ON audit_events(created_at DESC)',
|
||||||
|
'CREATE INDEX idx_capabilities_state ON capabilities(state)',
|
||||||
|
'CREATE INDEX idx_job_items_job_status ON job_items(job_id, status)',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const createMigrationsTable = `CREATE TABLE schema_migrations (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
checksum TEXT NOT NULL,
|
||||||
|
applied_at TEXT NOT NULL
|
||||||
|
)`;
|
||||||
|
|
||||||
|
function checksum(migration: Migration): string {
|
||||||
|
return createHash('sha256').update(JSON.stringify(migration.statements)).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCatalog(migrations: readonly Migration[]): void {
|
||||||
|
const ids = new Set<number>();
|
||||||
|
const names = new Set<string>();
|
||||||
|
let previous = -Infinity;
|
||||||
|
for (const migration of migrations) {
|
||||||
|
if (!Number.isSafeInteger(migration.id) || migration.id < 0)
|
||||||
|
throw new Error(`Migration id ${migration.id} is invalid`);
|
||||||
|
if (ids.has(migration.id)) throw new Error(`Duplicate migration id ${migration.id}`);
|
||||||
|
if (names.has(migration.name)) throw new Error(`Duplicate migration name ${migration.name}`);
|
||||||
|
if (migration.id <= previous) throw new Error('Migration ids must be strictly increasing');
|
||||||
|
ids.add(migration.id);
|
||||||
|
names.add(migration.name);
|
||||||
|
previous = migration.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function migrateDatabase(
|
||||||
|
database: SqliteDatabase,
|
||||||
|
migrations: readonly Migration[] = MIGRATIONS,
|
||||||
|
): void {
|
||||||
|
validateCatalog(migrations);
|
||||||
|
database.transaction(() => {
|
||||||
|
database.exec(
|
||||||
|
'CREATE TABLE IF NOT EXISTS schema_migrations (id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, checksum TEXT NOT NULL, applied_at TEXT NOT NULL)',
|
||||||
|
);
|
||||||
|
const maximum = database.prepare('SELECT MAX(id) AS id FROM schema_migrations').get() as {
|
||||||
|
id: number | null;
|
||||||
|
};
|
||||||
|
const applied = database.prepare('SELECT name, checksum FROM schema_migrations WHERE id = ?');
|
||||||
|
const appliedByName = database.prepare('SELECT id FROM schema_migrations WHERE name = ?');
|
||||||
|
const record = database.prepare(
|
||||||
|
'INSERT INTO schema_migrations (id, name, checksum, applied_at) VALUES (?, ?, ?, ?)',
|
||||||
|
);
|
||||||
|
for (const migration of migrations) {
|
||||||
|
const digest = checksum(migration);
|
||||||
|
const existing = applied.get(migration.id) as { name: string; checksum: string } | undefined;
|
||||||
|
if (existing) {
|
||||||
|
if (existing.name !== migration.name)
|
||||||
|
throw new Error(`Migration ${migration.id} name mismatch`);
|
||||||
|
if (existing.checksum !== digest)
|
||||||
|
throw new Error(`Migration ${migration.id} checksum drift detected`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const existingName = appliedByName.get(migration.name) as { id: number } | undefined;
|
||||||
|
if (existingName)
|
||||||
|
throw new Error(
|
||||||
|
`Migration ${migration.name} drift detected: applied as id ${existingName.id}`,
|
||||||
|
);
|
||||||
|
if (maximum.id !== null && migration.id < maximum.id)
|
||||||
|
throw new Error(
|
||||||
|
`Migration ${migration.id} is an unapplied backfill below applied id ${maximum.id}`,
|
||||||
|
);
|
||||||
|
for (const statement of migration.statements) database.exec(statement);
|
||||||
|
record.run(migration.id, migration.name, digest, new Date().toISOString());
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kept separate so a failed first migration transaction removes metadata too.
|
||||||
|
export const migrationMetadataSql = createMigrationsTable;
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
import { desc, sql } from 'drizzle-orm';
|
||||||
|
import {
|
||||||
|
type AnySQLiteColumn,
|
||||||
|
check,
|
||||||
|
index,
|
||||||
|
integer,
|
||||||
|
primaryKey,
|
||||||
|
sqliteTable,
|
||||||
|
text,
|
||||||
|
unique,
|
||||||
|
} from 'drizzle-orm/sqlite-core';
|
||||||
|
|
||||||
|
const timestamps = {
|
||||||
|
createdAt: text('created_at').notNull(),
|
||||||
|
updatedAt: text('updated_at').notNull(),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const instances = sqliteTable(
|
||||||
|
'instances',
|
||||||
|
{
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
name: text('name').notNull(),
|
||||||
|
baseUrl: text('base_url').notNull(),
|
||||||
|
authMode: text('auth_mode').notNull().default('password'),
|
||||||
|
enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true),
|
||||||
|
configRevision: integer('config_revision').notNull().default(1),
|
||||||
|
...timestamps,
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
unique('instances_base_url_unique').on(table.baseUrl),
|
||||||
|
check('instances_enabled_check', sql`${table.enabled} in (0, 1)`),
|
||||||
|
check('instances_config_revision_check', sql`${table.configRevision} > 0`),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const instanceTags = sqliteTable(
|
||||||
|
'instance_tags',
|
||||||
|
{
|
||||||
|
instanceId: text('instance_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => instances.id, { onDelete: 'cascade' }),
|
||||||
|
tag: text('tag').notNull(),
|
||||||
|
createdAt: text('created_at').notNull(),
|
||||||
|
},
|
||||||
|
(table) => [primaryKey({ columns: [table.instanceId, table.tag] })],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const capabilities = sqliteTable(
|
||||||
|
'capabilities',
|
||||||
|
{
|
||||||
|
instanceId: text('instance_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => instances.id, { onDelete: 'cascade' }),
|
||||||
|
operationId: text('operation_id').notNull(),
|
||||||
|
state: text('state').notNull(),
|
||||||
|
upstreamVersion: text('upstream_version'),
|
||||||
|
detailCode: text('detail_code'),
|
||||||
|
observedAt: text('observed_at').notNull(),
|
||||||
|
...timestamps,
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
primaryKey({ columns: [table.instanceId, table.operationId] }),
|
||||||
|
check(
|
||||||
|
'capabilities_state_check',
|
||||||
|
sql`${table.state} in ('supported', 'unsupported', 'auth-required', 'degraded', 'unknown')`,
|
||||||
|
),
|
||||||
|
index('idx_capabilities_state').on(table.state),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const statusSnapshots = sqliteTable(
|
||||||
|
'status_snapshots',
|
||||||
|
{
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
instanceId: text('instance_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => instances.id, { onDelete: 'cascade' }),
|
||||||
|
category: text('category').notNull(),
|
||||||
|
state: text('state').notNull(),
|
||||||
|
payloadJson: text('payload_json').notNull(),
|
||||||
|
observedAt: text('observed_at').notNull(),
|
||||||
|
expiresAt: text('expires_at'),
|
||||||
|
createdAt: text('created_at').notNull(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
check(
|
||||||
|
'status_snapshots_state_check',
|
||||||
|
sql`${table.state} in ('fresh', 'stale', 'expired', 'unknown')`,
|
||||||
|
),
|
||||||
|
index('idx_status_snapshots_instance_observed_at').on(table.instanceId, desc(table.observedAt)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const jobs = sqliteTable(
|
||||||
|
'jobs',
|
||||||
|
{
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
parentJobId: text('parent_job_id').references((): AnySQLiteColumn => jobs.id, {
|
||||||
|
onDelete: 'restrict',
|
||||||
|
}),
|
||||||
|
operationId: text('operation_id').notNull(),
|
||||||
|
riskLevel: text('risk_level').notNull(),
|
||||||
|
status: text('status').notNull(),
|
||||||
|
requestedBy: text('requested_by').notNull(),
|
||||||
|
requestId: text('request_id').notNull(),
|
||||||
|
parametersDigest: text('parameters_digest').notNull(),
|
||||||
|
createdAt: text('created_at').notNull(),
|
||||||
|
startedAt: text('started_at'),
|
||||||
|
finishedAt: text('finished_at'),
|
||||||
|
updatedAt: text('updated_at').notNull(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
check('jobs_risk_level_check', sql`${table.riskLevel} in ('R0', 'R1', 'R2', 'R3')`),
|
||||||
|
index('idx_jobs_status_created_at').on(table.status, desc(table.createdAt)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const jobItems = sqliteTable(
|
||||||
|
'job_items',
|
||||||
|
{
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
jobId: text('job_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => jobs.id, { onDelete: 'cascade' }),
|
||||||
|
instanceId: text('instance_id')
|
||||||
|
.notNull()
|
||||||
|
.references(() => instances.id, { onDelete: 'restrict' }),
|
||||||
|
attemptNumber: integer('attempt_number').notNull().default(1),
|
||||||
|
status: text('status').notNull(),
|
||||||
|
resultCode: text('result_code'),
|
||||||
|
createdAt: text('created_at').notNull(),
|
||||||
|
startedAt: text('started_at'),
|
||||||
|
finishedAt: text('finished_at'),
|
||||||
|
updatedAt: text('updated_at').notNull(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
unique('job_items_job_instance_attempt_unique').on(
|
||||||
|
table.jobId,
|
||||||
|
table.instanceId,
|
||||||
|
table.attemptNumber,
|
||||||
|
),
|
||||||
|
check('job_items_attempt_number_check', sql`${table.attemptNumber} > 0`),
|
||||||
|
index('idx_job_items_job_status').on(table.jobId, table.status),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const auditEvents = sqliteTable(
|
||||||
|
'audit_events',
|
||||||
|
{
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
instanceId: text('instance_id').references(() => instances.id, { onDelete: 'set null' }),
|
||||||
|
jobId: text('job_id').references(() => jobs.id, { onDelete: 'set null' }),
|
||||||
|
actor: text('actor').notNull(),
|
||||||
|
operationId: text('operation_id').notNull(),
|
||||||
|
riskLevel: text('risk_level').notNull(),
|
||||||
|
requestId: text('request_id').notNull(),
|
||||||
|
parametersSummaryJson: text('parameters_summary_json').notNull(),
|
||||||
|
bodyDigest: text('body_digest'),
|
||||||
|
resultCode: text('result_code').notNull(),
|
||||||
|
durationMs: integer('duration_ms').notNull(),
|
||||||
|
createdAt: text('created_at').notNull(),
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
check('audit_events_risk_level_check', sql`${table.riskLevel} in ('R0', 'R1', 'R2', 'R3')`),
|
||||||
|
check('audit_events_duration_ms_check', sql`${table.durationMs} >= 0`),
|
||||||
|
index('idx_audit_events_created_at').on(desc(table.createdAt)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const appSettings = sqliteTable('app_settings', {
|
||||||
|
key: text('key').primaryKey(),
|
||||||
|
valueJson: text('value_json').notNull(),
|
||||||
|
...timestamps,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const secretReferences = sqliteTable(
|
||||||
|
'secret_references',
|
||||||
|
{
|
||||||
|
id: text('id').primaryKey(),
|
||||||
|
instanceId: text('instance_id').references(() => instances.id, { onDelete: 'cascade' }),
|
||||||
|
purpose: text('purpose').notNull(),
|
||||||
|
provider: text('provider').notNull(),
|
||||||
|
externalReference: text('external_reference').notNull(),
|
||||||
|
...timestamps,
|
||||||
|
},
|
||||||
|
(table) => [
|
||||||
|
unique('secret_references_provider_external_unique').on(
|
||||||
|
table.provider,
|
||||||
|
table.externalReference,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
Generated
+371
@@ -36,10 +36,19 @@ importers:
|
|||||||
|
|
||||||
apps/api:
|
apps/api:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
better-sqlite3:
|
||||||
|
specifier: 12.11.1
|
||||||
|
version: 12.11.1
|
||||||
|
drizzle-orm:
|
||||||
|
specifier: 0.45.2
|
||||||
|
version: 0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)
|
||||||
fastify:
|
fastify:
|
||||||
specifier: 5.10.0
|
specifier: 5.10.0
|
||||||
version: 5.10.0
|
version: 5.10.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@types/better-sqlite3':
|
||||||
|
specifier: 7.6.13
|
||||||
|
version: 7.6.13
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 24.13.3
|
specifier: 24.13.3
|
||||||
version: 24.13.3
|
version: 24.13.3
|
||||||
@@ -446,6 +455,9 @@ packages:
|
|||||||
'@standard-schema/spec@1.1.0':
|
'@standard-schema/spec@1.1.0':
|
||||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||||
|
|
||||||
|
'@types/better-sqlite3@7.6.13':
|
||||||
|
resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==}
|
||||||
|
|
||||||
'@types/chai@5.2.3':
|
'@types/chai@5.2.3':
|
||||||
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
|
||||||
|
|
||||||
@@ -601,6 +613,19 @@ packages:
|
|||||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
|
base64-js@1.5.1:
|
||||||
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
|
|
||||||
|
better-sqlite3@12.11.1:
|
||||||
|
resolution: {integrity: sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==}
|
||||||
|
engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x}
|
||||||
|
|
||||||
|
bindings@1.5.0:
|
||||||
|
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
|
||||||
|
|
||||||
|
bl@4.1.0:
|
||||||
|
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||||
|
|
||||||
brace-expansion@1.1.16:
|
brace-expansion@1.1.16:
|
||||||
resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==}
|
resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==}
|
||||||
|
|
||||||
@@ -611,6 +636,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==}
|
resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
|
|
||||||
|
buffer@5.7.1:
|
||||||
|
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
|
||||||
|
|
||||||
callsites@3.1.0:
|
callsites@3.1.0:
|
||||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -623,6 +651,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
chownr@1.1.4:
|
||||||
|
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=7.0.0'}
|
||||||
@@ -654,6 +685,14 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
decompress-response@6.0.0:
|
||||||
|
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
deep-extend@0.6.0:
|
||||||
|
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
|
||||||
|
engines: {node: '>=4.0.0'}
|
||||||
|
|
||||||
deep-is@0.1.4:
|
deep-is@0.1.4:
|
||||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||||
|
|
||||||
@@ -665,6 +704,105 @@ packages:
|
|||||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
detect-libc@2.1.2:
|
||||||
|
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
drizzle-orm@0.45.2:
|
||||||
|
resolution: {integrity: sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@aws-sdk/client-rds-data': '>=3'
|
||||||
|
'@cloudflare/workers-types': '>=4'
|
||||||
|
'@electric-sql/pglite': '>=0.2.0'
|
||||||
|
'@libsql/client': '>=0.10.0'
|
||||||
|
'@libsql/client-wasm': '>=0.10.0'
|
||||||
|
'@neondatabase/serverless': '>=0.10.0'
|
||||||
|
'@op-engineering/op-sqlite': '>=2'
|
||||||
|
'@opentelemetry/api': ^1.4.1
|
||||||
|
'@planetscale/database': '>=1.13'
|
||||||
|
'@prisma/client': '*'
|
||||||
|
'@tidbcloud/serverless': '*'
|
||||||
|
'@types/better-sqlite3': '*'
|
||||||
|
'@types/pg': '*'
|
||||||
|
'@types/sql.js': '*'
|
||||||
|
'@upstash/redis': '>=1.34.7'
|
||||||
|
'@vercel/postgres': '>=0.8.0'
|
||||||
|
'@xata.io/client': '*'
|
||||||
|
better-sqlite3: '>=7'
|
||||||
|
bun-types: '*'
|
||||||
|
expo-sqlite: '>=14.0.0'
|
||||||
|
gel: '>=2'
|
||||||
|
knex: '*'
|
||||||
|
kysely: '*'
|
||||||
|
mysql2: '>=2'
|
||||||
|
pg: '>=8'
|
||||||
|
postgres: '>=3'
|
||||||
|
prisma: '*'
|
||||||
|
sql.js: '>=1'
|
||||||
|
sqlite3: '>=5'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@aws-sdk/client-rds-data':
|
||||||
|
optional: true
|
||||||
|
'@cloudflare/workers-types':
|
||||||
|
optional: true
|
||||||
|
'@electric-sql/pglite':
|
||||||
|
optional: true
|
||||||
|
'@libsql/client':
|
||||||
|
optional: true
|
||||||
|
'@libsql/client-wasm':
|
||||||
|
optional: true
|
||||||
|
'@neondatabase/serverless':
|
||||||
|
optional: true
|
||||||
|
'@op-engineering/op-sqlite':
|
||||||
|
optional: true
|
||||||
|
'@opentelemetry/api':
|
||||||
|
optional: true
|
||||||
|
'@planetscale/database':
|
||||||
|
optional: true
|
||||||
|
'@prisma/client':
|
||||||
|
optional: true
|
||||||
|
'@tidbcloud/serverless':
|
||||||
|
optional: true
|
||||||
|
'@types/better-sqlite3':
|
||||||
|
optional: true
|
||||||
|
'@types/pg':
|
||||||
|
optional: true
|
||||||
|
'@types/sql.js':
|
||||||
|
optional: true
|
||||||
|
'@upstash/redis':
|
||||||
|
optional: true
|
||||||
|
'@vercel/postgres':
|
||||||
|
optional: true
|
||||||
|
'@xata.io/client':
|
||||||
|
optional: true
|
||||||
|
better-sqlite3:
|
||||||
|
optional: true
|
||||||
|
bun-types:
|
||||||
|
optional: true
|
||||||
|
expo-sqlite:
|
||||||
|
optional: true
|
||||||
|
gel:
|
||||||
|
optional: true
|
||||||
|
knex:
|
||||||
|
optional: true
|
||||||
|
kysely:
|
||||||
|
optional: true
|
||||||
|
mysql2:
|
||||||
|
optional: true
|
||||||
|
pg:
|
||||||
|
optional: true
|
||||||
|
postgres:
|
||||||
|
optional: true
|
||||||
|
prisma:
|
||||||
|
optional: true
|
||||||
|
sql.js:
|
||||||
|
optional: true
|
||||||
|
sqlite3:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
end-of-stream@1.4.5:
|
||||||
|
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||||
|
|
||||||
es-module-lexer@1.7.0:
|
es-module-lexer@1.7.0:
|
||||||
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
|
||||||
|
|
||||||
@@ -725,6 +863,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
expand-template@2.0.3:
|
||||||
|
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
expect-type@1.4.0:
|
expect-type@1.4.0:
|
||||||
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
|
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
|
||||||
engines: {node: '>=12.0.0'}
|
engines: {node: '>=12.0.0'}
|
||||||
@@ -775,6 +917,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||||
engines: {node: '>=16.0.0'}
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
|
file-uri-to-path@1.0.0:
|
||||||
|
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
|
||||||
|
|
||||||
find-my-way@9.6.0:
|
find-my-way@9.6.0:
|
||||||
resolution: {integrity: sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==}
|
resolution: {integrity: sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
@@ -790,11 +935,17 @@ packages:
|
|||||||
flatted@3.4.2:
|
flatted@3.4.2:
|
||||||
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
||||||
|
|
||||||
|
fs-constants@1.0.0:
|
||||||
|
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
|
github-from-package@0.0.0:
|
||||||
|
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
|
||||||
|
|
||||||
glob-parent@6.0.2:
|
glob-parent@6.0.2:
|
||||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
@@ -815,6 +966,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
ieee754@1.2.1:
|
||||||
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
|
|
||||||
ignore@5.3.2:
|
ignore@5.3.2:
|
||||||
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
@@ -834,6 +988,9 @@ packages:
|
|||||||
inherits@2.0.4:
|
inherits@2.0.4:
|
||||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
|
||||||
|
ini@1.3.8:
|
||||||
|
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||||
|
|
||||||
ipaddr.js@2.4.0:
|
ipaddr.js@2.4.0:
|
||||||
resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==}
|
resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
@@ -897,6 +1054,10 @@ packages:
|
|||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
mimic-response@3.1.0:
|
||||||
|
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
minimatch@10.2.5:
|
minimatch@10.2.5:
|
||||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
@@ -908,10 +1069,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
|
resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
|
minimist@1.2.8:
|
||||||
|
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||||
|
|
||||||
minipass@7.1.3:
|
minipass@7.1.3:
|
||||||
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
|
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
|
mkdirp-classic@0.5.3:
|
||||||
|
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
|
||||||
|
|
||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
@@ -920,9 +1087,16 @@ packages:
|
|||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
napi-build-utils@2.0.0:
|
||||||
|
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
|
||||||
|
|
||||||
natural-compare@1.4.0:
|
natural-compare@1.4.0:
|
||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
|
node-abi@3.94.0:
|
||||||
|
resolution: {integrity: sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
obug@2.1.3:
|
obug@2.1.3:
|
||||||
resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==}
|
resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==}
|
||||||
engines: {node: '>=12.20.0'}
|
engines: {node: '>=12.20.0'}
|
||||||
@@ -931,6 +1105,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
|
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
|
once@1.4.0:
|
||||||
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@@ -983,6 +1160,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==}
|
resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
prebuild-install@7.1.3:
|
||||||
|
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
prelude-ls@1.2.1:
|
prelude-ls@1.2.1:
|
||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@@ -998,6 +1181,9 @@ packages:
|
|||||||
process-warning@5.0.0:
|
process-warning@5.0.0:
|
||||||
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
||||||
|
|
||||||
|
pump@3.0.4:
|
||||||
|
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
|
||||||
|
|
||||||
punycode@2.3.1:
|
punycode@2.3.1:
|
||||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1005,6 +1191,14 @@ packages:
|
|||||||
quick-format-unescaped@4.0.4:
|
quick-format-unescaped@4.0.4:
|
||||||
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||||
|
|
||||||
|
rc@1.2.8:
|
||||||
|
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
readable-stream@3.6.2:
|
||||||
|
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
real-require@0.2.0:
|
real-require@0.2.0:
|
||||||
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||||
engines: {node: '>= 12.13.0'}
|
engines: {node: '>= 12.13.0'}
|
||||||
@@ -1036,6 +1230,9 @@ packages:
|
|||||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
safe-buffer@5.2.1:
|
||||||
|
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
|
||||||
|
|
||||||
safe-regex2@5.1.1:
|
safe-regex2@5.1.1:
|
||||||
resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==}
|
resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -1069,6 +1266,12 @@ packages:
|
|||||||
siginfo@2.0.0:
|
siginfo@2.0.0:
|
||||||
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
|
||||||
|
|
||||||
|
simple-concat@1.0.1:
|
||||||
|
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
|
||||||
|
|
||||||
|
simple-get@4.0.1:
|
||||||
|
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
||||||
|
|
||||||
sonic-boom@4.2.1:
|
sonic-boom@4.2.1:
|
||||||
resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
|
resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
|
||||||
|
|
||||||
@@ -1090,6 +1293,13 @@ packages:
|
|||||||
std-env@3.10.0:
|
std-env@3.10.0:
|
||||||
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
|
||||||
|
|
||||||
|
string_decoder@1.3.0:
|
||||||
|
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
|
||||||
|
|
||||||
|
strip-json-comments@2.0.1:
|
||||||
|
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
strip-json-comments@3.1.1:
|
strip-json-comments@3.1.1:
|
||||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1098,6 +1308,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
tar-fs@2.1.5:
|
||||||
|
resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==}
|
||||||
|
|
||||||
|
tar-stream@2.2.0:
|
||||||
|
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
thread-stream@4.2.0:
|
thread-stream@4.2.0:
|
||||||
resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==}
|
resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
@@ -1131,6 +1348,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4'
|
typescript: '>=4.8.4'
|
||||||
|
|
||||||
|
tunnel-agent@0.6.0:
|
||||||
|
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@@ -1153,6 +1373,9 @@ packages:
|
|||||||
uri-js@4.4.1:
|
uri-js@4.4.1:
|
||||||
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
|
|
||||||
|
util-deprecate@1.0.2:
|
||||||
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
vite@7.3.6:
|
vite@7.3.6:
|
||||||
resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==}
|
resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@@ -1241,6 +1464,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
wrappy@1.0.2:
|
||||||
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
|
||||||
yocto-queue@0.1.0:
|
yocto-queue@0.1.0:
|
||||||
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -1512,6 +1738,10 @@ snapshots:
|
|||||||
|
|
||||||
'@standard-schema/spec@1.1.0': {}
|
'@standard-schema/spec@1.1.0': {}
|
||||||
|
|
||||||
|
'@types/better-sqlite3@7.6.13':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 24.13.3
|
||||||
|
|
||||||
'@types/chai@5.2.3':
|
'@types/chai@5.2.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/deep-eql': 4.0.2
|
'@types/deep-eql': 4.0.2
|
||||||
@@ -1702,6 +1932,23 @@ snapshots:
|
|||||||
|
|
||||||
balanced-match@4.0.4: {}
|
balanced-match@4.0.4: {}
|
||||||
|
|
||||||
|
base64-js@1.5.1: {}
|
||||||
|
|
||||||
|
better-sqlite3@12.11.1:
|
||||||
|
dependencies:
|
||||||
|
bindings: 1.5.0
|
||||||
|
prebuild-install: 7.1.3
|
||||||
|
|
||||||
|
bindings@1.5.0:
|
||||||
|
dependencies:
|
||||||
|
file-uri-to-path: 1.0.0
|
||||||
|
|
||||||
|
bl@4.1.0:
|
||||||
|
dependencies:
|
||||||
|
buffer: 5.7.1
|
||||||
|
inherits: 2.0.4
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
|
||||||
brace-expansion@1.1.16:
|
brace-expansion@1.1.16:
|
||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 1.0.2
|
balanced-match: 1.0.2
|
||||||
@@ -1715,6 +1962,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
balanced-match: 4.0.4
|
balanced-match: 4.0.4
|
||||||
|
|
||||||
|
buffer@5.7.1:
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
ieee754: 1.2.1
|
||||||
|
|
||||||
callsites@3.1.0: {}
|
callsites@3.1.0: {}
|
||||||
|
|
||||||
chai@6.2.2: {}
|
chai@6.2.2: {}
|
||||||
@@ -1724,6 +1976,8 @@ snapshots:
|
|||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
supports-color: 7.2.0
|
supports-color: 7.2.0
|
||||||
|
|
||||||
|
chownr@1.1.4: {}
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
@@ -1746,12 +2000,29 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
decompress-response@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
mimic-response: 3.1.0
|
||||||
|
|
||||||
|
deep-extend@0.6.0: {}
|
||||||
|
|
||||||
deep-is@0.1.4: {}
|
deep-is@0.1.4: {}
|
||||||
|
|
||||||
depd@2.0.0: {}
|
depd@2.0.0: {}
|
||||||
|
|
||||||
dequal@2.0.3: {}
|
dequal@2.0.3: {}
|
||||||
|
|
||||||
|
detect-libc@2.1.2: {}
|
||||||
|
|
||||||
|
drizzle-orm@0.45.2(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1):
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/better-sqlite3': 7.6.13
|
||||||
|
better-sqlite3: 12.11.1
|
||||||
|
|
||||||
|
end-of-stream@1.4.5:
|
||||||
|
dependencies:
|
||||||
|
once: 1.4.0
|
||||||
|
|
||||||
es-module-lexer@1.7.0: {}
|
es-module-lexer@1.7.0: {}
|
||||||
|
|
||||||
esbuild@0.28.1:
|
esbuild@0.28.1:
|
||||||
@@ -1857,6 +2128,8 @@ snapshots:
|
|||||||
|
|
||||||
esutils@2.0.3: {}
|
esutils@2.0.3: {}
|
||||||
|
|
||||||
|
expand-template@2.0.3: {}
|
||||||
|
|
||||||
expect-type@1.4.0: {}
|
expect-type@1.4.0: {}
|
||||||
|
|
||||||
fast-decode-uri-component@1.0.1: {}
|
fast-decode-uri-component@1.0.1: {}
|
||||||
@@ -1916,6 +2189,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flat-cache: 4.0.1
|
flat-cache: 4.0.1
|
||||||
|
|
||||||
|
file-uri-to-path@1.0.0: {}
|
||||||
|
|
||||||
find-my-way@9.6.0:
|
find-my-way@9.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
@@ -1934,9 +2209,13 @@ snapshots:
|
|||||||
|
|
||||||
flatted@3.4.2: {}
|
flatted@3.4.2: {}
|
||||||
|
|
||||||
|
fs-constants@1.0.0: {}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
github-from-package@0.0.0: {}
|
||||||
|
|
||||||
glob-parent@6.0.2:
|
glob-parent@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@@ -1959,6 +2238,8 @@ snapshots:
|
|||||||
statuses: 2.0.2
|
statuses: 2.0.2
|
||||||
toidentifier: 1.0.1
|
toidentifier: 1.0.1
|
||||||
|
|
||||||
|
ieee754@1.2.1: {}
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
|
|
||||||
ignore@7.0.6: {}
|
ignore@7.0.6: {}
|
||||||
@@ -1972,6 +2253,8 @@ snapshots:
|
|||||||
|
|
||||||
inherits@2.0.4: {}
|
inherits@2.0.4: {}
|
||||||
|
|
||||||
|
ini@1.3.8: {}
|
||||||
|
|
||||||
ipaddr.js@2.4.0: {}
|
ipaddr.js@2.4.0: {}
|
||||||
|
|
||||||
is-extglob@2.1.1: {}
|
is-extglob@2.1.1: {}
|
||||||
@@ -2027,6 +2310,8 @@ snapshots:
|
|||||||
|
|
||||||
mime@3.0.0: {}
|
mime@3.0.0: {}
|
||||||
|
|
||||||
|
mimic-response@3.1.0: {}
|
||||||
|
|
||||||
minimatch@10.2.5:
|
minimatch@10.2.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 5.0.7
|
brace-expansion: 5.0.7
|
||||||
@@ -2039,18 +2324,32 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.1.2
|
brace-expansion: 2.1.2
|
||||||
|
|
||||||
|
minimist@1.2.8: {}
|
||||||
|
|
||||||
minipass@7.1.3: {}
|
minipass@7.1.3: {}
|
||||||
|
|
||||||
|
mkdirp-classic@0.5.3: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
nanoid@3.3.16: {}
|
nanoid@3.3.16: {}
|
||||||
|
|
||||||
|
napi-build-utils@2.0.0: {}
|
||||||
|
|
||||||
natural-compare@1.4.0: {}
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
|
node-abi@3.94.0:
|
||||||
|
dependencies:
|
||||||
|
semver: 7.8.5
|
||||||
|
|
||||||
obug@2.1.3: {}
|
obug@2.1.3: {}
|
||||||
|
|
||||||
on-exit-leak-free@2.1.2: {}
|
on-exit-leak-free@2.1.2: {}
|
||||||
|
|
||||||
|
once@1.4.0:
|
||||||
|
dependencies:
|
||||||
|
wrappy: 1.0.2
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-is: 0.1.4
|
deep-is: 0.1.4
|
||||||
@@ -2113,6 +2412,21 @@ snapshots:
|
|||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
prebuild-install@7.1.3:
|
||||||
|
dependencies:
|
||||||
|
detect-libc: 2.1.2
|
||||||
|
expand-template: 2.0.3
|
||||||
|
github-from-package: 0.0.0
|
||||||
|
minimist: 1.2.8
|
||||||
|
mkdirp-classic: 0.5.3
|
||||||
|
napi-build-utils: 2.0.0
|
||||||
|
node-abi: 3.94.0
|
||||||
|
pump: 3.0.4
|
||||||
|
rc: 1.2.8
|
||||||
|
simple-get: 4.0.1
|
||||||
|
tar-fs: 2.1.5
|
||||||
|
tunnel-agent: 0.6.0
|
||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
prettier@3.8.1: {}
|
prettier@3.8.1: {}
|
||||||
@@ -2121,10 +2435,28 @@ snapshots:
|
|||||||
|
|
||||||
process-warning@5.0.0: {}
|
process-warning@5.0.0: {}
|
||||||
|
|
||||||
|
pump@3.0.4:
|
||||||
|
dependencies:
|
||||||
|
end-of-stream: 1.4.5
|
||||||
|
once: 1.4.0
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
quick-format-unescaped@4.0.4: {}
|
quick-format-unescaped@4.0.4: {}
|
||||||
|
|
||||||
|
rc@1.2.8:
|
||||||
|
dependencies:
|
||||||
|
deep-extend: 0.6.0
|
||||||
|
ini: 1.3.8
|
||||||
|
minimist: 1.2.8
|
||||||
|
strip-json-comments: 2.0.1
|
||||||
|
|
||||||
|
readable-stream@3.6.2:
|
||||||
|
dependencies:
|
||||||
|
inherits: 2.0.4
|
||||||
|
string_decoder: 1.3.0
|
||||||
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
real-require@0.2.0: {}
|
real-require@0.2.0: {}
|
||||||
|
|
||||||
real-require@1.0.0: {}
|
real-require@1.0.0: {}
|
||||||
@@ -2170,6 +2502,8 @@ snapshots:
|
|||||||
'@rollup/rollup-win32-x64-msvc': 4.62.2
|
'@rollup/rollup-win32-x64-msvc': 4.62.2
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
safe-buffer@5.2.1: {}
|
||||||
|
|
||||||
safe-regex2@5.1.1:
|
safe-regex2@5.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
ret: 0.5.0
|
ret: 0.5.0
|
||||||
@@ -2192,6 +2526,14 @@ snapshots:
|
|||||||
|
|
||||||
siginfo@2.0.0: {}
|
siginfo@2.0.0: {}
|
||||||
|
|
||||||
|
simple-concat@1.0.1: {}
|
||||||
|
|
||||||
|
simple-get@4.0.1:
|
||||||
|
dependencies:
|
||||||
|
decompress-response: 6.0.0
|
||||||
|
once: 1.4.0
|
||||||
|
simple-concat: 1.0.1
|
||||||
|
|
||||||
sonic-boom@4.2.1:
|
sonic-boom@4.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
atomic-sleep: 1.0.0
|
atomic-sleep: 1.0.0
|
||||||
@@ -2206,12 +2548,33 @@ snapshots:
|
|||||||
|
|
||||||
std-env@3.10.0: {}
|
std-env@3.10.0: {}
|
||||||
|
|
||||||
|
string_decoder@1.3.0:
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
|
||||||
|
strip-json-comments@2.0.1: {}
|
||||||
|
|
||||||
strip-json-comments@3.1.1: {}
|
strip-json-comments@3.1.1: {}
|
||||||
|
|
||||||
supports-color@7.2.0:
|
supports-color@7.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 4.0.0
|
has-flag: 4.0.0
|
||||||
|
|
||||||
|
tar-fs@2.1.5:
|
||||||
|
dependencies:
|
||||||
|
chownr: 1.1.4
|
||||||
|
mkdirp-classic: 0.5.3
|
||||||
|
pump: 3.0.4
|
||||||
|
tar-stream: 2.2.0
|
||||||
|
|
||||||
|
tar-stream@2.2.0:
|
||||||
|
dependencies:
|
||||||
|
bl: 4.1.0
|
||||||
|
end-of-stream: 1.4.5
|
||||||
|
fs-constants: 1.0.0
|
||||||
|
inherits: 2.0.4
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
|
||||||
thread-stream@4.2.0:
|
thread-stream@4.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
real-require: 1.0.0
|
real-require: 1.0.0
|
||||||
@@ -2235,6 +2598,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
tunnel-agent@0.6.0:
|
||||||
|
dependencies:
|
||||||
|
safe-buffer: 5.2.1
|
||||||
|
|
||||||
type-check@0.4.0:
|
type-check@0.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
@@ -2258,6 +2625,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.3.1
|
punycode: 2.3.1
|
||||||
|
|
||||||
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
vite@7.3.6(@types/node@24.13.3):
|
vite@7.3.6(@types/node@24.13.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.28.1
|
esbuild: 0.28.1
|
||||||
@@ -2318,4 +2687,6 @@ snapshots:
|
|||||||
|
|
||||||
word-wrap@1.2.5: {}
|
word-wrap@1.2.5: {}
|
||||||
|
|
||||||
|
wrappy@1.0.2: {}
|
||||||
|
|
||||||
yocto-queue@0.1.0: {}
|
yocto-queue@0.1.0: {}
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ packages:
|
|||||||
# Vitest uses esbuild for TypeScript transformation. pnpm 11 blocks every
|
# Vitest uses esbuild for TypeScript transformation. pnpm 11 blocks every
|
||||||
# dependency build by default, so keep the allow-list explicit and minimal.
|
# dependency build by default, so keep the allow-list explicit and minimal.
|
||||||
allowBuilds:
|
allowBuilds:
|
||||||
|
better-sqlite3: true
|
||||||
esbuild: true
|
esbuild: true
|
||||||
|
|||||||
Reference in New Issue
Block a user