@digimuza/saas-core 0.0.2

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 (474) hide show
  1. package/CLAUDE.md +1165 -0
  2. package/LICENSE +21 -0
  3. package/README.md +234 -0
  4. package/dist/api-keys/index.d.ts +9 -0
  5. package/dist/api-keys/index.d.ts.map +1 -0
  6. package/dist/api-keys/index.js +37 -0
  7. package/dist/api-keys/index.js.map +1 -0
  8. package/dist/api-keys/keys.d.ts +55 -0
  9. package/dist/api-keys/keys.d.ts.map +1 -0
  10. package/dist/api-keys/keys.js +92 -0
  11. package/dist/api-keys/keys.js.map +1 -0
  12. package/dist/api-keys/service.d.ts +111 -0
  13. package/dist/api-keys/service.d.ts.map +1 -0
  14. package/dist/api-keys/service.js +210 -0
  15. package/dist/api-keys/service.js.map +1 -0
  16. package/dist/audit/index.d.ts +5 -0
  17. package/dist/audit/index.d.ts.map +1 -0
  18. package/dist/audit/index.js +44 -0
  19. package/dist/audit/index.js.map +1 -0
  20. package/dist/audit/service.d.ts +88 -0
  21. package/dist/audit/service.d.ts.map +1 -0
  22. package/dist/audit/service.js +128 -0
  23. package/dist/audit/service.js.map +1 -0
  24. package/dist/auth/client.d.ts +5 -0
  25. package/dist/auth/client.d.ts.map +1 -0
  26. package/dist/auth/client.js +26 -0
  27. package/dist/auth/client.js.map +1 -0
  28. package/dist/auth/cookies.d.ts +51 -0
  29. package/dist/auth/cookies.d.ts.map +1 -0
  30. package/dist/auth/cookies.js +58 -0
  31. package/dist/auth/cookies.js.map +1 -0
  32. package/dist/auth/index.d.ts +14 -0
  33. package/dist/auth/index.d.ts.map +1 -0
  34. package/dist/auth/index.js +46 -0
  35. package/dist/auth/index.js.map +1 -0
  36. package/dist/auth/service.d.ts +67 -0
  37. package/dist/auth/service.d.ts.map +1 -0
  38. package/dist/auth/service.js +275 -0
  39. package/dist/auth/service.js.map +1 -0
  40. package/dist/auth/types.d.ts +56 -0
  41. package/dist/auth/types.d.ts.map +1 -0
  42. package/dist/auth/types.js +2 -0
  43. package/dist/auth/types.js.map +1 -0
  44. package/dist/authorization/index.d.ts +4 -0
  45. package/dist/authorization/index.d.ts.map +1 -0
  46. package/dist/authorization/index.js +24 -0
  47. package/dist/authorization/index.js.map +1 -0
  48. package/dist/authorization/service.d.ts +92 -0
  49. package/dist/authorization/service.d.ts.map +1 -0
  50. package/dist/authorization/service.js +254 -0
  51. package/dist/authorization/service.js.map +1 -0
  52. package/dist/billing/index.d.ts +16 -0
  53. package/dist/billing/index.d.ts.map +1 -0
  54. package/dist/billing/index.js +47 -0
  55. package/dist/billing/index.js.map +1 -0
  56. package/dist/billing/provider.d.ts +114 -0
  57. package/dist/billing/provider.d.ts.map +1 -0
  58. package/dist/billing/provider.js +2 -0
  59. package/dist/billing/provider.js.map +1 -0
  60. package/dist/billing/revolut.d.ts +68 -0
  61. package/dist/billing/revolut.d.ts.map +1 -0
  62. package/dist/billing/revolut.js +181 -0
  63. package/dist/billing/revolut.js.map +1 -0
  64. package/dist/billing/service.d.ts +124 -0
  65. package/dist/billing/service.d.ts.map +1 -0
  66. package/dist/billing/service.js +470 -0
  67. package/dist/billing/service.js.map +1 -0
  68. package/dist/cli/commands/dev.d.ts +43 -0
  69. package/dist/cli/commands/dev.d.ts.map +1 -0
  70. package/dist/cli/commands/dev.js +212 -0
  71. package/dist/cli/commands/dev.js.map +1 -0
  72. package/dist/cli/commands/project.d.ts +12 -0
  73. package/dist/cli/commands/project.d.ts.map +1 -0
  74. package/dist/cli/commands/project.js +49 -0
  75. package/dist/cli/commands/project.js.map +1 -0
  76. package/dist/cli/docker.d.ts +50 -0
  77. package/dist/cli/docker.d.ts.map +1 -0
  78. package/dist/cli/docker.js +84 -0
  79. package/dist/cli/docker.js.map +1 -0
  80. package/dist/cli/index.d.ts +6 -0
  81. package/dist/cli/index.d.ts.map +1 -0
  82. package/dist/cli/index.js +198 -0
  83. package/dist/cli/index.js.map +1 -0
  84. package/dist/cli/output.d.ts +30 -0
  85. package/dist/cli/output.d.ts.map +1 -0
  86. package/dist/cli/output.js +66 -0
  87. package/dist/cli/output.js.map +1 -0
  88. package/dist/cli/templates.d.ts +52 -0
  89. package/dist/cli/templates.d.ts.map +1 -0
  90. package/dist/cli/templates.js +225 -0
  91. package/dist/cli/templates.js.map +1 -0
  92. package/dist/config/client.d.ts +60 -0
  93. package/dist/config/client.d.ts.map +1 -0
  94. package/dist/config/client.js +94 -0
  95. package/dist/config/client.js.map +1 -0
  96. package/dist/config/config-loader.d.ts +2 -0
  97. package/dist/config/config-loader.d.ts.map +1 -0
  98. package/dist/config/config-loader.js +2 -0
  99. package/dist/config/config-loader.js.map +1 -0
  100. package/dist/config/env.d.ts +24 -0
  101. package/dist/config/env.d.ts.map +1 -0
  102. package/dist/config/env.js +40 -0
  103. package/dist/config/env.js.map +1 -0
  104. package/dist/config/errors.d.ts +17 -0
  105. package/dist/config/errors.d.ts.map +1 -0
  106. package/dist/config/errors.js +26 -0
  107. package/dist/config/errors.js.map +1 -0
  108. package/dist/config/index.d.ts +72 -0
  109. package/dist/config/index.d.ts.map +1 -0
  110. package/dist/config/index.js +106 -0
  111. package/dist/config/index.js.map +1 -0
  112. package/dist/config/runtime.d.ts +15 -0
  113. package/dist/config/runtime.d.ts.map +1 -0
  114. package/dist/config/runtime.js +22 -0
  115. package/dist/config/runtime.js.map +1 -0
  116. package/dist/config/sections.d.ts +226 -0
  117. package/dist/config/sections.d.ts.map +1 -0
  118. package/dist/config/sections.js +141 -0
  119. package/dist/config/sections.js.map +1 -0
  120. package/dist/db/client.d.ts +43 -0
  121. package/dist/db/client.d.ts.map +1 -0
  122. package/dist/db/client.js +77 -0
  123. package/dist/db/client.js.map +1 -0
  124. package/dist/db/errors.d.ts +39 -0
  125. package/dist/db/errors.d.ts.map +1 -0
  126. package/dist/db/errors.js +67 -0
  127. package/dist/db/errors.js.map +1 -0
  128. package/dist/db/index.d.ts +10 -0
  129. package/dist/db/index.d.ts.map +1 -0
  130. package/dist/db/index.js +10 -0
  131. package/dist/db/index.js.map +1 -0
  132. package/dist/db/migrate.d.ts +21 -0
  133. package/dist/db/migrate.d.ts.map +1 -0
  134. package/dist/db/migrate.js +28 -0
  135. package/dist/db/migrate.js.map +1 -0
  136. package/dist/db/schema/_shared.d.ts +30 -0
  137. package/dist/db/schema/_shared.d.ts.map +1 -0
  138. package/dist/db/schema/_shared.js +36 -0
  139. package/dist/db/schema/_shared.js.map +1 -0
  140. package/dist/db/schema/apikeys.d.ts +263 -0
  141. package/dist/db/schema/apikeys.d.ts.map +1 -0
  142. package/dist/db/schema/apikeys.js +70 -0
  143. package/dist/db/schema/apikeys.js.map +1 -0
  144. package/dist/db/schema/authorization.d.ts +465 -0
  145. package/dist/db/schema/authorization.d.ts.map +1 -0
  146. package/dist/db/schema/authorization.js +134 -0
  147. package/dist/db/schema/authorization.js.map +1 -0
  148. package/dist/db/schema/billing.d.ts +1100 -0
  149. package/dist/db/schema/billing.d.ts.map +1 -0
  150. package/dist/db/schema/billing.js +223 -0
  151. package/dist/db/schema/billing.js.map +1 -0
  152. package/dist/db/schema/events.d.ts +161 -0
  153. package/dist/db/schema/events.d.ts.map +1 -0
  154. package/dist/db/schema/events.js +66 -0
  155. package/dist/db/schema/events.js.map +1 -0
  156. package/dist/db/schema/features.d.ts +243 -0
  157. package/dist/db/schema/features.d.ts.map +1 -0
  158. package/dist/db/schema/features.js +87 -0
  159. package/dist/db/schema/features.js.map +1 -0
  160. package/dist/db/schema/files.d.ts +253 -0
  161. package/dist/db/schema/files.d.ts.map +1 -0
  162. package/dist/db/schema/files.js +74 -0
  163. package/dist/db/schema/files.js.map +1 -0
  164. package/dist/db/schema/index.d.ts +38 -0
  165. package/dist/db/schema/index.d.ts.map +1 -0
  166. package/dist/db/schema/index.js +38 -0
  167. package/dist/db/schema/index.js.map +1 -0
  168. package/dist/db/schema/invitations.d.ts +214 -0
  169. package/dist/db/schema/invitations.d.ts.map +1 -0
  170. package/dist/db/schema/invitations.js +80 -0
  171. package/dist/db/schema/invitations.js.map +1 -0
  172. package/dist/db/schema/messaging.d.ts +442 -0
  173. package/dist/db/schema/messaging.d.ts.map +1 -0
  174. package/dist/db/schema/messaging.js +120 -0
  175. package/dist/db/schema/messaging.js.map +1 -0
  176. package/dist/db/schema/users.d.ts +246 -0
  177. package/dist/db/schema/users.d.ts.map +1 -0
  178. package/dist/db/schema/users.js +60 -0
  179. package/dist/db/schema/users.js.map +1 -0
  180. package/dist/db/schema/webhooks.d.ts +693 -0
  181. package/dist/db/schema/webhooks.d.ts.map +1 -0
  182. package/dist/db/schema/webhooks.js +147 -0
  183. package/dist/db/schema/webhooks.js.map +1 -0
  184. package/dist/db/schema/workspaces.d.ts +196 -0
  185. package/dist/db/schema/workspaces.d.ts.map +1 -0
  186. package/dist/db/schema/workspaces.js +57 -0
  187. package/dist/db/schema/workspaces.js.map +1 -0
  188. package/dist/db/types.d.ts +18 -0
  189. package/dist/db/types.d.ts.map +1 -0
  190. package/dist/db/types.js +2 -0
  191. package/dist/db/types.js.map +1 -0
  192. package/dist/email/adapters.d.ts +29 -0
  193. package/dist/email/adapters.d.ts.map +1 -0
  194. package/dist/email/adapters.js +60 -0
  195. package/dist/email/adapters.js.map +1 -0
  196. package/dist/email/index.d.ts +20 -0
  197. package/dist/email/index.d.ts.map +1 -0
  198. package/dist/email/index.js +79 -0
  199. package/dist/email/index.js.map +1 -0
  200. package/dist/email/provider.d.ts +47 -0
  201. package/dist/email/provider.d.ts.map +1 -0
  202. package/dist/email/provider.js +7 -0
  203. package/dist/email/provider.js.map +1 -0
  204. package/dist/email/resend.d.ts +22 -0
  205. package/dist/email/resend.d.ts.map +1 -0
  206. package/dist/email/resend.js +51 -0
  207. package/dist/email/resend.js.map +1 -0
  208. package/dist/email/service.d.ts +55 -0
  209. package/dist/email/service.d.ts.map +1 -0
  210. package/dist/email/service.js +87 -0
  211. package/dist/email/service.js.map +1 -0
  212. package/dist/email/templates.d.ts +81 -0
  213. package/dist/email/templates.d.ts.map +1 -0
  214. package/dist/email/templates.js +84 -0
  215. package/dist/email/templates.js.map +1 -0
  216. package/dist/entitlements/index.d.ts +10 -0
  217. package/dist/entitlements/index.d.ts.map +1 -0
  218. package/dist/entitlements/index.js +28 -0
  219. package/dist/entitlements/index.js.map +1 -0
  220. package/dist/entitlements/service.d.ts +83 -0
  221. package/dist/entitlements/service.d.ts.map +1 -0
  222. package/dist/entitlements/service.js +158 -0
  223. package/dist/entitlements/service.js.map +1 -0
  224. package/dist/errors.d.ts +85 -0
  225. package/dist/errors.d.ts.map +1 -0
  226. package/dist/errors.js +95 -0
  227. package/dist/errors.js.map +1 -0
  228. package/dist/events/core-events.d.ts +120 -0
  229. package/dist/events/core-events.d.ts.map +1 -0
  230. package/dist/events/core-events.js +109 -0
  231. package/dist/events/core-events.js.map +1 -0
  232. package/dist/events/index.d.ts +15 -0
  233. package/dist/events/index.d.ts.map +1 -0
  234. package/dist/events/index.js +58 -0
  235. package/dist/events/index.js.map +1 -0
  236. package/dist/events/publisher.d.ts +46 -0
  237. package/dist/events/publisher.d.ts.map +1 -0
  238. package/dist/events/publisher.js +54 -0
  239. package/dist/events/publisher.js.map +1 -0
  240. package/dist/events/registry.d.ts +87 -0
  241. package/dist/events/registry.d.ts.map +1 -0
  242. package/dist/events/registry.js +34 -0
  243. package/dist/events/registry.js.map +1 -0
  244. package/dist/events/service.d.ts +78 -0
  245. package/dist/events/service.d.ts.map +1 -0
  246. package/dist/events/service.js +182 -0
  247. package/dist/events/service.js.map +1 -0
  248. package/dist/features/bucket.d.ts +22 -0
  249. package/dist/features/bucket.d.ts.map +1 -0
  250. package/dist/features/bucket.js +42 -0
  251. package/dist/features/bucket.js.map +1 -0
  252. package/dist/features/index.d.ts +15 -0
  253. package/dist/features/index.d.ts.map +1 -0
  254. package/dist/features/index.js +78 -0
  255. package/dist/features/index.js.map +1 -0
  256. package/dist/features/registry.d.ts +51 -0
  257. package/dist/features/registry.d.ts.map +1 -0
  258. package/dist/features/registry.js +52 -0
  259. package/dist/features/registry.js.map +1 -0
  260. package/dist/features/service.d.ts +78 -0
  261. package/dist/features/service.d.ts.map +1 -0
  262. package/dist/features/service.js +207 -0
  263. package/dist/features/service.js.map +1 -0
  264. package/dist/files/index.d.ts +6 -0
  265. package/dist/files/index.d.ts.map +1 -0
  266. package/dist/files/index.js +64 -0
  267. package/dist/files/index.js.map +1 -0
  268. package/dist/files/service.d.ts +92 -0
  269. package/dist/files/service.d.ts.map +1 -0
  270. package/dist/files/service.js +303 -0
  271. package/dist/files/service.js.map +1 -0
  272. package/dist/index.d.ts +33 -0
  273. package/dist/index.d.ts.map +1 -0
  274. package/dist/index.js +33 -0
  275. package/dist/index.js.map +1 -0
  276. package/dist/jobs/backend.d.ts +23 -0
  277. package/dist/jobs/backend.d.ts.map +1 -0
  278. package/dist/jobs/backend.js +23 -0
  279. package/dist/jobs/backend.js.map +1 -0
  280. package/dist/jobs/index.d.ts +29 -0
  281. package/dist/jobs/index.d.ts.map +1 -0
  282. package/dist/jobs/index.js +114 -0
  283. package/dist/jobs/index.js.map +1 -0
  284. package/dist/jobs/service.d.ts +67 -0
  285. package/dist/jobs/service.d.ts.map +1 -0
  286. package/dist/jobs/service.js +139 -0
  287. package/dist/jobs/service.js.map +1 -0
  288. package/dist/jobs/types.d.ts +78 -0
  289. package/dist/jobs/types.d.ts.map +1 -0
  290. package/dist/jobs/types.js +4 -0
  291. package/dist/jobs/types.js.map +1 -0
  292. package/dist/logger/default.d.ts +7 -0
  293. package/dist/logger/default.d.ts.map +1 -0
  294. package/dist/logger/default.js +28 -0
  295. package/dist/logger/default.js.map +1 -0
  296. package/dist/logger/index.d.ts +24 -0
  297. package/dist/logger/index.d.ts.map +1 -0
  298. package/dist/logger/index.js +90 -0
  299. package/dist/logger/index.js.map +1 -0
  300. package/dist/logger/redact.d.ts +40 -0
  301. package/dist/logger/redact.d.ts.map +1 -0
  302. package/dist/logger/redact.js +178 -0
  303. package/dist/logger/redact.js.map +1 -0
  304. package/dist/logger/service.d.ts +14 -0
  305. package/dist/logger/service.d.ts.map +1 -0
  306. package/dist/logger/service.js +56 -0
  307. package/dist/logger/service.js.map +1 -0
  308. package/dist/logger/sinks.d.ts +37 -0
  309. package/dist/logger/sinks.d.ts.map +1 -0
  310. package/dist/logger/sinks.js +135 -0
  311. package/dist/logger/sinks.js.map +1 -0
  312. package/dist/logger/types.d.ts +47 -0
  313. package/dist/logger/types.d.ts.map +1 -0
  314. package/dist/logger/types.js +8 -0
  315. package/dist/logger/types.js.map +1 -0
  316. package/dist/notifications/index.d.ts +8 -0
  317. package/dist/notifications/index.d.ts.map +1 -0
  318. package/dist/notifications/index.js +72 -0
  319. package/dist/notifications/index.js.map +1 -0
  320. package/dist/notifications/service.d.ts +79 -0
  321. package/dist/notifications/service.d.ts.map +1 -0
  322. package/dist/notifications/service.js +164 -0
  323. package/dist/notifications/service.js.map +1 -0
  324. package/dist/realtime/channels.d.ts +37 -0
  325. package/dist/realtime/channels.d.ts.map +1 -0
  326. package/dist/realtime/channels.js +58 -0
  327. package/dist/realtime/channels.js.map +1 -0
  328. package/dist/realtime/index.d.ts +19 -0
  329. package/dist/realtime/index.d.ts.map +1 -0
  330. package/dist/realtime/index.js +118 -0
  331. package/dist/realtime/index.js.map +1 -0
  332. package/dist/realtime/postgres.d.ts +48 -0
  333. package/dist/realtime/postgres.d.ts.map +1 -0
  334. package/dist/realtime/postgres.js +151 -0
  335. package/dist/realtime/postgres.js.map +1 -0
  336. package/dist/realtime/service.d.ts +75 -0
  337. package/dist/realtime/service.d.ts.map +1 -0
  338. package/dist/realtime/service.js +103 -0
  339. package/dist/realtime/service.js.map +1 -0
  340. package/dist/realtime/sse.d.ts +55 -0
  341. package/dist/realtime/sse.d.ts.map +1 -0
  342. package/dist/realtime/sse.js +123 -0
  343. package/dist/realtime/sse.js.map +1 -0
  344. package/dist/realtime/transport.d.ts +52 -0
  345. package/dist/realtime/transport.d.ts.map +1 -0
  346. package/dist/realtime/transport.js +2 -0
  347. package/dist/realtime/transport.js.map +1 -0
  348. package/dist/storage/index.d.ts +18 -0
  349. package/dist/storage/index.d.ts.map +1 -0
  350. package/dist/storage/index.js +89 -0
  351. package/dist/storage/index.js.map +1 -0
  352. package/dist/storage/keys.d.ts +50 -0
  353. package/dist/storage/keys.d.ts.map +1 -0
  354. package/dist/storage/keys.js +120 -0
  355. package/dist/storage/keys.js.map +1 -0
  356. package/dist/storage/memory.d.ts +32 -0
  357. package/dist/storage/memory.d.ts.map +1 -0
  358. package/dist/storage/memory.js +84 -0
  359. package/dist/storage/memory.js.map +1 -0
  360. package/dist/storage/provider.d.ts +93 -0
  361. package/dist/storage/provider.d.ts.map +1 -0
  362. package/dist/storage/provider.js +2 -0
  363. package/dist/storage/provider.js.map +1 -0
  364. package/dist/storage/s3.d.ts +34 -0
  365. package/dist/storage/s3.d.ts.map +1 -0
  366. package/dist/storage/s3.js +175 -0
  367. package/dist/storage/s3.js.map +1 -0
  368. package/dist/storage/service.d.ts +86 -0
  369. package/dist/storage/service.d.ts.map +1 -0
  370. package/dist/storage/service.js +107 -0
  371. package/dist/storage/service.js.map +1 -0
  372. package/dist/storage/sigv4.d.ts +58 -0
  373. package/dist/storage/sigv4.d.ts.map +1 -0
  374. package/dist/storage/sigv4.js +114 -0
  375. package/dist/storage/sigv4.js.map +1 -0
  376. package/dist/streams/client.d.ts +91 -0
  377. package/dist/streams/client.d.ts.map +1 -0
  378. package/dist/streams/client.js +128 -0
  379. package/dist/streams/client.js.map +1 -0
  380. package/dist/streams/events.d.ts +68 -0
  381. package/dist/streams/events.d.ts.map +1 -0
  382. package/dist/streams/events.js +81 -0
  383. package/dist/streams/events.js.map +1 -0
  384. package/dist/streams/index.d.ts +47 -0
  385. package/dist/streams/index.d.ts.map +1 -0
  386. package/dist/streams/index.js +47 -0
  387. package/dist/streams/index.js.map +1 -0
  388. package/dist/streams/realtime.d.ts +27 -0
  389. package/dist/streams/realtime.d.ts.map +1 -0
  390. package/dist/streams/realtime.js +50 -0
  391. package/dist/streams/realtime.js.map +1 -0
  392. package/dist/streams/state.d.ts +26 -0
  393. package/dist/streams/state.d.ts.map +1 -0
  394. package/dist/streams/state.js +34 -0
  395. package/dist/streams/state.js.map +1 -0
  396. package/dist/users/index.d.ts +9 -0
  397. package/dist/users/index.d.ts.map +1 -0
  398. package/dist/users/index.js +23 -0
  399. package/dist/users/index.js.map +1 -0
  400. package/dist/users/service.d.ts +43 -0
  401. package/dist/users/service.d.ts.map +1 -0
  402. package/dist/users/service.js +113 -0
  403. package/dist/users/service.js.map +1 -0
  404. package/dist/webhooks/inbound.d.ts +70 -0
  405. package/dist/webhooks/inbound.d.ts.map +1 -0
  406. package/dist/webhooks/inbound.js +146 -0
  407. package/dist/webhooks/inbound.js.map +1 -0
  408. package/dist/webhooks/index.d.ts +12 -0
  409. package/dist/webhooks/index.d.ts.map +1 -0
  410. package/dist/webhooks/index.js +65 -0
  411. package/dist/webhooks/index.js.map +1 -0
  412. package/dist/webhooks/outgoing.d.ts +89 -0
  413. package/dist/webhooks/outgoing.d.ts.map +1 -0
  414. package/dist/webhooks/outgoing.js +375 -0
  415. package/dist/webhooks/outgoing.js.map +1 -0
  416. package/dist/webhooks/signature.d.ts +40 -0
  417. package/dist/webhooks/signature.d.ts.map +1 -0
  418. package/dist/webhooks/signature.js +77 -0
  419. package/dist/webhooks/signature.js.map +1 -0
  420. package/dist/workspaces/context.d.ts +30 -0
  421. package/dist/workspaces/context.d.ts.map +1 -0
  422. package/dist/workspaces/context.js +12 -0
  423. package/dist/workspaces/context.js.map +1 -0
  424. package/dist/workspaces/index.d.ts +5 -0
  425. package/dist/workspaces/index.d.ts.map +1 -0
  426. package/dist/workspaces/index.js +21 -0
  427. package/dist/workspaces/index.js.map +1 -0
  428. package/dist/workspaces/service.d.ts +126 -0
  429. package/dist/workspaces/service.d.ts.map +1 -0
  430. package/dist/workspaces/service.js +495 -0
  431. package/dist/workspaces/service.js.map +1 -0
  432. package/docs/README.md +71 -0
  433. package/docs/api-keys.md +184 -0
  434. package/docs/audit.md +198 -0
  435. package/docs/auth.md +196 -0
  436. package/docs/authorization.md +225 -0
  437. package/docs/billing.md +293 -0
  438. package/docs/cli.md +238 -0
  439. package/docs/config.md +163 -0
  440. package/docs/db.md +219 -0
  441. package/docs/email.md +222 -0
  442. package/docs/entitlements.md +194 -0
  443. package/docs/errors.md +158 -0
  444. package/docs/events.md +279 -0
  445. package/docs/feature-flags.md +237 -0
  446. package/docs/files.md +229 -0
  447. package/docs/jobs.md +288 -0
  448. package/docs/logger.md +269 -0
  449. package/docs/notifications.md +179 -0
  450. package/docs/publishing.md +128 -0
  451. package/docs/realtime.md +360 -0
  452. package/docs/storage.md +279 -0
  453. package/docs/streams.md +296 -0
  454. package/docs/users.md +117 -0
  455. package/docs/webhooks.md +272 -0
  456. package/docs/workspaces.md +269 -0
  457. package/drizzle/0000_init_users.sql +21 -0
  458. package/drizzle/0001_workspaces_roles_permissions.sql +86 -0
  459. package/drizzle/0002_billing_entitlements.sql +97 -0
  460. package/drizzle/0003_events.sql +17 -0
  461. package/drizzle/0004_notifications_audit.sql +64 -0
  462. package/drizzle/0005_api_keys_webhooks.sql +77 -0
  463. package/drizzle/0006_feature_flags.sql +22 -0
  464. package/drizzle/0007_files.sql +23 -0
  465. package/drizzle/meta/0000_snapshot.json +160 -0
  466. package/drizzle/meta/0001_snapshot.json +837 -0
  467. package/drizzle/meta/0002_snapshot.json +1580 -0
  468. package/drizzle/meta/0003_snapshot.json +1751 -0
  469. package/drizzle/meta/0004_snapshot.json +2109 -0
  470. package/drizzle/meta/0005_snapshot.json +2719 -0
  471. package/drizzle/meta/0006_snapshot.json +2909 -0
  472. package/drizzle/meta/0007_snapshot.json +3113 -0
  473. package/drizzle/meta/_journal.json +62 -0
  474. package/package.json +180 -0
@@ -0,0 +1,269 @@
1
+ # Workspaces, members & invitations
2
+
3
+ ```ts
4
+ import { workspaces } from "@digimuza/saas-core/workspaces";
5
+ ```
6
+
7
+ > Orientation and recipes: [`src/workspaces/README.md`](../src/workspaces/README.md).
8
+
9
+
10
+ A workspace is the tenancy boundary. Users belong to many workspaces through
11
+ `workspace_members`; `workspace_id` never appears on `users`.
12
+
13
+ ---
14
+
15
+ ## `WorkspaceContext`
16
+
17
+ ```ts
18
+ interface WorkspaceContext {
19
+ readonly userId: string;
20
+ readonly workspaceId: string;
21
+ readonly membershipVerified: true;
22
+ }
23
+ ```
24
+
25
+ **Every tenant-scoped operation in the core takes one of these**, and the only way
26
+ to get one is a verified membership lookup. So a `workspaceId` read from a request
27
+ body, query string or path parameter will not type-check where a context is
28
+ required.
29
+
30
+ The marker is a convention, not a cryptographic guarantee — but it makes bypassing
31
+ the check a deliberate, greppable act rather than an oversight.
32
+
33
+ ```ts
34
+ // The standard shape of a tenant-scoped handler.
35
+ const user = await auth.requireUser();
36
+ const context = await workspaces.requireContext({
37
+ userId: user.id,
38
+ workspaceId: params.workspaceId, // untrusted until this call returns
39
+ });
40
+ await authorization.require(context, "projects.create");
41
+ ```
42
+
43
+ ## `workspaces.resolveContext(input)`
44
+
45
+ ```ts
46
+ function resolveContext(input: { userId: string; workspaceId: string }):
47
+ Promise<WorkspaceContext | undefined>
48
+ ```
49
+
50
+ Returns `undefined` when the user is not a member.
51
+
52
+ ## `workspaces.requireContext(input)`
53
+
54
+ ```ts
55
+ function requireContext(input: { userId: string; workspaceId: string }): Promise<WorkspaceContext>
56
+ ```
57
+
58
+ Throws **`NotFoundError`**, not `ForbiddenError`. A 403 would confirm that a given
59
+ workspace id exists, letting an attacker enumerate tenants by iterating ids. See
60
+ [errors.md](./errors.md#not-found-vs-forbidden).
61
+
62
+ ## `workspaces.requireContextBySlug(input)`
63
+
64
+ ```ts
65
+ function requireContextBySlug(input: { userId: string; slug: string }): Promise<WorkspaceContext>
66
+ ```
67
+
68
+ Same guarantee for URL-addressed workspaces. Resolves slug and membership in one
69
+ query, so a non-member cannot distinguish "no such slug" from "not your workspace".
70
+
71
+ ---
72
+
73
+ ## `workspaces.create(input)`
74
+
75
+ ```ts
76
+ function create(input: {
77
+ name: string;
78
+ slug?: string; // derived from name if omitted
79
+ ownerUserId: string;
80
+ systemRoles?: readonly SystemRoleDefinition[]; // defaults to admin + member
81
+ }): Promise<Workspace>
82
+ ```
83
+
84
+ In one transaction: creates the workspace, seeds the system roles, adds the creator
85
+ as a member, and grants them `admin`. Without that last step a new workspace would
86
+ have nobody able to administer it.
87
+
88
+ Throws `ConflictError` if the slug is taken, `InvalidInputError` if the name
89
+ contains no alphanumeric character.
90
+
91
+ ```ts
92
+ const workspace = await workspaces.create({
93
+ name: "Acme Corp", // slug becomes "acme-corp"
94
+ ownerUserId: user.id,
95
+ });
96
+ ```
97
+
98
+ ### Overriding the seeded roles
99
+
100
+ ```ts
101
+ await workspaces.create({
102
+ name: "Acme",
103
+ ownerUserId: user.id,
104
+ systemRoles: [
105
+ { key: "admin", name: "Administrator" },
106
+ { key: "editor", name: "Editor", isDefault: true },
107
+ { key: "viewer", name: "Viewer" },
108
+ ],
109
+ });
110
+ ```
111
+
112
+ The creator gets `admin` if it exists, otherwise the default role, otherwise the
113
+ first. Exactly one role may be `isDefault` per workspace — enforced by a partial
114
+ unique index.
115
+
116
+ ## `workspaces.get` / `update` / `delete`
117
+
118
+ ```ts
119
+ function get(context: WorkspaceContext): Promise<Workspace>
120
+ function update(context: WorkspaceContext, patch: { name?: string; slug?: string }): Promise<Workspace>
121
+ function delete(context: WorkspaceContext): Promise<void>
122
+ ```
123
+
124
+ `update` emits `workspace.updated` listing which fields changed, so consumers can
125
+ skip work they do not care about.
126
+
127
+ ## `workspaces.listForUser(userId)`
128
+
129
+ ```ts
130
+ function listForUser(userId: string): Promise<Workspace[]>
131
+ ```
132
+
133
+ Takes a raw user id rather than a context — this is the query that *builds* the
134
+ workspace switcher, before any workspace is chosen.
135
+
136
+ ---
137
+
138
+ ## Members
139
+
140
+ ### `workspaces.members.list(context)`
141
+
142
+ ```ts
143
+ function list(context: WorkspaceContext): Promise<WorkspaceMemberSummary[]>
144
+ ```
145
+
146
+ ```ts
147
+ interface WorkspaceMemberSummary {
148
+ userId: string;
149
+ email: string | null;
150
+ displayName: string | null;
151
+ avatarUrl: string | null;
152
+ joinedAt: Date;
153
+ roles: readonly { id: string; key: string; name: string }[];
154
+ }
155
+ ```
156
+
157
+ One query returning member/role pairs, folded into one entry per member.
158
+
159
+ ### `workspaces.members.add(context, input)`
160
+
161
+ ```ts
162
+ function add(context: WorkspaceContext, input: {
163
+ userId: string;
164
+ roleIds?: readonly string[]; // defaults to the workspace default role
165
+ }): Promise<void>
166
+ ```
167
+
168
+ Direct addition, bypassing the invitation flow — for admin tooling and for users
169
+ who already exist. Idempotent.
170
+
171
+ ### `workspaces.members.remove(context, userId)`
172
+
173
+ ```ts
174
+ function remove(context: WorkspaceContext, userId: string): Promise<void>
175
+ ```
176
+
177
+ Deletes first, then counts what is left, inside one transaction. Throws
178
+ `ConflictError` if that would empty the workspace, and `NotFoundError` if the user
179
+ is not a member of *this* workspace.
180
+
181
+ The order matters. Counting first was a real bug: it reported "last member" for
182
+ someone who was not a member at all — leaking the workspace's member count to an
183
+ outsider — and it raced, since two concurrent removals could each see two members
184
+ and both proceed.
185
+
186
+ Role grants cascade away via `workspace_member_roles_member_fk`.
187
+
188
+ ---
189
+
190
+ ## Invitations
191
+
192
+ ### `workspaces.members.invite(context, input)`
193
+
194
+ ```ts
195
+ function invite(context: WorkspaceContext, input: {
196
+ email: string;
197
+ roleId?: string; // falls back to the workspace default role
198
+ expiresInSeconds?: number; // 7 days
199
+ }): Promise<{ invitation: WorkspaceInvitation; token: string }>
200
+ ```
201
+
202
+ ```ts
203
+ const { invitation, token } = await workspaces.members.invite(context, {
204
+ email: "new@example.com",
205
+ });
206
+ await email.send({ to: invitation.email, token }); // the only chance to use it
207
+ ```
208
+
209
+ **`token` is returned once.** Only its SHA-256 hash is stored, so a database leak
210
+ does not let an attacker accept outstanding invitations. Lose the token and you
211
+ must revoke and re-issue.
212
+
213
+ Throws `ConflictError` if an invitation for that address is already outstanding —
214
+ a partial unique index allows one *pending* invitation per email per workspace
215
+ while keeping accepted and revoked history queryable. Email is lowercased so the
216
+ index behaves predictably.
217
+
218
+ Throws `InvalidInputError` for a malformed address.
219
+
220
+ ### `workspaces.members.acceptInvitation(input)`
221
+
222
+ ```ts
223
+ function acceptInvitation(input: { token: string; userId: string }): Promise<WorkspaceContext>
224
+ ```
225
+
226
+ Not workspace-scoped: the token identifies the workspace. `userId` must be the
227
+ **authenticated** user, never taken from the invitation payload.
228
+
229
+ Returns a verified context, so the caller can immediately act as the new member.
230
+
231
+ Missing, expired, revoked and already-accepted tokens all raise the **same**
232
+ `NotFoundError` with the same message. Distinguishing them would tell a
233
+ token-guessing attacker which guesses corresponded to real invitations.
234
+
235
+ Acceptance re-checks the guard in its `UPDATE ... WHERE`, so two concurrent
236
+ redemptions cannot both succeed.
237
+
238
+ ### `listInvitations` / `revokeInvitation`
239
+
240
+ ```ts
241
+ function listInvitations(context: WorkspaceContext): Promise<WorkspaceInvitation[]>
242
+ function revokeInvitation(context: WorkspaceContext, invitationId: string): Promise<void>
243
+ ```
244
+
245
+ `listInvitations` returns only outstanding ones. `revokeInvitation` is scoped to the
246
+ caller's workspace, so an id from another tenant matches nothing and raises
247
+ `NotFoundError`.
248
+
249
+ ---
250
+
251
+ ## `slugify(value)`
252
+
253
+ ```ts
254
+ function slugify(value: string): string
255
+ ```
256
+
257
+ Normalises Unicode, strips diacritics, lowercases, collapses non-alphanumerics to
258
+ hyphens, caps at 60 characters. Throws `InvalidInputError` if nothing usable
259
+ remains.
260
+
261
+ ---
262
+
263
+ ## Custom construction
264
+
265
+ ```ts
266
+ import { createWorkspacesService } from "@digimuza/saas-core/workspaces";
267
+
268
+ const workspaces = createWorkspacesService({ db, events }); // events optional
269
+ ```
@@ -0,0 +1,21 @@
1
+ CREATE TABLE "user_profiles" (
2
+ "user_id" uuid PRIMARY KEY NOT NULL,
3
+ "email" text NOT NULL,
4
+ "first_name" text,
5
+ "last_name" text,
6
+ "display_name" text,
7
+ "avatar_url" text,
8
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
9
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
10
+ );
11
+ --> statement-breakpoint
12
+ CREATE TABLE "users" (
13
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
14
+ "workos_user_id" text NOT NULL,
15
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
16
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
17
+ CONSTRAINT "users_workos_user_id_unique" UNIQUE("workos_user_id")
18
+ );
19
+ --> statement-breakpoint
20
+ ALTER TABLE "user_profiles" ADD CONSTRAINT "user_profiles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
21
+ CREATE INDEX "user_profiles_email_lower_idx" ON "user_profiles" USING btree (lower("email"));
@@ -0,0 +1,86 @@
1
+ CREATE TABLE "workspace_members" (
2
+ "workspace_id" uuid NOT NULL,
3
+ "user_id" uuid NOT NULL,
4
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
5
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
6
+ CONSTRAINT "workspace_members_workspace_id_user_id_pk" PRIMARY KEY("workspace_id","user_id")
7
+ );
8
+ --> statement-breakpoint
9
+ CREATE TABLE "workspaces" (
10
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
11
+ "name" text NOT NULL,
12
+ "slug" text NOT NULL,
13
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
14
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
15
+ CONSTRAINT "workspaces_slug_unique" UNIQUE("slug")
16
+ );
17
+ --> statement-breakpoint
18
+ CREATE TABLE "permissions" (
19
+ "key" text PRIMARY KEY NOT NULL,
20
+ "description" text,
21
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
22
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
23
+ );
24
+ --> statement-breakpoint
25
+ CREATE TABLE "role_permissions" (
26
+ "role_id" uuid NOT NULL,
27
+ "permission_key" text NOT NULL,
28
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
29
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
30
+ CONSTRAINT "role_permissions_role_id_permission_key_pk" PRIMARY KEY("role_id","permission_key")
31
+ );
32
+ --> statement-breakpoint
33
+ CREATE TABLE "roles" (
34
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
35
+ "workspace_id" uuid NOT NULL,
36
+ "key" text NOT NULL,
37
+ "name" text NOT NULL,
38
+ "description" text,
39
+ "is_system" boolean DEFAULT false NOT NULL,
40
+ "is_default" boolean DEFAULT false NOT NULL,
41
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
42
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
43
+ CONSTRAINT "roles_id_workspace_id_key" UNIQUE("id","workspace_id")
44
+ );
45
+ --> statement-breakpoint
46
+ CREATE TABLE "workspace_member_roles" (
47
+ "workspace_id" uuid NOT NULL,
48
+ "user_id" uuid NOT NULL,
49
+ "role_id" uuid NOT NULL,
50
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
51
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
52
+ CONSTRAINT "workspace_member_roles_workspace_id_user_id_role_id_pk" PRIMARY KEY("workspace_id","user_id","role_id")
53
+ );
54
+ --> statement-breakpoint
55
+ CREATE TABLE "workspace_invitations" (
56
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
57
+ "workspace_id" uuid NOT NULL,
58
+ "email" text NOT NULL,
59
+ "token_hash" text NOT NULL,
60
+ "invited_by_user_id" uuid,
61
+ "role_id" uuid,
62
+ "expires_at" timestamp with time zone NOT NULL,
63
+ "accepted_at" timestamp with time zone,
64
+ "revoked_at" timestamp with time zone,
65
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
66
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
67
+ CONSTRAINT "workspace_invitations_token_hash_unique" UNIQUE("token_hash")
68
+ );
69
+ --> statement-breakpoint
70
+ ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
71
+ ALTER TABLE "workspace_members" ADD CONSTRAINT "workspace_members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
72
+ ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
73
+ ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_permission_key_permissions_key_fk" FOREIGN KEY ("permission_key") REFERENCES "public"."permissions"("key") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
74
+ ALTER TABLE "roles" ADD CONSTRAINT "roles_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
75
+ ALTER TABLE "workspace_member_roles" ADD CONSTRAINT "workspace_member_roles_member_fk" FOREIGN KEY ("workspace_id","user_id") REFERENCES "public"."workspace_members"("workspace_id","user_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
76
+ ALTER TABLE "workspace_member_roles" ADD CONSTRAINT "workspace_member_roles_role_fk" FOREIGN KEY ("role_id","workspace_id") REFERENCES "public"."roles"("id","workspace_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
77
+ ALTER TABLE "workspace_invitations" ADD CONSTRAINT "workspace_invitations_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
78
+ ALTER TABLE "workspace_invitations" ADD CONSTRAINT "workspace_invitations_invited_by_user_id_users_id_fk" FOREIGN KEY ("invited_by_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
79
+ ALTER TABLE "workspace_invitations" ADD CONSTRAINT "workspace_invitations_role_fk" FOREIGN KEY ("role_id","workspace_id") REFERENCES "public"."roles"("id","workspace_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
80
+ CREATE INDEX "workspace_members_user_id_idx" ON "workspace_members" USING btree ("user_id");--> statement-breakpoint
81
+ CREATE INDEX "role_permissions_permission_key_idx" ON "role_permissions" USING btree ("permission_key");--> statement-breakpoint
82
+ CREATE UNIQUE INDEX "roles_workspace_id_key_idx" ON "roles" USING btree ("workspace_id","key");--> statement-breakpoint
83
+ CREATE UNIQUE INDEX "roles_workspace_default_idx" ON "roles" USING btree ("workspace_id") WHERE "roles"."is_default";--> statement-breakpoint
84
+ CREATE INDEX "workspace_member_roles_role_id_idx" ON "workspace_member_roles" USING btree ("role_id");--> statement-breakpoint
85
+ CREATE INDEX "workspace_invitations_workspace_id_idx" ON "workspace_invitations" USING btree ("workspace_id");--> statement-breakpoint
86
+ CREATE UNIQUE INDEX "workspace_invitations_pending_idx" ON "workspace_invitations" USING btree ("workspace_id",lower("email")) WHERE "workspace_invitations"."accepted_at" is null and "workspace_invitations"."revoked_at" is null;
@@ -0,0 +1,97 @@
1
+ CREATE TYPE "public"."billing_event_status" AS ENUM('pending', 'processed', 'failed', 'ignored');--> statement-breakpoint
2
+ CREATE TYPE "public"."billing_interval" AS ENUM('month', 'year', 'one_time');--> statement-breakpoint
3
+ CREATE TYPE "public"."entitlement_kind" AS ENUM('feature', 'limit');--> statement-breakpoint
4
+ CREATE TYPE "public"."subscription_status" AS ENUM('trialing', 'active', 'past_due', 'paused', 'canceled', 'incomplete');--> statement-breakpoint
5
+ CREATE TABLE "billing_customers" (
6
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
7
+ "workspace_id" uuid NOT NULL,
8
+ "provider" text NOT NULL,
9
+ "provider_customer_id" text NOT NULL,
10
+ "email" text,
11
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
12
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
13
+ CONSTRAINT "billing_customers_workspace_id_unique" UNIQUE("workspace_id"),
14
+ CONSTRAINT "billing_customers_provider_customer_unique" UNIQUE("provider","provider_customer_id")
15
+ );
16
+ --> statement-breakpoint
17
+ CREATE TABLE "billing_events" (
18
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
19
+ "provider" text NOT NULL,
20
+ "provider_event_id" text NOT NULL,
21
+ "type" text NOT NULL,
22
+ "payload" jsonb NOT NULL,
23
+ "status" "billing_event_status" DEFAULT 'pending' NOT NULL,
24
+ "processed_at" timestamp with time zone,
25
+ "error" text,
26
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
27
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
28
+ CONSTRAINT "billing_events_provider_event_unique" UNIQUE("provider","provider_event_id")
29
+ );
30
+ --> statement-breakpoint
31
+ CREATE TABLE "entitlements" (
32
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
33
+ "plan_id" uuid NOT NULL,
34
+ "key" text NOT NULL,
35
+ "kind" "entitlement_kind" NOT NULL,
36
+ "limit_value" bigint,
37
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
38
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
39
+ CONSTRAINT "entitlements_plan_id_key_unique" UNIQUE("plan_id","key")
40
+ );
41
+ --> statement-breakpoint
42
+ CREATE TABLE "plans" (
43
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
44
+ "key" text NOT NULL,
45
+ "name" text NOT NULL,
46
+ "description" text,
47
+ "provider_price_id" text,
48
+ "amount_cents" integer DEFAULT 0 NOT NULL,
49
+ "currency" text DEFAULT 'EUR' NOT NULL,
50
+ "interval" "billing_interval" DEFAULT 'month' NOT NULL,
51
+ "is_active" boolean DEFAULT true NOT NULL,
52
+ "sort_order" integer DEFAULT 0 NOT NULL,
53
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
54
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
55
+ CONSTRAINT "plans_key_unique" UNIQUE("key")
56
+ );
57
+ --> statement-breakpoint
58
+ CREATE TABLE "subscriptions" (
59
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
60
+ "workspace_id" uuid NOT NULL,
61
+ "billing_customer_id" uuid NOT NULL,
62
+ "plan_id" uuid NOT NULL,
63
+ "provider" text NOT NULL,
64
+ "provider_subscription_id" text NOT NULL,
65
+ "status" "subscription_status" NOT NULL,
66
+ "current_period_start" timestamp with time zone,
67
+ "current_period_end" timestamp with time zone,
68
+ "cancel_at_period_end" boolean DEFAULT false NOT NULL,
69
+ "canceled_at" timestamp with time zone,
70
+ "trial_ends_at" timestamp with time zone,
71
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
72
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
73
+ CONSTRAINT "subscriptions_provider_subscription_unique" UNIQUE("provider","provider_subscription_id")
74
+ );
75
+ --> statement-breakpoint
76
+ CREATE TABLE "usage_records" (
77
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
78
+ "workspace_id" uuid NOT NULL,
79
+ "key" text NOT NULL,
80
+ "quantity" integer DEFAULT 1 NOT NULL,
81
+ "occurred_at" timestamp with time zone DEFAULT now() NOT NULL,
82
+ "idempotency_key" text,
83
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
84
+ );
85
+ --> statement-breakpoint
86
+ ALTER TABLE "billing_customers" ADD CONSTRAINT "billing_customers_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
87
+ ALTER TABLE "entitlements" ADD CONSTRAINT "entitlements_plan_id_plans_id_fk" FOREIGN KEY ("plan_id") REFERENCES "public"."plans"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
88
+ ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
89
+ ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_billing_customer_id_billing_customers_id_fk" FOREIGN KEY ("billing_customer_id") REFERENCES "public"."billing_customers"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
90
+ ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_plan_id_plans_id_fk" FOREIGN KEY ("plan_id") REFERENCES "public"."plans"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
91
+ ALTER TABLE "usage_records" ADD CONSTRAINT "usage_records_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
92
+ CREATE INDEX "billing_events_status_idx" ON "billing_events" USING btree ("status");--> statement-breakpoint
93
+ CREATE INDEX "plans_is_active_idx" ON "plans" USING btree ("is_active");--> statement-breakpoint
94
+ CREATE INDEX "subscriptions_workspace_id_idx" ON "subscriptions" USING btree ("workspace_id");--> statement-breakpoint
95
+ CREATE UNIQUE INDEX "subscriptions_one_live_per_workspace_idx" ON "subscriptions" USING btree ("workspace_id") WHERE "subscriptions"."status" in ('trialing', 'active', 'past_due');--> statement-breakpoint
96
+ CREATE INDEX "usage_records_workspace_key_occurred_idx" ON "usage_records" USING btree ("workspace_id","key","occurred_at");--> statement-breakpoint
97
+ CREATE UNIQUE INDEX "usage_records_idempotency_idx" ON "usage_records" USING btree ("workspace_id","key","idempotency_key") WHERE "usage_records"."idempotency_key" is not null;
@@ -0,0 +1,17 @@
1
+ CREATE TABLE "events" (
2
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3
+ "type" text NOT NULL,
4
+ "workspace_id" uuid,
5
+ "actor_user_id" uuid,
6
+ "data" jsonb NOT NULL,
7
+ "occurred_at" timestamp with time zone DEFAULT now() NOT NULL,
8
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
9
+ "idempotency_key" text
10
+ );
11
+ --> statement-breakpoint
12
+ ALTER TABLE "events" ADD CONSTRAINT "events_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
13
+ ALTER TABLE "events" ADD CONSTRAINT "events_actor_user_id_users_id_fk" FOREIGN KEY ("actor_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
14
+ CREATE INDEX "events_workspace_occurred_idx" ON "events" USING btree ("workspace_id","occurred_at" DESC NULLS LAST);--> statement-breakpoint
15
+ CREATE INDEX "events_type_occurred_idx" ON "events" USING btree ("type","occurred_at" DESC NULLS LAST);--> statement-breakpoint
16
+ CREATE INDEX "events_actor_idx" ON "events" USING btree ("actor_user_id");--> statement-breakpoint
17
+ CREATE UNIQUE INDEX "events_idempotency_idx" ON "events" USING btree ("type","idempotency_key") WHERE "events"."idempotency_key" is not null;
@@ -0,0 +1,64 @@
1
+ CREATE TABLE "audit_logs" (
2
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
3
+ "actor_user_id" uuid,
4
+ "workspace_id" uuid,
5
+ "action" text NOT NULL,
6
+ "target_type" text,
7
+ "target_id" text,
8
+ "metadata" jsonb,
9
+ "request_id" text,
10
+ "ip_address" text,
11
+ "user_agent" text,
12
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
13
+ );
14
+ --> statement-breakpoint
15
+ CREATE TABLE "notifications" (
16
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
17
+ "user_id" uuid NOT NULL,
18
+ "workspace_id" uuid,
19
+ "type" text NOT NULL,
20
+ "title" text NOT NULL,
21
+ "body" text,
22
+ "url" text,
23
+ "data" jsonb,
24
+ "read_at" timestamp with time zone,
25
+ "dedupe_key" text,
26
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
27
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
28
+ );
29
+ --> statement-breakpoint
30
+ ALTER TABLE "notifications" ADD CONSTRAINT "notifications_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
31
+ ALTER TABLE "notifications" ADD CONSTRAINT "notifications_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
32
+ CREATE INDEX "audit_logs_workspace_created_idx" ON "audit_logs" USING btree ("workspace_id","created_at" DESC NULLS LAST);--> statement-breakpoint
33
+ CREATE INDEX "audit_logs_actor_created_idx" ON "audit_logs" USING btree ("actor_user_id","created_at" DESC NULLS LAST);--> statement-breakpoint
34
+ CREATE INDEX "audit_logs_action_created_idx" ON "audit_logs" USING btree ("action","created_at" DESC NULLS LAST);--> statement-breakpoint
35
+ CREATE INDEX "audit_logs_target_idx" ON "audit_logs" USING btree ("target_type","target_id");--> statement-breakpoint
36
+ CREATE INDEX "notifications_user_unread_idx" ON "notifications" USING btree ("user_id","read_at","created_at" DESC NULLS LAST);--> statement-breakpoint
37
+ CREATE INDEX "notifications_workspace_idx" ON "notifications" USING btree ("workspace_id","created_at" DESC NULLS LAST);--> statement-breakpoint
38
+ CREATE UNIQUE INDEX "notifications_dedupe_idx" ON "notifications" USING btree ("user_id","type","dedupe_key") WHERE "notifications"."dedupe_key" is not null;
39
+
40
+ --> statement-breakpoint
41
+ -- Audit immutability, enforced by PostgreSQL rather than by convention.
42
+ --
43
+ -- The service exposes no update or delete, but a trail that depends on
44
+ -- application discipline is not a trail: a migration, a data repair, or a psql
45
+ -- session would silently rewrite history. This rejects both operations outright,
46
+ -- so amending an entry requires deliberately dropping the trigger first — which
47
+ -- is itself an auditable act.
48
+ --
49
+ -- This is also why audit_logs carries no foreign keys. `ON DELETE SET NULL` is an
50
+ -- UPDATE, so a referencing audit row would make its own subject undeletable.
51
+ CREATE OR REPLACE FUNCTION saas_core_audit_logs_immutable()
52
+ RETURNS trigger AS $$
53
+ BEGIN
54
+ RAISE EXCEPTION
55
+ 'audit_logs is append-only: % is not permitted', TG_OP
56
+ USING ERRCODE = 'restrict_violation';
57
+ END;
58
+ $$ LANGUAGE plpgsql;--> statement-breakpoint
59
+ CREATE TRIGGER audit_logs_no_update
60
+ BEFORE UPDATE ON "audit_logs"
61
+ FOR EACH ROW EXECUTE FUNCTION saas_core_audit_logs_immutable();--> statement-breakpoint
62
+ CREATE TRIGGER audit_logs_no_delete
63
+ BEFORE DELETE ON "audit_logs"
64
+ FOR EACH ROW EXECUTE FUNCTION saas_core_audit_logs_immutable();
@@ -0,0 +1,77 @@
1
+ CREATE TYPE "public"."inbound_webhook_status" AS ENUM('pending', 'processed', 'failed', 'ignored');--> statement-breakpoint
2
+ CREATE TYPE "public"."webhook_delivery_status" AS ENUM('pending', 'succeeded', 'failed', 'exhausted');--> statement-breakpoint
3
+ CREATE TABLE "api_keys" (
4
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
5
+ "workspace_id" uuid NOT NULL,
6
+ "name" text NOT NULL,
7
+ "key_id" text NOT NULL,
8
+ "key_hash" text NOT NULL,
9
+ "scopes" text[] DEFAULT '{}'::text[] NOT NULL,
10
+ "created_by_user_id" uuid,
11
+ "last_used_at" timestamp with time zone,
12
+ "expires_at" timestamp with time zone,
13
+ "revoked_at" timestamp with time zone,
14
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
15
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
16
+ CONSTRAINT "api_keys_key_id_unique" UNIQUE("key_id")
17
+ );
18
+ --> statement-breakpoint
19
+ CREATE TABLE "inbound_webhooks" (
20
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
21
+ "source" text NOT NULL,
22
+ "external_id" text NOT NULL,
23
+ "type" text NOT NULL,
24
+ "payload" jsonb NOT NULL,
25
+ "workspace_id" uuid,
26
+ "status" "inbound_webhook_status" DEFAULT 'pending' NOT NULL,
27
+ "processed_at" timestamp with time zone,
28
+ "error" text,
29
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
30
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
31
+ CONSTRAINT "inbound_webhooks_source_external_unique" UNIQUE("source","external_id")
32
+ );
33
+ --> statement-breakpoint
34
+ CREATE TABLE "webhook_deliveries" (
35
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
36
+ "endpoint_id" uuid NOT NULL,
37
+ "event_id" uuid NOT NULL,
38
+ "event_type" text NOT NULL,
39
+ "payload" jsonb NOT NULL,
40
+ "status" "webhook_delivery_status" DEFAULT 'pending' NOT NULL,
41
+ "attempt_count" integer DEFAULT 0 NOT NULL,
42
+ "next_attempt_at" timestamp with time zone,
43
+ "last_attempted_at" timestamp with time zone,
44
+ "response_status" integer,
45
+ "error" text,
46
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
47
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
48
+ CONSTRAINT "webhook_deliveries_endpoint_event_unique" UNIQUE("endpoint_id","event_id")
49
+ );
50
+ --> statement-breakpoint
51
+ CREATE TABLE "webhook_endpoints" (
52
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
53
+ "workspace_id" uuid NOT NULL,
54
+ "url" text NOT NULL,
55
+ "description" text,
56
+ "secret" text NOT NULL,
57
+ "event_types" text[] DEFAULT '{}'::text[] NOT NULL,
58
+ "is_active" boolean DEFAULT true NOT NULL,
59
+ "consecutive_failures" integer DEFAULT 0 NOT NULL,
60
+ "disabled_at" timestamp with time zone,
61
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
62
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
63
+ CONSTRAINT "webhook_endpoints_workspace_url_unique" UNIQUE("workspace_id","url")
64
+ );
65
+ --> statement-breakpoint
66
+ ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
67
+ ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_created_by_user_id_users_id_fk" FOREIGN KEY ("created_by_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
68
+ ALTER TABLE "inbound_webhooks" ADD CONSTRAINT "inbound_webhooks_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
69
+ ALTER TABLE "webhook_deliveries" ADD CONSTRAINT "webhook_deliveries_endpoint_id_webhook_endpoints_id_fk" FOREIGN KEY ("endpoint_id") REFERENCES "public"."webhook_endpoints"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
70
+ ALTER TABLE "webhook_deliveries" ADD CONSTRAINT "webhook_deliveries_event_id_events_id_fk" FOREIGN KEY ("event_id") REFERENCES "public"."events"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
71
+ ALTER TABLE "webhook_endpoints" ADD CONSTRAINT "webhook_endpoints_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
72
+ CREATE INDEX "api_keys_workspace_idx" ON "api_keys" USING btree ("workspace_id","created_at" DESC NULLS LAST);--> statement-breakpoint
73
+ CREATE INDEX "inbound_webhooks_status_idx" ON "inbound_webhooks" USING btree ("status");--> statement-breakpoint
74
+ CREATE INDEX "inbound_webhooks_source_idx" ON "inbound_webhooks" USING btree ("source","created_at" DESC NULLS LAST);--> statement-breakpoint
75
+ CREATE INDEX "webhook_deliveries_due_idx" ON "webhook_deliveries" USING btree ("status","next_attempt_at");--> statement-breakpoint
76
+ CREATE INDEX "webhook_deliveries_endpoint_idx" ON "webhook_deliveries" USING btree ("endpoint_id","created_at" DESC NULLS LAST);--> statement-breakpoint
77
+ CREATE INDEX "webhook_endpoints_workspace_idx" ON "webhook_endpoints" USING btree ("workspace_id");
@@ -0,0 +1,22 @@
1
+ CREATE TYPE "public"."feature_flag_scope" AS ENUM('global', 'application', 'workspace', 'user');--> statement-breakpoint
2
+ CREATE TABLE "feature_flags" (
3
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
4
+ "key" text NOT NULL,
5
+ "scope" "feature_flag_scope" NOT NULL,
6
+ "application" text,
7
+ "workspace_id" uuid,
8
+ "user_id" uuid,
9
+ "enabled" boolean NOT NULL,
10
+ "variant" text,
11
+ "rollout_percentage" integer,
12
+ "description" text,
13
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
14
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
15
+ CONSTRAINT "feature_flags_target_unique" UNIQUE NULLS NOT DISTINCT("key","scope","application","workspace_id","user_id")
16
+ );
17
+ --> statement-breakpoint
18
+ ALTER TABLE "feature_flags" ADD CONSTRAINT "feature_flags_workspace_id_workspaces_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
19
+ ALTER TABLE "feature_flags" ADD CONSTRAINT "feature_flags_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
20
+ CREATE INDEX "feature_flags_key_scope_idx" ON "feature_flags" USING btree ("key","scope");--> statement-breakpoint
21
+ CREATE INDEX "feature_flags_workspace_idx" ON "feature_flags" USING btree ("workspace_id");--> statement-breakpoint
22
+ CREATE INDEX "feature_flags_user_idx" ON "feature_flags" USING btree ("user_id");