kanbo-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/better-sqlite3-fU3G4xnX.cjs +2254 -0
- package/dist/better-sqlite3-fU3G4xnX.cjs.map +1 -0
- package/dist/cli.cjs +42949 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/dist-Cd6wg8pp.cjs +942 -0
- package/dist/dist-Cd6wg8pp.cjs.map +1 -0
- package/dist/drizzle-postgres/0000_board.sql +135 -0
- package/dist/drizzle-postgres/0001_history_order.sql +2 -0
- package/dist/drizzle-postgres/0002_issue_pull_requests.sql +16 -0
- package/dist/drizzle-postgres/0003_milestone_start_date.sql +1 -0
- package/dist/drizzle-postgres/0004_status_entry_rules.sql +1 -0
- package/dist/drizzle-postgres/0005_run_external_session_ref.sql +1 -0
- package/dist/drizzle-postgres/README.md +61 -0
- package/dist/drizzle-postgres/meta/0000_snapshot.json +1078 -0
- package/dist/drizzle-postgres/meta/0001_snapshot.json +1090 -0
- package/dist/drizzle-postgres/meta/0002_snapshot.json +1239 -0
- package/dist/drizzle-postgres/meta/0003_snapshot.json +1245 -0
- package/dist/drizzle-postgres/meta/0004_snapshot.json +1251 -0
- package/dist/drizzle-postgres/meta/0005_snapshot.json +1257 -0
- package/dist/drizzle-postgres/meta/_journal.json +48 -0
- package/dist/drizzle-sqlite/0000_board.sql +133 -0
- package/dist/drizzle-sqlite/0001_issue_pull_requests.sql +15 -0
- package/dist/drizzle-sqlite/0002_milestone_start_date.sql +1 -0
- package/dist/drizzle-sqlite/0003_status_entry_rules.sql +1 -0
- package/dist/drizzle-sqlite/0004_run_external_session_ref.sql +1 -0
- package/dist/drizzle-sqlite/README.md +67 -0
- package/dist/drizzle-sqlite/meta/0000_snapshot.json +946 -0
- package/dist/drizzle-sqlite/meta/0001_snapshot.json +1054 -0
- package/dist/drizzle-sqlite/meta/0002_snapshot.json +1061 -0
- package/dist/drizzle-sqlite/meta/0003_snapshot.json +1068 -0
- package/dist/drizzle-sqlite/meta/0004_snapshot.json +1075 -0
- package/dist/drizzle-sqlite/meta/_journal.json +41 -0
- package/dist/lib/board-store-B16YarvX.d.mts +281 -0
- package/dist/lib/board-store-BHHo_ZlR.d.cts +281 -0
- package/dist/lib/index.cjs +340 -0
- package/dist/lib/index.cjs.map +1 -0
- package/dist/lib/index.d.cts +1608 -0
- package/dist/lib/index.d.mts +1608 -0
- package/dist/lib/index.mjs +284 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/manifest-C0EcwwgO.mjs +7866 -0
- package/dist/lib/manifest-C0EcwwgO.mjs.map +1 -0
- package/dist/lib/manifest-CpZOcB_6.cjs +8272 -0
- package/dist/lib/manifest-CpZOcB_6.cjs.map +1 -0
- package/dist/lib/mcp/index.cjs +184 -0
- package/dist/lib/mcp/index.cjs.map +1 -0
- package/dist/lib/mcp/index.d.cts +346 -0
- package/dist/lib/mcp/index.d.mts +346 -0
- package/dist/lib/mcp/index.mjs +173 -0
- package/dist/lib/mcp/index.mjs.map +1 -0
- package/dist/lib/migrate-BhYi9cXw.mjs +656 -0
- package/dist/lib/migrate-BhYi9cXw.mjs.map +1 -0
- package/dist/lib/migrate-DnMjTCvu.cjs +709 -0
- package/dist/lib/migrate-DnMjTCvu.cjs.map +1 -0
- package/dist/lib/migrations-path-CZxVOgeD.cjs +109 -0
- package/dist/lib/migrations-path-CZxVOgeD.cjs.map +1 -0
- package/dist/lib/migrations-path-Ciwan537.mjs +80 -0
- package/dist/lib/migrations-path-Ciwan537.mjs.map +1 -0
- package/dist/lib/postgres/index.cjs +56 -0
- package/dist/lib/postgres/index.cjs.map +1 -0
- package/dist/lib/postgres/index.d.cts +3403 -0
- package/dist/lib/postgres/index.d.mts +3403 -0
- package/dist/lib/postgres/index.mjs +35 -0
- package/dist/lib/postgres/index.mjs.map +1 -0
- package/dist/lib/roles.sql-B9rt-x4o.cjs +1033 -0
- package/dist/lib/roles.sql-B9rt-x4o.cjs.map +1 -0
- package/dist/lib/roles.sql-CvyCGt38.mjs +920 -0
- package/dist/lib/roles.sql-CvyCGt38.mjs.map +1 -0
- package/dist/lib/schema-epoch-ZyumVL_A.d.cts +16 -0
- package/dist/lib/schema-epoch-ZyumVL_A.d.mts +16 -0
- package/dist/lib/schema-fsHqNAip.d.cts +3612 -0
- package/dist/lib/schema-fsHqNAip.d.mts +3612 -0
- package/dist/lib/sqlite/index.cjs +23 -0
- package/dist/lib/sqlite/index.d.cts +175 -0
- package/dist/lib/sqlite/index.d.mts +175 -0
- package/dist/lib/sqlite/index.mjs +4 -0
- package/dist/lib/sqlite/schema.cjs +266 -0
- package/dist/lib/sqlite/schema.cjs.map +1 -0
- package/dist/lib/sqlite/schema.d.cts +2 -0
- package/dist/lib/sqlite/schema.d.mts +2 -0
- package/dist/lib/sqlite/schema.mjs +256 -0
- package/dist/lib/sqlite/schema.mjs.map +1 -0
- package/dist/lib/tool-names-9cmCn0gP.d.cts +112 -0
- package/dist/lib/tool-names-Dxg1UTUl.d.mts +112 -0
- package/dist/page/board.css +477 -0
- package/dist/page/board.js +2 -0
- package/drizzle-postgres/0000_board.sql +135 -0
- package/drizzle-postgres/0001_history_order.sql +2 -0
- package/drizzle-postgres/0002_issue_pull_requests.sql +16 -0
- package/drizzle-postgres/0003_milestone_start_date.sql +1 -0
- package/drizzle-postgres/0004_status_entry_rules.sql +1 -0
- package/drizzle-postgres/0005_run_external_session_ref.sql +1 -0
- package/drizzle-postgres/README.md +61 -0
- package/drizzle-postgres/meta/0000_snapshot.json +1078 -0
- package/drizzle-postgres/meta/0001_snapshot.json +1090 -0
- package/drizzle-postgres/meta/0002_snapshot.json +1239 -0
- package/drizzle-postgres/meta/0003_snapshot.json +1245 -0
- package/drizzle-postgres/meta/0004_snapshot.json +1251 -0
- package/drizzle-postgres/meta/0005_snapshot.json +1257 -0
- package/drizzle-postgres/meta/_journal.json +48 -0
- package/drizzle-sqlite/0000_board.sql +133 -0
- package/drizzle-sqlite/0001_issue_pull_requests.sql +15 -0
- package/drizzle-sqlite/0002_milestone_start_date.sql +1 -0
- package/drizzle-sqlite/0003_status_entry_rules.sql +1 -0
- package/drizzle-sqlite/0004_run_external_session_ref.sql +1 -0
- package/drizzle-sqlite/README.md +67 -0
- package/drizzle-sqlite/meta/0000_snapshot.json +946 -0
- package/drizzle-sqlite/meta/0001_snapshot.json +1054 -0
- package/drizzle-sqlite/meta/0002_snapshot.json +1061 -0
- package/drizzle-sqlite/meta/0003_snapshot.json +1068 -0
- package/drizzle-sqlite/meta/0004_snapshot.json +1075 -0
- package/drizzle-sqlite/meta/_journal.json +41 -0
- package/package.json +147 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-DnMjTCvu.cjs","names":["sql","issueRuns","issueComments","issueFieldChanges","issuePullRequests","issues","sql","issueStatuses","eq","issueMilestones","and","desc","inArray","asc","isNotNull","issueRelations","or","getTableColumns","kanbanMeta","require","createRequire","boardSqliteSchema","getTableConfig","BoardError","BOARD_TABLES_ADDED_WITHIN_EPOCH","dirname","fileURLToPath","resolveBoardMigrationsPath","hasMigrationJournal"],"sources":["../../src/sqlite/transaction.ts","../../src/sqlite/board-store.sqlite.ts","../../src/sqlite/open-database.ts","../../src/sqlite/migrate.ts"],"sourcesContent":["import type { Database as SqliteConnection } from 'better-sqlite3'\nimport { sql } from 'drizzle-orm'\nimport type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'\n\n/**\n * The database handle itself — what the host hands the board — and not an open\n * transaction on it: the helper reaches for the driver connection, which a\n * drizzle `SQLiteTransaction` does not carry.\n *\n * The schema parameter is `Record<string, unknown>` rather than any particular\n * schema, which is what lets one type stand for all three of the files a board\n * can live in (ruling 5-3): a handle drizzle was given a host app's whole schema\n * satisfies it, and so does one given only the board's own nine tables. The\n * board never reaches for `database.query`, so the relational builder those\n * generics shape is not something this package gives up by asking for less —\n * `src/sqlite/database-type.test.ts` is the probe that says both handles still\n * arrive here.\n */\nexport type SqliteDatabase = BetterSQLite3Database<Record<string, unknown>>\n\n/**\n * The better-sqlite3 handle Drizzle keeps beside the query builder. `drizzle()`\n * attaches it as `$client` (`drizzle-orm/better-sqlite3/driver.js`), but the\n * handle is declared as a `BetterSQLite3Database`, a class that does not\n * declare `$client` — which is why the assertion below restates the shape by\n * hand. Its `inTransaction` is the only honest answer to \"is a\n * transaction already open\", including one this module did not open.\n */\nfunction readSqliteConnection(database: SqliteDatabase): SqliteConnection {\n return (database as SqliteDatabase & { $client: SqliteConnection }).$client\n}\n\n/** The statements that open, keep and undo one transaction scope. */\ninterface TransactionScope {\n begin: string\n commit: string\n undo: string\n /** A savepoint has to be released after its rollback; the outer scope has not. */\n releaseAfterUndo: string | null\n}\n\n/**\n * One fixed name for every nested scope, the way better-sqlite3 names its own\n * (`lib/methods/transaction.js`). `release` and `rollback to` address the\n * innermost savepoint carrying the name, and these scopes are opened and closed\n * in stack order, so each call reaches its own — a counter in the name would\n * only repeat what SQLite already tracks, and would collide the moment two\n * scopes nested through different code paths.\n */\nconst SAVEPOINT_NAME = 'kanbo_tx'\n\n/**\n * How the outer `begin` takes its write lock. `deferred` — the SQLite default —\n * waits for the first write; `immediate` takes the lock up front, so two\n * processes racing for the same file fail fast instead of half-way through.\n * A nested scope is a savepoint and takes no lock of its own, so the mode only\n * reaches the outer statement.\n */\nexport type SqliteTransactionMode = 'deferred' | 'immediate'\n\nfunction readTransactionScope(connection: SqliteConnection, mode: SqliteTransactionMode): TransactionScope {\n if (!connection.inTransaction) {\n return {\n begin: mode === 'immediate' ? 'begin immediate' : 'begin',\n commit: 'commit',\n undo: 'rollback',\n releaseAfterUndo: null,\n }\n }\n return {\n begin: `savepoint ${SAVEPOINT_NAME}`,\n commit: `release ${SAVEPOINT_NAME}`,\n undo: `rollback to ${SAVEPOINT_NAME}`,\n releaseAfterUndo: `release ${SAVEPOINT_NAME}`,\n }\n}\n\n/**\n * Run `fn` inside one SQLite transaction that is allowed to await.\n *\n * `database.transaction(...)` cannot serve here: it commits the moment its\n * callback returns, and an async callback returns a pending promise, so the\n * transaction would close before the work inside it ran. The statements are\n * issued by hand instead, and the driver's own `inTransaction` flag picks which\n * — the same branch better-sqlite3 takes. With nothing open: `begin`, then\n * `commit`, or `rollback` when `fn` throws. With a transaction already open —\n * another call to this helper, or one a module opened through\n * `database.transaction` — a `savepoint` … `release` pair, undone by\n * `rollback to` followed by its own `release`. A second `begin` would throw,\n * which is why the flag decides and not a private counter. When SQLite has\n * already aborted the transaction itself the undo is skipped: it would throw\n * over the caller's error.\n *\n * **The callback must be the only database-touching async chain in flight —\n * no `Promise.all` sibling, no lock hand-off, no fire-and-forget promise\n * started before it — and inside it, only await operations that resolve\n * without I/O: store methods, synchronous database writes.** Even a resolved\n * promise yields to the microtask queue first, so a sibling chain already\n * queued in the same macrotask still runs inside this open transaction; a\n * chain started in a separate macrotask is unaffected. Awaiting anything that\n * truly suspends — a timer, the filesystem, the network — hands the\n * connection to whatever runs next, and its writes land in this transaction\n * too.\n */\nexport async function runSqliteTransaction<T>(\n database: SqliteDatabase,\n fn: () => Promise<T>,\n options?: { mode?: SqliteTransactionMode },\n): Promise<T> {\n const connection = readSqliteConnection(database)\n const scope = readTransactionScope(connection, options?.mode ?? 'deferred')\n\n database.run(sql.raw(scope.begin))\n try {\n const result = await fn()\n database.run(sql.raw(scope.commit))\n return result\n }\n catch (error) {\n if (connection.inTransaction) {\n database.run(sql.raw(scope.undo))\n if (scope.releaseAfterUndo) {\n database.run(sql.raw(scope.releaseAfterUndo))\n }\n }\n throw error\n }\n}\n","import { and, asc, desc, eq, getTableColumns, inArray, isNotNull, or, sql } from 'drizzle-orm'\n\nimport type {\n BoardActiveRun,\n BoardRunProjection,\n BoardRunProjectionScope,\n BoardStore,\n BoardTransactionMode,\n IssueCommentStore,\n IssueContextRefStore,\n IssueFieldChangeStore,\n IssueMilestoneStore,\n IssuePullRequestStore,\n IssueRelationStore,\n IssueRowStore,\n IssueRunStore,\n IssueStatusStore,\n KanbanMetaStore,\n} from '../board-store'\nimport {\n issueComments,\n issueFieldChanges,\n issueMilestones,\n issuePullRequests,\n issueRelations,\n issueRuns,\n issues,\n issueStatuses,\n kanbanMeta,\n} from './schema'\nimport type { SqliteDatabase } from './transaction'\nimport { runSqliteTransaction } from './transaction'\n\n/**\n * One `BoardStore` group with the `Promise` taken off every method: better-sqlite3\n * is synchronous, so the core returns rows directly and the facade below is what\n * adds the promises back.\n */\ntype SyncStoreGroup<TGroup> = {\n [TMethod in keyof TGroup]: TGroup[TMethod] extends (...args: infer TArgs) => Promise<infer TResult>\n ? (...args: TArgs) => TResult\n : never\n}\n\n/**\n * The synchronous core of the SQLite store — the methods `BoardStore` declares,\n * resolved rather than promised.\n *\n * Every statement in the module runs through this core; the store returned by\n * `createSqliteBoardStore` is a thin async wrapper over it. Transactions are not\n * part of it: only the facade offers one, because only an awaitable transaction\n * can hold a `BoardStore` callback open.\n */\ninterface SqliteBoardStoreCore {\n statuses: SyncStoreGroup<IssueStatusStore>\n milestones: SyncStoreGroup<IssueMilestoneStore>\n issues: SyncStoreGroup<IssueRowStore>\n comments: SyncStoreGroup<IssueCommentStore>\n runs: SyncStoreGroup<IssueRunStore>\n relations: SyncStoreGroup<IssueRelationStore>\n contextRefs: SyncStoreGroup<IssueContextRefStore>\n fieldChanges: SyncStoreGroup<IssueFieldChangeStore>\n pullRequests: SyncStoreGroup<IssuePullRequestStore>\n meta: SyncStoreGroup<KanbanMetaStore>\n}\n\n/**\n * The order two runs of one card are in when they started in the same second.\n *\n * `started_at` counts whole seconds and a run id is a random UUID, so the column\n * pair the attempt number is built on cannot separate two launches a second\n * apart — the row the board wrote second has to be the second attempt. SQLite\n * hands that out already: `rowid` rises with every insert. A store on another\n * engine answers the same question with whatever its own insert order is.\n */\nconst RUN_INSERTION_ORDER = sql`${issueRuns}.rowid asc`\n\n/**\n * The same tie-break for the two tables that record what happened to a card.\n *\n * `created_at` counts whole seconds here too, and a person approving a card and\n * an agent commenting on it inside the same second must come back in the order\n * the board wrote them: `readApproval` takes the *last* comment to decide\n * whether the card was approved or returned.\n */\nconst COMMENT_INSERTION_ORDER = sql`${issueComments}.rowid asc`\nconst FIELD_CHANGE_INSERTION_ORDER = sql`${issueFieldChanges}.rowid asc`\n\n/** And for a card's pull-request links, which list in the order they were made. */\nconst PULL_REQUEST_INSERTION_ORDER = sql`${issuePullRequests}.rowid asc`\n\n/**\n * The run facts of every card in scope, in one statement.\n *\n * The window functions do the grouping the caller would otherwise do with a\n * second query per card: `count(*)` counts the card's runs on every one of its\n * rows, and `row_number()` ranks them so that a `running` run wins, then the\n * newest start, then the row written last — the tie-break two runs started in\n * the same second need. Reading only rank 1 therefore leaves one row per card, carrying\n * the run the board should show — and `state` on that row says whether it is\n * still going, which is what turns it into an `activeRun` or into `null`.\n */\nfunction selectRunProjection(database: SqliteDatabase, scope: BoardRunProjectionScope): BoardRunProjection[] {\n if ('issueIds' in scope && scope.issueIds.length === 0) {\n return []\n }\n\n const cards = 'workspaceId' in scope\n ? sql`select ${issues.id} from ${issues} where ${issues.workspaceId} = ${scope.workspaceId}`\n : sql`values ${sql.join(scope.issueIds.map(issueId => sql`(${issueId})`), sql`, `)}`\n\n const rows = database.all<{\n issueId: string\n attemptCount: number\n id: string\n agentId: string | null\n agentName: string\n state: BoardActiveRun['state']\n branch: string | null\n executionMode: BoardActiveRun['executionMode']\n chatSessionId: string | null\n startedAt: number\n }>(sql`\n select\n ranked.issue_id as issueId,\n ranked.attempt_count as attemptCount,\n ranked.id as id,\n ranked.agent_id as agentId,\n ranked.agent_name as agentName,\n ranked.state as state,\n ranked.branch as branch,\n ranked.execution_mode as executionMode,\n ranked.chat_session_id as chatSessionId,\n ranked.started_at as startedAt\n from (\n select\n run.*,\n count(*) over (partition by run.issue_id) as attempt_count,\n row_number() over (\n partition by run.issue_id\n order by (run.state = 'running') desc, run.started_at desc, run.rowid desc\n ) as card_rank\n from ${issueRuns} as run\n where run.issue_id in (${cards})\n ) as ranked\n where ranked.card_rank = 1\n `)\n\n return rows.map(row => ({\n issueId: row.issueId,\n attemptCount: row.attemptCount,\n activeRun: row.state === 'running'\n ? {\n id: row.id,\n agentId: row.agentId,\n agentName: row.agentName,\n state: row.state,\n branch: row.branch,\n executionMode: row.executionMode,\n chatSessionId: row.chatSessionId,\n startedAt: row.startedAt,\n }\n : null,\n }))\n}\n\n/**\n * Bind the store's statements to one SQLite handle. `resolveDatabase` is called\n * per statement rather than captured, so the store follows a host that swaps its\n * database out (a server that resets its infrastructure between tests, say).\n */\nfunction createSqliteBoardStoreCore(resolveDatabase: () => SqliteDatabase): SqliteBoardStoreCore {\n return {\n statuses: {\n listByWorkspace: workspaceId => resolveDatabase()\n .select()\n .from(issueStatuses)\n .where(eq(issueStatuses.workspaceId, workspaceId))\n .orderBy(issueStatuses.order)\n .all(),\n listAllInBoardOrder: () => resolveDatabase()\n .select()\n .from(issueStatuses)\n .orderBy(issueStatuses.workspaceId, issueStatuses.order)\n .all(),\n listAll: () => resolveDatabase().select().from(issueStatuses).all(),\n findById: statusId => resolveDatabase()\n .select()\n .from(issueStatuses)\n .where(eq(issueStatuses.id, statusId))\n .get() ?? null,\n findInWorkspace: (workspaceId, statusId) => resolveDatabase()\n .select()\n .from(issueStatuses)\n .where(sql`${issueStatuses.workspaceId} = ${workspaceId} AND ${issueStatuses.id} = ${statusId}`)\n .get() ?? null,\n countByWorkspace: (workspaceId) => {\n const row = resolveDatabase()\n .select({ count: sql<number>`count(*)` })\n .from(issueStatuses)\n .where(eq(issueStatuses.workspaceId, workspaceId))\n .get()\n return row?.count ?? 0\n },\n create: values => resolveDatabase().insert(issueStatuses).values(values).returning().get(),\n update: (statusId, patch) => {\n resolveDatabase().update(issueStatuses).set(patch).where(eq(issueStatuses.id, statusId)).run()\n },\n delete: (statusId) => {\n resolveDatabase().delete(issueStatuses).where(eq(issueStatuses.id, statusId)).run()\n },\n },\n\n milestones: {\n listNewestFirst: (workspaceId) => {\n const predicates = [\n workspaceId ? eq(issueMilestones.workspaceId, workspaceId) : undefined,\n ].filter(predicate => predicate !== undefined)\n return resolveDatabase()\n .select()\n .from(issueMilestones)\n .where(predicates.length > 0 ? and(...predicates) : undefined)\n .orderBy(desc(issueMilestones.createdAt))\n .all()\n },\n listByWorkspace: workspaceId => resolveDatabase()\n .select()\n .from(issueMilestones)\n .where(eq(issueMilestones.workspaceId, workspaceId))\n .all(),\n listAll: () => resolveDatabase().select().from(issueMilestones).all(),\n findById: milestoneId => resolveDatabase()\n .select()\n .from(issueMilestones)\n .where(eq(issueMilestones.id, milestoneId))\n .get() ?? null,\n findInWorkspace: (workspaceId, milestoneId) => resolveDatabase()\n .select()\n .from(issueMilestones)\n .where(and(eq(issueMilestones.id, milestoneId), eq(issueMilestones.workspaceId, workspaceId)))\n .get() ?? null,\n create: values => resolveDatabase().insert(issueMilestones).values(values).returning().get(),\n update: (milestoneId, patch) => {\n resolveDatabase().update(issueMilestones).set(patch).where(eq(issueMilestones.id, milestoneId)).run()\n },\n delete: (milestoneId) => {\n resolveDatabase().delete(issueMilestones).where(eq(issueMilestones.id, milestoneId)).run()\n },\n },\n\n issues: {\n listInBoardOrder: (workspaceId) => {\n const predicates = [\n workspaceId ? eq(issues.workspaceId, workspaceId) : undefined,\n ].filter(predicate => predicate !== undefined)\n return resolveDatabase()\n .select()\n .from(issues)\n .where(predicates.length > 0 ? and(...predicates) : undefined)\n // Board position is authoritative; creation time only breaks ties between\n // issues that have never been dragged (all of which share order 0).\n .orderBy(issues.order, desc(issues.createdAt))\n .all()\n },\n listNewestFirst: () => resolveDatabase().select().from(issues).orderBy(desc(issues.createdAt)).all(),\n listAll: () => resolveDatabase().select().from(issues).all(),\n listByWorkspace: workspaceId => resolveDatabase()\n .select()\n .from(issues)\n .where(eq(issues.workspaceId, workspaceId))\n .all(),\n listByIds: issueIds => resolveDatabase().select().from(issues).where(inArray(issues.id, issueIds)).all(),\n findById: issueId => resolveDatabase().select().from(issues).where(eq(issues.id, issueId)).get() ?? null,\n findInWorkspace: (workspaceId, issueId) => resolveDatabase()\n .select()\n .from(issues)\n .where(and(eq(issues.id, issueId), eq(issues.workspaceId, workspaceId)))\n .get() ?? null,\n findByNumber: (workspaceId, number) => resolveDatabase()\n .select()\n .from(issues)\n .where(and(eq(issues.workspaceId, workspaceId), eq(issues.number, number)))\n .get() ?? null,\n maxNumber: (workspaceId) => {\n const row = resolveDatabase()\n .select({ maxNum: sql<number>`coalesce(max(${issues.number}), 0)` })\n .from(issues)\n .where(eq(issues.workspaceId, workspaceId))\n .get()\n return row?.maxNum ?? 0\n },\n maxOrder: (workspaceId) => {\n const row = resolveDatabase()\n .select({ maxOrder: sql<number>`coalesce(max(${issues.order}), 0)` })\n .from(issues)\n .where(eq(issues.workspaceId, workspaceId))\n .get()\n return row?.maxOrder ?? 0\n },\n create: values => resolveDatabase().insert(issues).values(values).returning().get(),\n update: (issueId, patch) => {\n resolveDatabase().update(issues).set(patch).where(eq(issues.id, issueId)).run()\n },\n updateMany: (issueIds, patch) => resolveDatabase()\n .update(issues)\n .set(patch)\n .where(sql`${issues.id} IN (${sql.join(issueIds.map(issueId => sql`${issueId}`), sql`, `)})`)\n .run()\n .changes,\n clearMilestoneReferences: (milestoneId, updatedAt) => {\n resolveDatabase()\n .update(issues)\n .set({ milestoneId: null, updatedAt })\n .where(eq(issues.milestoneId, milestoneId))\n .run()\n },\n clearParentReferences: (parentIssueId, updatedAt) => {\n resolveDatabase()\n .update(issues)\n .set({ parentIssueId: null, updatedAt })\n .where(eq(issues.parentIssueId, parentIssueId))\n .run()\n },\n clearDelegateReferences: (delegate, updatedAt) => resolveDatabase()\n .update(issues)\n .set({ delegateAgentId: null, delegateProviderTargetId: null, updatedAt })\n .where('agentId' in delegate\n ? eq(issues.delegateAgentId, delegate.agentId)\n : eq(issues.delegateProviderTargetId, delegate.providerTargetId))\n .run()\n .changes,\n delete: (issueId) => {\n resolveDatabase().delete(issues).where(eq(issues.id, issueId)).run()\n },\n },\n\n comments: {\n listByIssue: issueId => resolveDatabase()\n .select()\n .from(issueComments)\n .where(eq(issueComments.issueId, issueId))\n .orderBy(asc(issueComments.createdAt), COMMENT_INSERTION_ORDER)\n .all(),\n findById: commentId => resolveDatabase()\n .select()\n .from(issueComments)\n .where(eq(issueComments.id, commentId))\n .get() ?? null,\n findByDedupeKey: (issueId, dedupeKey) => resolveDatabase()\n .select()\n .from(issueComments)\n .where(and(eq(issueComments.issueId, issueId), eq(issueComments.dedupeKey, dedupeKey)))\n .get() ?? null,\n // `substr` rather than `like`: a key may carry `_` or `%`, which `like`\n // would read as wildcards.\n listByDedupeKeyPrefix: (issueId, prefix) => resolveDatabase()\n .select()\n .from(issueComments)\n .where(and(\n eq(issueComments.issueId, issueId),\n sql`substr(${issueComments.dedupeKey}, 1, ${prefix.length}) = ${prefix}`,\n ))\n .orderBy(asc(issueComments.createdAt), COMMENT_INSERTION_ORDER)\n .all(),\n create: values => resolveDatabase().insert(issueComments).values(values).returning().get(),\n createOnce: (values) => {\n const database = resolveDatabase()\n // `do nothing` returns no row when the key is already taken, and the\n // read below then finds whichever row holds it.\n const inserted = database.insert(issueComments).values(values).onConflictDoNothing().returning().get()\n if (inserted) {\n return inserted\n }\n const existing = database\n .select()\n .from(issueComments)\n .where(and(eq(issueComments.issueId, values.issueId), eq(issueComments.dedupeKey, values.dedupeKey)))\n .get()\n if (!existing) {\n // The insert was refused, and not by the dedupe index: some other\n // constraint on the row said no. Returning nothing here would hand the\n // caller an `IssueComment` that does not exist.\n throw new Error(\n `issue comment for ${values.issueId} was refused, and no comment carries dedupe key ${values.dedupeKey}`,\n )\n }\n return existing\n },\n delete: (commentId) => {\n resolveDatabase().delete(issueComments).where(eq(issueComments.id, commentId)).run()\n },\n },\n\n runs: {\n listByIssue: issueId => resolveDatabase()\n .select()\n .from(issueRuns)\n .where(eq(issueRuns.issueId, issueId))\n .orderBy(asc(issueRuns.startedAt), RUN_INSERTION_ORDER)\n .all(),\n listRunning: issueId => resolveDatabase()\n .select()\n .from(issueRuns)\n .where(and(eq(issueRuns.issueId, issueId), eq(issueRuns.state, 'running')))\n .orderBy(asc(issueRuns.startedAt), RUN_INSERTION_ORDER)\n .all(),\n listRunningWithSession: () => resolveDatabase()\n .select()\n .from(issueRuns)\n .where(and(eq(issueRuns.state, 'running'), isNotNull(issueRuns.chatSessionId)))\n .orderBy(asc(issueRuns.startedAt), RUN_INSERTION_ORDER)\n .all(),\n findById: runId => resolveDatabase().select().from(issueRuns).where(eq(issueRuns.id, runId)).get() ?? null,\n findByChatSessionId: chatSessionId => resolveDatabase()\n .select()\n .from(issueRuns)\n .where(eq(issueRuns.chatSessionId, chatSessionId))\n .orderBy(desc(issueRuns.startedAt), sql`${issueRuns}.rowid desc`)\n .get() ?? null,\n countByIssue: (issueId) => {\n const row = resolveDatabase()\n .select({ count: sql<number>`count(*)` })\n .from(issueRuns)\n .where(eq(issueRuns.issueId, issueId))\n .get()\n return row?.count ?? 0\n },\n create: values => resolveDatabase().insert(issueRuns).values(values).returning().get(),\n update: (runId, patch) => {\n resolveDatabase().update(issueRuns).set(patch).where(eq(issueRuns.id, runId)).run()\n },\n project: scope => selectRunProjection(resolveDatabase(), scope),\n },\n\n relations: {\n listByIssue: issueId => resolveDatabase()\n .select()\n .from(issueRelations)\n .where(or(eq(issueRelations.sourceIssueId, issueId), eq(issueRelations.targetIssueId, issueId)))\n .all(),\n findById: relationId => resolveDatabase()\n .select()\n .from(issueRelations)\n .where(eq(issueRelations.id, relationId))\n .get() ?? null,\n findMatching: edge => resolveDatabase()\n .select()\n .from(issueRelations)\n .where(and(\n eq(issueRelations.type, edge.type),\n edge.type === 'relates_to'\n ? or(\n and(\n eq(issueRelations.sourceIssueId, edge.sourceIssueId),\n eq(issueRelations.targetIssueId, edge.targetIssueId),\n ),\n and(\n eq(issueRelations.sourceIssueId, edge.targetIssueId),\n eq(issueRelations.targetIssueId, edge.sourceIssueId),\n ),\n )\n : and(\n eq(issueRelations.sourceIssueId, edge.sourceIssueId),\n eq(issueRelations.targetIssueId, edge.targetIssueId),\n ),\n ))\n .get() ?? null,\n create: values => resolveDatabase().insert(issueRelations).values(values).returning().get(),\n delete: (relationId) => {\n resolveDatabase().delete(issueRelations).where(eq(issueRelations.id, relationId)).run()\n },\n deleteByIssue: (issueId) => {\n resolveDatabase()\n .delete(issueRelations)\n .where(or(eq(issueRelations.sourceIssueId, issueId), eq(issueRelations.targetIssueId, issueId)))\n .run()\n },\n },\n\n contextRefs: {\n replace: (issueId, contextRefs, updatedAt) => {\n resolveDatabase().update(issues).set({ contextRefs, updatedAt }).where(eq(issues.id, issueId)).run()\n },\n },\n\n fieldChanges: {\n listByIssue: issueId => resolveDatabase()\n .select()\n .from(issueFieldChanges)\n .where(eq(issueFieldChanges.issueId, issueId))\n .orderBy(asc(issueFieldChanges.createdAt), FIELD_CHANGE_INSERTION_ORDER)\n .all(),\n listByIssueField: (issueId, field) => resolveDatabase()\n .select()\n .from(issueFieldChanges)\n .where(and(eq(issueFieldChanges.issueId, issueId), eq(issueFieldChanges.field, field)))\n .orderBy(asc(issueFieldChanges.createdAt), FIELD_CHANGE_INSERTION_ORDER)\n .all(),\n create: (values) => {\n resolveDatabase().insert(issueFieldChanges).values(values).run()\n },\n },\n\n pullRequests: {\n listByIssue: issueId => resolveDatabase()\n .select()\n .from(issuePullRequests)\n .where(eq(issuePullRequests.issueId, issueId))\n .orderBy(asc(issuePullRequests.createdAt), PULL_REQUEST_INSERTION_ORDER)\n .all(),\n listLinked: () => resolveDatabase()\n .select({ ...getTableColumns(issuePullRequests), workspaceId: issues.workspaceId })\n .from(issuePullRequests)\n .innerJoin(issues, eq(issues.id, issuePullRequests.issueId))\n .orderBy(asc(issuePullRequests.createdAt), PULL_REQUEST_INSERTION_ORDER)\n .all(),\n link: (values) => {\n const database = resolveDatabase()\n // `do nothing` returns no row when the card already names the pull\n // request, and the read below then finds the link that does.\n const inserted = database.insert(issuePullRequests).values(values).onConflictDoNothing().returning().get()\n if (inserted) {\n return inserted\n }\n const existing = database\n .select()\n .from(issuePullRequests)\n .where(and(\n eq(issuePullRequests.issueId, values.issueId),\n eq(issuePullRequests.owner, values.owner),\n eq(issuePullRequests.repo, values.repo),\n eq(issuePullRequests.number, values.number),\n ))\n .get()\n if (!existing) {\n // Refused, and not by the one index that makes a link idempotent.\n throw new Error(\n `pull request link for ${values.issueId} was refused, and the card names no ${values.owner}/${values.repo}#${values.number}`,\n )\n }\n return existing\n },\n unlink: (issueId, linkId) => resolveDatabase()\n .delete(issuePullRequests)\n .where(and(eq(issuePullRequests.id, linkId), eq(issuePullRequests.issueId, issueId)))\n .returning()\n .get() ?? null,\n },\n\n meta: {\n read: key => resolveDatabase().select().from(kanbanMeta).where(eq(kanbanMeta.key, key)).get() ?? null,\n bumpRevision: (key, updatedAt) => {\n resolveDatabase()\n .insert(kanbanMeta)\n .values({ key, revision: 1, updatedAt })\n .onConflictDoUpdate({\n target: kanbanMeta.key,\n set: { revision: sql`${kanbanMeta.revision} + 1`, updatedAt },\n })\n .run()\n },\n },\n }\n}\n\n/**\n * `BoardStore` backed by SQLite: every method resolves immediately, because the\n * core underneath it is synchronous.\n *\n * `database` is a function rather than a handle so the store follows whatever\n * the host currently has open — a server passes its single connection\n * accessor, a standalone reader passes its own.\n */\nexport function createSqliteBoardStore(options: { database: () => SqliteDatabase }): BoardStore {\n const core = createSqliteBoardStoreCore(options.database)\n\n const store: BoardStore = {\n statuses: {\n listByWorkspace: async workspaceId => core.statuses.listByWorkspace(workspaceId),\n listAllInBoardOrder: async () => core.statuses.listAllInBoardOrder(),\n listAll: async () => core.statuses.listAll(),\n findById: async statusId => core.statuses.findById(statusId),\n findInWorkspace: async (workspaceId, statusId) => core.statuses.findInWorkspace(workspaceId, statusId),\n countByWorkspace: async workspaceId => core.statuses.countByWorkspace(workspaceId),\n create: async values => core.statuses.create(values),\n update: async (statusId, patch) => core.statuses.update(statusId, patch),\n delete: async statusId => core.statuses.delete(statusId),\n },\n\n milestones: {\n listNewestFirst: async workspaceId => core.milestones.listNewestFirst(workspaceId),\n listByWorkspace: async workspaceId => core.milestones.listByWorkspace(workspaceId),\n listAll: async () => core.milestones.listAll(),\n findById: async milestoneId => core.milestones.findById(milestoneId),\n findInWorkspace: async (workspaceId, milestoneId) => core.milestones.findInWorkspace(workspaceId, milestoneId),\n create: async values => core.milestones.create(values),\n update: async (milestoneId, patch) => core.milestones.update(milestoneId, patch),\n delete: async milestoneId => core.milestones.delete(milestoneId),\n },\n\n issues: {\n listInBoardOrder: async workspaceId => core.issues.listInBoardOrder(workspaceId),\n listNewestFirst: async () => core.issues.listNewestFirst(),\n listAll: async () => core.issues.listAll(),\n listByWorkspace: async workspaceId => core.issues.listByWorkspace(workspaceId),\n listByIds: async issueIds => core.issues.listByIds(issueIds),\n findById: async issueId => core.issues.findById(issueId),\n findInWorkspace: async (workspaceId, issueId) => core.issues.findInWorkspace(workspaceId, issueId),\n findByNumber: async (workspaceId, number) => core.issues.findByNumber(workspaceId, number),\n maxNumber: async workspaceId => core.issues.maxNumber(workspaceId),\n maxOrder: async workspaceId => core.issues.maxOrder(workspaceId),\n create: async values => core.issues.create(values),\n update: async (issueId, patch) => core.issues.update(issueId, patch),\n updateMany: async (issueIds, patch) => core.issues.updateMany(issueIds, patch),\n clearMilestoneReferences: async (milestoneId, updatedAt) => core.issues.clearMilestoneReferences(milestoneId, updatedAt),\n clearParentReferences: async (parentIssueId, updatedAt) => core.issues.clearParentReferences(parentIssueId, updatedAt),\n clearDelegateReferences: async (delegate, updatedAt) => core.issues.clearDelegateReferences(delegate, updatedAt),\n delete: async issueId => core.issues.delete(issueId),\n },\n\n comments: {\n listByIssue: async issueId => core.comments.listByIssue(issueId),\n findById: async commentId => core.comments.findById(commentId),\n findByDedupeKey: async (issueId, dedupeKey) => core.comments.findByDedupeKey(issueId, dedupeKey),\n listByDedupeKeyPrefix: async (issueId, prefix) => core.comments.listByDedupeKeyPrefix(issueId, prefix),\n create: async values => core.comments.create(values),\n createOnce: async values => core.comments.createOnce(values),\n delete: async commentId => core.comments.delete(commentId),\n },\n\n runs: {\n listByIssue: async issueId => core.runs.listByIssue(issueId),\n listRunning: async issueId => core.runs.listRunning(issueId),\n listRunningWithSession: async () => core.runs.listRunningWithSession(),\n findById: async runId => core.runs.findById(runId),\n findByChatSessionId: async chatSessionId => core.runs.findByChatSessionId(chatSessionId),\n countByIssue: async issueId => core.runs.countByIssue(issueId),\n create: async values => core.runs.create(values),\n update: async (runId, patch) => core.runs.update(runId, patch),\n project: async scope => core.runs.project(scope),\n },\n\n relations: {\n listByIssue: async issueId => core.relations.listByIssue(issueId),\n findById: async relationId => core.relations.findById(relationId),\n findMatching: async edge => core.relations.findMatching(edge),\n create: async values => core.relations.create(values),\n delete: async relationId => core.relations.delete(relationId),\n deleteByIssue: async issueId => core.relations.deleteByIssue(issueId),\n },\n\n contextRefs: {\n replace: async (issueId, contextRefs, updatedAt) => core.contextRefs.replace(issueId, contextRefs, updatedAt),\n },\n\n fieldChanges: {\n listByIssue: async issueId => core.fieldChanges.listByIssue(issueId),\n listByIssueField: async (issueId, field) => core.fieldChanges.listByIssueField(issueId, field),\n create: async values => core.fieldChanges.create(values),\n },\n\n pullRequests: {\n listByIssue: async issueId => core.pullRequests.listByIssue(issueId),\n listLinked: async () => core.pullRequests.listLinked(),\n link: async values => core.pullRequests.link(values),\n unlink: async (issueId, linkId) => core.pullRequests.unlink(issueId, linkId),\n },\n\n meta: {\n read: async key => core.meta.read(key),\n bumpRevision: async (key, updatedAt) => core.meta.bumpRevision(key, updatedAt),\n },\n\n /**\n * Run `fn` inside one SQLite transaction, through the driver-level helper in\n * `./transaction.ts`.\n *\n * The caller rule lives there in full; in short: `fn` must be the only\n * database-touching async chain in flight, and inside it only await\n * operations that resolve without I/O — store methods, synchronous database\n * writes.\n */\n transaction: async <T>(fn: (tx: BoardStore) => Promise<T>, transactionOptions?: { mode?: BoardTransactionMode }): Promise<T> => {\n // One connection backs the whole store, so it is already the transactional view.\n return await runSqliteTransaction(options.database(), () => fn(store), transactionOptions)\n },\n }\n\n return store\n}\n","import { createRequire } from 'node:module'\n\nimport type { Database as SqliteConnection, Options as SqliteConnectionOptions } from 'better-sqlite3'\nimport { sql } from 'drizzle-orm'\nimport { getTableConfig } from 'drizzle-orm/sqlite-core'\n\nimport { BoardError } from '../domain/errors'\nimport { BOARD_SCHEMA_EPOCH, BOARD_TABLES_ADDED_WITHIN_EPOCH } from '../schema-epoch'\nimport { boardSqliteSchema } from './schema'\nimport type { SqliteDatabase } from './transaction'\n\nexport { BOARD_SCHEMA_EPOCH }\n\nconst require = createRequire(import.meta.url)\n\n/** The tables a board file must already carry for this package to read it. */\nconst REQUIRED_BOARD_TABLES = ['issue_runs', 'kanban_meta'] as const\n\n/** One open board file: the drizzle handle, the driver beneath it, and the way to let go. */\nexport interface BoardDatabase {\n database: SqliteDatabase\n connection: SqliteConnection\n close: () => void\n}\n\n/** What a caller is told when the native module a board file needs is not installed. */\nconst BETTER_SQLITE3_MISSING_MESSAGE = 'Install better-sqlite3 to use a board file'\n\n/**\n * `better-sqlite3` is an optional peer dependency (ruling 5): a Postgres-only\n * or capabilities-only install carries no native module, and importing\n * `kanbo/sqlite` for a type or for `runSqliteTransaction` should not\n * demand one either. So the require happens here, the one place a board file\n * is actually opened, rather than at the top of this module.\n */\nfunction loadBetterSqlite3(): new (path: string, options?: SqliteConnectionOptions) => SqliteConnection {\n try {\n return require('better-sqlite3')\n }\n catch (error) {\n throw new Error(BETTER_SQLITE3_MISSING_MESSAGE, { cause: error })\n }\n}\n\n/**\n * Drizzle's own better-sqlite3 driver, and why it is not imported at the top of\n * this file like every other import in the package.\n *\n * `drizzle-orm/better-sqlite3` imports the native module eagerly — it has an\n * overload where `drizzle()` constructs the connection itself — so a static\n * import of it makes `better-sqlite3` required by everything that can reach\n * this module, `kanbo capabilities` and `kanbo/mcp` included, and the\n * friendly message above becomes unreachable. A dynamic import keeps the whole\n * chain behind the one call that actually opens a file, which is what makes the\n * optional peer honest. `createRequire` would be the smaller change and is the\n * wrong one: it would load drizzle's CJS copy beside the ESM one this package\n * already holds, and it would resolve from `node_modules`, which the packaged\n * `dist/cli.cjs` deliberately does not have beside it.\n *\n * The module is remembered after the first load, so a command that opens two\n * board files pays for it once.\n */\nlet sqliteDriver: typeof import('drizzle-orm/better-sqlite3') | null = null\n\nasync function loadSqliteDriver(): Promise<typeof import('drizzle-orm/better-sqlite3')> {\n sqliteDriver ??= await import('drizzle-orm/better-sqlite3')\n return sqliteDriver\n}\n\n/**\n * Open a board file with the runtime pragmas a long-running server sets on its\n * own database, so a board read from a terminal behaves the same as one read\n * from inside an app. Migrations are not applied here: the file's owner applies\n * them (`kanbo init --file`, `kanbo migrate`, or the app the database belongs to).\n *\n * Asynchronous because the driver beneath it is loaded on demand — see\n * `loadSqliteDriver`. The native module is asked for first, so an install\n * without it hears the sentence above rather than a module-resolution stack\n * from inside drizzle.\n */\nexport async function openBoardDatabase(path: string): Promise<BoardDatabase> {\n const Database = loadBetterSqlite3()\n const { drizzle } = await loadSqliteDriver()\n const connection = new Database(path)\n connection.pragma('foreign_keys = ON')\n connection.pragma('journal_mode = WAL')\n connection.pragma('busy_timeout = 5000')\n // NORMAL is the recommended durability tradeoff with WAL (vs FULL fsync on every commit).\n connection.pragma('synchronous = NORMAL')\n return {\n database: drizzle(connection, { schema: boardSqliteSchema }),\n connection,\n close: () => connection.close(),\n }\n}\n\n/**\n * Every column this build expects to find, read off the schema itself — the\n * same idea as the Postgres guard's, so a column a later migration added\n * (`issue_milestones.start_date`) is looked for without being listed again.\n */\nfunction expectedColumns(): string[] {\n return Object.values(boardSqliteSchema).flatMap((table) => {\n const config = getTableConfig(table)\n return config.columns.map(column => `${config.name}.${column.name}`)\n })\n}\n\n/**\n * Refuse a board file this build cannot speak for, before any statement runs\n * against it: one with no board in it (`missingTables`), one whose board a\n * later migration has since added a table to (`missingAddedTables`) or a\n * column to (`missingColumns`), and one stamped with another epoch. An app\n * that migrates its own database on start need not call this; it is the guard a\n * standalone reader puts in front of a file it did not create.\n */\nexport function assertBoardSchema(database: SqliteDatabase): void {\n const present = new Set(database\n .all<{ name: string }>(sql`select name from sqlite_master where type = 'table'`)\n .map(row => row.name))\n const missing = REQUIRED_BOARD_TABLES.filter(table => !present.has(table))\n if (missing.length > 0) {\n throw new BoardError('board_schema_outdated', { missingTables: missing })\n }\n const missingAddedTables = BOARD_TABLES_ADDED_WITHIN_EPOCH.filter(table => !present.has(table))\n if (missingAddedTables.length > 0) {\n throw new BoardError('board_schema_outdated', { missingAddedTables })\n }\n const columns = new Set(database\n .all<{ tableName: string, columnName: string }>(sql`\n select m.name as tableName, p.name as columnName\n from sqlite_master m join pragma_table_info(m.name) p\n where m.type = 'table'`)\n .map(row => `${row.tableName}.${row.columnName}`))\n const missingColumns = expectedColumns().filter(column => !columns.has(column))\n if (missingColumns.length > 0) {\n throw new BoardError('board_schema_outdated', { missingColumns })\n }\n\n const row = database.get<{ revision: number } | undefined>(\n sql`select revision from kanban_meta where key = 'schema_epoch'`,\n )\n if (row?.revision !== BOARD_SCHEMA_EPOCH) {\n throw new BoardError('board_schema_outdated', { expected: BOARD_SCHEMA_EPOCH, found: row?.revision ?? null })\n }\n}\n\n/**\n * Who created this board file's tables from nothing: this package\n * (`kanbo init --file`), or a host app whose own database the board lives in.\n */\nexport type BoardFileOwner = 'kanbo' | 'host'\n\n/**\n * Which of the two ever wrote this file's board, going by the `kanban_meta`\n * marker `kanbo init --file` leaves behind (ruling 5-4).\n *\n * Absent reads as a host database — every file this package has never\n * touched, and a file this build cannot make sense of at all: `assertBoardSchema` is what says a board is missing or too old,\n * not this, so a table or a row that is not there yet is read as \"not this\n * package's file\" rather than an error of its own.\n */\nexport function readBoardFileOwner(database: SqliteDatabase): BoardFileOwner {\n try {\n const row = database.get<{ key: string } | undefined>(\n sql`select key from kanban_meta where key = 'file_owner'`,\n )\n return row ? 'kanbo' : 'host'\n }\n catch {\n return 'host'\n }\n}\n\n/**\n * Leave the marker that tells every later reader this file is this package's\n * own, not a host app's. `kanbo init --file` calls this once, right after\n * migrating; `insert or ignore` is what makes calling it again on the same\n * file a no-op rather than a conflict.\n */\nexport function markBoardFileOwnedByKanbo(database: SqliteDatabase): void {\n database.run(sql`insert or ignore into kanban_meta (key, revision) values ('file_owner', 1)`)\n}\n\n/** What a file already holds, before a board is opened on it or written into it. */\nexport interface BoardFileContents {\n /** No tables at all — a path nothing has been created in yet, or a zero-byte file. */\n empty: boolean\n /** Whose board the tables are, by the `file_owner` marker (ruling 5-4). Says nothing about an empty file. */\n owner: BoardFileOwner\n}\n\n/**\n * Look at a file on a connection of its own and hand it back closed, so a\n * caller can decide what to do with it before opening it for real.\n *\n * Two callers, two different decisions off the same two facts: the command line\n * asks whose file it is to know whether a host app's `workspaces` row may be\n * looked up in it, and a host app asks before it writes a board into it, because\n * a file with tables that are not this package's is somebody else's database\n * and migrating it would contaminate it (ruling 5-8).\n *\n * A path that is not a SQLite database at all — a README, a half-downloaded\n * file, a directory — throws, and that is deliberately not the same answer as\n * \"a database with somebody else's tables in it\": one is a mistake about which\n * path to use and the other is a mistake about which file is a board. No pragma\n * is set and nothing is written, so a file that turns out not to be ours is\n * left exactly as it was found, journal mode included.\n */\nexport function readBoardFileContents(path: string): BoardFileContents {\n const Database = loadBetterSqlite3()\n const connection = new Database(path, { fileMustExist: true })\n try {\n const tables = connection.prepare('select count(*) as count from sqlite_master where type = \\'table\\'').get() as { count: number }\n if (tables.count === 0) {\n return { empty: true, owner: 'host' }\n }\n return { empty: false, owner: readMarkedOwner(connection) }\n }\n finally {\n connection.close()\n }\n}\n\n/** The marker, read straight off the driver: the same question `readBoardFileOwner` asks a drizzle handle. */\nfunction readMarkedOwner(connection: SqliteConnection): BoardFileOwner {\n try {\n return connection.prepare('select key from kanban_meta where key = \\'file_owner\\'').get() ? 'kanbo' : 'host'\n }\n catch {\n return 'host'\n }\n}\n","import { dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport { migrate } from 'drizzle-orm/better-sqlite3/migrator'\n\nimport { hasMigrationJournal, resolveBoardMigrationsPath } from '../migrations-path'\nimport type { SqliteDatabase } from './transaction'\n\n/** The folder's name, wherever it has been put. */\nconst MIGRATIONS_DIRECTORY = 'drizzle-sqlite'\n\n/**\n * The board's SQLite migrations for the file that is running right now — the\n * ones that build a board *file of this package's own*. The two layouts this\n * has to survive are `src/migrations-path.ts`'s subject.\n */\nexport function getBoardSqliteMigrationsPath(): string {\n const here = dirname(fileURLToPath(import.meta.url))\n return resolveBoardMigrationsPath(here, MIGRATIONS_DIRECTORY, hasMigrationJournal)\n}\n\n/**\n * Create or update a board file this package owns.\n *\n * A host app's own database is not this: there the board is nine tables among\n * the app's own, and the app's migration runner migrates the whole file — which\n * may take two passes, because a migration that rebuilds a table has to drop\n * it with foreign keys off. Nothing here rebuilds anything — `drizzle-sqlite/` starts at the\n * board as it is now and will only ever be appended to — so drizzle's own\n * migrator is the whole of what this needs, and a file that has been migrated\n * before is a no-op by `__drizzle_migrations`.\n *\n * What the file holds afterwards is the board and nothing else: no\n * `workspaces`, no `agents`, no `provider_targets`. Every reference out of the\n * board is a plain text column for exactly that reason (ruling 5-1).\n */\nexport function migrateBoardFile(database: SqliteDatabase): void {\n migrate(database, { migrationsFolder: getBoardSqliteMigrationsPath() })\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
let node_fs = require("node:fs");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
//#region src/domain/errors.ts
|
|
4
|
+
/** A board rule the caller broke, carrying the offending values in `details`. */
|
|
5
|
+
var BoardError = class extends Error {
|
|
6
|
+
code;
|
|
7
|
+
details;
|
|
8
|
+
constructor(code, details) {
|
|
9
|
+
super(code);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.details = details;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/schema-epoch.ts
|
|
16
|
+
/**
|
|
17
|
+
* The board schema generation this build of the package speaks. A database
|
|
18
|
+
* stamped with anything else was written by another version, and the migration
|
|
19
|
+
* that would bring it forward belongs to whoever owns the database — never to a
|
|
20
|
+
* reader.
|
|
21
|
+
*
|
|
22
|
+
* The number says nothing about which engine holds the board: a SQLite file and
|
|
23
|
+
* a Postgres database at the same epoch carry the same logical schema, which is
|
|
24
|
+
* why a change to one is only ever made together with the same change to the
|
|
25
|
+
* other.
|
|
26
|
+
*/
|
|
27
|
+
const BOARD_SCHEMA_EPOCH = 1;
|
|
28
|
+
/**
|
|
29
|
+
* The tables a migration added to the board after epoch 1 was first written.
|
|
30
|
+
*
|
|
31
|
+
* A board that lacks one of these is not a database with no board in it — it
|
|
32
|
+
* has every table the first migration made — but an older board of the same
|
|
33
|
+
* epoch, which `kanbo migrate` (or, for a host app's own database, that app's migrations)
|
|
34
|
+
* brings forward. `assertBoardSchema` names them apart from the founding tables
|
|
35
|
+
* for exactly that reason: a host reads `missingTables` as "no board here" and
|
|
36
|
+
* anything else as "an older board".
|
|
37
|
+
*/
|
|
38
|
+
const BOARD_TABLES_ADDED_WITHIN_EPOCH = ["issue_pull_requests"];
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/migrations-path.ts
|
|
41
|
+
/**
|
|
42
|
+
* Where this build keeps a board's migrations, whichever dialect is asking.
|
|
43
|
+
*
|
|
44
|
+
* The folder is found relative to the file asking for it, because the same code
|
|
45
|
+
* runs from two layouts: from `src/<dialect>/` in a checkout, where the folder
|
|
46
|
+
* sits at the package root, and from `dist/cli.cjs` in an installed build, where
|
|
47
|
+
* the build has copied it in beside the bundle. The packaged command ships no
|
|
48
|
+
* `node_modules` and no package root — `dist/` is the whole of it — so the copy
|
|
49
|
+
* is the only migrations folder there is.
|
|
50
|
+
*
|
|
51
|
+
* Kept apart from the migrators on purpose: this is a packaging question, and
|
|
52
|
+
* two dialects ask it about two folders. The caller passes its own `here`, so
|
|
53
|
+
* the depth is the caller's rather than this file's.
|
|
54
|
+
*/
|
|
55
|
+
/** What makes a candidate folder the migrations folder rather than an empty directory. */
|
|
56
|
+
const JOURNAL_FILE = (0, node_path.join)("meta", "_journal.json");
|
|
57
|
+
/**
|
|
58
|
+
* The first of the layouts that is actually there, as seen from `here`.
|
|
59
|
+
*
|
|
60
|
+
* Pure, and takes its own `exists`, so all three layouts can be asked about on
|
|
61
|
+
* a machine that only has one of them. When none is there the source layout is
|
|
62
|
+
* handed back, so the migrator's complaint names the folder a developer is
|
|
63
|
+
* missing rather than one that only exists inside a packaged app.
|
|
64
|
+
*/
|
|
65
|
+
function resolveBoardMigrationsPath(here, directory, exists) {
|
|
66
|
+
const candidates = [
|
|
67
|
+
(0, node_path.join)(here, "..", "..", directory),
|
|
68
|
+
(0, node_path.join)(here, directory),
|
|
69
|
+
(0, node_path.join)(here, "..", "..", "..", directory)
|
|
70
|
+
];
|
|
71
|
+
return candidates.find(exists) ?? candidates[0];
|
|
72
|
+
}
|
|
73
|
+
/** Whether a candidate folder holds a drizzle journal — the real `exists` the two callers pass. */
|
|
74
|
+
function hasMigrationJournal(folder) {
|
|
75
|
+
return (0, node_fs.existsSync)((0, node_path.join)(folder, JOURNAL_FILE));
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
Object.defineProperty(exports, "BOARD_SCHEMA_EPOCH", {
|
|
79
|
+
enumerable: true,
|
|
80
|
+
get: function() {
|
|
81
|
+
return BOARD_SCHEMA_EPOCH;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
Object.defineProperty(exports, "BOARD_TABLES_ADDED_WITHIN_EPOCH", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
get: function() {
|
|
87
|
+
return BOARD_TABLES_ADDED_WITHIN_EPOCH;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
Object.defineProperty(exports, "BoardError", {
|
|
91
|
+
enumerable: true,
|
|
92
|
+
get: function() {
|
|
93
|
+
return BoardError;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
Object.defineProperty(exports, "hasMigrationJournal", {
|
|
97
|
+
enumerable: true,
|
|
98
|
+
get: function() {
|
|
99
|
+
return hasMigrationJournal;
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
Object.defineProperty(exports, "resolveBoardMigrationsPath", {
|
|
103
|
+
enumerable: true,
|
|
104
|
+
get: function() {
|
|
105
|
+
return resolveBoardMigrationsPath;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
//# sourceMappingURL=migrations-path-CZxVOgeD.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations-path-CZxVOgeD.cjs","names":["join","existsSync"],"sources":["../../src/domain/errors.ts","../../src/schema-epoch.ts","../../src/migrations-path.ts"],"sourcesContent":["/**\n * Every failure the board itself can name. HTTP status codes are not part of\n * this list: what each code is over HTTP is `BOARD_ERROR_RESPONSES`\n * (`../serve/board-error-responses.ts`), the one table `kanbo serve` and any app\n * serving the board over HTTP answer with.\n *\n * Five of them are about where a board lives rather than about what is on it,\n * and they are named here for the same reason as the rest: a host that keeps\n * one board in a file and another in a database still answers its callers in\n * one vocabulary. `board_storage_not_prepared` is a connection string pointing\n * at a database that holds no board this build can speak for,\n * `board_storage_unavailable` is one the host could not reach at all,\n * `board_storage_not_configured` is a workspace that names no board to look at,\n * `board_storage_not_empty` is a board still holding cards when something asked\n * to point the workspace somewhere else, and `board_mixed_workspaces` is a\n * single operation naming cards that turned out to live in two different\n * boards — which no transaction can commit as one.\n */\nexport type BoardErrorCode\n = | 'board_approval_requires_user'\n | 'board_column_rules_requires_user'\n | 'board_column_rules_unmet'\n | 'board_copy_conflict'\n | 'board_entry_rule_invalid'\n | 'board_execution_mode_locked'\n | 'board_mixed_workspaces'\n | 'board_pull_request_invalid'\n | 'board_pull_request_not_found'\n | 'board_pull_request_not_yours'\n | 'board_return_no_previous_column'\n | 'board_return_requires_user'\n | 'board_run_already_finished'\n | 'board_run_not_found'\n | 'board_run_session_ref_conflict'\n | 'board_run_session_ref_invalid'\n | 'board_run_session_ref_requires_user'\n | 'board_schema_outdated'\n | 'board_sprint_carry_invalid'\n | 'board_sprint_close_requires_user'\n | 'board_sprint_dates_invalid'\n | 'board_storage_not_configured'\n | 'board_storage_not_empty'\n | 'board_storage_not_prepared'\n | 'board_storage_unavailable'\n | 'issue_comment_delete_requires_user'\n | 'issue_comment_not_found'\n | 'issue_context_ref_invalid_index'\n | 'issue_milestone_not_found'\n | 'issue_not_found'\n | 'issue_parent_not_found'\n | 'issue_parent_self_reference'\n | 'issue_relation_not_found'\n | 'issue_relation_self_reference'\n | 'issue_status_name_empty'\n | 'issue_status_not_found'\n | 'issue_status_reference_conflict'\n | 'issue_workspace_not_found'\n\n/** A board rule the caller broke, carrying the offending values in `details`. */\nexport class BoardError extends Error {\n readonly code: BoardErrorCode\n readonly details?: Record<string, unknown>\n\n constructor(code: BoardErrorCode, details?: Record<string, unknown>) {\n super(code)\n this.code = code\n this.details = details\n }\n}\n","/**\n * The board schema generation this build of the package speaks. A database\n * stamped with anything else was written by another version, and the migration\n * that would bring it forward belongs to whoever owns the database — never to a\n * reader.\n *\n * The number says nothing about which engine holds the board: a SQLite file and\n * a Postgres database at the same epoch carry the same logical schema, which is\n * why a change to one is only ever made together with the same change to the\n * other.\n */\nexport const BOARD_SCHEMA_EPOCH = 1\n\n/**\n * The tables a migration added to the board after epoch 1 was first written.\n *\n * A board that lacks one of these is not a database with no board in it — it\n * has every table the first migration made — but an older board of the same\n * epoch, which `kanbo migrate` (or, for a host app's own database, that app's migrations)\n * brings forward. `assertBoardSchema` names them apart from the founding tables\n * for exactly that reason: a host reads `missingTables` as \"no board here\" and\n * anything else as \"an older board\".\n */\nexport const BOARD_TABLES_ADDED_WITHIN_EPOCH = ['issue_pull_requests'] as const\n","import { existsSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/**\n * Where this build keeps a board's migrations, whichever dialect is asking.\n *\n * The folder is found relative to the file asking for it, because the same code\n * runs from two layouts: from `src/<dialect>/` in a checkout, where the folder\n * sits at the package root, and from `dist/cli.cjs` in an installed build, where\n * the build has copied it in beside the bundle. The packaged command ships no\n * `node_modules` and no package root — `dist/` is the whole of it — so the copy\n * is the only migrations folder there is.\n *\n * Kept apart from the migrators on purpose: this is a packaging question, and\n * two dialects ask it about two folders. The caller passes its own `here`, so\n * the depth is the caller's rather than this file's.\n */\n\n/** What makes a candidate folder the migrations folder rather than an empty directory. */\nconst JOURNAL_FILE = join('meta', '_journal.json')\n\n/**\n * The first of the layouts that is actually there, as seen from `here`.\n *\n * Pure, and takes its own `exists`, so all three layouts can be asked about on\n * a machine that only has one of them. When none is there the source layout is\n * handed back, so the migrator's complaint names the folder a developer is\n * missing rather than one that only exists inside a packaged app.\n */\nexport function resolveBoardMigrationsPath(\n here: string,\n directory: string,\n exists: (folder: string) => boolean,\n): string {\n const candidates = [\n // src/<dialect>/ → the package root, in a checkout.\n join(here, '..', '..', directory),\n // dist/ → beside the bundle, in an installed build.\n join(here, directory),\n // dist/lib/<dialect>/ → the package root, one directory deeper than the\n // checkout layout because the library build nests each entry under its own\n // subpath. The package root is where the folder already lives — checked in\n // for a checkout, published under `files` for an installed tarball — so\n // this candidate names the same target as the first, not a new copy.\n join(here, '..', '..', '..', directory),\n ]\n return candidates.find(exists) ?? candidates[0]\n}\n\n/** Whether a candidate folder holds a drizzle journal — the real `exists` the two callers pass. */\nexport function hasMigrationJournal(folder: string): boolean {\n return existsSync(join(folder, JOURNAL_FILE))\n}\n"],"mappings":";;;;AA2DA,IAAa,aAAb,cAAgC,MAAM;CACpC;CACA;CAEA,YAAY,MAAsB,SAAmC;EACnE,MAAM,IAAI;EACV,KAAK,OAAO;EACZ,KAAK,UAAU;CACjB;AACF;;;;;;;;;;;;;;ACzDA,MAAa,qBAAqB;;;;;;;;;;;AAYlC,MAAa,kCAAkC,CAAC,qBAAqB;;;;;;;;;;;;;;;;;;ACJrE,MAAM,gBAAA,GAAeA,UAAAA,KAAAA,CAAK,QAAQ,eAAe;;;;;;;;;AAUjD,SAAgB,2BACd,MACA,WACA,QACQ;CACR,MAAM,aAAa;GAEjBA,GAAAA,UAAAA,KAAAA,CAAK,MAAM,MAAM,MAAM,SAAS;GAEhCA,GAAAA,UAAAA,KAAAA,CAAK,MAAM,SAAS;GAMpBA,GAAAA,UAAAA,KAAAA,CAAK,MAAM,MAAM,MAAM,MAAM,SAAS;CACxC;CACA,OAAO,WAAW,KAAK,MAAM,KAAK,WAAW;AAC/C;;AAGA,SAAgB,oBAAoB,QAAyB;CAC3D,QAAA,GAAOC,QAAAA,WAAAA,EAAAA,GAAWD,UAAAA,KAAAA,CAAK,QAAQ,YAAY,CAAC;AAC9C"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
//#region src/domain/errors.ts
|
|
4
|
+
/** A board rule the caller broke, carrying the offending values in `details`. */
|
|
5
|
+
var BoardError = class extends Error {
|
|
6
|
+
code;
|
|
7
|
+
details;
|
|
8
|
+
constructor(code, details) {
|
|
9
|
+
super(code);
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.details = details;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/schema-epoch.ts
|
|
16
|
+
/**
|
|
17
|
+
* The board schema generation this build of the package speaks. A database
|
|
18
|
+
* stamped with anything else was written by another version, and the migration
|
|
19
|
+
* that would bring it forward belongs to whoever owns the database — never to a
|
|
20
|
+
* reader.
|
|
21
|
+
*
|
|
22
|
+
* The number says nothing about which engine holds the board: a SQLite file and
|
|
23
|
+
* a Postgres database at the same epoch carry the same logical schema, which is
|
|
24
|
+
* why a change to one is only ever made together with the same change to the
|
|
25
|
+
* other.
|
|
26
|
+
*/
|
|
27
|
+
const BOARD_SCHEMA_EPOCH = 1;
|
|
28
|
+
/**
|
|
29
|
+
* The tables a migration added to the board after epoch 1 was first written.
|
|
30
|
+
*
|
|
31
|
+
* A board that lacks one of these is not a database with no board in it — it
|
|
32
|
+
* has every table the first migration made — but an older board of the same
|
|
33
|
+
* epoch, which `kanbo migrate` (or, for a host app's own database, that app's migrations)
|
|
34
|
+
* brings forward. `assertBoardSchema` names them apart from the founding tables
|
|
35
|
+
* for exactly that reason: a host reads `missingTables` as "no board here" and
|
|
36
|
+
* anything else as "an older board".
|
|
37
|
+
*/
|
|
38
|
+
const BOARD_TABLES_ADDED_WITHIN_EPOCH = ["issue_pull_requests"];
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/migrations-path.ts
|
|
41
|
+
/**
|
|
42
|
+
* Where this build keeps a board's migrations, whichever dialect is asking.
|
|
43
|
+
*
|
|
44
|
+
* The folder is found relative to the file asking for it, because the same code
|
|
45
|
+
* runs from two layouts: from `src/<dialect>/` in a checkout, where the folder
|
|
46
|
+
* sits at the package root, and from `dist/cli.cjs` in an installed build, where
|
|
47
|
+
* the build has copied it in beside the bundle. The packaged command ships no
|
|
48
|
+
* `node_modules` and no package root — `dist/` is the whole of it — so the copy
|
|
49
|
+
* is the only migrations folder there is.
|
|
50
|
+
*
|
|
51
|
+
* Kept apart from the migrators on purpose: this is a packaging question, and
|
|
52
|
+
* two dialects ask it about two folders. The caller passes its own `here`, so
|
|
53
|
+
* the depth is the caller's rather than this file's.
|
|
54
|
+
*/
|
|
55
|
+
/** What makes a candidate folder the migrations folder rather than an empty directory. */
|
|
56
|
+
const JOURNAL_FILE = join("meta", "_journal.json");
|
|
57
|
+
/**
|
|
58
|
+
* The first of the layouts that is actually there, as seen from `here`.
|
|
59
|
+
*
|
|
60
|
+
* Pure, and takes its own `exists`, so all three layouts can be asked about on
|
|
61
|
+
* a machine that only has one of them. When none is there the source layout is
|
|
62
|
+
* handed back, so the migrator's complaint names the folder a developer is
|
|
63
|
+
* missing rather than one that only exists inside a packaged app.
|
|
64
|
+
*/
|
|
65
|
+
function resolveBoardMigrationsPath(here, directory, exists) {
|
|
66
|
+
const candidates = [
|
|
67
|
+
join(here, "..", "..", directory),
|
|
68
|
+
join(here, directory),
|
|
69
|
+
join(here, "..", "..", "..", directory)
|
|
70
|
+
];
|
|
71
|
+
return candidates.find(exists) ?? candidates[0];
|
|
72
|
+
}
|
|
73
|
+
/** Whether a candidate folder holds a drizzle journal — the real `exists` the two callers pass. */
|
|
74
|
+
function hasMigrationJournal(folder) {
|
|
75
|
+
return existsSync(join(folder, JOURNAL_FILE));
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
export { BoardError as a, BOARD_TABLES_ADDED_WITHIN_EPOCH as i, resolveBoardMigrationsPath as n, BOARD_SCHEMA_EPOCH as r, hasMigrationJournal as t };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=migrations-path-Ciwan537.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations-path-Ciwan537.mjs","names":[],"sources":["../../src/domain/errors.ts","../../src/schema-epoch.ts","../../src/migrations-path.ts"],"sourcesContent":["/**\n * Every failure the board itself can name. HTTP status codes are not part of\n * this list: what each code is over HTTP is `BOARD_ERROR_RESPONSES`\n * (`../serve/board-error-responses.ts`), the one table `kanbo serve` and any app\n * serving the board over HTTP answer with.\n *\n * Five of them are about where a board lives rather than about what is on it,\n * and they are named here for the same reason as the rest: a host that keeps\n * one board in a file and another in a database still answers its callers in\n * one vocabulary. `board_storage_not_prepared` is a connection string pointing\n * at a database that holds no board this build can speak for,\n * `board_storage_unavailable` is one the host could not reach at all,\n * `board_storage_not_configured` is a workspace that names no board to look at,\n * `board_storage_not_empty` is a board still holding cards when something asked\n * to point the workspace somewhere else, and `board_mixed_workspaces` is a\n * single operation naming cards that turned out to live in two different\n * boards — which no transaction can commit as one.\n */\nexport type BoardErrorCode\n = | 'board_approval_requires_user'\n | 'board_column_rules_requires_user'\n | 'board_column_rules_unmet'\n | 'board_copy_conflict'\n | 'board_entry_rule_invalid'\n | 'board_execution_mode_locked'\n | 'board_mixed_workspaces'\n | 'board_pull_request_invalid'\n | 'board_pull_request_not_found'\n | 'board_pull_request_not_yours'\n | 'board_return_no_previous_column'\n | 'board_return_requires_user'\n | 'board_run_already_finished'\n | 'board_run_not_found'\n | 'board_run_session_ref_conflict'\n | 'board_run_session_ref_invalid'\n | 'board_run_session_ref_requires_user'\n | 'board_schema_outdated'\n | 'board_sprint_carry_invalid'\n | 'board_sprint_close_requires_user'\n | 'board_sprint_dates_invalid'\n | 'board_storage_not_configured'\n | 'board_storage_not_empty'\n | 'board_storage_not_prepared'\n | 'board_storage_unavailable'\n | 'issue_comment_delete_requires_user'\n | 'issue_comment_not_found'\n | 'issue_context_ref_invalid_index'\n | 'issue_milestone_not_found'\n | 'issue_not_found'\n | 'issue_parent_not_found'\n | 'issue_parent_self_reference'\n | 'issue_relation_not_found'\n | 'issue_relation_self_reference'\n | 'issue_status_name_empty'\n | 'issue_status_not_found'\n | 'issue_status_reference_conflict'\n | 'issue_workspace_not_found'\n\n/** A board rule the caller broke, carrying the offending values in `details`. */\nexport class BoardError extends Error {\n readonly code: BoardErrorCode\n readonly details?: Record<string, unknown>\n\n constructor(code: BoardErrorCode, details?: Record<string, unknown>) {\n super(code)\n this.code = code\n this.details = details\n }\n}\n","/**\n * The board schema generation this build of the package speaks. A database\n * stamped with anything else was written by another version, and the migration\n * that would bring it forward belongs to whoever owns the database — never to a\n * reader.\n *\n * The number says nothing about which engine holds the board: a SQLite file and\n * a Postgres database at the same epoch carry the same logical schema, which is\n * why a change to one is only ever made together with the same change to the\n * other.\n */\nexport const BOARD_SCHEMA_EPOCH = 1\n\n/**\n * The tables a migration added to the board after epoch 1 was first written.\n *\n * A board that lacks one of these is not a database with no board in it — it\n * has every table the first migration made — but an older board of the same\n * epoch, which `kanbo migrate` (or, for a host app's own database, that app's migrations)\n * brings forward. `assertBoardSchema` names them apart from the founding tables\n * for exactly that reason: a host reads `missingTables` as \"no board here\" and\n * anything else as \"an older board\".\n */\nexport const BOARD_TABLES_ADDED_WITHIN_EPOCH = ['issue_pull_requests'] as const\n","import { existsSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/**\n * Where this build keeps a board's migrations, whichever dialect is asking.\n *\n * The folder is found relative to the file asking for it, because the same code\n * runs from two layouts: from `src/<dialect>/` in a checkout, where the folder\n * sits at the package root, and from `dist/cli.cjs` in an installed build, where\n * the build has copied it in beside the bundle. The packaged command ships no\n * `node_modules` and no package root — `dist/` is the whole of it — so the copy\n * is the only migrations folder there is.\n *\n * Kept apart from the migrators on purpose: this is a packaging question, and\n * two dialects ask it about two folders. The caller passes its own `here`, so\n * the depth is the caller's rather than this file's.\n */\n\n/** What makes a candidate folder the migrations folder rather than an empty directory. */\nconst JOURNAL_FILE = join('meta', '_journal.json')\n\n/**\n * The first of the layouts that is actually there, as seen from `here`.\n *\n * Pure, and takes its own `exists`, so all three layouts can be asked about on\n * a machine that only has one of them. When none is there the source layout is\n * handed back, so the migrator's complaint names the folder a developer is\n * missing rather than one that only exists inside a packaged app.\n */\nexport function resolveBoardMigrationsPath(\n here: string,\n directory: string,\n exists: (folder: string) => boolean,\n): string {\n const candidates = [\n // src/<dialect>/ → the package root, in a checkout.\n join(here, '..', '..', directory),\n // dist/ → beside the bundle, in an installed build.\n join(here, directory),\n // dist/lib/<dialect>/ → the package root, one directory deeper than the\n // checkout layout because the library build nests each entry under its own\n // subpath. The package root is where the folder already lives — checked in\n // for a checkout, published under `files` for an installed tarball — so\n // this candidate names the same target as the first, not a new copy.\n join(here, '..', '..', '..', directory),\n ]\n return candidates.find(exists) ?? candidates[0]\n}\n\n/** Whether a candidate folder holds a drizzle journal — the real `exists` the two callers pass. */\nexport function hasMigrationJournal(folder: string): boolean {\n return existsSync(join(folder, JOURNAL_FILE))\n}\n"],"mappings":";;;;AA2DA,IAAa,aAAb,cAAgC,MAAM;CACpC;CACA;CAEA,YAAY,MAAsB,SAAmC;EACnE,MAAM,IAAI;EACV,KAAK,OAAO;EACZ,KAAK,UAAU;CACjB;AACF;;;;;;;;;;;;;;ACzDA,MAAa,qBAAqB;;;;;;;;;;;AAYlC,MAAa,kCAAkC,CAAC,qBAAqB;;;;;;;;;;;;;;;;;;ACJrE,MAAM,eAAe,KAAK,QAAQ,eAAe;;;;;;;;;AAUjD,SAAgB,2BACd,MACA,WACA,QACQ;CACR,MAAM,aAAa;EAEjB,KAAK,MAAM,MAAM,MAAM,SAAS;EAEhC,KAAK,MAAM,SAAS;EAMpB,KAAK,MAAM,MAAM,MAAM,MAAM,SAAS;CACxC;CACA,OAAO,WAAW,KAAK,MAAM,KAAK,WAAW;AAC/C;;AAGA,SAAgB,oBAAoB,QAAyB;CAC3D,OAAO,WAAW,KAAK,QAAQ,YAAY,CAAC;AAC9C"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_migrations_path = require("../migrations-path-CZxVOgeD.cjs");
|
|
3
|
+
const require_roles_sql = require("../roles.sql-B9rt-x4o.cjs");
|
|
4
|
+
let drizzle_orm = require("drizzle-orm");
|
|
5
|
+
let drizzle_orm_postgres_js_migrator = require("drizzle-orm/postgres-js/migrator");
|
|
6
|
+
let drizzle_orm_pglite_migrator = require("drizzle-orm/pglite/migrator");
|
|
7
|
+
//#region src/postgres/migrate.ts
|
|
8
|
+
/**
|
|
9
|
+
* Bring an external board database up to the schema this build speaks.
|
|
10
|
+
*
|
|
11
|
+
* Unlike a host app's database — whose schema the app migrates itself — an
|
|
12
|
+
* external database has no owner but this package, so this
|
|
13
|
+
* is the one place that creates or updates it. Drizzle records what it applied
|
|
14
|
+
* in `__drizzle_migrations`, which is what makes a second run a no-op.
|
|
15
|
+
*/
|
|
16
|
+
async function migrateBoardDatabase(database) {
|
|
17
|
+
const config = { migrationsFolder: require_roles_sql.getBoardMigrationsPath() };
|
|
18
|
+
if (isPgliteDatabase(database)) {
|
|
19
|
+
await (0, drizzle_orm_pglite_migrator.migrate)(database, config);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
await (0, drizzle_orm_postgres_js_migrator.migrate)(database, config);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Which driver is underneath. Drizzle stamps every one of its classes with a
|
|
26
|
+
* name for exactly this question, and asking it keeps the embedded Postgres out
|
|
27
|
+
* of the packaged command: importing the pglite *driver* to test against would
|
|
28
|
+
* pull the WebAssembly build in with it, while its migrator carries nothing.
|
|
29
|
+
*/
|
|
30
|
+
function isPgliteDatabase(database) {
|
|
31
|
+
return database.constructor[drizzle_orm.entityKind] === "PgliteDatabase";
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
exports.AGENT_APPROVAL_COMMENT_REFUSAL = require_roles_sql.AGENT_APPROVAL_COMMENT_REFUSAL;
|
|
35
|
+
exports.AGENT_WAITING_REFUSAL = require_roles_sql.AGENT_WAITING_REFUSAL;
|
|
36
|
+
exports.BOARD_AGENT_ROLE = require_roles_sql.BOARD_AGENT_ROLE;
|
|
37
|
+
exports.BOARD_PERSON_ROLE = require_roles_sql.BOARD_PERSON_ROLE;
|
|
38
|
+
exports.BOARD_POSTGRES_TABLES = require_roles_sql.BOARD_POSTGRES_TABLES;
|
|
39
|
+
exports.BOARD_REFUSAL_ERRCODE = require_roles_sql.BOARD_REFUSAL_ERRCODE;
|
|
40
|
+
exports.BOARD_ROLE_SQL = require_roles_sql.BOARD_ROLE_SQL;
|
|
41
|
+
exports.BOARD_SCHEMA_EPOCH = require_migrations_path.BOARD_SCHEMA_EPOCH;
|
|
42
|
+
exports.assertBoardSchema = require_roles_sql.assertBoardSchema;
|
|
43
|
+
exports.boardPostgresSchema = require_roles_sql.boardPostgresSchema;
|
|
44
|
+
exports.createPostgresBoardStore = require_roles_sql.createPostgresBoardStore;
|
|
45
|
+
exports.getBoardMigrationsPath = require_roles_sql.getBoardMigrationsPath;
|
|
46
|
+
exports.issueComments = require_roles_sql.issueComments;
|
|
47
|
+
exports.issueFieldChanges = require_roles_sql.issueFieldChanges;
|
|
48
|
+
exports.issueMilestones = require_roles_sql.issueMilestones;
|
|
49
|
+
exports.issueRelations = require_roles_sql.issueRelations;
|
|
50
|
+
exports.issueRuns = require_roles_sql.issueRuns;
|
|
51
|
+
exports.issueStatuses = require_roles_sql.issueStatuses;
|
|
52
|
+
exports.issues = require_roles_sql.issues;
|
|
53
|
+
exports.kanbanMeta = require_roles_sql.kanbanMeta;
|
|
54
|
+
exports.migrateBoardDatabase = migrateBoardDatabase;
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["getBoardMigrationsPath","migratePgliteDatabase","migratePostgresJsDatabase","entityKind"],"sources":["../../../src/postgres/migrate.ts"],"sourcesContent":["import { entityKind } from 'drizzle-orm'\nimport type { PgliteDatabase } from 'drizzle-orm/pglite'\nimport { migrate as migratePgliteDatabase } from 'drizzle-orm/pglite/migrator'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport { migrate as migratePostgresJsDatabase } from 'drizzle-orm/postgres-js/migrator'\n\nimport { getBoardMigrationsPath } from './migrations-path'\n\n/**\n * A Postgres board this package can migrate: the driver an app and the command\n * line connect with, and the embedded one the tests run on. Drizzle ships a\n * migrator per driver, so both are named here rather than one abstract\n * `PgDatabase` — the drivers differ in their result type, and nothing else.\n */\nexport type MigratablePostgresDatabase<TSchema extends Record<string, unknown> = Record<string, never>>\n = PgliteDatabase<TSchema> | PostgresJsDatabase<TSchema>\n\n/**\n * Bring an external board database up to the schema this build speaks.\n *\n * Unlike a host app's database — whose schema the app migrates itself — an\n * external database has no owner but this package, so this\n * is the one place that creates or updates it. Drizzle records what it applied\n * in `__drizzle_migrations`, which is what makes a second run a no-op.\n */\nexport async function migrateBoardDatabase<TSchema extends Record<string, unknown>>(\n database: MigratablePostgresDatabase<TSchema>,\n): Promise<void> {\n const config = { migrationsFolder: getBoardMigrationsPath() }\n if (isPgliteDatabase(database)) {\n await migratePgliteDatabase(database, config)\n return\n }\n await migratePostgresJsDatabase(database, config)\n}\n\n/**\n * Which driver is underneath. Drizzle stamps every one of its classes with a\n * name for exactly this question, and asking it keeps the embedded Postgres out\n * of the packaged command: importing the pglite *driver* to test against would\n * pull the WebAssembly build in with it, while its migrator carries nothing.\n */\nfunction isPgliteDatabase<TSchema extends Record<string, unknown>>(\n database: MigratablePostgresDatabase<TSchema>,\n): database is PgliteDatabase<TSchema> {\n return (database.constructor as { [entityKind]?: string })[entityKind] === 'PgliteDatabase'\n}\n"],"mappings":";;;;;;;;;;;;;;;AAyBA,eAAsB,qBACpB,UACe;CACf,MAAM,SAAS,EAAE,kBAAkBA,kBAAAA,uBAAuB,EAAE;CAC5D,IAAI,iBAAiB,QAAQ,GAAG;EAC9B,OAAA,GAAMC,4BAAAA,QAAAA,CAAsB,UAAU,MAAM;EAC5C;CACF;CACA,OAAA,GAAMC,iCAAAA,QAAAA,CAA0B,UAAU,MAAM;AAClD;;;;;;;AAQA,SAAS,iBACP,UACqC;CACrC,OAAQ,SAAS,YAA0CC,YAAAA,gBAAgB;AAC7E"}
|