feat(ql3): establish 3.0 incubation baseline

This commit is contained in:
whyour
2026-08-12 00:25:26 +08:00
parent 4bf92dcfeb
commit c699c32461
2817 changed files with 779642 additions and 653 deletions
@@ -0,0 +1,77 @@
import type { ApiCredentialRepository } from '@qinglong/runtime-core/api-credential';
import type { LocalOwnerBootstrapRepository } from '@qinglong/runtime-core/local-owner-bootstrap';
import type { LocalOwnerCredentialRecoveryRepository } from '@qinglong/runtime-core/local-owner-credential-recovery';
import type { LocalOwnerPepperReferenceRepository } from '@qinglong/runtime-core/local-owner-pepper';
import {
assertLocalSqliteOptions,
assertLocalSqlitePathBoundary,
LocalSqliteConfigurationError,
openLocalSqliteClient,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
} from './config';
import { LocalSqliteApiCredentialRepository } from '../security/apiCredentialRepository';
import { LocalSqliteOperationAuthority } from '../authority/operationAuthority';
import { LocalSqliteOwnerBootstrapRepository } from '../local-owner/ownerBootstrapRepository';
import { LocalSqliteOwnerCredentialRecoveryRepository } from '../local-owner/ownerCredentialRecoveryRepository';
import { LocalSqliteOwnerPepperRepository } from '../local-owner/ownerPepperRepository';
import {
auditLocalSqliteReadiness,
type LocalSqliteReadinessEvidence,
} from '../readiness/readiness';
/**
* A deliberately narrow and short-lived local authority. It is excluded from
* the default runtime entry point so a long-running application cannot retain
* owner-bootstrap power by accident.
*/
export interface LocalSqliteBootstrapDatabase {
readonly profile: LocalSqliteProfile;
readonly readiness: LocalSqliteReadinessEvidence;
readonly apiCredentials: ApiCredentialRepository;
readonly ownerBootstrap: LocalOwnerBootstrapRepository;
readonly ownerCredentialRecovery: LocalOwnerCredentialRecoveryRepository;
readonly ownerPepper: LocalOwnerPepperReferenceRepository;
close(): Promise<void>;
}
export async function openLocalSqliteBootstrapDatabase(
options: LocalSqliteDatabaseOptions,
): Promise<LocalSqliteBootstrapDatabase> {
assertLocalSqliteOptions(options);
assertLocalSqlitePathBoundary(options.databasePath, false);
const client = openLocalSqliteClient(options, false);
try {
const readiness = await auditLocalSqliteReadiness(client);
const authority = new LocalSqliteOperationAuthority(client);
const apiCredentials = new LocalSqliteApiCredentialRepository(authority);
const ownerBootstrap = new LocalSqliteOwnerBootstrapRepository(authority);
const ownerCredentialRecovery =
new LocalSqliteOwnerCredentialRecoveryRepository(authority);
const ownerPepper = new LocalSqliteOwnerPepperRepository(authority);
let closePromise: Promise<void> | undefined;
return Object.freeze({
profile: options.profile,
readiness,
apiCredentials,
ownerBootstrap,
ownerCredentialRecovery,
ownerPepper,
close() {
if (closePromise) return closePromise;
closePromise = authority.close();
return closePromise;
},
});
} catch (error) {
if (client.isOpen) client.close();
throw error;
}
}
export {
LocalSqliteConfigurationError,
type LocalSqliteDatabaseOptions,
type LocalSqliteProfile,
};
export type { LocalSqliteReadinessEvidence } from '../readiness/readiness';
@@ -0,0 +1,144 @@
import fs from 'node:fs';
import path from 'node:path';
import { DatabaseSync } from 'node:sqlite';
// Shared SQLite storage boundary for path, Profile and connection policy.
export type LocalSqliteProfile = 'edge' | 'standalone';
export interface LocalSqliteDatabaseOptions {
readonly databasePath: string;
readonly profile: LocalSqliteProfile;
readonly busyTimeoutMs?: number;
}
export class LocalSqliteConfigurationError extends TypeError {
readonly code = 'LOCAL_SQLITE_CONFIGURATION_INVALID';
constructor(message: string) {
super(`Local SQLite configuration is invalid: ${message}`);
this.name = 'LocalSqliteConfigurationError';
}
}
export function assertLocalSqliteOptions(
options: LocalSqliteDatabaseOptions,
): void {
if (
!options ||
typeof options !== 'object' ||
Array.isArray(options) ||
!path.isAbsolute(options.databasePath) ||
options.databasePath.length > 4096 ||
options.databasePath.includes('\0')
) {
throw new LocalSqliteConfigurationError(
'databasePath must be a bounded absolute path',
);
}
if (options.profile !== 'edge' && options.profile !== 'standalone') {
throw new LocalSqliteConfigurationError(
'profile must be edge or standalone',
);
}
const busyTimeoutMs = options.busyTimeoutMs ?? 5_000;
if (
!Number.isSafeInteger(busyTimeoutMs) ||
busyTimeoutMs < 100 ||
busyTimeoutMs > 30_000
) {
throw new LocalSqliteConfigurationError(
'busyTimeoutMs must be between 100 and 30000',
);
}
}
export function assertLocalSqlitePathBoundary(
databasePath: string,
allowMissing: boolean,
): void {
const parent = fs.lstatSync(path.dirname(databasePath));
if (!parent.isDirectory() || parent.isSymbolicLink()) {
throw new LocalSqliteConfigurationError(
'database parent must be a real directory',
);
}
try {
const target = fs.lstatSync(databasePath);
if (!target.isFile() || target.isSymbolicLink()) {
throw new LocalSqliteConfigurationError(
'database target must be a regular file',
);
}
} catch (error) {
if (
allowMissing &&
error &&
typeof error === 'object' &&
'code' in error &&
error.code === 'ENOENT'
) {
return;
}
throw error;
}
}
function expectedJournalMode(profile: LocalSqliteProfile): 'delete' | 'wal' {
return profile === 'edge' ? 'delete' : 'wal';
}
/** Opens one bounded local authority; callers own and must close the handle. */
export function openLocalSqliteClient(
options: LocalSqliteDatabaseOptions,
readOnly: boolean,
): DatabaseSync {
const busyTimeoutMs = options.busyTimeoutMs ?? 5_000;
const client = new DatabaseSync(options.databasePath, {
allowExtension: false,
defensive: true,
enableDoubleQuotedStringLiterals: false,
enableForeignKeyConstraints: true,
readOnly,
timeout: busyTimeoutMs,
});
try {
client.enableDefensive(true);
client.exec('PRAGMA trusted_schema = OFF');
client.exec('PRAGMA recursive_triggers = OFF');
client.exec('PRAGMA foreign_keys = ON');
if (!readOnly) {
const expected = expectedJournalMode(options.profile);
const journal = client
.prepare(`PRAGMA journal_mode = ${expected.toUpperCase()}`)
.get() as { journal_mode?: unknown } | undefined;
if (journal?.journal_mode !== expected) {
throw new LocalSqliteConfigurationError(
`${options.profile} database does not support ${expected} journal mode`,
);
}
client.exec('PRAGMA synchronous = FULL');
if (options.profile === 'standalone') {
client.exec('PRAGMA wal_autocheckpoint = 1000');
}
client.exec(
`PRAGMA journal_size_limit = ${
options.profile === 'edge' ? 8 * 1024 * 1024 : 64 * 1024 * 1024
}`,
);
client.exec(
`PRAGMA cache_size = ${options.profile === 'edge' ? -4096 : -16384}`,
);
client.exec(
`PRAGMA mmap_size = ${
options.profile === 'edge' ? 0 : 64 * 1024 * 1024
}`,
);
} else {
client.exec('PRAGMA query_only = ON');
}
return client;
} catch (error) {
client.close();
throw error;
}
}
@@ -0,0 +1,13 @@
/**
* Storage compatibility entrypoint for development tooling. Deployment Profile
* packages must import /runtime or /migration explicitly so executable DDL is
* not pulled into a long-lived process by accident.
*/
export * from '../runtime/runtimeDatabase';
export {
localSqliteMigrationDefinition,
localSqliteMigrationManifest,
migrateLocalSqliteDatabase,
migrateLocalSqlitePath,
type LocalSqliteMigrationResult,
} from '../migration/migration';
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,169 @@
import type { DatabaseSync } from 'node:sqlite';
export type SqliteQueryRow = Record<string, unknown>;
export type SqliteQueryValue = string | number | bigint | Uint8Array | null;
export interface SqlitePersistenceErrorContract {
readonly invalidRowValue: (property: string) => Error;
readonly invalidJson: (property: string) => Error;
readonly unsupportedRowValue: (property: string) => Error;
readonly duplicateIdentityRows: () => Error;
readonly mapDriverError: (error: unknown) => Error;
}
export interface SqlitePersistencePrimitives {
readonly requiredString: (row: SqliteQueryRow, property: string) => string;
readonly optionalString: (
row: SqliteQueryRow,
property: string,
) => string | undefined;
readonly requiredInteger: (row: SqliteQueryRow, property: string) => number;
readonly requiredBlob: (row: SqliteQueryRow, property: string) => Buffer;
readonly optionalInteger: (
row: SqliteQueryRow,
property: string,
) => number | undefined;
readonly requiredBoolean: (row: SqliteQueryRow, property: string) => boolean;
readonly requiredJson: (row: SqliteQueryRow, property: string) => unknown;
readonly requiredEnum: <T extends string>(
row: SqliteQueryRow,
property: string,
allowed: readonly T[],
) => T;
readonly queryRows: (
client: DatabaseSync,
sql: string,
values?: readonly SqliteQueryValue[],
) => SqliteQueryRow[];
readonly singleRow: (rows: SqliteQueryRow[]) => SqliteQueryRow | null;
}
export function sqliteDriverErrorCode(error: unknown): string | undefined {
if (!error || typeof error !== 'object') return undefined;
const value = (error as { code?: unknown }).code;
return typeof value === 'string' ? value : undefined;
}
export function sqliteDriverErrorNumber(error: unknown): number | undefined {
if (!error || typeof error !== 'object') return undefined;
const value = (error as { errcode?: unknown }).errcode;
return typeof value === 'number' ? value : undefined;
}
export function sqliteDriverErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : '';
}
export function isSqliteDriverError(error: unknown): boolean {
return (
sqliteDriverErrorNumber(error) !== undefined ||
sqliteDriverErrorCode(error)?.startsWith('ERR_SQLITE_') === true
);
}
export function createSqlitePersistencePrimitives(
errors: SqlitePersistenceErrorContract,
): SqlitePersistencePrimitives {
function requiredString(row: SqliteQueryRow, property: string): string {
const value = row[property];
if (typeof value !== 'string' || value.length === 0) {
throw errors.invalidRowValue(property);
}
return value;
}
function optionalString(
row: SqliteQueryRow,
property: string,
): string | undefined {
const value = row[property];
if (value === null || value === undefined) return undefined;
if (typeof value !== 'string') {
throw errors.invalidRowValue(property);
}
return value;
}
function requiredInteger(row: SqliteQueryRow, property: string): number {
const value = row[property];
if (typeof value === 'number' && Number.isSafeInteger(value)) return value;
throw errors.invalidRowValue(property);
}
function requiredBlob(row: SqliteQueryRow, property: string): Buffer {
const value = row[property];
if (!(value instanceof Uint8Array)) {
throw errors.invalidRowValue(property);
}
return Buffer.from(value);
}
function optionalInteger(
row: SqliteQueryRow,
property: string,
): number | undefined {
if (row[property] === null || row[property] === undefined) return undefined;
return requiredInteger(row, property);
}
function requiredBoolean(row: SqliteQueryRow, property: string): boolean {
const value = row[property];
if (value === 0) return false;
if (value === 1) return true;
throw errors.invalidRowValue(property);
}
function requiredJson(row: SqliteQueryRow, property: string): unknown {
const value = requiredString(row, property);
try {
return JSON.parse(value);
} catch {
throw errors.invalidJson(property);
}
}
function requiredEnum<T extends string>(
row: SqliteQueryRow,
property: string,
allowed: readonly T[],
): T {
const value = requiredString(row, property);
if (!allowed.includes(value as T)) {
throw errors.unsupportedRowValue(property);
}
return value as T;
}
function queryRows(
client: DatabaseSync,
sql: string,
values: readonly SqliteQueryValue[] = [],
): SqliteQueryRow[] {
try {
return client.prepare(sql).all(...values) as unknown as SqliteQueryRow[];
} catch (error) {
throw errors.mapDriverError(error);
}
}
function singleRow(rows: SqliteQueryRow[]): SqliteQueryRow | null {
const [row] = rows;
if (!row) return null;
if (rows.length !== 1) throw errors.duplicateIdentityRows();
return row;
}
return Object.freeze({
requiredString,
optionalString,
requiredInteger,
requiredBlob,
optionalInteger,
requiredBoolean,
requiredJson,
requiredEnum,
queryRows,
singleRow,
});
}