feat(storage): add resilient SQLite foundation

This commit is contained in:
chick
2026-07-16 13:20:59 +08:00
parent 26c1bcc6da
commit ef9a634a02
9 changed files with 1282 additions and 1 deletions
@@ -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,
),
],
);