signalk-parquet
Version:
Vessel data Parquet file archive with automated value and geospatial triggers. History API compliant with cloud backups and queries.
180 lines • 6.19 kB
TypeScript
/**
* SQLite Write-Ahead Buffer for crash-safe data ingestion
*
* Per-path table architecture: each SignalK path gets its own table in buffer.db.
* Scalar paths have a `value` column; object paths have `value_json` + flattened `value_*` columns.
* This eliminates column pollution from ALTER TABLE ADD COLUMN on a shared table.
*/
import { DataRecord } from '../types';
export interface BufferRecord {
id: number;
context: string;
received_timestamp: string;
signalk_timestamp: string;
value: number | string | boolean | null;
value_json: string | null;
source: string | null;
source_label: string | null;
source_type: string | null;
source_pgn: number | null;
source_src: string | null;
meta: string | null;
exported: number;
export_batch_id: string | null;
created_at: string;
[key: string]: unknown;
}
export interface BufferStats {
totalRecords: number;
pendingRecords: number;
exportedRecords: number;
oldestPendingTimestamp: string | null;
newestRecordTimestamp: string | null;
dbSizeBytes: number;
walSizeBytes: number;
}
export interface SQLiteBufferConfig {
dbPath: string;
maxBatchSize?: number;
retentionHours?: number;
}
/**
* Convert a SignalK path to a SQLite table name.
* Dots become underscores, any non-alphanumeric/underscore chars are stripped,
* prefixed with `buffer_`.
*/
export declare function pathToTableName(signalkPath: string): string;
export declare class SQLiteBuffer {
private db;
private _open;
private readonly dbPath;
private readonly retentionHours;
private tableMap;
constructor(config: SQLiteBufferConfig);
private createMetadataSchema;
/**
* Migrate from the legacy single buffer_records table to per-path tables.
* Runs once automatically if buffer_records exists but buffer_tables is empty.
*/
private migrateFromLegacy;
/**
* Load existing per-path tables from buffer_tables metadata and prepare INSERT statements.
*/
private loadExistingTables;
/**
* Build an INSERT statement for a per-path table.
*/
private buildInsertStmt;
/**
* Ensure a per-path table exists. Creates it on first insert for a new path.
*/
private ensureTable;
/**
* Ensure a dynamic value_* column exists on a per-path table.
* Only affects the single table for that path.
*/
private ensureColumn;
/**
* Check if the database connection is open
*/
isOpen(): boolean;
/**
* Insert a single record into the buffer
*/
insert(record: DataRecord): void;
/**
* Insert multiple records in a single transaction (much faster)
*/
insertBatch(records: DataRecord[]): void;
private prepareRecord;
/**
* Convert a BufferRecord back to a DataRecord.
* Path must be passed in since per-path tables have no path column.
*/
private bufferRecordToDataRecord;
/**
* Clean up old exported records from all per-path tables
*/
cleanup(): number;
/**
* Get buffer statistics aggregated across all per-path tables
*/
getStats(): BufferStats;
/**
* Get count of pending records (faster than full stats)
*/
getPendingCount(): number;
/**
* Checkpoint WAL file (useful before backup or to reduce WAL size)
*/
checkpoint(): void;
/**
* Get distinct dates that have unexported records, excluding today.
* Scans all per-path tables.
*/
getDatesWithUnexportedRecords(excludeToday?: boolean): string[];
/**
* Get distinct context/path combinations for a specific date (UTC).
* Scans all per-path tables.
*/
getPathsForDate(date: Date): Array<{
context: string;
path: string;
}>;
/**
* Get all records for a specific context, path, and date (UTC).
* Returns just DataRecord[] — no IDs needed since markDateExported() handles marking.
*/
getRecordsForPathAndDate(context: string, signalkPath: string, date: Date): DataRecord[];
/**
* Count unexported records for a specific context, path, and date (UTC).
*/
getRecordCountForPathAndDate(context: string, signalkPath: string, date: Date): number;
/**
* Get a batch of unexported records for a specific context, path, and date (UTC).
* Uses LIMIT/OFFSET to avoid materializing all rows at once.
*/
getRecordsForPathAndDateBatched(context: string, signalkPath: string, date: Date, limit: number, offset: number): DataRecord[];
/**
* Mark records for a specific date as exported.
* Queries the specific path's table.
*/
markDateExported(context: string, signalkPath: string, date: Date, batchId: string): void;
/**
* Get the set of known SignalK paths that have buffer tables.
* Used by SQL builders to check if a buffer table exists for federation.
*/
getKnownPaths(): Set<string>;
/**
* Check if a buffer table exists for a given SignalK path.
*/
hasTable(signalkPath: string): boolean;
/**
* Get the set of columns for a given path's buffer table.
* Returns undefined if no table exists for this path.
*/
getTableColumns(signalkPath: string): Set<string> | undefined;
/**
* Get the column schema (name + declared SQLite type) for a path's buffer table.
* Returns undefined if no table exists for this path.
*/
getTableSchema(signalkPath: string): Array<{
name: string;
type: string;
}> | undefined;
/**
* Read a batch of unexported rows for federated history queries, keyset-paginated
* by id so callers can stream large windows without materializing them all.
* Rows are raw table rows (all columns), matching the table schema.
*/
getRowsForFederation(signalkPath: string, context: string, fromIso: string, toIso: string, afterId: number, limit: number): Array<Record<string, unknown>>;
/**
* Get the database path
*/
getDbPath(): string;
/**
* Close the database connection
*/
close(): void;
}
//# sourceMappingURL=sqlite-buffer.d.ts.map