palaryn 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (607) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +716 -0
  3. package/dist/sdk/typescript/src/client.d.ts +71 -0
  4. package/dist/sdk/typescript/src/client.d.ts.map +1 -0
  5. package/dist/sdk/typescript/src/client.js +176 -0
  6. package/dist/sdk/typescript/src/client.js.map +1 -0
  7. package/dist/sdk/typescript/src/errors.d.ts +50 -0
  8. package/dist/sdk/typescript/src/errors.d.ts.map +1 -0
  9. package/dist/sdk/typescript/src/errors.js +103 -0
  10. package/dist/sdk/typescript/src/errors.js.map +1 -0
  11. package/dist/sdk/typescript/src/index.d.ts +4 -0
  12. package/dist/sdk/typescript/src/index.d.ts.map +1 -0
  13. package/dist/sdk/typescript/src/index.js +15 -0
  14. package/dist/sdk/typescript/src/index.js.map +1 -0
  15. package/dist/sdk/typescript/src/types.d.ts +101 -0
  16. package/dist/sdk/typescript/src/types.d.ts.map +1 -0
  17. package/dist/sdk/typescript/src/types.js +6 -0
  18. package/dist/sdk/typescript/src/types.js.map +1 -0
  19. package/dist/src/admin/index.d.ts +2 -0
  20. package/dist/src/admin/index.d.ts.map +1 -0
  21. package/dist/src/admin/index.js +6 -0
  22. package/dist/src/admin/index.js.map +1 -0
  23. package/dist/src/admin/routes.d.ts +5 -0
  24. package/dist/src/admin/routes.d.ts.map +1 -0
  25. package/dist/src/admin/routes.js +471 -0
  26. package/dist/src/admin/routes.js.map +1 -0
  27. package/dist/src/admin/templates.d.ts +51 -0
  28. package/dist/src/admin/templates.d.ts.map +1 -0
  29. package/dist/src/admin/templates.js +500 -0
  30. package/dist/src/admin/templates.js.map +1 -0
  31. package/dist/src/anomaly/detector.d.ts +141 -0
  32. package/dist/src/anomaly/detector.d.ts.map +1 -0
  33. package/dist/src/anomaly/detector.js +554 -0
  34. package/dist/src/anomaly/detector.js.map +1 -0
  35. package/dist/src/anomaly/index.d.ts +2 -0
  36. package/dist/src/anomaly/index.d.ts.map +1 -0
  37. package/dist/src/anomaly/index.js +7 -0
  38. package/dist/src/anomaly/index.js.map +1 -0
  39. package/dist/src/approval/manager.d.ts +147 -0
  40. package/dist/src/approval/manager.d.ts.map +1 -0
  41. package/dist/src/approval/manager.js +511 -0
  42. package/dist/src/approval/manager.js.map +1 -0
  43. package/dist/src/approval/webhook.d.ts +36 -0
  44. package/dist/src/approval/webhook.d.ts.map +1 -0
  45. package/dist/src/approval/webhook.js +135 -0
  46. package/dist/src/approval/webhook.js.map +1 -0
  47. package/dist/src/audit/logger.d.ts +70 -0
  48. package/dist/src/audit/logger.d.ts.map +1 -0
  49. package/dist/src/audit/logger.js +440 -0
  50. package/dist/src/audit/logger.js.map +1 -0
  51. package/dist/src/auth/index.d.ts +6 -0
  52. package/dist/src/auth/index.d.ts.map +1 -0
  53. package/dist/src/auth/index.js +22 -0
  54. package/dist/src/auth/index.js.map +1 -0
  55. package/dist/src/auth/password.d.ts +3 -0
  56. package/dist/src/auth/password.d.ts.map +1 -0
  57. package/dist/src/auth/password.js +25 -0
  58. package/dist/src/auth/password.js.map +1 -0
  59. package/dist/src/auth/pkce.d.ts +13 -0
  60. package/dist/src/auth/pkce.d.ts.map +1 -0
  61. package/dist/src/auth/pkce.js +58 -0
  62. package/dist/src/auth/pkce.js.map +1 -0
  63. package/dist/src/auth/providers.d.ts +28 -0
  64. package/dist/src/auth/providers.d.ts.map +1 -0
  65. package/dist/src/auth/providers.js +198 -0
  66. package/dist/src/auth/providers.js.map +1 -0
  67. package/dist/src/auth/routes.d.ts +14 -0
  68. package/dist/src/auth/routes.d.ts.map +1 -0
  69. package/dist/src/auth/routes.js +431 -0
  70. package/dist/src/auth/routes.js.map +1 -0
  71. package/dist/src/auth/session.d.ts +24 -0
  72. package/dist/src/auth/session.d.ts.map +1 -0
  73. package/dist/src/auth/session.js +105 -0
  74. package/dist/src/auth/session.js.map +1 -0
  75. package/dist/src/billing/index.d.ts +7 -0
  76. package/dist/src/billing/index.d.ts.map +1 -0
  77. package/dist/src/billing/index.js +14 -0
  78. package/dist/src/billing/index.js.map +1 -0
  79. package/dist/src/billing/plan-enforcer.d.ts +44 -0
  80. package/dist/src/billing/plan-enforcer.d.ts.map +1 -0
  81. package/dist/src/billing/plan-enforcer.js +110 -0
  82. package/dist/src/billing/plan-enforcer.js.map +1 -0
  83. package/dist/src/billing/routes.d.ts +15 -0
  84. package/dist/src/billing/routes.d.ts.map +1 -0
  85. package/dist/src/billing/routes.js +193 -0
  86. package/dist/src/billing/routes.js.map +1 -0
  87. package/dist/src/billing/stripe-client.d.ts +14 -0
  88. package/dist/src/billing/stripe-client.d.ts.map +1 -0
  89. package/dist/src/billing/stripe-client.js +51 -0
  90. package/dist/src/billing/stripe-client.js.map +1 -0
  91. package/dist/src/billing/webhook-handler.d.ts +19 -0
  92. package/dist/src/billing/webhook-handler.d.ts.map +1 -0
  93. package/dist/src/billing/webhook-handler.js +169 -0
  94. package/dist/src/billing/webhook-handler.js.map +1 -0
  95. package/dist/src/billing/webhook-routes.d.ts +5 -0
  96. package/dist/src/billing/webhook-routes.d.ts.map +1 -0
  97. package/dist/src/billing/webhook-routes.js +30 -0
  98. package/dist/src/billing/webhook-routes.js.map +1 -0
  99. package/dist/src/budget/manager.d.ts +95 -0
  100. package/dist/src/budget/manager.d.ts.map +1 -0
  101. package/dist/src/budget/manager.js +547 -0
  102. package/dist/src/budget/manager.js.map +1 -0
  103. package/dist/src/budget/usage-extractor.d.ts +38 -0
  104. package/dist/src/budget/usage-extractor.d.ts.map +1 -0
  105. package/dist/src/budget/usage-extractor.js +165 -0
  106. package/dist/src/budget/usage-extractor.js.map +1 -0
  107. package/dist/src/cli.d.ts +3 -0
  108. package/dist/src/cli.d.ts.map +1 -0
  109. package/dist/src/cli.js +115 -0
  110. package/dist/src/cli.js.map +1 -0
  111. package/dist/src/config/defaults.d.ts +3 -0
  112. package/dist/src/config/defaults.d.ts.map +1 -0
  113. package/dist/src/config/defaults.js +243 -0
  114. package/dist/src/config/defaults.js.map +1 -0
  115. package/dist/src/config/validate.d.ts +15 -0
  116. package/dist/src/config/validate.d.ts.map +1 -0
  117. package/dist/src/config/validate.js +105 -0
  118. package/dist/src/config/validate.js.map +1 -0
  119. package/dist/src/dlp/composite-scanner.d.ts +47 -0
  120. package/dist/src/dlp/composite-scanner.d.ts.map +1 -0
  121. package/dist/src/dlp/composite-scanner.js +186 -0
  122. package/dist/src/dlp/composite-scanner.js.map +1 -0
  123. package/dist/src/dlp/index.d.ts +10 -0
  124. package/dist/src/dlp/index.d.ts.map +1 -0
  125. package/dist/src/dlp/index.js +26 -0
  126. package/dist/src/dlp/index.js.map +1 -0
  127. package/dist/src/dlp/interfaces.d.ts +33 -0
  128. package/dist/src/dlp/interfaces.d.ts.map +1 -0
  129. package/dist/src/dlp/interfaces.js +3 -0
  130. package/dist/src/dlp/interfaces.js.map +1 -0
  131. package/dist/src/dlp/patterns.d.ts +9 -0
  132. package/dist/src/dlp/patterns.d.ts.map +1 -0
  133. package/dist/src/dlp/patterns.js +25 -0
  134. package/dist/src/dlp/patterns.js.map +1 -0
  135. package/dist/src/dlp/prompt-injection-backend.d.ts +68 -0
  136. package/dist/src/dlp/prompt-injection-backend.d.ts.map +1 -0
  137. package/dist/src/dlp/prompt-injection-backend.js +148 -0
  138. package/dist/src/dlp/prompt-injection-backend.js.map +1 -0
  139. package/dist/src/dlp/prompt-injection-patterns.d.ts +32 -0
  140. package/dist/src/dlp/prompt-injection-patterns.d.ts.map +1 -0
  141. package/dist/src/dlp/prompt-injection-patterns.js +290 -0
  142. package/dist/src/dlp/prompt-injection-patterns.js.map +1 -0
  143. package/dist/src/dlp/regex-backend.d.ts +32 -0
  144. package/dist/src/dlp/regex-backend.d.ts.map +1 -0
  145. package/dist/src/dlp/regex-backend.js +153 -0
  146. package/dist/src/dlp/regex-backend.js.map +1 -0
  147. package/dist/src/dlp/scanner.d.ts +122 -0
  148. package/dist/src/dlp/scanner.d.ts.map +1 -0
  149. package/dist/src/dlp/scanner.js +444 -0
  150. package/dist/src/dlp/scanner.js.map +1 -0
  151. package/dist/src/dlp/text-normalizer.d.ts +41 -0
  152. package/dist/src/dlp/text-normalizer.d.ts.map +1 -0
  153. package/dist/src/dlp/text-normalizer.js +203 -0
  154. package/dist/src/dlp/text-normalizer.js.map +1 -0
  155. package/dist/src/dlp/trufflehog-backend.d.ts +64 -0
  156. package/dist/src/dlp/trufflehog-backend.d.ts.map +1 -0
  157. package/dist/src/dlp/trufflehog-backend.js +151 -0
  158. package/dist/src/dlp/trufflehog-backend.js.map +1 -0
  159. package/dist/src/executor/http-executor.d.ts +25 -0
  160. package/dist/src/executor/http-executor.d.ts.map +1 -0
  161. package/dist/src/executor/http-executor.js +333 -0
  162. package/dist/src/executor/http-executor.js.map +1 -0
  163. package/dist/src/executor/index.d.ts +6 -0
  164. package/dist/src/executor/index.d.ts.map +1 -0
  165. package/dist/src/executor/index.js +12 -0
  166. package/dist/src/executor/index.js.map +1 -0
  167. package/dist/src/executor/interfaces.d.ts +11 -0
  168. package/dist/src/executor/interfaces.d.ts.map +1 -0
  169. package/dist/src/executor/interfaces.js +3 -0
  170. package/dist/src/executor/interfaces.js.map +1 -0
  171. package/dist/src/executor/noop-executor.d.ts +13 -0
  172. package/dist/src/executor/noop-executor.d.ts.map +1 -0
  173. package/dist/src/executor/noop-executor.js +21 -0
  174. package/dist/src/executor/noop-executor.js.map +1 -0
  175. package/dist/src/executor/registry.d.ts +30 -0
  176. package/dist/src/executor/registry.d.ts.map +1 -0
  177. package/dist/src/executor/registry.js +62 -0
  178. package/dist/src/executor/registry.js.map +1 -0
  179. package/dist/src/executor/slack-executor.d.ts +24 -0
  180. package/dist/src/executor/slack-executor.d.ts.map +1 -0
  181. package/dist/src/executor/slack-executor.js +147 -0
  182. package/dist/src/executor/slack-executor.js.map +1 -0
  183. package/dist/src/index.d.ts +25 -0
  184. package/dist/src/index.d.ts.map +1 -0
  185. package/dist/src/index.js +74 -0
  186. package/dist/src/index.js.map +1 -0
  187. package/dist/src/mcp/auth-verifier.d.ts +23 -0
  188. package/dist/src/mcp/auth-verifier.d.ts.map +1 -0
  189. package/dist/src/mcp/auth-verifier.js +162 -0
  190. package/dist/src/mcp/auth-verifier.js.map +1 -0
  191. package/dist/src/mcp/bridge.d.ts +132 -0
  192. package/dist/src/mcp/bridge.d.ts.map +1 -0
  193. package/dist/src/mcp/bridge.js +734 -0
  194. package/dist/src/mcp/bridge.js.map +1 -0
  195. package/dist/src/mcp/http-transport.d.ts +32 -0
  196. package/dist/src/mcp/http-transport.d.ts.map +1 -0
  197. package/dist/src/mcp/http-transport.js +538 -0
  198. package/dist/src/mcp/http-transport.js.map +1 -0
  199. package/dist/src/mcp/index.d.ts +10 -0
  200. package/dist/src/mcp/index.d.ts.map +1 -0
  201. package/dist/src/mcp/index.js +17 -0
  202. package/dist/src/mcp/index.js.map +1 -0
  203. package/dist/src/mcp/oauth-pages.d.ts +23 -0
  204. package/dist/src/mcp/oauth-pages.d.ts.map +1 -0
  205. package/dist/src/mcp/oauth-pages.js +121 -0
  206. package/dist/src/mcp/oauth-pages.js.map +1 -0
  207. package/dist/src/mcp/oauth-postgres-stores.d.ts +55 -0
  208. package/dist/src/mcp/oauth-postgres-stores.d.ts.map +1 -0
  209. package/dist/src/mcp/oauth-postgres-stores.js +226 -0
  210. package/dist/src/mcp/oauth-postgres-stores.js.map +1 -0
  211. package/dist/src/mcp/oauth-provider.d.ts +95 -0
  212. package/dist/src/mcp/oauth-provider.d.ts.map +1 -0
  213. package/dist/src/mcp/oauth-provider.js +360 -0
  214. package/dist/src/mcp/oauth-provider.js.map +1 -0
  215. package/dist/src/mcp/oauth-stores.d.ts +62 -0
  216. package/dist/src/mcp/oauth-stores.d.ts.map +1 -0
  217. package/dist/src/mcp/oauth-stores.js +154 -0
  218. package/dist/src/mcp/oauth-stores.js.map +1 -0
  219. package/dist/src/mcp/server.d.ts +18 -0
  220. package/dist/src/mcp/server.d.ts.map +1 -0
  221. package/dist/src/mcp/server.js +51 -0
  222. package/dist/src/mcp/server.js.map +1 -0
  223. package/dist/src/metrics/collector.d.ts +106 -0
  224. package/dist/src/metrics/collector.d.ts.map +1 -0
  225. package/dist/src/metrics/collector.js +311 -0
  226. package/dist/src/metrics/collector.js.map +1 -0
  227. package/dist/src/metrics/index.d.ts +2 -0
  228. package/dist/src/metrics/index.d.ts.map +1 -0
  229. package/dist/src/metrics/index.js +6 -0
  230. package/dist/src/metrics/index.js.map +1 -0
  231. package/dist/src/middleware/auth.d.ts +77 -0
  232. package/dist/src/middleware/auth.d.ts.map +1 -0
  233. package/dist/src/middleware/auth.js +720 -0
  234. package/dist/src/middleware/auth.js.map +1 -0
  235. package/dist/src/middleware/session.d.ts +18 -0
  236. package/dist/src/middleware/session.d.ts.map +1 -0
  237. package/dist/src/middleware/session.js +67 -0
  238. package/dist/src/middleware/session.js.map +1 -0
  239. package/dist/src/middleware/validate.d.ts +3 -0
  240. package/dist/src/middleware/validate.d.ts.map +1 -0
  241. package/dist/src/middleware/validate.js +85 -0
  242. package/dist/src/middleware/validate.js.map +1 -0
  243. package/dist/src/policy/engine.d.ts +107 -0
  244. package/dist/src/policy/engine.d.ts.map +1 -0
  245. package/dist/src/policy/engine.js +646 -0
  246. package/dist/src/policy/engine.js.map +1 -0
  247. package/dist/src/policy/index.d.ts +3 -0
  248. package/dist/src/policy/index.d.ts.map +1 -0
  249. package/dist/src/policy/index.js +8 -0
  250. package/dist/src/policy/index.js.map +1 -0
  251. package/dist/src/policy/opa-engine.d.ts +176 -0
  252. package/dist/src/policy/opa-engine.d.ts.map +1 -0
  253. package/dist/src/policy/opa-engine.js +790 -0
  254. package/dist/src/policy/opa-engine.js.map +1 -0
  255. package/dist/src/proxy/forward-proxy.d.ts +30 -0
  256. package/dist/src/proxy/forward-proxy.d.ts.map +1 -0
  257. package/dist/src/proxy/forward-proxy.js +580 -0
  258. package/dist/src/proxy/forward-proxy.js.map +1 -0
  259. package/dist/src/proxy/index.d.ts +2 -0
  260. package/dist/src/proxy/index.d.ts.map +1 -0
  261. package/dist/src/proxy/index.js +8 -0
  262. package/dist/src/proxy/index.js.map +1 -0
  263. package/dist/src/ratelimit/limiter.d.ts +45 -0
  264. package/dist/src/ratelimit/limiter.d.ts.map +1 -0
  265. package/dist/src/ratelimit/limiter.js +158 -0
  266. package/dist/src/ratelimit/limiter.js.map +1 -0
  267. package/dist/src/replay/engine.d.ts +40 -0
  268. package/dist/src/replay/engine.d.ts.map +1 -0
  269. package/dist/src/replay/engine.js +106 -0
  270. package/dist/src/replay/engine.js.map +1 -0
  271. package/dist/src/replay/index.d.ts +2 -0
  272. package/dist/src/replay/index.d.ts.map +1 -0
  273. package/dist/src/replay/index.js +6 -0
  274. package/dist/src/replay/index.js.map +1 -0
  275. package/dist/src/saas/index.d.ts +2 -0
  276. package/dist/src/saas/index.d.ts.map +1 -0
  277. package/dist/src/saas/index.js +18 -0
  278. package/dist/src/saas/index.js.map +1 -0
  279. package/dist/src/saas/routes.d.ts +18 -0
  280. package/dist/src/saas/routes.d.ts.map +1 -0
  281. package/dist/src/saas/routes.js +1566 -0
  282. package/dist/src/saas/routes.js.map +1 -0
  283. package/dist/src/server/app.d.ts +44 -0
  284. package/dist/src/server/app.d.ts.map +1 -0
  285. package/dist/src/server/app.js +854 -0
  286. package/dist/src/server/app.js.map +1 -0
  287. package/dist/src/server/errors.d.ts +32 -0
  288. package/dist/src/server/errors.d.ts.map +1 -0
  289. package/dist/src/server/errors.js +39 -0
  290. package/dist/src/server/errors.js.map +1 -0
  291. package/dist/src/server/gateway.d.ts +165 -0
  292. package/dist/src/server/gateway.d.ts.map +1 -0
  293. package/dist/src/server/gateway.js +964 -0
  294. package/dist/src/server/gateway.js.map +1 -0
  295. package/dist/src/server/index.d.ts +2 -0
  296. package/dist/src/server/index.d.ts.map +1 -0
  297. package/dist/src/server/index.js +295 -0
  298. package/dist/src/server/index.js.map +1 -0
  299. package/dist/src/server/logger.d.ts +33 -0
  300. package/dist/src/server/logger.d.ts.map +1 -0
  301. package/dist/src/server/logger.js +230 -0
  302. package/dist/src/server/logger.js.map +1 -0
  303. package/dist/src/server/stream-proxy.d.ts +32 -0
  304. package/dist/src/server/stream-proxy.d.ts.map +1 -0
  305. package/dist/src/server/stream-proxy.js +184 -0
  306. package/dist/src/server/stream-proxy.js.map +1 -0
  307. package/dist/src/storage/file-persistence.d.ts +48 -0
  308. package/dist/src/storage/file-persistence.d.ts.map +1 -0
  309. package/dist/src/storage/file-persistence.js +280 -0
  310. package/dist/src/storage/file-persistence.js.map +1 -0
  311. package/dist/src/storage/index.d.ts +5 -0
  312. package/dist/src/storage/index.d.ts.map +1 -0
  313. package/dist/src/storage/index.js +21 -0
  314. package/dist/src/storage/index.js.map +1 -0
  315. package/dist/src/storage/interfaces.d.ts +237 -0
  316. package/dist/src/storage/interfaces.d.ts.map +1 -0
  317. package/dist/src/storage/interfaces.js +3 -0
  318. package/dist/src/storage/interfaces.js.map +1 -0
  319. package/dist/src/storage/memory.d.ts +162 -0
  320. package/dist/src/storage/memory.d.ts.map +1 -0
  321. package/dist/src/storage/memory.js +603 -0
  322. package/dist/src/storage/memory.js.map +1 -0
  323. package/dist/src/storage/postgres.d.ts +267 -0
  324. package/dist/src/storage/postgres.d.ts.map +1 -0
  325. package/dist/src/storage/postgres.js +1555 -0
  326. package/dist/src/storage/postgres.js.map +1 -0
  327. package/dist/src/storage/redis.d.ts +202 -0
  328. package/dist/src/storage/redis.d.ts.map +1 -0
  329. package/dist/src/storage/redis.js +629 -0
  330. package/dist/src/storage/redis.js.map +1 -0
  331. package/dist/src/tracing/index.d.ts +2 -0
  332. package/dist/src/tracing/index.d.ts.map +1 -0
  333. package/dist/src/tracing/index.js +6 -0
  334. package/dist/src/tracing/index.js.map +1 -0
  335. package/dist/src/tracing/provider.d.ts +43 -0
  336. package/dist/src/tracing/provider.d.ts.map +1 -0
  337. package/dist/src/tracing/provider.js +74 -0
  338. package/dist/src/tracing/provider.js.map +1 -0
  339. package/dist/src/trust/calculator.d.ts +54 -0
  340. package/dist/src/trust/calculator.d.ts.map +1 -0
  341. package/dist/src/trust/calculator.js +102 -0
  342. package/dist/src/trust/calculator.js.map +1 -0
  343. package/dist/src/trust/index.d.ts +2 -0
  344. package/dist/src/trust/index.d.ts.map +1 -0
  345. package/dist/src/trust/index.js +7 -0
  346. package/dist/src/trust/index.js.map +1 -0
  347. package/dist/src/types/budget.d.ts +30 -0
  348. package/dist/src/types/budget.d.ts.map +1 -0
  349. package/dist/src/types/budget.js +3 -0
  350. package/dist/src/types/budget.js.map +1 -0
  351. package/dist/src/types/config.d.ts +176 -0
  352. package/dist/src/types/config.d.ts.map +1 -0
  353. package/dist/src/types/config.js +3 -0
  354. package/dist/src/types/config.js.map +1 -0
  355. package/dist/src/types/events.d.ts +24 -0
  356. package/dist/src/types/events.d.ts.map +1 -0
  357. package/dist/src/types/events.js +3 -0
  358. package/dist/src/types/events.js.map +1 -0
  359. package/dist/src/types/index.d.ts +8 -0
  360. package/dist/src/types/index.d.ts.map +1 -0
  361. package/dist/src/types/index.js +24 -0
  362. package/dist/src/types/index.js.map +1 -0
  363. package/dist/src/types/policy.d.ts +60 -0
  364. package/dist/src/types/policy.d.ts.map +1 -0
  365. package/dist/src/types/policy.js +3 -0
  366. package/dist/src/types/policy.js.map +1 -0
  367. package/dist/src/types/stripe-config.d.ts +12 -0
  368. package/dist/src/types/stripe-config.d.ts.map +1 -0
  369. package/dist/src/types/stripe-config.js +3 -0
  370. package/dist/src/types/stripe-config.js.map +1 -0
  371. package/dist/src/types/subscription.d.ts +24 -0
  372. package/dist/src/types/subscription.d.ts.map +1 -0
  373. package/dist/src/types/subscription.js +38 -0
  374. package/dist/src/types/subscription.js.map +1 -0
  375. package/dist/src/types/tool-call.d.ts +42 -0
  376. package/dist/src/types/tool-call.d.ts.map +1 -0
  377. package/dist/src/types/tool-call.js +3 -0
  378. package/dist/src/types/tool-call.js.map +1 -0
  379. package/dist/src/types/tool-result.d.ts +58 -0
  380. package/dist/src/types/tool-result.d.ts.map +1 -0
  381. package/dist/src/types/tool-result.js +3 -0
  382. package/dist/src/types/tool-result.js.map +1 -0
  383. package/dist/src/types/user.d.ts +101 -0
  384. package/dist/src/types/user.d.ts.map +1 -0
  385. package/dist/src/types/user.js +6 -0
  386. package/dist/src/types/user.js.map +1 -0
  387. package/dist/tests/integration/api.test.d.ts +2 -0
  388. package/dist/tests/integration/api.test.d.ts.map +1 -0
  389. package/dist/tests/integration/api.test.js +1199 -0
  390. package/dist/tests/integration/api.test.js.map +1 -0
  391. package/dist/tests/integration/proxy.test.d.ts +2 -0
  392. package/dist/tests/integration/proxy.test.d.ts.map +1 -0
  393. package/dist/tests/integration/proxy.test.js +251 -0
  394. package/dist/tests/integration/proxy.test.js.map +1 -0
  395. package/dist/tests/integration/storage.test.d.ts +16 -0
  396. package/dist/tests/integration/storage.test.d.ts.map +1 -0
  397. package/dist/tests/integration/storage.test.js +826 -0
  398. package/dist/tests/integration/storage.test.js.map +1 -0
  399. package/dist/tests/unit/admin.test.d.ts +2 -0
  400. package/dist/tests/unit/admin.test.d.ts.map +1 -0
  401. package/dist/tests/unit/admin.test.js +698 -0
  402. package/dist/tests/unit/admin.test.js.map +1 -0
  403. package/dist/tests/unit/anomaly-detector.test.d.ts +2 -0
  404. package/dist/tests/unit/anomaly-detector.test.d.ts.map +1 -0
  405. package/dist/tests/unit/anomaly-detector.test.js +903 -0
  406. package/dist/tests/unit/anomaly-detector.test.js.map +1 -0
  407. package/dist/tests/unit/approval-manager.test.d.ts +2 -0
  408. package/dist/tests/unit/approval-manager.test.d.ts.map +1 -0
  409. package/dist/tests/unit/approval-manager.test.js +528 -0
  410. package/dist/tests/unit/approval-manager.test.js.map +1 -0
  411. package/dist/tests/unit/approval-webhook.test.d.ts +2 -0
  412. package/dist/tests/unit/approval-webhook.test.d.ts.map +1 -0
  413. package/dist/tests/unit/approval-webhook.test.js +355 -0
  414. package/dist/tests/unit/approval-webhook.test.js.map +1 -0
  415. package/dist/tests/unit/audit-logger.test.d.ts +2 -0
  416. package/dist/tests/unit/audit-logger.test.d.ts.map +1 -0
  417. package/dist/tests/unit/audit-logger.test.js +635 -0
  418. package/dist/tests/unit/audit-logger.test.js.map +1 -0
  419. package/dist/tests/unit/auth-routes.test.d.ts +2 -0
  420. package/dist/tests/unit/auth-routes.test.d.ts.map +1 -0
  421. package/dist/tests/unit/auth-routes.test.js +281 -0
  422. package/dist/tests/unit/auth-routes.test.js.map +1 -0
  423. package/dist/tests/unit/auth.test.d.ts +2 -0
  424. package/dist/tests/unit/auth.test.d.ts.map +1 -0
  425. package/dist/tests/unit/auth.test.js +1382 -0
  426. package/dist/tests/unit/auth.test.js.map +1 -0
  427. package/dist/tests/unit/billing.test.d.ts +2 -0
  428. package/dist/tests/unit/billing.test.d.ts.map +1 -0
  429. package/dist/tests/unit/billing.test.js +579 -0
  430. package/dist/tests/unit/billing.test.js.map +1 -0
  431. package/dist/tests/unit/budget-manager.test.d.ts +2 -0
  432. package/dist/tests/unit/budget-manager.test.d.ts.map +1 -0
  433. package/dist/tests/unit/budget-manager.test.js +778 -0
  434. package/dist/tests/unit/budget-manager.test.js.map +1 -0
  435. package/dist/tests/unit/budget-race.test.d.ts +2 -0
  436. package/dist/tests/unit/budget-race.test.d.ts.map +1 -0
  437. package/dist/tests/unit/budget-race.test.js +58 -0
  438. package/dist/tests/unit/budget-race.test.js.map +1 -0
  439. package/dist/tests/unit/cli.test.d.ts +2 -0
  440. package/dist/tests/unit/cli.test.d.ts.map +1 -0
  441. package/dist/tests/unit/cli.test.js +93 -0
  442. package/dist/tests/unit/cli.test.js.map +1 -0
  443. package/dist/tests/unit/concurrency.test.d.ts +2 -0
  444. package/dist/tests/unit/concurrency.test.d.ts.map +1 -0
  445. package/dist/tests/unit/concurrency.test.js +1270 -0
  446. package/dist/tests/unit/concurrency.test.js.map +1 -0
  447. package/dist/tests/unit/config-validate.test.d.ts +2 -0
  448. package/dist/tests/unit/config-validate.test.d.ts.map +1 -0
  449. package/dist/tests/unit/config-validate.test.js +230 -0
  450. package/dist/tests/unit/config-validate.test.js.map +1 -0
  451. package/dist/tests/unit/defaults.test.d.ts +2 -0
  452. package/dist/tests/unit/defaults.test.d.ts.map +1 -0
  453. package/dist/tests/unit/defaults.test.js +364 -0
  454. package/dist/tests/unit/defaults.test.js.map +1 -0
  455. package/dist/tests/unit/dlp-backends.test.d.ts +2 -0
  456. package/dist/tests/unit/dlp-backends.test.d.ts.map +1 -0
  457. package/dist/tests/unit/dlp-backends.test.js +563 -0
  458. package/dist/tests/unit/dlp-backends.test.js.map +1 -0
  459. package/dist/tests/unit/dlp-scanner.test.d.ts +2 -0
  460. package/dist/tests/unit/dlp-scanner.test.d.ts.map +1 -0
  461. package/dist/tests/unit/dlp-scanner.test.js +739 -0
  462. package/dist/tests/unit/dlp-scanner.test.js.map +1 -0
  463. package/dist/tests/unit/error-responses.test.d.ts +2 -0
  464. package/dist/tests/unit/error-responses.test.d.ts.map +1 -0
  465. package/dist/tests/unit/error-responses.test.js +101 -0
  466. package/dist/tests/unit/error-responses.test.js.map +1 -0
  467. package/dist/tests/unit/executor-registry.test.d.ts +2 -0
  468. package/dist/tests/unit/executor-registry.test.d.ts.map +1 -0
  469. package/dist/tests/unit/executor-registry.test.js +390 -0
  470. package/dist/tests/unit/executor-registry.test.js.map +1 -0
  471. package/dist/tests/unit/forward-proxy.test.d.ts +2 -0
  472. package/dist/tests/unit/forward-proxy.test.d.ts.map +1 -0
  473. package/dist/tests/unit/forward-proxy.test.js +621 -0
  474. package/dist/tests/unit/forward-proxy.test.js.map +1 -0
  475. package/dist/tests/unit/gateway-features.test.d.ts +2 -0
  476. package/dist/tests/unit/gateway-features.test.d.ts.map +1 -0
  477. package/dist/tests/unit/gateway-features.test.js +753 -0
  478. package/dist/tests/unit/gateway-features.test.js.map +1 -0
  479. package/dist/tests/unit/http-executor.test.d.ts +2 -0
  480. package/dist/tests/unit/http-executor.test.d.ts.map +1 -0
  481. package/dist/tests/unit/http-executor.test.js +310 -0
  482. package/dist/tests/unit/http-executor.test.js.map +1 -0
  483. package/dist/tests/unit/mcp-bridge.test.d.ts +2 -0
  484. package/dist/tests/unit/mcp-bridge.test.d.ts.map +1 -0
  485. package/dist/tests/unit/mcp-bridge.test.js +1136 -0
  486. package/dist/tests/unit/mcp-bridge.test.js.map +1 -0
  487. package/dist/tests/unit/mcp-http-transport.test.d.ts +2 -0
  488. package/dist/tests/unit/mcp-http-transport.test.d.ts.map +1 -0
  489. package/dist/tests/unit/mcp-http-transport.test.js +899 -0
  490. package/dist/tests/unit/mcp-http-transport.test.js.map +1 -0
  491. package/dist/tests/unit/mcp-oauth.test.d.ts +2 -0
  492. package/dist/tests/unit/mcp-oauth.test.d.ts.map +1 -0
  493. package/dist/tests/unit/mcp-oauth.test.js +759 -0
  494. package/dist/tests/unit/mcp-oauth.test.js.map +1 -0
  495. package/dist/tests/unit/mcp-server.test.d.ts +15 -0
  496. package/dist/tests/unit/mcp-server.test.d.ts.map +1 -0
  497. package/dist/tests/unit/mcp-server.test.js +158 -0
  498. package/dist/tests/unit/mcp-server.test.js.map +1 -0
  499. package/dist/tests/unit/metrics.test.d.ts +2 -0
  500. package/dist/tests/unit/metrics.test.d.ts.map +1 -0
  501. package/dist/tests/unit/metrics.test.js +208 -0
  502. package/dist/tests/unit/metrics.test.js.map +1 -0
  503. package/dist/tests/unit/oauth.test.d.ts +2 -0
  504. package/dist/tests/unit/oauth.test.d.ts.map +1 -0
  505. package/dist/tests/unit/oauth.test.js +281 -0
  506. package/dist/tests/unit/oauth.test.js.map +1 -0
  507. package/dist/tests/unit/opa-circuit-breaker.test.d.ts +2 -0
  508. package/dist/tests/unit/opa-circuit-breaker.test.d.ts.map +1 -0
  509. package/dist/tests/unit/opa-circuit-breaker.test.js +297 -0
  510. package/dist/tests/unit/opa-circuit-breaker.test.js.map +1 -0
  511. package/dist/tests/unit/opa-engine.test.d.ts +2 -0
  512. package/dist/tests/unit/opa-engine.test.d.ts.map +1 -0
  513. package/dist/tests/unit/opa-engine.test.js +1813 -0
  514. package/dist/tests/unit/opa-engine.test.js.map +1 -0
  515. package/dist/tests/unit/pipeline-timing.test.d.ts +2 -0
  516. package/dist/tests/unit/pipeline-timing.test.d.ts.map +1 -0
  517. package/dist/tests/unit/pipeline-timing.test.js +528 -0
  518. package/dist/tests/unit/pipeline-timing.test.js.map +1 -0
  519. package/dist/tests/unit/policy-engine.test.d.ts +2 -0
  520. package/dist/tests/unit/policy-engine.test.d.ts.map +1 -0
  521. package/dist/tests/unit/policy-engine.test.js +1345 -0
  522. package/dist/tests/unit/policy-engine.test.js.map +1 -0
  523. package/dist/tests/unit/policy-store.test.d.ts +2 -0
  524. package/dist/tests/unit/policy-store.test.d.ts.map +1 -0
  525. package/dist/tests/unit/policy-store.test.js +60 -0
  526. package/dist/tests/unit/policy-store.test.js.map +1 -0
  527. package/dist/tests/unit/postgres-storage.test.d.ts +2 -0
  528. package/dist/tests/unit/postgres-storage.test.d.ts.map +1 -0
  529. package/dist/tests/unit/postgres-storage.test.js +614 -0
  530. package/dist/tests/unit/postgres-storage.test.js.map +1 -0
  531. package/dist/tests/unit/prompt-injection-backend.test.d.ts +2 -0
  532. package/dist/tests/unit/prompt-injection-backend.test.d.ts.map +1 -0
  533. package/dist/tests/unit/prompt-injection-backend.test.js +621 -0
  534. package/dist/tests/unit/prompt-injection-backend.test.js.map +1 -0
  535. package/dist/tests/unit/proxy-hardening.test.d.ts +2 -0
  536. package/dist/tests/unit/proxy-hardening.test.d.ts.map +1 -0
  537. package/dist/tests/unit/proxy-hardening.test.js +166 -0
  538. package/dist/tests/unit/proxy-hardening.test.js.map +1 -0
  539. package/dist/tests/unit/rate-limiter.test.d.ts +2 -0
  540. package/dist/tests/unit/rate-limiter.test.d.ts.map +1 -0
  541. package/dist/tests/unit/rate-limiter.test.js +443 -0
  542. package/dist/tests/unit/rate-limiter.test.js.map +1 -0
  543. package/dist/tests/unit/redis-storage.test.d.ts +2 -0
  544. package/dist/tests/unit/redis-storage.test.d.ts.map +1 -0
  545. package/dist/tests/unit/redis-storage.test.js +766 -0
  546. package/dist/tests/unit/redis-storage.test.js.map +1 -0
  547. package/dist/tests/unit/replay-engine.test.d.ts +2 -0
  548. package/dist/tests/unit/replay-engine.test.d.ts.map +1 -0
  549. package/dist/tests/unit/replay-engine.test.js +371 -0
  550. package/dist/tests/unit/replay-engine.test.js.map +1 -0
  551. package/dist/tests/unit/saas-routes.test.d.ts +2 -0
  552. package/dist/tests/unit/saas-routes.test.d.ts.map +1 -0
  553. package/dist/tests/unit/saas-routes.test.js +1399 -0
  554. package/dist/tests/unit/saas-routes.test.js.map +1 -0
  555. package/dist/tests/unit/session.test.d.ts +2 -0
  556. package/dist/tests/unit/session.test.d.ts.map +1 -0
  557. package/dist/tests/unit/session.test.js +532 -0
  558. package/dist/tests/unit/session.test.js.map +1 -0
  559. package/dist/tests/unit/slack-executor.test.d.ts +2 -0
  560. package/dist/tests/unit/slack-executor.test.d.ts.map +1 -0
  561. package/dist/tests/unit/slack-executor.test.js +209 -0
  562. package/dist/tests/unit/slack-executor.test.js.map +1 -0
  563. package/dist/tests/unit/storage-hardening.test.d.ts +2 -0
  564. package/dist/tests/unit/storage-hardening.test.d.ts.map +1 -0
  565. package/dist/tests/unit/storage-hardening.test.js +165 -0
  566. package/dist/tests/unit/storage-hardening.test.js.map +1 -0
  567. package/dist/tests/unit/storage.test.d.ts +2 -0
  568. package/dist/tests/unit/storage.test.d.ts.map +1 -0
  569. package/dist/tests/unit/storage.test.js +698 -0
  570. package/dist/tests/unit/storage.test.js.map +1 -0
  571. package/dist/tests/unit/text-normalizer.test.d.ts +2 -0
  572. package/dist/tests/unit/text-normalizer.test.d.ts.map +1 -0
  573. package/dist/tests/unit/text-normalizer.test.js +229 -0
  574. package/dist/tests/unit/text-normalizer.test.js.map +1 -0
  575. package/dist/tests/unit/tracing.test.d.ts +2 -0
  576. package/dist/tests/unit/tracing.test.d.ts.map +1 -0
  577. package/dist/tests/unit/tracing.test.js +611 -0
  578. package/dist/tests/unit/tracing.test.js.map +1 -0
  579. package/dist/tests/unit/trust-calculator.test.d.ts +2 -0
  580. package/dist/tests/unit/trust-calculator.test.d.ts.map +1 -0
  581. package/dist/tests/unit/trust-calculator.test.js +497 -0
  582. package/dist/tests/unit/trust-calculator.test.js.map +1 -0
  583. package/dist/tests/unit/ts-sdk.test.d.ts +2 -0
  584. package/dist/tests/unit/ts-sdk.test.d.ts.map +1 -0
  585. package/dist/tests/unit/ts-sdk.test.js +421 -0
  586. package/dist/tests/unit/ts-sdk.test.js.map +1 -0
  587. package/dist/tests/unit/usage-extractor-llm.test.d.ts +2 -0
  588. package/dist/tests/unit/usage-extractor-llm.test.d.ts.map +1 -0
  589. package/dist/tests/unit/usage-extractor-llm.test.js +139 -0
  590. package/dist/tests/unit/usage-extractor-llm.test.js.map +1 -0
  591. package/dist/tests/unit/usage-extractor.test.d.ts +2 -0
  592. package/dist/tests/unit/usage-extractor.test.d.ts.map +1 -0
  593. package/dist/tests/unit/usage-extractor.test.js +271 -0
  594. package/dist/tests/unit/usage-extractor.test.js.map +1 -0
  595. package/dist/tests/unit/user-stores.test.d.ts +2 -0
  596. package/dist/tests/unit/user-stores.test.d.ts.map +1 -0
  597. package/dist/tests/unit/user-stores.test.js +687 -0
  598. package/dist/tests/unit/user-stores.test.js.map +1 -0
  599. package/dist/tests/unit/validate.test.d.ts +2 -0
  600. package/dist/tests/unit/validate.test.d.ts.map +1 -0
  601. package/dist/tests/unit/validate.test.js +545 -0
  602. package/dist/tests/unit/validate.test.js.map +1 -0
  603. package/package.json +86 -0
  604. package/policy-packs/README.md +42 -0
  605. package/policy-packs/default.yaml +46 -0
  606. package/policy-packs/dev_fast.yaml +54 -0
  607. package/policy-packs/prod_strict.yaml +83 -0
@@ -0,0 +1,1555 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostgresPolicyStore = exports.PostgresBudgetConfigStore = exports.PostgresRateLimitConfigStore = exports.PostgresSubscriptionStore = exports.PostgresUserApiKeyStore = exports.PostgresSessionStore = exports.PostgresWorkspaceMemberStore = exports.PostgresWorkspaceStore = exports.PostgresOAuthAccountStore = exports.PostgresUserStore = exports.PostgresRateLimitStore = exports.PostgresIdempotencyStore = exports.PostgresApprovalStore = exports.PostgresAuditStore = exports.PostgresBudgetStore = void 0;
4
+ exports.initPostgresStorage = initPostgresStorage;
5
+ exports.createPostgresStores = createPostgresStores;
6
+ exports.createPostgresSaaSStores = createPostgresSaaSStores;
7
+ // ---------------------------------------------------------------------------
8
+ // Column whitelists (prevent SQL injection via dynamic column names)
9
+ // ---------------------------------------------------------------------------
10
+ const ALLOWED_USER_COLUMNS = new Set([
11
+ 'email', 'display_name', 'avatar_url', 'password_hash',
12
+ 'status', 'onboarding_completed', 'updated_at',
13
+ ]);
14
+ const ALLOWED_OAUTH_ACCOUNT_COLUMNS = new Set([
15
+ 'access_token_encrypted', 'refresh_token_encrypted',
16
+ 'token_expires_at', 'updated_at',
17
+ ]);
18
+ const ALLOWED_WORKSPACE_COLUMNS = new Set([
19
+ 'name', 'slug', 'plan', 'settings', 'updated_at',
20
+ ]);
21
+ const ALLOWED_SESSION_COLUMNS = new Set([
22
+ 'workspace_id', 'last_active_at',
23
+ ]);
24
+ const ALLOWED_USER_API_KEY_COLUMNS = new Set([
25
+ 'name', 'roles', 'tags', 'revoked', 'last_used_at',
26
+ ]);
27
+ const ALLOWED_SUBSCRIPTION_COLUMNS = new Set([
28
+ 'workspace_id', 'stripe_customer_id', 'stripe_subscription_id',
29
+ 'plan', 'status', 'current_period_start', 'current_period_end',
30
+ 'cancel_at_period_end', 'updated_at',
31
+ ]);
32
+ /**
33
+ * Filter update entries to only include allowed column names.
34
+ * Returns entries as [key, value] pairs with only whitelisted keys.
35
+ */
36
+ function sanitizeUpdateEntries(updates, allowedColumns) {
37
+ return Object.entries(updates).filter(([key]) => allowedColumns.has(key));
38
+ }
39
+ // ---------------------------------------------------------------------------
40
+ // Helpers
41
+ // ---------------------------------------------------------------------------
42
+ /**
43
+ * Fire-and-forget a Postgres query. Errors are logged but never thrown so the
44
+ * synchronous hot path is never blocked or crashed.
45
+ */
46
+ function asyncQuery(pool, text, values) {
47
+ pool.query(text, values).catch((err) => {
48
+ console.error('[postgres-storage] async query failed:', text.substring(0, 200), err.message);
49
+ });
50
+ }
51
+ /**
52
+ * Execute a Postgres query with exponential-backoff retry (up to 3 attempts).
53
+ * Returns a Promise that resolves when the query succeeds or rejects after
54
+ * exhausting retries. Use this for writes that must be tracked via flush().
55
+ */
56
+ function reliableAsyncQuery(pool, text, values) {
57
+ const maxAttempts = 3;
58
+ async function attempt(n) {
59
+ try {
60
+ await pool.query(text, values);
61
+ }
62
+ catch (err) {
63
+ if (n < maxAttempts) {
64
+ const delay = Math.pow(2, n) * 100; // 200ms, 400ms
65
+ await new Promise((r) => setTimeout(r, delay));
66
+ return attempt(n + 1);
67
+ }
68
+ throw new Error(`[postgres-storage] reliable query failed after retries: ${err.message}`);
69
+ }
70
+ }
71
+ return attempt(1);
72
+ }
73
+ /**
74
+ * Execute a Postgres query synchronously (awaited) with retry (3 attempts, exponential backoff).
75
+ * Throws on failure. Use for critical writes that must succeed before proceeding.
76
+ */
77
+ async function syncQuery(pool, text, values) {
78
+ const maxAttempts = 3;
79
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
80
+ try {
81
+ await pool.query(text, values);
82
+ return;
83
+ }
84
+ catch (err) {
85
+ if (attempt === maxAttempts) {
86
+ throw new Error(`[postgres-storage] query failed after ${maxAttempts} retries: ${err.message}`);
87
+ }
88
+ await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 100));
89
+ }
90
+ }
91
+ }
92
+ // ---------------------------------------------------------------------------
93
+ // 1. PostgresBudgetStore
94
+ // ---------------------------------------------------------------------------
95
+ class PostgresBudgetStore {
96
+ constructor(pool) {
97
+ this.pool = pool;
98
+ this.taskStates = new Map();
99
+ this.counters = new Map();
100
+ this.retryCounts = new Map();
101
+ this._pendingWrites = [];
102
+ }
103
+ // -- Table DDL ----------------------------------------------------------
104
+ static async createTables(pool) {
105
+ await pool.query(`
106
+ CREATE TABLE IF NOT EXISTS budget_task_states (
107
+ task_id TEXT PRIMARY KEY,
108
+ workspace_id TEXT NOT NULL,
109
+ actor_id TEXT NOT NULL,
110
+ spent_usd DOUBLE PRECISION NOT NULL DEFAULT 0,
111
+ steps INTEGER NOT NULL DEFAULT 0,
112
+ started_at TEXT NOT NULL
113
+ );
114
+ `);
115
+ await pool.query(`
116
+ CREATE TABLE IF NOT EXISTS budget_counters (
117
+ key TEXT PRIMARY KEY,
118
+ value DOUBLE PRECISION NOT NULL DEFAULT 0
119
+ );
120
+ `);
121
+ await pool.query(`
122
+ CREATE TABLE IF NOT EXISTS budget_retry_counts (
123
+ tool_call_id TEXT PRIMARY KEY,
124
+ count INTEGER NOT NULL DEFAULT 0
125
+ );
126
+ `);
127
+ }
128
+ // -- Hydrate cache from Postgres ----------------------------------------
129
+ async hydrate() {
130
+ try {
131
+ const { rows: states } = await this.pool.query('SELECT * FROM budget_task_states');
132
+ for (const r of states) {
133
+ this.taskStates.set(r.task_id, {
134
+ task_id: r.task_id,
135
+ workspace_id: r.workspace_id,
136
+ actor_id: r.actor_id,
137
+ spent_usd: Number(r.spent_usd),
138
+ steps: Number(r.steps),
139
+ started_at: r.started_at,
140
+ });
141
+ }
142
+ const { rows: ctrs } = await this.pool.query('SELECT * FROM budget_counters');
143
+ for (const r of ctrs) {
144
+ this.counters.set(r.key, Number(r.value));
145
+ }
146
+ const { rows: retries } = await this.pool.query('SELECT * FROM budget_retry_counts');
147
+ for (const r of retries) {
148
+ this.retryCounts.set(r.tool_call_id, Number(r.count));
149
+ }
150
+ }
151
+ catch (err) {
152
+ console.error('[PostgresBudgetStore] hydrate failed:', err.message);
153
+ }
154
+ }
155
+ // -- Interface implementation -------------------------------------------
156
+ getTaskState(taskId) {
157
+ return this.taskStates.get(taskId);
158
+ }
159
+ setTaskState(taskId, state) {
160
+ this.taskStates.set(taskId, state);
161
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO budget_task_states (task_id, workspace_id, actor_id, spent_usd, steps, started_at)
162
+ VALUES ($1, $2, $3, $4, $5, $6)
163
+ ON CONFLICT (task_id) DO UPDATE SET
164
+ workspace_id = EXCLUDED.workspace_id,
165
+ actor_id = EXCLUDED.actor_id,
166
+ spent_usd = EXCLUDED.spent_usd,
167
+ steps = EXCLUDED.steps,
168
+ started_at = EXCLUDED.started_at`, [state.task_id, state.workspace_id, state.actor_id, state.spent_usd, state.steps, state.started_at]));
169
+ }
170
+ getCounter(key) {
171
+ return this.counters.get(key) ?? 0;
172
+ }
173
+ incrementCounter(key, amount) {
174
+ const newVal = (this.counters.get(key) ?? 0) + amount;
175
+ this.counters.set(key, newVal);
176
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO budget_counters (key, value) VALUES ($1, $2)
177
+ ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`, [key, newVal]));
178
+ }
179
+ getRetryCount(toolCallId) {
180
+ return this.retryCounts.get(toolCallId) ?? 0;
181
+ }
182
+ incrementRetryCount(toolCallId) {
183
+ const count = (this.retryCounts.get(toolCallId) ?? 0) + 1;
184
+ this.retryCounts.set(toolCallId, count);
185
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO budget_retry_counts (tool_call_id, count) VALUES ($1, $2)
186
+ ON CONFLICT (tool_call_id) DO UPDATE SET count = EXCLUDED.count`, [toolCallId, count]));
187
+ return count;
188
+ }
189
+ async flush() {
190
+ const writes = this._pendingWrites;
191
+ this._pendingWrites = [];
192
+ await Promise.all(writes);
193
+ }
194
+ reset() {
195
+ this.taskStates.clear();
196
+ this.counters.clear();
197
+ this.retryCounts.clear();
198
+ asyncQuery(this.pool, 'TRUNCATE budget_task_states, budget_counters, budget_retry_counts');
199
+ }
200
+ }
201
+ exports.PostgresBudgetStore = PostgresBudgetStore;
202
+ // ---------------------------------------------------------------------------
203
+ // 2. PostgresAuditStore
204
+ // ---------------------------------------------------------------------------
205
+ class PostgresAuditStore {
206
+ constructor(pool) {
207
+ this.pool = pool;
208
+ this.events = [];
209
+ this._pendingWrites = [];
210
+ }
211
+ // -- Table DDL ----------------------------------------------------------
212
+ static async createTables(pool) {
213
+ await pool.query(`
214
+ CREATE TABLE IF NOT EXISTS audit_events (
215
+ event_id TEXT PRIMARY KEY,
216
+ event_type TEXT NOT NULL,
217
+ timestamp TEXT NOT NULL,
218
+ tool_call_id TEXT NOT NULL,
219
+ task_id TEXT NOT NULL,
220
+ workspace_id TEXT NOT NULL,
221
+ actor_id TEXT NOT NULL,
222
+ tool_name TEXT NOT NULL,
223
+ metadata JSONB NOT NULL DEFAULT '{}'
224
+ );
225
+ `);
226
+ }
227
+ // -- Hydrate cache from Postgres ----------------------------------------
228
+ async hydrate() {
229
+ try {
230
+ const { rows } = await this.pool.query('SELECT * FROM audit_events ORDER BY timestamp ASC');
231
+ this.events = rows.map((r) => ({
232
+ event_id: r.event_id,
233
+ event_type: r.event_type,
234
+ timestamp: r.timestamp,
235
+ tool_call_id: r.tool_call_id,
236
+ task_id: r.task_id,
237
+ workspace_id: r.workspace_id,
238
+ actor_id: r.actor_id,
239
+ tool_name: r.tool_name,
240
+ metadata: r.metadata ?? {},
241
+ }));
242
+ }
243
+ catch (err) {
244
+ console.error('[PostgresAuditStore] hydrate failed:', err.message);
245
+ }
246
+ }
247
+ // -- Interface implementation -------------------------------------------
248
+ append(event) {
249
+ this.events.push(event);
250
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO audit_events (event_id, event_type, timestamp, tool_call_id, task_id, workspace_id, actor_id, tool_name, metadata)
251
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
252
+ ON CONFLICT (event_id) DO NOTHING`, [
253
+ event.event_id,
254
+ event.event_type,
255
+ event.timestamp,
256
+ event.tool_call_id,
257
+ event.task_id,
258
+ event.workspace_id,
259
+ event.actor_id,
260
+ event.tool_name,
261
+ JSON.stringify(event.metadata),
262
+ ]));
263
+ }
264
+ async flush() {
265
+ const writes = this._pendingWrites;
266
+ this._pendingWrites = [];
267
+ await Promise.all(writes);
268
+ }
269
+ getByTaskId(taskId) {
270
+ return this.events
271
+ .filter((e) => e.task_id === taskId)
272
+ .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
273
+ }
274
+ getByToolCallId(toolCallId) {
275
+ return this.events
276
+ .filter((e) => e.tool_call_id === toolCallId)
277
+ .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
278
+ }
279
+ getByEventType(eventType) {
280
+ return this.events.filter((e) => e.event_type === eventType);
281
+ }
282
+ getAll() {
283
+ return [...this.events];
284
+ }
285
+ clear() {
286
+ this.events = [];
287
+ asyncQuery(this.pool, 'TRUNCATE audit_events');
288
+ }
289
+ }
290
+ exports.PostgresAuditStore = PostgresAuditStore;
291
+ // ---------------------------------------------------------------------------
292
+ // 3. PostgresApprovalStore
293
+ // ---------------------------------------------------------------------------
294
+ class PostgresApprovalStore {
295
+ constructor(pool) {
296
+ this.pool = pool;
297
+ this.approvals = new Map();
298
+ this.tokenIndex = new Map();
299
+ this._pendingWrites = [];
300
+ }
301
+ // -- Table DDL ----------------------------------------------------------
302
+ static async createTables(pool) {
303
+ await pool.query(`
304
+ CREATE TABLE IF NOT EXISTS approvals (
305
+ approval_id TEXT PRIMARY KEY,
306
+ data JSONB NOT NULL,
307
+ status TEXT NOT NULL DEFAULT 'pending',
308
+ workspace_id TEXT,
309
+ tool_call_id TEXT
310
+ );
311
+ `);
312
+ await pool.query(`
313
+ CREATE TABLE IF NOT EXISTS approval_tokens (
314
+ token TEXT PRIMARY KEY,
315
+ approval_id TEXT NOT NULL
316
+ );
317
+ `);
318
+ }
319
+ // -- Hydrate cache from Postgres ----------------------------------------
320
+ async hydrate() {
321
+ try {
322
+ const { rows: approvalRows } = await this.pool.query('SELECT * FROM approvals');
323
+ for (const r of approvalRows) {
324
+ const approval = r.data;
325
+ this.approvals.set(r.approval_id, approval);
326
+ }
327
+ const { rows: tokenRows } = await this.pool.query('SELECT * FROM approval_tokens');
328
+ for (const r of tokenRows) {
329
+ this.tokenIndex.set(r.token, r.approval_id);
330
+ }
331
+ }
332
+ catch (err) {
333
+ console.error('[PostgresApprovalStore] hydrate failed:', err.message);
334
+ }
335
+ }
336
+ // -- Interface implementation -------------------------------------------
337
+ save(approvalId, approval) {
338
+ this.approvals.set(approvalId, approval);
339
+ const status = approval.status ?? 'pending';
340
+ const workspaceId = approval.workspace_id ?? null;
341
+ const toolCallId = approval.tool_call_id ?? null;
342
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO approvals (approval_id, data, status, workspace_id, tool_call_id)
343
+ VALUES ($1, $2, $3, $4, $5)
344
+ ON CONFLICT (approval_id) DO UPDATE SET
345
+ data = EXCLUDED.data,
346
+ status = EXCLUDED.status,
347
+ workspace_id = EXCLUDED.workspace_id,
348
+ tool_call_id = EXCLUDED.tool_call_id`, [approvalId, JSON.stringify(approval), status, workspaceId, toolCallId]));
349
+ }
350
+ getById(approvalId) {
351
+ return this.approvals.get(approvalId);
352
+ }
353
+ getByToken(token) {
354
+ const id = this.tokenIndex.get(token);
355
+ return id ? this.approvals.get(id) : undefined;
356
+ }
357
+ getByToolCallId(toolCallId) {
358
+ for (const approval of this.approvals.values()) {
359
+ if (approval.tool_call_id === toolCallId)
360
+ return approval;
361
+ }
362
+ return undefined;
363
+ }
364
+ findPending(workspaceId) {
365
+ const results = [];
366
+ for (const approval of this.approvals.values()) {
367
+ if (approval.status !== 'pending')
368
+ continue;
369
+ if (workspaceId && approval.workspace_id !== workspaceId)
370
+ continue;
371
+ results.push(approval);
372
+ }
373
+ return results;
374
+ }
375
+ indexToken(token, approvalId) {
376
+ this.tokenIndex.set(token, approvalId);
377
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO approval_tokens (token, approval_id) VALUES ($1, $2)
378
+ ON CONFLICT (token) DO UPDATE SET approval_id = EXCLUDED.approval_id`, [token, approvalId]));
379
+ }
380
+ async compareAndSetStatus(approvalId, expectedStatus, approval) {
381
+ const existing = this.approvals.get(approvalId);
382
+ if (!existing || existing.status !== expectedStatus)
383
+ return false;
384
+ // Update in-memory cache optimistically
385
+ this.approvals.set(approvalId, approval);
386
+ // Atomic DB update: only succeeds if status still matches expectedStatus
387
+ try {
388
+ const result = await this.pool.query(`UPDATE approvals SET data = $2, status = $3
389
+ WHERE approval_id = $1 AND status = $4`, [approvalId, JSON.stringify(approval), approval.status, expectedStatus]);
390
+ if (result.rowCount === 0) {
391
+ // Another process already changed the status — rollback in-memory
392
+ this.approvals.set(approvalId, existing);
393
+ return false;
394
+ }
395
+ return true;
396
+ }
397
+ catch (err) {
398
+ // Rollback in-memory on DB error
399
+ this.approvals.set(approvalId, existing);
400
+ console.error('[PostgresApprovalStore] compareAndSetStatus failed:', err.message);
401
+ return false;
402
+ }
403
+ }
404
+ async flush() {
405
+ const writes = this._pendingWrites;
406
+ this._pendingWrites = [];
407
+ await Promise.all(writes);
408
+ }
409
+ clear() {
410
+ this.approvals.clear();
411
+ this.tokenIndex.clear();
412
+ asyncQuery(this.pool, 'TRUNCATE approvals, approval_tokens');
413
+ }
414
+ }
415
+ exports.PostgresApprovalStore = PostgresApprovalStore;
416
+ // ---------------------------------------------------------------------------
417
+ // 4. PostgresIdempotencyStore
418
+ // ---------------------------------------------------------------------------
419
+ class PostgresIdempotencyStore {
420
+ constructor(pool) {
421
+ this.pool = pool;
422
+ this.entries = new Map();
423
+ this._pendingWrites = [];
424
+ }
425
+ // -- Table DDL ----------------------------------------------------------
426
+ static async createTables(pool) {
427
+ await pool.query(`
428
+ CREATE TABLE IF NOT EXISTS idempotency_cache (
429
+ tool_call_id TEXT PRIMARY KEY,
430
+ result JSONB NOT NULL,
431
+ expires_at TIMESTAMPTZ NOT NULL
432
+ );
433
+ `);
434
+ }
435
+ // -- Hydrate cache from Postgres ----------------------------------------
436
+ async hydrate() {
437
+ try {
438
+ const now = new Date().toISOString();
439
+ const { rows } = await this.pool.query('SELECT * FROM idempotency_cache WHERE expires_at > $1', [now]);
440
+ for (const r of rows) {
441
+ this.entries.set(r.tool_call_id, {
442
+ result: r.result,
443
+ expiresAt: new Date(r.expires_at).getTime(),
444
+ });
445
+ }
446
+ }
447
+ catch (err) {
448
+ console.error('[PostgresIdempotencyStore] hydrate failed:', err.message);
449
+ }
450
+ }
451
+ // -- Interface implementation -------------------------------------------
452
+ async get(toolCallId) {
453
+ const entry = this.entries.get(toolCallId);
454
+ if (!entry)
455
+ return undefined;
456
+ if (Date.now() > entry.expiresAt) {
457
+ this.entries.delete(toolCallId);
458
+ asyncQuery(this.pool, 'DELETE FROM idempotency_cache WHERE tool_call_id = $1', [toolCallId]);
459
+ return undefined;
460
+ }
461
+ return entry.result;
462
+ }
463
+ set(toolCallId, result, ttlMs) {
464
+ const expiresAt = Date.now() + ttlMs;
465
+ this.entries.set(toolCallId, { result, expiresAt });
466
+ const expiresAtISO = new Date(expiresAt).toISOString();
467
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO idempotency_cache (tool_call_id, result, expires_at)
468
+ VALUES ($1, $2, $3)
469
+ ON CONFLICT (tool_call_id) DO UPDATE SET
470
+ result = EXCLUDED.result,
471
+ expires_at = EXCLUDED.expires_at`, [toolCallId, JSON.stringify(result), expiresAtISO]));
472
+ // Evict expired entries from the in-memory cache periodically
473
+ if (this.entries.size > 10000) {
474
+ const now = Date.now();
475
+ for (const [key, val] of this.entries) {
476
+ if (now > val.expiresAt)
477
+ this.entries.delete(key);
478
+ }
479
+ // Also purge expired rows from Postgres
480
+ asyncQuery(this.pool, 'DELETE FROM idempotency_cache WHERE expires_at < NOW()');
481
+ }
482
+ }
483
+ async has(toolCallId) {
484
+ return (await this.get(toolCallId)) !== undefined;
485
+ }
486
+ async flush() {
487
+ const writes = this._pendingWrites;
488
+ this._pendingWrites = [];
489
+ await Promise.all(writes);
490
+ }
491
+ clear() {
492
+ this.entries.clear();
493
+ asyncQuery(this.pool, 'TRUNCATE idempotency_cache');
494
+ }
495
+ }
496
+ exports.PostgresIdempotencyStore = PostgresIdempotencyStore;
497
+ // ---------------------------------------------------------------------------
498
+ // 5. PostgresRateLimitStore
499
+ // ---------------------------------------------------------------------------
500
+ class PostgresRateLimitStore {
501
+ constructor(pool) {
502
+ this.pool = pool;
503
+ this.windows = new Map();
504
+ }
505
+ // -- Table DDL ----------------------------------------------------------
506
+ static async createTables(pool) {
507
+ await pool.query(`
508
+ CREATE TABLE IF NOT EXISTS rate_limit_hits (
509
+ id SERIAL PRIMARY KEY,
510
+ key TEXT NOT NULL,
511
+ timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW()
512
+ );
513
+ `);
514
+ await pool.query(`
515
+ CREATE INDEX IF NOT EXISTS idx_rate_limit_hits_key_ts
516
+ ON rate_limit_hits (key, timestamp);
517
+ `);
518
+ }
519
+ // -- Hydrate cache from Postgres ----------------------------------------
520
+ async hydrate() {
521
+ try {
522
+ // Only load hits from the last hour (any reasonable upper-bound window)
523
+ const cutoff = new Date(Date.now() - 3600000).toISOString();
524
+ const { rows } = await this.pool.query('SELECT key, timestamp FROM rate_limit_hits WHERE timestamp > $1 ORDER BY timestamp ASC', [cutoff]);
525
+ for (const r of rows) {
526
+ const ts = new Date(r.timestamp).getTime();
527
+ const arr = this.windows.get(r.key) || [];
528
+ arr.push(ts);
529
+ this.windows.set(r.key, arr);
530
+ }
531
+ }
532
+ catch (err) {
533
+ console.error('[PostgresRateLimitStore] hydrate failed:', err.message);
534
+ }
535
+ }
536
+ // -- Interface implementation -------------------------------------------
537
+ hit(key, windowMs, maxRequests) {
538
+ const now = Date.now();
539
+ const cutoff = now - windowMs;
540
+ // Prune expired timestamps and add current hit (identical to InMemory)
541
+ let timestamps = this.windows.get(key) || [];
542
+ timestamps = timestamps.filter((t) => t > cutoff);
543
+ timestamps.push(now);
544
+ this.windows.set(key, timestamps);
545
+ const current = timestamps.length;
546
+ const allowed = current <= maxRequests;
547
+ const resetAt = timestamps.length > 0 ? timestamps[0] + windowMs : now + windowMs;
548
+ // Async: persist the hit to Postgres
549
+ asyncQuery(this.pool, 'INSERT INTO rate_limit_hits (key, timestamp) VALUES ($1, $2)', [key, new Date(now).toISOString()]);
550
+ // Async: prune old rows from Postgres periodically
551
+ if (current === 1) {
552
+ // First hit in this window -- good time to clean up old entries for this key
553
+ asyncQuery(this.pool, 'DELETE FROM rate_limit_hits WHERE key = $1 AND timestamp < $2', [key, new Date(cutoff).toISOString()]);
554
+ }
555
+ return { allowed, current, limit: maxRequests, resetAt };
556
+ }
557
+ reset() {
558
+ this.windows.clear();
559
+ asyncQuery(this.pool, 'TRUNCATE rate_limit_hits');
560
+ }
561
+ }
562
+ exports.PostgresRateLimitStore = PostgresRateLimitStore;
563
+ // ---------------------------------------------------------------------------
564
+ // 6. PostgresUserStore
565
+ // ---------------------------------------------------------------------------
566
+ class PostgresUserStore {
567
+ constructor(pool) {
568
+ this.pool = pool;
569
+ this.users = new Map();
570
+ this._pendingWrites = [];
571
+ }
572
+ static async createTables(pool) {
573
+ await pool.query(`
574
+ CREATE TABLE IF NOT EXISTS users (
575
+ id TEXT PRIMARY KEY,
576
+ email TEXT UNIQUE NOT NULL,
577
+ display_name TEXT NOT NULL,
578
+ avatar_url TEXT,
579
+ password_hash TEXT,
580
+ status TEXT NOT NULL DEFAULT 'active',
581
+ onboarding_completed BOOLEAN NOT NULL DEFAULT FALSE,
582
+ created_at TEXT NOT NULL,
583
+ updated_at TEXT NOT NULL
584
+ );
585
+ `);
586
+ // Migration: add password_hash for databases created before this column existed
587
+ await pool.query(`ALTER TABLE users ADD COLUMN IF NOT EXISTS password_hash TEXT`).catch(() => { });
588
+ }
589
+ async hydrate() {
590
+ try {
591
+ const { rows } = await this.pool.query('SELECT * FROM users');
592
+ for (const r of rows) {
593
+ this.users.set(r.id, {
594
+ id: r.id, email: r.email, display_name: r.display_name,
595
+ avatar_url: r.avatar_url || undefined,
596
+ password_hash: r.password_hash || undefined,
597
+ status: r.status,
598
+ onboarding_completed: r.onboarding_completed,
599
+ created_at: r.created_at, updated_at: r.updated_at,
600
+ });
601
+ }
602
+ }
603
+ catch (err) {
604
+ console.error('[PostgresUserStore] hydrate failed:', err.message);
605
+ }
606
+ }
607
+ create(user) {
608
+ this.users.set(user.id, { ...user });
609
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO users (id, email, display_name, avatar_url, password_hash, status, onboarding_completed, created_at, updated_at)
610
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
611
+ ON CONFLICT (id) DO NOTHING`, [user.id, user.email, user.display_name, user.avatar_url || null, user.password_hash || null,
612
+ user.status, user.onboarding_completed, user.created_at, user.updated_at]));
613
+ }
614
+ getById(id) {
615
+ const u = this.users.get(id);
616
+ return u ? { ...u } : undefined;
617
+ }
618
+ getByEmail(email) {
619
+ for (const u of this.users.values()) {
620
+ if (u.email === email)
621
+ return { ...u };
622
+ }
623
+ return undefined;
624
+ }
625
+ update(id, updates) {
626
+ const u = this.users.get(id);
627
+ if (!u)
628
+ return undefined;
629
+ const safeEntries = sanitizeUpdateEntries(updates, ALLOWED_USER_COLUMNS);
630
+ const safeUpdates = Object.fromEntries(safeEntries);
631
+ Object.assign(u, safeUpdates);
632
+ const setClauses = [];
633
+ const vals = [];
634
+ let idx = 1;
635
+ for (const [key, val] of safeEntries) {
636
+ setClauses.push(`${key} = $${idx}`);
637
+ vals.push(val);
638
+ idx++;
639
+ }
640
+ if (setClauses.length > 0) {
641
+ vals.push(id);
642
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `UPDATE users SET ${setClauses.join(', ')} WHERE id = $${idx}`, vals));
643
+ }
644
+ return { ...u };
645
+ }
646
+ delete(id) {
647
+ const existed = this.users.delete(id);
648
+ if (existed)
649
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, 'DELETE FROM users WHERE id = $1', [id]));
650
+ return existed;
651
+ }
652
+ async flush() {
653
+ const writes = this._pendingWrites;
654
+ this._pendingWrites = [];
655
+ await Promise.all(writes);
656
+ }
657
+ list() {
658
+ return Array.from(this.users.values()).map(u => ({ ...u }));
659
+ }
660
+ }
661
+ exports.PostgresUserStore = PostgresUserStore;
662
+ // ---------------------------------------------------------------------------
663
+ // 7. PostgresOAuthAccountStore
664
+ // ---------------------------------------------------------------------------
665
+ class PostgresOAuthAccountStore {
666
+ constructor(pool) {
667
+ this.pool = pool;
668
+ this.accounts = new Map();
669
+ this._pendingWrites = [];
670
+ }
671
+ static async createTables(pool) {
672
+ await pool.query(`
673
+ CREATE TABLE IF NOT EXISTS oauth_accounts (
674
+ id TEXT PRIMARY KEY,
675
+ user_id TEXT NOT NULL,
676
+ provider TEXT NOT NULL,
677
+ provider_user_id TEXT NOT NULL,
678
+ provider_email TEXT NOT NULL,
679
+ access_token_encrypted TEXT,
680
+ refresh_token_encrypted TEXT,
681
+ token_expires_at TEXT,
682
+ created_at TEXT NOT NULL,
683
+ updated_at TEXT NOT NULL,
684
+ UNIQUE(provider, provider_user_id)
685
+ );
686
+ `);
687
+ }
688
+ async hydrate() {
689
+ try {
690
+ const { rows } = await this.pool.query('SELECT * FROM oauth_accounts');
691
+ for (const r of rows) {
692
+ this.accounts.set(r.id, {
693
+ id: r.id, user_id: r.user_id, provider: r.provider,
694
+ provider_user_id: r.provider_user_id, provider_email: r.provider_email,
695
+ access_token_encrypted: r.access_token_encrypted || undefined,
696
+ refresh_token_encrypted: r.refresh_token_encrypted || undefined,
697
+ token_expires_at: r.token_expires_at || undefined,
698
+ created_at: r.created_at, updated_at: r.updated_at,
699
+ });
700
+ }
701
+ }
702
+ catch (err) {
703
+ console.error('[PostgresOAuthAccountStore] hydrate failed:', err.message);
704
+ }
705
+ }
706
+ create(account) {
707
+ this.accounts.set(account.id, { ...account });
708
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO oauth_accounts (id, user_id, provider, provider_user_id, provider_email, access_token_encrypted, refresh_token_encrypted, token_expires_at, created_at, updated_at)
709
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
710
+ ON CONFLICT (id) DO NOTHING`, [account.id, account.user_id, account.provider, account.provider_user_id, account.provider_email,
711
+ account.access_token_encrypted || null, account.refresh_token_encrypted || null,
712
+ account.token_expires_at || null, account.created_at, account.updated_at]));
713
+ }
714
+ getById(id) {
715
+ const a = this.accounts.get(id);
716
+ return a ? { ...a } : undefined;
717
+ }
718
+ getByProvider(provider, providerUserId) {
719
+ for (const a of this.accounts.values()) {
720
+ if (a.provider === provider && a.provider_user_id === providerUserId)
721
+ return { ...a };
722
+ }
723
+ return undefined;
724
+ }
725
+ getByUserId(userId) {
726
+ const results = [];
727
+ for (const a of this.accounts.values()) {
728
+ if (a.user_id === userId)
729
+ results.push({ ...a });
730
+ }
731
+ return results;
732
+ }
733
+ update(id, updates) {
734
+ const a = this.accounts.get(id);
735
+ if (!a)
736
+ return undefined;
737
+ const safeEntries = sanitizeUpdateEntries(updates, ALLOWED_OAUTH_ACCOUNT_COLUMNS);
738
+ const safeUpdates = Object.fromEntries(safeEntries);
739
+ Object.assign(a, safeUpdates);
740
+ const setClauses = [];
741
+ const vals = [];
742
+ let idx = 1;
743
+ for (const [key, val] of safeEntries) {
744
+ setClauses.push(`${key} = $${idx}`);
745
+ vals.push(val);
746
+ idx++;
747
+ }
748
+ if (setClauses.length > 0) {
749
+ vals.push(id);
750
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `UPDATE oauth_accounts SET ${setClauses.join(', ')} WHERE id = $${idx}`, vals));
751
+ }
752
+ return { ...a };
753
+ }
754
+ delete(id) {
755
+ const existed = this.accounts.delete(id);
756
+ if (existed)
757
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, 'DELETE FROM oauth_accounts WHERE id = $1', [id]));
758
+ return existed;
759
+ }
760
+ async flush() {
761
+ const writes = this._pendingWrites;
762
+ this._pendingWrites = [];
763
+ await Promise.all(writes);
764
+ }
765
+ }
766
+ exports.PostgresOAuthAccountStore = PostgresOAuthAccountStore;
767
+ // ---------------------------------------------------------------------------
768
+ // 8. PostgresWorkspaceStore
769
+ // ---------------------------------------------------------------------------
770
+ class PostgresWorkspaceStore {
771
+ constructor(pool) {
772
+ this.pool = pool;
773
+ this.workspaces = new Map();
774
+ this._pendingWrites = [];
775
+ }
776
+ static async createTables(pool) {
777
+ await pool.query(`
778
+ CREATE TABLE IF NOT EXISTS workspaces (
779
+ id TEXT PRIMARY KEY,
780
+ name TEXT NOT NULL,
781
+ slug TEXT UNIQUE NOT NULL,
782
+ owner_user_id TEXT NOT NULL,
783
+ plan TEXT NOT NULL DEFAULT 'free',
784
+ settings JSONB NOT NULL DEFAULT '{}',
785
+ created_at TEXT NOT NULL,
786
+ updated_at TEXT NOT NULL
787
+ );
788
+ `);
789
+ }
790
+ async hydrate() {
791
+ try {
792
+ const { rows } = await this.pool.query('SELECT * FROM workspaces');
793
+ for (const r of rows) {
794
+ this.workspaces.set(r.id, {
795
+ id: r.id, name: r.name, slug: r.slug, owner_user_id: r.owner_user_id,
796
+ plan: r.plan, settings: r.settings || {},
797
+ created_at: r.created_at, updated_at: r.updated_at,
798
+ });
799
+ }
800
+ }
801
+ catch (err) {
802
+ console.error('[PostgresWorkspaceStore] hydrate failed:', err.message);
803
+ }
804
+ }
805
+ create(workspace) {
806
+ this.workspaces.set(workspace.id, { ...workspace });
807
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO workspaces (id, name, slug, owner_user_id, plan, settings, created_at, updated_at)
808
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
809
+ ON CONFLICT (id) DO NOTHING`, [workspace.id, workspace.name, workspace.slug, workspace.owner_user_id,
810
+ workspace.plan, JSON.stringify(workspace.settings), workspace.created_at, workspace.updated_at]));
811
+ }
812
+ getById(id) {
813
+ const w = this.workspaces.get(id);
814
+ return w ? { ...w } : undefined;
815
+ }
816
+ getBySlug(slug) {
817
+ for (const w of this.workspaces.values()) {
818
+ if (w.slug === slug)
819
+ return { ...w };
820
+ }
821
+ return undefined;
822
+ }
823
+ getByOwner(userId) {
824
+ const results = [];
825
+ for (const w of this.workspaces.values()) {
826
+ if (w.owner_user_id === userId)
827
+ results.push({ ...w });
828
+ }
829
+ return results;
830
+ }
831
+ update(id, updates) {
832
+ const w = this.workspaces.get(id);
833
+ if (!w)
834
+ return undefined;
835
+ const safeEntries = sanitizeUpdateEntries(updates, ALLOWED_WORKSPACE_COLUMNS);
836
+ const safeUpdates = Object.fromEntries(safeEntries);
837
+ Object.assign(w, safeUpdates);
838
+ const setClauses = [];
839
+ const vals = [];
840
+ let idx = 1;
841
+ for (const [key, val] of safeEntries) {
842
+ setClauses.push(`${key} = $${idx}`);
843
+ vals.push(key === 'settings' ? JSON.stringify(val) : val);
844
+ idx++;
845
+ }
846
+ if (setClauses.length > 0) {
847
+ vals.push(id);
848
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `UPDATE workspaces SET ${setClauses.join(', ')} WHERE id = $${idx}`, vals));
849
+ }
850
+ return { ...w };
851
+ }
852
+ delete(id) {
853
+ const existed = this.workspaces.delete(id);
854
+ if (existed)
855
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, 'DELETE FROM workspaces WHERE id = $1', [id]));
856
+ return existed;
857
+ }
858
+ async flush() {
859
+ const writes = this._pendingWrites;
860
+ this._pendingWrites = [];
861
+ await Promise.all(writes);
862
+ }
863
+ list() {
864
+ return Array.from(this.workspaces.values()).map(w => ({ ...w }));
865
+ }
866
+ }
867
+ exports.PostgresWorkspaceStore = PostgresWorkspaceStore;
868
+ // ---------------------------------------------------------------------------
869
+ // 9. PostgresWorkspaceMemberStore
870
+ // ---------------------------------------------------------------------------
871
+ class PostgresWorkspaceMemberStore {
872
+ constructor(pool) {
873
+ this.pool = pool;
874
+ this.members = new Map();
875
+ this._pendingWrites = [];
876
+ }
877
+ static async createTables(pool) {
878
+ await pool.query(`
879
+ CREATE TABLE IF NOT EXISTS workspace_members (
880
+ id TEXT PRIMARY KEY,
881
+ workspace_id TEXT NOT NULL,
882
+ user_id TEXT NOT NULL,
883
+ role TEXT NOT NULL DEFAULT 'member',
884
+ joined_at TEXT NOT NULL,
885
+ UNIQUE(workspace_id, user_id)
886
+ );
887
+ `);
888
+ }
889
+ async hydrate() {
890
+ try {
891
+ const { rows } = await this.pool.query('SELECT * FROM workspace_members');
892
+ for (const r of rows) {
893
+ this.members.set(r.id, {
894
+ id: r.id, workspace_id: r.workspace_id, user_id: r.user_id,
895
+ role: r.role, joined_at: r.joined_at,
896
+ });
897
+ }
898
+ }
899
+ catch (err) {
900
+ console.error('[PostgresWorkspaceMemberStore] hydrate failed:', err.message);
901
+ }
902
+ }
903
+ create(member) {
904
+ this.members.set(member.id, { ...member });
905
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO workspace_members (id, workspace_id, user_id, role, joined_at)
906
+ VALUES ($1, $2, $3, $4, $5)
907
+ ON CONFLICT (id) DO NOTHING`, [member.id, member.workspace_id, member.user_id, member.role, member.joined_at]));
908
+ }
909
+ getById(id) {
910
+ const m = this.members.get(id);
911
+ return m ? { ...m } : undefined;
912
+ }
913
+ getByWorkspace(workspaceId) {
914
+ const results = [];
915
+ for (const m of this.members.values()) {
916
+ if (m.workspace_id === workspaceId)
917
+ results.push({ ...m });
918
+ }
919
+ return results;
920
+ }
921
+ getByUser(userId) {
922
+ const results = [];
923
+ for (const m of this.members.values()) {
924
+ if (m.user_id === userId)
925
+ results.push({ ...m });
926
+ }
927
+ return results;
928
+ }
929
+ getByWorkspaceAndUser(workspaceId, userId) {
930
+ for (const m of this.members.values()) {
931
+ if (m.workspace_id === workspaceId && m.user_id === userId)
932
+ return { ...m };
933
+ }
934
+ return undefined;
935
+ }
936
+ update(id, updates) {
937
+ const m = this.members.get(id);
938
+ if (!m)
939
+ return undefined;
940
+ Object.assign(m, updates);
941
+ if (updates.role) {
942
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, 'UPDATE workspace_members SET role = $1 WHERE id = $2', [updates.role, id]));
943
+ }
944
+ return { ...m };
945
+ }
946
+ delete(id) {
947
+ const existed = this.members.delete(id);
948
+ if (existed)
949
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, 'DELETE FROM workspace_members WHERE id = $1', [id]));
950
+ return existed;
951
+ }
952
+ async flush() {
953
+ const writes = this._pendingWrites;
954
+ this._pendingWrites = [];
955
+ await Promise.all(writes);
956
+ }
957
+ }
958
+ exports.PostgresWorkspaceMemberStore = PostgresWorkspaceMemberStore;
959
+ // ---------------------------------------------------------------------------
960
+ // 10. PostgresSessionStore
961
+ // ---------------------------------------------------------------------------
962
+ class PostgresSessionStore {
963
+ constructor(pool) {
964
+ this.pool = pool;
965
+ this.sessions = new Map();
966
+ this._pendingWrites = [];
967
+ }
968
+ static async createTables(pool) {
969
+ await pool.query(`
970
+ CREATE TABLE IF NOT EXISTS sessions (
971
+ id TEXT PRIMARY KEY,
972
+ user_id TEXT NOT NULL,
973
+ workspace_id TEXT,
974
+ ip_address TEXT,
975
+ user_agent TEXT,
976
+ expires_at TEXT NOT NULL,
977
+ last_active_at TEXT NOT NULL,
978
+ created_at TEXT NOT NULL
979
+ );
980
+ `);
981
+ await pool.query(`
982
+ CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions (user_id);
983
+ `);
984
+ }
985
+ async hydrate() {
986
+ try {
987
+ const { rows } = await this.pool.query('SELECT * FROM sessions');
988
+ const now = Date.now();
989
+ for (const r of rows) {
990
+ if (new Date(r.expires_at).getTime() <= now)
991
+ continue;
992
+ this.sessions.set(r.id, {
993
+ id: r.id, user_id: r.user_id, workspace_id: r.workspace_id || undefined,
994
+ ip_address: r.ip_address || undefined, user_agent: r.user_agent || undefined,
995
+ expires_at: r.expires_at, last_active_at: r.last_active_at, created_at: r.created_at,
996
+ });
997
+ }
998
+ }
999
+ catch (err) {
1000
+ console.error('[PostgresSessionStore] hydrate failed:', err.message);
1001
+ }
1002
+ }
1003
+ create(session) {
1004
+ this.sessions.set(session.id, { ...session });
1005
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO sessions (id, user_id, workspace_id, ip_address, user_agent, expires_at, last_active_at, created_at)
1006
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
1007
+ ON CONFLICT (id) DO NOTHING`, [session.id, session.user_id, session.workspace_id || null, session.ip_address || null,
1008
+ session.user_agent || null, session.expires_at, session.last_active_at, session.created_at]));
1009
+ }
1010
+ getById(id) {
1011
+ const s = this.sessions.get(id);
1012
+ if (!s)
1013
+ return undefined;
1014
+ if (new Date(s.expires_at).getTime() <= Date.now()) {
1015
+ this.sessions.delete(id);
1016
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, 'DELETE FROM sessions WHERE id = $1', [id]));
1017
+ return undefined;
1018
+ }
1019
+ return { ...s };
1020
+ }
1021
+ getByUserId(userId) {
1022
+ const now = Date.now();
1023
+ const results = [];
1024
+ for (const s of this.sessions.values()) {
1025
+ if (s.user_id === userId && new Date(s.expires_at).getTime() > now) {
1026
+ results.push({ ...s });
1027
+ }
1028
+ }
1029
+ return results;
1030
+ }
1031
+ update(id, updates) {
1032
+ const s = this.sessions.get(id);
1033
+ if (!s)
1034
+ return undefined;
1035
+ const safeEntries = sanitizeUpdateEntries(updates, ALLOWED_SESSION_COLUMNS);
1036
+ const safeUpdates = Object.fromEntries(safeEntries);
1037
+ Object.assign(s, safeUpdates);
1038
+ const setClauses = [];
1039
+ const vals = [];
1040
+ let idx = 1;
1041
+ for (const [key, val] of safeEntries) {
1042
+ setClauses.push(`${key} = $${idx}`);
1043
+ vals.push(val);
1044
+ idx++;
1045
+ }
1046
+ if (setClauses.length > 0) {
1047
+ vals.push(id);
1048
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `UPDATE sessions SET ${setClauses.join(', ')} WHERE id = $${idx}`, vals));
1049
+ }
1050
+ return { ...s };
1051
+ }
1052
+ delete(id) {
1053
+ const existed = this.sessions.delete(id);
1054
+ if (existed)
1055
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, 'DELETE FROM sessions WHERE id = $1', [id]));
1056
+ return existed;
1057
+ }
1058
+ deleteExpired() {
1059
+ const now = Date.now();
1060
+ let count = 0;
1061
+ for (const [id, s] of this.sessions) {
1062
+ if (new Date(s.expires_at).getTime() <= now) {
1063
+ this.sessions.delete(id);
1064
+ count++;
1065
+ }
1066
+ }
1067
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `DELETE FROM sessions WHERE expires_at < $1`, [new Date().toISOString()]));
1068
+ return count;
1069
+ }
1070
+ deleteByUserId(userId) {
1071
+ let count = 0;
1072
+ for (const [id, s] of this.sessions) {
1073
+ if (s.user_id === userId) {
1074
+ this.sessions.delete(id);
1075
+ count++;
1076
+ }
1077
+ }
1078
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, 'DELETE FROM sessions WHERE user_id = $1', [userId]));
1079
+ return count;
1080
+ }
1081
+ async flush() {
1082
+ const writes = this._pendingWrites;
1083
+ this._pendingWrites = [];
1084
+ await Promise.all(writes);
1085
+ }
1086
+ }
1087
+ exports.PostgresSessionStore = PostgresSessionStore;
1088
+ // ---------------------------------------------------------------------------
1089
+ // 11. PostgresUserApiKeyStore
1090
+ // ---------------------------------------------------------------------------
1091
+ class PostgresUserApiKeyStore {
1092
+ constructor(pool) {
1093
+ this.pool = pool;
1094
+ this.keys = new Map();
1095
+ this._pendingWrites = [];
1096
+ }
1097
+ static async createTables(pool) {
1098
+ await pool.query(`
1099
+ CREATE TABLE IF NOT EXISTS user_api_keys (
1100
+ id TEXT PRIMARY KEY,
1101
+ key_hash TEXT UNIQUE NOT NULL,
1102
+ key_prefix TEXT NOT NULL,
1103
+ user_id TEXT NOT NULL,
1104
+ workspace_id TEXT NOT NULL,
1105
+ name TEXT NOT NULL,
1106
+ roles JSONB NOT NULL DEFAULT '[]',
1107
+ tags JSONB NOT NULL DEFAULT '[]',
1108
+ revoked BOOLEAN NOT NULL DEFAULT FALSE,
1109
+ last_used_at TEXT,
1110
+ created_at TEXT NOT NULL
1111
+ );
1112
+ `);
1113
+ await pool.query(`
1114
+ CREATE INDEX IF NOT EXISTS idx_user_api_keys_workspace ON user_api_keys (workspace_id);
1115
+ `);
1116
+ await pool.query(`
1117
+ CREATE INDEX IF NOT EXISTS idx_user_api_keys_hash ON user_api_keys (key_hash);
1118
+ `);
1119
+ // Migration: add tags column for existing tables
1120
+ await pool.query(`
1121
+ ALTER TABLE user_api_keys ADD COLUMN IF NOT EXISTS tags JSONB NOT NULL DEFAULT '[]';
1122
+ `);
1123
+ }
1124
+ async hydrate() {
1125
+ try {
1126
+ const { rows } = await this.pool.query('SELECT * FROM user_api_keys');
1127
+ for (const r of rows) {
1128
+ this.keys.set(r.id, {
1129
+ id: r.id, key_hash: r.key_hash, key_prefix: r.key_prefix,
1130
+ user_id: r.user_id, workspace_id: r.workspace_id, name: r.name,
1131
+ roles: r.roles || [], tags: r.tags || [], revoked: r.revoked,
1132
+ last_used_at: r.last_used_at || undefined, created_at: r.created_at,
1133
+ });
1134
+ }
1135
+ }
1136
+ catch (err) {
1137
+ console.error('[PostgresUserApiKeyStore] hydrate failed:', err.message);
1138
+ }
1139
+ }
1140
+ create(apiKey) {
1141
+ this.keys.set(apiKey.id, { ...apiKey });
1142
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO user_api_keys (id, key_hash, key_prefix, user_id, workspace_id, name, roles, tags, revoked, last_used_at, created_at)
1143
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
1144
+ ON CONFLICT (id) DO NOTHING`, [apiKey.id, apiKey.key_hash, apiKey.key_prefix, apiKey.user_id, apiKey.workspace_id,
1145
+ apiKey.name, JSON.stringify(apiKey.roles), JSON.stringify(apiKey.tags || []),
1146
+ apiKey.revoked, apiKey.last_used_at || null, apiKey.created_at]));
1147
+ }
1148
+ getById(id) {
1149
+ const k = this.keys.get(id);
1150
+ return k ? { ...k } : undefined;
1151
+ }
1152
+ getByKeyHash(keyHash) {
1153
+ for (const k of this.keys.values()) {
1154
+ if (k.key_hash === keyHash)
1155
+ return { ...k };
1156
+ }
1157
+ return undefined;
1158
+ }
1159
+ verifyToken(token) {
1160
+ const crypto = require('crypto');
1161
+ const unsaltedHash = crypto.createHash('sha256').update(token).digest('hex');
1162
+ for (const k of this.keys.values()) {
1163
+ if (k.key_hash.includes(':')) {
1164
+ const colonIndex = k.key_hash.indexOf(':');
1165
+ const salt = k.key_hash.slice(0, colonIndex);
1166
+ const expectedHash = k.key_hash.slice(colonIndex + 1);
1167
+ const computedHash = crypto.createHash('sha256').update(salt + token).digest('hex');
1168
+ const a = Buffer.from(computedHash, 'hex');
1169
+ const b = Buffer.from(expectedHash, 'hex');
1170
+ if (a.length === b.length && crypto.timingSafeEqual(a, b)) {
1171
+ return { ...k };
1172
+ }
1173
+ }
1174
+ else {
1175
+ if (k.key_hash === unsaltedHash)
1176
+ return { ...k };
1177
+ }
1178
+ }
1179
+ return undefined;
1180
+ }
1181
+ getByWorkspace(workspaceId) {
1182
+ const results = [];
1183
+ for (const k of this.keys.values()) {
1184
+ if (k.workspace_id === workspaceId)
1185
+ results.push({ ...k });
1186
+ }
1187
+ return results;
1188
+ }
1189
+ getByUser(userId) {
1190
+ const results = [];
1191
+ for (const k of this.keys.values()) {
1192
+ if (k.user_id === userId)
1193
+ results.push({ ...k });
1194
+ }
1195
+ return results;
1196
+ }
1197
+ update(id, updates) {
1198
+ const k = this.keys.get(id);
1199
+ if (!k)
1200
+ return undefined;
1201
+ const safeEntries = sanitizeUpdateEntries(updates, ALLOWED_USER_API_KEY_COLUMNS);
1202
+ const safeUpdates = Object.fromEntries(safeEntries);
1203
+ Object.assign(k, safeUpdates);
1204
+ const setClauses = [];
1205
+ const vals = [];
1206
+ let idx = 1;
1207
+ for (const [key, val] of safeEntries) {
1208
+ setClauses.push(`${key} = $${idx}`);
1209
+ vals.push(key === 'roles' || key === 'tags' ? JSON.stringify(val) : val);
1210
+ idx++;
1211
+ }
1212
+ if (setClauses.length > 0) {
1213
+ vals.push(id);
1214
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `UPDATE user_api_keys SET ${setClauses.join(', ')} WHERE id = $${idx}`, vals));
1215
+ }
1216
+ return { ...k };
1217
+ }
1218
+ delete(id) {
1219
+ const existed = this.keys.delete(id);
1220
+ if (existed)
1221
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, 'DELETE FROM user_api_keys WHERE id = $1', [id]));
1222
+ return existed;
1223
+ }
1224
+ async flush() {
1225
+ const writes = this._pendingWrites;
1226
+ this._pendingWrites = [];
1227
+ await Promise.all(writes);
1228
+ }
1229
+ }
1230
+ exports.PostgresUserApiKeyStore = PostgresUserApiKeyStore;
1231
+ // ---------------------------------------------------------------------------
1232
+ // 12. PostgresSubscriptionStore
1233
+ // ---------------------------------------------------------------------------
1234
+ class PostgresSubscriptionStore {
1235
+ constructor(pool) {
1236
+ this.pool = pool;
1237
+ this.subscriptions = new Map();
1238
+ this._pendingWrites = [];
1239
+ }
1240
+ static async createTables(pool) {
1241
+ await pool.query(`
1242
+ CREATE TABLE IF NOT EXISTS subscriptions (
1243
+ id TEXT PRIMARY KEY,
1244
+ workspace_id TEXT NOT NULL,
1245
+ stripe_customer_id TEXT NOT NULL,
1246
+ stripe_subscription_id TEXT,
1247
+ plan TEXT NOT NULL,
1248
+ status TEXT NOT NULL DEFAULT 'active',
1249
+ current_period_start TEXT,
1250
+ current_period_end TEXT,
1251
+ cancel_at_period_end BOOLEAN NOT NULL DEFAULT FALSE,
1252
+ created_at TEXT NOT NULL,
1253
+ updated_at TEXT NOT NULL
1254
+ );
1255
+ `);
1256
+ await pool.query(`
1257
+ CREATE INDEX IF NOT EXISTS idx_subscriptions_workspace ON subscriptions (workspace_id);
1258
+ `);
1259
+ await pool.query(`
1260
+ CREATE INDEX IF NOT EXISTS idx_subscriptions_stripe_customer ON subscriptions (stripe_customer_id);
1261
+ `);
1262
+ await pool.query(`
1263
+ CREATE INDEX IF NOT EXISTS idx_subscriptions_stripe_subscription ON subscriptions (stripe_subscription_id);
1264
+ `);
1265
+ }
1266
+ async hydrate() {
1267
+ try {
1268
+ const { rows } = await this.pool.query('SELECT * FROM subscriptions');
1269
+ for (const r of rows) {
1270
+ this.subscriptions.set(r.id, {
1271
+ id: r.id, workspace_id: r.workspace_id,
1272
+ stripe_customer_id: r.stripe_customer_id,
1273
+ stripe_subscription_id: r.stripe_subscription_id || undefined,
1274
+ plan: r.plan, status: r.status,
1275
+ current_period_start: r.current_period_start || undefined,
1276
+ current_period_end: r.current_period_end || undefined,
1277
+ cancel_at_period_end: r.cancel_at_period_end ?? undefined,
1278
+ created_at: r.created_at, updated_at: r.updated_at,
1279
+ });
1280
+ }
1281
+ }
1282
+ catch (err) {
1283
+ console.error('[PostgresSubscriptionStore] hydrate failed:', err.message);
1284
+ }
1285
+ }
1286
+ create(subscription) {
1287
+ this.subscriptions.set(subscription.id, { ...subscription });
1288
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `INSERT INTO subscriptions (id, workspace_id, stripe_customer_id, stripe_subscription_id, plan, status, current_period_start, current_period_end, cancel_at_period_end, created_at, updated_at)
1289
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
1290
+ ON CONFLICT (id) DO NOTHING`, [subscription.id, subscription.workspace_id, subscription.stripe_customer_id,
1291
+ subscription.stripe_subscription_id || null, subscription.plan, subscription.status,
1292
+ subscription.current_period_start || null, subscription.current_period_end || null,
1293
+ subscription.cancel_at_period_end ?? false, subscription.created_at, subscription.updated_at]));
1294
+ }
1295
+ getById(id) {
1296
+ const s = this.subscriptions.get(id);
1297
+ return s ? { ...s } : undefined;
1298
+ }
1299
+ getByWorkspace(workspaceId) {
1300
+ for (const s of this.subscriptions.values()) {
1301
+ if (s.workspace_id === workspaceId)
1302
+ return { ...s };
1303
+ }
1304
+ return undefined;
1305
+ }
1306
+ getByStripeCustomerId(customerId) {
1307
+ for (const s of this.subscriptions.values()) {
1308
+ if (s.stripe_customer_id === customerId)
1309
+ return { ...s };
1310
+ }
1311
+ return undefined;
1312
+ }
1313
+ getByStripeSubscriptionId(subscriptionId) {
1314
+ for (const s of this.subscriptions.values()) {
1315
+ if (s.stripe_subscription_id === subscriptionId)
1316
+ return { ...s };
1317
+ }
1318
+ return undefined;
1319
+ }
1320
+ update(id, updates) {
1321
+ const s = this.subscriptions.get(id);
1322
+ if (!s)
1323
+ return undefined;
1324
+ const safeEntries = sanitizeUpdateEntries(updates, ALLOWED_SUBSCRIPTION_COLUMNS);
1325
+ const safeUpdates = Object.fromEntries(safeEntries);
1326
+ Object.assign(s, safeUpdates);
1327
+ const setClauses = [];
1328
+ const vals = [];
1329
+ let idx = 1;
1330
+ for (const [key, val] of safeEntries) {
1331
+ setClauses.push(`${key} = $${idx}`);
1332
+ vals.push(val);
1333
+ idx++;
1334
+ }
1335
+ if (setClauses.length > 0) {
1336
+ vals.push(id);
1337
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, `UPDATE subscriptions SET ${setClauses.join(', ')} WHERE id = $${idx}`, vals));
1338
+ }
1339
+ return { ...s };
1340
+ }
1341
+ delete(id) {
1342
+ const existed = this.subscriptions.delete(id);
1343
+ if (existed)
1344
+ this._pendingWrites.push(reliableAsyncQuery(this.pool, 'DELETE FROM subscriptions WHERE id = $1', [id]));
1345
+ return existed;
1346
+ }
1347
+ list() {
1348
+ return Array.from(this.subscriptions.values()).map(s => ({ ...s }));
1349
+ }
1350
+ async flush() {
1351
+ const writes = this._pendingWrites;
1352
+ this._pendingWrites = [];
1353
+ await Promise.all(writes);
1354
+ }
1355
+ }
1356
+ exports.PostgresSubscriptionStore = PostgresSubscriptionStore;
1357
+ // ---------------------------------------------------------------------------
1358
+ // Per-Workspace Rate Limit & Budget Config Stores
1359
+ // ---------------------------------------------------------------------------
1360
+ class PostgresRateLimitConfigStore {
1361
+ constructor(pool) {
1362
+ this.pool = pool;
1363
+ this.configs = new Map();
1364
+ }
1365
+ static async createTables(pool) {
1366
+ await pool.query(`
1367
+ CREATE TABLE IF NOT EXISTS workspace_rate_limit_configs (
1368
+ workspace_id TEXT PRIMARY KEY,
1369
+ config JSONB NOT NULL
1370
+ );
1371
+ `);
1372
+ }
1373
+ async hydrate() {
1374
+ try {
1375
+ const { rows } = await this.pool.query('SELECT * FROM workspace_rate_limit_configs');
1376
+ for (const r of rows) {
1377
+ this.configs.set(r.workspace_id, r.config);
1378
+ }
1379
+ }
1380
+ catch (err) {
1381
+ console.error('[PostgresRateLimitConfigStore] hydrate failed:', err.message);
1382
+ }
1383
+ }
1384
+ getByWorkspaceId(workspaceId) {
1385
+ return this.configs.get(workspaceId);
1386
+ }
1387
+ set(workspaceId, config) {
1388
+ this.configs.set(workspaceId, config);
1389
+ asyncQuery(this.pool, `INSERT INTO workspace_rate_limit_configs (workspace_id, config) VALUES ($1, $2)
1390
+ ON CONFLICT (workspace_id) DO UPDATE SET config = EXCLUDED.config`, [workspaceId, JSON.stringify(config)]);
1391
+ }
1392
+ delete(workspaceId) {
1393
+ const existed = this.configs.delete(workspaceId);
1394
+ asyncQuery(this.pool, `DELETE FROM workspace_rate_limit_configs WHERE workspace_id = $1`, [workspaceId]);
1395
+ return existed;
1396
+ }
1397
+ list() {
1398
+ return new Map(this.configs);
1399
+ }
1400
+ clear() {
1401
+ this.configs.clear();
1402
+ asyncQuery(this.pool, `DELETE FROM workspace_rate_limit_configs`);
1403
+ }
1404
+ }
1405
+ exports.PostgresRateLimitConfigStore = PostgresRateLimitConfigStore;
1406
+ class PostgresBudgetConfigStore {
1407
+ constructor(pool) {
1408
+ this.pool = pool;
1409
+ this.configs = new Map();
1410
+ }
1411
+ static async createTables(pool) {
1412
+ await pool.query(`
1413
+ CREATE TABLE IF NOT EXISTS workspace_budget_configs (
1414
+ workspace_id TEXT PRIMARY KEY,
1415
+ config JSONB NOT NULL
1416
+ );
1417
+ `);
1418
+ }
1419
+ async hydrate() {
1420
+ try {
1421
+ const { rows } = await this.pool.query('SELECT * FROM workspace_budget_configs');
1422
+ for (const r of rows) {
1423
+ this.configs.set(r.workspace_id, r.config);
1424
+ }
1425
+ }
1426
+ catch (err) {
1427
+ console.error('[PostgresBudgetConfigStore] hydrate failed:', err.message);
1428
+ }
1429
+ }
1430
+ getByWorkspaceId(workspaceId) {
1431
+ return this.configs.get(workspaceId);
1432
+ }
1433
+ set(workspaceId, config) {
1434
+ this.configs.set(workspaceId, config);
1435
+ asyncQuery(this.pool, `INSERT INTO workspace_budget_configs (workspace_id, config) VALUES ($1, $2)
1436
+ ON CONFLICT (workspace_id) DO UPDATE SET config = EXCLUDED.config`, [workspaceId, JSON.stringify(config)]);
1437
+ }
1438
+ delete(workspaceId) {
1439
+ const existed = this.configs.delete(workspaceId);
1440
+ asyncQuery(this.pool, `DELETE FROM workspace_budget_configs WHERE workspace_id = $1`, [workspaceId]);
1441
+ return existed;
1442
+ }
1443
+ list() {
1444
+ return new Map(this.configs);
1445
+ }
1446
+ clear() {
1447
+ this.configs.clear();
1448
+ asyncQuery(this.pool, `DELETE FROM workspace_budget_configs`);
1449
+ }
1450
+ }
1451
+ exports.PostgresBudgetConfigStore = PostgresBudgetConfigStore;
1452
+ class PostgresPolicyStore {
1453
+ constructor(pool) {
1454
+ this.pool = pool;
1455
+ this.policies = new Map();
1456
+ }
1457
+ static async createTables(pool) {
1458
+ await pool.query(`
1459
+ CREATE TABLE IF NOT EXISTS workspace_policies (
1460
+ workspace_id TEXT PRIMARY KEY,
1461
+ policy JSONB NOT NULL
1462
+ );
1463
+ `);
1464
+ }
1465
+ async hydrate() {
1466
+ try {
1467
+ const { rows } = await this.pool.query('SELECT * FROM workspace_policies');
1468
+ for (const r of rows) {
1469
+ this.policies.set(r.workspace_id, r.policy);
1470
+ }
1471
+ }
1472
+ catch (err) {
1473
+ console.error('[PostgresPolicyStore] hydrate failed:', err.message);
1474
+ }
1475
+ }
1476
+ getByWorkspaceId(workspaceId) {
1477
+ return this.policies.get(workspaceId);
1478
+ }
1479
+ set(workspaceId, policy) {
1480
+ this.policies.set(workspaceId, policy);
1481
+ asyncQuery(this.pool, `INSERT INTO workspace_policies (workspace_id, policy) VALUES ($1, $2)
1482
+ ON CONFLICT (workspace_id) DO UPDATE SET policy = EXCLUDED.policy`, [workspaceId, JSON.stringify(policy)]);
1483
+ }
1484
+ delete(workspaceId) {
1485
+ const existed = this.policies.delete(workspaceId);
1486
+ asyncQuery(this.pool, `DELETE FROM workspace_policies WHERE workspace_id = $1`, [workspaceId]);
1487
+ return existed;
1488
+ }
1489
+ list() {
1490
+ return new Map(this.policies);
1491
+ }
1492
+ clear() {
1493
+ this.policies.clear();
1494
+ asyncQuery(this.pool, `DELETE FROM workspace_policies`);
1495
+ }
1496
+ }
1497
+ exports.PostgresPolicyStore = PostgresPolicyStore;
1498
+ // ---------------------------------------------------------------------------
1499
+ // Top-level initialisation
1500
+ // ---------------------------------------------------------------------------
1501
+ /**
1502
+ * Creates all Postgres tables required by the storage backends.
1503
+ * Call once at startup before using any Postgres store.
1504
+ */
1505
+ async function initPostgresStorage(pool) {
1506
+ await PostgresBudgetStore.createTables(pool);
1507
+ await PostgresAuditStore.createTables(pool);
1508
+ await PostgresApprovalStore.createTables(pool);
1509
+ await PostgresIdempotencyStore.createTables(pool);
1510
+ await PostgresRateLimitStore.createTables(pool);
1511
+ // SaaS tables
1512
+ await PostgresUserStore.createTables(pool);
1513
+ await PostgresOAuthAccountStore.createTables(pool);
1514
+ await PostgresWorkspaceStore.createTables(pool);
1515
+ await PostgresWorkspaceMemberStore.createTables(pool);
1516
+ await PostgresSessionStore.createTables(pool);
1517
+ await PostgresUserApiKeyStore.createTables(pool);
1518
+ await PostgresSubscriptionStore.createTables(pool);
1519
+ // Per-workspace config tables
1520
+ await PostgresRateLimitConfigStore.createTables(pool);
1521
+ await PostgresBudgetConfigStore.createTables(pool);
1522
+ await PostgresPolicyStore.createTables(pool);
1523
+ }
1524
+ /**
1525
+ * Creates all Postgres store instances.
1526
+ * Call initPostgresStorage() first to ensure tables exist.
1527
+ */
1528
+ function createPostgresStores(pool) {
1529
+ return {
1530
+ budgetStore: new PostgresBudgetStore(pool),
1531
+ auditStore: new PostgresAuditStore(pool),
1532
+ approvalStore: new PostgresApprovalStore(pool),
1533
+ idempotencyStore: new PostgresIdempotencyStore(pool),
1534
+ rateLimitStore: new PostgresRateLimitStore(pool),
1535
+ };
1536
+ }
1537
+ /**
1538
+ * Creates SaaS-specific Postgres store instances.
1539
+ * Call initPostgresStorage() first to ensure tables exist.
1540
+ */
1541
+ function createPostgresSaaSStores(pool) {
1542
+ return {
1543
+ userStore: new PostgresUserStore(pool),
1544
+ oauthAccountStore: new PostgresOAuthAccountStore(pool),
1545
+ workspaceStore: new PostgresWorkspaceStore(pool),
1546
+ workspaceMemberStore: new PostgresWorkspaceMemberStore(pool),
1547
+ sessionStore: new PostgresSessionStore(pool),
1548
+ userApiKeyStore: new PostgresUserApiKeyStore(pool),
1549
+ subscriptionStore: new PostgresSubscriptionStore(pool),
1550
+ rateLimitConfigStore: new PostgresRateLimitConfigStore(pool),
1551
+ budgetConfigStore: new PostgresBudgetConfigStore(pool),
1552
+ policyStore: new PostgresPolicyStore(pool),
1553
+ };
1554
+ }
1555
+ //# sourceMappingURL=postgres.js.map