@opentag/store 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/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @opentag/store
2
+
3
+ SQLite and Drizzle persistence primitives for OpenTag.
4
+
5
+ Use this package when embedding the dispatcher, testing dispatcher behavior, or building a compatible control plane that wants OpenTag's default run storage and lease model.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @opentag/store
11
+ ```
12
+
13
+ ## Exports
14
+
15
+ - `migrateSchema`: creates or updates the SQLite schema.
16
+ - `createOpenTagRepository`: repository API for runners, bindings, runs, leases, progress, completion, and audit events.
17
+ - Drizzle table definitions from `schema.ts`.
18
+ - Types such as `ClaimedOpenTagRun`, `OpenTagAuditEvent`, `RepoBinding`, and `SlackChannelBinding`.
19
+
20
+ ## Example
21
+
22
+ ```ts
23
+ import Database from "better-sqlite3";
24
+ import { drizzle } from "drizzle-orm/better-sqlite3";
25
+ import { createOpenTagRepository, migrateSchema } from "@opentag/store";
26
+
27
+ const sqlite = new Database("opentag.db");
28
+ migrateSchema(sqlite);
29
+
30
+ const repo = createOpenTagRepository(drizzle(sqlite));
31
+
32
+ await repo.registerRunner({ runnerId: "runner_local", name: "Local Runner" });
33
+ await repo.createRepoBinding({
34
+ provider: "github",
35
+ owner: "acme",
36
+ repo: "demo",
37
+ runnerId: "runner_local"
38
+ });
39
+ ```
40
+
41
+ ## Stability
42
+
43
+ The repository methods are public API for embedded control planes. The raw Drizzle table definitions are lower-level and may evolve with migrations.
@@ -0,0 +1,3 @@
1
+ export * from "./repository.js";
2
+ export * from "./schema.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,420 @@
1
+ // src/repository.ts
2
+ import { OpenTagEventSchema, OpenTagRunResultSchema } from "@opentag/core";
3
+ import { and, asc, eq } from "drizzle-orm";
4
+
5
+ // src/schema.ts
6
+ import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
7
+ var runs = sqliteTable(
8
+ "runs",
9
+ {
10
+ id: text("id").primaryKey(),
11
+ eventId: text("event_id").notNull(),
12
+ status: text("status").notNull(),
13
+ eventJson: text("event_json").notNull(),
14
+ resultJson: text("result_json"),
15
+ assignedRunnerId: text("assigned_runner_id"),
16
+ executor: text("executor"),
17
+ leasedAt: text("leased_at"),
18
+ leaseExpiresAt: text("lease_expires_at"),
19
+ heartbeatAt: text("heartbeat_at"),
20
+ createdAt: text("created_at").notNull(),
21
+ updatedAt: text("updated_at").notNull()
22
+ },
23
+ (table) => ({
24
+ statusIdx: index("runs_status_idx").on(table.status),
25
+ runnerIdx: index("runs_runner_idx").on(table.assignedRunnerId)
26
+ })
27
+ );
28
+ var runEvents = sqliteTable("run_events", {
29
+ id: integer("id").primaryKey({ autoIncrement: true }),
30
+ runId: text("run_id").notNull(),
31
+ type: text("type").notNull(),
32
+ payloadJson: text("payload_json").notNull(),
33
+ createdAt: text("created_at").notNull()
34
+ });
35
+ var runners = sqliteTable("runners", {
36
+ runnerId: text("runner_id").primaryKey(),
37
+ name: text("name").notNull(),
38
+ createdAt: text("created_at").notNull(),
39
+ heartbeatAt: text("heartbeat_at")
40
+ });
41
+ var repoBindings = sqliteTable(
42
+ "repo_bindings",
43
+ {
44
+ id: integer("id").primaryKey({ autoIncrement: true }),
45
+ provider: text("provider").notNull(),
46
+ owner: text("owner").notNull(),
47
+ repo: text("repo").notNull(),
48
+ runnerId: text("runner_id").notNull(),
49
+ workspacePath: text("workspace_path"),
50
+ defaultExecutor: text("default_executor"),
51
+ allowedActorsJson: text("allowed_actors_json"),
52
+ createdAt: text("created_at").notNull()
53
+ },
54
+ (table) => ({
55
+ repoUniqueIdx: uniqueIndex("repo_bindings_provider_owner_repo_idx").on(table.provider, table.owner, table.repo)
56
+ })
57
+ );
58
+ var slackChannelBindings = sqliteTable(
59
+ "slack_channel_bindings",
60
+ {
61
+ id: integer("id").primaryKey({ autoIncrement: true }),
62
+ teamId: text("team_id").notNull(),
63
+ channelId: text("channel_id").notNull(),
64
+ owner: text("owner").notNull(),
65
+ repo: text("repo").notNull(),
66
+ createdAt: text("created_at").notNull()
67
+ },
68
+ (table) => ({
69
+ slackChannelUniqueIdx: uniqueIndex("slack_channel_bindings_team_channel_idx").on(table.teamId, table.channelId)
70
+ })
71
+ );
72
+ function migrateSchema(sqlite) {
73
+ sqlite.exec(`
74
+ CREATE TABLE IF NOT EXISTS runs (
75
+ id TEXT PRIMARY KEY,
76
+ event_id TEXT NOT NULL,
77
+ status TEXT NOT NULL,
78
+ event_json TEXT NOT NULL,
79
+ result_json TEXT,
80
+ assigned_runner_id TEXT,
81
+ executor TEXT,
82
+ leased_at TEXT,
83
+ lease_expires_at TEXT,
84
+ heartbeat_at TEXT,
85
+ created_at TEXT NOT NULL,
86
+ updated_at TEXT NOT NULL
87
+ );
88
+ CREATE INDEX IF NOT EXISTS runs_status_idx ON runs(status);
89
+ CREATE INDEX IF NOT EXISTS runs_runner_idx ON runs(assigned_runner_id);
90
+ CREATE TABLE IF NOT EXISTS run_events (
91
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
92
+ run_id TEXT NOT NULL,
93
+ type TEXT NOT NULL,
94
+ payload_json TEXT NOT NULL,
95
+ created_at TEXT NOT NULL
96
+ );
97
+ CREATE TABLE IF NOT EXISTS runners (
98
+ runner_id TEXT PRIMARY KEY,
99
+ name TEXT NOT NULL,
100
+ created_at TEXT NOT NULL,
101
+ heartbeat_at TEXT
102
+ );
103
+ CREATE TABLE IF NOT EXISTS repo_bindings (
104
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
105
+ provider TEXT NOT NULL,
106
+ owner TEXT NOT NULL,
107
+ repo TEXT NOT NULL,
108
+ runner_id TEXT NOT NULL,
109
+ workspace_path TEXT,
110
+ default_executor TEXT,
111
+ allowed_actors_json TEXT,
112
+ created_at TEXT NOT NULL
113
+ );
114
+ CREATE UNIQUE INDEX IF NOT EXISTS repo_bindings_provider_owner_repo_idx
115
+ ON repo_bindings(provider, owner, repo);
116
+ CREATE TABLE IF NOT EXISTS slack_channel_bindings (
117
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
118
+ team_id TEXT NOT NULL,
119
+ channel_id TEXT NOT NULL,
120
+ owner TEXT NOT NULL,
121
+ repo TEXT NOT NULL,
122
+ created_at TEXT NOT NULL
123
+ );
124
+ CREATE UNIQUE INDEX IF NOT EXISTS slack_channel_bindings_team_channel_idx
125
+ ON slack_channel_bindings(team_id, channel_id);
126
+ `);
127
+ const columns = sqlite.prepare("PRAGMA table_info(repo_bindings)").all();
128
+ const columnNames = new Set(columns.map((column) => column.name));
129
+ if (!columnNames.has("workspace_path")) {
130
+ sqlite.exec("ALTER TABLE repo_bindings ADD COLUMN workspace_path TEXT");
131
+ }
132
+ if (!columnNames.has("default_executor")) {
133
+ sqlite.exec("ALTER TABLE repo_bindings ADD COLUMN default_executor TEXT");
134
+ }
135
+ if (!columnNames.has("allowed_actors_json")) {
136
+ sqlite.exec("ALTER TABLE repo_bindings ADD COLUMN allowed_actors_json TEXT");
137
+ }
138
+ const runColumns = sqlite.prepare("PRAGMA table_info(runs)").all();
139
+ const runColumnNames = new Set(runColumns.map((column) => column.name));
140
+ if (!runColumnNames.has("leased_at")) {
141
+ sqlite.exec("ALTER TABLE runs ADD COLUMN leased_at TEXT");
142
+ }
143
+ if (!runColumnNames.has("heartbeat_at")) {
144
+ sqlite.exec("ALTER TABLE runs ADD COLUMN heartbeat_at TEXT");
145
+ }
146
+ }
147
+
148
+ // src/repository.ts
149
+ function nowIso() {
150
+ return (/* @__PURE__ */ new Date()).toISOString();
151
+ }
152
+ function isIsoExpired(iso, now) {
153
+ if (!iso) return false;
154
+ return new Date(iso).getTime() <= now.getTime();
155
+ }
156
+ function runFromRow(row) {
157
+ const result = row.resultJson ? OpenTagRunResultSchema.parse(JSON.parse(row.resultJson)) : void 0;
158
+ return {
159
+ id: row.id,
160
+ eventId: row.eventId,
161
+ status: row.status,
162
+ assignedRunnerId: row.assignedRunnerId ?? void 0,
163
+ executor: row.executor ?? void 0,
164
+ createdAt: row.createdAt,
165
+ updatedAt: row.updatedAt,
166
+ ...result ? { result } : {}
167
+ };
168
+ }
169
+ function repoKeyFromEvent(event) {
170
+ const owner = event.metadata["owner"];
171
+ const repo = event.metadata["repo"];
172
+ if (typeof owner !== "string" || typeof repo !== "string") return null;
173
+ return {
174
+ provider: typeof event.metadata["repoProvider"] === "string" ? event.metadata["repoProvider"] : "github",
175
+ owner,
176
+ repo
177
+ };
178
+ }
179
+ function createOpenTagRepository(db) {
180
+ async function appendRunEvent(input) {
181
+ await db.insert(runEvents).values({
182
+ runId: input.runId,
183
+ type: input.type,
184
+ payloadJson: JSON.stringify(input.payload),
185
+ createdAt: input.createdAt ?? nowIso()
186
+ });
187
+ }
188
+ return {
189
+ appendRunEvent,
190
+ async registerRunner(input) {
191
+ const createdAt = nowIso();
192
+ await db.insert(runners).values({ runnerId: input.runnerId, name: input.name, createdAt }).onConflictDoNothing();
193
+ },
194
+ async createRepoBinding(input) {
195
+ await db.insert(repoBindings).values({
196
+ ...input,
197
+ workspacePath: input.workspacePath ?? null,
198
+ defaultExecutor: input.defaultExecutor ?? null,
199
+ allowedActorsJson: input.allowedActors ? JSON.stringify(input.allowedActors) : null,
200
+ createdAt: nowIso()
201
+ }).onConflictDoUpdate({
202
+ target: [repoBindings.provider, repoBindings.owner, repoBindings.repo],
203
+ set: {
204
+ runnerId: input.runnerId,
205
+ workspacePath: input.workspacePath ?? null,
206
+ defaultExecutor: input.defaultExecutor ?? null,
207
+ allowedActorsJson: input.allowedActors ? JSON.stringify(input.allowedActors) : null
208
+ }
209
+ });
210
+ },
211
+ async createSlackChannelBinding(input) {
212
+ await db.insert(slackChannelBindings).values({
213
+ teamId: input.teamId,
214
+ channelId: input.channelId,
215
+ owner: input.owner,
216
+ repo: input.repo,
217
+ createdAt: nowIso()
218
+ }).onConflictDoUpdate({
219
+ target: [slackChannelBindings.teamId, slackChannelBindings.channelId],
220
+ set: {
221
+ owner: input.owner,
222
+ repo: input.repo
223
+ }
224
+ });
225
+ },
226
+ async createRun(input) {
227
+ const event = OpenTagEventSchema.parse(input.event);
228
+ const createdAt = nowIso();
229
+ await db.insert(runs).values({
230
+ id: input.id,
231
+ eventId: event.id,
232
+ status: "queued",
233
+ eventJson: JSON.stringify(event),
234
+ createdAt,
235
+ updatedAt: createdAt
236
+ });
237
+ await appendRunEvent({
238
+ runId: input.id,
239
+ type: "run.created",
240
+ payload: { eventId: event.id },
241
+ createdAt
242
+ });
243
+ return {
244
+ id: input.id,
245
+ eventId: event.id,
246
+ status: "queued",
247
+ createdAt,
248
+ updatedAt: createdAt
249
+ };
250
+ },
251
+ async claimNextRun(input) {
252
+ const now = /* @__PURE__ */ new Date();
253
+ const activeRows = await db.select().from(runs).where(and(eq(runs.status, "assigned"))).orderBy(asc(runs.createdAt));
254
+ for (const activeRow of activeRows) {
255
+ if (!isIsoExpired(activeRow.leaseExpiresAt, now)) continue;
256
+ const updatedAt2 = nowIso();
257
+ await db.update(runs).set({
258
+ status: "queued",
259
+ assignedRunnerId: null,
260
+ leasedAt: null,
261
+ leaseExpiresAt: null,
262
+ heartbeatAt: null,
263
+ updatedAt: updatedAt2
264
+ }).where(eq(runs.id, activeRow.id));
265
+ await appendRunEvent({
266
+ runId: activeRow.id,
267
+ type: "run.lease_expired",
268
+ payload: { previousRunnerId: activeRow.assignedRunnerId, previousLeaseExpiresAt: activeRow.leaseExpiresAt },
269
+ createdAt: updatedAt2
270
+ });
271
+ }
272
+ const queuedRows = await db.select().from(runs).where(eq(runs.status, "queued")).orderBy(asc(runs.createdAt));
273
+ const row = queuedRows.find((candidate) => {
274
+ const event = OpenTagEventSchema.parse(JSON.parse(candidate.eventJson));
275
+ const repoKey = repoKeyFromEvent(event);
276
+ if (!repoKey) return false;
277
+ const binding = db.select().from(repoBindings).where(
278
+ and(
279
+ eq(repoBindings.provider, repoKey.provider),
280
+ eq(repoBindings.owner, repoKey.owner),
281
+ eq(repoBindings.repo, repoKey.repo),
282
+ eq(repoBindings.runnerId, input.runnerId)
283
+ )
284
+ ).limit(1).get();
285
+ return Boolean(binding);
286
+ });
287
+ if (!row) return null;
288
+ const updatedAt = nowIso();
289
+ const leasedAt = updatedAt;
290
+ const leaseExpiresAt = new Date(Date.now() + input.leaseSeconds * 1e3).toISOString();
291
+ await db.update(runs).set({
292
+ status: "assigned",
293
+ assignedRunnerId: input.runnerId,
294
+ leasedAt,
295
+ leaseExpiresAt,
296
+ heartbeatAt: leasedAt,
297
+ updatedAt
298
+ }).where(eq(runs.id, row.id));
299
+ await appendRunEvent({
300
+ runId: row.id,
301
+ type: "run.claimed",
302
+ payload: { runnerId: input.runnerId, leasedAt, leaseExpiresAt },
303
+ createdAt: updatedAt
304
+ });
305
+ return {
306
+ run: {
307
+ id: row.id,
308
+ eventId: row.eventId,
309
+ status: "assigned",
310
+ assignedRunnerId: input.runnerId,
311
+ executor: row.executor ?? void 0,
312
+ createdAt: row.createdAt,
313
+ updatedAt
314
+ },
315
+ event: OpenTagEventSchema.parse(JSON.parse(row.eventJson))
316
+ };
317
+ },
318
+ async getRepoBinding(input) {
319
+ const row = await db.select().from(repoBindings).where(
320
+ and(eq(repoBindings.provider, input.provider), eq(repoBindings.owner, input.owner), eq(repoBindings.repo, input.repo))
321
+ ).limit(1).get();
322
+ if (!row) return null;
323
+ return {
324
+ provider: row.provider,
325
+ owner: row.owner,
326
+ repo: row.repo,
327
+ runnerId: row.runnerId,
328
+ ...row.workspacePath ? { workspacePath: row.workspacePath } : {},
329
+ ...row.defaultExecutor ? { defaultExecutor: row.defaultExecutor } : {},
330
+ ...row.allowedActorsJson ? { allowedActors: JSON.parse(row.allowedActorsJson) } : {}
331
+ };
332
+ },
333
+ async getSlackChannelBinding(input) {
334
+ const row = await db.select().from(slackChannelBindings).where(and(eq(slackChannelBindings.teamId, input.teamId), eq(slackChannelBindings.channelId, input.channelId))).limit(1).get();
335
+ if (!row) return null;
336
+ return {
337
+ teamId: row.teamId,
338
+ channelId: row.channelId,
339
+ owner: row.owner,
340
+ repo: row.repo
341
+ };
342
+ },
343
+ async heartbeat(input) {
344
+ const updatedAt = nowIso();
345
+ const row = await db.select().from(runs).where(and(eq(runs.id, input.runId), eq(runs.assignedRunnerId, input.runnerId))).limit(1).get();
346
+ if (!row) return false;
347
+ const leaseSeconds = input.leaseSeconds ?? 60;
348
+ const leaseExpiresAt = new Date(Date.now() + leaseSeconds * 1e3).toISOString();
349
+ await db.update(runs).set({ heartbeatAt: updatedAt, leaseExpiresAt, updatedAt }).where(eq(runs.id, input.runId));
350
+ await appendRunEvent({
351
+ runId: input.runId,
352
+ type: "run.heartbeat",
353
+ payload: { runnerId: input.runnerId, heartbeatAt: updatedAt, leaseExpiresAt },
354
+ createdAt: updatedAt
355
+ });
356
+ return true;
357
+ },
358
+ async markRunning(input) {
359
+ const updatedAt = nowIso();
360
+ await db.update(runs).set({ status: "running", executor: input.executor, updatedAt }).where(eq(runs.id, input.runId));
361
+ await appendRunEvent({
362
+ runId: input.runId,
363
+ type: "run.running",
364
+ payload: { executor: input.executor },
365
+ createdAt: updatedAt
366
+ });
367
+ },
368
+ async completeRun(input) {
369
+ const result = OpenTagRunResultSchema.parse(input.result);
370
+ const updatedAt = nowIso();
371
+ const status = result.conclusion === "success" ? "succeeded" : result.conclusion === "cancelled" ? "cancelled" : "failed";
372
+ await db.update(runs).set({ status, resultJson: JSON.stringify(result), updatedAt }).where(eq(runs.id, input.runId));
373
+ await appendRunEvent({
374
+ runId: input.runId,
375
+ type: "run.completed",
376
+ payload: result,
377
+ createdAt: updatedAt
378
+ });
379
+ },
380
+ async recordProgress(input) {
381
+ await appendRunEvent({
382
+ runId: input.runId,
383
+ type: "run.progress",
384
+ payload: {
385
+ type: input.type ?? "progress",
386
+ message: input.message,
387
+ at: input.at ?? nowIso()
388
+ }
389
+ });
390
+ },
391
+ async getRun(input) {
392
+ const row = await db.select().from(runs).where(eq(runs.id, input.runId)).limit(1).get();
393
+ if (!row) return null;
394
+ return {
395
+ run: runFromRow(row),
396
+ event: OpenTagEventSchema.parse(JSON.parse(row.eventJson))
397
+ };
398
+ },
399
+ async listRunEvents(input) {
400
+ const rows = await db.select().from(runEvents).where(eq(runEvents.runId, input.runId)).orderBy(asc(runEvents.id));
401
+ return rows.map((row) => ({
402
+ id: row.id,
403
+ runId: row.runId,
404
+ type: row.type,
405
+ payload: JSON.parse(row.payloadJson),
406
+ createdAt: row.createdAt
407
+ }));
408
+ }
409
+ };
410
+ }
411
+ export {
412
+ createOpenTagRepository,
413
+ migrateSchema,
414
+ repoBindings,
415
+ runEvents,
416
+ runners,
417
+ runs,
418
+ slackChannelBindings
419
+ };
420
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/repository.ts","../src/schema.ts"],"sourcesContent":["import { OpenTagEventSchema, OpenTagRunResultSchema, type OpenTagEvent, type OpenTagRun, type OpenTagRunResult } from \"@opentag/core\";\nimport { and, asc, eq } from \"drizzle-orm\";\nimport type { BetterSQLite3Database } from \"drizzle-orm/better-sqlite3\";\nimport { repoBindings, runEvents, runners, runs, slackChannelBindings } from \"./schema.js\";\n\nexport type ClaimedOpenTagRun = {\n run: OpenTagRun;\n event: OpenTagEvent;\n};\n\nexport type OpenTagAuditEvent = {\n id: number;\n runId: string;\n type: string;\n payload: unknown;\n createdAt: string;\n};\n\nexport type RepoBinding = {\n provider: string;\n owner: string;\n repo: string;\n runnerId: string;\n workspacePath?: string;\n defaultExecutor?: string;\n allowedActors?: string[];\n};\n\nexport type SlackChannelBinding = {\n teamId: string;\n channelId: string;\n owner: string;\n repo: string;\n};\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\nfunction isIsoExpired(iso: string | null, now: Date): boolean {\n if (!iso) return false;\n return new Date(iso).getTime() <= now.getTime();\n}\n\nfunction runFromRow(row: typeof runs.$inferSelect): OpenTagRun {\n const result = row.resultJson ? OpenTagRunResultSchema.parse(JSON.parse(row.resultJson)) : undefined;\n return {\n id: row.id,\n eventId: row.eventId,\n status: row.status as OpenTagRun[\"status\"],\n assignedRunnerId: row.assignedRunnerId ?? undefined,\n executor: row.executor ?? undefined,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n ...(result ? { result } : {})\n };\n}\n\nfunction repoKeyFromEvent(event: OpenTagEvent): { provider: string; owner: string; repo: string } | null {\n const owner = event.metadata[\"owner\"];\n const repo = event.metadata[\"repo\"];\n if (typeof owner !== \"string\" || typeof repo !== \"string\") return null;\n return {\n provider: typeof event.metadata[\"repoProvider\"] === \"string\" ? (event.metadata[\"repoProvider\"] as string) : \"github\",\n owner,\n repo\n };\n}\n\nexport function createOpenTagRepository(db: BetterSQLite3Database) {\n async function appendRunEvent(input: { runId: string; type: string; payload: unknown; createdAt?: string }): Promise<void> {\n await db.insert(runEvents).values({\n runId: input.runId,\n type: input.type,\n payloadJson: JSON.stringify(input.payload),\n createdAt: input.createdAt ?? nowIso()\n });\n }\n\n return {\n appendRunEvent,\n\n async registerRunner(input: { runnerId: string; name: string }): Promise<void> {\n const createdAt = nowIso();\n await db.insert(runners).values({ runnerId: input.runnerId, name: input.name, createdAt }).onConflictDoNothing();\n },\n\n async createRepoBinding(input: {\n provider: string;\n owner: string;\n repo: string;\n runnerId: string;\n workspacePath?: string;\n defaultExecutor?: string;\n allowedActors?: string[];\n }): Promise<void> {\n await db\n .insert(repoBindings)\n .values({\n ...input,\n workspacePath: input.workspacePath ?? null,\n defaultExecutor: input.defaultExecutor ?? null,\n allowedActorsJson: input.allowedActors ? JSON.stringify(input.allowedActors) : null,\n createdAt: nowIso()\n })\n .onConflictDoUpdate({\n target: [repoBindings.provider, repoBindings.owner, repoBindings.repo],\n set: {\n runnerId: input.runnerId,\n workspacePath: input.workspacePath ?? null,\n defaultExecutor: input.defaultExecutor ?? null,\n allowedActorsJson: input.allowedActors ? JSON.stringify(input.allowedActors) : null\n }\n });\n },\n\n async createSlackChannelBinding(input: SlackChannelBinding): Promise<void> {\n await db\n .insert(slackChannelBindings)\n .values({\n teamId: input.teamId,\n channelId: input.channelId,\n owner: input.owner,\n repo: input.repo,\n createdAt: nowIso()\n })\n .onConflictDoUpdate({\n target: [slackChannelBindings.teamId, slackChannelBindings.channelId],\n set: {\n owner: input.owner,\n repo: input.repo\n }\n });\n },\n\n async createRun(input: { id: string; event: OpenTagEvent }): Promise<OpenTagRun> {\n const event = OpenTagEventSchema.parse(input.event);\n const createdAt = nowIso();\n await db.insert(runs).values({\n id: input.id,\n eventId: event.id,\n status: \"queued\",\n eventJson: JSON.stringify(event),\n createdAt,\n updatedAt: createdAt\n });\n await appendRunEvent({\n runId: input.id,\n type: \"run.created\",\n payload: { eventId: event.id },\n createdAt\n });\n return {\n id: input.id,\n eventId: event.id,\n status: \"queued\",\n createdAt,\n updatedAt: createdAt\n };\n },\n\n async claimNextRun(input: { runnerId: string; leaseSeconds: number }): Promise<ClaimedOpenTagRun | null> {\n const now = new Date();\n const activeRows = await db.select().from(runs).where(and(eq(runs.status, \"assigned\"))).orderBy(asc(runs.createdAt));\n for (const activeRow of activeRows) {\n if (!isIsoExpired(activeRow.leaseExpiresAt, now)) continue;\n const updatedAt = nowIso();\n await db\n .update(runs)\n .set({\n status: \"queued\",\n assignedRunnerId: null,\n leasedAt: null,\n leaseExpiresAt: null,\n heartbeatAt: null,\n updatedAt\n })\n .where(eq(runs.id, activeRow.id));\n await appendRunEvent({\n runId: activeRow.id,\n type: \"run.lease_expired\",\n payload: { previousRunnerId: activeRow.assignedRunnerId, previousLeaseExpiresAt: activeRow.leaseExpiresAt },\n createdAt: updatedAt\n });\n }\n\n const queuedRows = await db.select().from(runs).where(eq(runs.status, \"queued\")).orderBy(asc(runs.createdAt));\n const row = queuedRows.find((candidate) => {\n const event = OpenTagEventSchema.parse(JSON.parse(candidate.eventJson));\n const repoKey = repoKeyFromEvent(event);\n if (!repoKey) return false;\n const binding = db\n .select()\n .from(repoBindings)\n .where(\n and(\n eq(repoBindings.provider, repoKey.provider),\n eq(repoBindings.owner, repoKey.owner),\n eq(repoBindings.repo, repoKey.repo),\n eq(repoBindings.runnerId, input.runnerId)\n )\n )\n .limit(1)\n .get();\n return Boolean(binding);\n });\n if (!row) return null;\n\n const updatedAt = nowIso();\n const leasedAt = updatedAt;\n const leaseExpiresAt = new Date(Date.now() + input.leaseSeconds * 1000).toISOString();\n await db\n .update(runs)\n .set({\n status: \"assigned\",\n assignedRunnerId: input.runnerId,\n leasedAt,\n leaseExpiresAt,\n heartbeatAt: leasedAt,\n updatedAt\n })\n .where(eq(runs.id, row.id));\n await appendRunEvent({\n runId: row.id,\n type: \"run.claimed\",\n payload: { runnerId: input.runnerId, leasedAt, leaseExpiresAt },\n createdAt: updatedAt\n });\n\n return {\n run: {\n id: row.id,\n eventId: row.eventId,\n status: \"assigned\",\n assignedRunnerId: input.runnerId,\n executor: row.executor ?? undefined,\n createdAt: row.createdAt,\n updatedAt\n },\n event: OpenTagEventSchema.parse(JSON.parse(row.eventJson))\n };\n },\n\n async getRepoBinding(input: { provider: string; owner: string; repo: string }): Promise<RepoBinding | null> {\n const row = await db\n .select()\n .from(repoBindings)\n .where(\n and(eq(repoBindings.provider, input.provider), eq(repoBindings.owner, input.owner), eq(repoBindings.repo, input.repo))\n )\n .limit(1)\n .get();\n if (!row) return null;\n return {\n provider: row.provider,\n owner: row.owner,\n repo: row.repo,\n runnerId: row.runnerId,\n ...(row.workspacePath ? { workspacePath: row.workspacePath } : {}),\n ...(row.defaultExecutor ? { defaultExecutor: row.defaultExecutor } : {}),\n ...(row.allowedActorsJson ? { allowedActors: JSON.parse(row.allowedActorsJson) as string[] } : {})\n };\n },\n\n async getSlackChannelBinding(input: { teamId: string; channelId: string }): Promise<SlackChannelBinding | null> {\n const row = await db\n .select()\n .from(slackChannelBindings)\n .where(and(eq(slackChannelBindings.teamId, input.teamId), eq(slackChannelBindings.channelId, input.channelId)))\n .limit(1)\n .get();\n if (!row) return null;\n return {\n teamId: row.teamId,\n channelId: row.channelId,\n owner: row.owner,\n repo: row.repo\n };\n },\n\n async heartbeat(input: { runId: string; runnerId: string; leaseSeconds?: number }): Promise<boolean> {\n const updatedAt = nowIso();\n const row = await db\n .select()\n .from(runs)\n .where(and(eq(runs.id, input.runId), eq(runs.assignedRunnerId, input.runnerId)))\n .limit(1)\n .get();\n if (!row) return false;\n const leaseSeconds = input.leaseSeconds ?? 60;\n const leaseExpiresAt = new Date(Date.now() + leaseSeconds * 1000).toISOString();\n await db\n .update(runs)\n .set({ heartbeatAt: updatedAt, leaseExpiresAt, updatedAt })\n .where(eq(runs.id, input.runId));\n await appendRunEvent({\n runId: input.runId,\n type: \"run.heartbeat\",\n payload: { runnerId: input.runnerId, heartbeatAt: updatedAt, leaseExpiresAt },\n createdAt: updatedAt\n });\n return true;\n },\n\n async markRunning(input: { runId: string; executor: string }): Promise<void> {\n const updatedAt = nowIso();\n await db.update(runs).set({ status: \"running\", executor: input.executor, updatedAt }).where(eq(runs.id, input.runId));\n await appendRunEvent({\n runId: input.runId,\n type: \"run.running\",\n payload: { executor: input.executor },\n createdAt: updatedAt\n });\n },\n\n async completeRun(input: { runId: string; result: OpenTagRunResult }): Promise<void> {\n const result = OpenTagRunResultSchema.parse(input.result);\n const updatedAt = nowIso();\n const status = result.conclusion === \"success\" ? \"succeeded\" : result.conclusion === \"cancelled\" ? \"cancelled\" : \"failed\";\n await db.update(runs).set({ status, resultJson: JSON.stringify(result), updatedAt }).where(eq(runs.id, input.runId));\n await appendRunEvent({\n runId: input.runId,\n type: \"run.completed\",\n payload: result,\n createdAt: updatedAt\n });\n },\n\n async recordProgress(input: { runId: string; message: string; type?: string; at?: string }): Promise<void> {\n await appendRunEvent({\n runId: input.runId,\n type: \"run.progress\",\n payload: {\n type: input.type ?? \"progress\",\n message: input.message,\n at: input.at ?? nowIso()\n }\n });\n },\n\n async getRun(input: { runId: string }): Promise<ClaimedOpenTagRun | null> {\n const row = await db.select().from(runs).where(eq(runs.id, input.runId)).limit(1).get();\n if (!row) return null;\n return {\n run: runFromRow(row),\n event: OpenTagEventSchema.parse(JSON.parse(row.eventJson))\n };\n },\n\n async listRunEvents(input: { runId: string }): Promise<OpenTagAuditEvent[]> {\n const rows = await db.select().from(runEvents).where(eq(runEvents.runId, input.runId)).orderBy(asc(runEvents.id));\n return rows.map((row) => ({\n id: row.id,\n runId: row.runId,\n type: row.type,\n payload: JSON.parse(row.payloadJson) as unknown,\n createdAt: row.createdAt\n }));\n }\n };\n}\n","import type Database from \"better-sqlite3\";\nimport { index, integer, sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\n\nexport const runs = sqliteTable(\n \"runs\",\n {\n id: text(\"id\").primaryKey(),\n eventId: text(\"event_id\").notNull(),\n status: text(\"status\").notNull(),\n eventJson: text(\"event_json\").notNull(),\n resultJson: text(\"result_json\"),\n assignedRunnerId: text(\"assigned_runner_id\"),\n executor: text(\"executor\"),\n leasedAt: text(\"leased_at\"),\n leaseExpiresAt: text(\"lease_expires_at\"),\n heartbeatAt: text(\"heartbeat_at\"),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull()\n },\n (table) => ({\n statusIdx: index(\"runs_status_idx\").on(table.status),\n runnerIdx: index(\"runs_runner_idx\").on(table.assignedRunnerId)\n })\n);\n\nexport const runEvents = sqliteTable(\"run_events\", {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n runId: text(\"run_id\").notNull(),\n type: text(\"type\").notNull(),\n payloadJson: text(\"payload_json\").notNull(),\n createdAt: text(\"created_at\").notNull()\n});\n\nexport const runners = sqliteTable(\"runners\", {\n runnerId: text(\"runner_id\").primaryKey(),\n name: text(\"name\").notNull(),\n createdAt: text(\"created_at\").notNull(),\n heartbeatAt: text(\"heartbeat_at\")\n});\n\nexport const repoBindings = sqliteTable(\n \"repo_bindings\",\n {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n provider: text(\"provider\").notNull(),\n owner: text(\"owner\").notNull(),\n repo: text(\"repo\").notNull(),\n runnerId: text(\"runner_id\").notNull(),\n workspacePath: text(\"workspace_path\"),\n defaultExecutor: text(\"default_executor\"),\n allowedActorsJson: text(\"allowed_actors_json\"),\n createdAt: text(\"created_at\").notNull()\n },\n (table) => ({\n repoUniqueIdx: uniqueIndex(\"repo_bindings_provider_owner_repo_idx\").on(table.provider, table.owner, table.repo)\n })\n);\n\nexport const slackChannelBindings = sqliteTable(\n \"slack_channel_bindings\",\n {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n teamId: text(\"team_id\").notNull(),\n channelId: text(\"channel_id\").notNull(),\n owner: text(\"owner\").notNull(),\n repo: text(\"repo\").notNull(),\n createdAt: text(\"created_at\").notNull()\n },\n (table) => ({\n slackChannelUniqueIdx: uniqueIndex(\"slack_channel_bindings_team_channel_idx\").on(table.teamId, table.channelId)\n })\n);\n\nexport function migrateSchema(sqlite: Database.Database): void {\n sqlite.exec(`\n CREATE TABLE IF NOT EXISTS runs (\n id TEXT PRIMARY KEY,\n event_id TEXT NOT NULL,\n status TEXT NOT NULL,\n event_json TEXT NOT NULL,\n result_json TEXT,\n assigned_runner_id TEXT,\n executor TEXT,\n leased_at TEXT,\n lease_expires_at TEXT,\n heartbeat_at TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS runs_status_idx ON runs(status);\n CREATE INDEX IF NOT EXISTS runs_runner_idx ON runs(assigned_runner_id);\n CREATE TABLE IF NOT EXISTS run_events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n run_id TEXT NOT NULL,\n type TEXT NOT NULL,\n payload_json TEXT NOT NULL,\n created_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS runners (\n runner_id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n created_at TEXT NOT NULL,\n heartbeat_at TEXT\n );\n CREATE TABLE IF NOT EXISTS repo_bindings (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n provider TEXT NOT NULL,\n owner TEXT NOT NULL,\n repo TEXT NOT NULL,\n runner_id TEXT NOT NULL,\n workspace_path TEXT,\n default_executor TEXT,\n allowed_actors_json TEXT,\n created_at TEXT NOT NULL\n );\n CREATE UNIQUE INDEX IF NOT EXISTS repo_bindings_provider_owner_repo_idx\n ON repo_bindings(provider, owner, repo);\n CREATE TABLE IF NOT EXISTS slack_channel_bindings (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n team_id TEXT NOT NULL,\n channel_id TEXT NOT NULL,\n owner TEXT NOT NULL,\n repo TEXT NOT NULL,\n created_at TEXT NOT NULL\n );\n CREATE UNIQUE INDEX IF NOT EXISTS slack_channel_bindings_team_channel_idx\n ON slack_channel_bindings(team_id, channel_id);\n `);\n const columns = sqlite.prepare(\"PRAGMA table_info(repo_bindings)\").all() as { name: string }[];\n const columnNames = new Set(columns.map((column) => column.name));\n if (!columnNames.has(\"workspace_path\")) {\n sqlite.exec(\"ALTER TABLE repo_bindings ADD COLUMN workspace_path TEXT\");\n }\n if (!columnNames.has(\"default_executor\")) {\n sqlite.exec(\"ALTER TABLE repo_bindings ADD COLUMN default_executor TEXT\");\n }\n if (!columnNames.has(\"allowed_actors_json\")) {\n sqlite.exec(\"ALTER TABLE repo_bindings ADD COLUMN allowed_actors_json TEXT\");\n }\n const runColumns = sqlite.prepare(\"PRAGMA table_info(runs)\").all() as { name: string }[];\n const runColumnNames = new Set(runColumns.map((column) => column.name));\n if (!runColumnNames.has(\"leased_at\")) {\n sqlite.exec(\"ALTER TABLE runs ADD COLUMN leased_at TEXT\");\n }\n if (!runColumnNames.has(\"heartbeat_at\")) {\n sqlite.exec(\"ALTER TABLE runs ADD COLUMN heartbeat_at TEXT\");\n }\n}\n"],"mappings":"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","names":["updatedAt"]}
@@ -0,0 +1,93 @@
1
+ import { type OpenTagEvent, type OpenTagRun, type OpenTagRunResult } from "@opentag/core";
2
+ import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
3
+ export type ClaimedOpenTagRun = {
4
+ run: OpenTagRun;
5
+ event: OpenTagEvent;
6
+ };
7
+ export type OpenTagAuditEvent = {
8
+ id: number;
9
+ runId: string;
10
+ type: string;
11
+ payload: unknown;
12
+ createdAt: string;
13
+ };
14
+ export type RepoBinding = {
15
+ provider: string;
16
+ owner: string;
17
+ repo: string;
18
+ runnerId: string;
19
+ workspacePath?: string;
20
+ defaultExecutor?: string;
21
+ allowedActors?: string[];
22
+ };
23
+ export type SlackChannelBinding = {
24
+ teamId: string;
25
+ channelId: string;
26
+ owner: string;
27
+ repo: string;
28
+ };
29
+ export declare function createOpenTagRepository(db: BetterSQLite3Database): {
30
+ appendRunEvent: (input: {
31
+ runId: string;
32
+ type: string;
33
+ payload: unknown;
34
+ createdAt?: string;
35
+ }) => Promise<void>;
36
+ registerRunner(input: {
37
+ runnerId: string;
38
+ name: string;
39
+ }): Promise<void>;
40
+ createRepoBinding(input: {
41
+ provider: string;
42
+ owner: string;
43
+ repo: string;
44
+ runnerId: string;
45
+ workspacePath?: string;
46
+ defaultExecutor?: string;
47
+ allowedActors?: string[];
48
+ }): Promise<void>;
49
+ createSlackChannelBinding(input: SlackChannelBinding): Promise<void>;
50
+ createRun(input: {
51
+ id: string;
52
+ event: OpenTagEvent;
53
+ }): Promise<OpenTagRun>;
54
+ claimNextRun(input: {
55
+ runnerId: string;
56
+ leaseSeconds: number;
57
+ }): Promise<ClaimedOpenTagRun | null>;
58
+ getRepoBinding(input: {
59
+ provider: string;
60
+ owner: string;
61
+ repo: string;
62
+ }): Promise<RepoBinding | null>;
63
+ getSlackChannelBinding(input: {
64
+ teamId: string;
65
+ channelId: string;
66
+ }): Promise<SlackChannelBinding | null>;
67
+ heartbeat(input: {
68
+ runId: string;
69
+ runnerId: string;
70
+ leaseSeconds?: number;
71
+ }): Promise<boolean>;
72
+ markRunning(input: {
73
+ runId: string;
74
+ executor: string;
75
+ }): Promise<void>;
76
+ completeRun(input: {
77
+ runId: string;
78
+ result: OpenTagRunResult;
79
+ }): Promise<void>;
80
+ recordProgress(input: {
81
+ runId: string;
82
+ message: string;
83
+ type?: string;
84
+ at?: string;
85
+ }): Promise<void>;
86
+ getRun(input: {
87
+ runId: string;
88
+ }): Promise<ClaimedOpenTagRun | null>;
89
+ listRunEvents(input: {
90
+ runId: string;
91
+ }): Promise<OpenTagAuditEvent[]>;
92
+ };
93
+ //# sourceMappingURL=repository.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../src/repository.ts"],"names":[],"mappings":"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"}