@intelligo-dev/core 1.0.0-beta.1 → 1.0.0-beta.13

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 (355) hide show
  1. package/NOTICE +6 -0
  2. package/README.md +102 -0
  3. package/dist/attachments/errors.d.ts +7 -0
  4. package/dist/attachments/errors.d.ts.map +1 -0
  5. package/dist/attachments/errors.js +11 -0
  6. package/dist/attachments/errors.js.map +1 -0
  7. package/dist/attachments/index.d.ts +8 -0
  8. package/dist/attachments/index.d.ts.map +1 -0
  9. package/dist/attachments/index.js +7 -0
  10. package/dist/attachments/index.js.map +1 -0
  11. package/dist/attachments/service.d.ts +45 -0
  12. package/dist/attachments/service.d.ts.map +1 -0
  13. package/dist/attachments/service.js +100 -0
  14. package/dist/attachments/service.js.map +1 -0
  15. package/dist/attachments/types.d.ts +6 -0
  16. package/dist/attachments/types.d.ts.map +1 -0
  17. package/dist/attachments/types.js +2 -0
  18. package/dist/attachments/types.js.map +1 -0
  19. package/dist/conversations/errors.d.ts +2 -8
  20. package/dist/conversations/errors.d.ts.map +1 -1
  21. package/dist/conversations/errors.js +2 -8
  22. package/dist/conversations/errors.js.map +1 -1
  23. package/dist/conversations/index.d.ts +4 -12
  24. package/dist/conversations/index.d.ts.map +1 -1
  25. package/dist/conversations/index.js +2 -11
  26. package/dist/conversations/index.js.map +1 -1
  27. package/dist/conversations/service.d.ts +49 -61
  28. package/dist/conversations/service.d.ts.map +1 -1
  29. package/dist/conversations/service.js +110 -83
  30. package/dist/conversations/service.js.map +1 -1
  31. package/dist/conversations/types.d.ts +1 -4
  32. package/dist/conversations/types.d.ts.map +1 -1
  33. package/dist/conversations/types.js +0 -3
  34. package/dist/conversations/types.js.map +1 -1
  35. package/dist/db/client.d.ts +21 -10
  36. package/dist/db/client.d.ts.map +1 -1
  37. package/dist/db/client.js +43 -18
  38. package/dist/db/client.js.map +1 -1
  39. package/dist/db/index.d.ts +3 -3
  40. package/dist/db/index.d.ts.map +1 -1
  41. package/dist/db/index.js +3 -7
  42. package/dist/db/index.js.map +1 -1
  43. package/dist/db/schema/agents.d.ts +14 -51
  44. package/dist/db/schema/agents.d.ts.map +1 -1
  45. package/dist/db/schema/agents.js +9 -21
  46. package/dist/db/schema/agents.js.map +1 -1
  47. package/dist/db/schema/ai.d.ts +86 -1556
  48. package/dist/db/schema/ai.d.ts.map +1 -1
  49. package/dist/db/schema/ai.js +32 -236
  50. package/dist/db/schema/ai.js.map +1 -1
  51. package/dist/db/schema/auth.d.ts +5 -34
  52. package/dist/db/schema/auth.d.ts.map +1 -1
  53. package/dist/db/schema/auth.js +25 -56
  54. package/dist/db/schema/auth.js.map +1 -1
  55. package/dist/db/schema/billing.d.ts +136 -107
  56. package/dist/db/schema/billing.d.ts.map +1 -1
  57. package/dist/db/schema/billing.js +72 -60
  58. package/dist/db/schema/billing.js.map +1 -1
  59. package/dist/db/schema/feature-flags.d.ts +3 -12
  60. package/dist/db/schema/feature-flags.d.ts.map +1 -1
  61. package/dist/db/schema/feature-flags.js +3 -12
  62. package/dist/db/schema/feature-flags.js.map +1 -1
  63. package/dist/db/schema/identity.d.ts +12 -274
  64. package/dist/db/schema/identity.d.ts.map +1 -1
  65. package/dist/db/schema/identity.js +19 -104
  66. package/dist/db/schema/identity.js.map +1 -1
  67. package/dist/db/schema/index.d.ts +8 -15
  68. package/dist/db/schema/index.d.ts.map +1 -1
  69. package/dist/db/schema/index.js +12 -42
  70. package/dist/db/schema/index.js.map +1 -1
  71. package/dist/db/schema/notifications.d.ts +3 -17
  72. package/dist/db/schema/notifications.d.ts.map +1 -1
  73. package/dist/db/schema/notifications.js +3 -17
  74. package/dist/db/schema/notifications.js.map +1 -1
  75. package/dist/db/schema/usage.d.ts +94 -77
  76. package/dist/db/schema/usage.d.ts.map +1 -1
  77. package/dist/db/schema/usage.js +59 -89
  78. package/dist/db/schema/usage.js.map +1 -1
  79. package/dist/db/validate.d.ts +2 -11
  80. package/dist/db/validate.d.ts.map +1 -1
  81. package/dist/db/validate.js +6 -17
  82. package/dist/db/validate.js.map +1 -1
  83. package/dist/db/workspace-queries.d.ts +5 -29
  84. package/dist/db/workspace-queries.d.ts.map +1 -1
  85. package/dist/db/workspace-queries.js +5 -29
  86. package/dist/db/workspace-queries.js.map +1 -1
  87. package/dist/documents/classifier.d.ts +5 -12
  88. package/dist/documents/classifier.d.ts.map +1 -1
  89. package/dist/documents/classifier.js +16 -16
  90. package/dist/documents/classifier.js.map +1 -1
  91. package/dist/documents/errors.d.ts +2 -10
  92. package/dist/documents/errors.d.ts.map +1 -1
  93. package/dist/documents/errors.js +2 -10
  94. package/dist/documents/errors.js.map +1 -1
  95. package/dist/documents/index.d.ts +4 -13
  96. package/dist/documents/index.d.ts.map +1 -1
  97. package/dist/documents/index.js +3 -12
  98. package/dist/documents/index.js.map +1 -1
  99. package/dist/documents/service.d.ts +19 -26
  100. package/dist/documents/service.d.ts.map +1 -1
  101. package/dist/documents/service.js +44 -38
  102. package/dist/documents/service.js.map +1 -1
  103. package/dist/documents/types.d.ts +6 -10
  104. package/dist/documents/types.d.ts.map +1 -1
  105. package/dist/documents/types.js +0 -3
  106. package/dist/documents/types.js.map +1 -1
  107. package/dist/email/brand.d.ts +17 -0
  108. package/dist/email/brand.d.ts.map +1 -0
  109. package/dist/email/brand.js +9 -0
  110. package/dist/email/brand.js.map +1 -0
  111. package/dist/email/index.d.ts +4 -4
  112. package/dist/email/index.js +4 -4
  113. package/dist/email/provider.d.ts +1 -7
  114. package/dist/email/provider.d.ts.map +1 -1
  115. package/dist/email/provider.js +10 -19
  116. package/dist/email/provider.js.map +1 -1
  117. package/dist/email/send.d.ts +12 -12
  118. package/dist/email/send.d.ts.map +1 -1
  119. package/dist/email/send.js +5 -19
  120. package/dist/email/send.js.map +1 -1
  121. package/dist/email/senders.d.ts +17 -30
  122. package/dist/email/senders.d.ts.map +1 -1
  123. package/dist/email/senders.js +33 -47
  124. package/dist/email/senders.js.map +1 -1
  125. package/dist/email/templates/base-layout.d.ts +1 -1
  126. package/dist/email/templates/base-layout.d.ts.map +1 -1
  127. package/dist/email/templates/base-layout.js +4 -6
  128. package/dist/email/templates/base-layout.js.map +1 -1
  129. package/dist/email/templates/index.d.ts +10 -10
  130. package/dist/email/templates/index.d.ts.map +1 -1
  131. package/dist/email/templates/index.js +10 -16
  132. package/dist/email/templates/index.js.map +1 -1
  133. package/dist/email/templates/password-reset.d.ts +2 -2
  134. package/dist/email/templates/password-reset.d.ts.map +1 -1
  135. package/dist/email/templates/password-reset.js +10 -22
  136. package/dist/email/templates/password-reset.js.map +1 -1
  137. package/dist/email/templates/payment-failed.d.ts +2 -2
  138. package/dist/email/templates/payment-failed.d.ts.map +1 -1
  139. package/dist/email/templates/payment-failed.js +11 -24
  140. package/dist/email/templates/payment-failed.js.map +1 -1
  141. package/dist/email/templates/quota-warning.d.ts +4 -4
  142. package/dist/email/templates/quota-warning.d.ts.map +1 -1
  143. package/dist/email/templates/quota-warning.js +19 -39
  144. package/dist/email/templates/quota-warning.js.map +1 -1
  145. package/dist/email/templates/subscription-confirmed.d.ts +2 -2
  146. package/dist/email/templates/subscription-confirmed.d.ts.map +1 -1
  147. package/dist/email/templates/subscription-confirmed.js +13 -28
  148. package/dist/email/templates/subscription-confirmed.js.map +1 -1
  149. package/dist/email/templates/trial-expiry.d.ts +2 -2
  150. package/dist/email/templates/trial-expiry.d.ts.map +1 -1
  151. package/dist/email/templates/trial-expiry.js +12 -36
  152. package/dist/email/templates/trial-expiry.js.map +1 -1
  153. package/dist/email/templates/trial-warning.d.ts +2 -2
  154. package/dist/email/templates/trial-warning.d.ts.map +1 -1
  155. package/dist/email/templates/trial-warning.js +15 -32
  156. package/dist/email/templates/trial-warning.js.map +1 -1
  157. package/dist/email/templates/verify-email.d.ts +2 -2
  158. package/dist/email/templates/verify-email.d.ts.map +1 -1
  159. package/dist/email/templates/verify-email.js +10 -22
  160. package/dist/email/templates/verify-email.js.map +1 -1
  161. package/dist/email/templates/welcome.d.ts +2 -2
  162. package/dist/email/templates/welcome.d.ts.map +1 -1
  163. package/dist/email/templates/welcome.js +13 -20
  164. package/dist/email/templates/welcome.js.map +1 -1
  165. package/dist/email/templates/workspace-invitation.d.ts +2 -2
  166. package/dist/email/templates/workspace-invitation.d.ts.map +1 -1
  167. package/dist/email/templates/workspace-invitation.js +12 -26
  168. package/dist/email/templates/workspace-invitation.js.map +1 -1
  169. package/dist/env.d.ts +5 -10
  170. package/dist/env.d.ts.map +1 -1
  171. package/dist/env.js +41 -31
  172. package/dist/env.js.map +1 -1
  173. package/dist/identity/audit.d.ts +11 -24
  174. package/dist/identity/audit.d.ts.map +1 -1
  175. package/dist/identity/audit.js +13 -27
  176. package/dist/identity/audit.js.map +1 -1
  177. package/dist/identity/errors.d.ts +4 -12
  178. package/dist/identity/errors.d.ts.map +1 -1
  179. package/dist/identity/errors.js +2 -8
  180. package/dist/identity/errors.js.map +1 -1
  181. package/dist/identity/index.d.ts +5 -13
  182. package/dist/identity/index.d.ts.map +1 -1
  183. package/dist/identity/index.js +4 -12
  184. package/dist/identity/index.js.map +1 -1
  185. package/dist/identity/service.d.ts +13 -47
  186. package/dist/identity/service.d.ts.map +1 -1
  187. package/dist/identity/service.js +31 -78
  188. package/dist/identity/service.js.map +1 -1
  189. package/dist/identity/types.d.ts +1 -4
  190. package/dist/identity/types.d.ts.map +1 -1
  191. package/dist/identity/types.js +0 -3
  192. package/dist/identity/types.js.map +1 -1
  193. package/dist/index.d.ts +1 -2
  194. package/dist/index.d.ts.map +1 -1
  195. package/dist/index.js +3 -10
  196. package/dist/index.js.map +1 -1
  197. package/dist/logger.d.ts +7 -18
  198. package/dist/logger.d.ts.map +1 -1
  199. package/dist/logger.js +45 -49
  200. package/dist/logger.js.map +1 -1
  201. package/dist/money.d.ts +95 -0
  202. package/dist/money.d.ts.map +1 -0
  203. package/dist/money.js +208 -0
  204. package/dist/money.js.map +1 -0
  205. package/dist/notifications/index.d.ts +9 -43
  206. package/dist/notifications/index.d.ts.map +1 -1
  207. package/dist/notifications/index.js +9 -49
  208. package/dist/notifications/index.js.map +1 -1
  209. package/dist/notifications/triggers.d.ts +9 -32
  210. package/dist/notifications/triggers.d.ts.map +1 -1
  211. package/dist/notifications/triggers.js +21 -58
  212. package/dist/notifications/triggers.js.map +1 -1
  213. package/dist/notifications/types.d.ts +1 -10
  214. package/dist/notifications/types.d.ts.map +1 -1
  215. package/dist/notifications/types.js +0 -9
  216. package/dist/notifications/types.js.map +1 -1
  217. package/dist/prompt.d.ts +38 -0
  218. package/dist/prompt.d.ts.map +1 -0
  219. package/dist/prompt.js +118 -0
  220. package/dist/prompt.js.map +1 -0
  221. package/dist/registry.d.ts +32 -0
  222. package/dist/registry.d.ts.map +1 -0
  223. package/dist/registry.js +60 -0
  224. package/dist/registry.js.map +1 -0
  225. package/dist/request-context.d.ts +49 -0
  226. package/dist/request-context.d.ts.map +1 -0
  227. package/dist/request-context.js +100 -0
  228. package/dist/request-context.js.map +1 -0
  229. package/dist/storage/index.d.ts +65 -0
  230. package/dist/storage/index.d.ts.map +1 -0
  231. package/dist/storage/index.js +109 -0
  232. package/dist/storage/index.js.map +1 -0
  233. package/package.json +74 -24
  234. package/src/attachments/errors.ts +18 -0
  235. package/src/attachments/index.ts +22 -0
  236. package/src/attachments/service.ts +178 -0
  237. package/src/attachments/types.ts +6 -0
  238. package/src/conversations/errors.ts +45 -0
  239. package/src/conversations/index.ts +39 -0
  240. package/src/conversations/service.ts +594 -0
  241. package/src/conversations/types.ts +15 -0
  242. package/src/db/client.ts +91 -0
  243. package/src/db/index.ts +3 -0
  244. package/src/db/migrations/0000_baseline.sql +652 -0
  245. package/src/db/migrations/0001_reconcile_chain_built_databases.sql +53 -0
  246. package/src/db/migrations/0002_webhook_claim_lease.sql +1 -0
  247. package/src/db/migrations/README.md +74 -36
  248. package/src/db/migrations/legacy-chain.json +197 -0
  249. package/src/db/migrations/meta/0000_snapshot.json +4182 -157
  250. package/src/db/migrations/meta/0002_snapshot.json +2478 -368
  251. package/src/db/migrations/meta/_journal.json +6 -279
  252. package/src/db/schema/agents.ts +57 -0
  253. package/src/db/schema/ai.ts +174 -0
  254. package/src/db/schema/auth.ts +183 -0
  255. package/src/db/schema/billing.ts +209 -0
  256. package/src/db/schema/feature-flags.ts +20 -0
  257. package/src/db/schema/identity.ts +250 -0
  258. package/src/db/schema/index.ts +12 -0
  259. package/src/db/schema/notifications.ts +31 -0
  260. package/src/db/schema/usage.ts +269 -0
  261. package/src/db/validate.ts +63 -0
  262. package/src/db/workspace-queries.ts +64 -0
  263. package/src/documents/classifier.ts +61 -0
  264. package/src/documents/errors.ts +44 -0
  265. package/src/documents/index.ts +24 -0
  266. package/src/documents/service.ts +256 -0
  267. package/src/documents/types.ts +24 -0
  268. package/src/email/brand.ts +24 -0
  269. package/src/email/index.ts +19 -0
  270. package/src/email/provider.ts +278 -0
  271. package/src/email/send.ts +169 -0
  272. package/src/email/senders.ts +292 -0
  273. package/src/email/templates/base-layout.tsx +145 -0
  274. package/src/email/templates/index.ts +28 -0
  275. package/src/email/templates/password-reset.tsx +72 -0
  276. package/src/email/templates/payment-failed.tsx +77 -0
  277. package/src/email/templates/quota-warning.tsx +118 -0
  278. package/src/email/templates/subscription-confirmed.tsx +91 -0
  279. package/src/email/templates/trial-expiry.tsx +124 -0
  280. package/src/email/templates/trial-warning.tsx +104 -0
  281. package/src/email/templates/verify-email.tsx +72 -0
  282. package/src/email/templates/welcome.tsx +64 -0
  283. package/src/email/templates/workspace-invitation.tsx +105 -0
  284. package/src/env.ts +180 -0
  285. package/src/identity/audit.ts +65 -0
  286. package/src/identity/errors.ts +44 -0
  287. package/src/identity/index.ts +26 -0
  288. package/src/identity/service.ts +202 -0
  289. package/src/identity/types.ts +28 -0
  290. package/src/index.ts +4 -0
  291. package/src/logger.ts +198 -0
  292. package/src/money.ts +270 -0
  293. package/src/notifications/index.ts +108 -0
  294. package/src/notifications/triggers.ts +158 -0
  295. package/src/notifications/types.ts +18 -0
  296. package/src/prompt.ts +142 -0
  297. package/src/registry.ts +75 -0
  298. package/src/request-context.ts +137 -0
  299. package/src/storage/index.ts +153 -0
  300. package/dist/db/schema/rag.d.ts +0 -171
  301. package/dist/db/schema/rag.d.ts.map +0 -1
  302. package/dist/db/schema/rag.js +0 -42
  303. package/dist/db/schema/rag.js.map +0 -1
  304. package/src/db/migrations/0000_sharp_night_nurse.sql +0 -52
  305. package/src/db/migrations/0001_hesitant_vapor.sql +0 -40
  306. package/src/db/migrations/0002_numerous_lady_vermin.sql +0 -235
  307. package/src/db/migrations/0003_mighty_jean_grey.sql +0 -37
  308. package/src/db/migrations/0004_cynical_grandmaster.sql +0 -2
  309. package/src/db/migrations/0005_careful_switch.sql +0 -8
  310. package/src/db/migrations/0006_tense_blue_marvel.sql +0 -16
  311. package/src/db/migrations/0007_next_squadron_sinister.sql +0 -1
  312. package/src/db/migrations/0008_powerful_scream.sql +0 -39
  313. package/src/db/migrations/0009_sad_ink.sql +0 -1
  314. package/src/db/migrations/0010_shocking_omega_flight.sql +0 -2
  315. package/src/db/migrations/0011_ambiguous_otto_octavius.sql +0 -151
  316. package/src/db/migrations/0011_hnsw_vector_index.sql +0 -17
  317. package/src/db/migrations/0012_user_profiles.sql +0 -30
  318. package/src/db/migrations/0013_trending_seasonal_columns.sql +0 -9
  319. package/src/db/migrations/0014_add_product_type_to_agents.sql +0 -6
  320. package/src/db/migrations/0015_new_v1_tables.sql +0 -149
  321. package/src/db/migrations/0016_document_types.sql +0 -30
  322. package/src/db/migrations/0017_document_metadata.sql +0 -2
  323. package/src/db/migrations/0018_identity_graph.sql +0 -200
  324. package/src/db/migrations/0019_db_driven_prompts.sql +0 -20
  325. package/src/db/migrations/0020_pending_extractions.sql +0 -35
  326. package/src/db/migrations/0021_rag_documents.sql +0 -39
  327. package/src/db/migrations/0022_user_quotas_usage_jsonb.sql +0 -70
  328. package/src/db/migrations/0023_user_facts_user_scoped_unique.sql +0 -17
  329. package/src/db/migrations/0024_user_memories_hnsw.sql +0 -23
  330. package/src/db/migrations/0025_audit_trigger_errcode.sql +0 -21
  331. package/src/db/migrations/0026_user_profile_snapshots_composite_pk.sql +0 -17
  332. package/src/db/migrations/0027_billing_settings_mnt_columns.sql +0 -54
  333. package/src/db/migrations/0028_trial_credits_normalized_email.sql +0 -37
  334. package/src/db/migrations/0029_usage_records_conversation_idx.sql +0 -13
  335. package/src/db/migrations/0030_pending_extractions_composite_idx.sql +0 -11
  336. package/src/db/migrations/0031_rag_documents_hnsw.sql +0 -16
  337. package/src/db/migrations/0032_rate_limit_unique_idx.sql +0 -16
  338. package/src/db/migrations/0033_pending_extractions_partial_idx.sql +0 -18
  339. package/src/db/migrations/0034_rate_limit_count.sql +0 -9
  340. package/src/db/migrations/0035_credit_reservations.sql +0 -24
  341. package/src/db/migrations/0036_executions_audit_jobs.sql +0 -96
  342. package/src/db/migrations/0037_rate_limit_minute_bucket.sql +0 -46
  343. package/src/db/migrations/0038_user_quotas_generic_usage.sql +0 -25
  344. package/src/db/migrations/0039_platform_admin_impersonation.sql +0 -27
  345. package/src/db/migrations/0040_ee_audit_chain.sql +0 -23
  346. package/src/db/migrations/meta/0001_snapshot.json +0 -631
  347. package/src/db/migrations/meta/0003_snapshot.json +0 -2488
  348. package/src/db/migrations/meta/0004_snapshot.json +0 -2500
  349. package/src/db/migrations/meta/0005_snapshot.json +0 -2548
  350. package/src/db/migrations/meta/0006_snapshot.json +0 -2650
  351. package/src/db/migrations/meta/0007_snapshot.json +0 -2657
  352. package/src/db/migrations/meta/0008_snapshot.json +0 -2719
  353. package/src/db/migrations/meta/0009_snapshot.json +0 -2726
  354. package/src/db/migrations/meta/0010_snapshot.json +0 -2739
  355. package/src/db/migrations/meta/0011_snapshot.json +0 -3886
@@ -0,0 +1,31 @@
1
+ import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core";
2
+
3
+ /**
4
+ * User-scoped, not workspace-scoped, so a user sees their notifications from
5
+ * every workspace in one place. `userId` references the users table by
6
+ * convention (no FK); `workspaceId` is null for user-level notifications.
7
+ */
8
+ export const notifications = pgTable(
9
+ "notifications",
10
+ {
11
+ id: text("id")
12
+ .primaryKey()
13
+ .$defaultFn(() => crypto.randomUUID()),
14
+ userId: text("user_id").notNull(),
15
+ workspaceId: text("workspace_id"),
16
+ type: text("type").notNull(),
17
+ title: text("title").notNull(),
18
+ message: text("message").notNull(),
19
+ isRead: boolean("is_read").notNull().default(false),
20
+ metadata: text("metadata"),
21
+ createdAt: timestamp("created_at").notNull().defaultNow(),
22
+ },
23
+ (table) => [
24
+ index("notifications_user_id_idx").on(table.userId),
25
+ index("notifications_user_id_is_read_idx").on(table.userId, table.isRead),
26
+ index("notifications_created_at_idx").on(table.createdAt),
27
+ ]
28
+ );
29
+
30
+ export type Notification = typeof notifications.$inferSelect;
31
+ export type InsertNotification = typeof notifications.$inferInsert;
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Token consumption per request and aggregated per period, which the quota
3
+ * engine checks before AI requests and records into after them.
4
+ */
5
+
6
+ import {
7
+ pgTable,
8
+ text,
9
+ timestamp,
10
+ integer,
11
+ bigint,
12
+ real,
13
+ index,
14
+ unique,
15
+ jsonb,
16
+ } from "drizzle-orm/pg-core";
17
+ import { organization, users } from "./auth";
18
+
19
+ /**
20
+ * One row per AI request: tokens, model and agent, for per-model and
21
+ * per-agent reporting.
22
+ */
23
+ export const usageRecords = pgTable(
24
+ "usage_records",
25
+ {
26
+ id: text("id").primaryKey(),
27
+ workspaceId: text("workspace_id")
28
+ .notNull()
29
+ .references(() => organization.id, { onDelete: "cascade" }),
30
+ userId: text("user_id")
31
+ .notNull()
32
+ .references(() => users.id, { onDelete: "cascade" }),
33
+ type: text("type").notNull(), // "ai_tokens", "api_call"
34
+ model: text("model"), // "gpt-4o", "gpt-4o-mini"
35
+ agent: text("agent"), // the product's agent slug
36
+ inputTokens: integer("input_tokens").notNull().default(0),
37
+ outputTokens: integer("output_tokens").notNull().default(0),
38
+ totalTokens: integer("total_tokens").notNull().default(0),
39
+ /**
40
+ * What the provider charged, in USD micros. Integer on purpose: float
41
+ * arithmetic accumulates sub-cent error over many records.
42
+ */
43
+ providerCostMicros: bigint("provider_cost_micros", { mode: "number" })
44
+ .notNull()
45
+ .default(0),
46
+ /** Snapshots of the rate and margin this charge was computed with. */
47
+ marginBp: integer("margin_bp").notNull().default(0),
48
+ usdRateMicros: bigint("usd_rate_micros", { mode: "number" })
49
+ .notNull()
50
+ .default(0),
51
+ /** Authoritative charge, in micros of `currency`. */
52
+ chargedMicros: bigint("charged_micros", { mode: "number" })
53
+ .notNull()
54
+ .default(0),
55
+ currency: text("currency").notNull().default("USD"),
56
+ /** Request correlation id for log tracing */
57
+ requestId: text("request_id"),
58
+ /**
59
+ * Owning execution (@intelligo-dev/executions). No FK: the executions
60
+ * table is owned by another package and this column is written by
61
+ * the settlement path, which may run before or after the execution
62
+ * row's terminal update.
63
+ */
64
+ executionId: text("execution_id"),
65
+ /** Conversation/chat id for grouping in usage dashboards */
66
+ conversationId: text("conversation_id"),
67
+ metadata: text("metadata"), // JSON string for additional data
68
+ recordedAt: timestamp("recorded_at").notNull().defaultNow(),
69
+ },
70
+ (table) => [
71
+ index("usage_records_workspace_id_idx").on(table.workspaceId),
72
+ index("usage_records_workspace_recorded_at_idx").on(
73
+ table.workspaceId,
74
+ table.recordedAt
75
+ ),
76
+ index("usage_records_workspace_type_idx").on(table.workspaceId, table.type),
77
+ index("usage_records_conversation_id_idx").on(table.conversationId),
78
+ index("usage_records_execution_id_idx").on(table.executionId),
79
+ ]
80
+ );
81
+
82
+ /**
83
+ * One row per workspace per billing period, incremented atomically, so a
84
+ * quota check is O(1) instead of a SUM over usage_records.
85
+ */
86
+ export const monthlyUsage = pgTable(
87
+ "monthly_usage",
88
+ {
89
+ id: text("id").primaryKey(),
90
+ workspaceId: text("workspace_id")
91
+ .notNull()
92
+ .references(() => organization.id, { onDelete: "cascade" }),
93
+ periodStart: timestamp("period_start").notNull(),
94
+ periodEnd: timestamp("period_end").notNull(),
95
+ tokensUsed: integer("tokens_used").notNull().default(0),
96
+ /** The plan allowance spent this period, in micros of `currency`. */
97
+ allowanceUsedMicros: bigint("allowance_used_micros", { mode: "number" })
98
+ .notNull()
99
+ .default(0),
100
+ currency: text("currency").notNull().default("USD"),
101
+ requestCount: integer("request_count").notNull().default(0),
102
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
103
+ },
104
+ (table) => [
105
+ unique("monthly_usage_workspace_period_unique").on(
106
+ table.workspaceId,
107
+ table.periodStart
108
+ ),
109
+ index("monthly_usage_workspace_id_idx").on(table.workspaceId),
110
+ ]
111
+ );
112
+
113
+ /**
114
+ * One-time trial grant, one per workspace (unique constraint). Separate from
115
+ * purchased credits; used when the plan quota is exceeded or the credit
116
+ * balance is zero. Status: active → depleted, converted or expired.
117
+ */
118
+ export const trialCredits = pgTable(
119
+ "trial_credits",
120
+ {
121
+ id: text("id").primaryKey(),
122
+ workspaceId: text("workspace_id")
123
+ .notNull()
124
+ .unique()
125
+ .references(() => organization.id, { onDelete: "cascade" }),
126
+ initialCredits: integer("initial_credits").notNull().default(100000),
127
+ creditsUsed: integer("credits_used").notNull().default(0),
128
+ creditsRemaining: integer("credits_remaining").notNull().default(100000),
129
+ /** The grant, in micros of `currency`. */
130
+ initialMicros: bigint("initial_micros", { mode: "number" })
131
+ .notNull()
132
+ .default(0),
133
+ usedMicros: bigint("used_micros", { mode: "number" }).notNull().default(0),
134
+ remainingMicros: bigint("remaining_micros", { mode: "number" })
135
+ .notNull()
136
+ .default(0),
137
+ currency: text("currency").notNull().default("USD"),
138
+ status: text("status").notNull().default("active"), // active|depleted|converted|expired
139
+ provisionedAt: timestamp("provisioned_at").notNull().defaultNow(),
140
+ trialEndDate: timestamp("trial_end_date"), // 14 days from provisioning
141
+ depletedAt: timestamp("depleted_at"),
142
+ convertedAt: timestamp("converted_at"),
143
+ createdByIp: text("created_by_ip"),
144
+ createdByEmail: text("created_by_email"),
145
+ /**
146
+ * Canonical form of createdByEmail for the per-email abuse check,
147
+ * stored so the count can use an index instead of a full-table scan.
148
+ */
149
+ normalizedEmail: text("normalized_email"),
150
+ },
151
+ (table) => [
152
+ index("trial_credits_expiry_idx").on(table.status, table.trialEndDate),
153
+ index("trial_credits_normalized_email_idx").on(table.normalizedEmail),
154
+ ]
155
+ );
156
+
157
+ /**
158
+ * One row per (workspace, endpoint, minute bucket). Each request upserts the
159
+ * row and atomically increments `count`, so N concurrent requests observe
160
+ * 1..N and exactly `limit` are admitted. Old buckets are deleted by the
161
+ * billing-maintenance job.
162
+ */
163
+ export const rateLimitEntries = pgTable(
164
+ "rate_limit_entries",
165
+ {
166
+ id: text("id").primaryKey(),
167
+ workspaceId: text("workspace_id")
168
+ .notNull()
169
+ .references(() => organization.id, { onDelete: "cascade" }),
170
+ endpoint: text("endpoint").notNull().default("chat"),
171
+ requestedAt: timestamp("requested_at").notNull().defaultNow(),
172
+ /** Floored minute bucket for unique constraint enforcement */
173
+ minuteBucket: timestamp("minute_bucket").notNull(),
174
+ /** Requests observed in this bucket — incremented via upsert */
175
+ count: integer("count").notNull().default(1),
176
+ },
177
+ (table) => [
178
+ index("rate_limit_entries_window_idx").on(
179
+ table.workspaceId,
180
+ table.endpoint,
181
+ table.requestedAt
182
+ ),
183
+ unique("rate_limit_entries_unique_bucket_idx").on(
184
+ table.workspaceId,
185
+ table.endpoint,
186
+ table.minuteBucket
187
+ ),
188
+ ]
189
+ );
190
+
191
+ /**
192
+ * Sent quota and trial notifications. The unique constraint on
193
+ * (workspaceId, type, periodKey) keeps one notification from being sent
194
+ * twice in a billing period.
195
+ */
196
+ export const notificationHistory = pgTable(
197
+ "notification_history",
198
+ {
199
+ id: text("id").primaryKey(),
200
+ workspaceId: text("workspace_id")
201
+ .notNull()
202
+ .references(() => organization.id, { onDelete: "cascade" }),
203
+ type: text("type").notNull(), // "quota_warning_80", "quota_warning_100", "trial_warning_20", "trial_depleted"
204
+ periodKey: text("period_key").notNull(), // "2026-02" for monthly, "trial" for one-time
205
+ sentAt: timestamp("sent_at").notNull().defaultNow(),
206
+ channel: text("channel").notNull().default("console"), // "console" | "email"
207
+ metadata: text("metadata"), // JSON string for additional context
208
+ },
209
+ (table) => [
210
+ unique("notification_history_dedup_unique").on(
211
+ table.workspaceId,
212
+ table.type,
213
+ table.periodKey
214
+ ),
215
+ index("notification_history_workspace_id_idx").on(table.workspaceId),
216
+ ]
217
+ );
218
+
219
+ /**
220
+ * Per-user, per-action usage counters, one row per user and workspace (not
221
+ * per period). Counters live in the `usage` JSONB map and increment
222
+ * atomically in SQL, so a quota check is O(1).
223
+ */
224
+ export const userQuotas = pgTable(
225
+ "user_quotas",
226
+ {
227
+ id: text("id").primaryKey(),
228
+ userId: text("user_id")
229
+ .notNull()
230
+ .references(() => users.id, { onDelete: "cascade" }),
231
+ workspaceId: text("workspace_id")
232
+ .notNull()
233
+ .references(() => organization.id, { onDelete: "cascade" }),
234
+ plan: text("plan").notNull().default("free"), // free | standard | pro
235
+ /**
236
+ * Per-action counters keyed by the product's own action slugs
237
+ * (`usage["chat"] = 42`), so a product adds a counter without a
238
+ * migration.
239
+ */
240
+ usage: jsonb("usage").notNull().default({}),
241
+ totalCostUsd: real("total_cost_usd").notNull().default(0),
242
+ createdAt: timestamp("created_at").notNull().defaultNow(),
243
+ updatedAt: timestamp("updated_at").notNull().defaultNow(),
244
+ },
245
+ (table) => [
246
+ index("user_quotas_user_id_idx").on(table.userId),
247
+ index("user_quotas_workspace_id_idx").on(table.workspaceId),
248
+ // Unique per (user, workspace), not per user: a user in a paid and a
249
+ // free workspace must not share one plan and one set of counters.
250
+ unique("user_quotas_user_workspace_unique").on(
251
+ table.userId,
252
+ table.workspaceId
253
+ ),
254
+ ]
255
+ );
256
+
257
+ export type UserQuota = typeof userQuotas.$inferSelect;
258
+ export type InsertUserQuota = typeof userQuotas.$inferInsert;
259
+ export type UsageRecord = typeof usageRecords.$inferSelect;
260
+ export type InsertUsageRecord = typeof usageRecords.$inferInsert;
261
+ export type MonthlyUsage = typeof monthlyUsage.$inferSelect;
262
+ export type InsertMonthlyUsage = typeof monthlyUsage.$inferInsert;
263
+ export type TrialCredit = typeof trialCredits.$inferSelect;
264
+ export type InsertTrialCredit = typeof trialCredits.$inferInsert;
265
+ export type RateLimitEntry = typeof rateLimitEntries.$inferSelect;
266
+ export type InsertRateLimitEntry = typeof rateLimitEntries.$inferInsert;
267
+ export type NotificationHistoryRecord = typeof notificationHistory.$inferSelect;
268
+ export type InsertNotificationHistoryRecord =
269
+ typeof notificationHistory.$inferInsert;
@@ -0,0 +1,63 @@
1
+ import { db } from "./client";
2
+ import { sql } from "drizzle-orm";
3
+
4
+ export interface DatabaseValidationResult {
5
+ isConnected: boolean;
6
+ tablesExist: boolean;
7
+ error?: string;
8
+ }
9
+
10
+ /** Checks that the database answers and the four auth tables exist. */
11
+ export async function validateDatabaseConnection(): Promise<DatabaseValidationResult> {
12
+ try {
13
+ await db.execute(sql`SELECT 1`);
14
+
15
+ const result = await db.execute(sql`
16
+ SELECT COUNT(*) as table_count
17
+ FROM information_schema.tables
18
+ WHERE table_schema = 'public'
19
+ AND table_name IN ('users', 'sessions', 'accounts', 'verifications')
20
+ `);
21
+
22
+ const tableCount = Number(result.rows[0]?.table_count ?? 0);
23
+ const tablesExist = tableCount === 4;
24
+
25
+ return {
26
+ isConnected: true,
27
+ tablesExist,
28
+ error: tablesExist
29
+ ? undefined
30
+ : "Auth tables do not exist. Run 'intelligo migrate' to apply the framework's migration chain.",
31
+ };
32
+ } catch (error) {
33
+ return {
34
+ isConnected: false,
35
+ tablesExist: false,
36
+ error: error instanceof Error ? error.message : "Unknown database error",
37
+ };
38
+ }
39
+ }
40
+
41
+ /** Startup check: throws with setup instructions when validation fails. */
42
+ export async function requireDatabaseConnection(): Promise<void> {
43
+ const result = await validateDatabaseConnection();
44
+
45
+ if (!result.isConnected) {
46
+ throw new Error(
47
+ `❌ Database connection failed: ${result.error}\n\n` +
48
+ `Make sure DATABASE_URL is configured in .env.local\n` +
49
+ `Example: DATABASE_URL=postgres://user:pass@host/db`
50
+ );
51
+ }
52
+
53
+ if (!result.tablesExist) {
54
+ throw new Error(
55
+ `❌ Database tables do not exist.\n\n` +
56
+ `Run the following commands to set up the database:\n` +
57
+ ` 1. intelligo migrate (applies the framework's migration chain)\n` +
58
+ ` 2. drizzle-kit migrate (your own tables, if any)`
59
+ );
60
+ }
61
+
62
+ console.log("✓ Database connection validated");
63
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Workspace scoping for tables with a `workspaceId` column. Every query on
3
+ * such a table filters by the workspace resolved from `requireWorkspace()`.
4
+ * Organization, member and invitation rows go through the Better-Auth API,
5
+ * which scopes them to the caller's session.
6
+ */
7
+
8
+ import { eq, and, type SQL } from "drizzle-orm";
9
+ import type { PgColumn } from "drizzle-orm/pg-core";
10
+
11
+ /**
12
+ * Creates a workspace-scoped WHERE condition for any table with a workspaceId column.
13
+ * Use it in every custom Drizzle query to enforce workspace isolation.
14
+ *
15
+ * @param workspaceIdColumn - The workspaceId column from the table schema
16
+ * @param workspaceId - The active workspace ID from requireWorkspace()
17
+ * @returns SQL condition: workspaceId = ?
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const { workspace } = await requireWorkspace();
22
+ * const conversations = await db
23
+ * .select()
24
+ * .from(conversationsTable)
25
+ * .where(workspaceEq(conversationsTable.workspaceId, workspace.id));
26
+ * ```
27
+ */
28
+ export function workspaceEq(
29
+ workspaceIdColumn: PgColumn,
30
+ workspaceId: string
31
+ ): SQL {
32
+ return eq(workspaceIdColumn, workspaceId);
33
+ }
34
+
35
+ /**
36
+ * Combines a workspace scope condition with additional WHERE conditions.
37
+ *
38
+ * @param workspaceIdColumn - The workspaceId column from the table schema
39
+ * @param workspaceId - The active workspace ID from requireWorkspace()
40
+ * @param additionalConditions - Extra WHERE conditions to AND together
41
+ * @returns Combined SQL condition: workspaceId = ? AND ...
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * const { workspace } = await requireWorkspace();
46
+ * const activeConversations = await db
47
+ * .select()
48
+ * .from(conversationsTable)
49
+ * .where(
50
+ * withWorkspaceFilter(
51
+ * conversationsTable.workspaceId,
52
+ * workspace.id,
53
+ * eq(conversationsTable.status, "active")
54
+ * )
55
+ * );
56
+ * ```
57
+ */
58
+ export function withWorkspaceFilter(
59
+ workspaceIdColumn: PgColumn,
60
+ workspaceId: string,
61
+ ...additionalConditions: SQL[]
62
+ ): SQL {
63
+ return and(eq(workspaceIdColumn, workspaceId), ...additionalConditions)!;
64
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Document title classifier. Products register their title patterns here;
3
+ * the documents service uses them to derive agent labels and the "report"
4
+ * filter. Kept free of the db import so client code and tests can load it.
5
+ */
6
+
7
+ import { createRegistry } from "../registry";
8
+
9
+ export type DocumentPatternEntry = {
10
+ /** Stable product slug — same one used across product configs. */
11
+ productSlug: string;
12
+ /** Human-readable agent label rendered in the documents UI. */
13
+ agentLabel: string;
14
+ /** Case-insensitive substring patterns; a document matches when its
15
+ * title contains ANY of these, whatever the case of either. */
16
+ patterns: string[];
17
+ };
18
+
19
+ /**
20
+ * Keyed by product slug rather than appended, so a composition root that
21
+ * runs twice (or two copies of it) replaces the entry instead of matching
22
+ * the same patterns twice. Insertion order decides first-match
23
+ * classification.
24
+ */
25
+ const registry = createRegistry<DocumentPatternEntry>("core/document-patterns");
26
+
27
+ export function registerDocumentPatterns(entry: DocumentPatternEntry): void {
28
+ registry.set(entry.productSlug, entry);
29
+ }
30
+
31
+ export function classifyDocumentTitle(title: string): {
32
+ productSlug: string | null;
33
+ agentLabel: string;
34
+ } {
35
+ const lower = title.toLowerCase();
36
+ for (const entry of registry.values()) {
37
+ if (entry.patterns.some((p) => lower.includes(p.toLowerCase()))) {
38
+ return { productSlug: entry.productSlug, agentLabel: entry.agentLabel };
39
+ }
40
+ }
41
+ return { productSlug: null, agentLabel: "AI Assistant" };
42
+ }
43
+
44
+ /** Returns true when the document looks like it belongs to any registered product. */
45
+ export function isProductDocument(
46
+ title: string,
47
+ productSlug?: string
48
+ ): boolean {
49
+ const { productSlug: matched } = classifyDocumentTitle(title);
50
+ if (!matched) return false;
51
+ if (productSlug) return matched === productSlug;
52
+ return true;
53
+ }
54
+
55
+ export function listRegisteredDocumentPatterns(): readonly DocumentPatternEntry[] {
56
+ return [...registry.values()];
57
+ }
58
+
59
+ export function clearDocumentPatternRegistry(): void {
60
+ registry.clear();
61
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Thrown by the documents service for every failure it recognizes. Shaping
3
+ * it into a transport envelope is the caller's job.
4
+ */
5
+
6
+ /**
7
+ * - `not_found` — no document (or no version of it) exists at the
8
+ * given id within the actor's workspace/user scope.
9
+ * - `forbidden` — a document with this id exists but is owned by a
10
+ * different user within the same workspace.
11
+ * - `invalid_input` — a caller-supplied argument failed a basic
12
+ * invariant (e.g. an unparsable or out-of-window delete timestamp).
13
+ * - `database_error` — the underlying insert/update did not return the
14
+ * row it was expected to.
15
+ */
16
+ export type DocumentServiceErrorCode =
17
+ "not_found" | "forbidden" | "invalid_input" | "database_error";
18
+
19
+ export class DocumentServiceError extends Error {
20
+ readonly code: DocumentServiceErrorCode;
21
+ // The ES2020 target predates `Error.cause`; declared so `err.cause`
22
+ // type-checks for callers.
23
+ readonly cause?: unknown;
24
+
25
+ constructor(
26
+ code: DocumentServiceErrorCode,
27
+ message: string,
28
+ options?: { cause?: unknown }
29
+ ) {
30
+ super(message);
31
+ this.name = "DocumentServiceError";
32
+ this.code = code;
33
+ if (options?.cause !== undefined) {
34
+ (this as { cause?: unknown }).cause = options.cause;
35
+ }
36
+ Object.setPrototypeOf(this, DocumentServiceError.prototype);
37
+ }
38
+ }
39
+
40
+ export function isDocumentServiceError(
41
+ error: unknown
42
+ ): error is DocumentServiceError {
43
+ return error instanceof DocumentServiceError;
44
+ }
@@ -0,0 +1,24 @@
1
+ export {
2
+ getUserDocuments,
3
+ getDocument,
4
+ getDocumentVersions,
5
+ saveDocument,
6
+ deleteDocumentVersions,
7
+ } from "./service";
8
+
9
+ export {
10
+ DocumentServiceError,
11
+ isDocumentServiceError,
12
+ type DocumentServiceErrorCode,
13
+ } from "./errors";
14
+
15
+ export type { DocumentActor, DocumentFilter, DocumentListItem } from "./types";
16
+
17
+ export {
18
+ registerDocumentPatterns,
19
+ classifyDocumentTitle,
20
+ isProductDocument,
21
+ listRegisteredDocumentPatterns,
22
+ clearDocumentPatternRegistry,
23
+ type DocumentPatternEntry,
24
+ } from "./classifier";