@storyshelf/core 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.
Files changed (189) hide show
  1. package/README.md +127 -0
  2. package/dist/adapter/auth.d.mts +50 -0
  3. package/dist/adapter/auth.mjs +10 -0
  4. package/dist/adapter/auth.mjs.map +1 -0
  5. package/dist/adapter/capture-runner.d.mts +70 -0
  6. package/dist/adapter/capture-runner.mjs +4 -0
  7. package/dist/adapter/database.d.mts +43 -0
  8. package/dist/adapter/database.mjs +4 -0
  9. package/dist/adapter/git-host/helpers.d.mts +12 -0
  10. package/dist/adapter/git-host/helpers.mjs +29 -0
  11. package/dist/adapter/git-host/helpers.mjs.map +1 -0
  12. package/dist/adapter/git-host.d.mts +46 -0
  13. package/dist/adapter/git-host.mjs +4 -0
  14. package/dist/adapter/storage.d.mts +22 -0
  15. package/dist/adapter/storage.mjs +4 -0
  16. package/dist/adapters/capture-queue.d.mts +49 -0
  17. package/dist/adapters/metadata.d.mts +24 -0
  18. package/dist/adapters/webhook-events.mjs +39 -0
  19. package/dist/adapters/webhook-events.mjs.map +1 -0
  20. package/dist/capture/adapter.d.mts +29 -0
  21. package/dist/capture/adapter.mjs +13 -0
  22. package/dist/capture/adapter.mjs.map +1 -0
  23. package/dist/capture/orchestrator.d.mts +23 -0
  24. package/dist/capture/orchestrator.mjs +103 -0
  25. package/dist/capture/orchestrator.mjs.map +1 -0
  26. package/dist/capture/pipeline.d.mts +41 -0
  27. package/dist/capture/pipeline.mjs +119 -0
  28. package/dist/capture/pipeline.mjs.map +1 -0
  29. package/dist/capture/queue.d.mts +35 -0
  30. package/dist/capture/queue.mjs +96 -0
  31. package/dist/capture/queue.mjs.map +1 -0
  32. package/dist/capture/status-fanout.mjs +66 -0
  33. package/dist/capture/status-fanout.mjs.map +1 -0
  34. package/dist/capture/storybook.d.mts +14 -0
  35. package/dist/capture/storybook.mjs +40 -0
  36. package/dist/capture/storybook.mjs.map +1 -0
  37. package/dist/config.d.mts +68 -0
  38. package/dist/config.mjs +69 -0
  39. package/dist/config.mjs.map +1 -0
  40. package/dist/ddl.d.mts +7 -0
  41. package/dist/ddl.mjs +155 -0
  42. package/dist/ddl.mjs.map +1 -0
  43. package/dist/diff/engine.d.mts +8 -0
  44. package/dist/diff/engine.mjs +79 -0
  45. package/dist/diff/engine.mjs.map +1 -0
  46. package/dist/diff/options.d.mts +40 -0
  47. package/dist/diff/options.mjs +15 -0
  48. package/dist/diff/options.mjs.map +1 -0
  49. package/dist/index.d.mts +48 -0
  50. package/dist/index.mjs +327 -0
  51. package/dist/index.mjs.map +1 -0
  52. package/dist/logger.d.mts +39 -0
  53. package/dist/logger.mjs +35 -0
  54. package/dist/logger.mjs.map +1 -0
  55. package/dist/middleware/csrf.mjs +39 -0
  56. package/dist/middleware/csrf.mjs.map +1 -0
  57. package/dist/middleware/rate-limit.mjs +38 -0
  58. package/dist/middleware/rate-limit.mjs.map +1 -0
  59. package/dist/models/baseline.d.mts +20 -0
  60. package/dist/models/baseline.mjs +89 -0
  61. package/dist/models/baseline.mjs.map +1 -0
  62. package/dist/models/build.d.mts +35 -0
  63. package/dist/models/build.mjs +77 -0
  64. package/dist/models/build.mjs.map +1 -0
  65. package/dist/models/comment.d.mts +20 -0
  66. package/dist/models/comment.mjs +42 -0
  67. package/dist/models/comment.mjs.map +1 -0
  68. package/dist/models/label.d.mts +43 -0
  69. package/dist/models/label.mjs +107 -0
  70. package/dist/models/label.mjs.map +1 -0
  71. package/dist/models/member.d.mts +27 -0
  72. package/dist/models/member.mjs +54 -0
  73. package/dist/models/member.mjs.map +1 -0
  74. package/dist/models/project.d.mts +42 -0
  75. package/dist/models/project.mjs +78 -0
  76. package/dist/models/project.mjs.map +1 -0
  77. package/dist/models/snapshot.d.mts +46 -0
  78. package/dist/models/snapshot.mjs +75 -0
  79. package/dist/models/snapshot.mjs.map +1 -0
  80. package/dist/models/status-config.mjs +61 -0
  81. package/dist/models/status-config.mjs.map +1 -0
  82. package/dist/models/token.d.mts +33 -0
  83. package/dist/models/token.mjs +60 -0
  84. package/dist/models/token.mjs.map +1 -0
  85. package/dist/models/webhook.d.mts +37 -0
  86. package/dist/models/webhook.mjs +67 -0
  87. package/dist/models/webhook.mjs.map +1 -0
  88. package/dist/pages/build-detail.mjs +333 -0
  89. package/dist/pages/build-detail.mjs.map +1 -0
  90. package/dist/pages/build-diff.mjs +437 -0
  91. package/dist/pages/build-diff.mjs.map +1 -0
  92. package/dist/pages/compute-jobs.mjs +196 -0
  93. package/dist/pages/compute-jobs.mjs.map +1 -0
  94. package/dist/pages/login.mjs +77 -0
  95. package/dist/pages/login.mjs.map +1 -0
  96. package/dist/pages/project-builds.mjs +228 -0
  97. package/dist/pages/project-builds.mjs.map +1 -0
  98. package/dist/pages/project-create.mjs +168 -0
  99. package/dist/pages/project-create.mjs.map +1 -0
  100. package/dist/pages/project-settings.mjs +111 -0
  101. package/dist/pages/project-settings.mjs.map +1 -0
  102. package/dist/pages/projects.mjs +178 -0
  103. package/dist/pages/projects.mjs.map +1 -0
  104. package/dist/pages/root.mjs +90 -0
  105. package/dist/pages/root.mjs.map +1 -0
  106. package/dist/pages/settings-general.mjs +191 -0
  107. package/dist/pages/settings-general.mjs.map +1 -0
  108. package/dist/pages/settings-labels.mjs +149 -0
  109. package/dist/pages/settings-labels.mjs.map +1 -0
  110. package/dist/pages/settings-members.mjs +124 -0
  111. package/dist/pages/settings-members.mjs.map +1 -0
  112. package/dist/pages/settings-status.mjs +150 -0
  113. package/dist/pages/settings-status.mjs.map +1 -0
  114. package/dist/pages/settings-tokens.mjs +95 -0
  115. package/dist/pages/settings-tokens.mjs.map +1 -0
  116. package/dist/pages/settings-webhooks.mjs +149 -0
  117. package/dist/pages/settings-webhooks.mjs.map +1 -0
  118. package/dist/retention/purge.d.mts +28 -0
  119. package/dist/retention/purge.mjs +66 -0
  120. package/dist/retention/purge.mjs.map +1 -0
  121. package/dist/routers/admin.mjs +41 -0
  122. package/dist/routers/admin.mjs.map +1 -0
  123. package/dist/routers/assets.mjs +24 -0
  124. package/dist/routers/assets.mjs.map +1 -0
  125. package/dist/routers/auth.mjs +77 -0
  126. package/dist/routers/auth.mjs.map +1 -0
  127. package/dist/routers/builds.handlers.mjs +87 -0
  128. package/dist/routers/builds.handlers.mjs.map +1 -0
  129. package/dist/routers/builds.mjs +162 -0
  130. package/dist/routers/builds.mjs.map +1 -0
  131. package/dist/routers/comments.mjs +89 -0
  132. package/dist/routers/comments.mjs.map +1 -0
  133. package/dist/routers/helpers.mjs +76 -0
  134. package/dist/routers/helpers.mjs.map +1 -0
  135. package/dist/routers/htmx.mjs +18 -0
  136. package/dist/routers/htmx.mjs.map +1 -0
  137. package/dist/routers/labels.mjs +77 -0
  138. package/dist/routers/labels.mjs.map +1 -0
  139. package/dist/routers/media.mjs +54 -0
  140. package/dist/routers/media.mjs.map +1 -0
  141. package/dist/routers/members.mjs +101 -0
  142. package/dist/routers/members.mjs.map +1 -0
  143. package/dist/routers/projects.mjs +121 -0
  144. package/dist/routers/projects.mjs.map +1 -0
  145. package/dist/routers/schemas.mjs +188 -0
  146. package/dist/routers/schemas.mjs.map +1 -0
  147. package/dist/routers/settings.mjs +235 -0
  148. package/dist/routers/settings.mjs.map +1 -0
  149. package/dist/routers/snapshots.mjs +143 -0
  150. package/dist/routers/snapshots.mjs.map +1 -0
  151. package/dist/routers/status-configs.mjs +100 -0
  152. package/dist/routers/status-configs.mjs.map +1 -0
  153. package/dist/routers/tokens.mjs +80 -0
  154. package/dist/routers/tokens.mjs.map +1 -0
  155. package/dist/routers/ui.mjs +142 -0
  156. package/dist/routers/ui.mjs.map +1 -0
  157. package/dist/routers/webhooks.mjs +89 -0
  158. package/dist/routers/webhooks.mjs.map +1 -0
  159. package/dist/schema.d.mts +4372 -0
  160. package/dist/schema.mjs +158 -0
  161. package/dist/schema.mjs.map +1 -0
  162. package/dist/store.mjs +18 -0
  163. package/dist/store.mjs.map +1 -0
  164. package/dist/types.d.mts +18 -0
  165. package/dist/types.mjs +49 -0
  166. package/dist/types.mjs.map +1 -0
  167. package/dist/ui/components.mjs +127 -0
  168. package/dist/ui/components.mjs.map +1 -0
  169. package/dist/ui/document.d.mts +8 -0
  170. package/dist/ui/document.mjs +338 -0
  171. package/dist/ui/document.mjs.map +1 -0
  172. package/dist/ui/styles.mjs +173 -0
  173. package/dist/ui/styles.mjs.map +1 -0
  174. package/dist/ui/theme.mjs +42 -0
  175. package/dist/ui/theme.mjs.map +1 -0
  176. package/dist/urls.d.mts +19 -0
  177. package/dist/urls.mjs +23 -0
  178. package/dist/urls.mjs.map +1 -0
  179. package/dist/utils/encrypt.mjs +49 -0
  180. package/dist/utils/encrypt.mjs.map +1 -0
  181. package/dist/utils/hash.mjs +22 -0
  182. package/dist/utils/hash.mjs.map +1 -0
  183. package/dist/utils/paths.d.mts +11 -0
  184. package/dist/utils/paths.mjs +23 -0
  185. package/dist/utils/paths.mjs.map +1 -0
  186. package/dist/utils/ulid.d.mts +8 -0
  187. package/dist/utils/ulid.mjs +41 -0
  188. package/dist/utils/ulid.mjs.map +1 -0
  189. package/package.json +162 -0
@@ -0,0 +1,78 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { projects } from "../schema.mjs";
5
+ import { slugify, ulid } from "../utils/ulid.mjs";
6
+ import { eq } from "drizzle-orm";
7
+ //#region src/models/project.ts
8
+ /** Data operations for project records. */
9
+ var ProjectModel = class {
10
+ db;
11
+ /**
12
+ * @param db - Database adapter.
13
+ */
14
+ constructor(db) {
15
+ this.db = db;
16
+ }
17
+ /**
18
+ * Create a project with a unique slug.
19
+ *
20
+ * @param input - Project creation input.
21
+ * @returns The created project.
22
+ */
23
+ async create(input) {
24
+ const now = (/* @__PURE__ */ new Date()).toISOString();
25
+ const slug = await this.uniqueSlug(input.name);
26
+ return this.db.insert(projects, {
27
+ id: ulid(),
28
+ name: input.name,
29
+ slug,
30
+ gitRepository: input.gitRepository,
31
+ gitDefaultBranch: input.gitDefaultBranch ?? "main",
32
+ storybookMeta: input.storybookMeta ? JSON.stringify(input.storybookMeta) : null,
33
+ createdAt: now,
34
+ updatedAt: now
35
+ });
36
+ }
37
+ /** Fetch a project by id, or null if not found. */
38
+ async get(id) {
39
+ return await this.db.get(projects, id);
40
+ }
41
+ /** Fetch a project by its slug, or null if not found. */
42
+ async getBySlug(slug) {
43
+ return (await this.db.list(projects, {
44
+ where: eq(projects.slug, slug),
45
+ limit: 1
46
+ }))[0] ?? null;
47
+ }
48
+ /** List all projects. */
49
+ async list() {
50
+ return await this.db.list(projects);
51
+ }
52
+ /** Update mutable fields of a project. */
53
+ async update(id, patch) {
54
+ const normalized = { ...patch };
55
+ if (patch.storybookMeta !== void 0 && patch.storybookMeta !== null && typeof patch.storybookMeta !== "string") normalized["storybookMeta"] = JSON.stringify(patch.storybookMeta);
56
+ return await this.db.update(projects, id, {
57
+ ...normalized,
58
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
59
+ });
60
+ }
61
+ /** Delete a project by id. */
62
+ async remove(id) {
63
+ await this.db.remove(projects, id);
64
+ }
65
+ async uniqueSlug(name) {
66
+ const base = slugify(name) || "project";
67
+ const existing = await this.db.list(projects, { where: eq(projects.slug, base) });
68
+ if (existing.length === 0) return base;
69
+ const taken = new Set(existing.map((p) => p.slug));
70
+ let suffix = 2;
71
+ while (taken.has(`${base}-${suffix}`)) suffix += 1;
72
+ return `${base}-${suffix}`;
73
+ }
74
+ };
75
+ //#endregion
76
+ export { ProjectModel };
77
+
78
+ //# sourceMappingURL=project.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.mjs","names":[],"sources":["../../src/models/project.ts"],"sourcesContent":["import { eq } from \"drizzle-orm\";\n\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { projects, type Project } from \"../schema.ts\";\nimport { slugify, ulid } from \"../utils/ulid.ts\";\n\nexport interface ProjectCreateInput {\n name: string;\n gitRepository?: string;\n gitDefaultBranch?: string;\n storybookMeta?: unknown;\n}\n\n/** Data operations for project records. */\nexport class ProjectModel {\n /**\n * @param db - Database adapter.\n */\n constructor(private readonly db: DatabaseAdapter) {}\n\n /**\n * Create a project with a unique slug.\n *\n * @param input - Project creation input.\n * @returns The created project.\n */\n async create(input: ProjectCreateInput): Promise<Project> {\n const now = new Date().toISOString();\n const slug = await this.uniqueSlug(input.name);\n return this.db.insert(projects, {\n id: ulid(),\n name: input.name,\n slug,\n gitRepository: input.gitRepository,\n gitDefaultBranch: input.gitDefaultBranch ?? \"main\",\n storybookMeta: input.storybookMeta ? JSON.stringify(input.storybookMeta) : null,\n createdAt: now,\n updatedAt: now,\n });\n }\n\n /** Fetch a project by id, or null if not found. */\n async get(id: string): Promise<Project | null> {\n return await this.db.get(projects, id);\n }\n\n /** Fetch a project by its slug, or null if not found. */\n async getBySlug(slug: string): Promise<Project | null> {\n const rows = await this.db.list(projects, { where: eq(projects.slug, slug), limit: 1 });\n return rows[0] ?? null;\n }\n\n /** List all projects. */\n async list(): Promise<Project[]> {\n return await this.db.list(projects);\n }\n\n /** Update mutable fields of a project. */\n async update(\n id: string,\n patch: Partial<Omit<Project, \"storybookMeta\">> & {\n storybookMeta?: unknown;\n },\n ): Promise<Project> {\n const normalized: Record<string, unknown> = { ...patch };\n if (patch.storybookMeta !== undefined && patch.storybookMeta !== null && typeof patch.storybookMeta !== \"string\") {\n normalized[\"storybookMeta\"] = JSON.stringify(patch.storybookMeta);\n }\n // oxlint-disable-next-line typescript/no-unnecessary-type-assertion\n return await this.db.update(projects, id, {\n ...normalized,\n updatedAt: new Date().toISOString(),\n } as unknown as Partial<Project>);\n }\n\n /** Delete a project by id. */\n async remove(id: string): Promise<void> {\n await this.db.remove(projects, id);\n }\n\n private async uniqueSlug(name: string): Promise<string> {\n const base = slugify(name) || \"project\";\n const existing = await this.db.list(projects, { where: eq(projects.slug, base) });\n if (existing.length === 0) {\n return base;\n }\n const taken = new Set(existing.map((p) => p.slug));\n let suffix = 2;\n while (taken.has(`${base}-${suffix}`)) {\n suffix += 1;\n }\n return `${base}-${suffix}`;\n }\n}\n\nexport type { Project };\n"],"mappings":";;;;;;;;AAcA,IAAa,eAAb,MAA0B;CAIK;;;;CAA7B,YAAY,IAAsC;EAArB,KAAA,KAAA;CAAsB;;;;;;;CAQnD,MAAM,OAAO,OAA6C;EACxD,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,OAAO,MAAM,KAAK,WAAW,MAAM,IAAI;EAC7C,OAAO,KAAK,GAAG,OAAO,UAAU;GAC9B,IAAI,KAAK;GACT,MAAM,MAAM;GACZ;GACA,eAAe,MAAM;GACrB,kBAAkB,MAAM,oBAAoB;GAC5C,eAAe,MAAM,gBAAgB,KAAK,UAAU,MAAM,aAAa,IAAI;GAC3E,WAAW;GACX,WAAW;EACb,CAAC;CACH;;CAGA,MAAM,IAAI,IAAqC;EAC7C,OAAO,MAAM,KAAK,GAAG,IAAI,UAAU,EAAE;CACvC;;CAGA,MAAM,UAAU,MAAuC;EAErD,QAAO,MADY,KAAK,GAAG,KAAK,UAAU;GAAE,OAAO,GAAG,SAAS,MAAM,IAAI;GAAG,OAAO;EAAE,CAAC,EAAA,CAC1E,MAAM;CACpB;;CAGA,MAAM,OAA2B;EAC/B,OAAO,MAAM,KAAK,GAAG,KAAK,QAAQ;CACpC;;CAGA,MAAM,OACJ,IACA,OAGkB;EAClB,MAAM,aAAsC,EAAE,GAAG,MAAM;EACvD,IAAI,MAAM,kBAAkB,KAAA,KAAa,MAAM,kBAAkB,QAAQ,OAAO,MAAM,kBAAkB,UACtG,WAAW,mBAAmB,KAAK,UAAU,MAAM,aAAa;EAGlE,OAAO,MAAM,KAAK,GAAG,OAAO,UAAU,IAAI;GACxC,GAAG;GACH,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAgC;CAClC;;CAGA,MAAM,OAAO,IAA2B;EACtC,MAAM,KAAK,GAAG,OAAO,UAAU,EAAE;CACnC;CAEA,MAAc,WAAW,MAA+B;EACtD,MAAM,OAAO,QAAQ,IAAI,KAAK;EAC9B,MAAM,WAAW,MAAM,KAAK,GAAG,KAAK,UAAU,EAAE,OAAO,GAAG,SAAS,MAAM,IAAI,EAAE,CAAC;EAChF,IAAI,SAAS,WAAW,GACtB,OAAO;EAET,MAAM,QAAQ,IAAI,IAAI,SAAS,KAAK,MAAM,EAAE,IAAI,CAAC;EACjD,IAAI,SAAS;EACb,OAAO,MAAM,IAAI,GAAG,KAAK,GAAG,QAAQ,GAClC,UAAU;EAEZ,OAAO,GAAG,KAAK,GAAG;CACpB;AACF"}
@@ -0,0 +1,46 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { SnapshotStatus } from "../types.mjs";
4
+ import { DatabaseAdapter } from "../adapter/database.mjs";
5
+ import { Snapshot } from "../schema.mjs";
6
+ //#region src/models/snapshot.d.ts
7
+ interface SnapshotCreateInput {
8
+ storyId: string;
9
+ storyName: string;
10
+ storyTitle: string;
11
+ storyImportPath?: string;
12
+ viewportName: string;
13
+ viewportWidth: number;
14
+ viewportHeight: number;
15
+ screenshotPath: string;
16
+ }
17
+ /** Data operations for snapshot records. */
18
+ declare class SnapshotModel {
19
+ private readonly db;
20
+ /**
21
+ * @param db - Database adapter.
22
+ */
23
+ constructor(db: DatabaseAdapter);
24
+ /**
25
+ * Create a snapshot for a story within a build.
26
+ *
27
+ * @param projectId - Project ID.
28
+ * @param buildId - Build ID.
29
+ * @param input - Snapshot creation input.
30
+ * @returns The created snapshot.
31
+ */
32
+ create(projectId: string, buildId: string, input: SnapshotCreateInput): Promise<Snapshot>;
33
+ /** List all snapshots belonging to a build. */
34
+ listByBuild(buildId: string): Promise<Snapshot[]>;
35
+ /** Fetch a snapshot by id, or null if not found. */
36
+ get(id: string): Promise<Snapshot | null>;
37
+ /** Update mutable fields of a snapshot. */
38
+ update(id: string, patch: Partial<Snapshot>): Promise<Snapshot>;
39
+ /** Set the status of a snapshot. */
40
+ setStatus(id: string, status: SnapshotStatus): Promise<Snapshot>;
41
+ /** Record a reviewer's decision on a snapshot. */
42
+ review(id: string, status: SnapshotStatus, userId: string): Promise<Snapshot>;
43
+ }
44
+ //#endregion
45
+ export { type Snapshot, SnapshotCreateInput, SnapshotModel };
46
+ //# sourceMappingURL=snapshot.d.mts.map
@@ -0,0 +1,75 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { snapshots } from "../schema.mjs";
5
+ import { ulid } from "../utils/ulid.mjs";
6
+ import { eq } from "drizzle-orm";
7
+ //#region src/models/snapshot.ts
8
+ /** Data operations for snapshot records. */
9
+ var SnapshotModel = class {
10
+ db;
11
+ /**
12
+ * @param db - Database adapter.
13
+ */
14
+ constructor(db) {
15
+ this.db = db;
16
+ }
17
+ /**
18
+ * Create a snapshot for a story within a build.
19
+ *
20
+ * @param projectId - Project ID.
21
+ * @param buildId - Build ID.
22
+ * @param input - Snapshot creation input.
23
+ * @returns The created snapshot.
24
+ */
25
+ async create(projectId, buildId, input) {
26
+ const now = (/* @__PURE__ */ new Date()).toISOString();
27
+ return await this.db.insert(snapshots, {
28
+ id: ulid(),
29
+ projectId,
30
+ buildId,
31
+ storyId: input.storyId,
32
+ storyName: input.storyName,
33
+ storyTitle: input.storyTitle,
34
+ storyImportPath: input.storyImportPath,
35
+ viewportName: input.viewportName,
36
+ viewportWidth: input.viewportWidth,
37
+ viewportHeight: input.viewportHeight,
38
+ screenshotPath: input.screenshotPath,
39
+ status: "pending",
40
+ createdAt: now,
41
+ updatedAt: now
42
+ });
43
+ }
44
+ /** List all snapshots belonging to a build. */
45
+ async listByBuild(buildId) {
46
+ return await this.db.list(snapshots, { where: eq(snapshots.buildId, buildId) });
47
+ }
48
+ /** Fetch a snapshot by id, or null if not found. */
49
+ async get(id) {
50
+ return await this.db.get(snapshots, id);
51
+ }
52
+ /** Update mutable fields of a snapshot. */
53
+ async update(id, patch) {
54
+ return await this.db.update(snapshots, id, {
55
+ ...patch,
56
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
57
+ });
58
+ }
59
+ /** Set the status of a snapshot. */
60
+ async setStatus(id, status) {
61
+ return await this.update(id, { status });
62
+ }
63
+ /** Record a reviewer's decision on a snapshot. */
64
+ async review(id, status, userId) {
65
+ return await this.update(id, {
66
+ status,
67
+ reviewedBy: userId,
68
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString()
69
+ });
70
+ }
71
+ };
72
+ //#endregion
73
+ export { SnapshotModel };
74
+
75
+ //# sourceMappingURL=snapshot.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.mjs","names":[],"sources":["../../src/models/snapshot.ts"],"sourcesContent":["import { eq } from \"drizzle-orm\";\n\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { snapshots, type Snapshot } from \"../schema.ts\";\nimport type { SnapshotStatus } from \"../types.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\nexport interface SnapshotCreateInput {\n storyId: string;\n storyName: string;\n storyTitle: string;\n storyImportPath?: string;\n viewportName: string;\n viewportWidth: number;\n viewportHeight: number;\n screenshotPath: string;\n}\n\n/** Data operations for snapshot records. */\nexport class SnapshotModel {\n /**\n * @param db - Database adapter.\n */\n constructor(private readonly db: DatabaseAdapter) {}\n\n /**\n * Create a snapshot for a story within a build.\n *\n * @param projectId - Project ID.\n * @param buildId - Build ID.\n * @param input - Snapshot creation input.\n * @returns The created snapshot.\n */\n async create(projectId: string, buildId: string, input: SnapshotCreateInput): Promise<Snapshot> {\n const now = new Date().toISOString();\n return await this.db.insert(snapshots, {\n id: ulid(),\n projectId,\n buildId,\n storyId: input.storyId,\n storyName: input.storyName,\n storyTitle: input.storyTitle,\n storyImportPath: input.storyImportPath,\n viewportName: input.viewportName,\n viewportWidth: input.viewportWidth,\n viewportHeight: input.viewportHeight,\n screenshotPath: input.screenshotPath,\n status: \"pending\",\n createdAt: now,\n updatedAt: now,\n });\n }\n\n /** List all snapshots belonging to a build. */\n async listByBuild(buildId: string): Promise<Snapshot[]> {\n return await this.db.list(snapshots, { where: eq(snapshots.buildId, buildId) });\n }\n\n /** Fetch a snapshot by id, or null if not found. */\n async get(id: string): Promise<Snapshot | null> {\n return await this.db.get(snapshots, id);\n }\n\n /** Update mutable fields of a snapshot. */\n async update(id: string, patch: Partial<Snapshot>): Promise<Snapshot> {\n return await this.db.update(snapshots, id, { ...patch, updatedAt: new Date().toISOString() });\n }\n\n /** Set the status of a snapshot. */\n async setStatus(id: string, status: SnapshotStatus): Promise<Snapshot> {\n return await this.update(id, { status });\n }\n\n /** Record a reviewer's decision on a snapshot. */\n async review(id: string, status: SnapshotStatus, userId: string): Promise<Snapshot> {\n return await this.update(id, { status, reviewedBy: userId, reviewedAt: new Date().toISOString() });\n }\n}\n\nexport type { Snapshot };\n"],"mappings":";;;;;;;;AAmBA,IAAa,gBAAb,MAA2B;CAII;;;;CAA7B,YAAY,IAAsC;EAArB,KAAA,KAAA;CAAsB;;;;;;;;;CAUnD,MAAM,OAAO,WAAmB,SAAiB,OAA+C;EAC9F,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAO,MAAM,KAAK,GAAG,OAAO,WAAW;GACrC,IAAI,KAAK;GACT;GACA;GACA,SAAS,MAAM;GACf,WAAW,MAAM;GACjB,YAAY,MAAM;GAClB,iBAAiB,MAAM;GACvB,cAAc,MAAM;GACpB,eAAe,MAAM;GACrB,gBAAgB,MAAM;GACtB,gBAAgB,MAAM;GACtB,QAAQ;GACR,WAAW;GACX,WAAW;EACb,CAAC;CACH;;CAGA,MAAM,YAAY,SAAsC;EACtD,OAAO,MAAM,KAAK,GAAG,KAAK,WAAW,EAAE,OAAO,GAAG,UAAU,SAAS,OAAO,EAAE,CAAC;CAChF;;CAGA,MAAM,IAAI,IAAsC;EAC9C,OAAO,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE;CACxC;;CAGA,MAAM,OAAO,IAAY,OAA6C;EACpE,OAAO,MAAM,KAAK,GAAG,OAAO,WAAW,IAAI;GAAE,GAAG;GAAO,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EAAE,CAAC;CAC9F;;CAGA,MAAM,UAAU,IAAY,QAA2C;EACrE,OAAO,MAAM,KAAK,OAAO,IAAI,EAAE,OAAO,CAAC;CACzC;;CAGA,MAAM,OAAO,IAAY,QAAwB,QAAmC;EAClF,OAAO,MAAM,KAAK,OAAO,IAAI;GAAE;GAAQ,YAAY;GAAQ,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;EAAE,CAAC;CACnG;AACF"}
@@ -0,0 +1,61 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { projectStatusConfigs } from "../schema.mjs";
5
+ import { ulid } from "../utils/ulid.mjs";
6
+ import { decrypt, encrypt } from "../utils/encrypt.mjs";
7
+ import { eq } from "drizzle-orm";
8
+ //#region src/models/status-config.ts
9
+ /** Data operations for per-project status provider configs. */
10
+ var StatusConfigModel = class {
11
+ db;
12
+ secret;
13
+ constructor(db, secret) {
14
+ this.db = db;
15
+ this.secret = secret;
16
+ }
17
+ /** List all status configs for a project. */
18
+ async list(projectId) {
19
+ return await this.db.list(projectStatusConfigs, { where: eq(projectStatusConfigs.projectId, projectId) });
20
+ }
21
+ /** Fetch a config by id scoped to a project, or null. */
22
+ async get(projectId, id) {
23
+ const found = (await this.db.list(projectStatusConfigs, {
24
+ where: eq(projectStatusConfigs.id, id),
25
+ limit: 1
26
+ }))[0] ?? null;
27
+ return found?.projectId === projectId ? found : null;
28
+ }
29
+ /** Decrypt the token for a stored row. */
30
+ decryptToken(row) {
31
+ return decrypt(this.secret, row.tokenEncrypted);
32
+ }
33
+ /** Parse the JSON config column. */
34
+ static parseConfig(row) {
35
+ return JSON.parse(row.config);
36
+ }
37
+ async create(projectId, input) {
38
+ const now = (/* @__PURE__ */ new Date()).toISOString();
39
+ return await this.db.insert(projectStatusConfigs, {
40
+ id: ulid(),
41
+ projectId,
42
+ provider: input.provider,
43
+ config: JSON.stringify(input.config),
44
+ tokenEncrypted: encrypt(this.secret, input.token),
45
+ createdAt: now,
46
+ updatedAt: now
47
+ });
48
+ }
49
+ async remove(projectId, id) {
50
+ const existing = await this.get(projectId, id);
51
+ if (existing) await this.db.remove(projectStatusConfigs, existing.id);
52
+ }
53
+ async removeByProject(projectId) {
54
+ const rows = await this.list(projectId);
55
+ for (const row of rows) await this.db.remove(projectStatusConfigs, row.id);
56
+ }
57
+ };
58
+ //#endregion
59
+ export { StatusConfigModel };
60
+
61
+ //# sourceMappingURL=status-config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-config.mjs","names":[],"sources":["../../src/models/status-config.ts"],"sourcesContent":["/* oxlint-disable eslint/no-await-in-loop, typescript/promise-function-async, eslint/require-await */\nimport { eq } from \"drizzle-orm\";\n\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { projectStatusConfigs, type ProjectStatusConfig } from \"../schema.ts\";\nimport { decrypt, encrypt } from \"../utils/encrypt.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\nexport interface StatusConfigCreateInput {\n provider: string;\n config: unknown;\n token: string;\n}\n\n/** Data operations for per-project status provider configs. */\nexport class StatusConfigModel {\n constructor(\n private readonly db: DatabaseAdapter,\n private readonly secret: string | undefined,\n ) {}\n\n /** List all status configs for a project. */\n async list(projectId: string): Promise<ProjectStatusConfig[]> {\n return await this.db.list(projectStatusConfigs, { where: eq(projectStatusConfigs.projectId, projectId) });\n }\n\n /** Fetch a config by id scoped to a project, or null. */\n async get(projectId: string, id: string): Promise<ProjectStatusConfig | null> {\n const rows = await this.db.list(projectStatusConfigs, { where: eq(projectStatusConfigs.id, id), limit: 1 });\n const found = rows[0] ?? null;\n return found?.projectId === projectId ? found : null;\n }\n\n /** Decrypt the token for a stored row. */\n decryptToken(row: ProjectStatusConfig): string {\n return decrypt(this.secret, row.tokenEncrypted);\n }\n\n /** Parse the JSON config column. */\n static parseConfig(row: ProjectStatusConfig): unknown {\n return JSON.parse(row.config) as unknown;\n }\n\n async create(projectId: string, input: StatusConfigCreateInput): Promise<ProjectStatusConfig> {\n const now = new Date().toISOString();\n return await this.db.insert(projectStatusConfigs, {\n id: ulid(),\n projectId,\n provider: input.provider,\n config: JSON.stringify(input.config),\n tokenEncrypted: encrypt(this.secret, input.token),\n createdAt: now,\n updatedAt: now,\n });\n }\n\n async remove(projectId: string, id: string): Promise<void> {\n const existing = await this.get(projectId, id);\n if (existing) {\n await this.db.remove(projectStatusConfigs, existing.id);\n }\n }\n\n async removeByProject(projectId: string): Promise<void> {\n const rows = await this.list(projectId);\n for (const row of rows) {\n await this.db.remove(projectStatusConfigs, row.id);\n }\n }\n}\n\nexport type { ProjectStatusConfig };\n"],"mappings":";;;;;;;;;AAeA,IAAa,oBAAb,MAA+B;CAEV;CACA;CAFnB,YACE,IACA,QACA;EAFiB,KAAA,KAAA;EACA,KAAA,SAAA;CAChB;;CAGH,MAAM,KAAK,WAAmD;EAC5D,OAAO,MAAM,KAAK,GAAG,KAAK,sBAAsB,EAAE,OAAO,GAAG,qBAAqB,WAAW,SAAS,EAAE,CAAC;CAC1G;;CAGA,MAAM,IAAI,WAAmB,IAAiD;EAE5E,MAAM,SAAQ,MADK,KAAK,GAAG,KAAK,sBAAsB;GAAE,OAAO,GAAG,qBAAqB,IAAI,EAAE;GAAG,OAAO;EAAE,CAAC,EAAA,CACvF,MAAM;EACzB,OAAO,OAAO,cAAc,YAAY,QAAQ;CAClD;;CAGA,aAAa,KAAkC;EAC7C,OAAO,QAAQ,KAAK,QAAQ,IAAI,cAAc;CAChD;;CAGA,OAAO,YAAY,KAAmC;EACpD,OAAO,KAAK,MAAM,IAAI,MAAM;CAC9B;CAEA,MAAM,OAAO,WAAmB,OAA8D;EAC5F,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAO,MAAM,KAAK,GAAG,OAAO,sBAAsB;GAChD,IAAI,KAAK;GACT;GACA,UAAU,MAAM;GAChB,QAAQ,KAAK,UAAU,MAAM,MAAM;GACnC,gBAAgB,QAAQ,KAAK,QAAQ,MAAM,KAAK;GAChD,WAAW;GACX,WAAW;EACb,CAAC;CACH;CAEA,MAAM,OAAO,WAAmB,IAA2B;EACzD,MAAM,WAAW,MAAM,KAAK,IAAI,WAAW,EAAE;EAC7C,IAAI,UACF,MAAM,KAAK,GAAG,OAAO,sBAAsB,SAAS,EAAE;CAE1D;CAEA,MAAM,gBAAgB,WAAkC;EACtD,MAAM,OAAO,MAAM,KAAK,KAAK,SAAS;EACtC,KAAK,MAAM,OAAO,MAChB,MAAM,KAAK,GAAG,OAAO,sBAAsB,IAAI,EAAE;CAErD;AACF"}
@@ -0,0 +1,33 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { DatabaseAdapter } from "../adapter/database.mjs";
4
+ import { Token } from "../schema.mjs";
5
+ //#region src/models/token.d.ts
6
+ /** Data operations for CI tokens. */
7
+ declare class TokenModel {
8
+ private readonly db;
9
+ /**
10
+ * @param db - Database adapter.
11
+ */
12
+ constructor(db: DatabaseAdapter);
13
+ /**
14
+ * Create a token record storing its hash.
15
+ *
16
+ * @param projectId - Project ID.
17
+ * @param name - Token name.
18
+ * @param hash - Hashed token value.
19
+ * @returns The created token.
20
+ */
21
+ create(projectId: string, name: string, hash: string): Promise<Token>;
22
+ /** List all tokens for a project. */
23
+ list(projectId: string): Promise<Token[]>;
24
+ /** Fetch a token by id within a project, or null if not found. */
25
+ get(projectId: string, id: string): Promise<Token | null>;
26
+ /** Fetch a token by its hashed value, or null if not found. */
27
+ findByHash(hash: string): Promise<Token | null>;
28
+ /** Delete a token by id. */
29
+ remove(id: string): Promise<void>;
30
+ }
31
+ //#endregion
32
+ export { type Token, TokenModel };
33
+ //# sourceMappingURL=token.d.mts.map
@@ -0,0 +1,60 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { tokens } from "../schema.mjs";
5
+ import { ulid } from "../utils/ulid.mjs";
6
+ import { and, eq } from "drizzle-orm";
7
+ //#region src/models/token.ts
8
+ /** Data operations for CI tokens. */
9
+ var TokenModel = class {
10
+ db;
11
+ /**
12
+ * @param db - Database adapter.
13
+ */
14
+ constructor(db) {
15
+ this.db = db;
16
+ }
17
+ /**
18
+ * Create a token record storing its hash.
19
+ *
20
+ * @param projectId - Project ID.
21
+ * @param name - Token name.
22
+ * @param hash - Hashed token value.
23
+ * @returns The created token.
24
+ */
25
+ async create(projectId, name, hash) {
26
+ return await this.db.insert(tokens, {
27
+ id: ulid(),
28
+ projectId,
29
+ name,
30
+ hash,
31
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
32
+ });
33
+ }
34
+ /** List all tokens for a project. */
35
+ async list(projectId) {
36
+ return await this.db.list(tokens, { where: eq(tokens.projectId, projectId) });
37
+ }
38
+ /** Fetch a token by id within a project, or null if not found. */
39
+ async get(projectId, id) {
40
+ return (await this.db.list(tokens, {
41
+ where: and(eq(tokens.projectId, projectId), eq(tokens.id, id)),
42
+ limit: 1
43
+ }))[0] ?? null;
44
+ }
45
+ /** Fetch a token by its hashed value, or null if not found. */
46
+ async findByHash(hash) {
47
+ return (await this.db.list(tokens, {
48
+ where: eq(tokens.hash, hash),
49
+ limit: 1
50
+ }))[0] ?? null;
51
+ }
52
+ /** Delete a token by id. */
53
+ async remove(id) {
54
+ await this.db.remove(tokens, id);
55
+ }
56
+ };
57
+ //#endregion
58
+ export { TokenModel };
59
+
60
+ //# sourceMappingURL=token.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.mjs","names":[],"sources":["../../src/models/token.ts"],"sourcesContent":["import { and, eq } from \"drizzle-orm\";\n\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { tokens, type Token } from \"../schema.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\n/** Data operations for CI tokens. */\nexport class TokenModel {\n /**\n * @param db - Database adapter.\n */\n constructor(private readonly db: DatabaseAdapter) {}\n\n /**\n * Create a token record storing its hash.\n *\n * @param projectId - Project ID.\n * @param name - Token name.\n * @param hash - Hashed token value.\n * @returns The created token.\n */\n async create(projectId: string, name: string, hash: string): Promise<Token> {\n return await this.db.insert(tokens, {\n id: ulid(),\n projectId,\n name,\n hash,\n createdAt: new Date().toISOString(),\n });\n }\n\n /** List all tokens for a project. */\n async list(projectId: string): Promise<Token[]> {\n return await this.db.list(tokens, { where: eq(tokens.projectId, projectId) });\n }\n\n /** Fetch a token by id within a project, or null if not found. */\n async get(projectId: string, id: string): Promise<Token | null> {\n const rows = await this.db.list(tokens, {\n where: and(eq(tokens.projectId, projectId), eq(tokens.id, id)),\n limit: 1,\n });\n return rows[0] ?? null;\n }\n\n /** Fetch a token by its hashed value, or null if not found. */\n async findByHash(hash: string): Promise<Token | null> {\n const rows = await this.db.list(tokens, { where: eq(tokens.hash, hash), limit: 1 });\n return rows[0] ?? null;\n }\n\n /** Delete a token by id. */\n async remove(id: string): Promise<void> {\n await this.db.remove(tokens, id);\n }\n}\n\nexport type { Token };\n"],"mappings":";;;;;;;;AAOA,IAAa,aAAb,MAAwB;CAIO;;;;CAA7B,YAAY,IAAsC;EAArB,KAAA,KAAA;CAAsB;;;;;;;;;CAUnD,MAAM,OAAO,WAAmB,MAAc,MAA8B;EAC1E,OAAO,MAAM,KAAK,GAAG,OAAO,QAAQ;GAClC,IAAI,KAAK;GACT;GACA;GACA;GACA,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EACpC,CAAC;CACH;;CAGA,MAAM,KAAK,WAAqC;EAC9C,OAAO,MAAM,KAAK,GAAG,KAAK,QAAQ,EAAE,OAAO,GAAG,OAAO,WAAW,SAAS,EAAE,CAAC;CAC9E;;CAGA,MAAM,IAAI,WAAmB,IAAmC;EAK9D,QAAO,MAJY,KAAK,GAAG,KAAK,QAAQ;GACtC,OAAO,IAAI,GAAG,OAAO,WAAW,SAAS,GAAG,GAAG,OAAO,IAAI,EAAE,CAAC;GAC7D,OAAO;EACT,CAAC,EAAA,CACW,MAAM;CACpB;;CAGA,MAAM,WAAW,MAAqC;EAEpD,QAAO,MADY,KAAK,GAAG,KAAK,QAAQ;GAAE,OAAO,GAAG,OAAO,MAAM,IAAI;GAAG,OAAO;EAAE,CAAC,EAAA,CACtE,MAAM;CACpB;;CAGA,MAAM,OAAO,IAA2B;EACtC,MAAM,KAAK,GAAG,OAAO,QAAQ,EAAE;CACjC;AACF"}
@@ -0,0 +1,37 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ import { DatabaseAdapter } from "../adapter/database.mjs";
4
+ import { Webhook } from "../schema.mjs";
5
+ //#region src/models/webhook.d.ts
6
+ interface WebhookCreateInput {
7
+ url: string;
8
+ events?: string[];
9
+ secret: string;
10
+ }
11
+ /** Data operations for webhook subscriptions. */
12
+ declare class WebhookModel {
13
+ private readonly db;
14
+ /**
15
+ * @param db - Database adapter.
16
+ */
17
+ constructor(db: DatabaseAdapter);
18
+ /** List all webhooks for a project. */
19
+ list(projectId: string): Promise<Webhook[]>;
20
+ /**
21
+ * Create a webhook subscription for a project.
22
+ *
23
+ * @param projectId - Project ID.
24
+ * @param input - Webhook creation input.
25
+ * @returns The created webhook.
26
+ */
27
+ create(projectId: string, input: WebhookCreateInput): Promise<Webhook>;
28
+ /** Fetch a webhook by id scoped to a project, or null if not found. */
29
+ get(projectId: string, id: string): Promise<Webhook | null>;
30
+ /** Remove a webhook if it belongs to the given project. */
31
+ remove(projectId: string, id: string): Promise<void>;
32
+ /** Decode the JSON-serialized event list of a webhook. */
33
+ static eventsOf(webhook: Webhook): string[];
34
+ }
35
+ //#endregion
36
+ export { type Webhook, WebhookCreateInput, WebhookModel };
37
+ //# sourceMappingURL=webhook.d.mts.map
@@ -0,0 +1,67 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { webhooks } from "../schema.mjs";
5
+ import { ulid } from "../utils/ulid.mjs";
6
+ import { eq } from "drizzle-orm";
7
+ //#region src/models/webhook.ts
8
+ /** Data operations for webhook subscriptions. */
9
+ var WebhookModel = class {
10
+ db;
11
+ /**
12
+ * @param db - Database adapter.
13
+ */
14
+ constructor(db) {
15
+ this.db = db;
16
+ }
17
+ /** List all webhooks for a project. */
18
+ async list(projectId) {
19
+ return await this.db.list(webhooks, { where: eq(webhooks.projectId, projectId) });
20
+ }
21
+ /**
22
+ * Create a webhook subscription for a project.
23
+ *
24
+ * @param projectId - Project ID.
25
+ * @param input - Webhook creation input.
26
+ * @returns The created webhook.
27
+ */
28
+ async create(projectId, input) {
29
+ const now = (/* @__PURE__ */ new Date()).toISOString();
30
+ return await this.db.insert(webhooks, {
31
+ id: ulid(),
32
+ projectId,
33
+ url: input.url,
34
+ secret: input.secret,
35
+ events: input.events && input.events.length > 0 ? JSON.stringify(input.events) : null,
36
+ createdAt: now,
37
+ updatedAt: now
38
+ });
39
+ }
40
+ /** Fetch a webhook by id scoped to a project, or null if not found. */
41
+ async get(projectId, id) {
42
+ const found = (await this.db.list(webhooks, {
43
+ where: eq(webhooks.id, id),
44
+ limit: 1
45
+ }))[0] ?? null;
46
+ return found?.projectId === projectId ? found : null;
47
+ }
48
+ /** Remove a webhook if it belongs to the given project. */
49
+ async remove(projectId, id) {
50
+ const existing = await this.get(projectId, id);
51
+ if (existing) await this.db.remove(webhooks, existing.id);
52
+ }
53
+ /** Decode the JSON-serialized event list of a webhook. */
54
+ static eventsOf(webhook) {
55
+ if (!webhook.events) return [];
56
+ try {
57
+ const parsed = JSON.parse(webhook.events);
58
+ return Array.isArray(parsed) ? parsed.filter((event) => typeof event === "string") : [];
59
+ } catch {
60
+ return [];
61
+ }
62
+ }
63
+ };
64
+ //#endregion
65
+ export { WebhookModel };
66
+
67
+ //# sourceMappingURL=webhook.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.mjs","names":[],"sources":["../../src/models/webhook.ts"],"sourcesContent":["import { eq } from \"drizzle-orm\";\n\nimport type { DatabaseAdapter } from \"../adapters/database.ts\";\nimport { webhooks, type Webhook } from \"../schema.ts\";\nimport { ulid } from \"../utils/ulid.ts\";\n\nexport interface WebhookCreateInput {\n url: string;\n events?: string[];\n secret: string;\n}\n\n/** Data operations for webhook subscriptions. */\nexport class WebhookModel {\n /**\n * @param db - Database adapter.\n */\n constructor(private readonly db: DatabaseAdapter) {}\n\n /** List all webhooks for a project. */\n async list(projectId: string): Promise<Webhook[]> {\n return await this.db.list(webhooks, { where: eq(webhooks.projectId, projectId) });\n }\n\n /**\n * Create a webhook subscription for a project.\n *\n * @param projectId - Project ID.\n * @param input - Webhook creation input.\n * @returns The created webhook.\n */\n async create(projectId: string, input: WebhookCreateInput): Promise<Webhook> {\n const now = new Date().toISOString();\n return await this.db.insert(webhooks, {\n id: ulid(),\n projectId,\n url: input.url,\n secret: input.secret,\n events: input.events && input.events.length > 0 ? JSON.stringify(input.events) : null,\n createdAt: now,\n updatedAt: now,\n });\n }\n\n /** Fetch a webhook by id scoped to a project, or null if not found. */\n async get(projectId: string, id: string): Promise<Webhook | null> {\n const rows = await this.db.list(webhooks, { where: eq(webhooks.id, id), limit: 1 });\n const found = rows[0] ?? null;\n return found?.projectId === projectId ? found : null;\n }\n\n /** Remove a webhook if it belongs to the given project. */\n async remove(projectId: string, id: string): Promise<void> {\n const existing = await this.get(projectId, id);\n if (existing) {\n await this.db.remove(webhooks, existing.id);\n }\n }\n\n /** Decode the JSON-serialized event list of a webhook. */\n static eventsOf(webhook: Webhook): string[] {\n if (!webhook.events) {\n return [];\n }\n try {\n const parsed: unknown = JSON.parse(webhook.events);\n return Array.isArray(parsed) ? parsed.filter((event): event is string => typeof event === \"string\") : [];\n } catch {\n return [];\n }\n }\n}\n\nexport type { Webhook };"],"mappings":";;;;;;;;AAaA,IAAa,eAAb,MAA0B;CAIK;;;;CAA7B,YAAY,IAAsC;EAArB,KAAA,KAAA;CAAsB;;CAGnD,MAAM,KAAK,WAAuC;EAChD,OAAO,MAAM,KAAK,GAAG,KAAK,UAAU,EAAE,OAAO,GAAG,SAAS,WAAW,SAAS,EAAE,CAAC;CAClF;;;;;;;;CASA,MAAM,OAAO,WAAmB,OAA6C;EAC3E,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,OAAO,MAAM,KAAK,GAAG,OAAO,UAAU;GACpC,IAAI,KAAK;GACT;GACA,KAAK,MAAM;GACX,QAAQ,MAAM;GACd,QAAQ,MAAM,UAAU,MAAM,OAAO,SAAS,IAAI,KAAK,UAAU,MAAM,MAAM,IAAI;GACjF,WAAW;GACX,WAAW;EACb,CAAC;CACH;;CAGA,MAAM,IAAI,WAAmB,IAAqC;EAEhE,MAAM,SAAQ,MADK,KAAK,GAAG,KAAK,UAAU;GAAE,OAAO,GAAG,SAAS,IAAI,EAAE;GAAG,OAAO;EAAE,CAAC,EAAA,CAC/D,MAAM;EACzB,OAAO,OAAO,cAAc,YAAY,QAAQ;CAClD;;CAGA,MAAM,OAAO,WAAmB,IAA2B;EACzD,MAAM,WAAW,MAAM,KAAK,IAAI,WAAW,EAAE;EAC7C,IAAI,UACF,MAAM,KAAK,GAAG,OAAO,UAAU,SAAS,EAAE;CAE9C;;CAGA,OAAO,SAAS,SAA4B;EAC1C,IAAI,CAAC,QAAQ,QACX,OAAO,CAAC;EAEV,IAAI;GACF,MAAM,SAAkB,KAAK,MAAM,QAAQ,MAAM;GACjD,OAAO,MAAM,QAAQ,MAAM,IAAI,OAAO,QAAQ,UAA2B,OAAO,UAAU,QAAQ,IAAI,CAAC;EACzG,QAAQ;GACN,OAAO,CAAC;EACV;CACF;AACF"}