/** * Shared database helper functions for SQLite store modules. * * Consolidates upsert and dependency patterns used across * sqlite-data-accessor.ts, tasks-sqlite.ts, and session-store.ts. * * @epic T4454 */ import type { ArchiveReasonValue, Session, Task } from '@cleocode/contracts'; import type { NodeSQLiteDatabase } from 'drizzle-orm/node-sqlite'; import type { NewTaskRow } from './tasks-schema.js'; import * as schema from './tasks-schema.js'; /** Drizzle database instance type. */ type DrizzleDb = NodeSQLiteDatabase; /** Archive-specific fields for task upsert. */ export interface ArchiveFields { archivedAt?: string; /** * T11578 · AC1: typed to the canonical {@link ArchiveReasonValue} enum so * writes conform to the consolidated `tasks_tasks.archive_reason` CHECK * constraint (the bare legacy `tasks` table had no CHECK, masking * out-of-enum values such as the historical `'completed'` literal). */ archiveReason?: ArchiveReasonValue; cycleTimeDays?: number | null; } /** * Upsert a single task row into the tasks table. * Handles both active task upsert and archived task upsert via optional archiveFields. * * When `allowOrphanParent` is true (bulk/migration mode, T5034): silently nulls out * parentId if the referenced parent does not exist, preventing FK violations. * When false (normal single-task writes, default): logs a warning but still proceeds * so that FK enforcement at the DB level provides the final safety net. * * Callers that perform bulk imports or archive restoration should pass * `allowOrphanParent: true` to enable the lenient behavior. */ export declare function upsertTask(db: DrizzleDb, row: NewTaskRow, archiveFields?: ArchiveFields, allowOrphanParent?: boolean): Promise; /** * Parse a `labels_json` text column into a deduplicated string-array. * * Invalid / non-array JSON yields an empty list — the junction is then emptied * for that task, matching the "no labels" state. * * @param labelsJson - The serialized JSON array from `tasks.labels_json`. * @returns Deduplicated, non-empty label strings. */ export declare function parseLabels(labelsJson: string | null | undefined): string[]; /** * Replace the {@link schema.taskLabels} junction rows for one task so they * exactly mirror its label set (T11356). * * Delete-then-insert keeps the junction authoritative without read-modify-write * races: the prior label set is dropped and the supplied set re-inserted. Called * from {@link upsertTask} and from raw-SQL proposal inserters that bypass it. * * @param db - Drizzle tasks.db handle. * @param taskId - The owning task id. * @param labels - The full label set the junction should reflect. */ export declare function updateTaskLabels(db: DrizzleDb, taskId: string, labels: string[]): Promise; /** * Upsert a single session row into the sessions table. */ export declare function upsertSession(db: DrizzleDb, session: Session): Promise; /** * Append-able session id-list / notes columns (T11357). * * These three columns are JSON arrays that grow on session events. The * append-in-SQL helper below targets exactly this set. */ export type AppendableSessionColumn = 'notesJson' | 'tasksCompletedJson' | 'tasksCreatedJson'; /** * Append one element to a session's JSON-array column **in SQL** via * `json_insert(col, '$[#]', ?)` — no app-side read-modify-write of the whole * array (T11357 · AC4). * * ## Why `json_insert` (TEXT) and not `jsonb_insert` (BLOB) * * `sessions.{notes,tasks_completed,tasks_created}_json` are read WHOLE by * `rowToSession` (`safeParseJsonArray(row.notesJson)`) and by backup/export * paths. Storing them as a JSONB BLOB would force every one of those readers * onto `json(col)` and break the plain-column reads. `json_insert` performs the * same `$[#]` end-of-array append the audit calls for while keeping the column * canonical TEXT, so existing whole-value readers stay correct. The `$[#]` * path is the SQLite idiom for "append to the end of the array". * * The column is coalesced to `'[]'` first so an append onto a NULL/empty column * yields a single-element array rather than NULL. * * @param db - Drizzle sessions.db handle (tasks.db schema). * @param sessionId - Target session id. * @param column - Which appendable array column to grow. * @param value - The string element to append. */ export declare function appendSessionListItem(db: DrizzleDb, sessionId: string, column: AppendableSessionColumn, value: string): Promise; /** * Update dependencies for a task: delete existing, then re-insert. * Optionally filters by a set of valid IDs. */ export declare function updateDependencies(db: DrizzleDb, taskId: string, depends: string[], validIds?: Set): Promise; /** * Batch-update dependencies for multiple tasks in two bulk SQL operations. * Replaces per-task updateDependencies() loops with: * 1. Single DELETE for all task IDs * 2. Single INSERT for all dependency rows * * Callers are responsible for wrapping this in a transaction if needed. */ export declare function batchUpdateDependencies(db: DrizzleDb, tasks: Array<{ taskId: string; deps: string[]; }>, validIds?: Set): Promise; /** * Batch-load dependencies for a list of tasks and apply them in-place. * Uses inArray for efficient querying. Optionally filters by a set of valid IDs. */ export declare function loadDependenciesForTasks(db: DrizzleDb, tasks: Task[], validationIds?: Set): Promise; /** * Batch-load relations for a list of tasks and apply them in-place. * Mirrors loadDependenciesForTasks pattern for task_relations table (T5168). */ export declare function loadRelationsForTasks(db: DrizzleDb, tasks: Task[]): Promise; export {}; //# sourceMappingURL=db-helpers.d.ts.map