@storyshelf/db-sqlite 0.3.2 → 0.5.4

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.
Files changed (73) hide show
  1. package/README.md +29 -3
  2. package/dist/better-sqlite3.d.mts +29 -0
  3. package/dist/better-sqlite3.mjs +116 -0
  4. package/dist/better-sqlite3.mjs.map +1 -0
  5. package/dist/bun-sqlite.d.mts +30 -0
  6. package/dist/bun-sqlite.mjs +120 -0
  7. package/dist/bun-sqlite.mjs.map +1 -0
  8. package/dist/d1.d.mts +38 -0
  9. package/dist/d1.mjs +108 -0
  10. package/dist/d1.mjs.map +1 -0
  11. package/dist/ddl.d.mts +14 -0
  12. package/dist/ddl.mjs +342 -0
  13. package/dist/ddl.mjs.map +1 -0
  14. package/dist/drizzle-factory.d.mts +19 -0
  15. package/dist/drizzle-factory.mjs +121 -0
  16. package/dist/drizzle-factory.mjs.map +1 -0
  17. package/dist/index.mjs +102 -36
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/schema/baseline.d.mts +204 -0
  20. package/dist/schema/baseline.mjs +23 -0
  21. package/dist/schema/baseline.mjs.map +1 -0
  22. package/dist/schema/build.d.mts +405 -0
  23. package/dist/schema/build.mjs +33 -0
  24. package/dist/schema/build.mjs.map +1 -0
  25. package/dist/schema/capture-attempt.d.mts +482 -0
  26. package/dist/schema/capture-attempt.mjs +40 -0
  27. package/dist/schema/capture-attempt.mjs.map +1 -0
  28. package/dist/schema/comment.d.mts +215 -0
  29. package/dist/schema/comment.mjs +26 -0
  30. package/dist/schema/comment.mjs.map +1 -0
  31. package/dist/schema/content-refs.d.mts +95 -0
  32. package/dist/schema/content-refs.mjs +16 -0
  33. package/dist/schema/content-refs.mjs.map +1 -0
  34. package/dist/schema/label.d.mts +288 -0
  35. package/dist/schema/label.mjs +30 -0
  36. package/dist/schema/label.mjs.map +1 -0
  37. package/dist/schema/member.d.mts +140 -0
  38. package/dist/schema/member.mjs +20 -0
  39. package/dist/schema/member.mjs.map +1 -0
  40. package/dist/schema/notification.d.mts +344 -0
  41. package/dist/schema/notification.mjs +33 -0
  42. package/dist/schema/notification.mjs.map +1 -0
  43. package/dist/schema/project-group-mapping.d.mts +119 -0
  44. package/dist/schema/project-group-mapping.mjs +18 -0
  45. package/dist/schema/project-group-mapping.mjs.map +1 -0
  46. package/dist/schema/project.d.mts +358 -0
  47. package/dist/schema/project.mjs +29 -0
  48. package/dist/schema/project.mjs.map +1 -0
  49. package/dist/schema/snapshot.d.mts +487 -0
  50. package/dist/schema/snapshot.mjs +38 -0
  51. package/dist/schema/snapshot.mjs.map +1 -0
  52. package/dist/schema/status-config.d.mts +157 -0
  53. package/dist/schema/status-config.mjs +20 -0
  54. package/dist/schema/status-config.mjs.map +1 -0
  55. package/dist/schema/token.d.mts +157 -0
  56. package/dist/schema/token.mjs +21 -0
  57. package/dist/schema/token.mjs.map +1 -0
  58. package/dist/schema/user-invite.d.mts +137 -0
  59. package/dist/schema/user-invite.mjs +19 -0
  60. package/dist/schema/user-invite.mjs.map +1 -0
  61. package/dist/schema/user.d.mts +238 -0
  62. package/dist/schema/user.mjs +23 -0
  63. package/dist/schema/user.mjs.map +1 -0
  64. package/dist/schema/webhook.d.mts +157 -0
  65. package/dist/schema/webhook.mjs +20 -0
  66. package/dist/schema/webhook.mjs.map +1 -0
  67. package/dist/schema.d.mts +47 -0
  68. package/dist/schema.mjs +46 -0
  69. package/dist/schema.mjs.map +1 -0
  70. package/dist/turso.d.mts +32 -0
  71. package/dist/turso.mjs +134 -0
  72. package/dist/turso.mjs.map +1 -0
  73. package/package.json +66 -10
package/dist/index.mjs CHANGED
@@ -1,32 +1,12 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { DatabaseSync } from "node:sqlite";
5
- import { createDrizzleAdapter } from "@storyshelf/core/adapter/database";
6
- import { DDL } from "@storyshelf/core/ddl";
7
- import { schema } from "@storyshelf/core/schema";
4
+ import { DDL, tableColumns } from "./ddl.mjs";
5
+ import { createDrizzleAdapter } from "./drizzle-factory.mjs";
6
+ import { schema } from "./schema.mjs";
8
7
  import { drizzle } from "drizzle-orm/sqlite-proxy";
8
+ import { DatabaseSync } from "node:sqlite";
9
9
  //#region src/index.ts
10
- const STORYBOOK_META_ALTER = "ALTER TABLE projects ADD COLUMN storybook_meta TEXT";
11
- const WEBHOOK_SECRET_ALTER = "ALTER TABLE webhooks ADD COLUMN secret_encrypted TEXT NOT NULL DEFAULT ''";
12
- const WEBHOOK_SECRET_DROP = "ALTER TABLE webhooks DROP COLUMN secret";
13
- /**
14
- * Execute one Drizzle statement against DatabaseSync, returning proxy-shaped rows.
15
- * Drizzle builds the SQL and params; only the transport is hand-written.
16
- * `values` shares the `all` shape (array rows); the factory never emits it.
17
- * Note: for `get`, the proxy passes `result.rows` straight to the row mapper,
18
- * so `rows` is the single row (or undefined on a miss) — not a one-row array.
19
- */
20
- function executeStatement(sqlite, sql, params, method) {
21
- const stmt = sqlite.prepare(sql);
22
- const args = params;
23
- if (method === "run") {
24
- stmt.run(...args);
25
- return { rows: [] };
26
- }
27
- if (method === "get") return { rows: stmt.get(...args) };
28
- return { rows: stmt.all(...args) };
29
- }
30
10
  /**
31
11
  * Create a SQLite-backed DatabaseAdapter using node:sqlite and Drizzle ORM.
32
12
  *
@@ -37,10 +17,7 @@ function createSqliteDatabase(path) {
37
17
  const sqlite = new DatabaseSync(path, { returnArrays: true });
38
18
  sqlite.exec("PRAGMA journal_mode = WAL");
39
19
  sqlite.exec("PRAGMA busy_timeout = 5000");
40
- const callback = (async (sql, params, method) => {
41
- await Promise.resolve();
42
- return executeStatement(sqlite, sql, params, method);
43
- });
20
+ const callback = createCallback(sqlite);
44
21
  const db = drizzle(callback, { schema });
45
22
  return createDrizzleAdapter(db, {
46
23
  metadata: {
@@ -50,21 +27,110 @@ function createSqliteDatabase(path) {
50
27
  kind: "sqlite",
51
28
  category: "database"
52
29
  },
30
+ tables: schema,
53
31
  migrate: () => {
54
- sqlite.exec(DDL);
55
- try {
56
- sqlite.exec(STORYBOOK_META_ALTER);
57
- } catch {}
58
- try {
59
- sqlite.exec(WEBHOOK_SECRET_ALTER);
60
- sqlite.exec(WEBHOOK_SECRET_DROP);
61
- } catch {}
32
+ runMigrations(sqlite);
62
33
  },
63
34
  close: () => {
64
35
  sqlite.close();
36
+ },
37
+ ping: () => {
38
+ sqlite.prepare("SELECT 1").get();
65
39
  }
66
40
  });
67
41
  }
42
+ const WEBHOOK_SECRET_DROP = "ALTER TABLE webhooks DROP COLUMN secret";
43
+ /**
44
+ * Re-runs share `(project_id, git_sha)` by design (ULID build ids), so the
45
+ * lookup index must not be unique. Existing databases created it UNIQUE —
46
+ * drop and recreate it plain.
47
+ */
48
+ const BUILDS_GITSHA_DEDUP = "DROP INDEX IF EXISTS builds_project_gitsha_idx";
49
+ const BUILDS_GITSHA_INDEX = "CREATE INDEX IF NOT EXISTS builds_project_gitsha_idx ON builds (project_id, git_sha)";
50
+ /**
51
+ * Execute one Drizzle statement against DatabaseSync, returning proxy-shaped rows.
52
+ * Drizzle builds the SQL and params; only the transport is hand-written.
53
+ * `values` shares the `all` shape (array rows); the factory never emits it.
54
+ * Note: for `get`, the proxy passes `result.rows` straight to the row mapper,
55
+ * so `rows` is the single row (or undefined on a miss) — not a one-row array.
56
+ */
57
+ function executeStatement(sqlite, sql, params, method) {
58
+ const stmt = sqlite.prepare(sql);
59
+ const args = params;
60
+ if (method === "run") {
61
+ stmt.run(...args);
62
+ return { rows: [] };
63
+ }
64
+ if (method === "get") return { rows: stmt.get(...args) };
65
+ return { rows: stmt.all(...args) };
66
+ }
67
+ function createCallback(sqlite) {
68
+ const callback = (async (sql, params, method) => {
69
+ await Promise.resolve();
70
+ return executeStatement(sqlite, sql, params, method);
71
+ });
72
+ return callback;
73
+ }
74
+ function execIgnore(sqlite, sql) {
75
+ try {
76
+ sqlite.exec(sql);
77
+ } catch {}
78
+ }
79
+ function migrateCommentsTable(sqlite) {
80
+ if (!sqlite.prepare("SELECT sql FROM sqlite_master WHERE type='table' AND name='comments'").get()?.sql.includes("user_id TEXT NOT NULL REFERENCES users")) return;
81
+ sqlite.exec("PRAGMA foreign_keys = OFF");
82
+ sqlite.exec(`
83
+ CREATE TABLE comments_new (
84
+ id TEXT PRIMARY KEY,
85
+ project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
86
+ build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,
87
+ snapshot_id TEXT REFERENCES snapshots(id) ON DELETE CASCADE,
88
+ user_id TEXT REFERENCES users(id) ON DELETE CASCADE,
89
+ body TEXT NOT NULL,
90
+ parent_id TEXT,
91
+ resolved INTEGER NOT NULL DEFAULT 0,
92
+ created_at TEXT NOT NULL,
93
+ updated_at TEXT NOT NULL
94
+ )
95
+ `);
96
+ sqlite.exec("INSERT INTO comments_new SELECT * FROM comments");
97
+ sqlite.exec("DROP TABLE comments");
98
+ sqlite.exec("ALTER TABLE comments_new RENAME TO comments");
99
+ sqlite.exec("CREATE INDEX IF NOT EXISTS comments_build_id_idx ON comments (build_id)");
100
+ sqlite.exec("PRAGMA foreign_keys = ON");
101
+ }
102
+ /** Add any DDL column missing from a volume created before it shipped. */
103
+ function ensureColumns(sqlite, ddl) {
104
+ for (const [table, columns] of tableColumns(ddl)) {
105
+ const rows = sqlite.prepare(`PRAGMA table_info(${table})`).all();
106
+ const existing = new Set(rows.map((row) => String(row[1])));
107
+ for (const column of columns) {
108
+ const [name] = column.split(/\s+/u);
109
+ if (name && !existing.has(name)) execIgnore(sqlite, `ALTER TABLE ${table} ADD COLUMN ${column}`);
110
+ }
111
+ }
112
+ }
113
+ function runMigrations(sqlite) {
114
+ sqlite.exec("PRAGMA foreign_keys = ON");
115
+ sqlite.exec(DDL);
116
+ ensureColumns(sqlite, DDL);
117
+ try {
118
+ sqlite.exec(WEBHOOK_SECRET_DROP);
119
+ } catch {}
120
+ migrateBuildsGitShaIndex(sqlite);
121
+ try {
122
+ migrateCommentsTable(sqlite);
123
+ } catch {
124
+ execIgnore(sqlite, "PRAGMA foreign_keys = ON");
125
+ }
126
+ }
127
+ /** Downgrade a legacy UNIQUE project/sha index to a plain lookup index. */
128
+ function migrateBuildsGitShaIndex(sqlite) {
129
+ try {
130
+ sqlite.exec(BUILDS_GITSHA_DEDUP);
131
+ sqlite.exec(BUILDS_GITSHA_INDEX);
132
+ } catch {}
133
+ }
68
134
  //#endregion
69
135
  export { createSqliteDatabase };
70
136
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { DatabaseSync, type SQLInputValue } from \"node:sqlite\";\nimport type { DatabaseAdapter } from \"@storyshelf/core/adapter/database\";\nimport { createDrizzleAdapter } from \"@storyshelf/core/adapter/database\";\nimport { DDL } from \"@storyshelf/core/ddl\";\nimport { schema } from \"@storyshelf/core/schema\";\nimport { drizzle, type AsyncRemoteCallback } from \"drizzle-orm/sqlite-proxy\";\n\ndeclare const __PKG_VERSION__: string | undefined;\n\nconst STORYBOOK_META_ALTER = \"ALTER TABLE projects ADD COLUMN storybook_meta TEXT\";\nconst WEBHOOK_SECRET_ALTER = \"ALTER TABLE webhooks ADD COLUMN secret_encrypted TEXT NOT NULL DEFAULT ''\";\nconst WEBHOOK_SECRET_DROP = \"ALTER TABLE webhooks DROP COLUMN secret\";\n\ntype ProxyMethod = \"run\" | \"all\" | \"values\" | \"get\";\n\n/**\n * Execute one Drizzle statement against DatabaseSync, returning proxy-shaped rows.\n * Drizzle builds the SQL and params; only the transport is hand-written.\n * `values` shares the `all` shape (array rows); the factory never emits it.\n * Note: for `get`, the proxy passes `result.rows` straight to the row mapper,\n * so `rows` is the single row (or undefined on a miss) — not a one-row array.\n */\nfunction executeStatement(\n sqlite: DatabaseSync,\n sql: string,\n params: unknown[],\n method: ProxyMethod,\n): { rows: unknown } {\n const stmt = sqlite.prepare(sql);\n // Single contained cast: Drizzle's params are SQLite scalars by construction.\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- tsc requires the narrowing; the rule misfires on the variadic overloads\n const args = params as SQLInputValue[];\n if (method === \"run\") {\n stmt.run(...args);\n return { rows: [] };\n }\n if (method === \"get\") {\n return { rows: stmt.get(...args) };\n }\n return { rows: stmt.all(...args) };\n}\n\n/**\n * Create a SQLite-backed DatabaseAdapter using node:sqlite and Drizzle ORM.\n *\n * @param path - Filesystem path to the SQLite database file.\n * @returns A DatabaseAdapter backed by the given SQLite file (WAL mode).\n */\nexport function createSqliteDatabase(path: string): DatabaseAdapter {\n const sqlite = new DatabaseSync(path, { returnArrays: true });\n sqlite.exec(\"PRAGMA journal_mode = WAL\");\n sqlite.exec(\"PRAGMA busy_timeout = 5000\");\n // The declared callback type claims `{ rows: any[] }`, but the session\n // passes `result.rows` straight to the row mapper for `get` — so `rows`\n // must be the single row (or undefined), not a one-row array. The cast\n // quarantines drizzle's inaccurate declaration at this boundary.\n const callback = (async (sql: string, params: unknown[], method: ProxyMethod) => {\n await Promise.resolve();\n return executeStatement(sqlite, sql, params, method);\n }) as AsyncRemoteCallback;\n const db = drizzle(callback, { schema });\n\n return createDrizzleAdapter(db, {\n metadata: {\n name: \"SQLite\",\n version: (globalThis as unknown as { __PKG_VERSION__?: string }).__PKG_VERSION__ ?? \"0.0.0\",\n description: \"SQLite database adapter (node:sqlite + Drizzle)\",\n kind: \"sqlite\",\n category: \"database\",\n },\n migrate: () => {\n sqlite.exec(DDL);\n try {\n sqlite.exec(STORYBOOK_META_ALTER);\n } catch {\n // Column already exists or other error — ignore for idempotency\n }\n try {\n sqlite.exec(WEBHOOK_SECRET_ALTER);\n sqlite.exec(WEBHOOK_SECRET_DROP);\n } catch {\n // Already migrated (or fresh schema) — ignore for idempotency\n }\n },\n close: () => {\n sqlite.close();\n },\n });\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;;;;;;;;AAW5B,SAAS,iBACP,QACA,KACA,QACA,QACmB;CACnB,MAAM,OAAO,OAAO,QAAQ,GAAG;CAG/B,MAAM,OAAO;CACb,IAAI,WAAW,OAAO;EACpB,KAAK,IAAI,GAAG,IAAI;EAChB,OAAO,EAAE,MAAM,CAAC,EAAE;CACpB;CACA,IAAI,WAAW,OACb,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE;CAEnC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE;AACnC;;;;;;;AAQA,SAAgB,qBAAqB,MAA+B;CAClE,MAAM,SAAS,IAAI,aAAa,MAAM,EAAE,cAAc,KAAK,CAAC;CAC5D,OAAO,KAAK,2BAA2B;CACvC,OAAO,KAAK,4BAA4B;CAKxC,MAAM,YAAY,OAAO,KAAa,QAAmB,WAAwB;EAC/E,MAAM,QAAQ,QAAQ;EACtB,OAAO,iBAAiB,QAAQ,KAAK,QAAQ,MAAM;CACrD;CACA,MAAM,KAAK,QAAQ,UAAU,EAAE,OAAO,CAAC;CAEvC,OAAO,qBAAqB,IAAI;EAC9B,UAAU;GACR,MAAM;GACN,SAAU,WAAuD,mBAAmB;GACpF,aAAa;GACb,MAAM;GACN,UAAU;EACZ;EACA,eAAe;GACb,OAAO,KAAK,GAAG;GACf,IAAI;IACF,OAAO,KAAK,oBAAoB;GAClC,QAAQ,CAER;GACA,IAAI;IACF,OAAO,KAAK,oBAAoB;IAChC,OAAO,KAAK,mBAAmB;GACjC,QAAQ,CAER;EACF;EACA,aAAa;GACX,OAAO,MAAM;EACf;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { DatabaseAdapter } from \"@storyshelf/core/adapter/database\";\nimport { drizzle, type AsyncRemoteCallback } from \"drizzle-orm/sqlite-proxy\";\nimport { DatabaseSync, type SQLInputValue } from \"node:sqlite\";\nimport { DDL, tableColumns } from \"./ddl.ts\";\nimport { createDrizzleAdapter } from \"./drizzle-factory.ts\";\nimport { schema } from \"./schema/index.ts\";\n\ndeclare const __PKG_VERSION__: string | undefined;\n\n/**\n * Create a SQLite-backed DatabaseAdapter using node:sqlite and Drizzle ORM.\n *\n * @param path - Filesystem path to the SQLite database file.\n * @returns A DatabaseAdapter backed by the given SQLite file (WAL mode).\n */\nexport function createSqliteDatabase(path: string): DatabaseAdapter {\n const sqlite = new DatabaseSync(path, { returnArrays: true });\n sqlite.exec(\"PRAGMA journal_mode = WAL\");\n sqlite.exec(\"PRAGMA busy_timeout = 5000\");\n const callback = createCallback(sqlite);\n const db = drizzle(callback, { schema });\n\n return createDrizzleAdapter(db, {\n metadata: {\n name: \"SQLite\",\n version: (globalThis as unknown as { __PKG_VERSION__?: string }).__PKG_VERSION__ ?? \"0.0.0\",\n description: \"SQLite database adapter (node:sqlite + Drizzle)\",\n kind: \"sqlite\",\n category: \"database\",\n },\n tables: schema,\n migrate: () => {\n runMigrations(sqlite);\n },\n close: () => {\n sqlite.close();\n },\n ping: () => {\n sqlite.prepare(\"SELECT 1\").get();\n },\n });\n}\n\nconst WEBHOOK_SECRET_DROP = \"ALTER TABLE webhooks DROP COLUMN secret\";\n\n/**\n * Re-runs share `(project_id, git_sha)` by design (ULID build ids), so the\n * lookup index must not be unique. Existing databases created it UNIQUE —\n * drop and recreate it plain.\n */\nconst BUILDS_GITSHA_DEDUP = \"DROP INDEX IF EXISTS builds_project_gitsha_idx\";\nconst BUILDS_GITSHA_INDEX =\n \"CREATE INDEX IF NOT EXISTS builds_project_gitsha_idx ON builds (project_id, git_sha)\";\n\ntype ProxyMethod = \"run\" | \"all\" | \"values\" | \"get\";\n\n/**\n * Execute one Drizzle statement against DatabaseSync, returning proxy-shaped rows.\n * Drizzle builds the SQL and params; only the transport is hand-written.\n * `values` shares the `all` shape (array rows); the factory never emits it.\n * Note: for `get`, the proxy passes `result.rows` straight to the row mapper,\n * so `rows` is the single row (or undefined on a miss) — not a one-row array.\n */\nfunction executeStatement(\n sqlite: DatabaseSync,\n sql: string,\n params: unknown[],\n method: ProxyMethod,\n): { rows: unknown } {\n const stmt = sqlite.prepare(sql);\n // Single contained cast: Drizzle's params are SQLite scalars by construction.\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- tsc requires the narrowing; the rule misfires on the variadic overloads\n const args = params as SQLInputValue[];\n if (method === \"run\") {\n stmt.run(...args);\n return { rows: [] };\n }\n if (method === \"get\") {\n return { rows: stmt.get(...args) };\n }\n return { rows: stmt.all(...args) };\n}\n\nfunction createCallback(sqlite: DatabaseSync): AsyncRemoteCallback {\n const callback = (async (sql: string, params: unknown[], method: ProxyMethod) => {\n await Promise.resolve();\n return executeStatement(sqlite, sql, params, method);\n }) as AsyncRemoteCallback;\n return callback;\n}\n\nfunction execIgnore(sqlite: DatabaseSync, sql: string): void {\n try {\n sqlite.exec(sql);\n } catch {\n // idempotent — column already exists or other no-op\n }\n}\n\nfunction migrateCommentsTable(sqlite: DatabaseSync): void {\n const row = sqlite\n .prepare(\"SELECT sql FROM sqlite_master WHERE type='table' AND name='comments'\")\n .get() as { sql: string } | undefined;\n if (!row?.sql.includes(\"user_id TEXT NOT NULL REFERENCES users\")) {\n return;\n }\n sqlite.exec(\"PRAGMA foreign_keys = OFF\");\n sqlite.exec(`\n CREATE TABLE comments_new (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,\n build_id TEXT NOT NULL REFERENCES builds(id) ON DELETE CASCADE,\n snapshot_id TEXT REFERENCES snapshots(id) ON DELETE CASCADE,\n user_id TEXT REFERENCES users(id) ON DELETE CASCADE,\n body TEXT NOT NULL,\n parent_id TEXT,\n resolved INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n )\n `);\n sqlite.exec(\"INSERT INTO comments_new SELECT * FROM comments\");\n sqlite.exec(\"DROP TABLE comments\");\n sqlite.exec(\"ALTER TABLE comments_new RENAME TO comments\");\n sqlite.exec(\"CREATE INDEX IF NOT EXISTS comments_build_id_idx ON comments (build_id)\");\n sqlite.exec(\"PRAGMA foreign_keys = ON\");\n}\n\n/** Add any DDL column missing from a volume created before it shipped. */\nfunction ensureColumns(sqlite: DatabaseSync, ddl: string): void {\n for (const [table, columns] of tableColumns(ddl)) {\n const rows = sqlite.prepare(`PRAGMA table_info(${table})`).all() as unknown as unknown[][];\n const existing = new Set(rows.map((row) => String(row[1])));\n for (const column of columns) {\n const [name] = column.split(/\\s+/u);\n if (name && !existing.has(name)) {\n execIgnore(sqlite, `ALTER TABLE ${table} ADD COLUMN ${column}`);\n }\n }\n }\n}\n\nfunction runMigrations(sqlite: DatabaseSync): void {\n sqlite.exec(\"PRAGMA foreign_keys = ON\");\n sqlite.exec(DDL);\n ensureColumns(sqlite, DDL);\n try {\n sqlite.exec(WEBHOOK_SECRET_DROP);\n } catch {\n // already migrated — ignore\n }\n migrateBuildsGitShaIndex(sqlite);\n try {\n migrateCommentsTable(sqlite);\n } catch {\n execIgnore(sqlite, \"PRAGMA foreign_keys = ON\");\n }\n}\n\n/** Downgrade a legacy UNIQUE project/sha index to a plain lookup index. */\nfunction migrateBuildsGitShaIndex(sqlite: DatabaseSync): void {\n try {\n sqlite.exec(BUILDS_GITSHA_DEDUP);\n sqlite.exec(BUILDS_GITSHA_INDEX);\n } catch {\n // already migrated — ignore\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,SAAgB,qBAAqB,MAA+B;CAClE,MAAM,SAAS,IAAI,aAAa,MAAM,EAAE,cAAc,KAAK,CAAC;CAC5D,OAAO,KAAK,2BAA2B;CACvC,OAAO,KAAK,4BAA4B;CACxC,MAAM,WAAW,eAAe,MAAM;CACtC,MAAM,KAAK,QAAQ,UAAU,EAAE,OAAO,CAAC;CAEvC,OAAO,qBAAqB,IAAI;EAC9B,UAAU;GACR,MAAM;GACN,SAAU,WAAuD,mBAAmB;GACpF,aAAa;GACb,MAAM;GACN,UAAU;EACZ;EACA,QAAQ;EACR,eAAe;GACb,cAAc,MAAM;EACtB;EACA,aAAa;GACX,OAAO,MAAM;EACf;EACA,YAAY;GACV,OAAO,QAAQ,UAAU,CAAC,CAAC,IAAI;EACjC;CACF,CAAC;AACH;AAEA,MAAM,sBAAsB;;;;;;AAO5B,MAAM,sBAAsB;AAC5B,MAAM,sBACJ;;;;;;;;AAWF,SAAS,iBACP,QACA,KACA,QACA,QACmB;CACnB,MAAM,OAAO,OAAO,QAAQ,GAAG;CAG/B,MAAM,OAAO;CACb,IAAI,WAAW,OAAO;EACpB,KAAK,IAAI,GAAG,IAAI;EAChB,OAAO,EAAE,MAAM,CAAC,EAAE;CACpB;CACA,IAAI,WAAW,OACb,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE;CAEnC,OAAO,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE;AACnC;AAEA,SAAS,eAAe,QAA2C;CACjE,MAAM,YAAY,OAAO,KAAa,QAAmB,WAAwB;EAC/E,MAAM,QAAQ,QAAQ;EACtB,OAAO,iBAAiB,QAAQ,KAAK,QAAQ,MAAM;CACrD;CACA,OAAO;AACT;AAEA,SAAS,WAAW,QAAsB,KAAmB;CAC3D,IAAI;EACF,OAAO,KAAK,GAAG;CACjB,QAAQ,CAER;AACF;AAEA,SAAS,qBAAqB,QAA4B;CAIxD,IAAI,CAHQ,OACT,QAAQ,sEAAsE,CAAC,CAC/E,IACI,CAAC,EAAE,IAAI,SAAS,wCAAwC,GAC7D;CAEF,OAAO,KAAK,2BAA2B;CACvC,OAAO,KAAK;;;;;;;;;;;;;GAaX;CACD,OAAO,KAAK,iDAAiD;CAC7D,OAAO,KAAK,qBAAqB;CACjC,OAAO,KAAK,6CAA6C;CACzD,OAAO,KAAK,yEAAyE;CACrF,OAAO,KAAK,0BAA0B;AACxC;;AAGA,SAAS,cAAc,QAAsB,KAAmB;CAC9D,KAAK,MAAM,CAAC,OAAO,YAAY,aAAa,GAAG,GAAG;EAChD,MAAM,OAAO,OAAO,QAAQ,qBAAqB,MAAM,EAAE,CAAC,CAAC,IAAI;EAC/D,MAAM,WAAW,IAAI,IAAI,KAAK,KAAK,QAAQ,OAAO,IAAI,EAAE,CAAC,CAAC;EAC1D,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,CAAC,QAAQ,OAAO,MAAM,MAAM;GAClC,IAAI,QAAQ,CAAC,SAAS,IAAI,IAAI,GAC5B,WAAW,QAAQ,eAAe,MAAM,cAAc,QAAQ;EAElE;CACF;AACF;AAEA,SAAS,cAAc,QAA4B;CACjD,OAAO,KAAK,0BAA0B;CACtC,OAAO,KAAK,GAAG;CACf,cAAc,QAAQ,GAAG;CACzB,IAAI;EACF,OAAO,KAAK,mBAAmB;CACjC,QAAQ,CAER;CACA,yBAAyB,MAAM;CAC/B,IAAI;EACF,qBAAqB,MAAM;CAC7B,QAAQ;EACN,WAAW,QAAQ,0BAA0B;CAC/C;AACF;;AAGA,SAAS,yBAAyB,QAA4B;CAC5D,IAAI;EACF,OAAO,KAAK,mBAAmB;EAC/B,OAAO,KAAK,mBAAmB;CACjC,QAAQ,CAER;AACF"}
@@ -0,0 +1,204 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ //#region src/schema/baseline.d.ts
4
+ /** Narrow `baselines` table definition. */
5
+ declare const baselines: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
6
+ name: "baselines";
7
+ schema: undefined;
8
+ columns: {
9
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
10
+ name: "id";
11
+ tableName: "baselines";
12
+ dataType: "string";
13
+ columnType: "SQLiteText";
14
+ data: string;
15
+ driverParam: string;
16
+ notNull: true;
17
+ hasDefault: false;
18
+ isPrimaryKey: true;
19
+ isAutoincrement: false;
20
+ hasRuntimeDefault: false;
21
+ enumValues: [string, ...string[]];
22
+ baseColumn: never;
23
+ identity: undefined;
24
+ generated: undefined;
25
+ }, {}, {
26
+ length: number | undefined;
27
+ }>;
28
+ projectId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
29
+ name: "project_id";
30
+ tableName: "baselines";
31
+ dataType: "string";
32
+ columnType: "SQLiteText";
33
+ data: string;
34
+ driverParam: string;
35
+ notNull: true;
36
+ hasDefault: false;
37
+ isPrimaryKey: false;
38
+ isAutoincrement: false;
39
+ hasRuntimeDefault: false;
40
+ enumValues: [string, ...string[]];
41
+ baseColumn: never;
42
+ identity: undefined;
43
+ generated: undefined;
44
+ }, {}, {
45
+ length: number | undefined;
46
+ }>;
47
+ storyId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
48
+ name: "story_id";
49
+ tableName: "baselines";
50
+ dataType: "string";
51
+ columnType: "SQLiteText";
52
+ data: string;
53
+ driverParam: string;
54
+ notNull: true;
55
+ hasDefault: false;
56
+ isPrimaryKey: false;
57
+ isAutoincrement: false;
58
+ hasRuntimeDefault: false;
59
+ enumValues: [string, ...string[]];
60
+ baseColumn: never;
61
+ identity: undefined;
62
+ generated: undefined;
63
+ }, {}, {
64
+ length: number | undefined;
65
+ }>;
66
+ viewportName: import("drizzle-orm/sqlite-core").SQLiteColumn<{
67
+ name: "viewport_name";
68
+ tableName: "baselines";
69
+ dataType: "string";
70
+ columnType: "SQLiteText";
71
+ data: string;
72
+ driverParam: string;
73
+ notNull: true;
74
+ hasDefault: true;
75
+ isPrimaryKey: false;
76
+ isAutoincrement: false;
77
+ hasRuntimeDefault: false;
78
+ enumValues: [string, ...string[]];
79
+ baseColumn: never;
80
+ identity: undefined;
81
+ generated: undefined;
82
+ }, {}, {
83
+ length: number | undefined;
84
+ }>;
85
+ branch: import("drizzle-orm/sqlite-core").SQLiteColumn<{
86
+ name: "branch";
87
+ tableName: "baselines";
88
+ dataType: "string";
89
+ columnType: "SQLiteText";
90
+ data: string;
91
+ driverParam: string;
92
+ notNull: true;
93
+ hasDefault: false;
94
+ isPrimaryKey: false;
95
+ isAutoincrement: false;
96
+ hasRuntimeDefault: false;
97
+ enumValues: [string, ...string[]];
98
+ baseColumn: never;
99
+ identity: undefined;
100
+ generated: undefined;
101
+ }, {}, {
102
+ length: number | undefined;
103
+ }>;
104
+ snapshotId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
105
+ name: "snapshot_id";
106
+ tableName: "baselines";
107
+ dataType: "string";
108
+ columnType: "SQLiteText";
109
+ data: string;
110
+ driverParam: string;
111
+ notNull: false;
112
+ hasDefault: false;
113
+ isPrimaryKey: false;
114
+ isAutoincrement: false;
115
+ hasRuntimeDefault: false;
116
+ enumValues: [string, ...string[]];
117
+ baseColumn: never;
118
+ identity: undefined;
119
+ generated: undefined;
120
+ }, {}, {
121
+ length: number | undefined;
122
+ }>;
123
+ screenshotPath: import("drizzle-orm/sqlite-core").SQLiteColumn<{
124
+ name: "screenshot_path";
125
+ tableName: "baselines";
126
+ dataType: "string";
127
+ columnType: "SQLiteText";
128
+ data: string;
129
+ driverParam: string;
130
+ notNull: true;
131
+ hasDefault: false;
132
+ isPrimaryKey: false;
133
+ isAutoincrement: false;
134
+ hasRuntimeDefault: false;
135
+ enumValues: [string, ...string[]];
136
+ baseColumn: never;
137
+ identity: undefined;
138
+ generated: undefined;
139
+ }, {}, {
140
+ length: number | undefined;
141
+ }>;
142
+ infraHash: import("drizzle-orm/sqlite-core").SQLiteColumn<{
143
+ name: "infra_hash";
144
+ tableName: "baselines";
145
+ dataType: "string";
146
+ columnType: "SQLiteText";
147
+ data: string;
148
+ driverParam: string;
149
+ notNull: false;
150
+ hasDefault: false;
151
+ isPrimaryKey: false;
152
+ isAutoincrement: false;
153
+ hasRuntimeDefault: false;
154
+ enumValues: [string, ...string[]];
155
+ baseColumn: never;
156
+ identity: undefined;
157
+ generated: undefined;
158
+ }, {}, {
159
+ length: number | undefined;
160
+ }>;
161
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
162
+ name: "created_at";
163
+ tableName: "baselines";
164
+ dataType: "string";
165
+ columnType: "SQLiteText";
166
+ data: string;
167
+ driverParam: string;
168
+ notNull: true;
169
+ hasDefault: false;
170
+ isPrimaryKey: false;
171
+ isAutoincrement: false;
172
+ hasRuntimeDefault: false;
173
+ enumValues: [string, ...string[]];
174
+ baseColumn: never;
175
+ identity: undefined;
176
+ generated: undefined;
177
+ }, {}, {
178
+ length: number | undefined;
179
+ }>;
180
+ updatedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
181
+ name: "updated_at";
182
+ tableName: "baselines";
183
+ dataType: "string";
184
+ columnType: "SQLiteText";
185
+ data: string;
186
+ driverParam: string;
187
+ notNull: true;
188
+ hasDefault: false;
189
+ isPrimaryKey: false;
190
+ isAutoincrement: false;
191
+ hasRuntimeDefault: false;
192
+ enumValues: [string, ...string[]];
193
+ baseColumn: never;
194
+ identity: undefined;
195
+ generated: undefined;
196
+ }, {}, {
197
+ length: number | undefined;
198
+ }>;
199
+ };
200
+ dialect: 'sqlite';
201
+ }>;
202
+ //#endregion
203
+ export { baselines };
204
+ //# sourceMappingURL=baseline.d.mts.map
@@ -0,0 +1,23 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { projects } from "./project.mjs";
5
+ import { index, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
6
+ //#region src/schema/baseline.ts
7
+ /** Narrow `baselines` table definition. */
8
+ const baselines = sqliteTable("baselines", {
9
+ id: text("id").primaryKey(),
10
+ projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
11
+ storyId: text("story_id").notNull(),
12
+ viewportName: text("viewport_name").notNull().default("desktop"),
13
+ branch: text("branch").notNull(),
14
+ snapshotId: text("snapshot_id"),
15
+ screenshotPath: text("screenshot_path").notNull(),
16
+ infraHash: text("infra_hash"),
17
+ createdAt: text("created_at").notNull(),
18
+ updatedAt: text("updated_at").notNull()
19
+ }, (t) => [uniqueIndex("baselines_project_story_viewport_branch_idx").on(t.projectId, t.storyId, t.viewportName, t.branch), index("baselines_project_story_idx").on(t.projectId, t.storyId)]);
20
+ //#endregion
21
+ export { baselines };
22
+
23
+ //# sourceMappingURL=baseline.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.mjs","names":[],"sources":["../../src/schema/baseline.ts"],"sourcesContent":["import { index, sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\nimport { projects } from \"./project.ts\";\n\n/** Narrow `baselines` table definition. */\nexport const baselines = sqliteTable(\n \"baselines\",\n {\n id: text(\"id\").primaryKey(),\n projectId: text(\"project_id\")\n .notNull()\n .references(() => projects.id, { onDelete: \"cascade\" }),\n storyId: text(\"story_id\").notNull(),\n viewportName: text(\"viewport_name\").notNull().default(\"desktop\"),\n branch: text(\"branch\").notNull(),\n snapshotId: text(\"snapshot_id\"),\n screenshotPath: text(\"screenshot_path\").notNull(),\n infraHash: text(\"infra_hash\"),\n createdAt: text(\"created_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (t) => [\n uniqueIndex(\"baselines_project_story_viewport_branch_idx\").on(\n t.projectId,\n t.storyId,\n t.viewportName,\n t.branch,\n ),\n index(\"baselines_project_story_idx\").on(t.projectId, t.storyId),\n ],\n);\n\n/** A baseline row. */\nexport interface Baseline {\n id: string;\n projectId: string;\n storyId: string;\n viewportName: string;\n branch: string;\n snapshotId: string | null;\n screenshotPath: string;\n infraHash: string | null;\n createdAt: string;\n updatedAt: string;\n}\n"],"mappings":";;;;;;;AAIA,MAAa,YAAY,YACvB,aACA;CACE,IAAI,KAAK,IAAI,CAAC,CAAC,WAAW;CAC1B,WAAW,KAAK,YAAY,CAAC,CAC1B,QAAQ,CAAC,CACT,iBAAiB,SAAS,IAAI,EAAE,UAAU,UAAU,CAAC;CACxD,SAAS,KAAK,UAAU,CAAC,CAAC,QAAQ;CAClC,cAAc,KAAK,eAAe,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,SAAS;CAC/D,QAAQ,KAAK,QAAQ,CAAC,CAAC,QAAQ;CAC/B,YAAY,KAAK,aAAa;CAC9B,gBAAgB,KAAK,iBAAiB,CAAC,CAAC,QAAQ;CAChD,WAAW,KAAK,YAAY;CAC5B,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;CACtC,WAAW,KAAK,YAAY,CAAC,CAAC,QAAQ;AACxC,IACC,MAAM,CACL,YAAY,6CAA6C,CAAC,CAAC,GACzD,EAAE,WACF,EAAE,SACF,EAAE,cACF,EAAE,MACJ,GACA,MAAM,6BAA6B,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,OAAO,CAChE,CACF"}