@bajustone/fortress 1.0.0-rc.1

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 (465) hide show
  1. package/CHANGELOG.md +1011 -0
  2. package/LICENSE +21 -0
  3. package/README.md +760 -0
  4. package/SECURITY.md +197 -0
  5. package/bin/fortress.ts +667 -0
  6. package/dist/auth-service-BcyQ2PuZ.d.cts +307 -0
  7. package/dist/auth-service-BkzZkWfH.d.ts +307 -0
  8. package/dist/config-B2366s_G.d.ts +665 -0
  9. package/dist/config-GtlVt6ZD.d.cts +665 -0
  10. package/dist/convert-routes-BLCQT57f.d.ts +72 -0
  11. package/dist/convert-routes-BdMvP2_X.d.cts +72 -0
  12. package/dist/crypto.cjs +61 -0
  13. package/dist/crypto.d.cts +36 -0
  14. package/dist/crypto.d.ts +36 -0
  15. package/dist/crypto.js +35 -0
  16. package/dist/drift-BT1wtMDJ.d.cts +22 -0
  17. package/dist/drift-k9LiYpRP.d.ts +22 -0
  18. package/dist/drizzle/pg.cjs +481 -0
  19. package/dist/drizzle/pg.d.cts +15 -0
  20. package/dist/drizzle/pg.d.ts +15 -0
  21. package/dist/drizzle/pg.js +454 -0
  22. package/dist/drizzle.cjs +1442 -0
  23. package/dist/drizzle.d.cts +85 -0
  24. package/dist/drizzle.d.ts +85 -0
  25. package/dist/drizzle.js +1411 -0
  26. package/dist/express.cjs +1357 -0
  27. package/dist/express.d.cts +333 -0
  28. package/dist/express.d.ts +333 -0
  29. package/dist/express.js +1314 -0
  30. package/dist/fetcher.cjs +77 -0
  31. package/dist/fetcher.d.cts +7 -0
  32. package/dist/fetcher.d.ts +7 -0
  33. package/dist/fetcher.js +41 -0
  34. package/dist/fortress-BmSvFfqK.d.cts +1089 -0
  35. package/dist/fortress-uA-_cheR.d.ts +1089 -0
  36. package/dist/hono.cjs +1530 -0
  37. package/dist/hono.d.cts +514 -0
  38. package/dist/hono.d.ts +514 -0
  39. package/dist/hono.js +1483 -0
  40. package/dist/index-CxEkfCA0.d.cts +77 -0
  41. package/dist/index-CxEkfCA0.d.ts +77 -0
  42. package/dist/index-D054pcb-.d.cts +146 -0
  43. package/dist/index-jAMbbUAY.d.ts +146 -0
  44. package/dist/index.cjs +9046 -0
  45. package/dist/index.d.cts +717 -0
  46. package/dist/index.d.ts +717 -0
  47. package/dist/index.js +8935 -0
  48. package/dist/jwt.cjs +255 -0
  49. package/dist/jwt.d.cts +90 -0
  50. package/dist/jwt.d.ts +90 -0
  51. package/dist/jwt.js +227 -0
  52. package/dist/otel.cjs +108 -0
  53. package/dist/otel.d.cts +62 -0
  54. package/dist/otel.d.ts +62 -0
  55. package/dist/otel.js +73 -0
  56. package/dist/plugins/account-lockout.cjs +322 -0
  57. package/dist/plugins/account-lockout.d.cts +42 -0
  58. package/dist/plugins/account-lockout.d.ts +42 -0
  59. package/dist/plugins/account-lockout.js +295 -0
  60. package/dist/plugins/admin.cjs +2378 -0
  61. package/dist/plugins/admin.d.cts +72 -0
  62. package/dist/plugins/admin.d.ts +72 -0
  63. package/dist/plugins/admin.js +2351 -0
  64. package/dist/plugins/api-key.cjs +792 -0
  65. package/dist/plugins/api-key.d.cts +121 -0
  66. package/dist/plugins/api-key.d.ts +121 -0
  67. package/dist/plugins/api-key.js +765 -0
  68. package/dist/plugins/audit-log.cjs +493 -0
  69. package/dist/plugins/audit-log.d.cts +86 -0
  70. package/dist/plugins/audit-log.d.ts +86 -0
  71. package/dist/plugins/audit-log.js +468 -0
  72. package/dist/plugins/data-isolation.cjs +211 -0
  73. package/dist/plugins/data-isolation.d.cts +49 -0
  74. package/dist/plugins/data-isolation.d.ts +49 -0
  75. package/dist/plugins/data-isolation.js +186 -0
  76. package/dist/plugins/email-verification.cjs +273 -0
  77. package/dist/plugins/email-verification.d.cts +44 -0
  78. package/dist/plugins/email-verification.d.ts +44 -0
  79. package/dist/plugins/email-verification.js +246 -0
  80. package/dist/plugins/magic-link.cjs +231 -0
  81. package/dist/plugins/magic-link.d.cts +39 -0
  82. package/dist/plugins/magic-link.d.ts +39 -0
  83. package/dist/plugins/magic-link.js +204 -0
  84. package/dist/plugins/oauth.cjs +1696 -0
  85. package/dist/plugins/oauth.d.cts +406 -0
  86. package/dist/plugins/oauth.d.ts +406 -0
  87. package/dist/plugins/oauth.js +1669 -0
  88. package/dist/plugins/openapi.cjs +1185 -0
  89. package/dist/plugins/openapi.d.cts +6 -0
  90. package/dist/plugins/openapi.d.ts +6 -0
  91. package/dist/plugins/openapi.js +1158 -0
  92. package/dist/plugins/rate-limit/express.cjs +55 -0
  93. package/dist/plugins/rate-limit/express.d.cts +64 -0
  94. package/dist/plugins/rate-limit/express.d.ts +64 -0
  95. package/dist/plugins/rate-limit/express.js +30 -0
  96. package/dist/plugins/rate-limit/hono.cjs +51 -0
  97. package/dist/plugins/rate-limit/hono.d.cts +59 -0
  98. package/dist/plugins/rate-limit/hono.d.ts +59 -0
  99. package/dist/plugins/rate-limit/hono.js +26 -0
  100. package/dist/plugins/rate-limit/sveltekit.cjs +49 -0
  101. package/dist/plugins/rate-limit/sveltekit.d.cts +59 -0
  102. package/dist/plugins/rate-limit/sveltekit.d.ts +59 -0
  103. package/dist/plugins/rate-limit/sveltekit.js +24 -0
  104. package/dist/plugins/rate-limit.cjs +354 -0
  105. package/dist/plugins/rate-limit.d.cts +140 -0
  106. package/dist/plugins/rate-limit.d.ts +140 -0
  107. package/dist/plugins/rate-limit.js +325 -0
  108. package/dist/plugins/social-login.cjs +905 -0
  109. package/dist/plugins/social-login.d.cts +114 -0
  110. package/dist/plugins/social-login.d.ts +114 -0
  111. package/dist/plugins/social-login.js +880 -0
  112. package/dist/plugins/tenancy.cjs +780 -0
  113. package/dist/plugins/tenancy.d.cts +108 -0
  114. package/dist/plugins/tenancy.d.ts +108 -0
  115. package/dist/plugins/tenancy.js +753 -0
  116. package/dist/plugins/two-factor.cjs +555 -0
  117. package/dist/plugins/two-factor.d.cts +67 -0
  118. package/dist/plugins/two-factor.d.ts +67 -0
  119. package/dist/plugins/two-factor.js +526 -0
  120. package/dist/plugins/webauthn.cjs +786 -0
  121. package/dist/plugins/webauthn.d.cts +72 -0
  122. package/dist/plugins/webauthn.d.ts +72 -0
  123. package/dist/plugins/webauthn.js +766 -0
  124. package/dist/plugins/webhook.cjs +819 -0
  125. package/dist/plugins/webhook.d.cts +254 -0
  126. package/dist/plugins/webhook.d.ts +254 -0
  127. package/dist/plugins/webhook.js +789 -0
  128. package/dist/protect-D1v_0upK.d.cts +123 -0
  129. package/dist/protect-DsxV_-dJ.d.ts +123 -0
  130. package/dist/sveltekit.cjs +1258 -0
  131. package/dist/sveltekit.d.cts +420 -0
  132. package/dist/sveltekit.d.ts +420 -0
  133. package/dist/sveltekit.js +1207 -0
  134. package/dist/testing.cjs +4294 -0
  135. package/dist/testing.d.cts +197 -0
  136. package/dist/testing.d.ts +197 -0
  137. package/dist/testing.js +4264 -0
  138. package/dist/types-et0R8tsC.d.cts +217 -0
  139. package/dist/types-et0R8tsC.d.ts +217 -0
  140. package/docs/adapter-typed-helpers.md +192 -0
  141. package/docs/admin-recipes.md +227 -0
  142. package/docs/architecture.md +434 -0
  143. package/docs/ci/github-actions.yml +59 -0
  144. package/docs/ci.md +109 -0
  145. package/docs/compatibility.md +115 -0
  146. package/docs/deployment.md +305 -0
  147. package/docs/hardening.md +118 -0
  148. package/docs/host-owned-routes.md +154 -0
  149. package/docs/migrations/0001-schema-version.md +54 -0
  150. package/docs/migrations/0002-initial-schema.md +84 -0
  151. package/docs/migrations/upgrade-guide.md +160 -0
  152. package/docs/observability.md +394 -0
  153. package/docs/plugins/account-lockout.md +131 -0
  154. package/docs/plugins/admin.md +402 -0
  155. package/docs/plugins/api-key.md +327 -0
  156. package/docs/plugins/audit-log.md +261 -0
  157. package/docs/plugins/data-isolation.md +186 -0
  158. package/docs/plugins/email-verification.md +121 -0
  159. package/docs/plugins/magic-link.md +123 -0
  160. package/docs/plugins/oauth.md +544 -0
  161. package/docs/plugins/openapi.md +250 -0
  162. package/docs/plugins/rate-limit.md +223 -0
  163. package/docs/plugins/social-login.md +337 -0
  164. package/docs/plugins/tenancy.md +122 -0
  165. package/docs/plugins/two-factor.md +191 -0
  166. package/docs/plugins/webauthn.md +188 -0
  167. package/docs/plugins/webhook.md +242 -0
  168. package/docs/policy-as-code.md +156 -0
  169. package/docs/route-manifest.md +46 -0
  170. package/docs/security.md +261 -0
  171. package/docs/threat-model.md +161 -0
  172. package/examples/README.md +119 -0
  173. package/examples/express-app/index.ts +747 -0
  174. package/examples/hono-app/docker-compose.yml +14 -0
  175. package/examples/hono-app/index.ts +1009 -0
  176. package/examples/policy/fortress.policy.json +47 -0
  177. package/examples/sveltekit-app/README.md +125 -0
  178. package/examples/sveltekit-app/src/app.d.ts +16 -0
  179. package/examples/sveltekit-app/src/hooks.server.ts +23 -0
  180. package/examples/sveltekit-app/src/lib/server/fortress.ts +81 -0
  181. package/examples/sveltekit-app/src/routes/api/echo/[id]/+server.ts +32 -0
  182. package/examples/sveltekit-app/src/routes/api/fortress/[...path]/+server.ts +15 -0
  183. package/examples/sveltekit-app/src/routes/dashboard/+page.server.ts +20 -0
  184. package/examples/sveltekit-app/src/routes/login/+page.server.ts +48 -0
  185. package/examples/sveltekit-app/src/routes/oauth/consent/+page.server.ts +69 -0
  186. package/examples/sveltekit-app/src/routes/oauth/consent/+page.svelte +83 -0
  187. package/migrations/pg/0001_schema_version.down.sql +2 -0
  188. package/migrations/pg/0001_schema_version.sql +8 -0
  189. package/migrations/pg/0002_initial_schema.down.sql +36 -0
  190. package/migrations/pg/0002_initial_schema.sql +376 -0
  191. package/migrations/pg/0003_auth_continuation.down.sql +6 -0
  192. package/migrations/pg/0003_auth_continuation.sql +30 -0
  193. package/migrations/pg/0004_tenant_default_unique.down.sql +1 -0
  194. package/migrations/pg/0004_tenant_default_unique.sql +14 -0
  195. package/migrations/pg/0005_hot_indexes_timestamptz.down.sql +71 -0
  196. package/migrations/pg/0005_hot_indexes_timestamptz.sql +71 -0
  197. package/migrations/pg/0006_canonical_email.down.sql +3 -0
  198. package/migrations/pg/0006_canonical_email.sql +8 -0
  199. package/migrations/pg/0007_audit_chain_anchor.down.sql +1 -0
  200. package/migrations/pg/0007_audit_chain_anchor.sql +8 -0
  201. package/migrations/pg/0008_two_factor_hardening.down.sql +8 -0
  202. package/migrations/pg/0008_two_factor_hardening.sql +8 -0
  203. package/migrations/pg/0009_encrypt_totp_secrets.down.sql +2 -0
  204. package/migrations/pg/0009_encrypt_totp_secrets.sql +5 -0
  205. package/migrations/pg/0010_bigint_append_only_ids.down.sql +2 -0
  206. package/migrations/pg/0010_bigint_append_only_ids.sql +23 -0
  207. package/migrations/sqlite/0001_schema_version.down.sql +2 -0
  208. package/migrations/sqlite/0001_schema_version.sql +8 -0
  209. package/migrations/sqlite/0002_initial_schema.down.sql +36 -0
  210. package/migrations/sqlite/0002_initial_schema.sql +376 -0
  211. package/migrations/sqlite/0003_auth_continuation.down.sql +27 -0
  212. package/migrations/sqlite/0003_auth_continuation.sql +45 -0
  213. package/migrations/sqlite/0004_tenant_default_unique.down.sql +1 -0
  214. package/migrations/sqlite/0004_tenant_default_unique.sql +13 -0
  215. package/migrations/sqlite/0005_hot_indexes_timestamptz.down.sql +10 -0
  216. package/migrations/sqlite/0005_hot_indexes_timestamptz.sql +10 -0
  217. package/migrations/sqlite/0006_canonical_email.down.sql +3 -0
  218. package/migrations/sqlite/0006_canonical_email.sql +8 -0
  219. package/migrations/sqlite/0007_audit_chain_anchor.down.sql +1 -0
  220. package/migrations/sqlite/0007_audit_chain_anchor.sql +8 -0
  221. package/migrations/sqlite/0008_two_factor_hardening.down.sql +14 -0
  222. package/migrations/sqlite/0008_two_factor_hardening.sql +7 -0
  223. package/migrations/sqlite/0009_encrypt_totp_secrets.down.sql +2 -0
  224. package/migrations/sqlite/0009_encrypt_totp_secrets.sql +5 -0
  225. package/migrations/sqlite/0010_bigint_append_only_ids.down.sql +1 -0
  226. package/migrations/sqlite/0010_bigint_append_only_ids.sql +2 -0
  227. package/package.json +398 -0
  228. package/src/adapters/database/index.ts +63 -0
  229. package/src/adapters/database/types.ts +22 -0
  230. package/src/changelog-script.test.ts +21 -0
  231. package/src/cli.test.ts +79 -0
  232. package/src/core/auth/auth-admin.test.ts +247 -0
  233. package/src/core/auth/auth-endpoints.ts +558 -0
  234. package/src/core/auth/auth-observer.test.ts +191 -0
  235. package/src/core/auth/auth-service.ts +1464 -0
  236. package/src/core/auth/email.test.ts +17 -0
  237. package/src/core/auth/email.ts +11 -0
  238. package/src/core/auth/hooks.test.ts +251 -0
  239. package/src/core/auth/impersonation.test.ts +179 -0
  240. package/src/core/auth/jwt.test.ts +184 -0
  241. package/src/core/auth/jwt.ts +239 -0
  242. package/src/core/auth/login-timing.test.ts +77 -0
  243. package/src/core/auth/password-policy.test.ts +253 -0
  244. package/src/core/auth/password-policy.ts +220 -0
  245. package/src/core/auth/password.test.ts +42 -0
  246. package/src/core/auth/password.ts +62 -0
  247. package/src/core/auth/post-auth-gate.test.ts +258 -0
  248. package/src/core/auth/post-auth-gate.ts +228 -0
  249. package/src/core/auth/refresh-token.test.ts +86 -0
  250. package/src/core/auth/refresh-token.ts +105 -0
  251. package/src/core/auth/timing-safe.ts +23 -0
  252. package/src/core/config.ts +212 -0
  253. package/src/core/endpoint.ts +149 -0
  254. package/src/core/errors.ts +199 -0
  255. package/src/core/fetcher-interop.test.ts +106 -0
  256. package/src/core/fortress.test.ts +354 -0
  257. package/src/core/fortress.ts +550 -0
  258. package/src/core/http/call.test.ts +148 -0
  259. package/src/core/http/call.ts +149 -0
  260. package/src/core/http/cookie-serialize.test.ts +198 -0
  261. package/src/core/http/cookie-serialize.ts +107 -0
  262. package/src/core/http/csrf.test.ts +140 -0
  263. package/src/core/http/csrf.ts +173 -0
  264. package/src/core/http/dispatch.ts +652 -0
  265. package/src/core/http/error-response.test.ts +69 -0
  266. package/src/core/http/error-response.ts +93 -0
  267. package/src/core/http/fortress-rbac.test.ts +43 -0
  268. package/src/core/http/fortress-rbac.ts +127 -0
  269. package/src/core/http/handle-request.test.ts +441 -0
  270. package/src/core/http/handle-request.ts +354 -0
  271. package/src/core/http/match.test.ts +122 -0
  272. package/src/core/http/match.ts +126 -0
  273. package/src/core/http/outbound.test.ts +34 -0
  274. package/src/core/http/outbound.ts +105 -0
  275. package/src/core/http/plugin-middleware.ts +67 -0
  276. package/src/core/http/principal.test.ts +345 -0
  277. package/src/core/http/principal.ts +86 -0
  278. package/src/core/http/protect.test.ts +220 -0
  279. package/src/core/http/protect.ts +394 -0
  280. package/src/core/http/token-extraction.test.ts +59 -0
  281. package/src/core/http/token-extraction.ts +53 -0
  282. package/src/core/iam/explain.test.ts +177 -0
  283. package/src/core/iam/explain.ts +302 -0
  284. package/src/core/iam/iam-endpoints.ts +779 -0
  285. package/src/core/iam/iam-service.test.ts +829 -0
  286. package/src/core/iam/iam-service.ts +1137 -0
  287. package/src/core/iam/permission-cache.test.ts +115 -0
  288. package/src/core/iam/permission-cache.ts +71 -0
  289. package/src/core/iam/permission-check-observer.test.ts +90 -0
  290. package/src/core/iam/permission-evaluator.test.ts +291 -0
  291. package/src/core/iam/permission-evaluator.ts +198 -0
  292. package/src/core/iam/permission-sync.test.ts +166 -0
  293. package/src/core/iam/permission-sync.ts +192 -0
  294. package/src/core/iam/resource-sync.test.ts +70 -0
  295. package/src/core/iam/resource-sync.ts +182 -0
  296. package/src/core/iam/service-account-http.test.ts +529 -0
  297. package/src/core/iam/service-account.test.ts +282 -0
  298. package/src/core/internal-adapter.test.ts +389 -0
  299. package/src/core/internal-adapter.ts +435 -0
  300. package/src/core/json-schema.ts +50 -0
  301. package/src/core/manifest/__snapshots__/route-manifest.test.ts.snap +192 -0
  302. package/src/core/manifest/drift.ts +103 -0
  303. package/src/core/manifest/route-manifest.test.ts +142 -0
  304. package/src/core/manifest/route-manifest.ts +154 -0
  305. package/src/core/migrate.test.ts +72 -0
  306. package/src/core/migrations/engine.test.ts +308 -0
  307. package/src/core/migrations/engine.ts +548 -0
  308. package/src/core/migrations/migrations.ts +1771 -0
  309. package/src/core/migrations/pg-migration.integration-test.ts +353 -0
  310. package/src/core/migrations/upgrade-fixture.test.ts +378 -0
  311. package/src/core/observability/db-instrumentation.ts +205 -0
  312. package/src/core/observability/listener-list.test.ts +124 -0
  313. package/src/core/observability/listener-list.ts +73 -0
  314. package/src/core/observability/logger.test.ts +43 -0
  315. package/src/core/observability/logger.ts +49 -0
  316. package/src/core/observability/spans.test.ts +193 -0
  317. package/src/core/observability/types.ts +80 -0
  318. package/src/core/openapi-drift.test.ts +41 -0
  319. package/src/core/openapi.ts +47 -0
  320. package/src/core/plugin-methods-map.ts +75 -0
  321. package/src/core/plugin-runner.test.ts +233 -0
  322. package/src/core/plugin-runner.ts +276 -0
  323. package/src/core/plugin.ts +210 -0
  324. package/src/core/policy/apply.ts +272 -0
  325. package/src/core/policy/diff.ts +288 -0
  326. package/src/core/policy/loader.test.ts +63 -0
  327. package/src/core/policy/loader.ts +214 -0
  328. package/src/core/policy/policy.test.ts +232 -0
  329. package/src/core/policy/types.ts +105 -0
  330. package/src/core/schema-builder.test.ts +660 -0
  331. package/src/core/schema-builder.ts +881 -0
  332. package/src/core/standard-schema.ts +56 -0
  333. package/src/core/types.ts +258 -0
  334. package/src/core/validation.test.ts +195 -0
  335. package/src/core/validation.ts +192 -0
  336. package/src/drizzle/adapter.test.ts +425 -0
  337. package/src/drizzle/adapter.ts +474 -0
  338. package/src/drizzle/index.ts +31 -0
  339. package/src/drizzle/pg/adapter.integration-test.ts +456 -0
  340. package/src/drizzle/pg/index.ts +13 -0
  341. package/src/drizzle/pg/pg.integration-test.ts +1539 -0
  342. package/src/drizzle/pg/schema.ts +539 -0
  343. package/src/drizzle/pg-error-map.test.ts +218 -0
  344. package/src/drizzle/pg-error-map.ts +151 -0
  345. package/src/drizzle/schema.ts +544 -0
  346. package/src/drizzle/sqlite-serialization.test.ts +106 -0
  347. package/src/express/express-api-key-user-routes.test.ts +241 -0
  348. package/src/express/express.test.ts +442 -0
  349. package/src/express/handle.ts +191 -0
  350. package/src/express/index.ts +62 -0
  351. package/src/express/middleware.ts +551 -0
  352. package/src/express/protect.ts +154 -0
  353. package/src/express/validated.test.ts +86 -0
  354. package/src/express/validated.ts +99 -0
  355. package/src/fetcher/index.ts +81 -0
  356. package/src/hono/convert-routes.test.ts +220 -0
  357. package/src/hono/convert-routes.ts +196 -0
  358. package/src/hono/converters.test.ts +50 -0
  359. package/src/hono/converters.ts +43 -0
  360. package/src/hono/handle.ts +79 -0
  361. package/src/hono/helpers.ts +2 -0
  362. package/src/hono/hono-api-key-user-routes.test.ts +225 -0
  363. package/src/hono/hono-handlers.test.ts +861 -0
  364. package/src/hono/hono.test.ts +454 -0
  365. package/src/hono/index.ts +101 -0
  366. package/src/hono/middleware/auth.ts +236 -0
  367. package/src/hono/middleware/auth.types.test.ts +94 -0
  368. package/src/hono/middleware/csrf.test.ts +127 -0
  369. package/src/hono/middleware/csrf.ts +68 -0
  370. package/src/hono/middleware/error-handler.ts +12 -0
  371. package/src/hono/middleware/plugin-middleware.ts +35 -0
  372. package/src/hono/middleware/rbac.ts +131 -0
  373. package/src/hono/middleware/security-headers.test.ts +88 -0
  374. package/src/hono/middleware/security-headers.ts +68 -0
  375. package/src/hono/openapi.ts +237 -0
  376. package/src/hono/protect.ts +87 -0
  377. package/src/hono/validated.test.ts +123 -0
  378. package/src/hono/validated.ts +62 -0
  379. package/src/index.ts +309 -0
  380. package/src/integration.test.ts +718 -0
  381. package/src/internal/changelog.ts +56 -0
  382. package/src/otel/index.ts +158 -0
  383. package/src/otel/otel-types.test.ts +35 -0
  384. package/src/otel/otel.test.ts +235 -0
  385. package/src/plugins/account-lockout/account-lockout.test.ts +367 -0
  386. package/src/plugins/account-lockout/index.ts +267 -0
  387. package/src/plugins/admin/admin-api-key.test.ts +438 -0
  388. package/src/plugins/admin/index.ts +1612 -0
  389. package/src/plugins/api-key/api-key.test.ts +684 -0
  390. package/src/plugins/api-key/core.ts +336 -0
  391. package/src/plugins/api-key/index.ts +315 -0
  392. package/src/plugins/audit-log/audit-log.test.ts +749 -0
  393. package/src/plugins/audit-log/index.ts +680 -0
  394. package/src/plugins/data-isolation/data-isolation.test.ts +197 -0
  395. package/src/plugins/data-isolation/index.ts +157 -0
  396. package/src/plugins/email-verification/email-verification.test.ts +199 -0
  397. package/src/plugins/email-verification/index.ts +190 -0
  398. package/src/plugins/magic-link/index.ts +129 -0
  399. package/src/plugins/magic-link/magic-link.test.ts +156 -0
  400. package/src/plugins/oauth/id-token.ts +86 -0
  401. package/src/plugins/oauth/index.ts +2145 -0
  402. package/src/plugins/oauth/jwks.test.ts +32 -0
  403. package/src/plugins/oauth/jwks.ts +182 -0
  404. package/src/plugins/oauth/oauth.test.ts +2220 -0
  405. package/src/plugins/oauth/pkce.ts +58 -0
  406. package/src/plugins/openapi/index.ts +298 -0
  407. package/src/plugins/openapi/openapi.test.ts +425 -0
  408. package/src/plugins/openapi/spec-builder.ts +287 -0
  409. package/src/plugins/rate-limit/express.test.ts +89 -0
  410. package/src/plugins/rate-limit/express.ts +86 -0
  411. package/src/plugins/rate-limit/hono.test.ts +60 -0
  412. package/src/plugins/rate-limit/hono.ts +74 -0
  413. package/src/plugins/rate-limit/index.ts +383 -0
  414. package/src/plugins/rate-limit/memory-store.ts +61 -0
  415. package/src/plugins/rate-limit/rate-limit.test.ts +756 -0
  416. package/src/plugins/rate-limit/sveltekit.test.ts +58 -0
  417. package/src/plugins/rate-limit/sveltekit.ts +66 -0
  418. package/src/plugins/social-login/index.ts +715 -0
  419. package/src/plugins/social-login/providers/apple.ts +34 -0
  420. package/src/plugins/social-login/providers/discord.ts +26 -0
  421. package/src/plugins/social-login/providers/github.ts +54 -0
  422. package/src/plugins/social-login/providers/google.ts +23 -0
  423. package/src/plugins/social-login/providers/index.ts +22 -0
  424. package/src/plugins/social-login/providers/microsoft.ts +35 -0
  425. package/src/plugins/social-login/providers/oidc.ts +37 -0
  426. package/src/plugins/social-login/providers/providers.test.ts +211 -0
  427. package/src/plugins/social-login/social-login.test.ts +675 -0
  428. package/src/plugins/social-login/types.ts +80 -0
  429. package/src/plugins/tenancy/index.ts +544 -0
  430. package/src/plugins/tenancy/tenancy.test.ts +323 -0
  431. package/src/plugins/two-factor/index.ts +569 -0
  432. package/src/plugins/two-factor/two-factor.test.ts +235 -0
  433. package/src/plugins/webauthn/index.ts +554 -0
  434. package/src/plugins/webauthn/webauthn.test.ts +472 -0
  435. package/src/plugins/webhook/builtin-events.ts +29 -0
  436. package/src/plugins/webhook/delivery.test.ts +51 -0
  437. package/src/plugins/webhook/delivery.ts +154 -0
  438. package/src/plugins/webhook/hooks.ts +89 -0
  439. package/src/plugins/webhook/index.ts +445 -0
  440. package/src/plugins/webhook/queue/database.ts +67 -0
  441. package/src/plugins/webhook/queue/in-memory.test.ts +48 -0
  442. package/src/plugins/webhook/queue/in-memory.ts +71 -0
  443. package/src/plugins/webhook/queue/types.ts +51 -0
  444. package/src/plugins/webhook/registry.test.ts +48 -0
  445. package/src/plugins/webhook/registry.ts +64 -0
  446. package/src/plugins/webhook/signing.ts +45 -0
  447. package/src/plugins/webhook/ssrf.ts +198 -0
  448. package/src/plugins/webhook/types.ts +138 -0
  449. package/src/plugins/webhook/webhook.test.ts +324 -0
  450. package/src/sveltekit/actions.ts +233 -0
  451. package/src/sveltekit/catch-all.ts +43 -0
  452. package/src/sveltekit/cookies.ts +136 -0
  453. package/src/sveltekit/handle.ts +356 -0
  454. package/src/sveltekit/helpers.ts +69 -0
  455. package/src/sveltekit/index.ts +74 -0
  456. package/src/sveltekit/protect.ts +80 -0
  457. package/src/sveltekit/sveltekit-types.test.ts +31 -0
  458. package/src/sveltekit/sveltekit.test.ts +799 -0
  459. package/src/sveltekit/types.ts +134 -0
  460. package/src/sveltekit/validated.test.ts +117 -0
  461. package/src/sveltekit/validated.ts +78 -0
  462. package/src/testing/adapter-conformance.test.ts +408 -0
  463. package/src/testing/checks.test.ts +124 -0
  464. package/src/testing/checks.ts +304 -0
  465. package/src/testing/index.ts +107 -0
@@ -0,0 +1,474 @@
1
+ import type { Column, SQL, Table } from 'drizzle-orm';
2
+ import type { DatabaseAdapter } from '../adapters/database';
3
+ import type { WhereClause } from '../adapters/database/types';
4
+
5
+ import { AsyncLocalStorage } from 'node:async_hooks';
6
+ import { and, eq, getTableColumns, gt, gte, inArray, isNull, like, lt, lte, ne, sql } from 'drizzle-orm';
7
+ import { Errors } from '../core/errors';
8
+ import { rethrowDbError } from './pg-error-map';
9
+ import { fortressPgSchema } from './pg/schema';
10
+ import { fortressSchema } from './schema';
11
+
12
+ export type DrizzleDialect = 'sqlite' | 'pg';
13
+
14
+ export interface DrizzleAdapterOptions {
15
+ /** Override default fortress table definitions with your own Drizzle tables */
16
+ tables?: Partial<Record<string, Table>>;
17
+ /** Database dialect — controls query execution and default table definitions (default: 'sqlite') */
18
+ dialect?: DrizzleDialect;
19
+ }
20
+
21
+ function buildTableMap(schema: typeof fortressSchema | typeof fortressPgSchema): Record<string, Table> {
22
+ return {
23
+ schema_version: schema.schemaVersion,
24
+ user: schema.users,
25
+ login_identifier: schema.loginIdentifiers,
26
+ refresh_token: schema.refreshTokens,
27
+ auth_continuation: schema.authContinuations,
28
+ group: schema.groups,
29
+ group_user: schema.groupUsers,
30
+ service_account: schema.serviceAccounts,
31
+ resource: schema.resources,
32
+ permission: schema.permissions,
33
+ role: schema.roles,
34
+ role_permission: schema.rolePermissions,
35
+ role_binding: schema.roleBindings,
36
+ direct_permission_binding: schema.directPermissionBindings,
37
+ email_verification_token: schema.emailVerificationTokens,
38
+ magic_link_token: schema.magicLinkTokens,
39
+ api_key: schema.apiKeys,
40
+ two_factor_secret: schema.twoFactorSecrets,
41
+ backup_code: schema.backupCodes,
42
+ trusted_device: schema.trustedDevices,
43
+ social_account: schema.socialAccounts,
44
+ tenant: schema.tenants,
45
+ tenant_user: schema.tenantUsers,
46
+ oauth_client: schema.oauthClients,
47
+ oauth_authorization_code: schema.oauthAuthorizationCodes,
48
+ oauth_access_token: schema.oauthAccessTokens,
49
+ oauth_refresh_token: schema.oauthRefreshTokens,
50
+ oauth_pending_flow: schema.oauthPendingFlows,
51
+ oauth_signing_key: schema.oauthSigningKeys,
52
+ user_scope_assignment: schema.userScopeAssignments,
53
+ account_lockout: schema.accountLockouts,
54
+ audit_log: schema.auditLogs,
55
+ audit_chain_state: schema.auditChainState,
56
+ webhook_endpoint: schema.webhookEndpoints,
57
+ webhook_delivery: schema.webhookDeliveries,
58
+ webauthn_credential: schema.webauthnCredentials,
59
+ webauthn_challenge: schema.webauthnChallenges,
60
+ };
61
+ }
62
+
63
+ const SQLITE_DEFAULT_TABLE_MAP = buildTableMap(fortressSchema);
64
+ const PG_DEFAULT_TABLE_MAP = buildTableMap(fortressPgSchema);
65
+
66
+ function getColumn(table: Table, field: string): Column {
67
+ const columns = getTableColumns(table);
68
+ // Try exact match first
69
+ if (columns[field])
70
+ return columns[field];
71
+
72
+ // Convert snake_case field names to camelCase column references
73
+ const camelCase = field.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());
74
+ if (columns[camelCase])
75
+ return columns[camelCase];
76
+
77
+ throw Errors.badRequest(`Unknown field: ${field} on table`);
78
+ }
79
+
80
+ function buildWhereCondition(table: Table, where: WhereClause[]): SQL | undefined {
81
+ // A missing/empty where would compile to `.where(undefined)`, which matches
82
+ // EVERY row — a silent full-table update/delete footgun. findMany/count
83
+ // guard the empty case before calling (an unfiltered read is legal); the
84
+ // mutating paths (update/delete) and findOne pass their required where here
85
+ // directly, so rejecting empty here fails those closed.
86
+ if (where.length === 0)
87
+ throw Errors.badRequest('A non-empty where clause is required (empty where would match all rows)');
88
+ const conditions = where.map((clause) => {
89
+ const column = getColumn(table, clause.field);
90
+
91
+ switch (clause.operator) {
92
+ case '=':
93
+ return eq(column, clause.value as any);
94
+ case '!=':
95
+ return ne(column, clause.value as any);
96
+ case 'in':
97
+ return inArray(column, clause.value as any[]);
98
+ case 'gt':
99
+ return gt(column, clause.value as any);
100
+ case 'lt':
101
+ return lt(column, clause.value as any);
102
+ case 'gte':
103
+ return gte(column, clause.value as any);
104
+ case 'lte':
105
+ return lte(column, clause.value as any);
106
+ case 'like':
107
+ return like(column, clause.value as string);
108
+ case 'isNull':
109
+ return isNull(column);
110
+ default:
111
+ throw Errors.badRequest(`Unsupported operator: ${clause.operator}`);
112
+ }
113
+ });
114
+
115
+ return conditions.length === 1 ? conditions[0] : and(...conditions);
116
+ }
117
+
118
+ /** Replace undefined values with null for database compatibility. */
119
+ function sanitizeData(data: Record<string, unknown>): Record<string, unknown> {
120
+ const result: Record<string, unknown> = {};
121
+ for (const [key, value] of Object.entries(data)) {
122
+ result[key] = value === undefined ? null : value;
123
+ }
124
+ return result;
125
+ }
126
+
127
+ /**
128
+ * Return true if a column name names a subject-id at the fortress API
129
+ * surface (RFC 7519 §4.1.2). Used to translate the database-native id type
130
+ * (commonly bigserial/integer) to fortress's opaque string-id contract on
131
+ * read — see {@link stringifyIds}.
132
+ *
133
+ * Matches:
134
+ * - `id` exactly (every model's primary key)
135
+ * - any camelCase field ending in `Id` (`userId`, `roleId`, `tenantId`, …)
136
+ *
137
+ * Does NOT match:
138
+ * - `tokenId`-like fields that are already strings in the schema (the
139
+ * transform is a no-op on strings, so this is safe)
140
+ * - timestamp fields (no `Id` suffix)
141
+ */
142
+ const ID_FIELD_RE = /[a-z]Id$/;
143
+ function isIdField(key: string): boolean {
144
+ return key === 'id' || ID_FIELD_RE.test(key);
145
+ }
146
+
147
+ /**
148
+ * Recursively stringify every id-like field in a row. Pure function, runs
149
+ * on every read path (findOne, findMany, rawQuery, create.returning,
150
+ * update.returning, delete.returning) so consumers always see string ids
151
+ * regardless of whether the underlying column is `integer`, `bigserial`,
152
+ * `uuid`, or `text`.
153
+ *
154
+ * - Numbers and bigints ⇒ stringified via `String(...)`.
155
+ * - Strings ⇒ passed through unchanged.
156
+ * - `null`/`undefined` ⇒ passed through (nullable FKs).
157
+ * - Arrays and nested objects are walked (rawQuery results may nest).
158
+ */
159
+ function stringifyIds<T>(row: T): T {
160
+ if (row == null || typeof row !== 'object')
161
+ return row;
162
+ if (Array.isArray(row))
163
+ return row.map(stringifyIds) as unknown as T;
164
+ const out: Record<string, unknown> = {};
165
+ for (const [key, value] of Object.entries(row as Record<string, unknown>)) {
166
+ if (isIdField(key) && (typeof value === 'number' || typeof value === 'bigint')) {
167
+ out[key] = String(value);
168
+ }
169
+ else if (value && typeof value === 'object' && !(value instanceof Date)) {
170
+ out[key] = stringifyIds(value);
171
+ }
172
+ else {
173
+ out[key] = value;
174
+ }
175
+ }
176
+ return out as T;
177
+ }
178
+
179
+ function buildRawSql(sqlText: string, params: unknown[] = []): SQL {
180
+ if (params.length === 0)
181
+ return sql.raw(sqlText);
182
+
183
+ if (sqlText.includes('?')) {
184
+ const parts = sqlText.split('?');
185
+ if (parts.length - 1 !== params.length) {
186
+ throw Errors.badRequest(`rawQuery placeholder count (${parts.length - 1}) does not match params (${params.length})`);
187
+ }
188
+ let query: SQL = sql.raw(parts[0]);
189
+ for (let i = 0; i < params.length; i++) {
190
+ query = sql`${query}${params[i]}${sql.raw(parts[i + 1])}`;
191
+ }
192
+ return query;
193
+ }
194
+
195
+ throw Errors.badRequest(
196
+ 'rawQuery uses ? positional placeholders on every dialect; params were provided but no ? placeholders were found',
197
+ );
198
+ }
199
+
200
+ function normalizeRawRows<T>(result: unknown): T[] {
201
+ if (Array.isArray(result))
202
+ return result as T[];
203
+ const rows = (result as { rows?: unknown })?.rows;
204
+ if (Array.isArray(rows))
205
+ return rows as T[];
206
+ return [];
207
+ }
208
+
209
+ function sqliteStatementReturnsRows(sqlText: string): boolean {
210
+ const normalized = sqlText.trimStart().toLowerCase();
211
+ return normalized.startsWith('select')
212
+ || normalized.startsWith('with')
213
+ || normalized.startsWith('pragma')
214
+ || normalized.includes(' returning ');
215
+ }
216
+
217
+ /**
218
+ * Minimal Drizzle DB interface — accepts any Drizzle database instance.
219
+ * Drizzle DB types vary by dialect (BunSQLiteDatabase, PostgresJsDatabase, etc.)
220
+ * so we use a loose structural type rather than importing a specific one.
221
+ */
222
+ // eslint-disable-next-line ts/no-unsafe-function-type -- Drizzle DB methods have dialect-specific signatures
223
+ interface DrizzleDB { insert: Function; select: Function; update: Function; delete: Function; transaction: Function }
224
+
225
+ /**
226
+ * Create a DatabaseAdapter backed by any Drizzle instance.
227
+ * Works with PostgreSQL and SQLite (bun:sqlite, better-sqlite3).
228
+ *
229
+ * @param db - Any Drizzle database instance
230
+ * @param options - Optional table overrides and dialect configuration
231
+ */
232
+ export function createDrizzleAdapter(db: DrizzleDB, options?: DrizzleAdapterOptions): DatabaseAdapter {
233
+ const dialect = options?.dialect ?? 'sqlite';
234
+ const defaults = dialect === 'pg' ? PG_DEFAULT_TABLE_MAP : SQLITE_DEFAULT_TABLE_MAP;
235
+ const tableMap: Record<string, Table> = { ...defaults, ...(options?.tables as Record<string, Table>) };
236
+ const isSqlite = dialect === 'sqlite';
237
+ // SQLite drivers used by Drizzle (better-sqlite3 / bun:sqlite) expose a
238
+ // single synchronous connection. Manual async transactions must therefore
239
+ // be serialized: an `await` between BEGIN and COMMIT would otherwise allow a
240
+ // second request to issue BEGIN on the same connection. SQLite is
241
+ // single-writer anyway, so this preserves correctness with predictable
242
+ // queuing semantics.
243
+ let sqliteTxChain: Promise<unknown> = Promise.resolve();
244
+ const sqliteTxContext = new AsyncLocalStorage<boolean>();
245
+
246
+ // Finding #16: a standalone (non-transaction) op touches the single SQLite
247
+ // connection immediately, so a plain write issued while a transaction is
248
+ // mid-BEGIN…COMMIT would interleave into that open transaction and be caught
249
+ // by its ROLLBACK. Route every standalone op through the SAME chain that
250
+ // serializes transactions. Ops already running inside a transaction callback
251
+ // bypass the queue — they must run directly on the open transaction rather
252
+ // than deadlock behind the tx that owns the chain.
253
+ function serializeSqlite<T>(op: () => Promise<T>): Promise<T> {
254
+ if (!isSqlite || sqliteTxContext.getStore())
255
+ return op();
256
+ const result = sqliteTxChain.then(op, op);
257
+ sqliteTxChain = result.catch(() => undefined);
258
+ return result;
259
+ }
260
+
261
+ /** Execute a query expecting a single row (or undefined). SQLite uses .get(), PG awaits the query. */
262
+ async function execOne<T>(query: any): Promise<T | undefined> {
263
+ const row = isSqlite
264
+ ? (query.get() as T | undefined)
265
+ : ((await query) as T[])[0];
266
+ return row === undefined ? undefined : stringifyIds(row);
267
+ }
268
+
269
+ /** Execute a query expecting an array of rows. SQLite uses .all(), PG awaits the query. */
270
+ async function execMany<T>(query: any): Promise<T[]> {
271
+ const rows = isSqlite
272
+ ? (query.all() as T[])
273
+ : ((await query) as T[]);
274
+ return rows.map(stringifyIds);
275
+ }
276
+
277
+ /** Execute a query where the result is discarded. SQLite uses .run(), PG awaits the query. */
278
+ async function execRun(query: any): Promise<void> {
279
+ if (isSqlite) {
280
+ query.run();
281
+ return;
282
+ }
283
+ await query;
284
+ }
285
+
286
+ function getTable(model: string): Table {
287
+ const table = tableMap[model];
288
+ if (!table) {
289
+ throw Errors.badRequest(`Unknown model: ${model}`);
290
+ }
291
+ return table;
292
+ }
293
+
294
+ /** Build a DatabaseAdapter backed by a specific Drizzle instance (db or tx) */
295
+ function buildAdapter(drizzle: DrizzleDB): DatabaseAdapter {
296
+ const self: DatabaseAdapter = {
297
+ get dialect() { return dialect; },
298
+
299
+ async create<T>(params: { model: string; data: Record<string, unknown> }): Promise<T> {
300
+ return serializeSqlite<T>(async () => {
301
+ const table = getTable(params.model);
302
+ try {
303
+ const result = await execOne<T>((drizzle as any).insert(table).values(sanitizeData(params.data) as any).returning());
304
+ return result as T;
305
+ }
306
+ catch (err) {
307
+ // Translate driver constraint errors (pg SQLSTATEs, sqlite
308
+ // SQLITE_CONSTRAINT_*) into the matching FortressError so
309
+ // unique-violation / FK-violation / etc. surface as CONFLICT /
310
+ // UNPROCESSABLE_ENTITY without every host writing the same try/catch.
311
+ rethrowDbError(err, dialect);
312
+ }
313
+ });
314
+ },
315
+
316
+ async findOne<T>(params: { model: string; where: WhereClause[] }): Promise<T | null> {
317
+ return serializeSqlite<T | null>(async () => {
318
+ const table = getTable(params.model);
319
+ const condition = buildWhereCondition(table, params.where);
320
+ const result = await execOne<T>((drizzle as any).select().from(table).where(condition).limit(1));
321
+ return (result as T) ?? null;
322
+ });
323
+ },
324
+
325
+ async findMany<T>(params: {
326
+ model: string;
327
+ where?: WhereClause[];
328
+ limit?: number;
329
+ offset?: number;
330
+ sortBy?: { field: string; direction: 'asc' | 'desc' };
331
+ }): Promise<T[]> {
332
+ return serializeSqlite<T[]>(async () => {
333
+ const table = getTable(params.model);
334
+ let query = (drizzle as any).select().from(table).$dynamic();
335
+
336
+ if (params.where && params.where.length > 0) {
337
+ const condition = buildWhereCondition(table, params.where);
338
+ query = query.where(condition);
339
+ }
340
+
341
+ if (params.sortBy) {
342
+ const column = getColumn(table, params.sortBy.field);
343
+ query = query.orderBy(
344
+ params.sortBy.direction === 'desc' ? sql`${column} desc` : sql`${column} asc`,
345
+ );
346
+ }
347
+
348
+ if (params.limit) {
349
+ query = query.limit(params.limit);
350
+ }
351
+
352
+ if (params.offset) {
353
+ query = query.offset(params.offset);
354
+ }
355
+
356
+ return execMany<T>(query);
357
+ });
358
+ },
359
+
360
+ async update<T>(params: { model: string; where: WhereClause[]; data: Record<string, unknown> }): Promise<T | null> {
361
+ return serializeSqlite<T | null>(async () => {
362
+ const table = getTable(params.model);
363
+ const condition = buildWhereCondition(table, params.where);
364
+ const query = (drizzle as any).update(table).set(sanitizeData(params.data) as any).where(condition).returning();
365
+ try {
366
+ // SQLite drivers only step the statement for rows that are read from
367
+ // RETURNING. Use .all() so UPDATEs that match multiple rows actually
368
+ // apply to every row (family/session revocation depends on this),
369
+ // while preserving the adapter contract of returning one row/null.
370
+ if (isSqlite) {
371
+ const rows = (query.all() as T[]).map(stringifyIds);
372
+ return rows[0] ?? null;
373
+ }
374
+ const result = await execOne<T>(query);
375
+ return (result as T) ?? null;
376
+ }
377
+ catch (err) {
378
+ rethrowDbError(err, dialect);
379
+ }
380
+ });
381
+ },
382
+
383
+ async delete(params: { model: string; where: WhereClause[] }): Promise<void> {
384
+ return serializeSqlite<void>(async () => {
385
+ const table = getTable(params.model);
386
+ const condition = buildWhereCondition(table, params.where);
387
+ try {
388
+ await execRun((drizzle as any).delete(table).where(condition));
389
+ }
390
+ catch (err) {
391
+ // FK violation on delete is the common case here — surfaces as
392
+ // UNPROCESSABLE_ENTITY rather than a raw driver error.
393
+ rethrowDbError(err, dialect);
394
+ }
395
+ });
396
+ },
397
+
398
+ async count(params: { model: string; where?: WhereClause[] }): Promise<number> {
399
+ return serializeSqlite<number>(async () => {
400
+ const table = getTable(params.model);
401
+ let query = (drizzle as any).select({ count: sql<number>`count(*)` }).from(table).$dynamic();
402
+
403
+ if (params.where && params.where.length > 0) {
404
+ const condition = buildWhereCondition(table, params.where);
405
+ query = query.where(condition);
406
+ }
407
+
408
+ const result = await execOne<{ count: number | string }>(query);
409
+ return Number(result?.count) || 0;
410
+ });
411
+ },
412
+
413
+ async rawQuery<T>(sqlText: string, params?: unknown[]): Promise<T[]> {
414
+ return serializeSqlite<T[]>(async () => {
415
+ const query = buildRawSql(sqlText, params ?? []);
416
+ if (isSqlite) {
417
+ if (typeof (drizzle as any).all === 'function' && sqliteStatementReturnsRows(sqlText))
418
+ return normalizeRawRows<T>((drizzle as any).all(query)).map(stringifyIds);
419
+ if (typeof (drizzle as any).run === 'function') {
420
+ (drizzle as any).run(query);
421
+ return [];
422
+ }
423
+ if (typeof (drizzle as any).execute === 'function')
424
+ return normalizeRawRows<T>(await (drizzle as any).execute(query)).map(stringifyIds);
425
+ throw Errors.badRequest('rawQuery is not supported by this SQLite Drizzle driver');
426
+ }
427
+ if (typeof (drizzle as any).execute !== 'function')
428
+ throw Errors.badRequest('rawQuery is not supported by this Drizzle driver');
429
+ return normalizeRawRows<T>(await (drizzle as any).execute(query)).map(stringifyIds);
430
+ });
431
+ },
432
+
433
+ async transaction<T>(fn: (tx: DatabaseAdapter) => Promise<T>): Promise<T> {
434
+ if (dialect === 'sqlite') {
435
+ // SQLite transactions are serialized on one connection. A nested
436
+ // tx.transaction(...) from inside the callback would otherwise be
437
+ // queued behind the still-open outer transaction while the outer
438
+ // callback awaits it: a self-deadlock. Detect that context and fail
439
+ // clearly instead of hanging.
440
+ if (sqliteTxContext.getStore()) {
441
+ throw Errors.badRequest('Nested transactions are not supported by the SQLite Drizzle adapter');
442
+ }
443
+
444
+ const run = async (): Promise<T> => {
445
+ (drizzle as any).run(sql`BEGIN IMMEDIATE`);
446
+ try {
447
+ const result = await sqliteTxContext.run(true, () => fn(self));
448
+ (drizzle as any).run(sql`COMMIT`);
449
+ return result;
450
+ }
451
+ catch (error) {
452
+ (drizzle as any).run(sql`ROLLBACK`);
453
+ throw error;
454
+ }
455
+ };
456
+
457
+ const result = sqliteTxChain.then(run, run);
458
+ sqliteTxChain = result.catch(() => undefined);
459
+ return result;
460
+ }
461
+
462
+ // PostgreSQL: use Drizzle's native async transaction
463
+ return (drizzle as any).transaction(async (tx: DrizzleDB) => {
464
+ const txAdapter = buildAdapter(tx);
465
+ return fn(txAdapter);
466
+ });
467
+ },
468
+ };
469
+
470
+ return self;
471
+ }
472
+
473
+ return buildAdapter(db);
474
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Drizzle ORM adapter for fortress.
3
+ *
4
+ * Works with any Drizzle database instance — PostgreSQL or SQLite.
5
+ * Use the SQLite-flavoured `fortressSchema` aggregate for SQLite, or import
6
+ * `@bajustone/fortress/drizzle/pg` for the PostgreSQL flavour. Tables are
7
+ * exported via the aggregate (typed as `Record<string, AnySQLiteTable>`) so
8
+ * the public API stays statically typed for JSR; consumers who want
9
+ * column-level inference should declare their own Drizzle schema and pass it
10
+ * via the `tables` option.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { drizzle } from 'drizzle-orm/postgres-js';
15
+ * import { createDrizzleAdapter } from '@bajustone/fortress/drizzle';
16
+ *
17
+ * const db = drizzle(sql);
18
+ * const adapter = createDrizzleAdapter(db, { dialect: 'pg' });
19
+ * ```
20
+ *
21
+ * @module
22
+ */
23
+
24
+ export { createDrizzleAdapter } from './adapter';
25
+ export type { DrizzleAdapterOptions, DrizzleDialect } from './adapter';
26
+ export { findSqlstate, rethrowDbError } from './pg-error-map';
27
+ // Re-export only the schema objects — individual table exports cause JSR "slow types"
28
+ // errors because sqliteTable()/pgTable() return types are too complex for JSR to infer.
29
+ // Consumers access tables via: fortressSchema.users, fortressPgSchema.roles, etc.
30
+ export { fortressPgSchema } from './pg/schema';
31
+ export { fortressSchema } from './schema';