feat(storage): add resilient SQLite foundation
This commit is contained in:
@@ -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,
|
||||
),
|
||||
],
|
||||
);
|
||||
Reference in New Issue
Block a user