@mastra/factory 0.24.0-alpha.9 → 0.25.0-alpha.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 (377) hide show
  1. package/README.md +40 -13
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +18 -4
  4. package/dist/auth.js.map +1 -1
  5. package/dist/boards/review.d.ts.map +1 -1
  6. package/dist/boards/review.js +3 -2
  7. package/dist/boards/review.js.map +1 -1
  8. package/dist/environment/build-runner.js +2 -1
  9. package/dist/environment/build-runner.js.map +1 -1
  10. package/dist/environment/build-workflow.js +1 -1
  11. package/dist/environment/build-workflow.js.map +1 -1
  12. package/dist/environment/build.js +16 -14
  13. package/dist/environment/build.js.map +1 -1
  14. package/dist/factory.d.ts +3 -0
  15. package/dist/factory.d.ts.map +1 -1
  16. package/dist/factory.js +172 -88
  17. package/dist/factory.js.map +1 -1
  18. package/dist/integrations/base.d.ts +15 -2
  19. package/dist/integrations/base.d.ts.map +1 -1
  20. package/dist/integrations/github/app-identity.js.map +1 -1
  21. package/dist/integrations/github/integration.js +1 -1
  22. package/dist/integrations/github/integration.js.map +1 -1
  23. package/dist/integrations/github/reconcile-worker.js +1 -1
  24. package/dist/integrations/github/reconcile-worker.js.map +1 -1
  25. package/dist/integrations/github/routes.d.ts +1 -1
  26. package/dist/integrations/github/routes.d.ts.map +1 -1
  27. package/dist/integrations/github/routes.js +24 -13
  28. package/dist/integrations/github/routes.js.map +1 -1
  29. package/dist/integrations/github/rules.d.ts.map +1 -1
  30. package/dist/integrations/github/rules.js +53 -9
  31. package/dist/integrations/github/rules.js.map +1 -1
  32. package/dist/integrations/github/sandbox.js +2 -2
  33. package/dist/integrations/github/sandbox.js.map +1 -1
  34. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  35. package/dist/integrations/github/session-subscriptions.js +1 -2
  36. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  37. package/dist/integrations/github/subscriptions.d.ts +7 -4
  38. package/dist/integrations/github/subscriptions.d.ts.map +1 -1
  39. package/dist/integrations/github/subscriptions.js +5 -5
  40. package/dist/integrations/github/subscriptions.js.map +1 -1
  41. package/dist/integrations/gitlab/api.js.map +1 -1
  42. package/dist/integrations/gitlab/integration.js.map +1 -1
  43. package/dist/integrations/gitlab/reconciliation-config.js +2 -1
  44. package/dist/integrations/gitlab/reconciliation-config.js.map +1 -1
  45. package/dist/integrations/gitlab/routes.d.ts.map +1 -1
  46. package/dist/integrations/gitlab/routes.js +10 -6
  47. package/dist/integrations/gitlab/routes.js.map +1 -1
  48. package/dist/integrations/gitlab/rules.js.map +1 -1
  49. package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -1
  50. package/dist/integrations/gitlab/session-subscriptions.js +1 -2
  51. package/dist/integrations/gitlab/session-subscriptions.js.map +1 -1
  52. package/dist/integrations/gitlab/subscriptions.d.ts +7 -4
  53. package/dist/integrations/gitlab/subscriptions.d.ts.map +1 -1
  54. package/dist/integrations/gitlab/subscriptions.js +5 -5
  55. package/dist/integrations/gitlab/subscriptions.js.map +1 -1
  56. package/dist/integrations/incidentio/api.js.map +1 -1
  57. package/dist/integrations/incidentio/integration.js.map +1 -1
  58. package/dist/integrations/incidentio/rules.js.map +1 -1
  59. package/dist/integrations/issue-reconcile-worker.js +1 -1
  60. package/dist/integrations/issue-reconcile-worker.js.map +1 -1
  61. package/dist/integrations/issue-reconciler.js +1 -1
  62. package/dist/integrations/issue-reconciler.js.map +1 -1
  63. package/dist/integrations/jira/api.js.map +1 -1
  64. package/dist/integrations/jira/integration.js.map +1 -1
  65. package/dist/integrations/jira/rules.js.map +1 -1
  66. package/dist/integrations/linear/agent-tools.d.ts.map +1 -1
  67. package/dist/integrations/linear/agent-tools.js.map +1 -1
  68. package/dist/integrations/linear/integration.js.map +1 -1
  69. package/dist/integrations/linear/rules.js.map +1 -1
  70. package/dist/integrations/platform/api-client.js.map +1 -1
  71. package/dist/integrations/platform/github/event-worker.js +1 -1
  72. package/dist/integrations/platform/github/event-worker.js.map +1 -1
  73. package/dist/integrations/platform/github/integration.js +2 -2
  74. package/dist/integrations/platform/github/integration.js.map +1 -1
  75. package/dist/integrations/platform/gitlab/event-worker.js.map +1 -1
  76. package/dist/integrations/platform/gitlab/integration.js.map +1 -1
  77. package/dist/integrations/platform/incidentio/integration.js +2 -1
  78. package/dist/integrations/platform/incidentio/integration.js.map +1 -1
  79. package/dist/integrations/platform/jira/integration.js.map +1 -1
  80. package/dist/integrations/platform/linear/event-worker.js +1 -1
  81. package/dist/integrations/platform/linear/event-worker.js.map +1 -1
  82. package/dist/integrations/platform/linear/integration.js.map +1 -1
  83. package/dist/{session → integrations/slack}/channel-reply-processor.d.ts +11 -9
  84. package/dist/integrations/slack/channel-reply-processor.d.ts.map +1 -0
  85. package/dist/integrations/slack/channel-reply-processor.js +61 -0
  86. package/dist/integrations/slack/channel-reply-processor.js.map +1 -0
  87. package/dist/integrations/slack/channel-session-link-processor.d.ts.map +1 -0
  88. package/dist/{session → integrations/slack}/channel-session-link-processor.js +14 -4
  89. package/dist/integrations/slack/channel-session-link-processor.js.map +1 -0
  90. package/dist/integrations/slack/connect-route.js +1 -1
  91. package/dist/integrations/slack/connect-route.js.map +1 -1
  92. package/dist/integrations/slack/feed-publisher.js +1 -1
  93. package/dist/integrations/slack/feed-publisher.js.map +1 -1
  94. package/dist/integrations/slack/integration.d.ts +25 -1
  95. package/dist/integrations/slack/integration.d.ts.map +1 -1
  96. package/dist/integrations/slack/integration.js +56 -6
  97. package/dist/integrations/slack/integration.js.map +1 -1
  98. package/dist/integrations/slack/reply-reminder-processor.d.ts +23 -0
  99. package/dist/integrations/slack/reply-reminder-processor.d.ts.map +1 -0
  100. package/dist/integrations/slack/reply-reminder-processor.js +58 -0
  101. package/dist/integrations/slack/reply-reminder-processor.js.map +1 -0
  102. package/dist/integrations/slack/reply-tool.d.ts +31 -0
  103. package/dist/integrations/slack/reply-tool.d.ts.map +1 -0
  104. package/dist/integrations/slack/reply-tool.js +203 -0
  105. package/dist/integrations/slack/reply-tool.js.map +1 -0
  106. package/dist/integrations/slack/share-file-tool.d.ts +45 -0
  107. package/dist/integrations/slack/share-file-tool.d.ts.map +1 -0
  108. package/dist/integrations/slack/share-file-tool.js +106 -0
  109. package/dist/integrations/slack/share-file-tool.js.map +1 -0
  110. package/dist/integrations/slack/slack.d.ts +16 -5
  111. package/dist/integrations/slack/slack.d.ts.map +1 -1
  112. package/dist/integrations/slack/slack.js +141 -40
  113. package/dist/integrations/slack/slack.js.map +1 -1
  114. package/dist/integrations/subscription-session.d.ts +0 -1
  115. package/dist/integrations/subscription-session.d.ts.map +1 -1
  116. package/dist/integrations/subscription-session.js +1 -4
  117. package/dist/integrations/subscription-session.js.map +1 -1
  118. package/dist/integrations/workos/integration.js.map +1 -1
  119. package/dist/mcp/events.d.ts +73 -0
  120. package/dist/mcp/events.d.ts.map +1 -0
  121. package/dist/mcp/events.js +158 -0
  122. package/dist/mcp/events.js.map +1 -0
  123. package/dist/mcp/routes.d.ts +0 -1
  124. package/dist/mcp/routes.d.ts.map +1 -1
  125. package/dist/mcp/routes.js +31 -4
  126. package/dist/mcp/routes.js.map +1 -1
  127. package/dist/packages/_internals/workspace/dist/index.js +18 -1
  128. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  129. package/dist/platform-auth-host.d.ts.map +1 -1
  130. package/dist/platform-auth-host.js +1 -1
  131. package/dist/platform-auth-host.js.map +1 -1
  132. package/dist/routes/attention-activity.js.map +1 -1
  133. package/dist/routes/attention-parked.js.map +1 -1
  134. package/dist/routes/attention-providers.js.map +1 -1
  135. package/dist/routes/automation-runs.js.map +1 -1
  136. package/dist/routes/config.d.ts +8 -7
  137. package/dist/routes/config.d.ts.map +1 -1
  138. package/dist/routes/config.js +70 -28
  139. package/dist/routes/config.js.map +1 -1
  140. package/dist/routes/contracts.d.ts +2 -0
  141. package/dist/routes/contracts.d.ts.map +1 -1
  142. package/dist/routes/contracts.js +2 -2
  143. package/dist/routes/contracts.js.map +1 -1
  144. package/dist/routes/custom-provider-source.js.map +1 -1
  145. package/dist/routes/fs.d.ts +31 -23
  146. package/dist/routes/fs.d.ts.map +1 -1
  147. package/dist/routes/fs.js +218 -64
  148. package/dist/routes/fs.js.map +1 -1
  149. package/dist/routes/intake.js +2 -1
  150. package/dist/routes/intake.js.map +1 -1
  151. package/dist/routes/knowledge.js +1 -1
  152. package/dist/routes/knowledge.js.map +1 -1
  153. package/dist/routes/oauth.js +3 -2
  154. package/dist/routes/oauth.js.map +1 -1
  155. package/dist/routes/projects.d.ts +1 -1
  156. package/dist/routes/projects.d.ts.map +1 -1
  157. package/dist/routes/projects.js +33 -31
  158. package/dist/routes/projects.js.map +1 -1
  159. package/dist/routes/provider-credentials.d.ts.map +1 -1
  160. package/dist/routes/provider-credentials.js +6 -1
  161. package/dist/routes/provider-credentials.js.map +1 -1
  162. package/dist/routes/session-access.d.ts +60 -0
  163. package/dist/routes/session-access.d.ts.map +1 -0
  164. package/dist/routes/session-access.js +202 -0
  165. package/dist/routes/session-access.js.map +1 -0
  166. package/dist/routes/skills.d.ts +2 -4
  167. package/dist/routes/skills.d.ts.map +1 -1
  168. package/dist/routes/skills.js +23 -68
  169. package/dist/routes/skills.js.map +1 -1
  170. package/dist/routes/source-control-sessions.d.ts +8 -1
  171. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  172. package/dist/routes/source-control-sessions.js +239 -19
  173. package/dist/routes/source-control-sessions.js.map +1 -1
  174. package/dist/routes/surface.d.ts +7 -0
  175. package/dist/routes/surface.d.ts.map +1 -1
  176. package/dist/routes/surface.js +20 -8
  177. package/dist/routes/surface.js.map +1 -1
  178. package/dist/routes/tenant-credentials.js.map +1 -1
  179. package/dist/routes/work-items.js.map +1 -1
  180. package/dist/rules/binding-context.js +11 -9
  181. package/dist/rules/binding-context.js.map +1 -1
  182. package/dist/rules/dispatcher.d.ts +1 -1
  183. package/dist/rules/dispatcher.d.ts.map +1 -1
  184. package/dist/rules/dispatcher.js +21 -13
  185. package/dist/rules/dispatcher.js.map +1 -1
  186. package/dist/rules/processor.d.ts +2 -2
  187. package/dist/rules/processor.d.ts.map +1 -1
  188. package/dist/rules/processor.js +3 -3
  189. package/dist/rules/processor.js.map +1 -1
  190. package/dist/rules/start-coordinator.d.ts +4 -0
  191. package/dist/rules/start-coordinator.d.ts.map +1 -1
  192. package/dist/rules/start-coordinator.js +36 -4
  193. package/dist/rules/start-coordinator.js.map +1 -1
  194. package/dist/rules/tools.js +2 -2
  195. package/dist/rules/tools.js.map +1 -1
  196. package/dist/rules/transition-service.js.map +1 -1
  197. package/dist/sandbox/callback-factory-sandbox.js.map +1 -1
  198. package/dist/sandbox/local-factory-sandbox.js.map +1 -1
  199. package/dist/sandbox/session-retirement.d.ts +11 -0
  200. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  201. package/dist/sandbox/session-retirement.js +38 -1
  202. package/dist/sandbox/session-retirement.js.map +1 -1
  203. package/dist/sandbox/session-sandbox.d.ts +2 -0
  204. package/dist/sandbox/session-sandbox.d.ts.map +1 -1
  205. package/dist/sandbox/session-sandbox.js +5 -1
  206. package/dist/sandbox/session-sandbox.js.map +1 -1
  207. package/dist/secret-encryption.d.ts.map +1 -1
  208. package/dist/secret-encryption.js +65 -25
  209. package/dist/secret-encryption.js.map +1 -1
  210. package/dist/session/credential-policy.d.ts +4 -2
  211. package/dist/session/credential-policy.d.ts.map +1 -1
  212. package/dist/session/credential-policy.js +18 -7
  213. package/dist/session/credential-policy.js.map +1 -1
  214. package/dist/session/default-model-hydration.d.ts +1 -0
  215. package/dist/session/default-model-hydration.d.ts.map +1 -1
  216. package/dist/session/default-model-hydration.js +3 -2
  217. package/dist/session/default-model-hydration.js.map +1 -1
  218. package/dist/session/environment-state-processor.js +1 -1
  219. package/dist/session/environment-state-processor.js.map +1 -1
  220. package/dist/session/factory-session.d.ts.map +1 -1
  221. package/dist/session/factory-session.js +14 -1
  222. package/dist/session/factory-session.js.map +1 -1
  223. package/dist/session/filesystem-capture.d.ts +3 -3
  224. package/dist/session/filesystem-capture.d.ts.map +1 -1
  225. package/dist/session/filesystem-capture.js +16 -17
  226. package/dist/session/filesystem-capture.js.map +1 -1
  227. package/dist/session/first-exec-capture.d.ts +1 -1
  228. package/dist/session/first-exec-capture.d.ts.map +1 -1
  229. package/dist/session/first-exec-capture.js +1 -1
  230. package/dist/session/first-exec-capture.js.map +1 -1
  231. package/dist/session/first-message-capture.d.ts +1 -1
  232. package/dist/session/first-message-capture.d.ts.map +1 -1
  233. package/dist/session/first-message-capture.js +1 -1
  234. package/dist/session/first-message-capture.js.map +1 -1
  235. package/dist/session/live-sessions.d.ts +1 -1
  236. package/dist/session/live-sessions.js +1 -1
  237. package/dist/session/live-sessions.js.map +1 -1
  238. package/dist/session/memory-settings-hydration.d.ts +1 -1
  239. package/dist/session/memory-settings-hydration.d.ts.map +1 -1
  240. package/dist/session/memory-settings-hydration.js +2 -1
  241. package/dist/session/memory-settings-hydration.js.map +1 -1
  242. package/dist/session/resource-migration.d.ts +105 -0
  243. package/dist/session/resource-migration.d.ts.map +1 -0
  244. package/dist/session/resource-migration.js +195 -0
  245. package/dist/session/resource-migration.js.map +1 -0
  246. package/dist/session/run-audit.d.ts +1 -0
  247. package/dist/session/run-audit.d.ts.map +1 -1
  248. package/dist/session/run-audit.js +7 -2
  249. package/dist/session/run-audit.js.map +1 -1
  250. package/dist/session/session-threads.d.ts +63 -0
  251. package/dist/session/session-threads.d.ts.map +1 -0
  252. package/dist/session/session-threads.js +97 -0
  253. package/dist/session/session-threads.js.map +1 -0
  254. package/dist/session/source-control-tools.js +8 -8
  255. package/dist/session/source-control-tools.js.map +1 -1
  256. package/dist/session/thinking-default.d.ts +6 -13
  257. package/dist/session/thinking-default.d.ts.map +1 -1
  258. package/dist/session/thinking-default.js +19 -24
  259. package/dist/session/thinking-default.js.map +1 -1
  260. package/dist/session/thread-session-stamp.d.ts +21 -0
  261. package/dist/session/thread-session-stamp.d.ts.map +1 -0
  262. package/dist/session/thread-session-stamp.js +20 -0
  263. package/dist/session/thread-session-stamp.js.map +1 -0
  264. package/dist/session/thread-title-mirror.d.ts +1 -1
  265. package/dist/session/thread-title-mirror.d.ts.map +1 -1
  266. package/dist/session/thread-title-mirror.js +1 -1
  267. package/dist/session/thread-title-mirror.js.map +1 -1
  268. package/dist/session-owner-signal.d.ts +2 -0
  269. package/dist/session-owner-signal.d.ts.map +1 -0
  270. package/dist/session-owner-signal.js +6 -0
  271. package/dist/session-owner-signal.js.map +1 -0
  272. package/dist/skills/service.d.ts +11 -14
  273. package/dist/skills/service.d.ts.map +1 -1
  274. package/dist/skills/service.js +12 -22
  275. package/dist/skills/service.js.map +1 -1
  276. package/dist/spa-static.js.map +1 -1
  277. package/dist/state-signing.js +1 -1
  278. package/dist/state-signing.js.map +1 -1
  279. package/dist/storage/domains/audit/actions.d.ts +1 -0
  280. package/dist/storage/domains/audit/actions.d.ts.map +1 -1
  281. package/dist/storage/domains/audit/actions.js +2 -1
  282. package/dist/storage/domains/audit/actions.js.map +1 -1
  283. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
  284. package/dist/storage/domains/audit/agent-audit.js +2 -2
  285. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  286. package/dist/storage/domains/audit/base.d.ts +18 -2
  287. package/dist/storage/domains/audit/base.d.ts.map +1 -1
  288. package/dist/storage/domains/audit/base.js +22 -4
  289. package/dist/storage/domains/audit/base.js.map +1 -1
  290. package/dist/storage/domains/audit/domain.js.map +1 -1
  291. package/dist/storage/domains/channel-identity/base.js.map +1 -1
  292. package/dist/storage/domains/comments/base.js.map +1 -1
  293. package/dist/storage/domains/comments/domain.js.map +1 -1
  294. package/dist/storage/domains/comments/feed-context.js.map +1 -1
  295. package/dist/storage/domains/credentials/base.js.map +1 -1
  296. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  297. package/dist/storage/domains/intake/base.js.map +1 -1
  298. package/dist/storage/domains/integrations/base.d.ts +8 -0
  299. package/dist/storage/domains/integrations/base.d.ts.map +1 -1
  300. package/dist/storage/domains/integrations/base.js +7 -0
  301. package/dist/storage/domains/integrations/base.js.map +1 -1
  302. package/dist/storage/domains/memory-settings/base.js.map +1 -1
  303. package/dist/storage/domains/model-defaults/base.d.ts +8 -0
  304. package/dist/storage/domains/model-defaults/base.d.ts.map +1 -1
  305. package/dist/storage/domains/model-defaults/base.js +18 -0
  306. package/dist/storage/domains/model-defaults/base.js.map +1 -1
  307. package/dist/storage/domains/projects/base.js.map +1 -1
  308. package/dist/storage/domains/queue-health/base.js.map +1 -1
  309. package/dist/storage/domains/source-control/base.d.ts +44 -0
  310. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  311. package/dist/storage/domains/source-control/base.js +109 -3
  312. package/dist/storage/domains/source-control/base.js.map +1 -1
  313. package/dist/storage/domains/source-control/inmemory.d.ts +23 -1
  314. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  315. package/dist/storage/domains/source-control/inmemory.js +35 -0
  316. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  317. package/dist/storage/domains/work-items/base.d.ts +14 -0
  318. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  319. package/dist/storage/domains/work-items/base.js +25 -1
  320. package/dist/storage/domains/work-items/base.js.map +1 -1
  321. package/dist/storage/domains/work-items/board-version.js.map +1 -1
  322. package/dist/storage/domains/work-items/metrics.js.map +1 -1
  323. package/dist/tools/approval-tools.js +1 -1
  324. package/dist/tools/approval-tools.js.map +1 -1
  325. package/dist/tools/deliver.d.ts +8 -5
  326. package/dist/tools/deliver.d.ts.map +1 -1
  327. package/dist/tools/deliver.js +22 -8
  328. package/dist/tools/deliver.js.map +1 -1
  329. package/dist/tools/messaging-tools.d.ts +1 -1
  330. package/dist/tools/messaging-tools.d.ts.map +1 -1
  331. package/dist/tools/messaging-tools.js +203 -72
  332. package/dist/tools/messaging-tools.js.map +1 -1
  333. package/dist/tools/permissions.d.ts +1 -1
  334. package/dist/tools/permissions.d.ts.map +1 -1
  335. package/dist/tools/permissions.js +3 -2
  336. package/dist/tools/permissions.js.map +1 -1
  337. package/dist/tools/reports.d.ts.map +1 -1
  338. package/dist/tools/reports.js +10 -3
  339. package/dist/tools/reports.js.map +1 -1
  340. package/dist/tools/run-watch.d.ts +2 -0
  341. package/dist/tools/run-watch.d.ts.map +1 -1
  342. package/dist/tools/run-watch.js +42 -2
  343. package/dist/tools/run-watch.js.map +1 -1
  344. package/dist/tools/sandbox-tools.d.ts +12 -0
  345. package/dist/tools/sandbox-tools.d.ts.map +1 -0
  346. package/dist/tools/sandbox-tools.js +40 -0
  347. package/dist/tools/sandbox-tools.js.map +1 -0
  348. package/dist/tools/scope.d.ts +2 -1
  349. package/dist/tools/scope.d.ts.map +1 -1
  350. package/dist/tools/scope.js +5 -7
  351. package/dist/tools/scope.js.map +1 -1
  352. package/dist/tools/session-tools.d.ts +1 -1
  353. package/dist/tools/session-tools.d.ts.map +1 -1
  354. package/dist/tools/session-tools.js +39 -18
  355. package/dist/tools/session-tools.js.map +1 -1
  356. package/dist/tools/sessions-state-processor.js +2 -2
  357. package/dist/tools/sessions-state-processor.js.map +1 -1
  358. package/dist/tools/side-sessions.d.ts +1 -1
  359. package/dist/tools/side-sessions.d.ts.map +1 -1
  360. package/dist/tools/side-sessions.js +1 -1
  361. package/dist/tools/side-sessions.js.map +1 -1
  362. package/dist/work-item-branch.d.ts +11 -3
  363. package/dist/work-item-branch.d.ts.map +1 -1
  364. package/dist/work-item-branch.js +34 -9
  365. package/dist/work-item-branch.js.map +1 -1
  366. package/dist/workspace.d.ts +7 -3
  367. package/dist/workspace.d.ts.map +1 -1
  368. package/dist/workspace.js +32 -17
  369. package/dist/workspace.js.map +1 -1
  370. package/factory-skills/configure-factory-rules/SKILL.md +2 -2
  371. package/package.json +16 -15
  372. package/dist/session/channel-reply-processor.d.ts.map +0 -1
  373. package/dist/session/channel-reply-processor.js +0 -46
  374. package/dist/session/channel-reply-processor.js.map +0 -1
  375. package/dist/session/channel-session-link-processor.d.ts.map +0 -1
  376. package/dist/session/channel-session-link-processor.js.map +0 -1
  377. /package/dist/{session → integrations/slack}/channel-session-link-processor.d.ts +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":["#migrateCredential","#db","#decryptCredential"],"sources":["../../../../src/storage/domains/credentials/base.ts"],"sourcesContent":["/**\n * Model provider credentials domain — tenant-scoped OAuth tokens and API keys\n * for model providers (Anthropic/Claude Max, OpenAI Codex, GitHub Copilot,\n * xAI, plus plain API-key providers).\n *\n * Tenancy is two-level:\n * - **user-scoped** rows (`userId` present): personal plan OAuth tokens and\n * personal API keys. OAuth plan tokens are ONLY ever user-scoped — they are\n * personal subscriptions, and sharing one would bill/rate-limit a single\n * account for the whole org.\n * - **org-scoped** rows (`userId` absent): shared API keys set by an org\n * admin, inherited by every member.\n *\n * Model calls resolve exactly one scope: user or org. Deployed tenant stores\n * never fall back to the other scope or implicitly borrow server environment keys.\n *\n * Secrets posture: credentials are stored server-side only and never returned\n * to the client (same as `linear_connections.access_token`). Encryption at\n * rest is out of scope, matching the existing posture.\n *\n * The domain also owns `oauth_login_sessions`: pending web login flows\n * (paste-code PKCE verifiers, device-code poll state) persisted server-side so\n * any replica can complete or poll a flow started on another.\n */\n\nimport type { AuthCredential, OAuthCredential } from '@mastra/code-sdk/auth/types';\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\nimport { createPlaintextFactorySecretEncryption } from '../../../secret-encryption.js';\nimport type { FactorySecretEncryption } from '../../../secret-encryption.js';\n\n/** Owning tenant of a credential row. `userId` absent = org-scoped row. */\nexport interface CredentialTenant {\n orgId: string;\n userId?: string;\n}\n\n/** Which tenancy level a credential row lives at. */\nexport type CredentialScope = 'user' | 'org';\n\n/** One stored credential, annotated with its scope. */\nexport interface CredentialRecord {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n updatedAt: Date;\n}\n\n/** Result of per-caller resolution: the winning credential and its scope. */\nexport interface ResolvedCredential {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n}\n\n/** Kind of pending web login flow a session row tracks. */\nexport type LoginSessionKind = 'paste-code' | 'device-code';\n\n/** Scope the completed credential should be written to. */\nexport type LoginCredentialScope = 'user' | 'org';\n\n/** One pending OAuth login flow, persisted so any replica can continue it. */\nexport interface LoginSessionRow {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n /** Where the completed credential lands; absent rows default to `'user'`. */\n credentialScope?: LoginCredentialScope;\n /** Serialized flow state (PKCE verifier, device-code pending state, ...). */\n pending: Record<string, unknown>;\n expiresAt: Date;\n /** Earliest time the next upstream poll is allowed (device-code flows). */\n nextPollAt: Date | null;\n createdAt: Date;\n}\n\nexport interface CreateLoginSessionInput {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n credentialScope?: LoginCredentialScope;\n pending: Record<string, unknown>;\n expiresAt: Date;\n nextPollAt?: Date | null;\n}\n\n/** Mirror of `AuthStorage.getApiKey()`'s expiry check. */\nexport function isOAuthCredentialExpired(credential: OAuthCredential, now = Date.now()): boolean {\n return now >= credential.expires;\n}\n\nexport const MODEL_CREDENTIALS_SCHEMA: CollectionSchema = {\n name: 'model_provider_credentials',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text', nullable: true },\n provider: { type: 'text' },\n type: { type: 'text' },\n data: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [\n // Scope is encoded in `user_id` nullability (NULL = org-scoped shared\n // row), enforced by two partial unique indexes.\n {\n name: 'model_provider_credentials_user_unique',\n columns: ['org_id', 'user_id', 'provider'],\n whereNotNull: 'user_id',\n },\n { name: 'model_provider_credentials_org_unique', columns: ['org_id', 'provider'], whereNull: 'user_id' },\n ],\n};\n\nexport const OAUTH_LOGIN_SESSIONS_SCHEMA: CollectionSchema = {\n name: 'oauth_login_sessions',\n columns: {\n session_id: { type: 'text', primaryKey: true },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n provider: { type: 'text' },\n kind: { type: 'text' },\n credential_scope: { type: 'text', nullable: true },\n pending: { type: 'json' },\n expires_at: { type: 'timestamp' },\n next_poll_at: { type: 'timestamp', nullable: true },\n created_at: { type: 'timestamp' },\n },\n};\n\n/** Column shape of one `model_provider_credentials` row as returned by ops. */\ninterface CredentialDbRow extends Record<string, unknown> {\n id: string;\n provider: string;\n user_id: string | null;\n data: unknown;\n updated_at: Date;\n}\n\n/** Column shape of one `oauth_login_sessions` row as returned by ops. */\ninterface LoginSessionDbRow extends Record<string, unknown> {\n session_id: string;\n org_id: string;\n user_id: string;\n provider: string;\n kind: LoginSessionKind;\n credential_scope: LoginCredentialScope | null;\n pending: Record<string, unknown>;\n expires_at: Date;\n next_poll_at: Date | null;\n created_at: Date;\n}\n\nfunction toSessionRow(db: LoginSessionDbRow): LoginSessionRow {\n return {\n sessionId: db.session_id,\n orgId: db.org_id,\n userId: db.user_id,\n provider: db.provider,\n kind: db.kind,\n ...(db.credential_scope ? { credentialScope: db.credential_scope } : {}),\n pending: db.pending,\n expiresAt: db.expires_at,\n nextPollAt: db.next_poll_at,\n createdAt: db.created_at,\n };\n}\n\n/** Filter selecting exactly the tenant's row for a provider (`null` = org row). */\nfunction tenantWhere(tenant: CredentialTenant, provider: string): CollectionWhere {\n return { org_id: tenant.orgId, provider, user_id: tenant.userId ?? null };\n}\n\n/**\n * Model-credentials storage, written once against the generic\n * `FactoryStorageOps` surface. `refreshOAuth()` and `claimLoginSession()`\n * ride `updateAtomic` so concurrent replicas serialize instead of\n * invalidating each other's rotating tokens / double-claiming a flow.\n */\nexport class ModelCredentialsStorage extends FactoryStorageDomain {\n constructor(private readonly encryption: FactorySecretEncryption = createPlaintextFactorySecretEncryption()) {\n super('model-credentials');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([MODEL_CREDENTIALS_SCHEMA, OAUTH_LOGIN_SESSIONS_SCHEMA]);\n const rows = await this.ops.findMany<CredentialDbRow>('model_provider_credentials', {});\n await Promise.all(rows.map(row => this.#migrateCredential(row.id)));\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('oauth_login_sessions', {});\n await this.ops.deleteMany('model_provider_credentials', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async #decryptCredential(value: unknown): Promise<AuthCredential> {\n return (await this.encryption.decrypt<AuthCredential>(value)).value;\n }\n\n async #migrateCredential(id: string): Promise<void> {\n await this.#db.updateAtomic<CredentialDbRow>('model_provider_credentials', { id }, async row => {\n const decrypted = await this.encryption.decrypt<AuthCredential>(row.data);\n if (!decrypted.needsReencryption) return null;\n return { data: await this.encryption.encrypt(decrypted.value) };\n });\n }\n\n /** Read the tenant's credential for a provider at exactly that scope. */\n async getCredential(tenant: CredentialTenant, provider: string): Promise<AuthCredential | undefined> {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', tenantWhere(tenant, provider));\n return row ? this.#decryptCredential(row.data) : undefined;\n }\n\n /** Upsert the tenant's credential (`created_at` is preserved on update). */\n async setCredential(tenant: CredentialTenant, provider: string, credential: AuthCredential): Promise<void> {\n const where = tenantWhere(tenant, provider);\n const encrypted = await this.encryption.encrypt(credential);\n const update = async () =>\n this.#db.updateMany('model_provider_credentials', where, {\n type: credential.type,\n data: encrypted,\n updated_at: new Date(),\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await this.#db.insertOne<CredentialDbRow>('model_provider_credentials', {\n org_id: tenant.orgId,\n user_id: tenant.userId ?? null,\n provider,\n type: credential.type,\n data: encrypted,\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n }\n\n /** Delete the tenant's credential at exactly that scope. True when a row was removed. */\n async removeCredential(tenant: CredentialTenant, provider: string): Promise<boolean> {\n const deleted = await this.#db.deleteMany('model_provider_credentials', tenantWhere(tenant, provider));\n return deleted > 0;\n }\n\n /** All credentials visible to a user: their own rows plus the org's shared rows. */\n async listCredentials(orgId: string, userId: string): Promise<CredentialRecord[]> {\n // SQL `IN` never matches NULL, so the org rows need their own query.\n const [userRows, orgRows] = await Promise.all([\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: userId }),\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: null }),\n ]);\n return Promise.all([\n ...userRows.map(async row => ({\n provider: row.provider,\n scope: 'user' as const,\n credential: await this.#decryptCredential(row.data),\n updatedAt: row.updated_at,\n })),\n ...orgRows.map(async row => ({\n provider: row.provider,\n scope: 'org' as const,\n credential: await this.#decryptCredential(row.data),\n updatedAt: row.updated_at,\n })),\n ]);\n }\n\n /** Resolve a provider credential from exactly one tenancy scope. */\n async resolveCredential(\n orgId: string,\n userId: string,\n provider: string,\n scope: CredentialScope,\n ): Promise<ResolvedCredential | undefined> {\n const readUser = async (): Promise<ResolvedCredential | undefined> => {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', {\n org_id: orgId,\n provider,\n user_id: userId,\n });\n return row\n ? { provider: row.provider, scope: 'user', credential: await this.#decryptCredential(row.data) }\n : undefined;\n };\n const readOrg = async (): Promise<ResolvedCredential | undefined> => {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', {\n org_id: orgId,\n provider,\n user_id: null,\n });\n return row\n ? { provider: row.provider, scope: 'org', credential: await this.#decryptCredential(row.data) }\n : undefined;\n };\n return scope === 'org' ? readOrg() : readUser();\n }\n\n /**\n * Refresh the tenant's OAuth credential under the backend's atomic\n * read-modify-write so concurrent replicas don't invalidate each other's\n * rotating refresh tokens. The expiry is re-checked under the lock —\n * another replica may have refreshed already, in which case `refreshFn` is\n * skipped and the fresh credential is returned. Returns `undefined` when no\n * OAuth row exists for the tenant.\n */\n async refreshOAuth(\n tenant: CredentialTenant,\n provider: string,\n refreshFn: (current: OAuthCredential) => Promise<OAuthCredential>,\n ): Promise<OAuthCredential | undefined> {\n let result: OAuthCredential | undefined;\n await this.#db.updateAtomic<CredentialDbRow>(\n 'model_provider_credentials',\n tenantWhere(tenant, provider),\n async row => {\n const decrypted = await this.encryption.decrypt<AuthCredential>(row.data);\n if (decrypted.value.type !== 'oauth') return null;\n const current = decrypted.value;\n // Re-check under the lock: another replica may have refreshed while we waited.\n if (!isOAuthCredentialExpired(current)) {\n result = current;\n return decrypted.needsReencryption ? { data: await this.encryption.encrypt(current) } : null;\n }\n const next = await refreshFn(current);\n result = next;\n return { data: await this.encryption.encrypt(next), updated_at: new Date() };\n },\n );\n return result;\n }\n\n /** Persist a pending login flow started by a web route. */\n async createLoginSession(input: CreateLoginSessionInput): Promise<LoginSessionRow> {\n const inserted = await this.#db.insertOne<LoginSessionDbRow>('oauth_login_sessions', {\n session_id: input.sessionId,\n org_id: input.orgId,\n user_id: input.userId,\n provider: input.provider,\n kind: input.kind,\n credential_scope: input.credentialScope ?? null,\n pending: input.pending,\n expires_at: input.expiresAt,\n next_poll_at: input.nextPollAt ?? null,\n created_at: new Date(),\n });\n return toSessionRow(inserted);\n }\n\n /**\n * Read a pending login flow. An expired session is deleted on read (TTL\n * cleanup) and reported as absent.\n */\n async getLoginSession(sessionId: string): Promise<LoginSessionRow | undefined> {\n const row = await this.#db.findOne<LoginSessionDbRow>('oauth_login_sessions', { session_id: sessionId });\n if (!row) return undefined;\n if (row.expires_at.getTime() <= Date.now()) {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n return undefined;\n }\n return toSessionRow(row);\n }\n\n /**\n * Atomically claim a due login session for one upstream completion/poll attempt.\n * Returns undefined when the session is missing, expired, owned by another\n * tenant/provider, or already claimed by a concurrent request.\n */\n async claimLoginSession(\n sessionId: string,\n owner: Pick<LoginSessionRow, 'orgId' | 'userId' | 'provider' | 'kind'>,\n ): Promise<LoginSessionRow | undefined> {\n const now = Date.now();\n let claimed = false;\n const updated = await this.#db.updateAtomic<LoginSessionDbRow>(\n 'oauth_login_sessions',\n { session_id: sessionId },\n row => {\n if (\n row.org_id !== owner.orgId ||\n row.user_id !== owner.userId ||\n row.provider !== owner.provider ||\n row.kind !== owner.kind\n ) {\n return null;\n }\n if (row.expires_at.getTime() <= now) return null;\n if (row.next_poll_at && row.next_poll_at.getTime() > now) return null;\n claimed = true;\n // Park the next poll at expiry so a concurrent claim loses until the\n // claimer either touches the session forward or deletes it.\n return { next_poll_at: row.expires_at };\n },\n );\n return claimed && updated ? toSessionRow(updated) : undefined;\n }\n\n /** Update a pending flow's serialized state and/or next allowed poll time. */\n async touchLoginSession(\n sessionId: string,\n updates: { pending?: Record<string, unknown>; nextPollAt?: Date | null },\n ): Promise<void> {\n const set: Record<string, unknown> = {};\n if (updates.pending !== undefined) set.pending = updates.pending;\n if (updates.nextPollAt !== undefined) set.next_poll_at = updates.nextPollAt;\n if (Object.keys(set).length === 0) return;\n await this.#db.updateMany('oauth_login_sessions', { session_id: sessionId }, set);\n }\n\n /** Remove a pending login flow (completed, cancelled, or failed). */\n async deleteLoginSession(sessionId: string): Promise<void> {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/credentials/base.ts"],"sourcesContent":["/**\n * Model provider credentials domain — tenant-scoped OAuth tokens and API keys\n * for model providers (Anthropic/Claude Max, OpenAI Codex, GitHub Copilot,\n * xAI, plus plain API-key providers).\n *\n * Tenancy is two-level:\n * - **user-scoped** rows (`userId` present): personal plan OAuth tokens and\n * personal API keys. OAuth plan tokens are ONLY ever user-scoped — they are\n * personal subscriptions, and sharing one would bill/rate-limit a single\n * account for the whole org.\n * - **org-scoped** rows (`userId` absent): shared API keys set by an org\n * admin, inherited by every member.\n *\n * Model calls resolve exactly one scope: user or org. Deployed tenant stores\n * never fall back to the other scope or implicitly borrow server environment keys.\n *\n * Secrets posture: credentials are stored server-side only and never returned\n * to the client (same as `linear_connections.access_token`). Encryption at\n * rest is out of scope, matching the existing posture.\n *\n * The domain also owns `oauth_login_sessions`: pending web login flows\n * (paste-code PKCE verifiers, device-code poll state) persisted server-side so\n * any replica can complete or poll a flow started on another.\n */\n\nimport type { AuthCredential, OAuthCredential } from '@mastra/code-sdk/auth/types';\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, CollectionWhere, FactoryStorageOps } from '@mastra/core/storage';\nimport { createPlaintextFactorySecretEncryption } from '../../../secret-encryption.js';\nimport type { FactorySecretEncryption } from '../../../secret-encryption.js';\n\n/** Owning tenant of a credential row. `userId` absent = org-scoped row. */\nexport interface CredentialTenant {\n orgId: string;\n userId?: string;\n}\n\n/** Which tenancy level a credential row lives at. */\nexport type CredentialScope = 'user' | 'org';\n\n/** One stored credential, annotated with its scope. */\nexport interface CredentialRecord {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n updatedAt: Date;\n}\n\n/** Result of per-caller resolution: the winning credential and its scope. */\nexport interface ResolvedCredential {\n provider: string;\n scope: CredentialScope;\n credential: AuthCredential;\n}\n\n/** Kind of pending web login flow a session row tracks. */\nexport type LoginSessionKind = 'paste-code' | 'device-code';\n\n/** Scope the completed credential should be written to. */\nexport type LoginCredentialScope = 'user' | 'org';\n\n/** One pending OAuth login flow, persisted so any replica can continue it. */\nexport interface LoginSessionRow {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n /** Where the completed credential lands; absent rows default to `'user'`. */\n credentialScope?: LoginCredentialScope;\n /** Serialized flow state (PKCE verifier, device-code pending state, ...). */\n pending: Record<string, unknown>;\n expiresAt: Date;\n /** Earliest time the next upstream poll is allowed (device-code flows). */\n nextPollAt: Date | null;\n createdAt: Date;\n}\n\nexport interface CreateLoginSessionInput {\n sessionId: string;\n orgId: string;\n userId: string;\n provider: string;\n kind: LoginSessionKind;\n credentialScope?: LoginCredentialScope;\n pending: Record<string, unknown>;\n expiresAt: Date;\n nextPollAt?: Date | null;\n}\n\n/** Mirror of `AuthStorage.getApiKey()`'s expiry check. */\nexport function isOAuthCredentialExpired(credential: OAuthCredential, now = Date.now()): boolean {\n return now >= credential.expires;\n}\n\nexport const MODEL_CREDENTIALS_SCHEMA: CollectionSchema = {\n name: 'model_provider_credentials',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text', nullable: true },\n provider: { type: 'text' },\n type: { type: 'text' },\n data: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [\n // Scope is encoded in `user_id` nullability (NULL = org-scoped shared\n // row), enforced by two partial unique indexes.\n {\n name: 'model_provider_credentials_user_unique',\n columns: ['org_id', 'user_id', 'provider'],\n whereNotNull: 'user_id',\n },\n { name: 'model_provider_credentials_org_unique', columns: ['org_id', 'provider'], whereNull: 'user_id' },\n ],\n};\n\nexport const OAUTH_LOGIN_SESSIONS_SCHEMA: CollectionSchema = {\n name: 'oauth_login_sessions',\n columns: {\n session_id: { type: 'text', primaryKey: true },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n provider: { type: 'text' },\n kind: { type: 'text' },\n credential_scope: { type: 'text', nullable: true },\n pending: { type: 'json' },\n expires_at: { type: 'timestamp' },\n next_poll_at: { type: 'timestamp', nullable: true },\n created_at: { type: 'timestamp' },\n },\n};\n\n/** Column shape of one `model_provider_credentials` row as returned by ops. */\ninterface CredentialDbRow extends Record<string, unknown> {\n id: string;\n provider: string;\n user_id: string | null;\n data: unknown;\n updated_at: Date;\n}\n\n/** Column shape of one `oauth_login_sessions` row as returned by ops. */\ninterface LoginSessionDbRow extends Record<string, unknown> {\n session_id: string;\n org_id: string;\n user_id: string;\n provider: string;\n kind: LoginSessionKind;\n credential_scope: LoginCredentialScope | null;\n pending: Record<string, unknown>;\n expires_at: Date;\n next_poll_at: Date | null;\n created_at: Date;\n}\n\nfunction toSessionRow(db: LoginSessionDbRow): LoginSessionRow {\n return {\n sessionId: db.session_id,\n orgId: db.org_id,\n userId: db.user_id,\n provider: db.provider,\n kind: db.kind,\n ...(db.credential_scope ? { credentialScope: db.credential_scope } : {}),\n pending: db.pending,\n expiresAt: db.expires_at,\n nextPollAt: db.next_poll_at,\n createdAt: db.created_at,\n };\n}\n\n/** Filter selecting exactly the tenant's row for a provider (`null` = org row). */\nfunction tenantWhere(tenant: CredentialTenant, provider: string): CollectionWhere {\n return { org_id: tenant.orgId, provider, user_id: tenant.userId ?? null };\n}\n\n/**\n * Model-credentials storage, written once against the generic\n * `FactoryStorageOps` surface. `refreshOAuth()` and `claimLoginSession()`\n * ride `updateAtomic` so concurrent replicas serialize instead of\n * invalidating each other's rotating tokens / double-claiming a flow.\n */\nexport class ModelCredentialsStorage extends FactoryStorageDomain {\n constructor(private readonly encryption: FactorySecretEncryption = createPlaintextFactorySecretEncryption()) {\n super('model-credentials');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([MODEL_CREDENTIALS_SCHEMA, OAUTH_LOGIN_SESSIONS_SCHEMA]);\n const rows = await this.ops.findMany<CredentialDbRow>('model_provider_credentials', {});\n await Promise.all(rows.map(row => this.#migrateCredential(row.id)));\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('oauth_login_sessions', {});\n await this.ops.deleteMany('model_provider_credentials', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async #decryptCredential(value: unknown): Promise<AuthCredential> {\n return (await this.encryption.decrypt<AuthCredential>(value)).value;\n }\n\n async #migrateCredential(id: string): Promise<void> {\n await this.#db.updateAtomic<CredentialDbRow>('model_provider_credentials', { id }, async row => {\n const decrypted = await this.encryption.decrypt<AuthCredential>(row.data);\n if (!decrypted.needsReencryption) return null;\n return { data: await this.encryption.encrypt(decrypted.value) };\n });\n }\n\n /** Read the tenant's credential for a provider at exactly that scope. */\n async getCredential(tenant: CredentialTenant, provider: string): Promise<AuthCredential | undefined> {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', tenantWhere(tenant, provider));\n return row ? this.#decryptCredential(row.data) : undefined;\n }\n\n /** Upsert the tenant's credential (`created_at` is preserved on update). */\n async setCredential(tenant: CredentialTenant, provider: string, credential: AuthCredential): Promise<void> {\n const where = tenantWhere(tenant, provider);\n const encrypted = await this.encryption.encrypt(credential);\n const update = async () =>\n this.#db.updateMany('model_provider_credentials', where, {\n type: credential.type,\n data: encrypted,\n updated_at: new Date(),\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await this.#db.insertOne<CredentialDbRow>('model_provider_credentials', {\n org_id: tenant.orgId,\n user_id: tenant.userId ?? null,\n provider,\n type: credential.type,\n data: encrypted,\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n }\n\n /** Delete the tenant's credential at exactly that scope. True when a row was removed. */\n async removeCredential(tenant: CredentialTenant, provider: string): Promise<boolean> {\n const deleted = await this.#db.deleteMany('model_provider_credentials', tenantWhere(tenant, provider));\n return deleted > 0;\n }\n\n /** All credentials visible to a user: their own rows plus the org's shared rows. */\n async listCredentials(orgId: string, userId: string): Promise<CredentialRecord[]> {\n // SQL `IN` never matches NULL, so the org rows need their own query.\n const [userRows, orgRows] = await Promise.all([\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: userId }),\n this.#db.findMany<CredentialDbRow>('model_provider_credentials', { org_id: orgId, user_id: null }),\n ]);\n return Promise.all([\n ...userRows.map(async row => ({\n provider: row.provider,\n scope: 'user' as const,\n credential: await this.#decryptCredential(row.data),\n updatedAt: row.updated_at,\n })),\n ...orgRows.map(async row => ({\n provider: row.provider,\n scope: 'org' as const,\n credential: await this.#decryptCredential(row.data),\n updatedAt: row.updated_at,\n })),\n ]);\n }\n\n /** Resolve a provider credential from exactly one tenancy scope. */\n async resolveCredential(\n orgId: string,\n userId: string,\n provider: string,\n scope: CredentialScope,\n ): Promise<ResolvedCredential | undefined> {\n const readUser = async (): Promise<ResolvedCredential | undefined> => {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', {\n org_id: orgId,\n provider,\n user_id: userId,\n });\n return row\n ? { provider: row.provider, scope: 'user', credential: await this.#decryptCredential(row.data) }\n : undefined;\n };\n const readOrg = async (): Promise<ResolvedCredential | undefined> => {\n const row = await this.#db.findOne<CredentialDbRow>('model_provider_credentials', {\n org_id: orgId,\n provider,\n user_id: null,\n });\n return row\n ? { provider: row.provider, scope: 'org', credential: await this.#decryptCredential(row.data) }\n : undefined;\n };\n return scope === 'org' ? readOrg() : readUser();\n }\n\n /**\n * Refresh the tenant's OAuth credential under the backend's atomic\n * read-modify-write so concurrent replicas don't invalidate each other's\n * rotating refresh tokens. The expiry is re-checked under the lock —\n * another replica may have refreshed already, in which case `refreshFn` is\n * skipped and the fresh credential is returned. Returns `undefined` when no\n * OAuth row exists for the tenant.\n */\n async refreshOAuth(\n tenant: CredentialTenant,\n provider: string,\n refreshFn: (current: OAuthCredential) => Promise<OAuthCredential>,\n ): Promise<OAuthCredential | undefined> {\n let result: OAuthCredential | undefined;\n await this.#db.updateAtomic<CredentialDbRow>(\n 'model_provider_credentials',\n tenantWhere(tenant, provider),\n async row => {\n const decrypted = await this.encryption.decrypt<AuthCredential>(row.data);\n if (decrypted.value.type !== 'oauth') return null;\n const current = decrypted.value;\n // Re-check under the lock: another replica may have refreshed while we waited.\n if (!isOAuthCredentialExpired(current)) {\n result = current;\n return decrypted.needsReencryption ? { data: await this.encryption.encrypt(current) } : null;\n }\n const next = await refreshFn(current);\n result = next;\n return { data: await this.encryption.encrypt(next), updated_at: new Date() };\n },\n );\n return result;\n }\n\n /** Persist a pending login flow started by a web route. */\n async createLoginSession(input: CreateLoginSessionInput): Promise<LoginSessionRow> {\n const inserted = await this.#db.insertOne<LoginSessionDbRow>('oauth_login_sessions', {\n session_id: input.sessionId,\n org_id: input.orgId,\n user_id: input.userId,\n provider: input.provider,\n kind: input.kind,\n credential_scope: input.credentialScope ?? null,\n pending: input.pending,\n expires_at: input.expiresAt,\n next_poll_at: input.nextPollAt ?? null,\n created_at: new Date(),\n });\n return toSessionRow(inserted);\n }\n\n /**\n * Read a pending login flow. An expired session is deleted on read (TTL\n * cleanup) and reported as absent.\n */\n async getLoginSession(sessionId: string): Promise<LoginSessionRow | undefined> {\n const row = await this.#db.findOne<LoginSessionDbRow>('oauth_login_sessions', { session_id: sessionId });\n if (!row) return undefined;\n if (row.expires_at.getTime() <= Date.now()) {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n return undefined;\n }\n return toSessionRow(row);\n }\n\n /**\n * Atomically claim a due login session for one upstream completion/poll attempt.\n * Returns undefined when the session is missing, expired, owned by another\n * tenant/provider, or already claimed by a concurrent request.\n */\n async claimLoginSession(\n sessionId: string,\n owner: Pick<LoginSessionRow, 'orgId' | 'userId' | 'provider' | 'kind'>,\n ): Promise<LoginSessionRow | undefined> {\n const now = Date.now();\n let claimed = false;\n const updated = await this.#db.updateAtomic<LoginSessionDbRow>(\n 'oauth_login_sessions',\n { session_id: sessionId },\n row => {\n if (\n row.org_id !== owner.orgId ||\n row.user_id !== owner.userId ||\n row.provider !== owner.provider ||\n row.kind !== owner.kind\n ) {\n return null;\n }\n if (row.expires_at.getTime() <= now) return null;\n if (row.next_poll_at && row.next_poll_at.getTime() > now) return null;\n claimed = true;\n // Park the next poll at expiry so a concurrent claim loses until the\n // claimer either touches the session forward or deletes it.\n return { next_poll_at: row.expires_at };\n },\n );\n return claimed && updated ? toSessionRow(updated) : undefined;\n }\n\n /** Update a pending flow's serialized state and/or next allowed poll time. */\n async touchLoginSession(\n sessionId: string,\n updates: { pending?: Record<string, unknown>; nextPollAt?: Date | null },\n ): Promise<void> {\n const set: Record<string, unknown> = {};\n if (updates.pending !== undefined) set.pending = updates.pending;\n if (updates.nextPollAt !== undefined) set.next_poll_at = updates.nextPollAt;\n if (Object.keys(set).length === 0) return;\n await this.#db.updateMany('oauth_login_sessions', { session_id: sessionId }, set);\n }\n\n /** Remove a pending login flow (completed, cancelled, or failed). */\n async deleteLoginSession(sessionId: string): Promise<void> {\n await this.#db.deleteMany('oauth_login_sessions', { session_id: sessionId });\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":["#migrateApiKey","#db","#toRecord","#decryptApiKey","#encryptApiKey","#write"],"sources":["../../../../src/storage/domains/custom-providers/base.ts"],"sourcesContent":["import { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\nimport { createPlaintextFactorySecretEncryption } from '../../../secret-encryption.js';\nimport type { FactorySecretEncryption } from '../../../secret-encryption.js';\n\n/**\n * A user-defined OpenAI-compatible provider. The DB-backed counterpart of the\n * local `settings.json` `customProviders` entries: org-scoped rows keyed by the\n * derived provider id (name slug), API key stored alongside so a deployed\n * server never reads the shared settings file.\n */\nexport interface CustomProviderRecord {\n id: string;\n orgId: string;\n createdBy: string;\n /** Stable slug derived from the name (`getCustomProviderId`). */\n providerId: string;\n name: string;\n url: string;\n apiKey: string | null;\n models: string[];\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface UpsertCustomProviderInput {\n providerId: string;\n name: string;\n url: string;\n apiKey?: string;\n models: string[];\n}\n\nexport const CUSTOM_PROVIDERS_SCHEMA: CollectionSchema = {\n name: 'custom_providers',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n created_by: { type: 'text' },\n provider_id: { type: 'text' },\n name: { type: 'text' },\n url: { type: 'text' },\n api_key: { type: 'text', nullable: true },\n models: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'custom_providers_org_provider_key', columns: ['org_id', 'provider_id'] }],\n};\n\ninterface CustomProviderDbRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n created_by: string;\n provider_id: string;\n name: string;\n url: string;\n api_key: string | null;\n models: string[];\n created_at: Date;\n updated_at: Date;\n}\n\nexport class CustomProvidersStorage extends FactoryStorageDomain {\n constructor(private readonly encryption: FactorySecretEncryption = createPlaintextFactorySecretEncryption()) {\n super('custom-providers');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([CUSTOM_PROVIDERS_SCHEMA]);\n const rows = await this.ops.findMany<CustomProviderDbRow>('custom_providers', {});\n await Promise.all(rows.filter(row => row.api_key !== null).map(row => this.#migrateApiKey(row.id)));\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('custom_providers', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async #toRecord(row: CustomProviderDbRow): Promise<CustomProviderRecord> {\n const apiKey = row.api_key === null ? null : (await this.#decryptApiKey(row.api_key)).value;\n return {\n id: row.id,\n orgId: row.org_id,\n createdBy: row.created_by,\n providerId: row.provider_id,\n name: row.name,\n url: row.url,\n apiKey,\n models: Array.isArray(row.models) ? row.models : [],\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n }\n\n /**\n * API keys are always strings. A legacy raw key that happens to be valid JSON\n * (e.g. `12345`) decodes to a non-string, so keep the stored text instead.\n */\n async #decryptApiKey(stored: string): Promise<{ value: string; needsReencryption: boolean }> {\n const decrypted = await this.encryption.decrypt<unknown>(stored);\n return {\n value: typeof decrypted.value === 'string' ? decrypted.value : stored,\n needsReencryption: decrypted.needsReencryption,\n };\n }\n\n async #encryptApiKey(apiKey: string | undefined): Promise<string | null> {\n return apiKey === undefined ? null : this.encryption.encrypt(apiKey);\n }\n\n async #migrateApiKey(id: string): Promise<void> {\n await this.#db.updateAtomic<CustomProviderDbRow>('custom_providers', { id }, async row => {\n if (row.api_key === null) return null;\n const decrypted = await this.#decryptApiKey(row.api_key);\n return decrypted.needsReencryption ? { api_key: await this.encryption.encrypt(decrypted.value) } : null;\n });\n }\n\n /**\n * Create or wholesale-replace a provider by `(orgId, providerId)` — mirrors\n * the settings.json upsert semantics (no key retention: an absent `apiKey`\n * clears the stored key). `previousProviderId` renames the provider: the\n * common path is a single atomic in-place update of the old row; renaming\n * onto an id that already exists overwrites the target first and removes the\n * old row last, so no interleaving or failure can lose the provider.\n */\n async upsert({\n orgId,\n userId,\n input,\n previousProviderId,\n }: {\n orgId: string;\n userId: string;\n input: UpsertCustomProviderInput;\n previousProviderId?: string;\n }): Promise<CustomProviderRecord> {\n const now = new Date();\n const encryptedApiKey = await this.#encryptApiKey(input.apiKey);\n const renameFrom = previousProviderId && previousProviderId !== input.providerId ? previousProviderId : undefined;\n\n if (renameFrom) {\n const target = await this.#db.findOne<CustomProviderDbRow>('custom_providers', {\n org_id: orgId,\n provider_id: input.providerId,\n });\n if (!target) {\n // In-place rename: one atomic write with no delete/insert gap, so a\n // failure can never drop the provider (and `created_at`/`created_by`\n // are preserved). If a concurrent create grabs the new id between the\n // lookup and this update, the unique index rejects the update and the\n // old row stays intact — the request fails cleanly and a retry\n // converges on the rename-onto-existing path below.\n const renamed = await this.#db.updateAtomic<CustomProviderDbRow>(\n 'custom_providers',\n { org_id: orgId, provider_id: renameFrom },\n () => ({\n provider_id: input.providerId,\n name: input.name,\n url: input.url,\n api_key: encryptedApiKey,\n models: input.models,\n updated_at: now,\n }),\n );\n if (renamed) return this.#toRecord(renamed);\n // Old row already gone — fall through to a plain create of the new id.\n }\n // Rename onto an existing id: overwrite the target below, then delete\n // the old row last — a failure in the gap leaves a redundant duplicate\n // (recoverable by re-submitting or deleting) instead of losing data.\n }\n\n const record = await this.#write({ orgId, userId, input, encryptedApiKey, now });\n if (renameFrom) {\n await this.#db.deleteMany('custom_providers', { org_id: orgId, provider_id: renameFrom });\n }\n return record;\n }\n\n /**\n * Update-first, then insert-and-catch-unique-violation (see `queue_health`):\n * concurrent creates of the same provider both succeed (last write wins on\n * the single row) instead of one failing on the unique index, and a row\n * deleted mid-flight is recreated rather than reported as a stale success.\n */\n async #write({\n orgId,\n userId,\n input,\n encryptedApiKey,\n now,\n }: {\n orgId: string;\n userId: string;\n input: UpsertCustomProviderInput;\n encryptedApiKey: string | null;\n now: Date;\n }): Promise<CustomProviderRecord> {\n const updateExisting = () =>\n this.#db.updateAtomic<CustomProviderDbRow>(\n 'custom_providers',\n { org_id: orgId, provider_id: input.providerId },\n () => ({\n name: input.name,\n url: input.url,\n api_key: encryptedApiKey,\n models: input.models,\n updated_at: now,\n }),\n );\n\n const updated = await updateExisting();\n if (updated) return this.#toRecord(updated);\n\n try {\n const row = await this.#db.insertOne<CustomProviderDbRow>('custom_providers', {\n org_id: orgId,\n created_by: userId,\n provider_id: input.providerId,\n name: input.name,\n url: input.url,\n api_key: encryptedApiKey,\n models: input.models,\n created_at: now,\n updated_at: now,\n });\n return this.#toRecord(row);\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n // Lost the insert race — apply this write to the winning row.\n const row = await updateExisting();\n if (!row) throw error;\n return this.#toRecord(row);\n }\n }\n\n async list({ orgId }: { orgId: string }): Promise<CustomProviderRecord[]> {\n const rows = await this.#db.findMany<CustomProviderDbRow>(\n 'custom_providers',\n { org_id: orgId },\n { orderBy: [['name', 'asc']] },\n );\n return Promise.all(rows.map(row => this.#toRecord(row)));\n }\n\n async delete({ orgId, providerId }: { orgId: string; providerId: string }): Promise<boolean> {\n return (await this.#db.deleteMany('custom_providers', { org_id: orgId, provider_id: providerId })) > 0;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/custom-providers/base.ts"],"sourcesContent":["import { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\nimport { createPlaintextFactorySecretEncryption } from '../../../secret-encryption.js';\nimport type { FactorySecretEncryption } from '../../../secret-encryption.js';\n\n/**\n * A user-defined OpenAI-compatible provider. The DB-backed counterpart of the\n * local `settings.json` `customProviders` entries: org-scoped rows keyed by the\n * derived provider id (name slug), API key stored alongside so a deployed\n * server never reads the shared settings file.\n */\nexport interface CustomProviderRecord {\n id: string;\n orgId: string;\n createdBy: string;\n /** Stable slug derived from the name (`getCustomProviderId`). */\n providerId: string;\n name: string;\n url: string;\n apiKey: string | null;\n models: string[];\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface UpsertCustomProviderInput {\n providerId: string;\n name: string;\n url: string;\n apiKey?: string;\n models: string[];\n}\n\nexport const CUSTOM_PROVIDERS_SCHEMA: CollectionSchema = {\n name: 'custom_providers',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n created_by: { type: 'text' },\n provider_id: { type: 'text' },\n name: { type: 'text' },\n url: { type: 'text' },\n api_key: { type: 'text', nullable: true },\n models: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'custom_providers_org_provider_key', columns: ['org_id', 'provider_id'] }],\n};\n\ninterface CustomProviderDbRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n created_by: string;\n provider_id: string;\n name: string;\n url: string;\n api_key: string | null;\n models: string[];\n created_at: Date;\n updated_at: Date;\n}\n\nexport class CustomProvidersStorage extends FactoryStorageDomain {\n constructor(private readonly encryption: FactorySecretEncryption = createPlaintextFactorySecretEncryption()) {\n super('custom-providers');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([CUSTOM_PROVIDERS_SCHEMA]);\n const rows = await this.ops.findMany<CustomProviderDbRow>('custom_providers', {});\n await Promise.all(rows.filter(row => row.api_key !== null).map(row => this.#migrateApiKey(row.id)));\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('custom_providers', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async #toRecord(row: CustomProviderDbRow): Promise<CustomProviderRecord> {\n const apiKey = row.api_key === null ? null : (await this.#decryptApiKey(row.api_key)).value;\n return {\n id: row.id,\n orgId: row.org_id,\n createdBy: row.created_by,\n providerId: row.provider_id,\n name: row.name,\n url: row.url,\n apiKey,\n models: Array.isArray(row.models) ? row.models : [],\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n }\n\n /**\n * API keys are always strings. A legacy raw key that happens to be valid JSON\n * (e.g. `12345`) decodes to a non-string, so keep the stored text instead.\n */\n async #decryptApiKey(stored: string): Promise<{ value: string; needsReencryption: boolean }> {\n const decrypted = await this.encryption.decrypt<unknown>(stored);\n return {\n value: typeof decrypted.value === 'string' ? decrypted.value : stored,\n needsReencryption: decrypted.needsReencryption,\n };\n }\n\n async #encryptApiKey(apiKey: string | undefined): Promise<string | null> {\n return apiKey === undefined ? null : this.encryption.encrypt(apiKey);\n }\n\n async #migrateApiKey(id: string): Promise<void> {\n await this.#db.updateAtomic<CustomProviderDbRow>('custom_providers', { id }, async row => {\n if (row.api_key === null) return null;\n const decrypted = await this.#decryptApiKey(row.api_key);\n return decrypted.needsReencryption ? { api_key: await this.encryption.encrypt(decrypted.value) } : null;\n });\n }\n\n /**\n * Create or wholesale-replace a provider by `(orgId, providerId)` — mirrors\n * the settings.json upsert semantics (no key retention: an absent `apiKey`\n * clears the stored key). `previousProviderId` renames the provider: the\n * common path is a single atomic in-place update of the old row; renaming\n * onto an id that already exists overwrites the target first and removes the\n * old row last, so no interleaving or failure can lose the provider.\n */\n async upsert({\n orgId,\n userId,\n input,\n previousProviderId,\n }: {\n orgId: string;\n userId: string;\n input: UpsertCustomProviderInput;\n previousProviderId?: string;\n }): Promise<CustomProviderRecord> {\n const now = new Date();\n const encryptedApiKey = await this.#encryptApiKey(input.apiKey);\n const renameFrom = previousProviderId && previousProviderId !== input.providerId ? previousProviderId : undefined;\n\n if (renameFrom) {\n const target = await this.#db.findOne<CustomProviderDbRow>('custom_providers', {\n org_id: orgId,\n provider_id: input.providerId,\n });\n if (!target) {\n // In-place rename: one atomic write with no delete/insert gap, so a\n // failure can never drop the provider (and `created_at`/`created_by`\n // are preserved). If a concurrent create grabs the new id between the\n // lookup and this update, the unique index rejects the update and the\n // old row stays intact — the request fails cleanly and a retry\n // converges on the rename-onto-existing path below.\n const renamed = await this.#db.updateAtomic<CustomProviderDbRow>(\n 'custom_providers',\n { org_id: orgId, provider_id: renameFrom },\n () => ({\n provider_id: input.providerId,\n name: input.name,\n url: input.url,\n api_key: encryptedApiKey,\n models: input.models,\n updated_at: now,\n }),\n );\n if (renamed) return this.#toRecord(renamed);\n // Old row already gone — fall through to a plain create of the new id.\n }\n // Rename onto an existing id: overwrite the target below, then delete\n // the old row last — a failure in the gap leaves a redundant duplicate\n // (recoverable by re-submitting or deleting) instead of losing data.\n }\n\n const record = await this.#write({ orgId, userId, input, encryptedApiKey, now });\n if (renameFrom) {\n await this.#db.deleteMany('custom_providers', { org_id: orgId, provider_id: renameFrom });\n }\n return record;\n }\n\n /**\n * Update-first, then insert-and-catch-unique-violation (see `queue_health`):\n * concurrent creates of the same provider both succeed (last write wins on\n * the single row) instead of one failing on the unique index, and a row\n * deleted mid-flight is recreated rather than reported as a stale success.\n */\n async #write({\n orgId,\n userId,\n input,\n encryptedApiKey,\n now,\n }: {\n orgId: string;\n userId: string;\n input: UpsertCustomProviderInput;\n encryptedApiKey: string | null;\n now: Date;\n }): Promise<CustomProviderRecord> {\n const updateExisting = () =>\n this.#db.updateAtomic<CustomProviderDbRow>(\n 'custom_providers',\n { org_id: orgId, provider_id: input.providerId },\n () => ({\n name: input.name,\n url: input.url,\n api_key: encryptedApiKey,\n models: input.models,\n updated_at: now,\n }),\n );\n\n const updated = await updateExisting();\n if (updated) return this.#toRecord(updated);\n\n try {\n const row = await this.#db.insertOne<CustomProviderDbRow>('custom_providers', {\n org_id: orgId,\n created_by: userId,\n provider_id: input.providerId,\n name: input.name,\n url: input.url,\n api_key: encryptedApiKey,\n models: input.models,\n created_at: now,\n updated_at: now,\n });\n return this.#toRecord(row);\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n // Lost the insert race — apply this write to the winning row.\n const row = await updateExisting();\n if (!row) throw error;\n return this.#toRecord(row);\n }\n }\n\n async list({ orgId }: { orgId: string }): Promise<CustomProviderRecord[]> {\n const rows = await this.#db.findMany<CustomProviderDbRow>(\n 'custom_providers',\n { org_id: orgId },\n { orderBy: [['name', 'asc']] },\n );\n return Promise.all(rows.map(row => this.#toRecord(row)));\n }\n\n async delete({ orgId, providerId }: { orgId: string; providerId: string }): Promise<boolean> {\n return (await this.#db.deleteMany('custom_providers', { org_id: orgId, provider_id: providerId })) > 0;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":["#foldLegacyUserSelections","#insertConfigIfAbsent","#db"],"sources":["../../../../src/storage/domains/intake/base.ts"],"sourcesContent":["/**\n * Org-wide intake selections for every configured intake integration.\n *\n * Integration ids are dynamic. Each integration contributes provider-neutral\n * sources through `FactoryIntegration.intake`; this domain only persists which\n * source ids the organization selected. The selection feeds shared boards, so\n * every member reads the same one.\n */\n\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\n\nexport interface IntakeSelection {\n enabled: boolean;\n /** Provider-owned source ids; `null` means nothing is selected. */\n sourceIds: string[] | null;\n repositoryByLinearProject?: Record<string, string>;\n}\n\nexport type IntakeConfig = Record<string, IntakeSelection>;\n\nexport const DEFAULT_INTAKE_CONFIG: IntakeConfig = {};\n\nexport const INTAKE_ORG_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'intake_org_settings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n config: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'intake_org_settings_org_unique', columns: ['org_id'] }],\n};\n\n/** Collection evolution is additive, so the per-member unique index cannot be narrowed: rows stay and are folded at boot. */\nexport const LEGACY_INTAKE_USER_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'intake_settings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n config: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'intake_settings_org_user_unique', columns: ['org_id', 'user_id'] }],\n};\n\n/** What the org effectively saw while selections were personal: a member's sources fed the shared board only while their switch was on. */\nexport function mergeIntakeSelections(configs: readonly IntakeConfig[]): IntakeConfig {\n const merged: IntakeConfig = Object.create(null);\n for (const config of configs) {\n for (const [integrationId, selection] of Object.entries(config)) {\n const current = merged[integrationId] ?? { enabled: false, sourceIds: null };\n const syncing = selection.enabled ? (selection.sourceIds ?? []) : [];\n const sourceIds = [...new Set([...(current.sourceIds ?? []), ...syncing])];\n merged[integrationId] = {\n enabled: current.enabled || selection.enabled,\n sourceIds: sourceIds.length ? sourceIds : null,\n };\n }\n }\n return merged;\n}\n\n/**\n * Binds an intake source to the Factory project its items belong to.\n *\n * Intake selections are org-wide, so they cannot say *where* an\n * ingested item should land. GitHub items are naturally scoped by their linked\n * repository; providers without that link (Linear) need this explicit binding\n * so viewing one project's board cannot materialize another project's items.\n */\nexport const INTAKE_SOURCE_BINDINGS_SCHEMA: CollectionSchema = {\n name: 'intake_source_bindings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n integration_id: { type: 'text' },\n source_id: { type: 'text' },\n factory_project_id: { type: 'text' },\n /** Installed board the source feeds; `null` keeps the legacy source-type routing (issues → work, PRs → review). */\n board: { type: 'text', nullable: true },\n created_by_user_id: { type: 'text', nullable: true },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n indexes: [{ name: 'intake_source_bindings_project_idx', columns: ['org_id', 'factory_project_id'] }],\n uniqueIndexes: [\n {\n name: 'intake_source_bindings_org_integration_source_unique',\n columns: ['org_id', 'integration_id', 'source_id'],\n },\n ],\n};\n\nexport interface IntakeSourceBinding {\n integrationId: string;\n sourceId: string;\n factoryProjectId: string;\n /** Installed board the source feeds, or `null` for legacy source-type routing. */\n board: string | null;\n}\n\n/**\n * Routes items carrying a label to an installed board, per Factory project.\n *\n * Repository-scoped providers (GitHub) already know which project an item\n * belongs to, but one repository can feed several boards: an issue labelled\n * `release` belongs on a release board while the rest stay on Work. Labels\n * without a route fall back to the provider's built-in board.\n */\nexport const INTAKE_LABEL_ROUTES_SCHEMA: CollectionSchema = {\n name: 'intake_label_routes',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n factory_project_id: { type: 'text' },\n integration_id: { type: 'text' },\n /** Label as the provider reports it; matched case-insensitively. */\n label: { type: 'text' },\n board: { type: 'text' },\n created_by_user_id: { type: 'text', nullable: true },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n indexes: [{ name: 'intake_label_routes_project_idx', columns: ['org_id', 'factory_project_id'] }],\n uniqueIndexes: [\n {\n name: 'intake_label_routes_org_project_integration_label_unique',\n columns: ['org_id', 'factory_project_id', 'integration_id', 'label'],\n },\n ],\n};\n\nexport interface IntakeLabelRoute {\n factoryProjectId: string;\n integrationId: string;\n label: string;\n board: string;\n}\n\ntype IntakeLabelRouteRow = {\n factory_project_id: string;\n integration_id: string;\n label: string;\n board: string;\n};\nfunction toIntakeLabelRoute(row: IntakeLabelRouteRow): IntakeLabelRoute {\n return {\n factoryProjectId: row.factory_project_id,\n integrationId: row.integration_id,\n label: row.label,\n board: row.board,\n };\n}\n\n/** Labels are matched the way GitHub treats them: case-insensitively. */\nexport function normalizeIntakeLabel(label: string): string {\n return label.trim().toLowerCase();\n}\n\n/**\n * The board a labelled item should land on under `routes`, or `undefined` when\n * none of its labels is routed. Ties resolve to the first route in `routes`\n * order, so callers should pass a deterministically ordered list.\n */\nexport function resolveIntakeLabelRoute(\n routes: readonly IntakeLabelRoute[],\n labels: readonly string[] | undefined,\n): IntakeLabelRoute | undefined {\n if (!labels?.length) return undefined;\n const normalized = new Set(labels.map(normalizeIntakeLabel));\n return routes.find(route => normalized.has(normalizeIntakeLabel(route.label)));\n}\n\ntype IntakeSourceBindingRow = {\n integration_id: string;\n source_id: string;\n factory_project_id: string;\n board?: string | null;\n};\nfunction toIntakeSourceBinding(row: IntakeSourceBindingRow): IntakeSourceBinding {\n return {\n integrationId: row.integration_id,\n sourceId: row.source_id,\n factoryProjectId: row.factory_project_id,\n board: row.board ?? null,\n };\n}\n\nexport class IntakeStorage extends FactoryStorageDomain {\n constructor() {\n super('intake');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([\n INTAKE_ORG_SETTINGS_SCHEMA,\n LEGACY_INTAKE_USER_SETTINGS_SCHEMA,\n INTAKE_SOURCE_BINDINGS_SCHEMA,\n INTAKE_LABEL_ROUTES_SCHEMA,\n ]);\n await this.#foldLegacyUserSelections();\n }\n\n async #foldLegacyUserSelections(): Promise<void> {\n const legacyRows = await this.ops.findMany<{ org_id: string; config: IntakeConfig }>('intake_settings', {});\n const byOrg = new Map<string, IntakeConfig[]>();\n for (const row of legacyRows) {\n byOrg.set(row.org_id, [...(byOrg.get(row.org_id) ?? []), row.config]);\n }\n for (const [orgId, configs] of byOrg) {\n const folded = await this.ops.findOne('intake_org_settings', { org_id: orgId });\n if (folded) continue;\n await this.#insertConfigIfAbsent(orgId, mergeIntakeSelections(configs));\n }\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('intake_org_settings', {});\n await this.ops.deleteMany('intake_settings', {});\n await this.ops.deleteMany('intake_source_bindings', {});\n await this.ops.deleteMany('intake_label_routes', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async getConfig({\n orgId,\n integrationIds,\n }: {\n orgId: string;\n /**\n * When provided, the result contains exactly these integration ids, with\n * unset integrations defaulting to `{ enabled: true, sourceIds: null }`.\n */\n integrationIds?: string[];\n }): Promise<IntakeConfig> {\n const row = await this.#db.findOne<{ config: IntakeConfig }>('intake_org_settings', { org_id: orgId });\n const saved = structuredClone(row?.config ?? DEFAULT_INTAKE_CONFIG);\n if (!integrationIds) return saved;\n return Object.fromEntries(\n integrationIds.map(integrationId => [integrationId, saved[integrationId] ?? { enabled: true, sourceIds: null }]),\n );\n }\n\n async saveConfig({ orgId, config }: { orgId: string; config: IntakeConfig }): Promise<void> {\n const now = new Date();\n const where = { org_id: orgId };\n const updated = await this.#db.updateMany('intake_org_settings', where, { config, updated_at: now });\n if (updated > 0) return;\n if (await this.#insertConfigIfAbsent(orgId, config)) return;\n await this.#db.updateMany('intake_org_settings', where, { config, updated_at: now });\n }\n\n async #insertConfigIfAbsent(orgId: string, config: IntakeConfig): Promise<boolean> {\n const now = new Date();\n try {\n await this.#db.insertOne('intake_org_settings', { org_id: orgId, config, created_at: now, updated_at: now });\n return true;\n } catch (error) {\n if (error instanceof UniqueViolationError) return false;\n throw error;\n }\n }\n\n /** Every source binding in the org, optionally narrowed to one integration. */\n async listBindings({\n orgId,\n integrationId,\n }: {\n orgId: string;\n integrationId?: string;\n }): Promise<IntakeSourceBinding[]> {\n const rows = await this.#db.findMany<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n ...(integrationId ? { integration_id: integrationId } : {}),\n });\n return rows.map(toIntakeSourceBinding);\n }\n\n /** Source ids bound to one Factory project. Empty means \"nothing bound\". */\n async listBoundSourceIds({\n orgId,\n integrationId,\n factoryProjectId,\n }: {\n orgId: string;\n integrationId: string;\n factoryProjectId: string;\n }): Promise<string[]> {\n const rows = await this.#db.findMany<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n factory_project_id: factoryProjectId,\n });\n return rows.map(row => row.source_id);\n }\n\n /** The binding for one source, or `null` when it is unbound. */\n async getBinding({\n orgId,\n integrationId,\n sourceId,\n }: {\n orgId: string;\n integrationId: string;\n sourceId: string;\n }): Promise<IntakeSourceBinding | null> {\n const row = await this.#db.findOne<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n source_id: sourceId,\n });\n return row ? toIntakeSourceBinding(row) : null;\n }\n\n async clearBinding({\n orgId,\n integrationId,\n sourceId,\n }: {\n orgId: string;\n integrationId: string;\n sourceId: string;\n }): Promise<IntakeSourceBinding | null> {\n const where = { org_id: orgId, integration_id: integrationId, source_id: sourceId };\n return this.storage.withTransaction(\n async ops => {\n const row = await ops.findOne<IntakeSourceBindingRow>('intake_source_bindings', where);\n if (!row) return null;\n await ops.deleteMany('intake_source_bindings', where);\n return toIntakeSourceBinding(row);\n },\n { isolationLevel: 'serializable' },\n );\n }\n\n async setBinding({\n orgId,\n integrationId,\n sourceId,\n factoryProjectId,\n board = null,\n userId,\n }: {\n orgId: string;\n integrationId: string;\n sourceId: string;\n factoryProjectId: string;\n board?: string | null;\n userId?: string;\n }): Promise<void> {\n const where = { org_id: orgId, integration_id: integrationId, source_id: sourceId };\n const now = new Date();\n const patch = { factory_project_id: factoryProjectId, board, updated_at: now };\n const updated = await this.#db.updateMany('intake_source_bindings', where, patch);\n if (updated > 0) return;\n try {\n await this.#db.insertOne('intake_source_bindings', {\n ...where,\n factory_project_id: factoryProjectId,\n board,\n created_by_user_id: userId ?? null,\n created_at: now,\n updated_at: now,\n });\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n await this.#db.updateMany('intake_source_bindings', where, patch);\n }\n }\n /** Atomically replace legacy provider source ids in both selection and routing state. */\n async migrateSourceIds({\n orgId,\n integrationId,\n migrations,\n }: {\n orgId: string;\n integrationId: string;\n migrations: Array<{ from: string; to: string }>;\n }): Promise<{ migrations: Array<{ from: string; to: string }>; conflicts: Array<{ from: string; to: string }> }> {\n if (migrations.length === 0) return { migrations: [], conflicts: [] };\n return this.storage.withTransaction(\n async ops => {\n const bindings = await ops.findMany<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n });\n const bindingBySource = new Map(bindings.map(binding => [binding.source_id, binding] as const));\n const grouped = new Map<string, Array<{ from: string; to: string }>>();\n for (const migration of migrations) {\n grouped.set(migration.to, [...(grouped.get(migration.to) ?? []), migration]);\n }\n const conflicts: Array<{ from: string; to: string }> = [];\n for (const [to, group] of grouped) {\n const candidates = [bindingBySource.get(to), ...group.map(({ from }) => bindingBySource.get(from))].filter(\n (binding): binding is IntakeSourceBindingRow => binding !== undefined,\n );\n const destinations = new Set(\n candidates.map(binding => `${binding.factory_project_id}\\0${binding.board ?? ''}`),\n );\n if (destinations.size > 1) conflicts.push(...group);\n }\n if (conflicts.length > 0) return { migrations: [], conflicts };\n\n const now = new Date();\n for (const [to, group] of grouped) {\n const canonical = bindingBySource.get(to);\n const legacy = group.flatMap(({ from }) => {\n const binding = bindingBySource.get(from);\n return binding ? [{ from, binding }] : [];\n });\n if (!canonical && legacy.length > 0) {\n const [first, ...duplicates] = legacy;\n await ops.updateMany(\n 'intake_source_bindings',\n { org_id: orgId, integration_id: integrationId, source_id: first!.from },\n { source_id: to, updated_at: now },\n );\n for (const duplicate of duplicates) {\n await ops.deleteMany('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n source_id: duplicate.from,\n });\n }\n } else if (canonical) {\n for (const entry of legacy) {\n await ops.deleteMany('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n source_id: entry.from,\n });\n }\n }\n }\n\n const configRow = await ops.findOne<{ config: IntakeConfig }>('intake_org_settings', { org_id: orgId });\n if (configRow) {\n const replacements = new Map(migrations.map(({ from, to }) => [from, to] as const));\n const selected = configRow.config[integrationId]?.sourceIds ?? [];\n const nextSelected = [...new Set(selected.map(sourceId => replacements.get(sourceId) ?? sourceId))];\n if (\n nextSelected.length !== selected.length ||\n nextSelected.some((sourceId, index) => sourceId !== selected[index])\n ) {\n const config = structuredClone(configRow.config);\n config[integrationId] = {\n ...(config[integrationId] ?? { enabled: true }),\n sourceIds: nextSelected.length > 0 ? nextSelected : null,\n };\n await ops.updateMany('intake_org_settings', { org_id: orgId }, { config, updated_at: now });\n }\n }\n return { migrations, conflicts: [] };\n },\n { isolationLevel: 'serializable' },\n );\n }\n\n /** Label routes in the org, optionally narrowed to one project and/or integration. Sorted by label. */\n async listLabelRoutes({\n orgId,\n factoryProjectId,\n integrationId,\n }: {\n orgId: string;\n factoryProjectId?: string;\n integrationId?: string;\n }): Promise<IntakeLabelRoute[]> {\n const rows = await this.#db.findMany<IntakeLabelRouteRow>('intake_label_routes', {\n org_id: orgId,\n ...(factoryProjectId ? { factory_project_id: factoryProjectId } : {}),\n ...(integrationId ? { integration_id: integrationId } : {}),\n });\n return rows.map(toIntakeLabelRoute).sort((a, b) => a.label.localeCompare(b.label));\n }\n\n async setLabelRoute({\n orgId,\n factoryProjectId,\n integrationId,\n label,\n board,\n userId,\n }: {\n orgId: string;\n factoryProjectId: string;\n integrationId: string;\n label: string;\n board: string;\n userId?: string;\n }): Promise<void> {\n const where = {\n org_id: orgId,\n factory_project_id: factoryProjectId,\n integration_id: integrationId,\n label: normalizeIntakeLabel(label),\n };\n const now = new Date();\n const patch = { board, updated_at: now };\n const updated = await this.#db.updateMany('intake_label_routes', where, patch);\n if (updated > 0) return;\n try {\n await this.#db.insertOne('intake_label_routes', {\n ...where,\n board,\n created_by_user_id: userId ?? null,\n created_at: now,\n updated_at: now,\n });\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n await this.#db.updateMany('intake_label_routes', where, patch);\n }\n }\n\n /** Remove a label route; returns the route that was removed, or `null` when none existed. */\n async clearLabelRoute({\n orgId,\n factoryProjectId,\n integrationId,\n label,\n }: {\n orgId: string;\n factoryProjectId: string;\n integrationId: string;\n label: string;\n }): Promise<IntakeLabelRoute | null> {\n const where = {\n org_id: orgId,\n factory_project_id: factoryProjectId,\n integration_id: integrationId,\n label: normalizeIntakeLabel(label),\n };\n return this.storage.withTransaction(\n async ops => {\n const row = await ops.findOne<IntakeLabelRouteRow>('intake_label_routes', where);\n if (!row) return null;\n await ops.deleteMany('intake_label_routes', where);\n return toIntakeLabelRoute(row);\n },\n { isolationLevel: 'serializable' },\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/intake/base.ts"],"sourcesContent":["/**\n * Org-wide intake selections for every configured intake integration.\n *\n * Integration ids are dynamic. Each integration contributes provider-neutral\n * sources through `FactoryIntegration.intake`; this domain only persists which\n * source ids the organization selected. The selection feeds shared boards, so\n * every member reads the same one.\n */\n\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\n\nexport interface IntakeSelection {\n enabled: boolean;\n /** Provider-owned source ids; `null` means nothing is selected. */\n sourceIds: string[] | null;\n repositoryByLinearProject?: Record<string, string>;\n}\n\nexport type IntakeConfig = Record<string, IntakeSelection>;\n\nexport const DEFAULT_INTAKE_CONFIG: IntakeConfig = {};\n\nexport const INTAKE_ORG_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'intake_org_settings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n config: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'intake_org_settings_org_unique', columns: ['org_id'] }],\n};\n\n/** Collection evolution is additive, so the per-member unique index cannot be narrowed: rows stay and are folded at boot. */\nexport const LEGACY_INTAKE_USER_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'intake_settings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n config: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'intake_settings_org_user_unique', columns: ['org_id', 'user_id'] }],\n};\n\n/** What the org effectively saw while selections were personal: a member's sources fed the shared board only while their switch was on. */\nexport function mergeIntakeSelections(configs: readonly IntakeConfig[]): IntakeConfig {\n const merged: IntakeConfig = Object.create(null);\n for (const config of configs) {\n for (const [integrationId, selection] of Object.entries(config)) {\n const current = merged[integrationId] ?? { enabled: false, sourceIds: null };\n const syncing = selection.enabled ? (selection.sourceIds ?? []) : [];\n const sourceIds = [...new Set([...(current.sourceIds ?? []), ...syncing])];\n merged[integrationId] = {\n enabled: current.enabled || selection.enabled,\n sourceIds: sourceIds.length ? sourceIds : null,\n };\n }\n }\n return merged;\n}\n\n/**\n * Binds an intake source to the Factory project its items belong to.\n *\n * Intake selections are org-wide, so they cannot say *where* an\n * ingested item should land. GitHub items are naturally scoped by their linked\n * repository; providers without that link (Linear) need this explicit binding\n * so viewing one project's board cannot materialize another project's items.\n */\nexport const INTAKE_SOURCE_BINDINGS_SCHEMA: CollectionSchema = {\n name: 'intake_source_bindings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n integration_id: { type: 'text' },\n source_id: { type: 'text' },\n factory_project_id: { type: 'text' },\n /** Installed board the source feeds; `null` keeps the legacy source-type routing (issues → work, PRs → review). */\n board: { type: 'text', nullable: true },\n created_by_user_id: { type: 'text', nullable: true },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n indexes: [{ name: 'intake_source_bindings_project_idx', columns: ['org_id', 'factory_project_id'] }],\n uniqueIndexes: [\n {\n name: 'intake_source_bindings_org_integration_source_unique',\n columns: ['org_id', 'integration_id', 'source_id'],\n },\n ],\n};\n\nexport interface IntakeSourceBinding {\n integrationId: string;\n sourceId: string;\n factoryProjectId: string;\n /** Installed board the source feeds, or `null` for legacy source-type routing. */\n board: string | null;\n}\n\n/**\n * Routes items carrying a label to an installed board, per Factory project.\n *\n * Repository-scoped providers (GitHub) already know which project an item\n * belongs to, but one repository can feed several boards: an issue labelled\n * `release` belongs on a release board while the rest stay on Work. Labels\n * without a route fall back to the provider's built-in board.\n */\nexport const INTAKE_LABEL_ROUTES_SCHEMA: CollectionSchema = {\n name: 'intake_label_routes',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n factory_project_id: { type: 'text' },\n integration_id: { type: 'text' },\n /** Label as the provider reports it; matched case-insensitively. */\n label: { type: 'text' },\n board: { type: 'text' },\n created_by_user_id: { type: 'text', nullable: true },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n indexes: [{ name: 'intake_label_routes_project_idx', columns: ['org_id', 'factory_project_id'] }],\n uniqueIndexes: [\n {\n name: 'intake_label_routes_org_project_integration_label_unique',\n columns: ['org_id', 'factory_project_id', 'integration_id', 'label'],\n },\n ],\n};\n\nexport interface IntakeLabelRoute {\n factoryProjectId: string;\n integrationId: string;\n label: string;\n board: string;\n}\n\ntype IntakeLabelRouteRow = {\n factory_project_id: string;\n integration_id: string;\n label: string;\n board: string;\n};\nfunction toIntakeLabelRoute(row: IntakeLabelRouteRow): IntakeLabelRoute {\n return {\n factoryProjectId: row.factory_project_id,\n integrationId: row.integration_id,\n label: row.label,\n board: row.board,\n };\n}\n\n/** Labels are matched the way GitHub treats them: case-insensitively. */\nexport function normalizeIntakeLabel(label: string): string {\n return label.trim().toLowerCase();\n}\n\n/**\n * The board a labelled item should land on under `routes`, or `undefined` when\n * none of its labels is routed. Ties resolve to the first route in `routes`\n * order, so callers should pass a deterministically ordered list.\n */\nexport function resolveIntakeLabelRoute(\n routes: readonly IntakeLabelRoute[],\n labels: readonly string[] | undefined,\n): IntakeLabelRoute | undefined {\n if (!labels?.length) return undefined;\n const normalized = new Set(labels.map(normalizeIntakeLabel));\n return routes.find(route => normalized.has(normalizeIntakeLabel(route.label)));\n}\n\ntype IntakeSourceBindingRow = {\n integration_id: string;\n source_id: string;\n factory_project_id: string;\n board?: string | null;\n};\nfunction toIntakeSourceBinding(row: IntakeSourceBindingRow): IntakeSourceBinding {\n return {\n integrationId: row.integration_id,\n sourceId: row.source_id,\n factoryProjectId: row.factory_project_id,\n board: row.board ?? null,\n };\n}\n\nexport class IntakeStorage extends FactoryStorageDomain {\n constructor() {\n super('intake');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([\n INTAKE_ORG_SETTINGS_SCHEMA,\n LEGACY_INTAKE_USER_SETTINGS_SCHEMA,\n INTAKE_SOURCE_BINDINGS_SCHEMA,\n INTAKE_LABEL_ROUTES_SCHEMA,\n ]);\n await this.#foldLegacyUserSelections();\n }\n\n async #foldLegacyUserSelections(): Promise<void> {\n const legacyRows = await this.ops.findMany<{ org_id: string; config: IntakeConfig }>('intake_settings', {});\n const byOrg = new Map<string, IntakeConfig[]>();\n for (const row of legacyRows) {\n byOrg.set(row.org_id, [...(byOrg.get(row.org_id) ?? []), row.config]);\n }\n for (const [orgId, configs] of byOrg) {\n const folded = await this.ops.findOne('intake_org_settings', { org_id: orgId });\n if (folded) continue;\n await this.#insertConfigIfAbsent(orgId, mergeIntakeSelections(configs));\n }\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('intake_org_settings', {});\n await this.ops.deleteMany('intake_settings', {});\n await this.ops.deleteMany('intake_source_bindings', {});\n await this.ops.deleteMany('intake_label_routes', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async getConfig({\n orgId,\n integrationIds,\n }: {\n orgId: string;\n /**\n * When provided, the result contains exactly these integration ids, with\n * unset integrations defaulting to `{ enabled: true, sourceIds: null }`.\n */\n integrationIds?: string[];\n }): Promise<IntakeConfig> {\n const row = await this.#db.findOne<{ config: IntakeConfig }>('intake_org_settings', { org_id: orgId });\n const saved = structuredClone(row?.config ?? DEFAULT_INTAKE_CONFIG);\n if (!integrationIds) return saved;\n return Object.fromEntries(\n integrationIds.map(integrationId => [integrationId, saved[integrationId] ?? { enabled: true, sourceIds: null }]),\n );\n }\n\n async saveConfig({ orgId, config }: { orgId: string; config: IntakeConfig }): Promise<void> {\n const now = new Date();\n const where = { org_id: orgId };\n const updated = await this.#db.updateMany('intake_org_settings', where, { config, updated_at: now });\n if (updated > 0) return;\n if (await this.#insertConfigIfAbsent(orgId, config)) return;\n await this.#db.updateMany('intake_org_settings', where, { config, updated_at: now });\n }\n\n async #insertConfigIfAbsent(orgId: string, config: IntakeConfig): Promise<boolean> {\n const now = new Date();\n try {\n await this.#db.insertOne('intake_org_settings', { org_id: orgId, config, created_at: now, updated_at: now });\n return true;\n } catch (error) {\n if (error instanceof UniqueViolationError) return false;\n throw error;\n }\n }\n\n /** Every source binding in the org, optionally narrowed to one integration. */\n async listBindings({\n orgId,\n integrationId,\n }: {\n orgId: string;\n integrationId?: string;\n }): Promise<IntakeSourceBinding[]> {\n const rows = await this.#db.findMany<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n ...(integrationId ? { integration_id: integrationId } : {}),\n });\n return rows.map(toIntakeSourceBinding);\n }\n\n /** Source ids bound to one Factory project. Empty means \"nothing bound\". */\n async listBoundSourceIds({\n orgId,\n integrationId,\n factoryProjectId,\n }: {\n orgId: string;\n integrationId: string;\n factoryProjectId: string;\n }): Promise<string[]> {\n const rows = await this.#db.findMany<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n factory_project_id: factoryProjectId,\n });\n return rows.map(row => row.source_id);\n }\n\n /** The binding for one source, or `null` when it is unbound. */\n async getBinding({\n orgId,\n integrationId,\n sourceId,\n }: {\n orgId: string;\n integrationId: string;\n sourceId: string;\n }): Promise<IntakeSourceBinding | null> {\n const row = await this.#db.findOne<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n source_id: sourceId,\n });\n return row ? toIntakeSourceBinding(row) : null;\n }\n\n async clearBinding({\n orgId,\n integrationId,\n sourceId,\n }: {\n orgId: string;\n integrationId: string;\n sourceId: string;\n }): Promise<IntakeSourceBinding | null> {\n const where = { org_id: orgId, integration_id: integrationId, source_id: sourceId };\n return this.storage.withTransaction(\n async ops => {\n const row = await ops.findOne<IntakeSourceBindingRow>('intake_source_bindings', where);\n if (!row) return null;\n await ops.deleteMany('intake_source_bindings', where);\n return toIntakeSourceBinding(row);\n },\n { isolationLevel: 'serializable' },\n );\n }\n\n async setBinding({\n orgId,\n integrationId,\n sourceId,\n factoryProjectId,\n board = null,\n userId,\n }: {\n orgId: string;\n integrationId: string;\n sourceId: string;\n factoryProjectId: string;\n board?: string | null;\n userId?: string;\n }): Promise<void> {\n const where = { org_id: orgId, integration_id: integrationId, source_id: sourceId };\n const now = new Date();\n const patch = { factory_project_id: factoryProjectId, board, updated_at: now };\n const updated = await this.#db.updateMany('intake_source_bindings', where, patch);\n if (updated > 0) return;\n try {\n await this.#db.insertOne('intake_source_bindings', {\n ...where,\n factory_project_id: factoryProjectId,\n board,\n created_by_user_id: userId ?? null,\n created_at: now,\n updated_at: now,\n });\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n await this.#db.updateMany('intake_source_bindings', where, patch);\n }\n }\n /** Atomically replace legacy provider source ids in both selection and routing state. */\n async migrateSourceIds({\n orgId,\n integrationId,\n migrations,\n }: {\n orgId: string;\n integrationId: string;\n migrations: Array<{ from: string; to: string }>;\n }): Promise<{ migrations: Array<{ from: string; to: string }>; conflicts: Array<{ from: string; to: string }> }> {\n if (migrations.length === 0) return { migrations: [], conflicts: [] };\n return this.storage.withTransaction(\n async ops => {\n const bindings = await ops.findMany<IntakeSourceBindingRow>('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n });\n const bindingBySource = new Map(bindings.map(binding => [binding.source_id, binding] as const));\n const grouped = new Map<string, Array<{ from: string; to: string }>>();\n for (const migration of migrations) {\n grouped.set(migration.to, [...(grouped.get(migration.to) ?? []), migration]);\n }\n const conflicts: Array<{ from: string; to: string }> = [];\n for (const [to, group] of grouped) {\n const candidates = [bindingBySource.get(to), ...group.map(({ from }) => bindingBySource.get(from))].filter(\n (binding): binding is IntakeSourceBindingRow => binding !== undefined,\n );\n const destinations = new Set(\n candidates.map(binding => `${binding.factory_project_id}\\0${binding.board ?? ''}`),\n );\n if (destinations.size > 1) conflicts.push(...group);\n }\n if (conflicts.length > 0) return { migrations: [], conflicts };\n\n const now = new Date();\n for (const [to, group] of grouped) {\n const canonical = bindingBySource.get(to);\n const legacy = group.flatMap(({ from }) => {\n const binding = bindingBySource.get(from);\n return binding ? [{ from, binding }] : [];\n });\n if (!canonical && legacy.length > 0) {\n const [first, ...duplicates] = legacy;\n await ops.updateMany(\n 'intake_source_bindings',\n { org_id: orgId, integration_id: integrationId, source_id: first!.from },\n { source_id: to, updated_at: now },\n );\n for (const duplicate of duplicates) {\n await ops.deleteMany('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n source_id: duplicate.from,\n });\n }\n } else if (canonical) {\n for (const entry of legacy) {\n await ops.deleteMany('intake_source_bindings', {\n org_id: orgId,\n integration_id: integrationId,\n source_id: entry.from,\n });\n }\n }\n }\n\n const configRow = await ops.findOne<{ config: IntakeConfig }>('intake_org_settings', { org_id: orgId });\n if (configRow) {\n const replacements = new Map(migrations.map(({ from, to }) => [from, to] as const));\n const selected = configRow.config[integrationId]?.sourceIds ?? [];\n const nextSelected = [...new Set(selected.map(sourceId => replacements.get(sourceId) ?? sourceId))];\n if (\n nextSelected.length !== selected.length ||\n nextSelected.some((sourceId, index) => sourceId !== selected[index])\n ) {\n const config = structuredClone(configRow.config);\n config[integrationId] = {\n ...(config[integrationId] ?? { enabled: true }),\n sourceIds: nextSelected.length > 0 ? nextSelected : null,\n };\n await ops.updateMany('intake_org_settings', { org_id: orgId }, { config, updated_at: now });\n }\n }\n return { migrations, conflicts: [] };\n },\n { isolationLevel: 'serializable' },\n );\n }\n\n /** Label routes in the org, optionally narrowed to one project and/or integration. Sorted by label. */\n async listLabelRoutes({\n orgId,\n factoryProjectId,\n integrationId,\n }: {\n orgId: string;\n factoryProjectId?: string;\n integrationId?: string;\n }): Promise<IntakeLabelRoute[]> {\n const rows = await this.#db.findMany<IntakeLabelRouteRow>('intake_label_routes', {\n org_id: orgId,\n ...(factoryProjectId ? { factory_project_id: factoryProjectId } : {}),\n ...(integrationId ? { integration_id: integrationId } : {}),\n });\n return rows.map(toIntakeLabelRoute).sort((a, b) => a.label.localeCompare(b.label));\n }\n\n async setLabelRoute({\n orgId,\n factoryProjectId,\n integrationId,\n label,\n board,\n userId,\n }: {\n orgId: string;\n factoryProjectId: string;\n integrationId: string;\n label: string;\n board: string;\n userId?: string;\n }): Promise<void> {\n const where = {\n org_id: orgId,\n factory_project_id: factoryProjectId,\n integration_id: integrationId,\n label: normalizeIntakeLabel(label),\n };\n const now = new Date();\n const patch = { board, updated_at: now };\n const updated = await this.#db.updateMany('intake_label_routes', where, patch);\n if (updated > 0) return;\n try {\n await this.#db.insertOne('intake_label_routes', {\n ...where,\n board,\n created_by_user_id: userId ?? null,\n created_at: now,\n updated_at: now,\n });\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n await this.#db.updateMany('intake_label_routes', where, patch);\n }\n }\n\n /** Remove a label route; returns the route that was removed, or `null` when none existed. */\n async clearLabelRoute({\n orgId,\n factoryProjectId,\n integrationId,\n label,\n }: {\n orgId: string;\n factoryProjectId: string;\n integrationId: string;\n label: string;\n }): Promise<IntakeLabelRoute | null> {\n const where = {\n org_id: orgId,\n factory_project_id: factoryProjectId,\n integration_id: integrationId,\n label: normalizeIntakeLabel(label),\n };\n return this.storage.withTransaction(\n async ops => {\n const row = await ops.findOne<IntakeLabelRouteRow>('intake_label_routes', where);\n if (!row) return null;\n await ops.deleteMany('intake_label_routes', where);\n return toIntakeLabelRoute(row);\n },\n { isolationLevel: 'serializable' },\n );\n }\n}\n"],"mappings":"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"}
@@ -121,6 +121,14 @@ export declare class IntegrationStorage extends FactoryStorageDomain {
121
121
  private readonly encryption;
122
122
  constructor(encryption?: FactorySecretEncryption);
123
123
  init(): Promise<void>;
124
+ /**
125
+ * Move every subscription still recorded under `fromResourceId` onto
126
+ * `toResourceId`, across integrations. A rerun finds nothing to move.
127
+ */
128
+ moveSubscriptionsToResource(input: {
129
+ fromResourceId: string;
130
+ toResourceId: string;
131
+ }): Promise<number>;
124
132
  dangerouslyClearAll(): Promise<void>;
125
133
  /** A typed handle pre-scoped to `integrationId`. */
126
134
  forIntegration<TConnection = Record<string, unknown>, TSettings = Record<string, unknown>, TSubscription = Record<string, unknown>>(integrationId: string): IntegrationStorageHandle<TConnection, TSettings, TSubscription>;
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/integrations/base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/integrations/base.ts"],"names":[],"mappings":"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"}
@@ -118,6 +118,13 @@ var IntegrationStorage = class extends FactoryStorageDomain {
118
118
  const [connections, settings] = await Promise.all([this.ops.findMany("integration_connections", {}), this.ops.findMany("integration_settings", {})]);
119
119
  await Promise.all([...connections.map((row) => this.#migrateValue("integration_connections", row.id, "data")), ...settings.map((row) => this.#migrateValue("integration_settings", row.id, "config"))]);
120
120
  }
121
+ /**
122
+ * Move every subscription still recorded under `fromResourceId` onto
123
+ * `toResourceId`, across integrations. A rerun finds nothing to move.
124
+ */
125
+ async moveSubscriptionsToResource(input) {
126
+ return this.ops.updateMany("integration_subscriptions", { resource_id: input.fromResourceId }, { resource_id: input.toResourceId });
127
+ }
121
128
  async dangerouslyClearAll() {
122
129
  await this.ops.deleteMany("integration_subscriptions", {});
123
130
  await this.ops.deleteMany("integration_settings", {});
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":["#migrateValue","#db"],"sources":["../../../../src/storage/domains/integrations/base.ts"],"sourcesContent":["/**\n * Generic integration storage — the default persistence surface for\n * `FactoryIntegration`s.\n *\n * Most integrations persist the same three shapes: an org-owned OAuth\n * **connection**, **subscriptions** binding external objects to\n * sessions/threads, and per-`(org, user)` **settings**. This built-in domain\n * stores all three in shared collections keyed by `integration_id`, so an\n * integration author writes zero schema, zero DDL, and zero queries — and\n * org scoping is enforced by construction through the pre-scoped handle\n * returned by {@link IntegrationStorage.forIntegration}.\n *\n * Source-control integrations additionally use the shared, provider-scoped\n * source-control domain for installations, projects, and sessions.\n *\n * Payloads (`data` / `config`) are JSON documents typed per-integration via\n * the handle's generics. JSON round-trips exactly what JSON can represent:\n * store timestamps inside payloads as epoch millis or ISO strings, not\n * `Date`s.\n */\n\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\nimport { createPlaintextFactorySecretEncryption } from '../../../secret-encryption.js';\nimport type { FactorySecretEncryption } from '../../../secret-encryption.js';\n\nexport const INTEGRATION_CONNECTIONS_SCHEMA: CollectionSchema = {\n name: 'integration_connections',\n columns: {\n id: { type: 'uuid-pk' },\n integration_id: { type: 'text' },\n org_id: { type: 'text' },\n /** Who connected it (audit only — never scopes reads). */\n user_id: { type: 'text', nullable: true },\n data: { type: 'json' },\n metadata: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'integration_connections_integration_org_unique', columns: ['integration_id', 'org_id'] }],\n};\n\nexport const INTEGRATION_SUBSCRIPTIONS_SCHEMA: CollectionSchema = {\n name: 'integration_subscriptions',\n columns: {\n id: { type: 'uuid-pk' },\n integration_id: { type: 'text' },\n org_id: { type: 'text' },\n /** Canonical external-object key the integration derives (e.g. `pr:{installation}:{repo}:{number}`). */\n target_key: { type: 'text' },\n session_id: { type: 'text', nullable: true },\n resource_id: { type: 'text', nullable: true },\n thread_id: { type: 'text', nullable: true },\n session_scope: { type: 'text', nullable: true },\n status: { type: 'text' },\n data: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n indexes: [\n { name: 'integration_subscriptions_target_idx', columns: ['integration_id', 'target_key'] },\n { name: 'integration_subscriptions_org_idx', columns: ['integration_id', 'org_id'] },\n ],\n};\n\nexport const INTEGRATION_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'integration_settings',\n columns: {\n id: { type: 'uuid-pk' },\n integration_id: { type: 'text' },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n config: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [\n { name: 'integration_settings_integration_org_user_unique', columns: ['integration_id', 'org_id', 'user_id'] },\n ],\n};\n\n/** An integration's org-owned connection (typically the OAuth grant). */\nexport interface IntegrationConnection<TData> {\n id: string;\n orgId: string;\n /** Who connected it (audit only). */\n userId: string | null;\n data: TData;\n metadata: Record<string, unknown>;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/** A subscription binding an external object to a session/thread. */\nexport interface IntegrationSubscription<TData> {\n id: string;\n orgId: string;\n targetKey: string;\n sessionId: string | null;\n resourceId: string | null;\n threadId: string | null;\n sessionScope: string | null;\n status: string;\n data: TData;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport type CreateIntegrationSubscriptionInput<TData> = {\n orgId: string;\n targetKey: string;\n sessionId?: string | null;\n resourceId?: string | null;\n threadId?: string | null;\n sessionScope?: string | null;\n /** Defaults to `'active'`. */\n status?: string;\n} & ({} extends TData ? { data?: TData } : { data: TData });\n\n/**\n * Typed query surface pre-scoped to one `integration_id`. Every read and\n * write is automatically filtered/stamped with the integration id, so an\n * integration cannot reach another integration's rows.\n */\nexport interface IntegrationStorageHandle<\n TConnection = Record<string, unknown>,\n TSettings = Record<string, unknown>,\n TSubscription = Record<string, unknown>,\n> {\n readonly integrationId: string;\n connections: {\n /** The org's connection, or `null` when not connected. */\n get(orgId: string): Promise<IntegrationConnection<TConnection> | null>;\n /** Insert or replace the org's connection (one per org; `created_at` preserved on update). */\n upsert(\n orgId: string,\n input: { userId?: string | null; data: TConnection; metadata?: Record<string, unknown> },\n ): Promise<void>;\n /**\n * Atomic read-modify-write of the connection's `data` payload (token\n * rotation etc.). Returns the updated connection, or `null` when the org\n * has no connection.\n */\n update(orgId: string, fn: (data: TConnection) => TConnection): Promise<IntegrationConnection<TConnection> | null>;\n /** Remove the org's connection. Returns whether a row was deleted. */\n delete(orgId: string): Promise<boolean>;\n };\n subscriptions: {\n create(input: CreateIntegrationSubscriptionInput<TSubscription>): Promise<IntegrationSubscription<TSubscription>>;\n /** All subscriptions for an external object (webhook fan-out), optionally filtered by status. */\n listByTarget(targetKey: string, opts?: { status?: string }): Promise<IntegrationSubscription<TSubscription>[]>;\n listBySession(sessionId: string): Promise<IntegrationSubscription<TSubscription>[]>;\n listByThread(resourceId: string, threadId: string): Promise<IntegrationSubscription<TSubscription>[]>;\n updateStatus(id: string, status: string): Promise<void>;\n delete(id: string): Promise<boolean>;\n /** Targeted cleanup, always org-scoped. Returns the number of rows deleted. */\n deleteWhere(where: { orgId: string; targetKey?: string; sessionId?: string }): Promise<number>;\n };\n settings: {\n /** The `(org, user)` config, or `null` when never saved. */\n get(orgId: string, userId: string): Promise<TSettings | null>;\n /** Upsert the `(org, user)` config (`created_at` preserved on update). */\n save(orgId: string, userId: string, config: TSettings): Promise<void>;\n };\n}\n\ninterface ConnectionRow extends Record<string, unknown> {\n id: string;\n integration_id: string;\n org_id: string;\n user_id: string | null;\n data: unknown;\n metadata: Record<string, unknown>;\n created_at: Date;\n updated_at: Date;\n}\n\ninterface SubscriptionRow extends Record<string, unknown> {\n id: string;\n integration_id: string;\n org_id: string;\n target_key: string;\n session_id: string | null;\n resource_id: string | null;\n thread_id: string | null;\n session_scope: string | null;\n status: string;\n data: unknown;\n created_at: Date;\n updated_at: Date;\n}\n\n/**\n * The built-in generic integration domain, written once against the\n * `FactoryStorageOps` surface — works on any `FactoryStorage` backend.\n */\nexport class IntegrationStorage extends FactoryStorageDomain {\n constructor(private readonly encryption: FactorySecretEncryption = createPlaintextFactorySecretEncryption()) {\n super('integrations');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([\n INTEGRATION_CONNECTIONS_SCHEMA,\n INTEGRATION_SUBSCRIPTIONS_SCHEMA,\n INTEGRATION_SETTINGS_SCHEMA,\n ]);\n const [connections, settings] = await Promise.all([\n this.ops.findMany<ConnectionRow>('integration_connections', {}),\n this.ops.findMany<{ id: string; config: unknown }>('integration_settings', {}),\n ]);\n await Promise.all([\n ...connections.map(row => this.#migrateValue('integration_connections', row.id, 'data')),\n ...settings.map(row => this.#migrateValue('integration_settings', row.id, 'config')),\n ]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('integration_subscriptions', {});\n await this.ops.deleteMany('integration_settings', {});\n await this.ops.deleteMany('integration_connections', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async #migrateValue(collection: string, id: string, column: 'data' | 'config'): Promise<void> {\n await this.#db.updateAtomic<Record<string, unknown>>(collection, { id }, async row => {\n const decrypted = await this.encryption.decrypt(row[column]);\n return decrypted.needsReencryption ? { [column]: await this.encryption.encrypt(decrypted.value) } : null;\n });\n }\n\n /** A typed handle pre-scoped to `integrationId`. */\n forIntegration<\n TConnection = Record<string, unknown>,\n TSettings = Record<string, unknown>,\n TSubscription = Record<string, unknown>,\n >(integrationId: string): IntegrationStorageHandle<TConnection, TSettings, TSubscription> {\n if (!integrationId) throw new Error('[IntegrationStorage] integrationId is required.');\n const db = () => this.#db;\n const scoped = { integration_id: integrationId };\n\n const mapConnection = async (row: ConnectionRow): Promise<IntegrationConnection<TConnection>> => ({\n id: row.id,\n orgId: row.org_id,\n userId: row.user_id,\n data: (await this.encryption.decrypt<TConnection>(row.data)).value,\n metadata: row.metadata ?? {},\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n });\n\n const mapSubscription = (row: SubscriptionRow): IntegrationSubscription<TSubscription> => ({\n id: row.id,\n orgId: row.org_id,\n targetKey: row.target_key,\n sessionId: row.session_id,\n resourceId: row.resource_id,\n threadId: row.thread_id,\n sessionScope: row.session_scope,\n status: row.status,\n data: row.data as TSubscription,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n });\n\n return {\n integrationId,\n connections: {\n get: async orgId => {\n const row = await db().findOne<ConnectionRow>('integration_connections', { ...scoped, org_id: orgId });\n return row ? mapConnection(row) : null;\n },\n upsert: async (orgId, input) => {\n const where = { ...scoped, org_id: orgId };\n const encrypted = await this.encryption.encrypt(input.data);\n const update = async () =>\n db().updateMany('integration_connections', where, {\n user_id: input.userId ?? null,\n data: encrypted,\n metadata: input.metadata ?? {},\n updated_at: new Date(),\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await db().insertOne<ConnectionRow>('integration_connections', {\n ...where,\n user_id: input.userId ?? null,\n data: encrypted,\n metadata: input.metadata ?? {},\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n },\n update: async (orgId, fn) => {\n const row = await db().updateAtomic<ConnectionRow>(\n 'integration_connections',\n { ...scoped, org_id: orgId },\n async current => {\n const data = (await this.encryption.decrypt<TConnection>(current.data)).value;\n return { data: await this.encryption.encrypt(fn(data)), updated_at: new Date() };\n },\n );\n return row ? mapConnection(row) : null;\n },\n delete: async orgId => {\n const deleted = await db().deleteMany('integration_connections', { ...scoped, org_id: orgId });\n return deleted > 0;\n },\n },\n subscriptions: {\n create: async input => {\n const now = new Date();\n const row = await db().insertOne<SubscriptionRow>('integration_subscriptions', {\n ...scoped,\n org_id: input.orgId,\n target_key: input.targetKey,\n session_id: input.sessionId ?? null,\n resource_id: input.resourceId ?? null,\n thread_id: input.threadId ?? null,\n session_scope: input.sessionScope ?? null,\n status: input.status ?? 'active',\n data: input.data ?? {},\n created_at: now,\n updated_at: now,\n });\n return mapSubscription(row);\n },\n listByTarget: async (targetKey, opts) => {\n const rows = await db().findMany<SubscriptionRow>(\n 'integration_subscriptions',\n { ...scoped, target_key: targetKey, ...(opts?.status !== undefined ? { status: opts.status } : {}) },\n { orderBy: [['created_at', 'asc']] },\n );\n return rows.map(mapSubscription);\n },\n listBySession: async sessionId => {\n const rows = await db().findMany<SubscriptionRow>(\n 'integration_subscriptions',\n { ...scoped, session_id: sessionId },\n { orderBy: [['created_at', 'asc']] },\n );\n return rows.map(mapSubscription);\n },\n listByThread: async (resourceId, threadId) => {\n const rows = await db().findMany<SubscriptionRow>(\n 'integration_subscriptions',\n { ...scoped, resource_id: resourceId, thread_id: threadId },\n { orderBy: [['created_at', 'asc']] },\n );\n return rows.map(mapSubscription);\n },\n updateStatus: async (id, status) => {\n await db().updateMany('integration_subscriptions', { ...scoped, id }, { status, updated_at: new Date() });\n },\n delete: async id => {\n const deleted = await db().deleteMany('integration_subscriptions', { ...scoped, id });\n return deleted > 0;\n },\n deleteWhere: async where => {\n return db().deleteMany('integration_subscriptions', {\n ...scoped,\n org_id: where.orgId,\n ...(where.targetKey !== undefined ? { target_key: where.targetKey } : {}),\n ...(where.sessionId !== undefined ? { session_id: where.sessionId } : {}),\n });\n },\n },\n settings: {\n get: async (orgId, userId) => {\n const row = await db().findOne<{ config: unknown }>('integration_settings', {\n ...scoped,\n org_id: orgId,\n user_id: userId,\n });\n return row ? (await this.encryption.decrypt<TSettings>(row.config)).value : null;\n },\n save: async (orgId, userId, config) => {\n const where = { ...scoped, org_id: orgId, user_id: userId };\n const encrypted = await this.encryption.encrypt(config);\n const update = async () =>\n db().updateMany('integration_settings', where, {\n config: encrypted,\n updated_at: new Date(),\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await db().insertOne('integration_settings', {\n ...where,\n config: encrypted,\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n },\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,iCAAmD;CAC9D,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,gBAAgB,EAAE,MAAM,OAAO;EAC/B,QAAQ,EAAE,MAAM,OAAO;;EAEvB,SAAS;GAAE,MAAM;GAAQ,UAAU;EAAK;EACxC,MAAM,EAAE,MAAM,OAAO;EACrB,UAAU,EAAE,MAAM,OAAO;EACzB,YAAY,EAAE,MAAM,YAAY;EAChC,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,eAAe,CAAC;EAAE,MAAM;EAAkD,SAAS,CAAC,kBAAkB,QAAQ;CAAE,CAAC;AACnH;AAEA,MAAa,mCAAqD;CAChE,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,gBAAgB,EAAE,MAAM,OAAO;EAC/B,QAAQ,EAAE,MAAM,OAAO;;EAEvB,YAAY,EAAE,MAAM,OAAO;EAC3B,YAAY;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC3C,aAAa;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC5C,WAAW;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC1C,eAAe;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC9C,QAAQ,EAAE,MAAM,OAAO;EACvB,MAAM,EAAE,MAAM,OAAO;EACrB,YAAY,EAAE,MAAM,YAAY;EAChC,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,SAAS,CACP;EAAE,MAAM;EAAwC,SAAS,CAAC,kBAAkB,YAAY;CAAE,GAC1F;EAAE,MAAM;EAAqC,SAAS,CAAC,kBAAkB,QAAQ;CAAE,CACrF;AACF;AAEA,MAAa,8BAAgD;CAC3D,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,gBAAgB,EAAE,MAAM,OAAO;EAC/B,QAAQ,EAAE,MAAM,OAAO;EACvB,SAAS,EAAE,MAAM,OAAO;EACxB,QAAQ,EAAE,MAAM,OAAO;EACvB,YAAY,EAAE,MAAM,YAAY;EAChC,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,eAAe,CACb;EAAE,MAAM;EAAoD,SAAS;GAAC;GAAkB;GAAU;EAAS;CAAE,CAC/G;AACF;;;;;AAqHA,IAAa,qBAAb,cAAwC,qBAAqB;CAC9B;CAA7B,YAAY,aAAuD,uCAAuC,GAAG;EAC3G,MAAM,cAAc;EADO,KAAA,aAAA;CAE7B;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,kBAAkB;GAC3B;GACA;GACA;EACF,CAAC;EACD,MAAM,CAAC,aAAa,YAAY,MAAM,QAAQ,IAAI,CAChD,KAAK,IAAI,SAAwB,2BAA2B,CAAC,CAAC,GAC9D,KAAK,IAAI,SAA0C,wBAAwB,CAAC,CAAC,CAC/E,CAAC;EACD,MAAM,QAAQ,IAAI,CAChB,GAAG,YAAY,KAAI,QAAO,KAAKA,cAAc,2BAA2B,IAAI,IAAI,MAAM,CAAC,GACvF,GAAG,SAAS,KAAI,QAAO,KAAKA,cAAc,wBAAwB,IAAI,IAAI,QAAQ,CAAC,CACrF,CAAC;CACH;CAEA,MAAM,sBAAqC;EACzC,MAAM,KAAK,IAAI,WAAW,6BAA6B,CAAC,CAAC;EACzD,MAAM,KAAK,IAAI,WAAW,wBAAwB,CAAC,CAAC;EACpD,MAAM,KAAK,IAAI,WAAW,2BAA2B,CAAC,CAAC;CACzD;CAEA,IAAIC,MAAyB;EAC3B,OAAO,KAAK;CACd;CAEA,MAAMD,cAAc,YAAoB,IAAY,QAA0C;EAC5F,MAAM,KAAKC,IAAI,aAAsC,YAAY,EAAE,GAAG,GAAG,OAAM,QAAO;GACpF,MAAM,YAAY,MAAM,KAAK,WAAW,QAAQ,IAAI,OAAO;GAC3D,OAAO,UAAU,oBAAoB,GAAG,SAAS,MAAM,KAAK,WAAW,QAAQ,UAAU,KAAK,EAAE,IAAI;EACtG,CAAC;CACH;;CAGA,eAIE,eAAwF;EACxF,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,iDAAiD;EACrF,MAAM,WAAW,KAAKA;EACtB,MAAM,SAAS,EAAE,gBAAgB,cAAc;EAE/C,MAAM,gBAAgB,OAAO,SAAqE;GAChG,IAAI,IAAI;GACR,OAAO,IAAI;GACX,QAAQ,IAAI;GACZ,OAAO,MAAM,KAAK,WAAW,QAAqB,IAAI,IAAI,EAAA,CAAG;GAC7D,UAAU,IAAI,YAAY,CAAC;GAC3B,WAAW,IAAI;GACf,WAAW,IAAI;EACjB;EAEA,MAAM,mBAAmB,SAAkE;GACzF,IAAI,IAAI;GACR,OAAO,IAAI;GACX,WAAW,IAAI;GACf,WAAW,IAAI;GACf,YAAY,IAAI;GAChB,UAAU,IAAI;GACd,cAAc,IAAI;GAClB,QAAQ,IAAI;GACZ,MAAM,IAAI;GACV,WAAW,IAAI;GACf,WAAW,IAAI;EACjB;EAEA,OAAO;GACL;GACA,aAAa;IACX,KAAK,OAAM,UAAS;KAClB,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,QAAuB,2BAA2B;MAAE,GAAG;MAAQ,QAAQ;KAAM,CAAC;KACrG,OAAO,MAAM,cAAc,GAAG,IAAI;IACpC;IACA,QAAQ,OAAO,OAAO,UAAU;KAC9B,MAAM,QAAQ;MAAE,GAAG;MAAQ,QAAQ;KAAM;KACzC,MAAM,YAAY,MAAM,KAAK,WAAW,QAAQ,MAAM,IAAI;KAC1D,MAAM,SAAS,YACb,GAAG,CAAC,CAAC,WAAW,2BAA2B,OAAO;MAChD,SAAS,MAAM,UAAU;MACzB,MAAM;MACN,UAAU,MAAM,YAAY,CAAC;MAC7B,4BAAY,IAAI,KAAK;KACvB,CAAC;KAEH,IAAI;KACJ,KAAK,IAAI,UAAU,GAAG,UAAU,GAAG,WAAW;MAC5C,IAAK,MAAM,OAAO,IAAK,GAAG;MAE1B,MAAM,sBAAM,IAAI,KAAK;MACrB,IAAI;OACF,MAAM,GAAG,CAAC,CAAC,UAAyB,2BAA2B;QAC7D,GAAG;QACH,SAAS,MAAM,UAAU;QACzB,MAAM;QACN,UAAU,MAAM,YAAY,CAAC;QAC7B,YAAY;QACZ,YAAY;OACd,CAAC;OACD;MACF,SAAS,OAAO;OACd,IAAI,EAAE,iBAAiB,uBAAuB,MAAM;OACpD,YAAY;MACd;KACF;KACA,MAAM;IACR;IACA,QAAQ,OAAO,OAAO,OAAO;KAC3B,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,aACrB,2BACA;MAAE,GAAG;MAAQ,QAAQ;KAAM,GAC3B,OAAM,YAAW;MACf,MAAM,QAAQ,MAAM,KAAK,WAAW,QAAqB,QAAQ,IAAI,EAAA,CAAG;MACxE,OAAO;OAAE,MAAM,MAAM,KAAK,WAAW,QAAQ,GAAG,IAAI,CAAC;OAAG,4BAAY,IAAI,KAAK;MAAE;KACjF,CACF;KACA,OAAO,MAAM,cAAc,GAAG,IAAI;IACpC;IACA,QAAQ,OAAM,UAAS;KAErB,OAAO,MADe,GAAG,CAAC,CAAC,WAAW,2BAA2B;MAAE,GAAG;MAAQ,QAAQ;KAAM,CAAC,IAC5E;IACnB;GACF;GACA,eAAe;IACb,QAAQ,OAAM,UAAS;KACrB,MAAM,sBAAM,IAAI,KAAK;KACrB,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,UAA2B,6BAA6B;MAC7E,GAAG;MACH,QAAQ,MAAM;MACd,YAAY,MAAM;MAClB,YAAY,MAAM,aAAa;MAC/B,aAAa,MAAM,cAAc;MACjC,WAAW,MAAM,YAAY;MAC7B,eAAe,MAAM,gBAAgB;MACrC,QAAQ,MAAM,UAAU;MACxB,MAAM,MAAM,QAAQ,CAAC;MACrB,YAAY;MACZ,YAAY;KACd,CAAC;KACD,OAAO,gBAAgB,GAAG;IAC5B;IACA,cAAc,OAAO,WAAW,SAAS;KAMvC,QAAO,MALY,GAAG,CAAC,CAAC,SACtB,6BACA;MAAE,GAAG;MAAQ,YAAY;MAAW,GAAI,MAAM,WAAW,KAAA,IAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;KAAG,GACnG,EAAE,SAAS,CAAC,CAAC,cAAc,KAAK,CAAC,EAAE,CACrC,EAAA,CACY,IAAI,eAAe;IACjC;IACA,eAAe,OAAM,cAAa;KAMhC,QAAO,MALY,GAAG,CAAC,CAAC,SACtB,6BACA;MAAE,GAAG;MAAQ,YAAY;KAAU,GACnC,EAAE,SAAS,CAAC,CAAC,cAAc,KAAK,CAAC,EAAE,CACrC,EAAA,CACY,IAAI,eAAe;IACjC;IACA,cAAc,OAAO,YAAY,aAAa;KAM5C,QAAO,MALY,GAAG,CAAC,CAAC,SACtB,6BACA;MAAE,GAAG;MAAQ,aAAa;MAAY,WAAW;KAAS,GAC1D,EAAE,SAAS,CAAC,CAAC,cAAc,KAAK,CAAC,EAAE,CACrC,EAAA,CACY,IAAI,eAAe;IACjC;IACA,cAAc,OAAO,IAAI,WAAW;KAClC,MAAM,GAAG,CAAC,CAAC,WAAW,6BAA6B;MAAE,GAAG;MAAQ;KAAG,GAAG;MAAE;MAAQ,4BAAY,IAAI,KAAK;KAAE,CAAC;IAC1G;IACA,QAAQ,OAAM,OAAM;KAElB,OAAO,MADe,GAAG,CAAC,CAAC,WAAW,6BAA6B;MAAE,GAAG;MAAQ;KAAG,CAAC,IACnE;IACnB;IACA,aAAa,OAAM,UAAS;KAC1B,OAAO,GAAG,CAAC,CAAC,WAAW,6BAA6B;MAClD,GAAG;MACH,QAAQ,MAAM;MACd,GAAI,MAAM,cAAc,KAAA,IAAY,EAAE,YAAY,MAAM,UAAU,IAAI,CAAC;MACvE,GAAI,MAAM,cAAc,KAAA,IAAY,EAAE,YAAY,MAAM,UAAU,IAAI,CAAC;KACzE,CAAC;IACH;GACF;GACA,UAAU;IACR,KAAK,OAAO,OAAO,WAAW;KAC5B,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,QAA6B,wBAAwB;MAC1E,GAAG;MACH,QAAQ;MACR,SAAS;KACX,CAAC;KACD,OAAO,OAAO,MAAM,KAAK,WAAW,QAAmB,IAAI,MAAM,EAAA,CAAG,QAAQ;IAC9E;IACA,MAAM,OAAO,OAAO,QAAQ,WAAW;KACrC,MAAM,QAAQ;MAAE,GAAG;MAAQ,QAAQ;MAAO,SAAS;KAAO;KAC1D,MAAM,YAAY,MAAM,KAAK,WAAW,QAAQ,MAAM;KACtD,MAAM,SAAS,YACb,GAAG,CAAC,CAAC,WAAW,wBAAwB,OAAO;MAC7C,QAAQ;MACR,4BAAY,IAAI,KAAK;KACvB,CAAC;KAEH,IAAI;KACJ,KAAK,IAAI,UAAU,GAAG,UAAU,GAAG,WAAW;MAC5C,IAAK,MAAM,OAAO,IAAK,GAAG;MAE1B,MAAM,sBAAM,IAAI,KAAK;MACrB,IAAI;OACF,MAAM,GAAG,CAAC,CAAC,UAAU,wBAAwB;QAC3C,GAAG;QACH,QAAQ;QACR,YAAY;QACZ,YAAY;OACd,CAAC;OACD;MACF,SAAS,OAAO;OACd,IAAI,EAAE,iBAAiB,uBAAuB,MAAM;OACpD,YAAY;MACd;KACF;KACA,MAAM;IACR;GACF;EACF;CACF;AACF"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/integrations/base.ts"],"sourcesContent":["/**\n * Generic integration storage — the default persistence surface for\n * `FactoryIntegration`s.\n *\n * Most integrations persist the same three shapes: an org-owned OAuth\n * **connection**, **subscriptions** binding external objects to\n * sessions/threads, and per-`(org, user)` **settings**. This built-in domain\n * stores all three in shared collections keyed by `integration_id`, so an\n * integration author writes zero schema, zero DDL, and zero queries — and\n * org scoping is enforced by construction through the pre-scoped handle\n * returned by {@link IntegrationStorage.forIntegration}.\n *\n * Source-control integrations additionally use the shared, provider-scoped\n * source-control domain for installations, projects, and sessions.\n *\n * Payloads (`data` / `config`) are JSON documents typed per-integration via\n * the handle's generics. JSON round-trips exactly what JSON can represent:\n * store timestamps inside payloads as epoch millis or ISO strings, not\n * `Date`s.\n */\n\nimport { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\nimport { createPlaintextFactorySecretEncryption } from '../../../secret-encryption.js';\nimport type { FactorySecretEncryption } from '../../../secret-encryption.js';\n\nexport const INTEGRATION_CONNECTIONS_SCHEMA: CollectionSchema = {\n name: 'integration_connections',\n columns: {\n id: { type: 'uuid-pk' },\n integration_id: { type: 'text' },\n org_id: { type: 'text' },\n /** Who connected it (audit only — never scopes reads). */\n user_id: { type: 'text', nullable: true },\n data: { type: 'json' },\n metadata: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'integration_connections_integration_org_unique', columns: ['integration_id', 'org_id'] }],\n};\n\nexport const INTEGRATION_SUBSCRIPTIONS_SCHEMA: CollectionSchema = {\n name: 'integration_subscriptions',\n columns: {\n id: { type: 'uuid-pk' },\n integration_id: { type: 'text' },\n org_id: { type: 'text' },\n /** Canonical external-object key the integration derives (e.g. `pr:{installation}:{repo}:{number}`). */\n target_key: { type: 'text' },\n session_id: { type: 'text', nullable: true },\n resource_id: { type: 'text', nullable: true },\n thread_id: { type: 'text', nullable: true },\n session_scope: { type: 'text', nullable: true },\n status: { type: 'text' },\n data: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n indexes: [\n { name: 'integration_subscriptions_target_idx', columns: ['integration_id', 'target_key'] },\n { name: 'integration_subscriptions_org_idx', columns: ['integration_id', 'org_id'] },\n ],\n};\n\nexport const INTEGRATION_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'integration_settings',\n columns: {\n id: { type: 'uuid-pk' },\n integration_id: { type: 'text' },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n config: { type: 'json' },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [\n { name: 'integration_settings_integration_org_user_unique', columns: ['integration_id', 'org_id', 'user_id'] },\n ],\n};\n\n/** An integration's org-owned connection (typically the OAuth grant). */\nexport interface IntegrationConnection<TData> {\n id: string;\n orgId: string;\n /** Who connected it (audit only). */\n userId: string | null;\n data: TData;\n metadata: Record<string, unknown>;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/** A subscription binding an external object to a session/thread. */\nexport interface IntegrationSubscription<TData> {\n id: string;\n orgId: string;\n targetKey: string;\n sessionId: string | null;\n resourceId: string | null;\n threadId: string | null;\n sessionScope: string | null;\n status: string;\n data: TData;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport type CreateIntegrationSubscriptionInput<TData> = {\n orgId: string;\n targetKey: string;\n sessionId?: string | null;\n resourceId?: string | null;\n threadId?: string | null;\n sessionScope?: string | null;\n /** Defaults to `'active'`. */\n status?: string;\n} & ({} extends TData ? { data?: TData } : { data: TData });\n\n/**\n * Typed query surface pre-scoped to one `integration_id`. Every read and\n * write is automatically filtered/stamped with the integration id, so an\n * integration cannot reach another integration's rows.\n */\nexport interface IntegrationStorageHandle<\n TConnection = Record<string, unknown>,\n TSettings = Record<string, unknown>,\n TSubscription = Record<string, unknown>,\n> {\n readonly integrationId: string;\n connections: {\n /** The org's connection, or `null` when not connected. */\n get(orgId: string): Promise<IntegrationConnection<TConnection> | null>;\n /** Insert or replace the org's connection (one per org; `created_at` preserved on update). */\n upsert(\n orgId: string,\n input: { userId?: string | null; data: TConnection; metadata?: Record<string, unknown> },\n ): Promise<void>;\n /**\n * Atomic read-modify-write of the connection's `data` payload (token\n * rotation etc.). Returns the updated connection, or `null` when the org\n * has no connection.\n */\n update(orgId: string, fn: (data: TConnection) => TConnection): Promise<IntegrationConnection<TConnection> | null>;\n /** Remove the org's connection. Returns whether a row was deleted. */\n delete(orgId: string): Promise<boolean>;\n };\n subscriptions: {\n create(input: CreateIntegrationSubscriptionInput<TSubscription>): Promise<IntegrationSubscription<TSubscription>>;\n /** All subscriptions for an external object (webhook fan-out), optionally filtered by status. */\n listByTarget(targetKey: string, opts?: { status?: string }): Promise<IntegrationSubscription<TSubscription>[]>;\n listBySession(sessionId: string): Promise<IntegrationSubscription<TSubscription>[]>;\n listByThread(resourceId: string, threadId: string): Promise<IntegrationSubscription<TSubscription>[]>;\n updateStatus(id: string, status: string): Promise<void>;\n delete(id: string): Promise<boolean>;\n /** Targeted cleanup, always org-scoped. Returns the number of rows deleted. */\n deleteWhere(where: { orgId: string; targetKey?: string; sessionId?: string }): Promise<number>;\n };\n settings: {\n /** The `(org, user)` config, or `null` when never saved. */\n get(orgId: string, userId: string): Promise<TSettings | null>;\n /** Upsert the `(org, user)` config (`created_at` preserved on update). */\n save(orgId: string, userId: string, config: TSettings): Promise<void>;\n };\n}\n\ninterface ConnectionRow extends Record<string, unknown> {\n id: string;\n integration_id: string;\n org_id: string;\n user_id: string | null;\n data: unknown;\n metadata: Record<string, unknown>;\n created_at: Date;\n updated_at: Date;\n}\n\ninterface SubscriptionRow extends Record<string, unknown> {\n id: string;\n integration_id: string;\n org_id: string;\n target_key: string;\n session_id: string | null;\n resource_id: string | null;\n thread_id: string | null;\n session_scope: string | null;\n status: string;\n data: unknown;\n created_at: Date;\n updated_at: Date;\n}\n\n/**\n * The built-in generic integration domain, written once against the\n * `FactoryStorageOps` surface — works on any `FactoryStorage` backend.\n */\nexport class IntegrationStorage extends FactoryStorageDomain {\n constructor(private readonly encryption: FactorySecretEncryption = createPlaintextFactorySecretEncryption()) {\n super('integrations');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([\n INTEGRATION_CONNECTIONS_SCHEMA,\n INTEGRATION_SUBSCRIPTIONS_SCHEMA,\n INTEGRATION_SETTINGS_SCHEMA,\n ]);\n const [connections, settings] = await Promise.all([\n this.ops.findMany<ConnectionRow>('integration_connections', {}),\n this.ops.findMany<{ id: string; config: unknown }>('integration_settings', {}),\n ]);\n await Promise.all([\n ...connections.map(row => this.#migrateValue('integration_connections', row.id, 'data')),\n ...settings.map(row => this.#migrateValue('integration_settings', row.id, 'config')),\n ]);\n }\n\n /**\n * Move every subscription still recorded under `fromResourceId` onto\n * `toResourceId`, across integrations. A rerun finds nothing to move.\n */\n async moveSubscriptionsToResource(input: { fromResourceId: string; toResourceId: string }): Promise<number> {\n return this.ops.updateMany(\n 'integration_subscriptions',\n { resource_id: input.fromResourceId },\n { resource_id: input.toResourceId },\n );\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('integration_subscriptions', {});\n await this.ops.deleteMany('integration_settings', {});\n await this.ops.deleteMany('integration_connections', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async #migrateValue(collection: string, id: string, column: 'data' | 'config'): Promise<void> {\n await this.#db.updateAtomic<Record<string, unknown>>(collection, { id }, async row => {\n const decrypted = await this.encryption.decrypt(row[column]);\n return decrypted.needsReencryption ? { [column]: await this.encryption.encrypt(decrypted.value) } : null;\n });\n }\n\n /** A typed handle pre-scoped to `integrationId`. */\n forIntegration<\n TConnection = Record<string, unknown>,\n TSettings = Record<string, unknown>,\n TSubscription = Record<string, unknown>,\n >(integrationId: string): IntegrationStorageHandle<TConnection, TSettings, TSubscription> {\n if (!integrationId) throw new Error('[IntegrationStorage] integrationId is required.');\n const db = () => this.#db;\n const scoped = { integration_id: integrationId };\n\n const mapConnection = async (row: ConnectionRow): Promise<IntegrationConnection<TConnection>> => ({\n id: row.id,\n orgId: row.org_id,\n userId: row.user_id,\n data: (await this.encryption.decrypt<TConnection>(row.data)).value,\n metadata: row.metadata ?? {},\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n });\n\n const mapSubscription = (row: SubscriptionRow): IntegrationSubscription<TSubscription> => ({\n id: row.id,\n orgId: row.org_id,\n targetKey: row.target_key,\n sessionId: row.session_id,\n resourceId: row.resource_id,\n threadId: row.thread_id,\n sessionScope: row.session_scope,\n status: row.status,\n data: row.data as TSubscription,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n });\n\n return {\n integrationId,\n connections: {\n get: async orgId => {\n const row = await db().findOne<ConnectionRow>('integration_connections', { ...scoped, org_id: orgId });\n return row ? mapConnection(row) : null;\n },\n upsert: async (orgId, input) => {\n const where = { ...scoped, org_id: orgId };\n const encrypted = await this.encryption.encrypt(input.data);\n const update = async () =>\n db().updateMany('integration_connections', where, {\n user_id: input.userId ?? null,\n data: encrypted,\n metadata: input.metadata ?? {},\n updated_at: new Date(),\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await db().insertOne<ConnectionRow>('integration_connections', {\n ...where,\n user_id: input.userId ?? null,\n data: encrypted,\n metadata: input.metadata ?? {},\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n },\n update: async (orgId, fn) => {\n const row = await db().updateAtomic<ConnectionRow>(\n 'integration_connections',\n { ...scoped, org_id: orgId },\n async current => {\n const data = (await this.encryption.decrypt<TConnection>(current.data)).value;\n return { data: await this.encryption.encrypt(fn(data)), updated_at: new Date() };\n },\n );\n return row ? mapConnection(row) : null;\n },\n delete: async orgId => {\n const deleted = await db().deleteMany('integration_connections', { ...scoped, org_id: orgId });\n return deleted > 0;\n },\n },\n subscriptions: {\n create: async input => {\n const now = new Date();\n const row = await db().insertOne<SubscriptionRow>('integration_subscriptions', {\n ...scoped,\n org_id: input.orgId,\n target_key: input.targetKey,\n session_id: input.sessionId ?? null,\n resource_id: input.resourceId ?? null,\n thread_id: input.threadId ?? null,\n session_scope: input.sessionScope ?? null,\n status: input.status ?? 'active',\n data: input.data ?? {},\n created_at: now,\n updated_at: now,\n });\n return mapSubscription(row);\n },\n listByTarget: async (targetKey, opts) => {\n const rows = await db().findMany<SubscriptionRow>(\n 'integration_subscriptions',\n { ...scoped, target_key: targetKey, ...(opts?.status !== undefined ? { status: opts.status } : {}) },\n { orderBy: [['created_at', 'asc']] },\n );\n return rows.map(mapSubscription);\n },\n listBySession: async sessionId => {\n const rows = await db().findMany<SubscriptionRow>(\n 'integration_subscriptions',\n { ...scoped, session_id: sessionId },\n { orderBy: [['created_at', 'asc']] },\n );\n return rows.map(mapSubscription);\n },\n listByThread: async (resourceId, threadId) => {\n const rows = await db().findMany<SubscriptionRow>(\n 'integration_subscriptions',\n { ...scoped, resource_id: resourceId, thread_id: threadId },\n { orderBy: [['created_at', 'asc']] },\n );\n return rows.map(mapSubscription);\n },\n updateStatus: async (id, status) => {\n await db().updateMany('integration_subscriptions', { ...scoped, id }, { status, updated_at: new Date() });\n },\n delete: async id => {\n const deleted = await db().deleteMany('integration_subscriptions', { ...scoped, id });\n return deleted > 0;\n },\n deleteWhere: async where => {\n return db().deleteMany('integration_subscriptions', {\n ...scoped,\n org_id: where.orgId,\n ...(where.targetKey !== undefined ? { target_key: where.targetKey } : {}),\n ...(where.sessionId !== undefined ? { session_id: where.sessionId } : {}),\n });\n },\n },\n settings: {\n get: async (orgId, userId) => {\n const row = await db().findOne<{ config: unknown }>('integration_settings', {\n ...scoped,\n org_id: orgId,\n user_id: userId,\n });\n return row ? (await this.encryption.decrypt<TSettings>(row.config)).value : null;\n },\n save: async (orgId, userId, config) => {\n const where = { ...scoped, org_id: orgId, user_id: userId };\n const encrypted = await this.encryption.encrypt(config);\n const update = async () =>\n db().updateMany('integration_settings', where, {\n config: encrypted,\n updated_at: new Date(),\n });\n\n let lastError: unknown;\n for (let attempt = 0; attempt < 2; attempt++) {\n if ((await update()) > 0) return;\n\n const now = new Date();\n try {\n await db().insertOne('integration_settings', {\n ...where,\n config: encrypted,\n created_at: now,\n updated_at: now,\n });\n return;\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n lastError = error;\n }\n }\n throw lastError;\n },\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,iCAAmD;CAC9D,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,gBAAgB,EAAE,MAAM,OAAO;EAC/B,QAAQ,EAAE,MAAM,OAAO;;EAEvB,SAAS;GAAE,MAAM;GAAQ,UAAU;EAAK;EACxC,MAAM,EAAE,MAAM,OAAO;EACrB,UAAU,EAAE,MAAM,OAAO;EACzB,YAAY,EAAE,MAAM,YAAY;EAChC,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,eAAe,CAAC;EAAE,MAAM;EAAkD,SAAS,CAAC,kBAAkB,QAAQ;CAAE,CAAC;AACnH;AAEA,MAAa,mCAAqD;CAChE,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,gBAAgB,EAAE,MAAM,OAAO;EAC/B,QAAQ,EAAE,MAAM,OAAO;;EAEvB,YAAY,EAAE,MAAM,OAAO;EAC3B,YAAY;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC3C,aAAa;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC5C,WAAW;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC1C,eAAe;GAAE,MAAM;GAAQ,UAAU;EAAK;EAC9C,QAAQ,EAAE,MAAM,OAAO;EACvB,MAAM,EAAE,MAAM,OAAO;EACrB,YAAY,EAAE,MAAM,YAAY;EAChC,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,SAAS,CACP;EAAE,MAAM;EAAwC,SAAS,CAAC,kBAAkB,YAAY;CAAE,GAC1F;EAAE,MAAM;EAAqC,SAAS,CAAC,kBAAkB,QAAQ;CAAE,CACrF;AACF;AAEA,MAAa,8BAAgD;CAC3D,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,gBAAgB,EAAE,MAAM,OAAO;EAC/B,QAAQ,EAAE,MAAM,OAAO;EACvB,SAAS,EAAE,MAAM,OAAO;EACxB,QAAQ,EAAE,MAAM,OAAO;EACvB,YAAY,EAAE,MAAM,YAAY;EAChC,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,eAAe,CACb;EAAE,MAAM;EAAoD,SAAS;GAAC;GAAkB;GAAU;EAAS;CAAE,CAC/G;AACF;;;;;AAqHA,IAAa,qBAAb,cAAwC,qBAAqB;CAC9B;CAA7B,YAAY,aAAuD,uCAAuC,GAAG;EAC3G,MAAM,cAAc;EADO,KAAA,aAAA;CAE7B;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,kBAAkB;GAC3B;GACA;GACA;EACF,CAAC;EACD,MAAM,CAAC,aAAa,YAAY,MAAM,QAAQ,IAAI,CAChD,KAAK,IAAI,SAAwB,2BAA2B,CAAC,CAAC,GAC9D,KAAK,IAAI,SAA0C,wBAAwB,CAAC,CAAC,CAC/E,CAAC;EACD,MAAM,QAAQ,IAAI,CAChB,GAAG,YAAY,KAAI,QAAO,KAAK,cAAc,2BAA2B,IAAI,IAAI,MAAM,CAAC,GACvF,GAAG,SAAS,KAAI,QAAO,KAAK,cAAc,wBAAwB,IAAI,IAAI,QAAQ,CAAC,CACrF,CAAC;CACH;;;;;CAMA,MAAM,4BAA4B,OAA0E;EAC1G,OAAO,KAAK,IAAI,WACd,6BACA,EAAE,aAAa,MAAM,eAAe,GACpC,EAAE,aAAa,MAAM,aAAa,CACpC;CACF;CAEA,MAAM,sBAAqC;EACzC,MAAM,KAAK,IAAI,WAAW,6BAA6B,CAAC,CAAC;EACzD,MAAM,KAAK,IAAI,WAAW,wBAAwB,CAAC,CAAC;EACpD,MAAM,KAAK,IAAI,WAAW,2BAA2B,CAAC,CAAC;CACzD;CAEA,IAAI,MAAyB;EAC3B,OAAO,KAAK;CACd;CAEA,MAAM,cAAc,YAAoB,IAAY,QAA0C;EAC5F,MAAM,KAAK,IAAI,aAAsC,YAAY,EAAE,GAAG,GAAG,OAAM,QAAO;GACpF,MAAM,YAAY,MAAM,KAAK,WAAW,QAAQ,IAAI,OAAO;GAC3D,OAAO,UAAU,oBAAoB,GAAG,SAAS,MAAM,KAAK,WAAW,QAAQ,UAAU,KAAK,EAAE,IAAI;EACtG,CAAC;CACH;;CAGA,eAIE,eAAwF;EACxF,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,iDAAiD;EACrF,MAAM,WAAW,KAAK;EACtB,MAAM,SAAS,EAAE,gBAAgB,cAAc;EAE/C,MAAM,gBAAgB,OAAO,SAAqE;GAChG,IAAI,IAAI;GACR,OAAO,IAAI;GACX,QAAQ,IAAI;GACZ,OAAO,MAAM,KAAK,WAAW,QAAqB,IAAI,IAAI,EAAA,CAAG;GAC7D,UAAU,IAAI,YAAY,CAAC;GAC3B,WAAW,IAAI;GACf,WAAW,IAAI;EACjB;EAEA,MAAM,mBAAmB,SAAkE;GACzF,IAAI,IAAI;GACR,OAAO,IAAI;GACX,WAAW,IAAI;GACf,WAAW,IAAI;GACf,YAAY,IAAI;GAChB,UAAU,IAAI;GACd,cAAc,IAAI;GAClB,QAAQ,IAAI;GACZ,MAAM,IAAI;GACV,WAAW,IAAI;GACf,WAAW,IAAI;EACjB;EAEA,OAAO;GACL;GACA,aAAa;IACX,KAAK,OAAM,UAAS;KAClB,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,QAAuB,2BAA2B;MAAE,GAAG;MAAQ,QAAQ;KAAM,CAAC;KACrG,OAAO,MAAM,cAAc,GAAG,IAAI;IACpC;IACA,QAAQ,OAAO,OAAO,UAAU;KAC9B,MAAM,QAAQ;MAAE,GAAG;MAAQ,QAAQ;KAAM;KACzC,MAAM,YAAY,MAAM,KAAK,WAAW,QAAQ,MAAM,IAAI;KAC1D,MAAM,SAAS,YACb,GAAG,CAAC,CAAC,WAAW,2BAA2B,OAAO;MAChD,SAAS,MAAM,UAAU;MACzB,MAAM;MACN,UAAU,MAAM,YAAY,CAAC;MAC7B,4BAAY,IAAI,KAAK;KACvB,CAAC;KAEH,IAAI;KACJ,KAAK,IAAI,UAAU,GAAG,UAAU,GAAG,WAAW;MAC5C,IAAK,MAAM,OAAO,IAAK,GAAG;MAE1B,MAAM,sBAAM,IAAI,KAAK;MACrB,IAAI;OACF,MAAM,GAAG,CAAC,CAAC,UAAyB,2BAA2B;QAC7D,GAAG;QACH,SAAS,MAAM,UAAU;QACzB,MAAM;QACN,UAAU,MAAM,YAAY,CAAC;QAC7B,YAAY;QACZ,YAAY;OACd,CAAC;OACD;MACF,SAAS,OAAO;OACd,IAAI,EAAE,iBAAiB,uBAAuB,MAAM;OACpD,YAAY;MACd;KACF;KACA,MAAM;IACR;IACA,QAAQ,OAAO,OAAO,OAAO;KAC3B,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,aACrB,2BACA;MAAE,GAAG;MAAQ,QAAQ;KAAM,GAC3B,OAAM,YAAW;MACf,MAAM,QAAQ,MAAM,KAAK,WAAW,QAAqB,QAAQ,IAAI,EAAA,CAAG;MACxE,OAAO;OAAE,MAAM,MAAM,KAAK,WAAW,QAAQ,GAAG,IAAI,CAAC;OAAG,4BAAY,IAAI,KAAK;MAAE;KACjF,CACF;KACA,OAAO,MAAM,cAAc,GAAG,IAAI;IACpC;IACA,QAAQ,OAAM,UAAS;KAErB,OAAO,MADe,GAAG,CAAC,CAAC,WAAW,2BAA2B;MAAE,GAAG;MAAQ,QAAQ;KAAM,CAAC,IAC5E;IACnB;GACF;GACA,eAAe;IACb,QAAQ,OAAM,UAAS;KACrB,MAAM,sBAAM,IAAI,KAAK;KACrB,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,UAA2B,6BAA6B;MAC7E,GAAG;MACH,QAAQ,MAAM;MACd,YAAY,MAAM;MAClB,YAAY,MAAM,aAAa;MAC/B,aAAa,MAAM,cAAc;MACjC,WAAW,MAAM,YAAY;MAC7B,eAAe,MAAM,gBAAgB;MACrC,QAAQ,MAAM,UAAU;MACxB,MAAM,MAAM,QAAQ,CAAC;MACrB,YAAY;MACZ,YAAY;KACd,CAAC;KACD,OAAO,gBAAgB,GAAG;IAC5B;IACA,cAAc,OAAO,WAAW,SAAS;KAMvC,QAAO,MALY,GAAG,CAAC,CAAC,SACtB,6BACA;MAAE,GAAG;MAAQ,YAAY;MAAW,GAAI,MAAM,WAAW,KAAA,IAAY,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;KAAG,GACnG,EAAE,SAAS,CAAC,CAAC,cAAc,KAAK,CAAC,EAAE,CACrC,EAAA,CACY,IAAI,eAAe;IACjC;IACA,eAAe,OAAM,cAAa;KAMhC,QAAO,MALY,GAAG,CAAC,CAAC,SACtB,6BACA;MAAE,GAAG;MAAQ,YAAY;KAAU,GACnC,EAAE,SAAS,CAAC,CAAC,cAAc,KAAK,CAAC,EAAE,CACrC,EAAA,CACY,IAAI,eAAe;IACjC;IACA,cAAc,OAAO,YAAY,aAAa;KAM5C,QAAO,MALY,GAAG,CAAC,CAAC,SACtB,6BACA;MAAE,GAAG;MAAQ,aAAa;MAAY,WAAW;KAAS,GAC1D,EAAE,SAAS,CAAC,CAAC,cAAc,KAAK,CAAC,EAAE,CACrC,EAAA,CACY,IAAI,eAAe;IACjC;IACA,cAAc,OAAO,IAAI,WAAW;KAClC,MAAM,GAAG,CAAC,CAAC,WAAW,6BAA6B;MAAE,GAAG;MAAQ;KAAG,GAAG;MAAE;MAAQ,4BAAY,IAAI,KAAK;KAAE,CAAC;IAC1G;IACA,QAAQ,OAAM,OAAM;KAElB,OAAO,MADe,GAAG,CAAC,CAAC,WAAW,6BAA6B;MAAE,GAAG;MAAQ;KAAG,CAAC,IACnE;IACnB;IACA,aAAa,OAAM,UAAS;KAC1B,OAAO,GAAG,CAAC,CAAC,WAAW,6BAA6B;MAClD,GAAG;MACH,QAAQ,MAAM;MACd,GAAI,MAAM,cAAc,KAAA,IAAY,EAAE,YAAY,MAAM,UAAU,IAAI,CAAC;MACvE,GAAI,MAAM,cAAc,KAAA,IAAY,EAAE,YAAY,MAAM,UAAU,IAAI,CAAC;KACzE,CAAC;IACH;GACF;GACA,UAAU;IACR,KAAK,OAAO,OAAO,WAAW;KAC5B,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,QAA6B,wBAAwB;MAC1E,GAAG;MACH,QAAQ;MACR,SAAS;KACX,CAAC;KACD,OAAO,OAAO,MAAM,KAAK,WAAW,QAAmB,IAAI,MAAM,EAAA,CAAG,QAAQ;IAC9E;IACA,MAAM,OAAO,OAAO,QAAQ,WAAW;KACrC,MAAM,QAAQ;MAAE,GAAG;MAAQ,QAAQ;MAAO,SAAS;KAAO;KAC1D,MAAM,YAAY,MAAM,KAAK,WAAW,QAAQ,MAAM;KACtD,MAAM,SAAS,YACb,GAAG,CAAC,CAAC,WAAW,wBAAwB,OAAO;MAC7C,QAAQ;MACR,4BAAY,IAAI,KAAK;KACvB,CAAC;KAEH,IAAI;KACJ,KAAK,IAAI,UAAU,GAAG,UAAU,GAAG,WAAW;MAC5C,IAAK,MAAM,OAAO,IAAK,GAAG;MAE1B,MAAM,sBAAM,IAAI,KAAK;MACrB,IAAI;OACF,MAAM,GAAG,CAAC,CAAC,UAAU,wBAAwB;QAC3C,GAAG;QACH,QAAQ;QACR,YAAY;QACZ,YAAY;OACd,CAAC;OACD;MACF,SAAS,OAAO;OACd,IAAI,EAAE,iBAAiB,uBAAuB,MAAM;OACpD,YAAY;MACd;KACF;KACA,MAAM;IACR;GACF;EACF;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"base.js","names":["#db"],"sources":["../../../../src/storage/domains/memory-settings/base.ts"],"sourcesContent":["import { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\n\n/**\n * A user's persisted memory (observational-memory) configuration. The\n * DB-backed tenant counterpart of the local `settings.json` OM fields\n * (`observerModelOverride`, `reflectorModelOverride`, `omObservationThreshold`,\n * `omReflectionThreshold`, `omObserveAttachments`): one row per `(org, user)`,\n * every knob nullable so only explicitly-changed values are stored.\n */\nexport interface MemorySettingsRecord {\n orgId: string;\n userId: string;\n observerModelId: string | null;\n reflectorModelId: string | null;\n observationThreshold: number | null;\n reflectionThreshold: number | null;\n observeAttachments: 'auto' | boolean | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/** Partial update — only the provided knobs are written. `null` resets a model role to auto. */\nexport interface MemorySettingsPatch {\n observerModelId?: string | null;\n reflectorModelId?: string | null;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n}\n\n/**\n * Sentinel `user_id` for a factory project's shared memory settings. Factory\n * runs are org-shared, so their OM configuration lives on the project — this\n * key reuses the same `(org, user)` table without a schema change. Real user\n * ids are WorkOS ids (`user_…`) or the `local` sentinel, so the prefix cannot\n * collide.\n */\nexport function factoryMemorySettingsUserId(factoryProjectId: string): string {\n return `factory-project:${factoryProjectId}`;\n}\n\nexport const MEMORY_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'memory_settings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n observer_model_id: { type: 'text', nullable: true },\n reflector_model_id: { type: 'text', nullable: true },\n observation_threshold: { type: 'integer', nullable: true },\n reflection_threshold: { type: 'integer', nullable: true },\n // 'auto' | true | false — json keeps the tri-state without string encoding.\n observe_attachments: { type: 'json', nullable: true },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'memory_settings_org_user_key', columns: ['org_id', 'user_id'] }],\n};\n\ninterface MemorySettingsDbRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n user_id: string;\n observer_model_id: string | null;\n reflector_model_id: string | null;\n observation_threshold: number | null;\n reflection_threshold: number | null;\n observe_attachments: 'auto' | boolean | null;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction toRecord(row: MemorySettingsDbRow): MemorySettingsRecord {\n return {\n orgId: row.org_id,\n userId: row.user_id,\n observerModelId: row.observer_model_id,\n reflectorModelId: row.reflector_model_id,\n observationThreshold: row.observation_threshold,\n reflectionThreshold: row.reflection_threshold,\n observeAttachments: row.observe_attachments,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nfunction patchToColumns(patch: MemorySettingsPatch): Partial<MemorySettingsDbRow> {\n const columns: Partial<MemorySettingsDbRow> = {};\n if (patch.observerModelId !== undefined) columns.observer_model_id = patch.observerModelId;\n if (patch.reflectorModelId !== undefined) columns.reflector_model_id = patch.reflectorModelId;\n if (patch.observationThreshold !== undefined) columns.observation_threshold = patch.observationThreshold;\n if (patch.reflectionThreshold !== undefined) columns.reflection_threshold = patch.reflectionThreshold;\n if (patch.observeAttachments !== undefined) columns.observe_attachments = patch.observeAttachments;\n return columns;\n}\n\nexport class MemorySettingsStorage extends FactoryStorageDomain {\n constructor() {\n super('memory-settings');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([MEMORY_SETTINGS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('memory_settings', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async get({ orgId, userId }: { orgId: string; userId: string }): Promise<MemorySettingsRecord | null> {\n const row = await this.#db.findOne<MemorySettingsDbRow>('memory_settings', { org_id: orgId, user_id: userId });\n return row ? toRecord(row) : null;\n }\n\n /**\n * Upsert the user's row, writing only the knobs present in `patch`.\n * Concurrent first writes are resolved via the shared\n * insert-then-catch-unique-violation pattern (see `queue_health`).\n */\n async patch({\n orgId,\n userId,\n patch,\n }: {\n orgId: string;\n userId: string;\n patch: MemorySettingsPatch;\n }): Promise<MemorySettingsRecord> {\n const now = new Date();\n const updateExisting = () =>\n this.#db.updateAtomic<MemorySettingsDbRow>('memory_settings', { org_id: orgId, user_id: userId }, () => ({\n ...patchToColumns(patch),\n updated_at: now,\n }));\n\n const updated = await updateExisting();\n if (updated) return toRecord(updated);\n\n try {\n const row = await this.#db.insertOne<MemorySettingsDbRow>('memory_settings', {\n org_id: orgId,\n user_id: userId,\n observer_model_id: null,\n reflector_model_id: null,\n observation_threshold: null,\n reflection_threshold: null,\n observe_attachments: null,\n ...patchToColumns(patch),\n created_at: now,\n updated_at: now,\n });\n return toRecord(row);\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n // Lost the first-write race — apply the patch to the winning row.\n const row = await updateExisting();\n if (!row) throw error;\n return toRecord(row);\n }\n }\n}\n"],"mappings":";;;;;;;;;AAsCA,SAAgB,4BAA4B,kBAAkC;CAC5E,OAAO,mBAAmB;AAC5B;AAEA,MAAa,yBAA2C;CACtD,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,QAAQ,EAAE,MAAM,OAAO;EACvB,SAAS,EAAE,MAAM,OAAO;EACxB,mBAAmB;GAAE,MAAM;GAAQ,UAAU;EAAK;EAClD,oBAAoB;GAAE,MAAM;GAAQ,UAAU;EAAK;EACnD,uBAAuB;GAAE,MAAM;GAAW,UAAU;EAAK;EACzD,sBAAsB;GAAE,MAAM;GAAW,UAAU;EAAK;EAExD,qBAAqB;GAAE,MAAM;GAAQ,UAAU;EAAK;EACpD,YAAY,EAAE,MAAM,YAAY;EAChC,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,eAAe,CAAC;EAAE,MAAM;EAAgC,SAAS,CAAC,UAAU,SAAS;CAAE,CAAC;AAC1F;AAeA,SAAS,SAAS,KAAgD;CAChE,OAAO;EACL,OAAO,IAAI;EACX,QAAQ,IAAI;EACZ,iBAAiB,IAAI;EACrB,kBAAkB,IAAI;EACtB,sBAAsB,IAAI;EAC1B,qBAAqB,IAAI;EACzB,oBAAoB,IAAI;EACxB,WAAW,IAAI;EACf,WAAW,IAAI;CACjB;AACF;AAEA,SAAS,eAAe,OAA0D;CAChF,MAAM,UAAwC,CAAC;CAC/C,IAAI,MAAM,oBAAoB,KAAA,GAAW,QAAQ,oBAAoB,MAAM;CAC3E,IAAI,MAAM,qBAAqB,KAAA,GAAW,QAAQ,qBAAqB,MAAM;CAC7E,IAAI,MAAM,yBAAyB,KAAA,GAAW,QAAQ,wBAAwB,MAAM;CACpF,IAAI,MAAM,wBAAwB,KAAA,GAAW,QAAQ,uBAAuB,MAAM;CAClF,IAAI,MAAM,uBAAuB,KAAA,GAAW,QAAQ,sBAAsB,MAAM;CAChF,OAAO;AACT;AAEA,IAAa,wBAAb,cAA2C,qBAAqB;CAC9D,cAAc;EACZ,MAAM,iBAAiB;CACzB;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,kBAAkB,CAAC,sBAAsB,CAAC;CACvD;CAEA,MAAM,sBAAqC;EACzC,MAAM,KAAK,IAAI,WAAW,mBAAmB,CAAC,CAAC;CACjD;CAEA,IAAIA,MAAyB;EAC3B,OAAO,KAAK;CACd;CAEA,MAAM,IAAI,EAAE,OAAO,UAAmF;EACpG,MAAM,MAAM,MAAM,KAAKA,IAAI,QAA6B,mBAAmB;GAAE,QAAQ;GAAO,SAAS;EAAO,CAAC;EAC7G,OAAO,MAAM,SAAS,GAAG,IAAI;CAC/B;;;;;;CAOA,MAAM,MAAM,EACV,OACA,QACA,SAKgC;EAChC,MAAM,sBAAM,IAAI,KAAK;EACrB,MAAM,uBACJ,KAAKA,IAAI,aAAkC,mBAAmB;GAAE,QAAQ;GAAO,SAAS;EAAO,UAAU;GACvG,GAAG,eAAe,KAAK;GACvB,YAAY;EACd,EAAE;EAEJ,MAAM,UAAU,MAAM,eAAe;EACrC,IAAI,SAAS,OAAO,SAAS,OAAO;EAEpC,IAAI;GAaF,OAAO,SAAS,MAZE,KAAKA,IAAI,UAA+B,mBAAmB;IAC3E,QAAQ;IACR,SAAS;IACT,mBAAmB;IACnB,oBAAoB;IACpB,uBAAuB;IACvB,sBAAsB;IACtB,qBAAqB;IACrB,GAAG,eAAe,KAAK;IACvB,YAAY;IACZ,YAAY;GACd,CAAC,CACkB;EACrB,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,uBAAuB,MAAM;GAEpD,MAAM,MAAM,MAAM,eAAe;GACjC,IAAI,CAAC,KAAK,MAAM;GAChB,OAAO,SAAS,GAAG;EACrB;CACF;AACF"}
1
+ {"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/memory-settings/base.ts"],"sourcesContent":["import { FactoryStorageDomain, UniqueViolationError } from '@mastra/core/storage';\nimport type { CollectionSchema, FactoryStorageOps } from '@mastra/core/storage';\n\n/**\n * A user's persisted memory (observational-memory) configuration. The\n * DB-backed tenant counterpart of the local `settings.json` OM fields\n * (`observerModelOverride`, `reflectorModelOverride`, `omObservationThreshold`,\n * `omReflectionThreshold`, `omObserveAttachments`): one row per `(org, user)`,\n * every knob nullable so only explicitly-changed values are stored.\n */\nexport interface MemorySettingsRecord {\n orgId: string;\n userId: string;\n observerModelId: string | null;\n reflectorModelId: string | null;\n observationThreshold: number | null;\n reflectionThreshold: number | null;\n observeAttachments: 'auto' | boolean | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\n/** Partial update — only the provided knobs are written. `null` resets a model role to auto. */\nexport interface MemorySettingsPatch {\n observerModelId?: string | null;\n reflectorModelId?: string | null;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n}\n\n/**\n * Sentinel `user_id` for a factory project's shared memory settings. Factory\n * runs are org-shared, so their OM configuration lives on the project — this\n * key reuses the same `(org, user)` table without a schema change. Real user\n * ids are WorkOS ids (`user_…`) or the `local` sentinel, so the prefix cannot\n * collide.\n */\nexport function factoryMemorySettingsUserId(factoryProjectId: string): string {\n return `factory-project:${factoryProjectId}`;\n}\n\nexport const MEMORY_SETTINGS_SCHEMA: CollectionSchema = {\n name: 'memory_settings',\n columns: {\n id: { type: 'uuid-pk' },\n org_id: { type: 'text' },\n user_id: { type: 'text' },\n observer_model_id: { type: 'text', nullable: true },\n reflector_model_id: { type: 'text', nullable: true },\n observation_threshold: { type: 'integer', nullable: true },\n reflection_threshold: { type: 'integer', nullable: true },\n // 'auto' | true | false — json keeps the tri-state without string encoding.\n observe_attachments: { type: 'json', nullable: true },\n created_at: { type: 'timestamp' },\n updated_at: { type: 'timestamp' },\n },\n uniqueIndexes: [{ name: 'memory_settings_org_user_key', columns: ['org_id', 'user_id'] }],\n};\n\ninterface MemorySettingsDbRow extends Record<string, unknown> {\n id: string;\n org_id: string;\n user_id: string;\n observer_model_id: string | null;\n reflector_model_id: string | null;\n observation_threshold: number | null;\n reflection_threshold: number | null;\n observe_attachments: 'auto' | boolean | null;\n created_at: Date;\n updated_at: Date;\n}\n\nfunction toRecord(row: MemorySettingsDbRow): MemorySettingsRecord {\n return {\n orgId: row.org_id,\n userId: row.user_id,\n observerModelId: row.observer_model_id,\n reflectorModelId: row.reflector_model_id,\n observationThreshold: row.observation_threshold,\n reflectionThreshold: row.reflection_threshold,\n observeAttachments: row.observe_attachments,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n };\n}\n\nfunction patchToColumns(patch: MemorySettingsPatch): Partial<MemorySettingsDbRow> {\n const columns: Partial<MemorySettingsDbRow> = {};\n if (patch.observerModelId !== undefined) columns.observer_model_id = patch.observerModelId;\n if (patch.reflectorModelId !== undefined) columns.reflector_model_id = patch.reflectorModelId;\n if (patch.observationThreshold !== undefined) columns.observation_threshold = patch.observationThreshold;\n if (patch.reflectionThreshold !== undefined) columns.reflection_threshold = patch.reflectionThreshold;\n if (patch.observeAttachments !== undefined) columns.observe_attachments = patch.observeAttachments;\n return columns;\n}\n\nexport class MemorySettingsStorage extends FactoryStorageDomain {\n constructor() {\n super('memory-settings');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections([MEMORY_SETTINGS_SCHEMA]);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany('memory_settings', {});\n }\n\n get #db(): FactoryStorageOps {\n return this.ops;\n }\n\n async get({ orgId, userId }: { orgId: string; userId: string }): Promise<MemorySettingsRecord | null> {\n const row = await this.#db.findOne<MemorySettingsDbRow>('memory_settings', { org_id: orgId, user_id: userId });\n return row ? toRecord(row) : null;\n }\n\n /**\n * Upsert the user's row, writing only the knobs present in `patch`.\n * Concurrent first writes are resolved via the shared\n * insert-then-catch-unique-violation pattern (see `queue_health`).\n */\n async patch({\n orgId,\n userId,\n patch,\n }: {\n orgId: string;\n userId: string;\n patch: MemorySettingsPatch;\n }): Promise<MemorySettingsRecord> {\n const now = new Date();\n const updateExisting = () =>\n this.#db.updateAtomic<MemorySettingsDbRow>('memory_settings', { org_id: orgId, user_id: userId }, () => ({\n ...patchToColumns(patch),\n updated_at: now,\n }));\n\n const updated = await updateExisting();\n if (updated) return toRecord(updated);\n\n try {\n const row = await this.#db.insertOne<MemorySettingsDbRow>('memory_settings', {\n org_id: orgId,\n user_id: userId,\n observer_model_id: null,\n reflector_model_id: null,\n observation_threshold: null,\n reflection_threshold: null,\n observe_attachments: null,\n ...patchToColumns(patch),\n created_at: now,\n updated_at: now,\n });\n return toRecord(row);\n } catch (error) {\n if (!(error instanceof UniqueViolationError)) throw error;\n // Lost the first-write race — apply the patch to the winning row.\n const row = await updateExisting();\n if (!row) throw error;\n return toRecord(row);\n }\n }\n}\n"],"mappings":";;;;;;;;;AAsCA,SAAgB,4BAA4B,kBAAkC;CAC5E,OAAO,mBAAmB;AAC5B;AAEA,MAAa,yBAA2C;CACtD,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,QAAQ,EAAE,MAAM,OAAO;EACvB,SAAS,EAAE,MAAM,OAAO;EACxB,mBAAmB;GAAE,MAAM;GAAQ,UAAU;EAAK;EAClD,oBAAoB;GAAE,MAAM;GAAQ,UAAU;EAAK;EACnD,uBAAuB;GAAE,MAAM;GAAW,UAAU;EAAK;EACzD,sBAAsB;GAAE,MAAM;GAAW,UAAU;EAAK;EAExD,qBAAqB;GAAE,MAAM;GAAQ,UAAU;EAAK;EACpD,YAAY,EAAE,MAAM,YAAY;EAChC,YAAY,EAAE,MAAM,YAAY;CAClC;CACA,eAAe,CAAC;EAAE,MAAM;EAAgC,SAAS,CAAC,UAAU,SAAS;CAAE,CAAC;AAC1F;AAeA,SAAS,SAAS,KAAgD;CAChE,OAAO;EACL,OAAO,IAAI;EACX,QAAQ,IAAI;EACZ,iBAAiB,IAAI;EACrB,kBAAkB,IAAI;EACtB,sBAAsB,IAAI;EAC1B,qBAAqB,IAAI;EACzB,oBAAoB,IAAI;EACxB,WAAW,IAAI;EACf,WAAW,IAAI;CACjB;AACF;AAEA,SAAS,eAAe,OAA0D;CAChF,MAAM,UAAwC,CAAC;CAC/C,IAAI,MAAM,oBAAoB,KAAA,GAAW,QAAQ,oBAAoB,MAAM;CAC3E,IAAI,MAAM,qBAAqB,KAAA,GAAW,QAAQ,qBAAqB,MAAM;CAC7E,IAAI,MAAM,yBAAyB,KAAA,GAAW,QAAQ,wBAAwB,MAAM;CACpF,IAAI,MAAM,wBAAwB,KAAA,GAAW,QAAQ,uBAAuB,MAAM;CAClF,IAAI,MAAM,uBAAuB,KAAA,GAAW,QAAQ,sBAAsB,MAAM;CAChF,OAAO;AACT;AAEA,IAAa,wBAAb,cAA2C,qBAAqB;CAC9D,cAAc;EACZ,MAAM,iBAAiB;CACzB;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,kBAAkB,CAAC,sBAAsB,CAAC;CACvD;CAEA,MAAM,sBAAqC;EACzC,MAAM,KAAK,IAAI,WAAW,mBAAmB,CAAC,CAAC;CACjD;CAEA,IAAI,MAAyB;EAC3B,OAAO,KAAK;CACd;CAEA,MAAM,IAAI,EAAE,OAAO,UAAmF;EACpG,MAAM,MAAM,MAAM,KAAK,IAAI,QAA6B,mBAAmB;GAAE,QAAQ;GAAO,SAAS;EAAO,CAAC;EAC7G,OAAO,MAAM,SAAS,GAAG,IAAI;CAC/B;;;;;;CAOA,MAAM,MAAM,EACV,OACA,QACA,SAKgC;EAChC,MAAM,sBAAM,IAAI,KAAK;EACrB,MAAM,uBACJ,KAAK,IAAI,aAAkC,mBAAmB;GAAE,QAAQ;GAAO,SAAS;EAAO,UAAU;GACvG,GAAG,eAAe,KAAK;GACvB,YAAY;EACd,EAAE;EAEJ,MAAM,UAAU,MAAM,eAAe;EACrC,IAAI,SAAS,OAAO,SAAS,OAAO;EAEpC,IAAI;GAaF,OAAO,SAAS,MAZE,KAAK,IAAI,UAA+B,mBAAmB;IAC3E,QAAQ;IACR,SAAS;IACT,mBAAmB;IACnB,oBAAoB;IACpB,uBAAuB;IACvB,sBAAsB;IACtB,qBAAqB;IACrB,GAAG,eAAe,KAAK;IACvB,YAAY;IACZ,YAAY;GACd,CAAC,CACkB;EACrB,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,uBAAuB,MAAM;GAEpD,MAAM,MAAM,MAAM,eAAe;GACjC,IAAI,CAAC,KAAK,MAAM;GAChB,OAAO,SAAS,GAAG;EACrB;CACF;AACF"}
@@ -1,9 +1,11 @@
1
+ import type { ThinkingLevelSetting } from '@mastra/code-sdk/thinking';
1
2
  import { FactoryStorageDomain } from '@mastra/core/storage';
2
3
  import type { CollectionSchema } from '@mastra/core/storage';
3
4
  export interface ModelDefaultRecord {
4
5
  orgId: string;
5
6
  userId: string;
6
7
  modelId: string;
8
+ thinkingLevel: ThinkingLevelSetting | null;
7
9
  updatedAt: Date;
8
10
  }
9
11
  export declare const USER_DEFAULT_MODELS_SCHEMA: CollectionSchema;
@@ -21,6 +23,12 @@ export declare class ModelDefaultsStorage extends FactoryStorageDomain {
21
23
  userId: string;
22
24
  modelId: string;
23
25
  }): Promise<ModelDefaultRecord>;
26
+ /** Null when the user has no personal model: a personal thinking level never exists without one. */
27
+ setThinkingLevel({ orgId, userId, thinkingLevel, }: {
28
+ orgId: string;
29
+ userId: string;
30
+ thinkingLevel: ThinkingLevelSetting | null;
31
+ }): Promise<ModelDefaultRecord | null>;
24
32
  clear({ orgId, userId }: {
25
33
  orgId: string;
26
34
  userId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/model-defaults/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAwB,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAqB,MAAM,sBAAsB,CAAC;AAEhF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,eAAO,MAAM,0BAA0B,EAAE,gBAUxC,CAAC;AAmBF,qBAAa,oBAAqB,SAAQ,oBAAoB;;;IAKtD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQpC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAQ7F,GAAG,CAAC,EACR,KAAK,EACL,MAAM,EACN,OAAO,GACR,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAyBzB,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAGpF"}
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/model-defaults/base.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAwB,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAqB,MAAM,sBAAsB,CAAC;AAEhF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC3C,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,eAAO,MAAM,0BAA0B,EAAE,gBAWxC,CAAC;AAqBF,qBAAa,oBAAqB,SAAQ,oBAAoB;;;IAKtD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQpC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAQ7F,GAAG,CAAC,EACR,KAAK,EACL,MAAM,EACN,OAAO,GACR,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IA0B/B,oGAAoG;IAC9F,gBAAgB,CAAC,EACrB,KAAK,EACL,MAAM,EACN,aAAa,GACd,EAAE;QACD,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,aAAa,EAAE,oBAAoB,GAAG,IAAI,CAAC;KAC5C,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAShC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAGpF"}