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

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 (356) 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 +339 -105
  56. package/dist/db/schema/billing.d.ts.map +1 -1
  57. package/dist/db/schema/billing.js +99 -58
  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 +6 -13
  182. package/dist/identity/index.d.ts.map +1 -1
  183. package/dist/identity/index.js +5 -12
  184. package/dist/identity/index.js.map +1 -1
  185. package/dist/identity/service.d.ts +29 -47
  186. package/dist/identity/service.d.ts.map +1 -1
  187. package/dist/identity/service.js +107 -79
  188. package/dist/identity/service.js.map +1 -1
  189. package/dist/identity/types.d.ts +28 -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 +6 -8
  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/0003_local_payments.sql +18 -0
  248. package/src/db/migrations/README.md +103 -36
  249. package/src/db/migrations/legacy-chain.json +206 -0
  250. package/src/db/migrations/meta/0000_snapshot.json +4182 -157
  251. package/src/db/migrations/meta/0002_snapshot.json +2478 -368
  252. package/src/db/migrations/meta/0003_snapshot.json +2567 -554
  253. package/src/db/migrations/meta/_journal.json +8 -274
  254. package/src/db/schema/agents.ts +57 -0
  255. package/src/db/schema/ai.ts +174 -0
  256. package/src/db/schema/auth.ts +183 -0
  257. package/src/db/schema/billing.ts +245 -0
  258. package/src/db/schema/feature-flags.ts +20 -0
  259. package/src/db/schema/identity.ts +250 -0
  260. package/src/db/schema/index.ts +12 -0
  261. package/src/db/schema/notifications.ts +31 -0
  262. package/src/db/schema/usage.ts +269 -0
  263. package/src/db/validate.ts +63 -0
  264. package/src/db/workspace-queries.ts +64 -0
  265. package/src/documents/classifier.ts +61 -0
  266. package/src/documents/errors.ts +44 -0
  267. package/src/documents/index.ts +24 -0
  268. package/src/documents/service.ts +256 -0
  269. package/src/documents/types.ts +24 -0
  270. package/src/email/brand.ts +24 -0
  271. package/src/email/index.ts +19 -0
  272. package/src/email/provider.ts +278 -0
  273. package/src/email/send.ts +169 -0
  274. package/src/email/senders.ts +292 -0
  275. package/src/email/templates/base-layout.tsx +145 -0
  276. package/src/email/templates/index.ts +28 -0
  277. package/src/email/templates/password-reset.tsx +72 -0
  278. package/src/email/templates/payment-failed.tsx +77 -0
  279. package/src/email/templates/quota-warning.tsx +118 -0
  280. package/src/email/templates/subscription-confirmed.tsx +91 -0
  281. package/src/email/templates/trial-expiry.tsx +124 -0
  282. package/src/email/templates/trial-warning.tsx +104 -0
  283. package/src/email/templates/verify-email.tsx +72 -0
  284. package/src/email/templates/welcome.tsx +64 -0
  285. package/src/email/templates/workspace-invitation.tsx +105 -0
  286. package/src/env.ts +180 -0
  287. package/src/identity/audit.ts +65 -0
  288. package/src/identity/errors.ts +44 -0
  289. package/src/identity/index.ts +29 -0
  290. package/src/identity/service.ts +303 -0
  291. package/src/identity/types.ts +58 -0
  292. package/src/index.ts +4 -0
  293. package/src/logger.ts +198 -0
  294. package/src/money.ts +270 -0
  295. package/src/notifications/index.ts +112 -0
  296. package/src/notifications/triggers.ts +158 -0
  297. package/src/notifications/types.ts +26 -0
  298. package/src/prompt.ts +142 -0
  299. package/src/registry.ts +75 -0
  300. package/src/request-context.ts +137 -0
  301. package/src/storage/index.ts +153 -0
  302. package/dist/db/schema/rag.d.ts +0 -171
  303. package/dist/db/schema/rag.d.ts.map +0 -1
  304. package/dist/db/schema/rag.js +0 -42
  305. package/dist/db/schema/rag.js.map +0 -1
  306. package/src/db/migrations/0000_sharp_night_nurse.sql +0 -52
  307. package/src/db/migrations/0001_hesitant_vapor.sql +0 -40
  308. package/src/db/migrations/0002_numerous_lady_vermin.sql +0 -235
  309. package/src/db/migrations/0003_mighty_jean_grey.sql +0 -37
  310. package/src/db/migrations/0004_cynical_grandmaster.sql +0 -2
  311. package/src/db/migrations/0005_careful_switch.sql +0 -8
  312. package/src/db/migrations/0006_tense_blue_marvel.sql +0 -16
  313. package/src/db/migrations/0007_next_squadron_sinister.sql +0 -1
  314. package/src/db/migrations/0008_powerful_scream.sql +0 -39
  315. package/src/db/migrations/0009_sad_ink.sql +0 -1
  316. package/src/db/migrations/0010_shocking_omega_flight.sql +0 -2
  317. package/src/db/migrations/0011_ambiguous_otto_octavius.sql +0 -151
  318. package/src/db/migrations/0011_hnsw_vector_index.sql +0 -17
  319. package/src/db/migrations/0012_user_profiles.sql +0 -30
  320. package/src/db/migrations/0013_trending_seasonal_columns.sql +0 -9
  321. package/src/db/migrations/0014_add_product_type_to_agents.sql +0 -6
  322. package/src/db/migrations/0015_new_v1_tables.sql +0 -149
  323. package/src/db/migrations/0016_document_types.sql +0 -30
  324. package/src/db/migrations/0017_document_metadata.sql +0 -2
  325. package/src/db/migrations/0018_identity_graph.sql +0 -200
  326. package/src/db/migrations/0019_db_driven_prompts.sql +0 -20
  327. package/src/db/migrations/0020_pending_extractions.sql +0 -35
  328. package/src/db/migrations/0021_rag_documents.sql +0 -39
  329. package/src/db/migrations/0022_user_quotas_usage_jsonb.sql +0 -70
  330. package/src/db/migrations/0023_user_facts_user_scoped_unique.sql +0 -17
  331. package/src/db/migrations/0024_user_memories_hnsw.sql +0 -23
  332. package/src/db/migrations/0025_audit_trigger_errcode.sql +0 -21
  333. package/src/db/migrations/0026_user_profile_snapshots_composite_pk.sql +0 -17
  334. package/src/db/migrations/0027_billing_settings_mnt_columns.sql +0 -54
  335. package/src/db/migrations/0028_trial_credits_normalized_email.sql +0 -37
  336. package/src/db/migrations/0029_usage_records_conversation_idx.sql +0 -13
  337. package/src/db/migrations/0030_pending_extractions_composite_idx.sql +0 -11
  338. package/src/db/migrations/0031_rag_documents_hnsw.sql +0 -16
  339. package/src/db/migrations/0032_rate_limit_unique_idx.sql +0 -16
  340. package/src/db/migrations/0033_pending_extractions_partial_idx.sql +0 -18
  341. package/src/db/migrations/0034_rate_limit_count.sql +0 -9
  342. package/src/db/migrations/0035_credit_reservations.sql +0 -24
  343. package/src/db/migrations/0036_executions_audit_jobs.sql +0 -96
  344. package/src/db/migrations/0037_rate_limit_minute_bucket.sql +0 -46
  345. package/src/db/migrations/0038_user_quotas_generic_usage.sql +0 -25
  346. package/src/db/migrations/0039_platform_admin_impersonation.sql +0 -27
  347. package/src/db/migrations/0040_ee_audit_chain.sql +0 -23
  348. package/src/db/migrations/meta/0001_snapshot.json +0 -631
  349. package/src/db/migrations/meta/0004_snapshot.json +0 -2500
  350. package/src/db/migrations/meta/0005_snapshot.json +0 -2548
  351. package/src/db/migrations/meta/0006_snapshot.json +0 -2650
  352. package/src/db/migrations/meta/0007_snapshot.json +0 -2657
  353. package/src/db/migrations/meta/0008_snapshot.json +0 -2719
  354. package/src/db/migrations/meta/0009_snapshot.json +0 -2726
  355. package/src/db/migrations/meta/0010_snapshot.json +0 -2739
  356. package/src/db/migrations/meta/0011_snapshot.json +0 -3886
@@ -0,0 +1,250 @@
1
+ /**
2
+ * The user identity graph: a user's facts, episodic memories and audit
3
+ * trail, synthesized into one profile snapshot an agent session reads in
4
+ * one query.
5
+ *
6
+ * - `user_facts`: the structured source of truth, queryable by category
7
+ * and weighted by confidence and importance.
8
+ * - `user_memories`: unstructured episodic recall via pgvector search.
9
+ * - `user_profile_snapshots`: the cached synthesis, since voice agents
10
+ * cannot rebuild the profile inside a turn's latency budget.
11
+ * - `user_memory_audit`: append-only log of every write, so a user can see
12
+ * where each fact came from.
13
+ */
14
+
15
+ import {
16
+ pgTable,
17
+ primaryKey,
18
+ text,
19
+ timestamp,
20
+ integer,
21
+ index,
22
+ uniqueIndex,
23
+ jsonb,
24
+ doublePrecision,
25
+ } from "drizzle-orm/pg-core";
26
+ import { vector } from "drizzle-orm/pg-core/columns/vector_extension/vector";
27
+ import { organization, users } from "./auth";
28
+
29
+ /**
30
+ * A fact's category, the filter axis for synthesis and display. A closed
31
+ * list on purpose: a new category is a decision, not a free-form string.
32
+ */
33
+ export const USER_FACT_CATEGORIES = [
34
+ "interest",
35
+ "skill",
36
+ "goal",
37
+ "constraint",
38
+ "trait",
39
+ "value",
40
+ "preference",
41
+ "context",
42
+ "background",
43
+ ] as const;
44
+
45
+ export type UserFactCategory = (typeof USER_FACT_CATEGORIES)[number];
46
+
47
+ /**
48
+ * How a fact entered the system. "explicit_user_input" facts are
49
+ * considered ground truth and survive conflict resolution against
50
+ * lower-confidence LLM extractions.
51
+ */
52
+ export const USER_FACT_EXTRACTION_METHODS = [
53
+ "llm_extraction",
54
+ "explicit_user_input",
55
+ "inline_tool",
56
+ "onboarding_form",
57
+ ] as const;
58
+
59
+ export type UserFactExtractionMethod =
60
+ (typeof USER_FACT_EXTRACTION_METHODS)[number];
61
+
62
+ export const userFacts = pgTable(
63
+ "user_facts",
64
+ {
65
+ id: text("id").primaryKey(),
66
+ userId: text("user_id")
67
+ .notNull()
68
+ .references(() => users.id, { onDelete: "cascade" }),
69
+ workspaceId: text("workspace_id")
70
+ .notNull()
71
+ .references(() => organization.id, { onDelete: "cascade" }),
72
+
73
+ category: text("category").$type<UserFactCategory>().notNull(),
74
+ key: text("key").notNull(),
75
+ value: jsonb("value").notNull(),
76
+ confidence: doublePrecision("confidence").notNull(),
77
+ importance: integer("importance").notNull().default(5),
78
+
79
+ sourceProduct: text("source_product"),
80
+ sourceSessionId: text("source_session_id"),
81
+ sourceMessageId: text("source_message_id"),
82
+ extractionMethod:
83
+ text("extraction_method").$type<UserFactExtractionMethod>(),
84
+
85
+ createdAt: timestamp("created_at").notNull().defaultNow(),
86
+ lastConfirmedAt: timestamp("last_confirmed_at"),
87
+ expiresAt: timestamp("expires_at"),
88
+ },
89
+ (table) => [
90
+ index("user_facts_user_idx").on(table.userId),
91
+ index("user_facts_workspace_idx").on(table.workspaceId),
92
+ index("user_facts_category_idx").on(table.workspaceId, table.category),
93
+ // Scoped to (user, workspace, category, key) so members of a shared
94
+ // workspace can hold the same fact key without collision.
95
+ uniqueIndex("user_facts_user_workspace_category_key_uniq").on(
96
+ table.userId,
97
+ table.workspaceId,
98
+ table.category,
99
+ table.key
100
+ ),
101
+ ]
102
+ );
103
+
104
+ export const USER_MEMORY_KINDS = [
105
+ "conversation_summary",
106
+ "event",
107
+ "insight",
108
+ "preference_statement",
109
+ "decision",
110
+ "struggle",
111
+ "success",
112
+ ] as const;
113
+
114
+ export type UserMemoryKind = (typeof USER_MEMORY_KINDS)[number];
115
+
116
+ export const userMemories = pgTable(
117
+ "user_memories",
118
+ {
119
+ id: text("id").primaryKey(),
120
+ userId: text("user_id")
121
+ .notNull()
122
+ .references(() => users.id, { onDelete: "cascade" }),
123
+ workspaceId: text("workspace_id")
124
+ .notNull()
125
+ .references(() => organization.id, { onDelete: "cascade" }),
126
+
127
+ kind: text("kind").$type<UserMemoryKind>().notNull(),
128
+ content: text("content").notNull(),
129
+ embedding: vector({ dimensions: 1536 }),
130
+
131
+ sourceProduct: text("source_product"),
132
+ sourceSessionId: text("source_session_id"),
133
+ sourceMessageIds: text("source_message_ids").array(),
134
+
135
+ importance: integer("importance").notNull().default(5),
136
+ decayFactor: doublePrecision("decay_factor").notNull().default(1.0),
137
+ metadata: jsonb("metadata"),
138
+
139
+ createdAt: timestamp("created_at").notNull().defaultNow(),
140
+ },
141
+ (table) => [
142
+ index("user_memories_user_idx").on(table.userId),
143
+ index("user_memories_workspace_idx").on(table.workspaceId),
144
+ index("user_memories_kind_idx").on(table.workspaceId, table.kind),
145
+ // The pgvector index is created in migration SQL: drizzle-orm cannot
146
+ // express it.
147
+ ]
148
+ );
149
+
150
+ export const SYNTHESIS_TRIGGER_REASONS = [
151
+ "fact_threshold",
152
+ "session_close",
153
+ "daily_cron",
154
+ "manual",
155
+ ] as const;
156
+
157
+ export type SynthesisTriggerReason = (typeof SYNTHESIS_TRIGGER_REASONS)[number];
158
+
159
+ export const userProfileSnapshots = pgTable(
160
+ "user_profile_snapshots",
161
+ {
162
+ userId: text("user_id")
163
+ .notNull()
164
+ .references(() => users.id, { onDelete: "cascade" }),
165
+ workspaceId: text("workspace_id")
166
+ .notNull()
167
+ .references(() => organization.id, { onDelete: "cascade" }),
168
+ version: integer("version").notNull(),
169
+
170
+ summary: text("summary").notNull(),
171
+ summaryEn: text("summary_en"),
172
+ summaryMn: text("summary_mn"),
173
+
174
+ factsDigest: jsonb("facts_digest").notNull(),
175
+ activeGoals: jsonb("active_goals"),
176
+ personalitySignals: jsonb("personality_signals"),
177
+ relationshipNotes: jsonb("relationship_notes"),
178
+
179
+ synthesizedByModel: text("synthesized_by_model"),
180
+ synthesizedAt: timestamp("synthesized_at").notNull().defaultNow(),
181
+ nextSynthesisAt: timestamp("next_synthesis_at"),
182
+ triggerReason: text("trigger_reason").$type<SynthesisTriggerReason>(),
183
+ },
184
+ // One snapshot per user per workspace: a user in two workspaces gets two
185
+ // distinct profiles.
186
+ (table) => [
187
+ primaryKey({ columns: [table.userId, table.workspaceId] }),
188
+ index("user_profile_snapshots_workspace_idx").on(table.workspaceId),
189
+ ]
190
+ );
191
+
192
+ export const AUDIT_TARGET_KINDS = ["fact", "memory", "snapshot"] as const;
193
+ export type AuditTargetKind = (typeof AUDIT_TARGET_KINDS)[number];
194
+
195
+ export const AUDIT_ACTIONS = [
196
+ "create",
197
+ "update",
198
+ "delete",
199
+ "view",
200
+ "export",
201
+ ] as const;
202
+ export type AuditAction = (typeof AUDIT_ACTIONS)[number];
203
+
204
+ export const AUDIT_ACTOR_KINDS = [
205
+ "agent",
206
+ "user",
207
+ "system_job",
208
+ "admin",
209
+ ] as const;
210
+ export type AuditActorKind = (typeof AUDIT_ACTOR_KINDS)[number];
211
+
212
+ export const userMemoryAudit = pgTable(
213
+ "user_memory_audit",
214
+ {
215
+ id: text("id").primaryKey(),
216
+ userId: text("user_id")
217
+ .notNull()
218
+ .references(() => users.id, { onDelete: "cascade" }),
219
+ workspaceId: text("workspace_id")
220
+ .notNull()
221
+ .references(() => organization.id, { onDelete: "cascade" }),
222
+
223
+ targetKind: text("target_kind").$type<AuditTargetKind>().notNull(),
224
+ targetId: text("target_id").notNull(),
225
+ action: text("action").$type<AuditAction>().notNull(),
226
+ actorKind: text("actor_kind").$type<AuditActorKind>().notNull(),
227
+ actorId: text("actor_id"),
228
+
229
+ beforeValue: jsonb("before_value"),
230
+ afterValue: jsonb("after_value"),
231
+ reason: text("reason"),
232
+
233
+ createdAt: timestamp("created_at").notNull().defaultNow(),
234
+ },
235
+ (table) => [
236
+ index("user_memory_audit_user_idx").on(table.userId),
237
+ index("user_memory_audit_workspace_idx").on(table.workspaceId),
238
+ index("user_memory_audit_target_idx").on(table.targetKind, table.targetId),
239
+ ]
240
+ );
241
+
242
+ export type UserFact = typeof userFacts.$inferSelect;
243
+ export type InsertUserFact = typeof userFacts.$inferInsert;
244
+ export type UserMemory = typeof userMemories.$inferSelect;
245
+ export type InsertUserMemory = typeof userMemories.$inferInsert;
246
+ export type UserProfileSnapshot = typeof userProfileSnapshots.$inferSelect;
247
+ export type InsertUserProfileSnapshot =
248
+ typeof userProfileSnapshots.$inferInsert;
249
+ export type UserMemoryAuditRow = typeof userMemoryAudit.$inferSelect;
250
+ export type InsertUserMemoryAudit = typeof userMemoryAudit.$inferInsert;
@@ -0,0 +1,12 @@
1
+ // Better-Auth's tables, including organization, member and invitation.
2
+ // Product code goes through the Better-Auth API rather than querying the
3
+ // organization tables directly, so membership and role rules stay in one
4
+ // place.
5
+ export * from "./auth";
6
+ export * from "./billing";
7
+ export * from "./usage";
8
+ export * from "./notifications";
9
+ export * from "./feature-flags";
10
+ export * from "./ai";
11
+ export * from "./agents";
12
+ export * from "./identity";
@@ -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
+ }