@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
package/docs/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @digimuza/saas-core — module reference
2
+
3
+ Per-module guides. Each covers what the module is for, every exported function
4
+ with its signature, and the reasoning behind the design decisions that are not
5
+ obvious from the types.
6
+
7
+ The [root README](../README.md) is the architectural overview; start there if you
8
+ have not read it. Each module also carries a shorter, task-oriented README beside
9
+ its code — see [`src/README.md`](../src/README.md) for that index. The split is
10
+ deliberate: the colocated README orients you, these guides are the reference, and
11
+ neither repeats the other.
12
+
13
+ ## Modules
14
+
15
+ | Module | Import from | Guide |
16
+ | --- | --- | --- |
17
+ | Configuration | `@digimuza/saas-core/config` | [config.md](./config.md) |
18
+ | Database & schema | `@digimuza/saas-core/db`, `/db/schema` | [db.md](./db.md) |
19
+ | Authentication | `@digimuza/saas-core/auth` | [auth.md](./auth.md) |
20
+ | Users & profiles | `@digimuza/saas-core/users` | [users.md](./users.md) |
21
+ | Workspaces & members | `@digimuza/saas-core/workspaces` | [workspaces.md](./workspaces.md) |
22
+ | Roles & permissions | `@digimuza/saas-core/authorization` | [authorization.md](./authorization.md) |
23
+ | Billing | `@digimuza/saas-core/billing` | [billing.md](./billing.md) |
24
+ | Entitlements | `@digimuza/saas-core/entitlements` | [entitlements.md](./entitlements.md) |
25
+ | Events | `@digimuza/saas-core/events` | [events.md](./events.md) |
26
+ | Background jobs | `@digimuza/saas-core/jobs` | [jobs.md](./jobs.md) |
27
+ | Realtime | `@digimuza/saas-core/realtime` | [realtime.md](./realtime.md) |
28
+ | Streams (RxJS) | `@digimuza/saas-core/streams` | [streams.md](./streams.md) |
29
+ | Email | `@digimuza/saas-core/email` | [email.md](./email.md) |
30
+ | Notifications | `@digimuza/saas-core/notifications` | [notifications.md](./notifications.md) |
31
+ | Audit | `@digimuza/saas-core/audit` | [audit.md](./audit.md) |
32
+ | API keys | `@digimuza/saas-core/api-keys` | [api-keys.md](./api-keys.md) |
33
+ | Webhooks | `@digimuza/saas-core/webhooks` | [webhooks.md](./webhooks.md) |
34
+ | Feature flags | `@digimuza/saas-core/feature-flags` | [feature-flags.md](./feature-flags.md) |
35
+ | Storage | `@digimuza/saas-core/storage` | [storage.md](./storage.md) |
36
+ | Files | `@digimuza/saas-core/files` | [files.md](./files.md) |
37
+ | Logger | `@digimuza/saas-core/logger` | [logger.md](./logger.md) |
38
+ | CLI | the `saas-core` binary | [cli.md](./cli.md) |
39
+ | Publishing | release workflow | [publishing.md](./publishing.md) |
40
+ | Errors | `@digimuza/saas-core` or `/errors` | [errors.md](./errors.md) |
41
+
42
+ ## Two conventions that apply everywhere
43
+
44
+ **Every module exports both a factory and a default instance.**
45
+ `createWorkspacesService({ db, events })` builds one explicitly — use it in tests
46
+ and when you need different dependencies. The default (`workspaces`) is wired to
47
+ the configuration-driven database handle and the core event registry, and its
48
+ dependencies resolve on *first use*, not at import. So importing a module never
49
+ requires configuration you have not set up.
50
+
51
+ **Tenant-scoped operations take a `WorkspaceContext`, never a raw id.**
52
+ A context can only come from a verified membership lookup
53
+ (`workspaces.requireContext`). Passing a `workspaceId` from a request body where a
54
+ context is expected will not type-check. See
55
+ [workspaces.md](./workspaces.md#workspacecontext).
56
+
57
+ ## Reading order for a new application
58
+
59
+ 1. [config.md](./config.md) — get configuration flowing from Infisical
60
+ 2. [db.md](./db.md) — run the core migrations, reference core tables from yours
61
+ 3. [auth.md](./auth.md) — sign-in, callback, session
62
+ 4. [workspaces.md](./workspaces.md) — tenancy, and how a request becomes a context
63
+ 5. [authorization.md](./authorization.md) — register permissions, gate actions
64
+ 6. [billing.md](./billing.md) + [entitlements.md](./entitlements.md) — charge, then gate on what was paid for
65
+ 7. [events.md](./events.md) — declare your own events
66
+ 8. [jobs.md](./jobs.md) — move slow work off the request
67
+ 9. [realtime.md](./realtime.md) — push what happened to the browser
68
+ 10. [streams.md](./streams.md) — react to it, in the server or the browser
69
+ 11. [notifications.md](./notifications.md) + [email.md](./email.md) — tell the user
70
+ 12. [audit.md](./audit.md) — keep an immutable record of what mattered
71
+ 13. [api-keys.md](./api-keys.md) + [webhooks.md](./webhooks.md) — let other systems in and out
@@ -0,0 +1,184 @@
1
+ # API keys
2
+
3
+ ```ts
4
+ import { apiKeys, generateApiKey, parseApiKey } from "@digimuza/saas-core/api-keys";
5
+ ```
6
+
7
+ > Orientation and recipes: [`src/api-keys/README.md`](../src/api-keys/README.md).
8
+
9
+ Workspace-scoped machine credentials. A key is **not a user**: it holds *scopes*,
10
+ not roles, so authorization for a key is a scope check rather than a permission
11
+ check.
12
+
13
+ ---
14
+
15
+ ## The key format
16
+
17
+ ```
18
+ dgk_<32 hex>_<64 hex>
19
+ │ │ └── secret — 256 bits, SHA-256 hashed, never stored
20
+ │ └─────────── keyId — 128 bits, stored plaintext and indexed
21
+ └──────────────────── prefix — identifies the key as ours
22
+ ```
23
+
24
+ **Why the split.** Hashing the whole key would mean either scanning every row's
25
+ hash on every request, or storing a searchable copy — the first is O(n) per API
26
+ call, the second defeats the point of hashing. A plaintext, indexed `keyId` makes
27
+ validation one lookup.
28
+
29
+ **Why hex and not base64url.** base64url includes `_`, which collides with the `_`
30
+ delimiter and makes the token ambiguous to split. That fails only for the fraction
31
+ of keys whose random bytes happen to encode one — the worst kind of bug. A test
32
+ generates 200 keys and asserts three parts every time.
33
+
34
+ **Why SHA-256 and not bcrypt.** The secret is 256 bits of CSPRNG output, so there
35
+ is no dictionary to attack. bcrypt and Argon2 exist to make *low-entropy human
36
+ passwords* expensive to guess; applying one here would add tens of milliseconds to
37
+ every authenticated request and buy nothing.
38
+
39
+ ---
40
+
41
+ ## `apiKeys.create(context, input)`
42
+
43
+ ```ts
44
+ function create(context: WorkspaceContext, input: {
45
+ name: string; // max 120 chars
46
+ scopes?: readonly string[]; // deduplicated, max 64
47
+ expiresAt?: Date | null; // null never expires
48
+ prefix?: string; // 2-16 lowercase alphanumerics
49
+ }): Promise<{ key: ApiKeySummary; token: string }>
50
+ ```
51
+
52
+ ```ts
53
+ const { key, token } = await apiKeys.create(context, {
54
+ name: "CI deploy",
55
+ scopes: ["projects.read", "projects.deploy"],
56
+ });
57
+ showOnce(token);
58
+ ```
59
+
60
+ **`token` exists exactly once.** Only the hash of its secret half is stored, so
61
+ there is no way to recover it. Losing it means revoking and issuing a new one.
62
+
63
+ `ApiKeySummary` never contains a secret or a hash. `masked` is `dgk_a1b2c3…` —
64
+ enough to match a log line to a row.
65
+
66
+ ## `apiKeys.validate(token)` / `tryValidate(token)`
67
+
68
+ ```ts
69
+ function validate(token: string): Promise<ApiKeyPrincipal>
70
+ function tryValidate(token: string): Promise<ApiKeyPrincipal | undefined>
71
+
72
+ interface ApiKeyPrincipal {
73
+ apiKeyId: string;
74
+ keyId: string;
75
+ workspaceId: string;
76
+ name: string;
77
+ scopes: readonly string[];
78
+ createdByUserId: string | null;
79
+ }
80
+ ```
81
+
82
+ Note what is absent: there is no user. The workspace comes from the key's own row,
83
+ never from the request.
84
+
85
+ **Every failure produces the same error.** Malformed, unknown, revoked and expired
86
+ all raise `UnauthorizedError("Invalid API key.")`, so a caller cannot probe. There
87
+ is a test asserting all three yield one distinct message.
88
+
89
+ Comparison is constant-time — a `===` on hex digests leaks, through timing, how
90
+ many leading characters matched.
91
+
92
+ ### `last_used_at`
93
+
94
+ Recorded after the key is known good, and only when the stored value is at least a
95
+ minute stale. Updating on every request would turn validation into a write path;
96
+ the update is also guarded so two concurrent requests do not both write.
97
+
98
+ Configure with `lastUsedThrottleMs`.
99
+
100
+ ## `apiKeys.resolveContext(token)`
101
+
102
+ ```ts
103
+ function resolveContext(token: string): Promise<{
104
+ context: WorkspaceContext;
105
+ principal: ApiKeyPrincipal;
106
+ }>
107
+ ```
108
+
109
+ Validates, then confirms the key's creator is **still a member** of the workspace,
110
+ and returns a context usable with the rest of the core.
111
+
112
+ The membership re-check is the point. Without it a key would keep working after the
113
+ person who created it lost access — the classic way a machine credential outlives
114
+ the authority it was granted under.
115
+
116
+ ```ts
117
+ // After the creator is removed from the workspace:
118
+ await apiKeys.validate(token); // still fine — scopes still apply
119
+ await apiKeys.resolveContext(token); // UnauthorizedError
120
+ ```
121
+
122
+ So a scope-only integration keeps working, and only the `WorkspaceContext` is
123
+ refused. Re-issue the key under a current member.
124
+
125
+ For a machine-to-machine integration with genuinely no user behind it, there is no
126
+ membership to verify — `unsafeContextFromPrincipal(principal)` is the deliberate
127
+ escape hatch, named so it cannot be used by accident.
128
+
129
+ ## `apiKeys.list(context, options?)`
130
+
131
+ ```ts
132
+ function list(context: WorkspaceContext, options?: { includeRevoked?: boolean }):
133
+ Promise<ApiKeySummary[]>
134
+ ```
135
+
136
+ Revoked keys are hidden by default.
137
+
138
+ ## `apiKeys.revoke(context, apiKeyId)`
139
+
140
+ Immediate and idempotent. Scoped to the caller's workspace, so another tenant's key
141
+ id raises `NotFoundError` rather than being revocable.
142
+
143
+ ## Scopes
144
+
145
+ ```ts
146
+ function requireScope(principal: ApiKeyPrincipal, scope: string): void // throws ForbiddenError
147
+ function hasScopes(principal: ApiKeyPrincipal, scopes: readonly string[]): boolean
148
+ ```
149
+
150
+ `ForbiddenError.permission` carries the missing scope.
151
+
152
+ **Scopes are not permissions.** Permissions belong to users through roles; scopes
153
+ belong to keys. See [authorization.md](./authorization.md) for the user side — and
154
+ note that both may apply to a request, depending on how you authenticate it.
155
+
156
+ ---
157
+
158
+ ## Low-level helpers
159
+
160
+ ```ts
161
+ generateApiKey(prefix?): GeneratedApiKey // { token, prefix, keyId, secret, keyHash }
162
+ parseApiKey(token): ParsedApiKey | undefined // undefined, never an explanation
163
+ hashApiKeySecret(secret): string
164
+ secretMatchesHash(secret, keyHash): boolean // constant-time
165
+ maskApiKey(prefix, keyId): string
166
+ ```
167
+
168
+ `parseApiKey` returns `undefined` rather than throwing, and never says why —
169
+ distinguishing "wrong shape" from "unknown key" from "revoked" would let an
170
+ attacker probe. It also bounds the input length before doing any work, so an
171
+ oversized string costs nothing.
172
+
173
+ ---
174
+
175
+ ## Testing
176
+
177
+ ```ts
178
+ createApiKeysService({
179
+ db,
180
+ workspaces, // omit and resolveContext throws an explanation
181
+ now: () => fixedClock,
182
+ lastUsedThrottleMs: 60_000,
183
+ });
184
+ ```
package/docs/audit.md ADDED
@@ -0,0 +1,198 @@
1
+ # Audit
2
+
3
+ ```ts
4
+ import { audit, auditFromEvent, createAuditService } from "@digimuza/saas-core/audit";
5
+ ```
6
+
7
+ > Orientation and recipes: [`src/audit/README.md`](../src/audit/README.md).
8
+
9
+ A durable, immutable record of consequential actions — who did what, to what, when.
10
+ For a human reading it after the fact.
11
+
12
+ ---
13
+
14
+ ## Audit is not events
15
+
16
+ | | [events](./events.md) | audit |
17
+ | --- | --- | --- |
18
+ | Audience | the application | a person, later |
19
+ | Volume | everything | consequential actions only |
20
+ | Immutability | by convention | by **database trigger** |
21
+ | Delivery | may fan out to handlers | synchronous write, never queued |
22
+
23
+ Recording every event would bury what matters among the routine.
24
+
25
+ ---
26
+
27
+ ## `audit.log(input, tx?)`
28
+
29
+ ```ts
30
+ function log(input: {
31
+ action: string; // "member.role.updated" — past tense, dotted
32
+ actorId?: string | null; // null for the system
33
+ workspaceId?: string | null; // null for platform-level
34
+ targetType?: string;
35
+ targetId?: string;
36
+ metadata?: Record<string, unknown>;
37
+ request?: { requestId?: string; ipAddress?: string; userAgent?: string };
38
+ occurredAt?: Date;
39
+ }, tx?: CoreDatabase): Promise<AuditLogEntry>
40
+ ```
41
+
42
+ Durable the moment it returns — written straight to PostgreSQL, never through
43
+ realtime, an in-process handler, or a queue.
44
+
45
+ ```ts
46
+ await audit.log({
47
+ action: "member.role.updated",
48
+ actorId: user.id,
49
+ workspaceId: context.workspaceId,
50
+ targetType: "user",
51
+ targetId: member.id,
52
+ metadata: { from: "member", to: "admin" },
53
+ request: { requestId, ipAddress, userAgent },
54
+ });
55
+ ```
56
+
57
+ Pass `tx` to make the entry and the change it describes commit together:
58
+
59
+ ```ts
60
+ await db.transaction(async (tx) => {
61
+ await assignRole(tx, member.id, roleId);
62
+ await audit.log({ action: "member.role.updated", /* ... */ }, tx);
63
+ });
64
+ ```
65
+
66
+ Both directions are tested: the entry commits with the change, and rolls back with
67
+ a failure.
68
+
69
+ `action` is capped at 120 characters so one oversized string cannot make a page of
70
+ the trail unreadable.
71
+
72
+ ## `audit.logMany(inputs, tx?)`
73
+
74
+ ```ts
75
+ function logMany(inputs: readonly AuditEntryInput[], tx?: CoreDatabase): Promise<AuditLogEntry[]>
76
+ ```
77
+
78
+ One statement for several entries.
79
+
80
+ ## `audit.list(context, options?)`
81
+
82
+ ```ts
83
+ function list(context: WorkspaceContext, options?: {
84
+ actions?: readonly string[];
85
+ actorId?: string;
86
+ targetType?: string;
87
+ targetId?: string;
88
+ since?: Date;
89
+ before?: Date; // keyset pagination
90
+ limit?: number; // default 50, capped at 500
91
+ }): Promise<AuditLogEntry[]>
92
+ ```
93
+
94
+ Requires a verified `WorkspaceContext` — a trail is tenant data. Newest first,
95
+ tie-broken on id so keyset pagination cannot loop on equal timestamps.
96
+
97
+ ## `audit.listPlatform(options?)`
98
+
99
+ Entries with no workspace: account-level actions, and anything belonging to a
100
+ workspace that has since been deleted.
101
+
102
+ **Not tenant-scoped.** Gate it on an operator-level permission of your own — the
103
+ core cannot know who is allowed to read it.
104
+
105
+ ---
106
+
107
+ ## Immutability, enforced by PostgreSQL
108
+
109
+ There is no update or delete API, and a trigger rejects both operations:
110
+
111
+ ```sql
112
+ CREATE TRIGGER audit_logs_no_update
113
+ BEFORE UPDATE ON "audit_logs"
114
+ FOR EACH ROW EXECUTE FUNCTION saas_core_audit_logs_immutable();
115
+ ```
116
+
117
+ So a migration, a data repair, or a stray `psql` session cannot quietly rewrite
118
+ history either. Amending an entry means deliberately dropping the trigger — which
119
+ is itself an auditable act.
120
+
121
+ ```ts
122
+ await expectDatabaseError(
123
+ db.update(auditLogs).set({ action: "nothing.happened" }).where(eq(auditLogs.id, id)),
124
+ /append-only/,
125
+ );
126
+ ```
127
+
128
+ Both operations are tested against real PostgreSQL.
129
+
130
+ ---
131
+
132
+ ## Why `audit_logs` has no foreign keys
133
+
134
+ `actor_user_id` and `workspace_id` are plain `uuid` columns. Two reasons, and they
135
+ reinforce each other:
136
+
137
+ 1. **An entry must outlive what it describes.** `ON DELETE CASCADE` would erase the
138
+ trail exactly when it matters most. `SET NULL` would keep the row and destroy
139
+ its value.
140
+ 2. **`SET NULL` is an `UPDATE`,** which the immutability trigger rejects — so a
141
+ referencing audit row would make its own subject *undeletable*. That is a real
142
+ conflict, found by a test: deleting a user with audit entries failed outright.
143
+
144
+ They are historical identifiers, not live references. Join explicitly when
145
+ displaying a trail, and expect misses:
146
+
147
+ ```ts
148
+ const rows = await db
149
+ .select({ entry: auditLogs, actor: userProfiles })
150
+ .from(auditLogs)
151
+ .leftJoin(userProfiles, eq(userProfiles.userId, auditLogs.actorUserId))
152
+ .where(eq(auditLogs.workspaceId, context.workspaceId));
153
+ ```
154
+
155
+ There is deliberately no Drizzle `relations()` for `audit_logs` — declaring one
156
+ would imply the actor is always resolvable.
157
+
158
+ ---
159
+
160
+ ## `auditFromEvent(event, mapping)`
161
+
162
+ ```ts
163
+ function auditFromEvent(
164
+ event: SaasEvent,
165
+ mapping: Record<string, (event: never) => Omit<AuditEntryInput, "actorId" | "workspaceId">>,
166
+ ): AuditEntryInput | undefined
167
+ ```
168
+
169
+ Translate selected events into audit entries. Returns `undefined` for anything
170
+ unmapped, so the mapping is explicit about what is worth keeping.
171
+
172
+ ```ts
173
+ events.onAny(async (event) => {
174
+ const entry = auditFromEvent(event, {
175
+ "workspace.member.removed": (e) => ({
176
+ action: "member.removed",
177
+ targetType: "user",
178
+ targetId: e.data.userId,
179
+ }),
180
+ });
181
+ if (entry !== undefined) await audit.log(entry);
182
+ });
183
+ ```
184
+
185
+ `actorId`, `workspaceId` and `occurredAt` come from the event.
186
+
187
+ **But note the shape.** This writes audit from a *handler*, and a handler is not
188
+ durable — a crash between the commit and the handler loses the entry. Convenient
189
+ for the routine cases; for anything that must never be missed, call `audit.log` in
190
+ the same transaction as the change.
191
+
192
+ ---
193
+
194
+ ## Custom construction
195
+
196
+ ```ts
197
+ createAuditService({ db, now: () => fixedClock });
198
+ ```
package/docs/auth.md ADDED
@@ -0,0 +1,196 @@
1
+ # Authentication
2
+
3
+ ```ts
4
+ import {
5
+ createConfiguredAuthService,
6
+ createCookieStore,
7
+ } from "@digimuza/saas-core/auth";
8
+ ```
9
+
10
+ > Orientation and recipes: [`src/auth/README.md`](../src/auth/README.md).
11
+
12
+
13
+ WorkOS owns identity — OAuth, SAML, OIDC, passkeys, MFA and session sealing. The
14
+ core keeps its details behind a small API so application code never imports the
15
+ WorkOS SDK; a test enforces that.
16
+
17
+ ---
18
+
19
+ ## Setup
20
+
21
+ ```ts
22
+ // lib/auth.ts
23
+ import { cookies } from "next/headers";
24
+ import { createConfiguredAuthService, createCookieStore } from "@digimuza/saas-core/auth";
25
+
26
+ export const auth = createConfiguredAuthService(createCookieStore(cookies));
27
+ ```
28
+
29
+ Pass the accessor `cookies`, **not** `cookies()`. Next resolves the jar per
30
+ request, so calling it once at module scope would bind every request to the first
31
+ one.
32
+
33
+ ### Why you have to wire this yourself
34
+
35
+ The core does not import `next/headers`. Next has no `exports` map, so that
36
+ specifier only resolves through a bundler — importing it would make the package
37
+ unloadable in plain Node, where migration scripts, Trigger.dev tasks and Vitest
38
+ run. Three lines in your app keeps the core framework-agnostic.
39
+
40
+ For any other runtime, implement `CookieStore` (three methods) or use
41
+ `createMemoryCookieStore()`.
42
+
43
+ ---
44
+
45
+ ## `auth.getAuthorizationUrl(options?)`
46
+
47
+ ```ts
48
+ function getAuthorizationUrl(options?: {
49
+ redirectUri?: string;
50
+ state?: string;
51
+ screenHint?: "sign-up" | "sign-in";
52
+ loginHint?: string;
53
+ organizationId?: string;
54
+ connectionId?: string;
55
+ invitationToken?: string;
56
+ }): Promise<{ url: string; state: string }>
57
+ ```
58
+
59
+ Builds the WorkOS AuthKit URL **and stores a single-use CSRF state cookie** —
60
+ which is why it is async.
61
+
62
+ ```ts
63
+ // app/login/route.ts
64
+ const { url } = await auth.getAuthorizationUrl({ screenHint: "sign-in" });
65
+ redirect(url);
66
+ ```
67
+
68
+ `provider: "authkit"` is sent only when neither `organizationId` nor
69
+ `connectionId` is given: WorkOS expects exactly one selector, and sending both
70
+ conflicts with the more specific choice.
71
+
72
+ ## `auth.handleCallback(input)`
73
+
74
+ ```ts
75
+ function handleCallback(input: { code: string; state?: string }): Promise<CallbackResult>
76
+ ```
77
+
78
+ ```ts
79
+ // app/callback/route.ts
80
+ const { session } = await auth.handleCallback({
81
+ code: searchParams.get("code")!,
82
+ state: searchParams.get("state")!,
83
+ });
84
+ ```
85
+
86
+ In order: verifies `state` against the cookie in constant time, consumes the
87
+ cookie, exchanges the code, seals the session into an httpOnly cookie, and syncs
88
+ the identity into `users` / `user_profiles`.
89
+
90
+ **The state check is not optional ceremony.** Without it an attacker can feed a
91
+ victim's browser a callback URL bearing the attacker's `code` and sign them into
92
+ the attacker's account. The code is not exchanged unless the state matches —
93
+ tested.
94
+
95
+ Throws `UnauthorizedError` for a missing, unknown or mismatched state.
96
+
97
+ ## `auth.getSession()`
98
+
99
+ ```ts
100
+ function getSession(): Promise<AuthSession | undefined>
101
+ ```
102
+
103
+ ```ts
104
+ interface AuthSession {
105
+ userId: string; // local id — key off this
106
+ workosUserId: string; // identity-provider id
107
+ sessionId: string;
108
+ email: string;
109
+ organizationId?: string;
110
+ impersonatorEmail?: string;
111
+ }
112
+ ```
113
+
114
+ Reads the cookie, unseals, and validates. An expired access token is refreshed
115
+ transparently — that is the normal case, not an error, since the sealed session
116
+ carries a refresh token. Any other failure reason (tampered or absent cookie) is
117
+ treated as unauthenticated, and no refresh is attempted.
118
+
119
+ Also provisions the local user if this database has not seen the WorkOS identity
120
+ before — true after a restore, or on the first sign-in following a deploy.
121
+
122
+ ### Refresh and Server Components
123
+
124
+ Next.js Server Components cannot write cookies, and attempting it throws. The
125
+ refresh already succeeded at WorkOS, so the request *is* authenticated — the write
126
+ failure is tolerated and the session returned. If you want the rotated token to
127
+ persist, refresh in middleware, where writing cookies is allowed.
128
+
129
+ ## `auth.requireSession()` / `auth.requireUser()`
130
+
131
+ ```ts
132
+ function requireSession(): Promise<AuthSession>
133
+ function requireUser(): Promise<UserWithProfile>
134
+ ```
135
+
136
+ Throw `UnauthorizedError` when unauthenticated. Use these in route handlers and
137
+ Server Actions; use the `get*` variants when unauthenticated is a valid state.
138
+
139
+ ## `auth.getCurrentUser()`
140
+
141
+ ```ts
142
+ function getCurrentUser(): Promise<UserWithProfile | undefined>
143
+ ```
144
+
145
+ The session's user, with their profile. See [users.md](./users.md).
146
+
147
+ ## `auth.signOut(options?)`
148
+
149
+ ```ts
150
+ function signOut(options?: { returnTo?: string }): Promise<{ logoutUrl: string | undefined }>
151
+ ```
152
+
153
+ ```ts
154
+ const { logoutUrl } = await auth.signOut();
155
+ redirect(logoutUrl ?? "/");
156
+ ```
157
+
158
+ Clears the cookie **before** contacting WorkOS, so a provider outage cannot leave
159
+ a user signed in locally. You must redirect to `logoutUrl` — clearing the cookie
160
+ alone leaves the WorkOS session alive, so the next sign-in would silently succeed
161
+ without re-authenticating.
162
+
163
+ Returns `undefined` when there was no session, or when the sealed session could
164
+ not be read (nothing left to log out).
165
+
166
+ ---
167
+
168
+ ## Cookie attributes
169
+
170
+ `httpOnly`, `Secure` outside development, `SameSite=Lax`, `path=/`, 30-day
171
+ lifetime.
172
+
173
+ `Lax` and not `Strict`: the identity provider returns the user via a top-level GET
174
+ navigation, and `Strict` would withhold the cookie on that request and break every
175
+ sign-in. `Secure` follows `APP_ENV` — requiring it in local development over plain
176
+ HTTP would also break sign-in.
177
+
178
+ ---
179
+
180
+ ## Custom construction
181
+
182
+ ```ts
183
+ import { createAuthService } from "@digimuza/saas-core/auth";
184
+
185
+ const auth = createAuthService({
186
+ workos, cookies, users,
187
+ clientId, cookiePassword, redirectUri,
188
+ secureCookies: true,
189
+ sessionCookieName: "my_session", // default "dg_session"
190
+ stateCookieName: "my_state", // default "dg_oauth_state"
191
+ sessionMaxAgeSeconds: 60 * 60 * 24 * 7,
192
+ });
193
+ ```
194
+
195
+ `getWorkos()` returns the configured, `globalThis`-cached WorkOS client if you need
196
+ a capability the core does not wrap.