@hiai-gg/docsmint 0.4.8 → 0.5.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 (137) hide show
  1. package/backend/src/lib/api-key-facade.ts +1 -0
  2. package/dist/backend/index.js +38379 -18500
  3. package/dist/backend-api-key-facade.js +772 -0
  4. package/dist/client.js +2 -7
  5. package/dist/frontend/{CategoryDialog-CxVyaBV5.js → CategoryDialog-DyytjQn6.js} +109 -108
  6. package/dist/frontend/ConfirmDialog-DgMTQBq4.js +134 -0
  7. package/dist/frontend/{CreateSnapshotDialog-BDgFTeFn.js → CreateSnapshotDialog-CXW4sCuf.js} +29 -29
  8. package/dist/frontend/{DatePicker-BZRBluzF.js → DatePicker-BkaaNS1l.js} +34 -34
  9. package/dist/frontend/{DeleteDialog-CvAr2WPu.js → DeleteDialog-8k6JYWCx.js} +3 -3
  10. package/dist/frontend/{FolderNode--l1g8bcL.js → FolderNode-DAGPgi-M.js} +8 -8
  11. package/dist/frontend/{FolderTreeSelector-CyB8y2Sm.js → FolderTreeSelector-BMB1MS9N.js} +3 -3
  12. package/dist/frontend/{Icon-CooFj4Ee.js → Icon-Db-o32_E.js} +1 -1
  13. package/dist/frontend/{SettingsDialog-BB7h7GWw.js → SettingsDialog-DEx1eEZm.js} +290 -290
  14. package/dist/frontend/ShareDialog-ChZogJtW.js +166 -0
  15. package/dist/frontend/Sidebar-BdB8CfXS.js +2037 -0
  16. package/dist/frontend/TagCreateDialog-Bb0-Vtzh.js +261 -0
  17. package/dist/frontend/api/attachments.js +1 -1
  18. package/dist/frontend/api/categories.d.ts +3 -3
  19. package/dist/frontend/api/categories.js +1 -1
  20. package/dist/frontend/api/documents.js +1 -1
  21. package/dist/frontend/api/share.js +1 -1
  22. package/dist/frontend/api/tags.js +1 -1
  23. package/dist/frontend/{api-keys-CpkrA8rD.js → api-keys-DIwAdYkg.js} +10 -10
  24. package/dist/frontend/app-shell.d.ts +5 -1
  25. package/dist/frontend/app-shell.js +83 -72
  26. package/dist/frontend/{attachments-BuILSs56.js → attachments-C2DBzU-r.js} +13 -13
  27. package/dist/frontend/{camera-iKXDOOWT.js → camera-CzCxBOpc.js} +2 -2
  28. package/dist/frontend/{categories-Drnns7N6.js → categories-X7gK8azc.js} +0 -2
  29. package/dist/frontend/{check-D-UuvBuh.js → check-BIi-Ghti.js} +2 -2
  30. package/dist/frontend/{chevron-down-CiPtYyFh.js → chevron-down-C2ft7XUV.js} +2 -2
  31. package/dist/frontend/{circle-check-tSPE1V6t.js → circle-check-BrqfiQH1.js} +2 -2
  32. package/dist/frontend/{clock-DJHbHZb8.js → clock-Dz86E4Kz.js} +2 -2
  33. package/dist/frontend/collaboration.d.ts +4 -2
  34. package/dist/frontend/collaboration.js +114 -89
  35. package/dist/frontend/components/category-dialog.js +1 -1
  36. package/dist/frontend/components/create-snapshot-dialog.js +1 -1
  37. package/dist/frontend/components/delete-dialog.js +1 -1
  38. package/dist/frontend/components/editor/document-editor.js +1169 -1168
  39. package/dist/frontend/components/editor/document-title.js +2 -2
  40. package/dist/frontend/components/editor/markdown-toggle.js +4 -4
  41. package/dist/frontend/components/folder-node.js +1 -1
  42. package/dist/frontend/components/folder-tree-selector.js +1 -1
  43. package/dist/frontend/components/save-as-dialog.js +145 -144
  44. package/dist/frontend/components/settings.js +1 -1
  45. package/dist/frontend/components/share-dialog.js +1 -1
  46. package/dist/frontend/components/sidebar.js +1 -1
  47. package/dist/frontend/components/tag-create-dialog.js +1 -1
  48. package/dist/frontend/components/version-history.js +34 -34
  49. package/dist/frontend/{confirm-dialog-C4jTRbiS.js → confirm-dialog-CzmoZw3a.js} +20 -20
  50. package/dist/frontend/{copy-GppwRMjX.js → copy-CvCh4ZVt.js} +2 -2
  51. package/dist/frontend/dashboard.js +1025 -1011
  52. package/dist/frontend/{dialog-Bevp9mmM.js → dialog-Ce79C9fd.js} +1 -1
  53. package/dist/frontend/{dist-C3857Spp.js → dist-DQa_OHOa.js} +2 -2
  54. package/dist/frontend/{documents-Bc7b_MSZ.js → documents-CwCs1rA7.js} +52 -12
  55. package/dist/frontend/{dropdown-menu-CRSby3e9.js → dropdown-menu-Cre80-CJ.js} +2 -2
  56. package/dist/frontend/{folder-DeWhnD7O.js → folder-Cx60MRpi.js} +3 -3
  57. package/dist/frontend/i18n.js +3 -3
  58. package/dist/frontend/{messages-C5Q890i2.js → messages-DIovIZrK.js} +46 -44
  59. package/dist/frontend/{rotate-ccw-B4Q7brmV.js → rotate-ccw-ta4NosZ0.js} +2 -2
  60. package/dist/frontend/{route-context-BqQQ5c4i.js → route-context-DYQlUfzL.js} +14 -11
  61. package/dist/frontend/search.js +99 -99
  62. package/dist/frontend/{share-Cu7SSJuJ.js → share-D7JtkC85.js} +2 -2
  63. package/dist/frontend/{tag-HxRazVN_.js → tag-DhVFHMcV.js} +3 -3
  64. package/dist/frontend/{tag-store.svelte-CG7RrlDT.js → tag-store.svelte-ogVCMCbv.js} +1 -1
  65. package/dist/frontend/{tags-CtCV0Djo.js → tags-BdCaMGfj.js} +18 -18
  66. package/dist/frontend/{x-CzBsnJ2x.js → x-cfdlccjs.js} +2 -2
  67. package/dist/frontend-ssr/api/attachments.js +1 -1
  68. package/dist/frontend-ssr/api/categories.js +1 -1
  69. package/dist/frontend-ssr/api/documents.js +1 -1
  70. package/dist/frontend-ssr/api/share.js +1 -1
  71. package/dist/frontend-ssr/api/tags.js +1 -1
  72. package/dist/frontend-ssr/app-shell.js +30 -26
  73. package/dist/frontend-ssr/assets/{CategoryDialog-BcGW8-Kf.js → CategoryDialog-CLAGMZPz.js} +12 -10
  74. package/dist/frontend-ssr/assets/ConfirmDialog-BhAelejz.js +131 -0
  75. package/dist/frontend-ssr/assets/{CreateSnapshotDialog-1bROJ-Rn.js → CreateSnapshotDialog-DNAjd4eD.js} +2 -2
  76. package/dist/frontend-ssr/assets/{DatePicker-B1ML8OVz.js → DatePicker-Cp6Qmgdb.js} +17 -29
  77. package/dist/frontend-ssr/assets/{DeleteDialog-tBYaP0L3.js → DeleteDialog-DG9Lm38J.js} +2 -2
  78. package/dist/frontend-ssr/assets/{FolderNode-CpHrpOED.js → FolderNode-Ty6fMbDu.js} +5 -5
  79. package/dist/frontend-ssr/assets/{FolderTreeSelector-DZNZ2oI6.js → FolderTreeSelector-Bxi8EqMm.js} +8 -12
  80. package/dist/frontend-ssr/assets/{Icon-B0Zqppjo.js → Icon-QwirKi9Y.js} +16 -28
  81. package/dist/frontend-ssr/assets/{SettingsDialog-C2SMWJL8.js → SettingsDialog-BymUxBgg.js} +20 -27
  82. package/dist/frontend-ssr/assets/{ShareDialog-vMvJLRfn.js → ShareDialog-C-9VDKSz.js} +3 -1
  83. package/dist/frontend-ssr/assets/{Sidebar-DKqmwreh.js → Sidebar-DzbIUZFc.js} +45 -55
  84. package/dist/frontend-ssr/assets/{TagCreateDialog-B0DPEfmQ.js → TagCreateDialog-B7wDbVqG.js} +7 -5
  85. package/dist/frontend-ssr/assets/{api-keys-DFqX39MT.js → api-keys-DIbclwj5.js} +10 -10
  86. package/dist/frontend-ssr/assets/{attachments-CWBxE3Vz.js → attachments-CXgatN0B.js} +9 -9
  87. package/dist/frontend-ssr/assets/{camera-CdxscTw-.js → camera-D6s20LDi.js} +6 -10
  88. package/dist/frontend-ssr/assets/{categories-CTCtdv7V.js → categories-av9xjeg5.js} +0 -2
  89. package/dist/frontend-ssr/assets/{chevron-down-CSSNGNh_.js → chevron-down-CvqKATjM.js} +6 -10
  90. package/dist/frontend-ssr/assets/{chevron-right-B4p7Sk6X.js → chevron-right-DKD7WXjz.js} +6 -10
  91. package/dist/frontend-ssr/assets/{circle-check-CI1v4vYh.js → circle-check-CPS9dKEo.js} +6 -10
  92. package/dist/frontend-ssr/assets/{clock-IL-eX2di.js → clock-CzsE58TH.js} +6 -10
  93. package/dist/frontend-ssr/assets/{confirm-dialog-BCthVwUY.js → confirm-dialog-B3C0WeMn.js} +20 -32
  94. package/dist/frontend-ssr/assets/{copy-CjV57nGQ.js → copy-DkkCPkBL.js} +6 -10
  95. package/dist/frontend-ssr/assets/{documents-PObdTOiT.js → documents-CZ-wQohy.js} +58 -12
  96. package/dist/frontend-ssr/assets/{dropdown-menu-Bj5rbqbB.js → dropdown-menu-CGkf8-rb.js} +6 -10
  97. package/dist/frontend-ssr/assets/{folder-DChhG7qN.js → folder-DYIE-XPP.js} +6 -10
  98. package/dist/frontend-ssr/assets/{loader-circle-DZsrZ-er.js → loader-circle-CIEWGyfP.js} +6 -10
  99. package/dist/frontend-ssr/assets/{messages-CvqpZNJe.js → messages-D-auzk9H.js} +23 -1
  100. package/dist/frontend-ssr/assets/{rotate-ccw-BlgWwaiq.js → rotate-ccw-dJSqPyTd.js} +6 -10
  101. package/dist/frontend-ssr/assets/{route-context-uca_b2Px.js → route-context-BPRerEtZ.js} +11 -8
  102. package/dist/frontend-ssr/assets/{share-BTBJelxA.js → share-qTqvd6Lu.js} +2 -2
  103. package/dist/frontend-ssr/assets/{tag-GZEuGWk1.js → tag-BmiVU_Ej.js} +11 -19
  104. package/dist/frontend-ssr/assets/{tag-store.svelte-DvgUduhd.js → tag-store.svelte-BM1lUI_P.js} +1 -1
  105. package/dist/frontend-ssr/assets/{tags-BId2wdYd.js → tags-BQsI_6OW.js} +12 -12
  106. package/dist/frontend-ssr/collaboration.js +37 -12
  107. package/dist/frontend-ssr/components/category-dialog.js +1 -1
  108. package/dist/frontend-ssr/components/create-snapshot-dialog.js +1 -1
  109. package/dist/frontend-ssr/components/delete-dialog.js +1 -1
  110. package/dist/frontend-ssr/components/editor/document-editor.js +108 -186
  111. package/dist/frontend-ssr/components/editor/document-title.js +1 -1
  112. package/dist/frontend-ssr/components/editor/markdown-toggle.js +2 -2
  113. package/dist/frontend-ssr/components/folder-node.js +1 -1
  114. package/dist/frontend-ssr/components/folder-tree-selector.js +1 -1
  115. package/dist/frontend-ssr/components/save-as-dialog.js +7 -5
  116. package/dist/frontend-ssr/components/settings.js +1 -1
  117. package/dist/frontend-ssr/components/share-dialog.js +1 -1
  118. package/dist/frontend-ssr/components/sidebar.js +1 -1
  119. package/dist/frontend-ssr/components/tag-create-dialog.js +1 -1
  120. package/dist/frontend-ssr/components/version-history.js +19 -27
  121. package/dist/frontend-ssr/dashboard.js +78 -113
  122. package/dist/frontend-ssr/i18n.js +1 -1
  123. package/dist/frontend-ssr/search.js +15 -19
  124. package/dist/index.d.ts +1 -1
  125. package/dist/index.js +1 -1
  126. package/dist/types.d.ts +1 -3
  127. package/dist/workspace.d.ts +0 -2
  128. package/dist/workspace.js +0 -2
  129. package/package.json +2 -2
  130. package/packages/cli/src/index.ts +1 -1
  131. package/packages/db/src/with-tenant.ts +2 -0
  132. package/packages/mcp-server/src/index.ts +1 -1
  133. package/dist/frontend/ConfirmDialog-NFGXRa3E.js +0 -214
  134. package/dist/frontend/ShareDialog-BUOkYSlw.js +0 -165
  135. package/dist/frontend/Sidebar-fbtYC3qs.js +0 -2037
  136. package/dist/frontend/TagCreateDialog-B9YZBNHi.js +0 -260
  137. package/dist/frontend-ssr/assets/ConfirmDialog-DjMFKc0V.js +0 -420
@@ -0,0 +1,772 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+
17
+ // ../db/src/schema.ts
18
+ var exports_schema = {};
19
+ __export(exports_schema, {
20
+ versions: () => versions,
21
+ versionRelations: () => versionRelations,
22
+ verifications: () => verifications,
23
+ users: () => users,
24
+ tags: () => tags,
25
+ tagRelations: () => tagRelations,
26
+ shareRoleEnum: () => shareRoleEnum,
27
+ shareLinks: () => shareLinks,
28
+ shareLinkRelations: () => shareLinkRelations,
29
+ sessions: () => sessions,
30
+ pipelineStatusEnum: () => pipelineStatusEnum,
31
+ pipelineStageEnum: () => pipelineStageEnum,
32
+ lifecycleOperations: () => lifecycleOperations,
33
+ lifecycleOperationStatusEnum: () => lifecycleOperationStatusEnum,
34
+ lifecycleOperationKindEnum: () => lifecycleOperationKindEnum,
35
+ guestAccessRelations: () => guestAccessRelations,
36
+ guestAccess: () => guestAccess,
37
+ folders: () => folders,
38
+ folderRelations: () => folderRelations,
39
+ embeddingStatusEnum: () => embeddingStatusEnum,
40
+ documents: () => documents,
41
+ documentVisibilityEnum: () => documentVisibilityEnum,
42
+ documentTags: () => documentTags,
43
+ documentTagRelations: () => documentTagRelations,
44
+ documentRelations: () => documentRelations,
45
+ documentPipelineRuns: () => documentPipelineRuns,
46
+ documentPipelineBatches: () => documentPipelineBatches,
47
+ documentEmbeddings: () => documentEmbeddings,
48
+ documentEmbeddingRelations: () => documentEmbeddingRelations,
49
+ documentCreateOperations: () => documentCreateOperations,
50
+ categoryRelations: () => categoryRelations,
51
+ categories: () => categories,
52
+ auditLog: () => auditLog,
53
+ attachments: () => attachments,
54
+ attachmentRelations: () => attachmentRelations,
55
+ apiKeys: () => apiKeys,
56
+ apiKeyRelations: () => apiKeyRelations,
57
+ accounts: () => accounts
58
+ });
59
+ import { pgTable, uuid, text, timestamp, bigint, jsonb, index, uniqueIndex, customType, boolean, check, integer, pgEnum } from "drizzle-orm/pg-core";
60
+ import { relations, sql } from "drizzle-orm";
61
+ var vector = customType({
62
+ dataType(config) {
63
+ return `vector(${config.dimensions})`;
64
+ },
65
+ toDriver(value) {
66
+ return JSON.stringify(value);
67
+ },
68
+ fromDriver(value) {
69
+ if (typeof value === "string")
70
+ return JSON.parse(value);
71
+ return value;
72
+ }
73
+ });
74
+ var tsvector = customType({
75
+ dataType() {
76
+ return "tsvector";
77
+ }
78
+ });
79
+ var documentVisibilityEnum = pgEnum("document_visibility", ["private", "shared", "public"]);
80
+ var shareRoleEnum = pgEnum("share_role", ["viewer", "commenter", "editor"]);
81
+ var embeddingStatusEnum = pgEnum("embedding_status", [
82
+ "pending",
83
+ "processing",
84
+ "ready",
85
+ "failed",
86
+ "stale"
87
+ ]);
88
+ var pipelineStageEnum = pgEnum("pipeline_stage", [
89
+ "prepare",
90
+ "embed",
91
+ "graph",
92
+ "summarize",
93
+ "finalize"
94
+ ]);
95
+ var pipelineStatusEnum = pgEnum("pipeline_status", [
96
+ "pending",
97
+ "processing",
98
+ "ready",
99
+ "retrying",
100
+ "failed",
101
+ "ready_with_warnings",
102
+ "skipped",
103
+ "cancelled"
104
+ ]);
105
+ var lifecycleOperationKindEnum = pgEnum("lifecycle_operation_kind", [
106
+ "export",
107
+ "purge"
108
+ ]);
109
+ var lifecycleOperationStatusEnum = pgEnum("lifecycle_operation_status", [
110
+ "pending",
111
+ "running",
112
+ "retryable",
113
+ "completed",
114
+ "rejected"
115
+ ]);
116
+ var users = pgTable("users", {
117
+ id: uuid("id").primaryKey().defaultRandom(),
118
+ email: text("email").notNull().unique(),
119
+ name: text("name"),
120
+ emailVerified: boolean("email_verified").default(false),
121
+ image: text("image"),
122
+ createdAt: timestamp("created_at").defaultNow().notNull(),
123
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
124
+ });
125
+ var sessions = pgTable("sessions", {
126
+ id: uuid("id").primaryKey().defaultRandom(),
127
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
128
+ token: text("token").notNull().unique(),
129
+ expiresAt: timestamp("expires_at").notNull(),
130
+ ipAddress: text("ip_address"),
131
+ userAgent: text("user_agent"),
132
+ revokedAt: timestamp("revoked_at"),
133
+ createdAt: timestamp("created_at").defaultNow().notNull(),
134
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
135
+ }, (table) => [
136
+ index("sessions_user_id_idx").on(table.userId),
137
+ index("sessions_revoked_at_idx").on(table.revokedAt).where(sql`${table.revokedAt} IS NOT NULL`)
138
+ ]);
139
+ var accounts = pgTable("accounts", {
140
+ id: uuid("id").primaryKey().defaultRandom(),
141
+ userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
142
+ accountId: text("account_id").notNull(),
143
+ providerId: text("provider_id").notNull(),
144
+ accessToken: text("access_token"),
145
+ refreshToken: text("refresh_token"),
146
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
147
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
148
+ scope: text("scope"),
149
+ password: text("password"),
150
+ createdAt: timestamp("created_at").defaultNow().notNull(),
151
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
152
+ }, (table) => [
153
+ index("accounts_user_id_idx").on(table.userId),
154
+ uniqueIndex("accounts_provider_account_idx").on(table.providerId, table.accountId)
155
+ ]);
156
+ var verifications = pgTable("verifications", {
157
+ id: uuid("id").primaryKey().defaultRandom(),
158
+ identifier: text("identifier").notNull(),
159
+ value: text("value").notNull(),
160
+ expiresAt: timestamp("expires_at").notNull(),
161
+ createdAt: timestamp("created_at").defaultNow().notNull(),
162
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
163
+ }, (table) => [
164
+ index("verifications_identifier_idx").on(table.identifier)
165
+ ]);
166
+ var folders = pgTable("folders", {
167
+ id: uuid("id").primaryKey().defaultRandom(),
168
+ ownerId: uuid("owner_id").notNull().references(() => users.id, { onDelete: "cascade" }),
169
+ workspaceId: text("workspace_id"),
170
+ parentId: uuid("parent_id").references(() => folders.id, {
171
+ onDelete: "set null"
172
+ }),
173
+ categoryId: uuid("category_id").references(() => categories.id, {
174
+ onDelete: "set null"
175
+ }),
176
+ name: text("name").notNull(),
177
+ order: integer("order").notNull().default(0),
178
+ createdAt: timestamp("created_at").defaultNow().notNull(),
179
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
180
+ }, (table) => [
181
+ index("folders_owner_id_idx").on(table.ownerId),
182
+ index("folders_parent_id_idx").on(table.parentId),
183
+ index("folders_category_id_idx").on(table.categoryId)
184
+ ]);
185
+ var folderRelations = relations(folders, ({ one, many }) => ({
186
+ owner: one(users, { fields: [folders.ownerId], references: [users.id] }),
187
+ parent: one(folders, {
188
+ fields: [folders.parentId],
189
+ references: [folders.id],
190
+ relationName: "folderParent"
191
+ }),
192
+ category: one(categories, {
193
+ fields: [folders.categoryId],
194
+ references: [categories.id]
195
+ }),
196
+ children: many(folders, { relationName: "folderParent" }),
197
+ documents: many(documents)
198
+ }));
199
+ var documents = pgTable("documents", {
200
+ id: uuid("id").primaryKey().defaultRandom(),
201
+ ownerId: uuid("owner_id").notNull().references(() => users.id, { onDelete: "cascade" }),
202
+ workspaceId: text("workspace_id"),
203
+ folderId: uuid("folder_id").references(() => folders.id, {
204
+ onDelete: "set null"
205
+ }),
206
+ categoryId: uuid("category_id").references(() => categories.id, {
207
+ onDelete: "set null"
208
+ }),
209
+ title: text("title").notNull().default("Untitled"),
210
+ content: text("content").default(""),
211
+ contentJson: jsonb("content_json"),
212
+ metadata: jsonb("metadata"),
213
+ visibility: documentVisibilityEnum("visibility").notNull().default("private"),
214
+ contentHash: text("content_hash"),
215
+ lastSignificantHash: text("last_significant_hash"),
216
+ lastSignificantUpdateAt: timestamp("last_significant_update_at"),
217
+ pendingMinorChanges: boolean("pending_minor_changes").default(false).notNull(),
218
+ metadataChangedAt: timestamp("metadata_changed_at"),
219
+ searchVector: tsvector("search_vector").generatedAlwaysAs(sql`to_tsvector('english', left(COALESCE(title, '') || ' ' || regexp_replace(COALESCE(content, ''), 'data:[^[:space:])>]+', ' ', 'g'), 200000))`),
220
+ searchVectorSimple: tsvector("search_vector_simple").generatedAlwaysAs(sql`to_tsvector('simple', left(COALESCE(title, '') || ' ' || regexp_replace(COALESCE(content, ''), 'data:[^[:space:])>]+', ' ', 'g'), 200000))`),
221
+ embeddingStatus: embeddingStatusEnum("embedding_status").notNull().default("pending"),
222
+ activeEmbeddingGeneration: uuid("active_embedding_generation"),
223
+ pendingEmbeddingGeneration: uuid("pending_embedding_generation"),
224
+ embeddingProfile: text("embedding_profile"),
225
+ embeddingErrorCode: text("embedding_error_code"),
226
+ embeddingUpdatedAt: timestamp("embedding_updated_at"),
227
+ deletedAt: timestamp("deleted_at"),
228
+ createdAt: timestamp("created_at").defaultNow().notNull(),
229
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
230
+ }, (table) => [
231
+ index("documents_owner_id_idx").on(table.ownerId),
232
+ index("documents_folder_id_idx").on(table.folderId),
233
+ index("documents_category_id_idx").on(table.categoryId),
234
+ index("documents_created_at_idx").on(table.createdAt),
235
+ index("idx_documents_search_vector").using("gin", table.searchVector),
236
+ index("idx_documents_search_vector_simple").using("gin", table.searchVectorSimple),
237
+ index("documents_embedding_status_idx").on(table.embeddingStatus),
238
+ index("documents_workspace_deleted_at_idx").on(table.workspaceId, table.deletedAt),
239
+ index("idx_documents_title_trgm").using("gin", sql`${table.title} gin_trgm_ops`)
240
+ ]);
241
+ var documentRelations = relations(documents, ({ one, many }) => ({
242
+ owner: one(users, { fields: [documents.ownerId], references: [users.id] }),
243
+ folder: one(folders, {
244
+ fields: [documents.folderId],
245
+ references: [folders.id]
246
+ }),
247
+ category: one(categories, {
248
+ fields: [documents.categoryId],
249
+ references: [categories.id]
250
+ }),
251
+ tags: many(documentTags),
252
+ attachments: many(attachments),
253
+ versions: many(versions)
254
+ }));
255
+ var documentPipelineRuns = pgTable("document_pipeline_runs", {
256
+ id: uuid("id").primaryKey().defaultRandom(),
257
+ documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }),
258
+ ownerId: uuid("owner_id").notNull().references(() => users.id, { onDelete: "cascade" }),
259
+ workspaceId: text("workspace_id"),
260
+ generationId: uuid("generation_id").notNull(),
261
+ revision: text("revision").notNull(),
262
+ source: text("source").notNull(),
263
+ status: pipelineStatusEnum("status").notNull().default("pending"),
264
+ prepareStatus: pipelineStatusEnum("prepare_status").notNull().default("pending"),
265
+ embedStatus: pipelineStatusEnum("embed_status").notNull().default("pending"),
266
+ graphStatus: pipelineStatusEnum("graph_status").notNull().default("pending"),
267
+ summarizeStatus: pipelineStatusEnum("summarize_status").notNull().default("pending"),
268
+ finalizeStatus: pipelineStatusEnum("finalize_status").notNull().default("pending"),
269
+ totalBatches: integer("total_batches").notNull().default(0),
270
+ completedBatches: integer("completed_batches").notNull().default(0),
271
+ failedBatches: integer("failed_batches").notNull().default(0),
272
+ errorCode: text("error_code"),
273
+ attempts: integer("attempts").notNull().default(0),
274
+ requestedAt: timestamp("requested_at").defaultNow().notNull(),
275
+ startedAt: timestamp("started_at"),
276
+ completedAt: timestamp("completed_at"),
277
+ heartbeatAt: timestamp("heartbeat_at"),
278
+ availableAt: timestamp("available_at"),
279
+ createdAt: timestamp("created_at").defaultNow().notNull(),
280
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
281
+ }, (table) => [
282
+ uniqueIndex("document_pipeline_runs_document_generation_idx").on(table.documentId, table.generationId),
283
+ index("document_pipeline_runs_owner_status_updated_idx").on(table.ownerId, table.status, table.updatedAt)
284
+ ]);
285
+ var documentPipelineBatches = pgTable("document_pipeline_batches", {
286
+ id: uuid("id").primaryKey().defaultRandom(),
287
+ workspaceId: text("workspace_id"),
288
+ documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }),
289
+ generationId: uuid("generation_id").notNull(),
290
+ batchIndex: integer("batch_index").notNull(),
291
+ stage: pipelineStageEnum("stage").notNull().default("embed"),
292
+ chunkStart: integer("chunk_start").notNull(),
293
+ chunkEnd: integer("chunk_end").notNull(),
294
+ status: pipelineStatusEnum("status").notNull().default("pending"),
295
+ attempts: integer("attempts").notNull().default(0),
296
+ embeddingProfile: text("embedding_profile"),
297
+ errorCode: text("error_code"),
298
+ availableAt: timestamp("available_at"),
299
+ startedAt: timestamp("started_at"),
300
+ completedAt: timestamp("completed_at"),
301
+ heartbeatAt: timestamp("heartbeat_at"),
302
+ createdAt: timestamp("created_at").defaultNow().notNull(),
303
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
304
+ }, (table) => [
305
+ uniqueIndex("document_pipeline_batches_generation_index_idx").on(table.generationId, table.batchIndex),
306
+ index("document_pipeline_batches_stage_status_available_idx").on(table.stage, table.status, table.availableAt),
307
+ index("document_pipeline_batches_document_id_idx").on(table.documentId)
308
+ ]);
309
+ var tags = pgTable("tags", {
310
+ id: uuid("id").primaryKey().defaultRandom(),
311
+ ownerId: uuid("owner_id").notNull().references(() => users.id, { onDelete: "cascade" }),
312
+ workspaceId: text("workspace_id"),
313
+ name: text("name").notNull(),
314
+ color: text("color"),
315
+ createdAt: timestamp("created_at").defaultNow().notNull()
316
+ }, (table) => [
317
+ index("tags_owner_id_idx").on(table.ownerId),
318
+ uniqueIndex("tags_owner_name_idx").on(table.ownerId, table.name)
319
+ ]);
320
+ var tagRelations = relations(tags, ({ many }) => ({
321
+ documents: many(documentTags)
322
+ }));
323
+ var categories = pgTable("categories", {
324
+ id: uuid("id").primaryKey().defaultRandom(),
325
+ ownerId: uuid("owner_id").notNull().references(() => users.id, { onDelete: "cascade" }),
326
+ workspaceId: text("workspace_id"),
327
+ name: text("name").notNull(),
328
+ order: integer("order").notNull().default(0),
329
+ apiMode: text("api_mode").notNull().default("unavailable"),
330
+ apiPermissionRead: boolean("api_permission_read").notNull().default(false),
331
+ apiPermissionEdit: boolean("api_permission_edit").notNull().default(false),
332
+ apiPermissionWrite: boolean("api_permission_write").notNull().default(false),
333
+ createdAt: timestamp("created_at").defaultNow().notNull(),
334
+ updatedAt: timestamp("updated_at").defaultNow().notNull()
335
+ }, (table) => [
336
+ index("categories_owner_id_idx").on(table.ownerId),
337
+ index("categories_api_mode_idx").on(table.apiMode)
338
+ ]);
339
+ var categoryRelations = relations(categories, ({ one, many }) => ({
340
+ owner: one(users, { fields: [categories.ownerId], references: [users.id] }),
341
+ folders: many(folders),
342
+ documents: many(documents)
343
+ }));
344
+ var documentTags = pgTable("document_tags", {
345
+ workspaceId: text("workspace_id"),
346
+ documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }),
347
+ tagId: uuid("tag_id").notNull().references(() => tags.id, { onDelete: "cascade" })
348
+ }, (table) => [
349
+ uniqueIndex("document_tags_unique_idx").on(table.documentId, table.tagId)
350
+ ]);
351
+ var documentTagRelations = relations(documentTags, ({ one }) => ({
352
+ document: one(documents, {
353
+ fields: [documentTags.documentId],
354
+ references: [documents.id]
355
+ }),
356
+ tag: one(tags, { fields: [documentTags.tagId], references: [tags.id] })
357
+ }));
358
+ var shareLinks = pgTable("share_links", {
359
+ id: uuid("id").primaryKey().defaultRandom(),
360
+ documentId: uuid("document_id").references(() => documents.id, {
361
+ onDelete: "cascade"
362
+ }),
363
+ folderId: uuid("folder_id").references(() => folders.id, {
364
+ onDelete: "cascade"
365
+ }),
366
+ categoryId: uuid("category_id").references(() => categories.id, {
367
+ onDelete: "cascade"
368
+ }),
369
+ token: text("token").notNull().unique(),
370
+ passwordHash: text("password_hash"),
371
+ role: shareRoleEnum("role").notNull().default("viewer"),
372
+ expiresAt: timestamp("expires_at"),
373
+ createdBy: uuid("created_by").notNull().references(() => users.id, { onDelete: "cascade" }),
374
+ workspaceId: text("workspace_id"),
375
+ createdAt: timestamp("created_at").defaultNow().notNull()
376
+ }, (table) => [
377
+ index("share_links_token_idx").on(table.token),
378
+ index("share_links_document_id_idx").on(table.documentId),
379
+ index("share_links_folder_id_idx").on(table.folderId),
380
+ index("share_links_category_id_idx").on(table.categoryId),
381
+ check("share_links_exactly_one_target_check", sql`num_nonnulls(${table.documentId}, ${table.folderId}, ${table.categoryId}) = 1`)
382
+ ]);
383
+ var shareLinkRelations = relations(shareLinks, ({ one, many }) => ({
384
+ document: one(documents, {
385
+ fields: [shareLinks.documentId],
386
+ references: [documents.id]
387
+ }),
388
+ folder: one(folders, {
389
+ fields: [shareLinks.folderId],
390
+ references: [folders.id]
391
+ }),
392
+ category: one(categories, {
393
+ fields: [shareLinks.categoryId],
394
+ references: [categories.id]
395
+ }),
396
+ creator: one(users, {
397
+ fields: [shareLinks.createdBy],
398
+ references: [users.id]
399
+ }),
400
+ guestAccess: many(guestAccess)
401
+ }));
402
+ var guestAccess = pgTable("guest_access", {
403
+ id: uuid("id").primaryKey().defaultRandom(),
404
+ shareLinkId: uuid("share_link_id").notNull().references(() => shareLinks.id, { onDelete: "cascade" }),
405
+ workspaceId: text("workspace_id"),
406
+ guestEmail: text("guest_email").notNull(),
407
+ grantedAt: timestamp("granted_at").defaultNow().notNull()
408
+ }, (table) => [index("guest_access_share_link_idx").on(table.shareLinkId)]);
409
+ var guestAccessRelations = relations(guestAccess, ({ one }) => ({
410
+ shareLink: one(shareLinks, {
411
+ fields: [guestAccess.shareLinkId],
412
+ references: [shareLinks.id]
413
+ })
414
+ }));
415
+ var attachments = pgTable("attachments", {
416
+ id: uuid("id").primaryKey().defaultRandom(),
417
+ documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }),
418
+ workspaceId: text("workspace_id"),
419
+ filename: text("filename").notNull(),
420
+ mimeType: text("mime_type").notNull(),
421
+ size: bigint("size", { mode: "number" }).notNull(),
422
+ storageKey: text("storage_key").notNull(),
423
+ createdAt: timestamp("created_at").defaultNow().notNull()
424
+ }, (table) => [index("attachments_document_id_idx").on(table.documentId)]);
425
+ var attachmentRelations = relations(attachments, ({ one }) => ({
426
+ document: one(documents, {
427
+ fields: [attachments.documentId],
428
+ references: [documents.id]
429
+ })
430
+ }));
431
+ var versions = pgTable("versions", {
432
+ id: uuid("id").primaryKey().defaultRandom(),
433
+ documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }),
434
+ workspaceId: text("workspace_id"),
435
+ content: text("content").notNull(),
436
+ contentJson: jsonb("content_json"),
437
+ createdBy: uuid("created_by").notNull().references(() => users.id, { onDelete: "cascade" }),
438
+ createdAt: timestamp("created_at").defaultNow().notNull(),
439
+ label: text("label"),
440
+ description: text("description"),
441
+ isSnapshot: boolean("is_snapshot").default(false),
442
+ restoredFrom: uuid("restored_from")
443
+ }, (table) => [
444
+ index("versions_document_id_idx").on(table.documentId),
445
+ index("versions_created_at_idx").on(table.createdAt),
446
+ index("versions_is_snapshot_idx").on(table.isSnapshot)
447
+ ]);
448
+ var documentEmbeddings = pgTable("document_embeddings", {
449
+ id: uuid("id").primaryKey().defaultRandom(),
450
+ documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }),
451
+ workspaceId: text("workspace_id"),
452
+ chunkIndex: bigint("chunk_index", { mode: "number" }).notNull(),
453
+ chunkText: text("chunk_text").notNull(),
454
+ chunkHash: text("chunk_hash"),
455
+ charStart: integer("char_start").notNull().default(0),
456
+ charEnd: integer("char_end").notNull().default(0),
457
+ embedding: vector("embedding", { dimensions: 1024 }),
458
+ embeddingModel: text("embedding_model").notNull().default(""),
459
+ generationId: uuid("generation_id").notNull(),
460
+ embeddingDimensions: integer("embedding_dimensions").notNull().default(1024),
461
+ embeddingProfile: text("embedding_profile").notNull().default("legacy"),
462
+ isValid: boolean("is_valid").notNull().default(false),
463
+ createdAt: timestamp("created_at").defaultNow().notNull()
464
+ }, (table) => [
465
+ index("document_embeddings_doc_id_idx").on(table.documentId),
466
+ uniqueIndex("document_embeddings_doc_chunk_idx").on(table.documentId, table.generationId, table.chunkIndex),
467
+ index("document_embeddings_generation_valid_idx").on(table.documentId, table.generationId, table.isValid),
468
+ index("idx_document_embeddings_embedding_model").on(table.embeddingModel),
469
+ index("idx_document_embeddings_hnsw").using("hnsw", sql`${table.embedding} vector_cosine_ops`),
470
+ index("idx_document_embeddings_diskann").using("diskann", sql`${table.embedding} vector_cosine_ops`)
471
+ ]);
472
+ var documentEmbeddingRelations = relations(documentEmbeddings, ({ one }) => ({
473
+ document: one(documents, {
474
+ fields: [documentEmbeddings.documentId],
475
+ references: [documents.id]
476
+ })
477
+ }));
478
+ var apiKeys = pgTable("api_keys", {
479
+ id: uuid("id").primaryKey().defaultRandom(),
480
+ ownerId: uuid("owner_id").notNull().references(() => users.id, { onDelete: "cascade" }),
481
+ workspaceId: text("workspace_id"),
482
+ name: text("name").notNull(),
483
+ keyHash: text("key_hash").notNull().unique(),
484
+ prefix: text("prefix").notNull(),
485
+ encryptedKey: text("encrypted_key"),
486
+ scopes: jsonb("scopes").notNull().default("[]"),
487
+ lastUsedAt: timestamp("last_used_at"),
488
+ expiresAt: timestamp("expires_at"),
489
+ createdAt: timestamp("created_at").defaultNow().notNull()
490
+ }, (table) => [
491
+ index("idx_api_keys_owner").on(table.ownerId),
492
+ index("idx_api_keys_prefix").on(table.prefix)
493
+ ]);
494
+ var apiKeyRelations = relations(apiKeys, ({ one }) => ({
495
+ owner: one(users, { fields: [apiKeys.ownerId], references: [users.id] })
496
+ }));
497
+ var auditLog = pgTable("audit_log", {
498
+ id: uuid("id").primaryKey().defaultRandom(),
499
+ actorId: uuid("actor_id").notNull(),
500
+ workspaceId: text("workspace_id"),
501
+ action: text("action").notNull(),
502
+ resourceType: text("resource_type").notNull(),
503
+ resourceId: uuid("resource_id"),
504
+ details: jsonb("details").notNull().default("{}"),
505
+ ipAddress: text("ip_address"),
506
+ userAgent: text("user_agent"),
507
+ createdAt: timestamp("created_at").defaultNow().notNull()
508
+ }, (table) => [
509
+ index("idx_audit_log_actor").on(table.actorId),
510
+ index("idx_audit_log_resource").on(table.resourceType, table.resourceId),
511
+ index("idx_audit_log_created").on(table.createdAt)
512
+ ]);
513
+ var lifecycleOperations = pgTable("lifecycle_operations", {
514
+ id: uuid("id").primaryKey().defaultRandom(),
515
+ actorUserId: uuid("actor_user_id").references(() => users.id, { onDelete: "set null" }),
516
+ actorSubjectHash: text("actor_subject_hash").notNull(),
517
+ idempotencyKey: text("idempotency_key").notNull(),
518
+ operationKind: lifecycleOperationKindEnum("operation_kind").notNull(),
519
+ status: lifecycleOperationStatusEnum("status").notNull().default("pending"),
520
+ leaseOwner: text("lease_owner"),
521
+ leaseExpiresAt: timestamp("lease_expires_at"),
522
+ fenceTokenHash: text("fence_token_hash"),
523
+ completedSteps: jsonb("completed_steps").notNull().default("[]"),
524
+ terminalResult: jsonb("terminal_result"),
525
+ safeErrorCode: text("safe_error_code"),
526
+ attemptCount: integer("attempt_count").notNull().default(0),
527
+ createdAt: timestamp("created_at").defaultNow().notNull(),
528
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
529
+ completedAt: timestamp("completed_at")
530
+ }, (table) => [
531
+ uniqueIndex("lifecycle_operations_actor_idempotency_idx").on(table.actorUserId, table.idempotencyKey),
532
+ index("lifecycle_operations_status_lease_idx").on(table.status, table.leaseExpiresAt),
533
+ index("lifecycle_operations_actor_idx").on(table.actorUserId),
534
+ index("lifecycle_operations_retryable_idx").on(table.status).where(sql`${table.status} = 'retryable'`),
535
+ check("lifecycle_operations_actor_subject_hash", sql`${table.actorSubjectHash} ~ '^[a-f0-9]{64}$'`)
536
+ ]);
537
+ var documentCreateOperations = pgTable("document_create_operations", {
538
+ id: uuid("id").primaryKey().defaultRandom(),
539
+ workspaceId: text("workspace_id").notNull(),
540
+ actorUserId: uuid("actor_user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
541
+ idempotencyKey: text("idempotency_key").notNull(),
542
+ documentId: uuid("document_id").notNull().references(() => documents.id, { onDelete: "cascade" }),
543
+ createdAt: timestamp("created_at").defaultNow().notNull()
544
+ }, (table) => [
545
+ uniqueIndex("document_create_operations_workspace_actor_key_idx").on(table.workspaceId, table.actorUserId, table.idempotencyKey),
546
+ uniqueIndex("document_create_operations_document_idx").on(table.documentId)
547
+ ]);
548
+ var versionRelations = relations(versions, ({ one }) => ({
549
+ document: one(documents, {
550
+ fields: [versions.documentId],
551
+ references: [documents.id]
552
+ }),
553
+ creator: one(users, {
554
+ fields: [versions.createdBy],
555
+ references: [users.id]
556
+ })
557
+ }));
558
+
559
+ // ../db/src/with-tenant.ts
560
+ import { sql as sql2 } from "drizzle-orm";
561
+
562
+ // ../db/src/client.ts
563
+ import { drizzle } from "drizzle-orm/postgres-js";
564
+ import postgres from "postgres";
565
+ var databaseUrl = process.env.DATABASE_URL || "postgresql://hiai_app:changeme@localhost:5437/hiai_docs";
566
+ var client = postgres(databaseUrl, {
567
+ max: 20,
568
+ idle_timeout: 30,
569
+ connect_timeout: 10
570
+ });
571
+ var db = drizzle(client, { schema: exports_schema });
572
+
573
+ // ../db/src/with-tenant.ts
574
+ var ZERO_UUID = "00000000-0000-0000-0000-000000000000";
575
+ function adminTenantContext(ownerId) {
576
+ const resolved = ownerId ?? process.env.OWNER_ID;
577
+ if (!resolved) {
578
+ console.warn("[hiai-docs/db] adminTenantContext: ownerId not provided and OWNER_ID env not set, using empty string");
579
+ }
580
+ return {
581
+ userId: resolved ?? "",
582
+ role: "admin"
583
+ };
584
+ }
585
+ async function withTenant(ctx, fn) {
586
+ return db.transaction(async (tx) => {
587
+ await tx.execute(sql2`SELECT set_config('app.current_user_id', ${ctx.userId}, true)`);
588
+ await tx.execute(sql2`SELECT set_config('app.current_user_role', ${ctx.role}, true)`);
589
+ await tx.execute(sql2`SELECT set_config('app.current_workspace_id', ${ctx.workspaceId ?? ""}, true)`);
590
+ return fn(tx);
591
+ });
592
+ }
593
+
594
+ // ../../backend/src/lib/api-keys.ts
595
+ import { and, desc, eq } from "drizzle-orm";
596
+
597
+ // ../../backend/src/lib/api-key-encryption.ts
598
+ var VERSION = "v1";
599
+ var AAD = new TextEncoder().encode("hiai-docs:category-api-key:v1");
600
+ function bytesToBase64(bytes) {
601
+ let binary = "";
602
+ for (const byte of bytes)
603
+ binary += String.fromCharCode(byte);
604
+ return btoa(binary);
605
+ }
606
+ function base64ToBytes(value) {
607
+ const binary = atob(value);
608
+ const bytes = new Uint8Array(new ArrayBuffer(binary.length));
609
+ for (let index2 = 0;index2 < binary.length; index2 += 1) {
610
+ bytes[index2] = binary.charCodeAt(index2);
611
+ }
612
+ return bytes;
613
+ }
614
+ async function importEncryptionKey(secret) {
615
+ if (secret.length < 32) {
616
+ throw new Error("API_KEY_ENCRYPTION_SECRET must be at least 32 characters");
617
+ }
618
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(secret));
619
+ return crypto.subtle.importKey("raw", digest, "AES-GCM", false, [
620
+ "encrypt",
621
+ "decrypt"
622
+ ]);
623
+ }
624
+ async function encryptApiKey(rawKey, secret) {
625
+ const iv = crypto.getRandomValues(new Uint8Array(12));
626
+ const key = await importEncryptionKey(secret);
627
+ const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData: AAD }, key, new TextEncoder().encode(rawKey));
628
+ return `${VERSION}.${bytesToBase64(iv)}.${bytesToBase64(new Uint8Array(ciphertext))}`;
629
+ }
630
+ async function decryptApiKey(payload, secret) {
631
+ const [version, encodedIv, encodedCiphertext] = payload.split(".");
632
+ if (version !== VERSION || !encodedIv || !encodedCiphertext) {
633
+ throw new Error("Unsupported encrypted API key payload");
634
+ }
635
+ const key = await importEncryptionKey(secret);
636
+ const plaintext = await crypto.subtle.decrypt({
637
+ name: "AES-GCM",
638
+ iv: base64ToBytes(encodedIv),
639
+ additionalData: AAD
640
+ }, key, base64ToBytes(encodedCiphertext));
641
+ return new TextDecoder().decode(plaintext);
642
+ }
643
+
644
+ // ../../backend/src/lib/api-keys.ts
645
+ var API_KEY_ADMIN_TENANT = adminTenantContext(ZERO_UUID);
646
+ var GLOBAL_API_SCOPE = "global";
647
+ var CATEGORY_SCOPE_PATTERN = /^category:([0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}):(read|edit|write)$/;
648
+ function parseApiKeyScopes(value) {
649
+ if (!Array.isArray(value) || value.length === 0)
650
+ return null;
651
+ const scopes = [];
652
+ const seen = new Set;
653
+ for (const candidate of value) {
654
+ if (typeof candidate !== "string" || seen.has(candidate))
655
+ return null;
656
+ if (candidate !== GLOBAL_API_SCOPE && !CATEGORY_SCOPE_PATTERN.test(candidate)) {
657
+ return null;
658
+ }
659
+ seen.add(candidate);
660
+ scopes.push(candidate);
661
+ }
662
+ return scopes;
663
+ }
664
+ function buildCategoryApiKeyScopes(categoryId, permissions) {
665
+ return ["read", "edit", "write"].filter((permission) => permissions[permission]).map((permission) => `category:${categoryId}:${permission}`);
666
+ }
667
+ function categoryIdFromApiKeyScopes(scopes) {
668
+ const parsed = parseApiKeyScopes(scopes);
669
+ if (!parsed)
670
+ return null;
671
+ for (const scope of parsed) {
672
+ const match = CATEGORY_SCOPE_PATTERN.exec(scope);
673
+ if (match?.[1])
674
+ return match[1];
675
+ }
676
+ return null;
677
+ }
678
+ function hashKey(key) {
679
+ const hasher = new Bun.CryptoHasher("sha256");
680
+ hasher.update(key);
681
+ return hasher.digest("hex");
682
+ }
683
+ async function createApiKey(ownerId, name, scopes, expiresAt, options) {
684
+ const rawKey = crypto.randomUUID();
685
+ const keyHash = hashKey(rawKey);
686
+ const prefix = rawKey.slice(0, 8);
687
+ const encryptedKey = options?.encryptionSecret ? await encryptApiKey(rawKey, options.encryptionSecret) : null;
688
+ const [row] = await withTenant({ userId: ownerId, role: "user" }, (tx) => tx.insert(apiKeys).values({
689
+ ownerId,
690
+ name,
691
+ keyHash,
692
+ prefix,
693
+ scopes,
694
+ expiresAt: expiresAt ?? null,
695
+ encryptedKey
696
+ }).returning({ id: apiKeys.id }));
697
+ if (!row) {
698
+ throw new Error("Failed to create API key");
699
+ }
700
+ return { key: rawKey, prefix, id: row.id };
701
+ }
702
+ async function revealCategoryApiKey(id, ownerId, encryptionSecret) {
703
+ const [row] = await withTenant({ userId: ownerId, role: "user" }, (tx) => tx.select({
704
+ encryptedKey: apiKeys.encryptedKey,
705
+ scopes: apiKeys.scopes
706
+ }).from(apiKeys).where(and(eq(apiKeys.id, id), eq(apiKeys.ownerId, ownerId))).limit(1));
707
+ if (!row?.encryptedKey)
708
+ return null;
709
+ const scopes = row.scopes ?? [];
710
+ if (!categoryIdFromApiKeyScopes(scopes))
711
+ return null;
712
+ return decryptApiKey(row.encryptedKey, encryptionSecret);
713
+ }
714
+ async function listApiKeys(ownerId) {
715
+ const rows = await withTenant({ userId: ownerId, role: "user" }, (tx) => tx.select({
716
+ id: apiKeys.id,
717
+ name: apiKeys.name,
718
+ prefix: apiKeys.prefix,
719
+ scopes: apiKeys.scopes,
720
+ lastUsedAt: apiKeys.lastUsedAt,
721
+ expiresAt: apiKeys.expiresAt,
722
+ createdAt: apiKeys.createdAt,
723
+ encryptedKey: apiKeys.encryptedKey
724
+ }).from(apiKeys).where(eq(apiKeys.ownerId, ownerId)).orderBy(desc(apiKeys.createdAt)));
725
+ return rows.map((r) => ({
726
+ id: r.id,
727
+ name: r.name,
728
+ prefix: r.prefix,
729
+ lastUsedAt: r.lastUsedAt,
730
+ expiresAt: r.expiresAt,
731
+ createdAt: r.createdAt,
732
+ recoverable: r.encryptedKey !== null,
733
+ scopes: r.scopes ?? []
734
+ }));
735
+ }
736
+ async function revokeApiKey(id, ownerId) {
737
+ const deleted = await withTenant({ userId: ownerId, role: "user" }, (tx) => tx.delete(apiKeys).where(and(eq(apiKeys.id, id), eq(apiKeys.ownerId, ownerId))).returning({ id: apiKeys.id }));
738
+ return deleted.length > 0;
739
+ }
740
+ async function validateApiKey(key) {
741
+ const keyHash = hashKey(key);
742
+ const [row] = await withTenant(API_KEY_ADMIN_TENANT, (tx) => tx.select({
743
+ id: apiKeys.id,
744
+ ownerId: apiKeys.ownerId,
745
+ scopes: apiKeys.scopes,
746
+ expiresAt: apiKeys.expiresAt
747
+ }).from(apiKeys).where(eq(apiKeys.keyHash, keyHash)).limit(1));
748
+ if (!row) {
749
+ return null;
750
+ }
751
+ if (row.expiresAt && new Date(row.expiresAt) < new Date) {
752
+ return null;
753
+ }
754
+ const scopes = parseApiKeyScopes(row.scopes ?? []);
755
+ if (!scopes)
756
+ return null;
757
+ await withTenant(API_KEY_ADMIN_TENANT, (tx) => tx.update(apiKeys).set({ lastUsedAt: new Date }).where(eq(apiKeys.id, row.id)));
758
+ return {
759
+ id: row.id,
760
+ ownerId: row.ownerId,
761
+ scopes
762
+ };
763
+ }
764
+ export {
765
+ validateApiKey as verifyApiKey,
766
+ revokeApiKey,
767
+ revealCategoryApiKey,
768
+ listApiKeys,
769
+ createApiKey as issueApiKey,
770
+ buildCategoryApiKeyScopes,
771
+ GLOBAL_API_SCOPE
772
+ };