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,709 @@
|
|
|
1
|
+
const require_migrations_path = require("./migrations-path-CZxVOgeD.cjs");
|
|
2
|
+
const require_sqlite_schema = require("./sqlite/schema.cjs");
|
|
3
|
+
let drizzle_orm = require("drizzle-orm");
|
|
4
|
+
let drizzle_orm_sqlite_core = require("drizzle-orm/sqlite-core");
|
|
5
|
+
let node_module = require("node:module");
|
|
6
|
+
let node_path = require("node:path");
|
|
7
|
+
let node_url = require("node:url");
|
|
8
|
+
let drizzle_orm_better_sqlite3_migrator = require("drizzle-orm/better-sqlite3/migrator");
|
|
9
|
+
//#region src/sqlite/transaction.ts
|
|
10
|
+
/**
|
|
11
|
+
* The better-sqlite3 handle Drizzle keeps beside the query builder. `drizzle()`
|
|
12
|
+
* attaches it as `$client` (`drizzle-orm/better-sqlite3/driver.js`), but the
|
|
13
|
+
* handle is declared as a `BetterSQLite3Database`, a class that does not
|
|
14
|
+
* declare `$client` — which is why the assertion below restates the shape by
|
|
15
|
+
* hand. Its `inTransaction` is the only honest answer to "is a
|
|
16
|
+
* transaction already open", including one this module did not open.
|
|
17
|
+
*/
|
|
18
|
+
function readSqliteConnection(database) {
|
|
19
|
+
return database.$client;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* One fixed name for every nested scope, the way better-sqlite3 names its own
|
|
23
|
+
* (`lib/methods/transaction.js`). `release` and `rollback to` address the
|
|
24
|
+
* innermost savepoint carrying the name, and these scopes are opened and closed
|
|
25
|
+
* in stack order, so each call reaches its own — a counter in the name would
|
|
26
|
+
* only repeat what SQLite already tracks, and would collide the moment two
|
|
27
|
+
* scopes nested through different code paths.
|
|
28
|
+
*/
|
|
29
|
+
const SAVEPOINT_NAME = "kanbo_tx";
|
|
30
|
+
function readTransactionScope(connection, mode) {
|
|
31
|
+
if (!connection.inTransaction) return {
|
|
32
|
+
begin: mode === "immediate" ? "begin immediate" : "begin",
|
|
33
|
+
commit: "commit",
|
|
34
|
+
undo: "rollback",
|
|
35
|
+
releaseAfterUndo: null
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
begin: `savepoint ${SAVEPOINT_NAME}`,
|
|
39
|
+
commit: `release ${SAVEPOINT_NAME}`,
|
|
40
|
+
undo: `rollback to ${SAVEPOINT_NAME}`,
|
|
41
|
+
releaseAfterUndo: `release ${SAVEPOINT_NAME}`
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Run `fn` inside one SQLite transaction that is allowed to await.
|
|
46
|
+
*
|
|
47
|
+
* `database.transaction(...)` cannot serve here: it commits the moment its
|
|
48
|
+
* callback returns, and an async callback returns a pending promise, so the
|
|
49
|
+
* transaction would close before the work inside it ran. The statements are
|
|
50
|
+
* issued by hand instead, and the driver's own `inTransaction` flag picks which
|
|
51
|
+
* — the same branch better-sqlite3 takes. With nothing open: `begin`, then
|
|
52
|
+
* `commit`, or `rollback` when `fn` throws. With a transaction already open —
|
|
53
|
+
* another call to this helper, or one a module opened through
|
|
54
|
+
* `database.transaction` — a `savepoint` … `release` pair, undone by
|
|
55
|
+
* `rollback to` followed by its own `release`. A second `begin` would throw,
|
|
56
|
+
* which is why the flag decides and not a private counter. When SQLite has
|
|
57
|
+
* already aborted the transaction itself the undo is skipped: it would throw
|
|
58
|
+
* over the caller's error.
|
|
59
|
+
*
|
|
60
|
+
* **The callback must be the only database-touching async chain in flight —
|
|
61
|
+
* no `Promise.all` sibling, no lock hand-off, no fire-and-forget promise
|
|
62
|
+
* started before it — and inside it, only await operations that resolve
|
|
63
|
+
* without I/O: store methods, synchronous database writes.** Even a resolved
|
|
64
|
+
* promise yields to the microtask queue first, so a sibling chain already
|
|
65
|
+
* queued in the same macrotask still runs inside this open transaction; a
|
|
66
|
+
* chain started in a separate macrotask is unaffected. Awaiting anything that
|
|
67
|
+
* truly suspends — a timer, the filesystem, the network — hands the
|
|
68
|
+
* connection to whatever runs next, and its writes land in this transaction
|
|
69
|
+
* too.
|
|
70
|
+
*/
|
|
71
|
+
async function runSqliteTransaction(database, fn, options) {
|
|
72
|
+
const connection = readSqliteConnection(database);
|
|
73
|
+
const scope = readTransactionScope(connection, options?.mode ?? "deferred");
|
|
74
|
+
database.run(drizzle_orm.sql.raw(scope.begin));
|
|
75
|
+
try {
|
|
76
|
+
const result = await fn();
|
|
77
|
+
database.run(drizzle_orm.sql.raw(scope.commit));
|
|
78
|
+
return result;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (connection.inTransaction) {
|
|
81
|
+
database.run(drizzle_orm.sql.raw(scope.undo));
|
|
82
|
+
if (scope.releaseAfterUndo) database.run(drizzle_orm.sql.raw(scope.releaseAfterUndo));
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/sqlite/board-store.sqlite.ts
|
|
89
|
+
/**
|
|
90
|
+
* The order two runs of one card are in when they started in the same second.
|
|
91
|
+
*
|
|
92
|
+
* `started_at` counts whole seconds and a run id is a random UUID, so the column
|
|
93
|
+
* pair the attempt number is built on cannot separate two launches a second
|
|
94
|
+
* apart — the row the board wrote second has to be the second attempt. SQLite
|
|
95
|
+
* hands that out already: `rowid` rises with every insert. A store on another
|
|
96
|
+
* engine answers the same question with whatever its own insert order is.
|
|
97
|
+
*/
|
|
98
|
+
const RUN_INSERTION_ORDER = drizzle_orm.sql`${require_sqlite_schema.issueRuns}.rowid asc`;
|
|
99
|
+
/**
|
|
100
|
+
* The same tie-break for the two tables that record what happened to a card.
|
|
101
|
+
*
|
|
102
|
+
* `created_at` counts whole seconds here too, and a person approving a card and
|
|
103
|
+
* an agent commenting on it inside the same second must come back in the order
|
|
104
|
+
* the board wrote them: `readApproval` takes the *last* comment to decide
|
|
105
|
+
* whether the card was approved or returned.
|
|
106
|
+
*/
|
|
107
|
+
const COMMENT_INSERTION_ORDER = drizzle_orm.sql`${require_sqlite_schema.issueComments}.rowid asc`;
|
|
108
|
+
const FIELD_CHANGE_INSERTION_ORDER = drizzle_orm.sql`${require_sqlite_schema.issueFieldChanges}.rowid asc`;
|
|
109
|
+
/** And for a card's pull-request links, which list in the order they were made. */
|
|
110
|
+
const PULL_REQUEST_INSERTION_ORDER = drizzle_orm.sql`${require_sqlite_schema.issuePullRequests}.rowid asc`;
|
|
111
|
+
/**
|
|
112
|
+
* The run facts of every card in scope, in one statement.
|
|
113
|
+
*
|
|
114
|
+
* The window functions do the grouping the caller would otherwise do with a
|
|
115
|
+
* second query per card: `count(*)` counts the card's runs on every one of its
|
|
116
|
+
* rows, and `row_number()` ranks them so that a `running` run wins, then the
|
|
117
|
+
* newest start, then the row written last — the tie-break two runs started in
|
|
118
|
+
* the same second need. Reading only rank 1 therefore leaves one row per card, carrying
|
|
119
|
+
* the run the board should show — and `state` on that row says whether it is
|
|
120
|
+
* still going, which is what turns it into an `activeRun` or into `null`.
|
|
121
|
+
*/
|
|
122
|
+
function selectRunProjection(database, scope) {
|
|
123
|
+
if ("issueIds" in scope && scope.issueIds.length === 0) return [];
|
|
124
|
+
const cards = "workspaceId" in scope ? drizzle_orm.sql`select ${require_sqlite_schema.issues.id} from ${require_sqlite_schema.issues} where ${require_sqlite_schema.issues.workspaceId} = ${scope.workspaceId}` : drizzle_orm.sql`values ${drizzle_orm.sql.join(scope.issueIds.map((issueId) => drizzle_orm.sql`(${issueId})`), drizzle_orm.sql`, `)}`;
|
|
125
|
+
return database.all(drizzle_orm.sql`
|
|
126
|
+
select
|
|
127
|
+
ranked.issue_id as issueId,
|
|
128
|
+
ranked.attempt_count as attemptCount,
|
|
129
|
+
ranked.id as id,
|
|
130
|
+
ranked.agent_id as agentId,
|
|
131
|
+
ranked.agent_name as agentName,
|
|
132
|
+
ranked.state as state,
|
|
133
|
+
ranked.branch as branch,
|
|
134
|
+
ranked.execution_mode as executionMode,
|
|
135
|
+
ranked.chat_session_id as chatSessionId,
|
|
136
|
+
ranked.started_at as startedAt
|
|
137
|
+
from (
|
|
138
|
+
select
|
|
139
|
+
run.*,
|
|
140
|
+
count(*) over (partition by run.issue_id) as attempt_count,
|
|
141
|
+
row_number() over (
|
|
142
|
+
partition by run.issue_id
|
|
143
|
+
order by (run.state = 'running') desc, run.started_at desc, run.rowid desc
|
|
144
|
+
) as card_rank
|
|
145
|
+
from ${require_sqlite_schema.issueRuns} as run
|
|
146
|
+
where run.issue_id in (${cards})
|
|
147
|
+
) as ranked
|
|
148
|
+
where ranked.card_rank = 1
|
|
149
|
+
`).map((row) => ({
|
|
150
|
+
issueId: row.issueId,
|
|
151
|
+
attemptCount: row.attemptCount,
|
|
152
|
+
activeRun: row.state === "running" ? {
|
|
153
|
+
id: row.id,
|
|
154
|
+
agentId: row.agentId,
|
|
155
|
+
agentName: row.agentName,
|
|
156
|
+
state: row.state,
|
|
157
|
+
branch: row.branch,
|
|
158
|
+
executionMode: row.executionMode,
|
|
159
|
+
chatSessionId: row.chatSessionId,
|
|
160
|
+
startedAt: row.startedAt
|
|
161
|
+
} : null
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Bind the store's statements to one SQLite handle. `resolveDatabase` is called
|
|
166
|
+
* per statement rather than captured, so the store follows a host that swaps its
|
|
167
|
+
* database out (a server that resets its infrastructure between tests, say).
|
|
168
|
+
*/
|
|
169
|
+
function createSqliteBoardStoreCore(resolveDatabase) {
|
|
170
|
+
return {
|
|
171
|
+
statuses: {
|
|
172
|
+
listByWorkspace: (workspaceId) => resolveDatabase().select().from(require_sqlite_schema.issueStatuses).where((0, drizzle_orm.eq)(require_sqlite_schema.issueStatuses.workspaceId, workspaceId)).orderBy(require_sqlite_schema.issueStatuses.order).all(),
|
|
173
|
+
listAllInBoardOrder: () => resolveDatabase().select().from(require_sqlite_schema.issueStatuses).orderBy(require_sqlite_schema.issueStatuses.workspaceId, require_sqlite_schema.issueStatuses.order).all(),
|
|
174
|
+
listAll: () => resolveDatabase().select().from(require_sqlite_schema.issueStatuses).all(),
|
|
175
|
+
findById: (statusId) => resolveDatabase().select().from(require_sqlite_schema.issueStatuses).where((0, drizzle_orm.eq)(require_sqlite_schema.issueStatuses.id, statusId)).get() ?? null,
|
|
176
|
+
findInWorkspace: (workspaceId, statusId) => resolveDatabase().select().from(require_sqlite_schema.issueStatuses).where(drizzle_orm.sql`${require_sqlite_schema.issueStatuses.workspaceId} = ${workspaceId} AND ${require_sqlite_schema.issueStatuses.id} = ${statusId}`).get() ?? null,
|
|
177
|
+
countByWorkspace: (workspaceId) => {
|
|
178
|
+
return resolveDatabase().select({ count: drizzle_orm.sql`count(*)` }).from(require_sqlite_schema.issueStatuses).where((0, drizzle_orm.eq)(require_sqlite_schema.issueStatuses.workspaceId, workspaceId)).get()?.count ?? 0;
|
|
179
|
+
},
|
|
180
|
+
create: (values) => resolveDatabase().insert(require_sqlite_schema.issueStatuses).values(values).returning().get(),
|
|
181
|
+
update: (statusId, patch) => {
|
|
182
|
+
resolveDatabase().update(require_sqlite_schema.issueStatuses).set(patch).where((0, drizzle_orm.eq)(require_sqlite_schema.issueStatuses.id, statusId)).run();
|
|
183
|
+
},
|
|
184
|
+
delete: (statusId) => {
|
|
185
|
+
resolveDatabase().delete(require_sqlite_schema.issueStatuses).where((0, drizzle_orm.eq)(require_sqlite_schema.issueStatuses.id, statusId)).run();
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
milestones: {
|
|
189
|
+
listNewestFirst: (workspaceId) => {
|
|
190
|
+
const predicates = [workspaceId ? (0, drizzle_orm.eq)(require_sqlite_schema.issueMilestones.workspaceId, workspaceId) : void 0].filter((predicate) => predicate !== void 0);
|
|
191
|
+
return resolveDatabase().select().from(require_sqlite_schema.issueMilestones).where(predicates.length > 0 ? (0, drizzle_orm.and)(...predicates) : void 0).orderBy((0, drizzle_orm.desc)(require_sqlite_schema.issueMilestones.createdAt)).all();
|
|
192
|
+
},
|
|
193
|
+
listByWorkspace: (workspaceId) => resolveDatabase().select().from(require_sqlite_schema.issueMilestones).where((0, drizzle_orm.eq)(require_sqlite_schema.issueMilestones.workspaceId, workspaceId)).all(),
|
|
194
|
+
listAll: () => resolveDatabase().select().from(require_sqlite_schema.issueMilestones).all(),
|
|
195
|
+
findById: (milestoneId) => resolveDatabase().select().from(require_sqlite_schema.issueMilestones).where((0, drizzle_orm.eq)(require_sqlite_schema.issueMilestones.id, milestoneId)).get() ?? null,
|
|
196
|
+
findInWorkspace: (workspaceId, milestoneId) => resolveDatabase().select().from(require_sqlite_schema.issueMilestones).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueMilestones.id, milestoneId), (0, drizzle_orm.eq)(require_sqlite_schema.issueMilestones.workspaceId, workspaceId))).get() ?? null,
|
|
197
|
+
create: (values) => resolveDatabase().insert(require_sqlite_schema.issueMilestones).values(values).returning().get(),
|
|
198
|
+
update: (milestoneId, patch) => {
|
|
199
|
+
resolveDatabase().update(require_sqlite_schema.issueMilestones).set(patch).where((0, drizzle_orm.eq)(require_sqlite_schema.issueMilestones.id, milestoneId)).run();
|
|
200
|
+
},
|
|
201
|
+
delete: (milestoneId) => {
|
|
202
|
+
resolveDatabase().delete(require_sqlite_schema.issueMilestones).where((0, drizzle_orm.eq)(require_sqlite_schema.issueMilestones.id, milestoneId)).run();
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
issues: {
|
|
206
|
+
listInBoardOrder: (workspaceId) => {
|
|
207
|
+
const predicates = [workspaceId ? (0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, workspaceId) : void 0].filter((predicate) => predicate !== void 0);
|
|
208
|
+
return resolveDatabase().select().from(require_sqlite_schema.issues).where(predicates.length > 0 ? (0, drizzle_orm.and)(...predicates) : void 0).orderBy(require_sqlite_schema.issues.order, (0, drizzle_orm.desc)(require_sqlite_schema.issues.createdAt)).all();
|
|
209
|
+
},
|
|
210
|
+
listNewestFirst: () => resolveDatabase().select().from(require_sqlite_schema.issues).orderBy((0, drizzle_orm.desc)(require_sqlite_schema.issues.createdAt)).all(),
|
|
211
|
+
listAll: () => resolveDatabase().select().from(require_sqlite_schema.issues).all(),
|
|
212
|
+
listByWorkspace: (workspaceId) => resolveDatabase().select().from(require_sqlite_schema.issues).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, workspaceId)).all(),
|
|
213
|
+
listByIds: (issueIds) => resolveDatabase().select().from(require_sqlite_schema.issues).where((0, drizzle_orm.inArray)(require_sqlite_schema.issues.id, issueIds)).all(),
|
|
214
|
+
findById: (issueId) => resolveDatabase().select().from(require_sqlite_schema.issues).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.id, issueId)).get() ?? null,
|
|
215
|
+
findInWorkspace: (workspaceId, issueId) => resolveDatabase().select().from(require_sqlite_schema.issues).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issues.id, issueId), (0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, workspaceId))).get() ?? null,
|
|
216
|
+
findByNumber: (workspaceId, number) => resolveDatabase().select().from(require_sqlite_schema.issues).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, workspaceId), (0, drizzle_orm.eq)(require_sqlite_schema.issues.number, number))).get() ?? null,
|
|
217
|
+
maxNumber: (workspaceId) => {
|
|
218
|
+
return resolveDatabase().select({ maxNum: drizzle_orm.sql`coalesce(max(${require_sqlite_schema.issues.number}), 0)` }).from(require_sqlite_schema.issues).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, workspaceId)).get()?.maxNum ?? 0;
|
|
219
|
+
},
|
|
220
|
+
maxOrder: (workspaceId) => {
|
|
221
|
+
return resolveDatabase().select({ maxOrder: drizzle_orm.sql`coalesce(max(${require_sqlite_schema.issues.order}), 0)` }).from(require_sqlite_schema.issues).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.workspaceId, workspaceId)).get()?.maxOrder ?? 0;
|
|
222
|
+
},
|
|
223
|
+
create: (values) => resolveDatabase().insert(require_sqlite_schema.issues).values(values).returning().get(),
|
|
224
|
+
update: (issueId, patch) => {
|
|
225
|
+
resolveDatabase().update(require_sqlite_schema.issues).set(patch).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.id, issueId)).run();
|
|
226
|
+
},
|
|
227
|
+
updateMany: (issueIds, patch) => resolveDatabase().update(require_sqlite_schema.issues).set(patch).where(drizzle_orm.sql`${require_sqlite_schema.issues.id} IN (${drizzle_orm.sql.join(issueIds.map((issueId) => drizzle_orm.sql`${issueId}`), drizzle_orm.sql`, `)})`).run().changes,
|
|
228
|
+
clearMilestoneReferences: (milestoneId, updatedAt) => {
|
|
229
|
+
resolveDatabase().update(require_sqlite_schema.issues).set({
|
|
230
|
+
milestoneId: null,
|
|
231
|
+
updatedAt
|
|
232
|
+
}).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.milestoneId, milestoneId)).run();
|
|
233
|
+
},
|
|
234
|
+
clearParentReferences: (parentIssueId, updatedAt) => {
|
|
235
|
+
resolveDatabase().update(require_sqlite_schema.issues).set({
|
|
236
|
+
parentIssueId: null,
|
|
237
|
+
updatedAt
|
|
238
|
+
}).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.parentIssueId, parentIssueId)).run();
|
|
239
|
+
},
|
|
240
|
+
clearDelegateReferences: (delegate, updatedAt) => resolveDatabase().update(require_sqlite_schema.issues).set({
|
|
241
|
+
delegateAgentId: null,
|
|
242
|
+
delegateProviderTargetId: null,
|
|
243
|
+
updatedAt
|
|
244
|
+
}).where("agentId" in delegate ? (0, drizzle_orm.eq)(require_sqlite_schema.issues.delegateAgentId, delegate.agentId) : (0, drizzle_orm.eq)(require_sqlite_schema.issues.delegateProviderTargetId, delegate.providerTargetId)).run().changes,
|
|
245
|
+
delete: (issueId) => {
|
|
246
|
+
resolveDatabase().delete(require_sqlite_schema.issues).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.id, issueId)).run();
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
comments: {
|
|
250
|
+
listByIssue: (issueId) => resolveDatabase().select().from(require_sqlite_schema.issueComments).where((0, drizzle_orm.eq)(require_sqlite_schema.issueComments.issueId, issueId)).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issueComments.createdAt), COMMENT_INSERTION_ORDER).all(),
|
|
251
|
+
findById: (commentId) => resolveDatabase().select().from(require_sqlite_schema.issueComments).where((0, drizzle_orm.eq)(require_sqlite_schema.issueComments.id, commentId)).get() ?? null,
|
|
252
|
+
findByDedupeKey: (issueId, dedupeKey) => resolveDatabase().select().from(require_sqlite_schema.issueComments).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueComments.issueId, issueId), (0, drizzle_orm.eq)(require_sqlite_schema.issueComments.dedupeKey, dedupeKey))).get() ?? null,
|
|
253
|
+
listByDedupeKeyPrefix: (issueId, prefix) => resolveDatabase().select().from(require_sqlite_schema.issueComments).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueComments.issueId, issueId), drizzle_orm.sql`substr(${require_sqlite_schema.issueComments.dedupeKey}, 1, ${prefix.length}) = ${prefix}`)).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issueComments.createdAt), COMMENT_INSERTION_ORDER).all(),
|
|
254
|
+
create: (values) => resolveDatabase().insert(require_sqlite_schema.issueComments).values(values).returning().get(),
|
|
255
|
+
createOnce: (values) => {
|
|
256
|
+
const database = resolveDatabase();
|
|
257
|
+
const inserted = database.insert(require_sqlite_schema.issueComments).values(values).onConflictDoNothing().returning().get();
|
|
258
|
+
if (inserted) return inserted;
|
|
259
|
+
const existing = database.select().from(require_sqlite_schema.issueComments).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueComments.issueId, values.issueId), (0, drizzle_orm.eq)(require_sqlite_schema.issueComments.dedupeKey, values.dedupeKey))).get();
|
|
260
|
+
if (!existing) throw new Error(`issue comment for ${values.issueId} was refused, and no comment carries dedupe key ${values.dedupeKey}`);
|
|
261
|
+
return existing;
|
|
262
|
+
},
|
|
263
|
+
delete: (commentId) => {
|
|
264
|
+
resolveDatabase().delete(require_sqlite_schema.issueComments).where((0, drizzle_orm.eq)(require_sqlite_schema.issueComments.id, commentId)).run();
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
runs: {
|
|
268
|
+
listByIssue: (issueId) => resolveDatabase().select().from(require_sqlite_schema.issueRuns).where((0, drizzle_orm.eq)(require_sqlite_schema.issueRuns.issueId, issueId)).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issueRuns.startedAt), RUN_INSERTION_ORDER).all(),
|
|
269
|
+
listRunning: (issueId) => resolveDatabase().select().from(require_sqlite_schema.issueRuns).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueRuns.issueId, issueId), (0, drizzle_orm.eq)(require_sqlite_schema.issueRuns.state, "running"))).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issueRuns.startedAt), RUN_INSERTION_ORDER).all(),
|
|
270
|
+
listRunningWithSession: () => resolveDatabase().select().from(require_sqlite_schema.issueRuns).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueRuns.state, "running"), (0, drizzle_orm.isNotNull)(require_sqlite_schema.issueRuns.chatSessionId))).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issueRuns.startedAt), RUN_INSERTION_ORDER).all(),
|
|
271
|
+
findById: (runId) => resolveDatabase().select().from(require_sqlite_schema.issueRuns).where((0, drizzle_orm.eq)(require_sqlite_schema.issueRuns.id, runId)).get() ?? null,
|
|
272
|
+
findByChatSessionId: (chatSessionId) => resolveDatabase().select().from(require_sqlite_schema.issueRuns).where((0, drizzle_orm.eq)(require_sqlite_schema.issueRuns.chatSessionId, chatSessionId)).orderBy((0, drizzle_orm.desc)(require_sqlite_schema.issueRuns.startedAt), drizzle_orm.sql`${require_sqlite_schema.issueRuns}.rowid desc`).get() ?? null,
|
|
273
|
+
countByIssue: (issueId) => {
|
|
274
|
+
return resolveDatabase().select({ count: drizzle_orm.sql`count(*)` }).from(require_sqlite_schema.issueRuns).where((0, drizzle_orm.eq)(require_sqlite_schema.issueRuns.issueId, issueId)).get()?.count ?? 0;
|
|
275
|
+
},
|
|
276
|
+
create: (values) => resolveDatabase().insert(require_sqlite_schema.issueRuns).values(values).returning().get(),
|
|
277
|
+
update: (runId, patch) => {
|
|
278
|
+
resolveDatabase().update(require_sqlite_schema.issueRuns).set(patch).where((0, drizzle_orm.eq)(require_sqlite_schema.issueRuns.id, runId)).run();
|
|
279
|
+
},
|
|
280
|
+
project: (scope) => selectRunProjection(resolveDatabase(), scope)
|
|
281
|
+
},
|
|
282
|
+
relations: {
|
|
283
|
+
listByIssue: (issueId) => resolveDatabase().select().from(require_sqlite_schema.issueRelations).where((0, drizzle_orm.or)((0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.sourceIssueId, issueId), (0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.targetIssueId, issueId))).all(),
|
|
284
|
+
findById: (relationId) => resolveDatabase().select().from(require_sqlite_schema.issueRelations).where((0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.id, relationId)).get() ?? null,
|
|
285
|
+
findMatching: (edge) => resolveDatabase().select().from(require_sqlite_schema.issueRelations).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.type, edge.type), edge.type === "relates_to" ? (0, drizzle_orm.or)((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.sourceIssueId, edge.sourceIssueId), (0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.targetIssueId, edge.targetIssueId)), (0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.sourceIssueId, edge.targetIssueId), (0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.targetIssueId, edge.sourceIssueId))) : (0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.sourceIssueId, edge.sourceIssueId), (0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.targetIssueId, edge.targetIssueId)))).get() ?? null,
|
|
286
|
+
create: (values) => resolveDatabase().insert(require_sqlite_schema.issueRelations).values(values).returning().get(),
|
|
287
|
+
delete: (relationId) => {
|
|
288
|
+
resolveDatabase().delete(require_sqlite_schema.issueRelations).where((0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.id, relationId)).run();
|
|
289
|
+
},
|
|
290
|
+
deleteByIssue: (issueId) => {
|
|
291
|
+
resolveDatabase().delete(require_sqlite_schema.issueRelations).where((0, drizzle_orm.or)((0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.sourceIssueId, issueId), (0, drizzle_orm.eq)(require_sqlite_schema.issueRelations.targetIssueId, issueId))).run();
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
contextRefs: { replace: (issueId, contextRefs, updatedAt) => {
|
|
295
|
+
resolveDatabase().update(require_sqlite_schema.issues).set({
|
|
296
|
+
contextRefs,
|
|
297
|
+
updatedAt
|
|
298
|
+
}).where((0, drizzle_orm.eq)(require_sqlite_schema.issues.id, issueId)).run();
|
|
299
|
+
} },
|
|
300
|
+
fieldChanges: {
|
|
301
|
+
listByIssue: (issueId) => resolveDatabase().select().from(require_sqlite_schema.issueFieldChanges).where((0, drizzle_orm.eq)(require_sqlite_schema.issueFieldChanges.issueId, issueId)).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issueFieldChanges.createdAt), FIELD_CHANGE_INSERTION_ORDER).all(),
|
|
302
|
+
listByIssueField: (issueId, field) => resolveDatabase().select().from(require_sqlite_schema.issueFieldChanges).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issueFieldChanges.issueId, issueId), (0, drizzle_orm.eq)(require_sqlite_schema.issueFieldChanges.field, field))).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issueFieldChanges.createdAt), FIELD_CHANGE_INSERTION_ORDER).all(),
|
|
303
|
+
create: (values) => {
|
|
304
|
+
resolveDatabase().insert(require_sqlite_schema.issueFieldChanges).values(values).run();
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
pullRequests: {
|
|
308
|
+
listByIssue: (issueId) => resolveDatabase().select().from(require_sqlite_schema.issuePullRequests).where((0, drizzle_orm.eq)(require_sqlite_schema.issuePullRequests.issueId, issueId)).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issuePullRequests.createdAt), PULL_REQUEST_INSERTION_ORDER).all(),
|
|
309
|
+
listLinked: () => resolveDatabase().select({
|
|
310
|
+
...(0, drizzle_orm.getTableColumns)(require_sqlite_schema.issuePullRequests),
|
|
311
|
+
workspaceId: require_sqlite_schema.issues.workspaceId
|
|
312
|
+
}).from(require_sqlite_schema.issuePullRequests).innerJoin(require_sqlite_schema.issues, (0, drizzle_orm.eq)(require_sqlite_schema.issues.id, require_sqlite_schema.issuePullRequests.issueId)).orderBy((0, drizzle_orm.asc)(require_sqlite_schema.issuePullRequests.createdAt), PULL_REQUEST_INSERTION_ORDER).all(),
|
|
313
|
+
link: (values) => {
|
|
314
|
+
const database = resolveDatabase();
|
|
315
|
+
const inserted = database.insert(require_sqlite_schema.issuePullRequests).values(values).onConflictDoNothing().returning().get();
|
|
316
|
+
if (inserted) return inserted;
|
|
317
|
+
const existing = database.select().from(require_sqlite_schema.issuePullRequests).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issuePullRequests.issueId, values.issueId), (0, drizzle_orm.eq)(require_sqlite_schema.issuePullRequests.owner, values.owner), (0, drizzle_orm.eq)(require_sqlite_schema.issuePullRequests.repo, values.repo), (0, drizzle_orm.eq)(require_sqlite_schema.issuePullRequests.number, values.number))).get();
|
|
318
|
+
if (!existing) throw new Error(`pull request link for ${values.issueId} was refused, and the card names no ${values.owner}/${values.repo}#${values.number}`);
|
|
319
|
+
return existing;
|
|
320
|
+
},
|
|
321
|
+
unlink: (issueId, linkId) => resolveDatabase().delete(require_sqlite_schema.issuePullRequests).where((0, drizzle_orm.and)((0, drizzle_orm.eq)(require_sqlite_schema.issuePullRequests.id, linkId), (0, drizzle_orm.eq)(require_sqlite_schema.issuePullRequests.issueId, issueId))).returning().get() ?? null
|
|
322
|
+
},
|
|
323
|
+
meta: {
|
|
324
|
+
read: (key) => resolveDatabase().select().from(require_sqlite_schema.kanbanMeta).where((0, drizzle_orm.eq)(require_sqlite_schema.kanbanMeta.key, key)).get() ?? null,
|
|
325
|
+
bumpRevision: (key, updatedAt) => {
|
|
326
|
+
resolveDatabase().insert(require_sqlite_schema.kanbanMeta).values({
|
|
327
|
+
key,
|
|
328
|
+
revision: 1,
|
|
329
|
+
updatedAt
|
|
330
|
+
}).onConflictDoUpdate({
|
|
331
|
+
target: require_sqlite_schema.kanbanMeta.key,
|
|
332
|
+
set: {
|
|
333
|
+
revision: drizzle_orm.sql`${require_sqlite_schema.kanbanMeta.revision} + 1`,
|
|
334
|
+
updatedAt
|
|
335
|
+
}
|
|
336
|
+
}).run();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* `BoardStore` backed by SQLite: every method resolves immediately, because the
|
|
343
|
+
* core underneath it is synchronous.
|
|
344
|
+
*
|
|
345
|
+
* `database` is a function rather than a handle so the store follows whatever
|
|
346
|
+
* the host currently has open — a server passes its single connection
|
|
347
|
+
* accessor, a standalone reader passes its own.
|
|
348
|
+
*/
|
|
349
|
+
function createSqliteBoardStore(options) {
|
|
350
|
+
const core = createSqliteBoardStoreCore(options.database);
|
|
351
|
+
const store = {
|
|
352
|
+
statuses: {
|
|
353
|
+
listByWorkspace: async (workspaceId) => core.statuses.listByWorkspace(workspaceId),
|
|
354
|
+
listAllInBoardOrder: async () => core.statuses.listAllInBoardOrder(),
|
|
355
|
+
listAll: async () => core.statuses.listAll(),
|
|
356
|
+
findById: async (statusId) => core.statuses.findById(statusId),
|
|
357
|
+
findInWorkspace: async (workspaceId, statusId) => core.statuses.findInWorkspace(workspaceId, statusId),
|
|
358
|
+
countByWorkspace: async (workspaceId) => core.statuses.countByWorkspace(workspaceId),
|
|
359
|
+
create: async (values) => core.statuses.create(values),
|
|
360
|
+
update: async (statusId, patch) => core.statuses.update(statusId, patch),
|
|
361
|
+
delete: async (statusId) => core.statuses.delete(statusId)
|
|
362
|
+
},
|
|
363
|
+
milestones: {
|
|
364
|
+
listNewestFirst: async (workspaceId) => core.milestones.listNewestFirst(workspaceId),
|
|
365
|
+
listByWorkspace: async (workspaceId) => core.milestones.listByWorkspace(workspaceId),
|
|
366
|
+
listAll: async () => core.milestones.listAll(),
|
|
367
|
+
findById: async (milestoneId) => core.milestones.findById(milestoneId),
|
|
368
|
+
findInWorkspace: async (workspaceId, milestoneId) => core.milestones.findInWorkspace(workspaceId, milestoneId),
|
|
369
|
+
create: async (values) => core.milestones.create(values),
|
|
370
|
+
update: async (milestoneId, patch) => core.milestones.update(milestoneId, patch),
|
|
371
|
+
delete: async (milestoneId) => core.milestones.delete(milestoneId)
|
|
372
|
+
},
|
|
373
|
+
issues: {
|
|
374
|
+
listInBoardOrder: async (workspaceId) => core.issues.listInBoardOrder(workspaceId),
|
|
375
|
+
listNewestFirst: async () => core.issues.listNewestFirst(),
|
|
376
|
+
listAll: async () => core.issues.listAll(),
|
|
377
|
+
listByWorkspace: async (workspaceId) => core.issues.listByWorkspace(workspaceId),
|
|
378
|
+
listByIds: async (issueIds) => core.issues.listByIds(issueIds),
|
|
379
|
+
findById: async (issueId) => core.issues.findById(issueId),
|
|
380
|
+
findInWorkspace: async (workspaceId, issueId) => core.issues.findInWorkspace(workspaceId, issueId),
|
|
381
|
+
findByNumber: async (workspaceId, number) => core.issues.findByNumber(workspaceId, number),
|
|
382
|
+
maxNumber: async (workspaceId) => core.issues.maxNumber(workspaceId),
|
|
383
|
+
maxOrder: async (workspaceId) => core.issues.maxOrder(workspaceId),
|
|
384
|
+
create: async (values) => core.issues.create(values),
|
|
385
|
+
update: async (issueId, patch) => core.issues.update(issueId, patch),
|
|
386
|
+
updateMany: async (issueIds, patch) => core.issues.updateMany(issueIds, patch),
|
|
387
|
+
clearMilestoneReferences: async (milestoneId, updatedAt) => core.issues.clearMilestoneReferences(milestoneId, updatedAt),
|
|
388
|
+
clearParentReferences: async (parentIssueId, updatedAt) => core.issues.clearParentReferences(parentIssueId, updatedAt),
|
|
389
|
+
clearDelegateReferences: async (delegate, updatedAt) => core.issues.clearDelegateReferences(delegate, updatedAt),
|
|
390
|
+
delete: async (issueId) => core.issues.delete(issueId)
|
|
391
|
+
},
|
|
392
|
+
comments: {
|
|
393
|
+
listByIssue: async (issueId) => core.comments.listByIssue(issueId),
|
|
394
|
+
findById: async (commentId) => core.comments.findById(commentId),
|
|
395
|
+
findByDedupeKey: async (issueId, dedupeKey) => core.comments.findByDedupeKey(issueId, dedupeKey),
|
|
396
|
+
listByDedupeKeyPrefix: async (issueId, prefix) => core.comments.listByDedupeKeyPrefix(issueId, prefix),
|
|
397
|
+
create: async (values) => core.comments.create(values),
|
|
398
|
+
createOnce: async (values) => core.comments.createOnce(values),
|
|
399
|
+
delete: async (commentId) => core.comments.delete(commentId)
|
|
400
|
+
},
|
|
401
|
+
runs: {
|
|
402
|
+
listByIssue: async (issueId) => core.runs.listByIssue(issueId),
|
|
403
|
+
listRunning: async (issueId) => core.runs.listRunning(issueId),
|
|
404
|
+
listRunningWithSession: async () => core.runs.listRunningWithSession(),
|
|
405
|
+
findById: async (runId) => core.runs.findById(runId),
|
|
406
|
+
findByChatSessionId: async (chatSessionId) => core.runs.findByChatSessionId(chatSessionId),
|
|
407
|
+
countByIssue: async (issueId) => core.runs.countByIssue(issueId),
|
|
408
|
+
create: async (values) => core.runs.create(values),
|
|
409
|
+
update: async (runId, patch) => core.runs.update(runId, patch),
|
|
410
|
+
project: async (scope) => core.runs.project(scope)
|
|
411
|
+
},
|
|
412
|
+
relations: {
|
|
413
|
+
listByIssue: async (issueId) => core.relations.listByIssue(issueId),
|
|
414
|
+
findById: async (relationId) => core.relations.findById(relationId),
|
|
415
|
+
findMatching: async (edge) => core.relations.findMatching(edge),
|
|
416
|
+
create: async (values) => core.relations.create(values),
|
|
417
|
+
delete: async (relationId) => core.relations.delete(relationId),
|
|
418
|
+
deleteByIssue: async (issueId) => core.relations.deleteByIssue(issueId)
|
|
419
|
+
},
|
|
420
|
+
contextRefs: { replace: async (issueId, contextRefs, updatedAt) => core.contextRefs.replace(issueId, contextRefs, updatedAt) },
|
|
421
|
+
fieldChanges: {
|
|
422
|
+
listByIssue: async (issueId) => core.fieldChanges.listByIssue(issueId),
|
|
423
|
+
listByIssueField: async (issueId, field) => core.fieldChanges.listByIssueField(issueId, field),
|
|
424
|
+
create: async (values) => core.fieldChanges.create(values)
|
|
425
|
+
},
|
|
426
|
+
pullRequests: {
|
|
427
|
+
listByIssue: async (issueId) => core.pullRequests.listByIssue(issueId),
|
|
428
|
+
listLinked: async () => core.pullRequests.listLinked(),
|
|
429
|
+
link: async (values) => core.pullRequests.link(values),
|
|
430
|
+
unlink: async (issueId, linkId) => core.pullRequests.unlink(issueId, linkId)
|
|
431
|
+
},
|
|
432
|
+
meta: {
|
|
433
|
+
read: async (key) => core.meta.read(key),
|
|
434
|
+
bumpRevision: async (key, updatedAt) => core.meta.bumpRevision(key, updatedAt)
|
|
435
|
+
},
|
|
436
|
+
/**
|
|
437
|
+
* Run `fn` inside one SQLite transaction, through the driver-level helper in
|
|
438
|
+
* `./transaction.ts`.
|
|
439
|
+
*
|
|
440
|
+
* The caller rule lives there in full; in short: `fn` must be the only
|
|
441
|
+
* database-touching async chain in flight, and inside it only await
|
|
442
|
+
* operations that resolve without I/O — store methods, synchronous database
|
|
443
|
+
* writes.
|
|
444
|
+
*/
|
|
445
|
+
transaction: async (fn, transactionOptions) => {
|
|
446
|
+
return await runSqliteTransaction(options.database(), () => fn(store), transactionOptions);
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
return store;
|
|
450
|
+
}
|
|
451
|
+
//#endregion
|
|
452
|
+
//#region src/sqlite/open-database.ts
|
|
453
|
+
const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
454
|
+
/** The tables a board file must already carry for this package to read it. */
|
|
455
|
+
const REQUIRED_BOARD_TABLES = ["issue_runs", "kanban_meta"];
|
|
456
|
+
/** What a caller is told when the native module a board file needs is not installed. */
|
|
457
|
+
const BETTER_SQLITE3_MISSING_MESSAGE = "Install better-sqlite3 to use a board file";
|
|
458
|
+
/**
|
|
459
|
+
* `better-sqlite3` is an optional peer dependency (ruling 5): a Postgres-only
|
|
460
|
+
* or capabilities-only install carries no native module, and importing
|
|
461
|
+
* `kanbo/sqlite` for a type or for `runSqliteTransaction` should not
|
|
462
|
+
* demand one either. So the require happens here, the one place a board file
|
|
463
|
+
* is actually opened, rather than at the top of this module.
|
|
464
|
+
*/
|
|
465
|
+
function loadBetterSqlite3() {
|
|
466
|
+
try {
|
|
467
|
+
return require$1("better-sqlite3");
|
|
468
|
+
} catch (error) {
|
|
469
|
+
throw new Error(BETTER_SQLITE3_MISSING_MESSAGE, { cause: error });
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Drizzle's own better-sqlite3 driver, and why it is not imported at the top of
|
|
474
|
+
* this file like every other import in the package.
|
|
475
|
+
*
|
|
476
|
+
* `drizzle-orm/better-sqlite3` imports the native module eagerly — it has an
|
|
477
|
+
* overload where `drizzle()` constructs the connection itself — so a static
|
|
478
|
+
* import of it makes `better-sqlite3` required by everything that can reach
|
|
479
|
+
* this module, `kanbo capabilities` and `kanbo/mcp` included, and the
|
|
480
|
+
* friendly message above becomes unreachable. A dynamic import keeps the whole
|
|
481
|
+
* chain behind the one call that actually opens a file, which is what makes the
|
|
482
|
+
* optional peer honest. `createRequire` would be the smaller change and is the
|
|
483
|
+
* wrong one: it would load drizzle's CJS copy beside the ESM one this package
|
|
484
|
+
* already holds, and it would resolve from `node_modules`, which the packaged
|
|
485
|
+
* `dist/cli.cjs` deliberately does not have beside it.
|
|
486
|
+
*
|
|
487
|
+
* The module is remembered after the first load, so a command that opens two
|
|
488
|
+
* board files pays for it once.
|
|
489
|
+
*/
|
|
490
|
+
let sqliteDriver = null;
|
|
491
|
+
async function loadSqliteDriver() {
|
|
492
|
+
sqliteDriver ??= await import("drizzle-orm/better-sqlite3");
|
|
493
|
+
return sqliteDriver;
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Open a board file with the runtime pragmas a long-running server sets on its
|
|
497
|
+
* own database, so a board read from a terminal behaves the same as one read
|
|
498
|
+
* from inside an app. Migrations are not applied here: the file's owner applies
|
|
499
|
+
* them (`kanbo init --file`, `kanbo migrate`, or the app the database belongs to).
|
|
500
|
+
*
|
|
501
|
+
* Asynchronous because the driver beneath it is loaded on demand — see
|
|
502
|
+
* `loadSqliteDriver`. The native module is asked for first, so an install
|
|
503
|
+
* without it hears the sentence above rather than a module-resolution stack
|
|
504
|
+
* from inside drizzle.
|
|
505
|
+
*/
|
|
506
|
+
async function openBoardDatabase(path) {
|
|
507
|
+
const Database = loadBetterSqlite3();
|
|
508
|
+
const { drizzle } = await loadSqliteDriver();
|
|
509
|
+
const connection = new Database(path);
|
|
510
|
+
connection.pragma("foreign_keys = ON");
|
|
511
|
+
connection.pragma("journal_mode = WAL");
|
|
512
|
+
connection.pragma("busy_timeout = 5000");
|
|
513
|
+
connection.pragma("synchronous = NORMAL");
|
|
514
|
+
return {
|
|
515
|
+
database: drizzle(connection, { schema: require_sqlite_schema.boardSqliteSchema }),
|
|
516
|
+
connection,
|
|
517
|
+
close: () => connection.close()
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* Every column this build expects to find, read off the schema itself — the
|
|
522
|
+
* same idea as the Postgres guard's, so a column a later migration added
|
|
523
|
+
* (`issue_milestones.start_date`) is looked for without being listed again.
|
|
524
|
+
*/
|
|
525
|
+
function expectedColumns() {
|
|
526
|
+
return Object.values(require_sqlite_schema.boardSqliteSchema).flatMap((table) => {
|
|
527
|
+
const config = (0, drizzle_orm_sqlite_core.getTableConfig)(table);
|
|
528
|
+
return config.columns.map((column) => `${config.name}.${column.name}`);
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Refuse a board file this build cannot speak for, before any statement runs
|
|
533
|
+
* against it: one with no board in it (`missingTables`), one whose board a
|
|
534
|
+
* later migration has since added a table to (`missingAddedTables`) or a
|
|
535
|
+
* column to (`missingColumns`), and one stamped with another epoch. An app
|
|
536
|
+
* that migrates its own database on start need not call this; it is the guard a
|
|
537
|
+
* standalone reader puts in front of a file it did not create.
|
|
538
|
+
*/
|
|
539
|
+
function assertBoardSchema(database) {
|
|
540
|
+
const present = new Set(database.all(drizzle_orm.sql`select name from sqlite_master where type = 'table'`).map((row) => row.name));
|
|
541
|
+
const missing = REQUIRED_BOARD_TABLES.filter((table) => !present.has(table));
|
|
542
|
+
if (missing.length > 0) throw new require_migrations_path.BoardError("board_schema_outdated", { missingTables: missing });
|
|
543
|
+
const missingAddedTables = require_migrations_path.BOARD_TABLES_ADDED_WITHIN_EPOCH.filter((table) => !present.has(table));
|
|
544
|
+
if (missingAddedTables.length > 0) throw new require_migrations_path.BoardError("board_schema_outdated", { missingAddedTables });
|
|
545
|
+
const columns = new Set(database.all(drizzle_orm.sql`
|
|
546
|
+
select m.name as tableName, p.name as columnName
|
|
547
|
+
from sqlite_master m join pragma_table_info(m.name) p
|
|
548
|
+
where m.type = 'table'`).map((row) => `${row.tableName}.${row.columnName}`));
|
|
549
|
+
const missingColumns = expectedColumns().filter((column) => !columns.has(column));
|
|
550
|
+
if (missingColumns.length > 0) throw new require_migrations_path.BoardError("board_schema_outdated", { missingColumns });
|
|
551
|
+
const row = database.get(drizzle_orm.sql`select revision from kanban_meta where key = 'schema_epoch'`);
|
|
552
|
+
if (row?.revision !== 1) throw new require_migrations_path.BoardError("board_schema_outdated", {
|
|
553
|
+
expected: 1,
|
|
554
|
+
found: row?.revision ?? null
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Which of the two ever wrote this file's board, going by the `kanban_meta`
|
|
559
|
+
* marker `kanbo init --file` leaves behind (ruling 5-4).
|
|
560
|
+
*
|
|
561
|
+
* Absent reads as a host database — every file this package has never
|
|
562
|
+
* touched, and a file this build cannot make sense of at all: `assertBoardSchema` is what says a board is missing or too old,
|
|
563
|
+
* not this, so a table or a row that is not there yet is read as "not this
|
|
564
|
+
* package's file" rather than an error of its own.
|
|
565
|
+
*/
|
|
566
|
+
function readBoardFileOwner(database) {
|
|
567
|
+
try {
|
|
568
|
+
return database.get(drizzle_orm.sql`select key from kanban_meta where key = 'file_owner'`) ? "kanbo" : "host";
|
|
569
|
+
} catch {
|
|
570
|
+
return "host";
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Leave the marker that tells every later reader this file is this package's
|
|
575
|
+
* own, not a host app's. `kanbo init --file` calls this once, right after
|
|
576
|
+
* migrating; `insert or ignore` is what makes calling it again on the same
|
|
577
|
+
* file a no-op rather than a conflict.
|
|
578
|
+
*/
|
|
579
|
+
function markBoardFileOwnedByKanbo(database) {
|
|
580
|
+
database.run(drizzle_orm.sql`insert or ignore into kanban_meta (key, revision) values ('file_owner', 1)`);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Look at a file on a connection of its own and hand it back closed, so a
|
|
584
|
+
* caller can decide what to do with it before opening it for real.
|
|
585
|
+
*
|
|
586
|
+
* Two callers, two different decisions off the same two facts: the command line
|
|
587
|
+
* asks whose file it is to know whether a host app's `workspaces` row may be
|
|
588
|
+
* looked up in it, and a host app asks before it writes a board into it, because
|
|
589
|
+
* a file with tables that are not this package's is somebody else's database
|
|
590
|
+
* and migrating it would contaminate it (ruling 5-8).
|
|
591
|
+
*
|
|
592
|
+
* A path that is not a SQLite database at all — a README, a half-downloaded
|
|
593
|
+
* file, a directory — throws, and that is deliberately not the same answer as
|
|
594
|
+
* "a database with somebody else's tables in it": one is a mistake about which
|
|
595
|
+
* path to use and the other is a mistake about which file is a board. No pragma
|
|
596
|
+
* is set and nothing is written, so a file that turns out not to be ours is
|
|
597
|
+
* left exactly as it was found, journal mode included.
|
|
598
|
+
*/
|
|
599
|
+
function readBoardFileContents(path) {
|
|
600
|
+
const connection = new (loadBetterSqlite3())(path, { fileMustExist: true });
|
|
601
|
+
try {
|
|
602
|
+
if (connection.prepare("select count(*) as count from sqlite_master where type = 'table'").get().count === 0) return {
|
|
603
|
+
empty: true,
|
|
604
|
+
owner: "host"
|
|
605
|
+
};
|
|
606
|
+
return {
|
|
607
|
+
empty: false,
|
|
608
|
+
owner: readMarkedOwner(connection)
|
|
609
|
+
};
|
|
610
|
+
} finally {
|
|
611
|
+
connection.close();
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/** The marker, read straight off the driver: the same question `readBoardFileOwner` asks a drizzle handle. */
|
|
615
|
+
function readMarkedOwner(connection) {
|
|
616
|
+
try {
|
|
617
|
+
return connection.prepare("select key from kanban_meta where key = 'file_owner'").get() ? "kanbo" : "host";
|
|
618
|
+
} catch {
|
|
619
|
+
return "host";
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
//#endregion
|
|
623
|
+
//#region src/sqlite/migrate.ts
|
|
624
|
+
/** The folder's name, wherever it has been put. */
|
|
625
|
+
const MIGRATIONS_DIRECTORY = "drizzle-sqlite";
|
|
626
|
+
/**
|
|
627
|
+
* The board's SQLite migrations for the file that is running right now — the
|
|
628
|
+
* ones that build a board *file of this package's own*. The two layouts this
|
|
629
|
+
* has to survive are `src/migrations-path.ts`'s subject.
|
|
630
|
+
*/
|
|
631
|
+
function getBoardSqliteMigrationsPath() {
|
|
632
|
+
const here = (0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
633
|
+
return require_migrations_path.resolveBoardMigrationsPath(here, MIGRATIONS_DIRECTORY, require_migrations_path.hasMigrationJournal);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Create or update a board file this package owns.
|
|
637
|
+
*
|
|
638
|
+
* A host app's own database is not this: there the board is nine tables among
|
|
639
|
+
* the app's own, and the app's migration runner migrates the whole file — which
|
|
640
|
+
* may take two passes, because a migration that rebuilds a table has to drop
|
|
641
|
+
* it with foreign keys off. Nothing here rebuilds anything — `drizzle-sqlite/` starts at the
|
|
642
|
+
* board as it is now and will only ever be appended to — so drizzle's own
|
|
643
|
+
* migrator is the whole of what this needs, and a file that has been migrated
|
|
644
|
+
* before is a no-op by `__drizzle_migrations`.
|
|
645
|
+
*
|
|
646
|
+
* What the file holds afterwards is the board and nothing else: no
|
|
647
|
+
* `workspaces`, no `agents`, no `provider_targets`. Every reference out of the
|
|
648
|
+
* board is a plain text column for exactly that reason (ruling 5-1).
|
|
649
|
+
*/
|
|
650
|
+
function migrateBoardFile(database) {
|
|
651
|
+
(0, drizzle_orm_better_sqlite3_migrator.migrate)(database, { migrationsFolder: getBoardSqliteMigrationsPath() });
|
|
652
|
+
}
|
|
653
|
+
//#endregion
|
|
654
|
+
Object.defineProperty(exports, "assertBoardSchema", {
|
|
655
|
+
enumerable: true,
|
|
656
|
+
get: function() {
|
|
657
|
+
return assertBoardSchema;
|
|
658
|
+
}
|
|
659
|
+
});
|
|
660
|
+
Object.defineProperty(exports, "createSqliteBoardStore", {
|
|
661
|
+
enumerable: true,
|
|
662
|
+
get: function() {
|
|
663
|
+
return createSqliteBoardStore;
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
Object.defineProperty(exports, "getBoardSqliteMigrationsPath", {
|
|
667
|
+
enumerable: true,
|
|
668
|
+
get: function() {
|
|
669
|
+
return getBoardSqliteMigrationsPath;
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
Object.defineProperty(exports, "markBoardFileOwnedByKanbo", {
|
|
673
|
+
enumerable: true,
|
|
674
|
+
get: function() {
|
|
675
|
+
return markBoardFileOwnedByKanbo;
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
Object.defineProperty(exports, "migrateBoardFile", {
|
|
679
|
+
enumerable: true,
|
|
680
|
+
get: function() {
|
|
681
|
+
return migrateBoardFile;
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
Object.defineProperty(exports, "openBoardDatabase", {
|
|
685
|
+
enumerable: true,
|
|
686
|
+
get: function() {
|
|
687
|
+
return openBoardDatabase;
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
Object.defineProperty(exports, "readBoardFileContents", {
|
|
691
|
+
enumerable: true,
|
|
692
|
+
get: function() {
|
|
693
|
+
return readBoardFileContents;
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
Object.defineProperty(exports, "readBoardFileOwner", {
|
|
697
|
+
enumerable: true,
|
|
698
|
+
get: function() {
|
|
699
|
+
return readBoardFileOwner;
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
Object.defineProperty(exports, "runSqliteTransaction", {
|
|
703
|
+
enumerable: true,
|
|
704
|
+
get: function() {
|
|
705
|
+
return runSqliteTransaction;
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
//# sourceMappingURL=migrate-DnMjTCvu.cjs.map
|