@geekmidas/testkit 10.0.0-alpha.12 → 10.0.0-alpha.120

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 (295) hide show
  1. package/README.md +74 -0
  2. package/dist/Factory.cjs +51 -2
  3. package/dist/Factory.cjs.map +1 -0
  4. package/dist/Factory.d.cts +143 -3
  5. package/dist/Factory.d.cts.map +1 -0
  6. package/dist/Factory.d.mts +143 -3
  7. package/dist/Factory.d.mts.map +1 -0
  8. package/dist/Factory.mjs +50 -2
  9. package/dist/Factory.mjs.map +1 -0
  10. package/dist/KyselyFactory.cjs +237 -4
  11. package/dist/KyselyFactory.cjs.map +1 -0
  12. package/dist/KyselyFactory.d.cts +224 -4
  13. package/dist/KyselyFactory.d.cts.map +1 -0
  14. package/dist/KyselyFactory.d.mts +224 -4
  15. package/dist/KyselyFactory.d.mts.map +1 -0
  16. package/dist/KyselyFactory.mjs +236 -4
  17. package/dist/KyselyFactory.mjs.map +1 -0
  18. package/dist/ObjectionFactory.cjs +265 -4
  19. package/dist/ObjectionFactory.cjs.map +1 -0
  20. package/dist/ObjectionFactory.d.cts +237 -4
  21. package/dist/ObjectionFactory.d.cts.map +1 -0
  22. package/dist/ObjectionFactory.d.mts +237 -4
  23. package/dist/ObjectionFactory.d.mts.map +1 -0
  24. package/dist/ObjectionFactory.mjs +264 -4
  25. package/dist/ObjectionFactory.mjs.map +1 -0
  26. package/dist/PostgresKyselyMigrator.cjs +94 -3
  27. package/dist/PostgresKyselyMigrator.cjs.map +1 -0
  28. package/dist/PostgresKyselyMigrator.d.cts +80 -3
  29. package/dist/PostgresKyselyMigrator.d.cts.map +1 -0
  30. package/dist/PostgresKyselyMigrator.d.mts +80 -3
  31. package/dist/PostgresKyselyMigrator.d.mts.map +1 -0
  32. package/dist/PostgresKyselyMigrator.mjs +93 -3
  33. package/dist/PostgresKyselyMigrator.mjs.map +1 -0
  34. package/dist/{PostgresMigrator-C_QQ6q35.d.mts → PostgresMigrator-CDPnnwMJ.d.cts} +11 -5
  35. package/dist/PostgresMigrator-CDPnnwMJ.d.cts.map +1 -0
  36. package/dist/{PostgresMigrator-CeYy-eHF.d.cts → PostgresMigrator-CDPnnwMJ.d.mts} +11 -5
  37. package/dist/PostgresMigrator-CDPnnwMJ.d.mts.map +1 -0
  38. package/dist/PostgresMigrator.cjs +178 -2
  39. package/dist/PostgresMigrator.cjs.map +1 -0
  40. package/dist/PostgresMigrator.d.cts +1 -1
  41. package/dist/PostgresMigrator.d.mts +1 -1
  42. package/dist/PostgresMigrator.mjs +175 -2
  43. package/dist/PostgresMigrator.mjs.map +1 -0
  44. package/dist/PostgresObjectionMigrator.cjs +118 -3
  45. package/dist/PostgresObjectionMigrator.cjs.map +1 -0
  46. package/dist/PostgresObjectionMigrator.d.cts +75 -3
  47. package/dist/PostgresObjectionMigrator.d.cts.map +1 -0
  48. package/dist/PostgresObjectionMigrator.d.mts +75 -3
  49. package/dist/PostgresObjectionMigrator.d.mts.map +1 -0
  50. package/dist/PostgresObjectionMigrator.mjs +117 -3
  51. package/dist/PostgresObjectionMigrator.mjs.map +1 -0
  52. package/dist/VitestKyselyTransactionIsolator.cjs +66 -3
  53. package/dist/VitestKyselyTransactionIsolator.cjs.map +1 -0
  54. package/dist/VitestKyselyTransactionIsolator.d.cts +60 -3
  55. package/dist/VitestKyselyTransactionIsolator.d.cts.map +1 -0
  56. package/dist/VitestKyselyTransactionIsolator.d.mts +60 -3
  57. package/dist/VitestKyselyTransactionIsolator.d.mts.map +1 -0
  58. package/dist/VitestKyselyTransactionIsolator.mjs +65 -3
  59. package/dist/VitestKyselyTransactionIsolator.mjs.map +1 -0
  60. package/dist/VitestObjectionTransactionIsolator.cjs +62 -3
  61. package/dist/VitestObjectionTransactionIsolator.cjs.map +1 -0
  62. package/dist/VitestObjectionTransactionIsolator.d.cts +56 -3
  63. package/dist/VitestObjectionTransactionIsolator.d.cts.map +1 -0
  64. package/dist/VitestObjectionTransactionIsolator.d.mts +56 -3
  65. package/dist/VitestObjectionTransactionIsolator.d.mts.map +1 -0
  66. package/dist/VitestObjectionTransactionIsolator.mjs +61 -3
  67. package/dist/VitestObjectionTransactionIsolator.mjs.map +1 -0
  68. package/dist/VitestTransactionIsolator.cjs +187 -4
  69. package/dist/VitestTransactionIsolator.cjs.map +1 -0
  70. package/dist/VitestTransactionIsolator.d.cts +219 -2
  71. package/dist/VitestTransactionIsolator.d.cts.map +1 -0
  72. package/dist/VitestTransactionIsolator.d.mts +219 -2
  73. package/dist/VitestTransactionIsolator.d.mts.map +1 -0
  74. package/dist/VitestTransactionIsolator.mjs +184 -2
  75. package/dist/VitestTransactionIsolator.mjs.map +1 -0
  76. package/dist/aws.cjs +3 -4
  77. package/dist/aws.cjs.map +1 -1
  78. package/dist/aws.d.cts +3 -8
  79. package/dist/aws.d.cts.map +1 -1
  80. package/dist/aws.d.mts +3 -8
  81. package/dist/aws.d.mts.map +1 -1
  82. package/dist/aws.mjs +1 -2
  83. package/dist/aws.mjs.map +1 -1
  84. package/dist/benchmark.cjs +2 -2
  85. package/dist/benchmark.cjs.map +1 -1
  86. package/dist/benchmark.d.cts +4 -6
  87. package/dist/benchmark.d.cts.map +1 -1
  88. package/dist/benchmark.d.mts +4 -6
  89. package/dist/benchmark.d.mts.map +1 -1
  90. package/dist/benchmark.mjs +1 -1
  91. package/dist/benchmark.mjs.map +1 -1
  92. package/dist/better-auth.cjs +4 -4
  93. package/dist/better-auth.cjs.map +1 -1
  94. package/dist/better-auth.d.cts +13142 -5
  95. package/dist/better-auth.d.cts.map +1 -1
  96. package/dist/better-auth.d.mts +13142 -5
  97. package/dist/better-auth.d.mts.map +1 -1
  98. package/dist/better-auth.mjs +2 -2
  99. package/dist/better-auth.mjs.map +1 -1
  100. package/dist/browser.cjs +149 -0
  101. package/dist/browser.cjs.map +1 -0
  102. package/dist/browser.d.cts +62 -0
  103. package/dist/browser.d.cts.map +1 -0
  104. package/dist/browser.d.mts +62 -0
  105. package/dist/browser.d.mts.map +1 -0
  106. package/dist/browser.mjs +147 -0
  107. package/dist/browser.mjs.map +1 -0
  108. package/dist/context.cjs +80 -0
  109. package/dist/context.cjs.map +1 -0
  110. package/dist/context.d.cts +55 -0
  111. package/dist/context.d.cts.map +1 -0
  112. package/dist/context.d.mts +55 -0
  113. package/dist/context.d.mts.map +1 -0
  114. package/dist/context.mjs +73 -0
  115. package/dist/context.mjs.map +1 -0
  116. package/dist/faker.cjs +313 -8
  117. package/dist/faker.cjs.map +1 -0
  118. package/dist/faker.d.cts +169 -2
  119. package/dist/faker.d.cts.map +1 -0
  120. package/dist/faker.d.mts +169 -2
  121. package/dist/faker.d.mts.map +1 -0
  122. package/dist/faker.mjs +305 -2
  123. package/dist/faker.mjs.map +1 -0
  124. package/dist/helpers.cjs +6 -5
  125. package/dist/helpers.cjs.map +1 -1
  126. package/dist/helpers.d.cts +1 -5
  127. package/dist/helpers.d.cts.map +1 -1
  128. package/dist/helpers.d.mts +1 -5
  129. package/dist/helpers.d.mts.map +1 -1
  130. package/dist/helpers.mjs +1 -2
  131. package/dist/helpers.mjs.map +1 -1
  132. package/dist/initScript.cjs +15 -15
  133. package/dist/initScript.cjs.map +1 -1
  134. package/dist/initScript.d.cts +2 -4
  135. package/dist/initScript.d.cts.map +1 -1
  136. package/dist/initScript.d.mts +2 -4
  137. package/dist/initScript.d.mts.map +1 -1
  138. package/dist/initScript.mjs +10 -12
  139. package/dist/initScript.mjs.map +1 -1
  140. package/dist/kysely.cjs +10 -13
  141. package/dist/kysely.cjs.map +1 -1
  142. package/dist/kysely.d.cts +10 -14
  143. package/dist/kysely.d.cts.map +1 -1
  144. package/dist/kysely.d.mts +10 -14
  145. package/dist/kysely.d.mts.map +1 -1
  146. package/dist/kysely.mjs +10 -14
  147. package/dist/kysely.mjs.map +1 -1
  148. package/dist/logger.cjs +3 -4
  149. package/dist/logger.cjs.map +1 -1
  150. package/dist/logger.d.cts +1 -5
  151. package/dist/logger.d.cts.map +1 -1
  152. package/dist/logger.d.mts +1 -5
  153. package/dist/logger.d.mts.map +1 -1
  154. package/dist/logger.mjs +1 -2
  155. package/dist/logger.mjs.map +1 -1
  156. package/dist/mailbox.cjs +95 -0
  157. package/dist/mailbox.cjs.map +1 -0
  158. package/dist/mailbox.d.cts +58 -0
  159. package/dist/mailbox.d.cts.map +1 -0
  160. package/dist/mailbox.d.mts +58 -0
  161. package/dist/mailbox.d.mts.map +1 -0
  162. package/dist/mailbox.mjs +92 -0
  163. package/dist/mailbox.mjs.map +1 -0
  164. package/dist/objection.cjs +10 -13
  165. package/dist/objection.cjs.map +1 -1
  166. package/dist/objection.d.cts +10 -14
  167. package/dist/objection.d.cts.map +1 -1
  168. package/dist/objection.d.mts +10 -14
  169. package/dist/objection.d.mts.map +1 -1
  170. package/dist/objection.mjs +10 -14
  171. package/dist/objection.mjs.map +1 -1
  172. package/dist/os/directory.cjs +31 -2
  173. package/dist/os/directory.cjs.map +1 -0
  174. package/dist/os/directory.d.cts +9 -2
  175. package/dist/os/directory.d.cts.map +1 -0
  176. package/dist/os/directory.d.mts +9 -2
  177. package/dist/os/directory.d.mts.map +1 -0
  178. package/dist/os/directory.mjs +25 -2
  179. package/dist/os/directory.mjs.map +1 -0
  180. package/dist/os/index.cjs +3 -3
  181. package/dist/os/index.d.cts +1 -1
  182. package/dist/os/index.d.mts +1 -1
  183. package/dist/os/index.mjs +2 -3
  184. package/dist/requestContext.cjs +10 -11
  185. package/dist/requestContext.cjs.map +1 -1
  186. package/dist/requestContext.d.cts +4 -8
  187. package/dist/requestContext.d.cts.map +1 -1
  188. package/dist/requestContext.d.mts +4 -8
  189. package/dist/requestContext.d.mts.map +1 -1
  190. package/dist/requestContext.mjs +1 -2
  191. package/dist/requestContext.mjs.map +1 -1
  192. package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-VH7oDXx4.cjs} +8 -10
  193. package/dist/timer.cjs +2 -2
  194. package/dist/timer.cjs.map +1 -1
  195. package/dist/timer.d.cts +1 -4
  196. package/dist/timer.d.cts.map +1 -1
  197. package/dist/timer.d.mts +1 -4
  198. package/dist/timer.d.mts.map +1 -1
  199. package/dist/timer.mjs +1 -1
  200. package/dist/timer.mjs.map +1 -1
  201. package/dist/transactions.cjs +239 -0
  202. package/dist/transactions.cjs.map +1 -0
  203. package/dist/transactions.d.cts +44 -0
  204. package/dist/transactions.d.cts.map +1 -0
  205. package/dist/transactions.d.mts +44 -0
  206. package/dist/transactions.d.mts.map +1 -0
  207. package/dist/transactions.mjs +234 -0
  208. package/dist/transactions.mjs.map +1 -0
  209. package/package.json +52 -8
  210. package/dist/Factory-BFVnMMCC.mjs +0 -51
  211. package/dist/Factory-BFVnMMCC.mjs.map +0 -1
  212. package/dist/Factory-BhjUOBWN.cjs +0 -57
  213. package/dist/Factory-BhjUOBWN.cjs.map +0 -1
  214. package/dist/Factory-Bx0AJXZB.d.cts +0 -147
  215. package/dist/Factory-Bx0AJXZB.d.cts.map +0 -1
  216. package/dist/Factory-ClyZLRsO.d.mts +0 -147
  217. package/dist/Factory-ClyZLRsO.d.mts.map +0 -1
  218. package/dist/KyselyFactory-BFqVIn_0.cjs +0 -246
  219. package/dist/KyselyFactory-BFqVIn_0.cjs.map +0 -1
  220. package/dist/KyselyFactory-CTZmiGI9.d.mts +0 -228
  221. package/dist/KyselyFactory-CTZmiGI9.d.mts.map +0 -1
  222. package/dist/KyselyFactory-DMswpwji.mjs +0 -241
  223. package/dist/KyselyFactory-DMswpwji.mjs.map +0 -1
  224. package/dist/KyselyFactory-KLeKH43i.d.cts +0 -228
  225. package/dist/KyselyFactory-KLeKH43i.d.cts.map +0 -1
  226. package/dist/ObjectionFactory-BeFBYcan.cjs +0 -272
  227. package/dist/ObjectionFactory-BeFBYcan.cjs.map +0 -1
  228. package/dist/ObjectionFactory-DDrKwAoO.d.mts +0 -241
  229. package/dist/ObjectionFactory-DDrKwAoO.d.mts.map +0 -1
  230. package/dist/ObjectionFactory-QCJ7u0Ql.mjs +0 -267
  231. package/dist/ObjectionFactory-QCJ7u0Ql.mjs.map +0 -1
  232. package/dist/ObjectionFactory-aMGvAKt9.d.cts +0 -241
  233. package/dist/ObjectionFactory-aMGvAKt9.d.cts.map +0 -1
  234. package/dist/PostgresKyselyMigrator-B2M6Gjvv.d.cts +0 -84
  235. package/dist/PostgresKyselyMigrator-B2M6Gjvv.d.cts.map +0 -1
  236. package/dist/PostgresKyselyMigrator-BGBxjnFv.cjs +0 -101
  237. package/dist/PostgresKyselyMigrator-BGBxjnFv.cjs.map +0 -1
  238. package/dist/PostgresKyselyMigrator-CvwXycJE.d.mts +0 -84
  239. package/dist/PostgresKyselyMigrator-CvwXycJE.d.mts.map +0 -1
  240. package/dist/PostgresKyselyMigrator-PI0QhvB1.mjs +0 -95
  241. package/dist/PostgresKyselyMigrator-PI0QhvB1.mjs.map +0 -1
  242. package/dist/PostgresMigrator-C_QQ6q35.d.mts.map +0 -1
  243. package/dist/PostgresMigrator-CeYy-eHF.d.cts.map +0 -1
  244. package/dist/PostgresMigrator-DVAY04qN.mjs +0 -166
  245. package/dist/PostgresMigrator-DVAY04qN.mjs.map +0 -1
  246. package/dist/PostgresMigrator-M9jpzOvN.cjs +0 -172
  247. package/dist/PostgresMigrator-M9jpzOvN.cjs.map +0 -1
  248. package/dist/PostgresObjectionMigrator-1j6YIB1c.d.mts +0 -79
  249. package/dist/PostgresObjectionMigrator-1j6YIB1c.d.mts.map +0 -1
  250. package/dist/PostgresObjectionMigrator-DHVC9h_P.d.cts +0 -79
  251. package/dist/PostgresObjectionMigrator-DHVC9h_P.d.cts.map +0 -1
  252. package/dist/PostgresObjectionMigrator-DSaPhwjY.cjs +0 -123
  253. package/dist/PostgresObjectionMigrator-DSaPhwjY.cjs.map +0 -1
  254. package/dist/PostgresObjectionMigrator-DjPKdUbm.mjs +0 -118
  255. package/dist/PostgresObjectionMigrator-DjPKdUbm.mjs.map +0 -1
  256. package/dist/VitestKyselyTransactionIsolator-1Saieke7.mjs +0 -67
  257. package/dist/VitestKyselyTransactionIsolator-1Saieke7.mjs.map +0 -1
  258. package/dist/VitestKyselyTransactionIsolator-BjJSXryR.cjs +0 -72
  259. package/dist/VitestKyselyTransactionIsolator-BjJSXryR.cjs.map +0 -1
  260. package/dist/VitestKyselyTransactionIsolator-CwlTo6yf.d.mts +0 -64
  261. package/dist/VitestKyselyTransactionIsolator-CwlTo6yf.d.mts.map +0 -1
  262. package/dist/VitestKyselyTransactionIsolator-DJasVuIZ.d.cts +0 -64
  263. package/dist/VitestKyselyTransactionIsolator-DJasVuIZ.d.cts.map +0 -1
  264. package/dist/VitestObjectionTransactionIsolator-B_VSWFum.mjs +0 -63
  265. package/dist/VitestObjectionTransactionIsolator-B_VSWFum.mjs.map +0 -1
  266. package/dist/VitestObjectionTransactionIsolator-CTTjvTPO.d.cts +0 -60
  267. package/dist/VitestObjectionTransactionIsolator-CTTjvTPO.d.cts.map +0 -1
  268. package/dist/VitestObjectionTransactionIsolator-CyZG6nq4.d.mts +0 -60
  269. package/dist/VitestObjectionTransactionIsolator-CyZG6nq4.d.mts.map +0 -1
  270. package/dist/VitestObjectionTransactionIsolator-DDoJTu7e.cjs +0 -68
  271. package/dist/VitestObjectionTransactionIsolator-DDoJTu7e.cjs.map +0 -1
  272. package/dist/VitestTransactionIsolator-BLaw80cx.d.mts +0 -223
  273. package/dist/VitestTransactionIsolator-BLaw80cx.d.mts.map +0 -1
  274. package/dist/VitestTransactionIsolator-CvwPecpl.mjs +0 -188
  275. package/dist/VitestTransactionIsolator-CvwPecpl.mjs.map +0 -1
  276. package/dist/VitestTransactionIsolator-DyiX-QtK.cjs +0 -206
  277. package/dist/VitestTransactionIsolator-DyiX-QtK.cjs.map +0 -1
  278. package/dist/VitestTransactionIsolator-glcxyS_R.d.cts +0 -223
  279. package/dist/VitestTransactionIsolator-glcxyS_R.d.cts.map +0 -1
  280. package/dist/directory-BIcVWgJk.d.cts +0 -13
  281. package/dist/directory-BIcVWgJk.d.cts.map +0 -1
  282. package/dist/directory-BxjqmtCT.mjs +0 -27
  283. package/dist/directory-BxjqmtCT.mjs.map +0 -1
  284. package/dist/directory-CYXmolu1.d.mts +0 -13
  285. package/dist/directory-CYXmolu1.d.mts.map +0 -1
  286. package/dist/directory-DD_xQoSh.cjs +0 -33
  287. package/dist/directory-DD_xQoSh.cjs.map +0 -1
  288. package/dist/faker-B14IEMIN.cjs +0 -304
  289. package/dist/faker-B14IEMIN.cjs.map +0 -1
  290. package/dist/faker-BGKYFoCT.mjs +0 -262
  291. package/dist/faker-BGKYFoCT.mjs.map +0 -1
  292. package/dist/faker-BSH1EMtg.d.cts +0 -166
  293. package/dist/faker-BSH1EMtg.d.cts.map +0 -1
  294. package/dist/faker-BcjUfHxx.d.mts +0 -166
  295. package/dist/faker-BcjUfHxx.d.mts.map +0 -1
@@ -0,0 +1,239 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
3
+ let pg = require("pg");
4
+ pg = require_rolldown_runtime.__toESM(pg, 1);
5
+ let kysely = require("kysely");
6
+ //#region src/transactions.ts
7
+ /**
8
+ * A transaction per database, per test — as each runs deployed.
9
+ *
10
+ * An app's database and a schema tenant such as the auth server's are separate
11
+ * databases to the code using them: separate connections, credentials, roles
12
+ * and search paths. A test keeps them that way. Each gets its own connection,
13
+ * opened into a transaction the first time the test touches it, and every one
14
+ * is rolled back when the test ends.
15
+ *
16
+ * Code under test must be free to use transactions itself, and better-auth
17
+ * does — it also calls `db.connection()`, which Kysely refuses on a
18
+ * `Transaction`. So what a test hands over is not a `Transaction` but a Kysely
19
+ * over the one connection already inside the test's transaction, on which a
20
+ * `BEGIN` becomes a savepoint and a `COMMIT` releases it. Nothing the code does
21
+ * can end the transaction the test will roll back.
22
+ *
23
+ * A statement the code runs outside a transaction of its own gets a savepoint
24
+ * too, so a failure — a duplicate key two concurrent requests raced to insert —
25
+ * fails that statement alone, as it would deployed, rather than every request
26
+ * the test makes after it.
27
+ *
28
+ * A savepoint the test or the code opens by name — `savepoint refused`, a
29
+ * statement expected to fail, `rollback to savepoint refused` — is its own
30
+ * transaction control, and passes through untouched. While one is open,
31
+ * statements run in it as they would inside a `begin`: unwrapped, so a failure
32
+ * aborts the transaction until the matching `rollback to savepoint`.
33
+ */
34
+ /**
35
+ * The connection settings for a database URL.
36
+ *
37
+ * `?search_path=` is not a libpq parameter: a URL carrying it connects and then
38
+ * resolves every name against `public`. Postgres takes it as a startup option,
39
+ * which is what a schema tenant's URL needs to reach its own tables.
40
+ */
41
+ function connectionConfig(url) {
42
+ const parsed = new URL(url);
43
+ const searchPath = parsed.searchParams.get("search_path");
44
+ if (!searchPath) return { connectionString: url };
45
+ parsed.searchParams.delete("search_path");
46
+ return {
47
+ connectionString: parsed.toString(),
48
+ options: `-c search_path=${searchPath}`
49
+ };
50
+ }
51
+ /**
52
+ * Open a connection to `url` and begin the transaction a test will roll back.
53
+ *
54
+ * `config` is what the database's own clients are built with — its plugins
55
+ * above all. The code under test is handed this Kysely in place of its own, so
56
+ * one built without them runs different queries than production does.
57
+ */
58
+ async function openBoundTransaction(url, config = {}) {
59
+ const client = new pg.default.Client(connectionConfig(url));
60
+ await client.connect();
61
+ await client.query("BEGIN");
62
+ const inTurn = turns();
63
+ const named = [];
64
+ return {
65
+ db: new kysely.Kysely({
66
+ ...config,
67
+ dialect: new kysely.PostgresDialect({ pool: {
68
+ connect: async () => savepointing(client, inTurn, named),
69
+ end: async () => {}
70
+ } })
71
+ }),
72
+ async rollback() {
73
+ try {
74
+ await client.query("ROLLBACK");
75
+ } finally {
76
+ await client.end();
77
+ }
78
+ }
79
+ };
80
+ }
81
+ /**
82
+ * The client, with transaction control turned into savepoints.
83
+ *
84
+ * Kysely issues `begin`/`commit`/`rollback` as plain statements; this is the
85
+ * one place they can be caught. A depth counter names the savepoints, so
86
+ * nested transactions nest.
87
+ */
88
+ function savepointing(client, inTurn, named) {
89
+ let depth = 0;
90
+ const query = client.query.bind(client);
91
+ const wrapped = Object.create(client);
92
+ const closeTransaction = (level) => {
93
+ for (let i = named.length - 1; i >= 0; i--) {
94
+ const savepoint = named[i];
95
+ if (savepoint.checkout === wrapped && savepoint.depth >= level) named.splice(i, 1);
96
+ }
97
+ };
98
+ Object.assign(wrapped, {
99
+ query: (text, ...rest) => {
100
+ if (typeof text !== "string" || typeof rest.at(-1) === "function") return query(text, ...rest);
101
+ const statement = text.trim().toLowerCase();
102
+ if (statement === "begin" || statement.startsWith("start transaction")) {
103
+ const savepoint = `test_sp_${++depth}`;
104
+ return inTurn(() => query(`SAVEPOINT ${savepoint}`));
105
+ }
106
+ if (statement === "commit") {
107
+ const level = depth--;
108
+ closeTransaction(level);
109
+ return inTurn(() => query(`RELEASE SAVEPOINT test_sp_${level}`));
110
+ }
111
+ if (statement === "rollback") {
112
+ const level = depth--;
113
+ closeTransaction(level);
114
+ return inTurn(() => query(`ROLLBACK TO SAVEPOINT test_sp_${level}`));
115
+ }
116
+ const control = savepointControl(text);
117
+ if (control) return inTurn(async () => {
118
+ const result = await query(text, ...rest);
119
+ track(named, control, {
120
+ checkout: wrapped,
121
+ depth
122
+ });
123
+ return result;
124
+ });
125
+ if (depth > 0 || named.length > 0) return inTurn(() => query(text, ...rest));
126
+ return inTurn(() => autocommitted(query, text, rest));
127
+ },
128
+ release: () => {}
129
+ });
130
+ return wrapped;
131
+ }
132
+ const IDENTIFIER = String.raw`("(?:[^"]|"")+"|[^\s;"]+)`;
133
+ const SAVEPOINT_CONTROL = [
134
+ ["savepoint", new RegExp(String.raw`^savepoint\s+${IDENTIFIER}\s*;?$`, "i")],
135
+ ["release", new RegExp(String.raw`^release\s+(?:savepoint\s+)?${IDENTIFIER}\s*;?$`, "i")],
136
+ ["rollback to", new RegExp(String.raw`^rollback\s+(?:(?:work|transaction)\s+)?to\s+(?:savepoint\s+)?${IDENTIFIER}\s*;?$`, "i")]
137
+ ];
138
+ /**
139
+ * The savepoint a statement opens, releases or rolls back to, named as
140
+ * Postgres names it: an unquoted name folded to lower case, a quoted one as
141
+ * written.
142
+ */
143
+ function savepointControl(text) {
144
+ const statement = text.trim();
145
+ for (const [action, pattern] of SAVEPOINT_CONTROL) {
146
+ const match = pattern.exec(statement);
147
+ if (!match) continue;
148
+ const identifier = match[1];
149
+ return {
150
+ action,
151
+ name: identifier.startsWith("\"") ? identifier.slice(1, -1).replaceAll("\"\"", "\"") : identifier.toLowerCase()
152
+ };
153
+ }
154
+ }
155
+ /**
156
+ * Keep the named savepoints as Postgres does: `RELEASE x` ends x and every
157
+ * savepoint after it; `ROLLBACK TO x` ends those after it and keeps x open.
158
+ */
159
+ function track(named, control, at) {
160
+ if (control.action === "savepoint") {
161
+ named.push({
162
+ name: control.name,
163
+ ...at
164
+ });
165
+ return;
166
+ }
167
+ const index = named.findLastIndex((s) => s.name === control.name);
168
+ if (index === -1) return;
169
+ named.splice(control.action === "release" ? index : index + 1);
170
+ }
171
+ /**
172
+ * One statement at a time on a connection, in the order they were asked for.
173
+ *
174
+ * The connection runs them one at a time anyway; this keeps a statement and
175
+ * the savepoint around it together, so nothing another request sends lands
176
+ * between them — a `RELEASE` also ends every savepoint opened after its own.
177
+ */
178
+ function turns() {
179
+ let queue = Promise.resolve();
180
+ return (work) => {
181
+ const turn = queue.then(work, work);
182
+ queue = turn.catch(() => {});
183
+ return turn;
184
+ };
185
+ }
186
+ /**
187
+ * A statement run outside any transaction the code opened, failing the way
188
+ * it would deployed: on its own.
189
+ *
190
+ * Deployed, such a statement is its own transaction, so one that fails — a
191
+ * duplicate key two concurrent requests both tried to insert — fails alone,
192
+ * and code written for that (Better Auth's rate limiter reads the row the
193
+ * other request inserted, and carries on) does carry on. Inside the test's
194
+ * transaction a failed statement would abort everything after it, every
195
+ * request of the test included. A savepoint around it puts back what
196
+ * deployed would have.
197
+ */
198
+ async function autocommitted(query, text, rest) {
199
+ await query("SAVEPOINT test_statement");
200
+ try {
201
+ const result = await query(text, ...rest);
202
+ await query("RELEASE SAVEPOINT test_statement");
203
+ return result;
204
+ } catch (error) {
205
+ await query("ROLLBACK TO SAVEPOINT test_statement");
206
+ await query("RELEASE SAVEPOINT test_statement");
207
+ throw error;
208
+ }
209
+ }
210
+ /**
211
+ * The transactions one test has opened, by database.
212
+ *
213
+ * `get` opens a database's transaction the first time it is asked for and
214
+ * hands back the same one after that — including to a caller that asks while
215
+ * it is still opening. `rollbackAll` ends every one of them.
216
+ */
217
+ var TransactionRegistry = class {
218
+ open = /* @__PURE__ */ new Map();
219
+ get(key, url, config) {
220
+ let transaction = this.open.get(key);
221
+ if (!transaction) {
222
+ transaction = openBoundTransaction(url, config);
223
+ this.open.set(key, transaction);
224
+ }
225
+ return transaction.then(({ db }) => db);
226
+ }
227
+ async rollbackAll() {
228
+ const transactions = [...this.open.values()];
229
+ this.open.clear();
230
+ const failed = (await Promise.allSettled(transactions.map(async (transaction) => (await transaction).rollback()))).find((result) => result.status === "rejected");
231
+ if (failed) throw failed.reason;
232
+ }
233
+ };
234
+ //#endregion
235
+ exports.TransactionRegistry = TransactionRegistry;
236
+ exports.connectionConfig = connectionConfig;
237
+ exports.openBoundTransaction = openBoundTransaction;
238
+
239
+ //# sourceMappingURL=transactions.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactions.cjs","names":["Kysely","PostgresDialect"],"sources":["../src/transactions.ts"],"sourcesContent":["/**\n * A transaction per database, per test — as each runs deployed.\n *\n * An app's database and a schema tenant such as the auth server's are separate\n * databases to the code using them: separate connections, credentials, roles\n * and search paths. A test keeps them that way. Each gets its own connection,\n * opened into a transaction the first time the test touches it, and every one\n * is rolled back when the test ends.\n *\n * Code under test must be free to use transactions itself, and better-auth\n * does — it also calls `db.connection()`, which Kysely refuses on a\n * `Transaction`. So what a test hands over is not a `Transaction` but a Kysely\n * over the one connection already inside the test's transaction, on which a\n * `BEGIN` becomes a savepoint and a `COMMIT` releases it. Nothing the code does\n * can end the transaction the test will roll back.\n *\n * A statement the code runs outside a transaction of its own gets a savepoint\n * too, so a failure — a duplicate key two concurrent requests raced to insert —\n * fails that statement alone, as it would deployed, rather than every request\n * the test makes after it.\n *\n * A savepoint the test or the code opens by name — `savepoint refused`, a\n * statement expected to fail, `rollback to savepoint refused` — is its own\n * transaction control, and passes through untouched. While one is open,\n * statements run in it as they would inside a `begin`: unwrapped, so a failure\n * aborts the transaction until the matching `rollback to savepoint`.\n */\n\nimport { Kysely, type KyselyConfig, PostgresDialect } from 'kysely';\nimport pg from 'pg';\n\n/** How to reach one database: a URL, and its search path as a startup option. */\nexport interface ConnectionConfig {\n\tconnectionString: string;\n\toptions?: string;\n}\n\n/**\n * The connection settings for a database URL.\n *\n * `?search_path=` is not a libpq parameter: a URL carrying it connects and then\n * resolves every name against `public`. Postgres takes it as a startup option,\n * which is what a schema tenant's URL needs to reach its own tables.\n */\nexport function connectionConfig(url: string): ConnectionConfig {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tif (!searchPath) return { connectionString: url };\n\n\tparsed.searchParams.delete('search_path');\n\treturn {\n\t\tconnectionString: parsed.toString(),\n\t\toptions: `-c search_path=${searchPath}`,\n\t};\n}\n\n/** One database's connection, inside a transaction the test will roll back. */\nexport interface BoundTransaction {\n\t/** Everything queried through this runs inside the test's transaction. */\n\treadonly db: Kysely<any>;\n\t/** Undo everything, and let the connection go. */\n\trollback(): Promise<void>;\n}\n\n/**\n * Open a connection to `url` and begin the transaction a test will roll back.\n *\n * `config` is what the database's own clients are built with — its plugins\n * above all. The code under test is handed this Kysely in place of its own, so\n * one built without them runs different queries than production does.\n */\nexport async function openBoundTransaction(\n\turl: string,\n\tconfig: Omit<KyselyConfig, 'dialect'> = {},\n): Promise<BoundTransaction> {\n\tconst client = new pg.Client(connectionConfig(url));\n\tawait client.connect();\n\tawait client.query('BEGIN');\n\t// Shared by every checkout of the connection: concurrent requests in one\n\t// test each check it out, and all of them take turns on it.\n\tconst inTurn = turns();\n\t// The savepoints opened by name, shared by every checkout as the\n\t// connection's transaction is.\n\tconst named: NamedSavepoint[] = [];\n\n\treturn {\n\t\tdb: new Kysely({\n\t\t\t...config,\n\t\t\tdialect: new PostgresDialect({\n\t\t\t\t// A \"pool\" of the one connection. Released never, ended never: the\n\t\t\t\t// test owns it, and it closes in `rollback`.\n\t\t\t\tpool: {\n\t\t\t\t\tconnect: async () => savepointing(client, inTurn, named),\n\t\t\t\t\tend: async () => {},\n\t\t\t\t} as unknown as pg.Pool,\n\t\t\t}),\n\t\t}),\n\t\tasync rollback() {\n\t\t\ttry {\n\t\t\t\tawait client.query('ROLLBACK');\n\t\t\t} finally {\n\t\t\t\tawait client.end();\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * The client, with transaction control turned into savepoints.\n *\n * Kysely issues `begin`/`commit`/`rollback` as plain statements; this is the\n * one place they can be caught. A depth counter names the savepoints, so\n * nested transactions nest.\n */\nfunction savepointing(\n\tclient: pg.Client,\n\tinTurn: Turns,\n\tnamed: NamedSavepoint[],\n): pg.PoolClient {\n\tlet depth = 0;\n\tconst query = client.query.bind(client) as (\n\t\t...args: unknown[]\n\t) => Promise<unknown>;\n\tconst wrapped = Object.create(client) as pg.PoolClient;\n\n\t// Ending one of the code's transactions ends every savepoint opened in it.\n\tconst closeTransaction = (level: number) => {\n\t\tfor (let i = named.length - 1; i >= 0; i--) {\n\t\t\tconst savepoint = named[i]!;\n\t\t\tif (savepoint.checkout === wrapped && savepoint.depth >= level) {\n\t\t\t\tnamed.splice(i, 1);\n\t\t\t}\n\t\t}\n\t};\n\n\tObject.assign(wrapped, {\n\t\tquery: (text: unknown, ...rest: unknown[]) => {\n\t\t\t// A cursor, a query config, a callback: not what Kysely sends for a\n\t\t\t// statement, and nothing to wrap.\n\t\t\tif (typeof text !== 'string' || typeof rest.at(-1) === 'function') {\n\t\t\t\treturn query(text, ...rest);\n\t\t\t}\n\t\t\tconst statement = text.trim().toLowerCase();\n\n\t\t\tif (statement === 'begin' || statement.startsWith('start transaction')) {\n\t\t\t\tconst savepoint = `test_sp_${++depth}`;\n\t\t\t\treturn inTurn(() => query(`SAVEPOINT ${savepoint}`));\n\t\t\t}\n\t\t\tif (statement === 'commit') {\n\t\t\t\tconst level = depth--;\n\t\t\t\tcloseTransaction(level);\n\t\t\t\treturn inTurn(() => query(`RELEASE SAVEPOINT test_sp_${level}`));\n\t\t\t}\n\t\t\tif (statement === 'rollback') {\n\t\t\t\tconst level = depth--;\n\t\t\t\tcloseTransaction(level);\n\t\t\t\treturn inTurn(() => query(`ROLLBACK TO SAVEPOINT test_sp_${level}`));\n\t\t\t}\n\n\t\t\tconst control = savepointControl(text);\n\t\t\tif (control) {\n\t\t\t\treturn inTurn(async () => {\n\t\t\t\t\tconst result = await query(text, ...rest);\n\t\t\t\t\ttrack(named, control, { checkout: wrapped, depth });\n\t\t\t\t\treturn result;\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (depth > 0 || named.length > 0) {\n\t\t\t\treturn inTurn(() => query(text, ...rest));\n\t\t\t}\n\t\t\treturn inTurn(() => autocommitted(query, text, rest));\n\t\t},\n\t\trelease: () => {},\n\t});\n\treturn wrapped;\n}\n\n/** A savepoint the test or the code opened by name, and where it opened it. */\ninterface NamedSavepoint {\n\tname: string;\n\t/** The checkout, and its transaction depth, the savepoint was opened at. */\n\tcheckout: pg.PoolClient;\n\tdepth: number;\n}\n\n/** `SAVEPOINT x`, `RELEASE [SAVEPOINT] x`, `ROLLBACK [WORK] TO [SAVEPOINT] x`. */\ninterface SavepointControl {\n\taction: 'savepoint' | 'release' | 'rollback to';\n\tname: string;\n}\n\nconst IDENTIFIER = String.raw`(\"(?:[^\"]|\"\")+\"|[^\\s;\"]+)`;\nconst SAVEPOINT_CONTROL: [SavepointControl['action'], RegExp][] = [\n\t['savepoint', new RegExp(String.raw`^savepoint\\s+${IDENTIFIER}\\s*;?$`, 'i')],\n\t[\n\t\t'release',\n\t\tnew RegExp(\n\t\t\tString.raw`^release\\s+(?:savepoint\\s+)?${IDENTIFIER}\\s*;?$`,\n\t\t\t'i',\n\t\t),\n\t],\n\t[\n\t\t'rollback to',\n\t\tnew RegExp(\n\t\t\tString.raw`^rollback\\s+(?:(?:work|transaction)\\s+)?to\\s+(?:savepoint\\s+)?${IDENTIFIER}\\s*;?$`,\n\t\t\t'i',\n\t\t),\n\t],\n];\n\n/**\n * The savepoint a statement opens, releases or rolls back to, named as\n * Postgres names it: an unquoted name folded to lower case, a quoted one as\n * written.\n */\nfunction savepointControl(text: string): SavepointControl | undefined {\n\tconst statement = text.trim();\n\tfor (const [action, pattern] of SAVEPOINT_CONTROL) {\n\t\tconst match = pattern.exec(statement);\n\t\tif (!match) continue;\n\t\tconst identifier = match[1]!;\n\t\tconst name = identifier.startsWith('\"')\n\t\t\t? identifier.slice(1, -1).replaceAll('\"\"', '\"')\n\t\t\t: identifier.toLowerCase();\n\t\treturn { action, name };\n\t}\n\treturn undefined;\n}\n\n/**\n * Keep the named savepoints as Postgres does: `RELEASE x` ends x and every\n * savepoint after it; `ROLLBACK TO x` ends those after it and keeps x open.\n */\nfunction track(\n\tnamed: NamedSavepoint[],\n\tcontrol: SavepointControl,\n\tat: Omit<NamedSavepoint, 'name'>,\n): void {\n\tif (control.action === 'savepoint') {\n\t\tnamed.push({ name: control.name, ...at });\n\t\treturn;\n\t}\n\tconst index = named.findLastIndex((s) => s.name === control.name);\n\t// One opened before this connection was handed over, or by a name the\n\t// wrapper uses itself: nothing tracked to end.\n\tif (index === -1) return;\n\tnamed.splice(control.action === 'release' ? index : index + 1);\n}\n\n/** Runs work in the order it was asked for, each after the last has settled. */\ntype Turns = <T>(work: () => Promise<T>) => Promise<T>;\n\n/**\n * One statement at a time on a connection, in the order they were asked for.\n *\n * The connection runs them one at a time anyway; this keeps a statement and\n * the savepoint around it together, so nothing another request sends lands\n * between them — a `RELEASE` also ends every savepoint opened after its own.\n */\nfunction turns(): Turns {\n\tlet queue: Promise<unknown> = Promise.resolve();\n\treturn (work) => {\n\t\tconst turn = queue.then(work, work);\n\t\tqueue = turn.catch(() => {});\n\t\treturn turn;\n\t};\n}\n\n/**\n * A statement run outside any transaction the code opened, failing the way\n * it would deployed: on its own.\n *\n * Deployed, such a statement is its own transaction, so one that fails — a\n * duplicate key two concurrent requests both tried to insert — fails alone,\n * and code written for that (Better Auth's rate limiter reads the row the\n * other request inserted, and carries on) does carry on. Inside the test's\n * transaction a failed statement would abort everything after it, every\n * request of the test included. A savepoint around it puts back what\n * deployed would have.\n */\nasync function autocommitted(\n\tquery: (...args: unknown[]) => Promise<unknown>,\n\ttext: string,\n\trest: unknown[],\n): Promise<unknown> {\n\tawait query('SAVEPOINT test_statement');\n\ttry {\n\t\tconst result = await query(text, ...rest);\n\t\tawait query('RELEASE SAVEPOINT test_statement');\n\t\treturn result;\n\t} catch (error) {\n\t\tawait query('ROLLBACK TO SAVEPOINT test_statement');\n\t\tawait query('RELEASE SAVEPOINT test_statement');\n\t\tthrow error;\n\t}\n}\n\n/**\n * The transactions one test has opened, by database.\n *\n * `get` opens a database's transaction the first time it is asked for and\n * hands back the same one after that — including to a caller that asks while\n * it is still opening. `rollbackAll` ends every one of them.\n */\nexport class TransactionRegistry<TKey = string> {\n\tprivate readonly open = new Map<TKey, Promise<BoundTransaction>>();\n\n\tget(\n\t\tkey: TKey,\n\t\turl: string,\n\t\tconfig?: Omit<KyselyConfig, 'dialect'>,\n\t): Promise<Kysely<any>> {\n\t\tlet transaction = this.open.get(key);\n\t\tif (!transaction) {\n\t\t\ttransaction = openBoundTransaction(url, config);\n\t\t\tthis.open.set(key, transaction);\n\t\t}\n\t\treturn transaction.then(({ db }) => db);\n\t}\n\n\tasync rollbackAll(): Promise<void> {\n\t\tconst transactions = [...this.open.values()];\n\t\tthis.open.clear();\n\n\t\tconst results = await Promise.allSettled(\n\t\t\ttransactions.map(async (transaction) => (await transaction).rollback()),\n\t\t);\n\t\tconst failed = results.find(\n\t\t\t(result): result is PromiseRejectedResult => result.status === 'rejected',\n\t\t);\n\t\tif (failed) throw failed.reason;\n\t}\n}\n"],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { Kysely, KyselyConfig } from "kysely";
2
+ //#region src/transactions.d.ts
3
+ /** How to reach one database: a URL, and its search path as a startup option. */
4
+ export interface ConnectionConfig {
5
+ connectionString: string;
6
+ options?: string;
7
+ }
8
+ /**
9
+ * The connection settings for a database URL.
10
+ *
11
+ * `?search_path=` is not a libpq parameter: a URL carrying it connects and then
12
+ * resolves every name against `public`. Postgres takes it as a startup option,
13
+ * which is what a schema tenant's URL needs to reach its own tables.
14
+ */
15
+ export declare function connectionConfig(url: string): ConnectionConfig;
16
+ /** One database's connection, inside a transaction the test will roll back. */
17
+ export interface BoundTransaction {
18
+ /** Everything queried through this runs inside the test's transaction. */
19
+ readonly db: Kysely<any>;
20
+ /** Undo everything, and let the connection go. */
21
+ rollback(): Promise<void>;
22
+ }
23
+ /**
24
+ * Open a connection to `url` and begin the transaction a test will roll back.
25
+ *
26
+ * `config` is what the database's own clients are built with — its plugins
27
+ * above all. The code under test is handed this Kysely in place of its own, so
28
+ * one built without them runs different queries than production does.
29
+ */
30
+ export declare function openBoundTransaction(url: string, config?: Omit<KyselyConfig, 'dialect'>): Promise<BoundTransaction>;
31
+ /**
32
+ * The transactions one test has opened, by database.
33
+ *
34
+ * `get` opens a database's transaction the first time it is asked for and
35
+ * hands back the same one after that — including to a caller that asks while
36
+ * it is still opening. `rollbackAll` ends every one of them.
37
+ */
38
+ export declare class TransactionRegistry<TKey = string> {
39
+ private readonly open;
40
+ get(key: TKey, url: string, config?: Omit<KyselyConfig, 'dialect'>): Promise<Kysely<any>>;
41
+ rollbackAll(): Promise<void>;
42
+ }
43
+ //#endregion
44
+ //# sourceMappingURL=transactions.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactions.d.cts","names":[],"sources":["../src/transactions.ts"],"mappings":";;;iBAgCiB;EAChB;EACA;;;;;;;;;wBAUe,iBAAiB,cAAc;;iBAa9B;;WAEP,IAAI;;EAEb,YAAY;;;;;;;;;wBAUS,qBACrB,aACA,SAAQ,KAAK,2BACX,QAAQ;;;;;;;;qBAsOE,oBAAoB;mBACf;EAEjB,IACC,KAAK,MACL,aACA,SAAS,KAAK,2BACZ,QAAQ;EASL,eAAe"}
@@ -0,0 +1,44 @@
1
+ import { Kysely, KyselyConfig } from "kysely";
2
+ //#region src/transactions.d.ts
3
+ /** How to reach one database: a URL, and its search path as a startup option. */
4
+ export interface ConnectionConfig {
5
+ connectionString: string;
6
+ options?: string;
7
+ }
8
+ /**
9
+ * The connection settings for a database URL.
10
+ *
11
+ * `?search_path=` is not a libpq parameter: a URL carrying it connects and then
12
+ * resolves every name against `public`. Postgres takes it as a startup option,
13
+ * which is what a schema tenant's URL needs to reach its own tables.
14
+ */
15
+ export declare function connectionConfig(url: string): ConnectionConfig;
16
+ /** One database's connection, inside a transaction the test will roll back. */
17
+ export interface BoundTransaction {
18
+ /** Everything queried through this runs inside the test's transaction. */
19
+ readonly db: Kysely<any>;
20
+ /** Undo everything, and let the connection go. */
21
+ rollback(): Promise<void>;
22
+ }
23
+ /**
24
+ * Open a connection to `url` and begin the transaction a test will roll back.
25
+ *
26
+ * `config` is what the database's own clients are built with — its plugins
27
+ * above all. The code under test is handed this Kysely in place of its own, so
28
+ * one built without them runs different queries than production does.
29
+ */
30
+ export declare function openBoundTransaction(url: string, config?: Omit<KyselyConfig, 'dialect'>): Promise<BoundTransaction>;
31
+ /**
32
+ * The transactions one test has opened, by database.
33
+ *
34
+ * `get` opens a database's transaction the first time it is asked for and
35
+ * hands back the same one after that — including to a caller that asks while
36
+ * it is still opening. `rollbackAll` ends every one of them.
37
+ */
38
+ export declare class TransactionRegistry<TKey = string> {
39
+ private readonly open;
40
+ get(key: TKey, url: string, config?: Omit<KyselyConfig, 'dialect'>): Promise<Kysely<any>>;
41
+ rollbackAll(): Promise<void>;
42
+ }
43
+ //#endregion
44
+ //# sourceMappingURL=transactions.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactions.d.mts","names":[],"sources":["../src/transactions.ts"],"mappings":";;;iBAgCiB;EAChB;EACA;;;;;;;;;wBAUe,iBAAiB,cAAc;;iBAa9B;;WAEP,IAAI;;EAEb,YAAY;;;;;;;;;wBAUS,qBACrB,aACA,SAAQ,KAAK,2BACX,QAAQ;;;;;;;;qBAsOE,oBAAoB;mBACf;EAEjB,IACC,KAAK,MACL,aACA,SAAS,KAAK,2BACZ,QAAQ;EASL,eAAe"}
@@ -0,0 +1,234 @@
1
+ import pg from "pg";
2
+ import { Kysely, PostgresDialect } from "kysely";
3
+ //#region src/transactions.ts
4
+ /**
5
+ * A transaction per database, per test — as each runs deployed.
6
+ *
7
+ * An app's database and a schema tenant such as the auth server's are separate
8
+ * databases to the code using them: separate connections, credentials, roles
9
+ * and search paths. A test keeps them that way. Each gets its own connection,
10
+ * opened into a transaction the first time the test touches it, and every one
11
+ * is rolled back when the test ends.
12
+ *
13
+ * Code under test must be free to use transactions itself, and better-auth
14
+ * does — it also calls `db.connection()`, which Kysely refuses on a
15
+ * `Transaction`. So what a test hands over is not a `Transaction` but a Kysely
16
+ * over the one connection already inside the test's transaction, on which a
17
+ * `BEGIN` becomes a savepoint and a `COMMIT` releases it. Nothing the code does
18
+ * can end the transaction the test will roll back.
19
+ *
20
+ * A statement the code runs outside a transaction of its own gets a savepoint
21
+ * too, so a failure — a duplicate key two concurrent requests raced to insert —
22
+ * fails that statement alone, as it would deployed, rather than every request
23
+ * the test makes after it.
24
+ *
25
+ * A savepoint the test or the code opens by name — `savepoint refused`, a
26
+ * statement expected to fail, `rollback to savepoint refused` — is its own
27
+ * transaction control, and passes through untouched. While one is open,
28
+ * statements run in it as they would inside a `begin`: unwrapped, so a failure
29
+ * aborts the transaction until the matching `rollback to savepoint`.
30
+ */
31
+ /**
32
+ * The connection settings for a database URL.
33
+ *
34
+ * `?search_path=` is not a libpq parameter: a URL carrying it connects and then
35
+ * resolves every name against `public`. Postgres takes it as a startup option,
36
+ * which is what a schema tenant's URL needs to reach its own tables.
37
+ */
38
+ function connectionConfig(url) {
39
+ const parsed = new URL(url);
40
+ const searchPath = parsed.searchParams.get("search_path");
41
+ if (!searchPath) return { connectionString: url };
42
+ parsed.searchParams.delete("search_path");
43
+ return {
44
+ connectionString: parsed.toString(),
45
+ options: `-c search_path=${searchPath}`
46
+ };
47
+ }
48
+ /**
49
+ * Open a connection to `url` and begin the transaction a test will roll back.
50
+ *
51
+ * `config` is what the database's own clients are built with — its plugins
52
+ * above all. The code under test is handed this Kysely in place of its own, so
53
+ * one built without them runs different queries than production does.
54
+ */
55
+ async function openBoundTransaction(url, config = {}) {
56
+ const client = new pg.Client(connectionConfig(url));
57
+ await client.connect();
58
+ await client.query("BEGIN");
59
+ const inTurn = turns();
60
+ const named = [];
61
+ return {
62
+ db: new Kysely({
63
+ ...config,
64
+ dialect: new PostgresDialect({ pool: {
65
+ connect: async () => savepointing(client, inTurn, named),
66
+ end: async () => {}
67
+ } })
68
+ }),
69
+ async rollback() {
70
+ try {
71
+ await client.query("ROLLBACK");
72
+ } finally {
73
+ await client.end();
74
+ }
75
+ }
76
+ };
77
+ }
78
+ /**
79
+ * The client, with transaction control turned into savepoints.
80
+ *
81
+ * Kysely issues `begin`/`commit`/`rollback` as plain statements; this is the
82
+ * one place they can be caught. A depth counter names the savepoints, so
83
+ * nested transactions nest.
84
+ */
85
+ function savepointing(client, inTurn, named) {
86
+ let depth = 0;
87
+ const query = client.query.bind(client);
88
+ const wrapped = Object.create(client);
89
+ const closeTransaction = (level) => {
90
+ for (let i = named.length - 1; i >= 0; i--) {
91
+ const savepoint = named[i];
92
+ if (savepoint.checkout === wrapped && savepoint.depth >= level) named.splice(i, 1);
93
+ }
94
+ };
95
+ Object.assign(wrapped, {
96
+ query: (text, ...rest) => {
97
+ if (typeof text !== "string" || typeof rest.at(-1) === "function") return query(text, ...rest);
98
+ const statement = text.trim().toLowerCase();
99
+ if (statement === "begin" || statement.startsWith("start transaction")) {
100
+ const savepoint = `test_sp_${++depth}`;
101
+ return inTurn(() => query(`SAVEPOINT ${savepoint}`));
102
+ }
103
+ if (statement === "commit") {
104
+ const level = depth--;
105
+ closeTransaction(level);
106
+ return inTurn(() => query(`RELEASE SAVEPOINT test_sp_${level}`));
107
+ }
108
+ if (statement === "rollback") {
109
+ const level = depth--;
110
+ closeTransaction(level);
111
+ return inTurn(() => query(`ROLLBACK TO SAVEPOINT test_sp_${level}`));
112
+ }
113
+ const control = savepointControl(text);
114
+ if (control) return inTurn(async () => {
115
+ const result = await query(text, ...rest);
116
+ track(named, control, {
117
+ checkout: wrapped,
118
+ depth
119
+ });
120
+ return result;
121
+ });
122
+ if (depth > 0 || named.length > 0) return inTurn(() => query(text, ...rest));
123
+ return inTurn(() => autocommitted(query, text, rest));
124
+ },
125
+ release: () => {}
126
+ });
127
+ return wrapped;
128
+ }
129
+ const IDENTIFIER = String.raw`("(?:[^"]|"")+"|[^\s;"]+)`;
130
+ const SAVEPOINT_CONTROL = [
131
+ ["savepoint", new RegExp(String.raw`^savepoint\s+${IDENTIFIER}\s*;?$`, "i")],
132
+ ["release", new RegExp(String.raw`^release\s+(?:savepoint\s+)?${IDENTIFIER}\s*;?$`, "i")],
133
+ ["rollback to", new RegExp(String.raw`^rollback\s+(?:(?:work|transaction)\s+)?to\s+(?:savepoint\s+)?${IDENTIFIER}\s*;?$`, "i")]
134
+ ];
135
+ /**
136
+ * The savepoint a statement opens, releases or rolls back to, named as
137
+ * Postgres names it: an unquoted name folded to lower case, a quoted one as
138
+ * written.
139
+ */
140
+ function savepointControl(text) {
141
+ const statement = text.trim();
142
+ for (const [action, pattern] of SAVEPOINT_CONTROL) {
143
+ const match = pattern.exec(statement);
144
+ if (!match) continue;
145
+ const identifier = match[1];
146
+ return {
147
+ action,
148
+ name: identifier.startsWith("\"") ? identifier.slice(1, -1).replaceAll("\"\"", "\"") : identifier.toLowerCase()
149
+ };
150
+ }
151
+ }
152
+ /**
153
+ * Keep the named savepoints as Postgres does: `RELEASE x` ends x and every
154
+ * savepoint after it; `ROLLBACK TO x` ends those after it and keeps x open.
155
+ */
156
+ function track(named, control, at) {
157
+ if (control.action === "savepoint") {
158
+ named.push({
159
+ name: control.name,
160
+ ...at
161
+ });
162
+ return;
163
+ }
164
+ const index = named.findLastIndex((s) => s.name === control.name);
165
+ if (index === -1) return;
166
+ named.splice(control.action === "release" ? index : index + 1);
167
+ }
168
+ /**
169
+ * One statement at a time on a connection, in the order they were asked for.
170
+ *
171
+ * The connection runs them one at a time anyway; this keeps a statement and
172
+ * the savepoint around it together, so nothing another request sends lands
173
+ * between them — a `RELEASE` also ends every savepoint opened after its own.
174
+ */
175
+ function turns() {
176
+ let queue = Promise.resolve();
177
+ return (work) => {
178
+ const turn = queue.then(work, work);
179
+ queue = turn.catch(() => {});
180
+ return turn;
181
+ };
182
+ }
183
+ /**
184
+ * A statement run outside any transaction the code opened, failing the way
185
+ * it would deployed: on its own.
186
+ *
187
+ * Deployed, such a statement is its own transaction, so one that fails — a
188
+ * duplicate key two concurrent requests both tried to insert — fails alone,
189
+ * and code written for that (Better Auth's rate limiter reads the row the
190
+ * other request inserted, and carries on) does carry on. Inside the test's
191
+ * transaction a failed statement would abort everything after it, every
192
+ * request of the test included. A savepoint around it puts back what
193
+ * deployed would have.
194
+ */
195
+ async function autocommitted(query, text, rest) {
196
+ await query("SAVEPOINT test_statement");
197
+ try {
198
+ const result = await query(text, ...rest);
199
+ await query("RELEASE SAVEPOINT test_statement");
200
+ return result;
201
+ } catch (error) {
202
+ await query("ROLLBACK TO SAVEPOINT test_statement");
203
+ await query("RELEASE SAVEPOINT test_statement");
204
+ throw error;
205
+ }
206
+ }
207
+ /**
208
+ * The transactions one test has opened, by database.
209
+ *
210
+ * `get` opens a database's transaction the first time it is asked for and
211
+ * hands back the same one after that — including to a caller that asks while
212
+ * it is still opening. `rollbackAll` ends every one of them.
213
+ */
214
+ var TransactionRegistry = class {
215
+ open = /* @__PURE__ */ new Map();
216
+ get(key, url, config) {
217
+ let transaction = this.open.get(key);
218
+ if (!transaction) {
219
+ transaction = openBoundTransaction(url, config);
220
+ this.open.set(key, transaction);
221
+ }
222
+ return transaction.then(({ db }) => db);
223
+ }
224
+ async rollbackAll() {
225
+ const transactions = [...this.open.values()];
226
+ this.open.clear();
227
+ const failed = (await Promise.allSettled(transactions.map(async (transaction) => (await transaction).rollback()))).find((result) => result.status === "rejected");
228
+ if (failed) throw failed.reason;
229
+ }
230
+ };
231
+ //#endregion
232
+ export { TransactionRegistry, connectionConfig, openBoundTransaction };
233
+
234
+ //# sourceMappingURL=transactions.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactions.mjs","names":[],"sources":["../src/transactions.ts"],"sourcesContent":["/**\n * A transaction per database, per test — as each runs deployed.\n *\n * An app's database and a schema tenant such as the auth server's are separate\n * databases to the code using them: separate connections, credentials, roles\n * and search paths. A test keeps them that way. Each gets its own connection,\n * opened into a transaction the first time the test touches it, and every one\n * is rolled back when the test ends.\n *\n * Code under test must be free to use transactions itself, and better-auth\n * does — it also calls `db.connection()`, which Kysely refuses on a\n * `Transaction`. So what a test hands over is not a `Transaction` but a Kysely\n * over the one connection already inside the test's transaction, on which a\n * `BEGIN` becomes a savepoint and a `COMMIT` releases it. Nothing the code does\n * can end the transaction the test will roll back.\n *\n * A statement the code runs outside a transaction of its own gets a savepoint\n * too, so a failure — a duplicate key two concurrent requests raced to insert —\n * fails that statement alone, as it would deployed, rather than every request\n * the test makes after it.\n *\n * A savepoint the test or the code opens by name — `savepoint refused`, a\n * statement expected to fail, `rollback to savepoint refused` — is its own\n * transaction control, and passes through untouched. While one is open,\n * statements run in it as they would inside a `begin`: unwrapped, so a failure\n * aborts the transaction until the matching `rollback to savepoint`.\n */\n\nimport { Kysely, type KyselyConfig, PostgresDialect } from 'kysely';\nimport pg from 'pg';\n\n/** How to reach one database: a URL, and its search path as a startup option. */\nexport interface ConnectionConfig {\n\tconnectionString: string;\n\toptions?: string;\n}\n\n/**\n * The connection settings for a database URL.\n *\n * `?search_path=` is not a libpq parameter: a URL carrying it connects and then\n * resolves every name against `public`. Postgres takes it as a startup option,\n * which is what a schema tenant's URL needs to reach its own tables.\n */\nexport function connectionConfig(url: string): ConnectionConfig {\n\tconst parsed = new URL(url);\n\tconst searchPath = parsed.searchParams.get('search_path');\n\tif (!searchPath) return { connectionString: url };\n\n\tparsed.searchParams.delete('search_path');\n\treturn {\n\t\tconnectionString: parsed.toString(),\n\t\toptions: `-c search_path=${searchPath}`,\n\t};\n}\n\n/** One database's connection, inside a transaction the test will roll back. */\nexport interface BoundTransaction {\n\t/** Everything queried through this runs inside the test's transaction. */\n\treadonly db: Kysely<any>;\n\t/** Undo everything, and let the connection go. */\n\trollback(): Promise<void>;\n}\n\n/**\n * Open a connection to `url` and begin the transaction a test will roll back.\n *\n * `config` is what the database's own clients are built with — its plugins\n * above all. The code under test is handed this Kysely in place of its own, so\n * one built without them runs different queries than production does.\n */\nexport async function openBoundTransaction(\n\turl: string,\n\tconfig: Omit<KyselyConfig, 'dialect'> = {},\n): Promise<BoundTransaction> {\n\tconst client = new pg.Client(connectionConfig(url));\n\tawait client.connect();\n\tawait client.query('BEGIN');\n\t// Shared by every checkout of the connection: concurrent requests in one\n\t// test each check it out, and all of them take turns on it.\n\tconst inTurn = turns();\n\t// The savepoints opened by name, shared by every checkout as the\n\t// connection's transaction is.\n\tconst named: NamedSavepoint[] = [];\n\n\treturn {\n\t\tdb: new Kysely({\n\t\t\t...config,\n\t\t\tdialect: new PostgresDialect({\n\t\t\t\t// A \"pool\" of the one connection. Released never, ended never: the\n\t\t\t\t// test owns it, and it closes in `rollback`.\n\t\t\t\tpool: {\n\t\t\t\t\tconnect: async () => savepointing(client, inTurn, named),\n\t\t\t\t\tend: async () => {},\n\t\t\t\t} as unknown as pg.Pool,\n\t\t\t}),\n\t\t}),\n\t\tasync rollback() {\n\t\t\ttry {\n\t\t\t\tawait client.query('ROLLBACK');\n\t\t\t} finally {\n\t\t\t\tawait client.end();\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * The client, with transaction control turned into savepoints.\n *\n * Kysely issues `begin`/`commit`/`rollback` as plain statements; this is the\n * one place they can be caught. A depth counter names the savepoints, so\n * nested transactions nest.\n */\nfunction savepointing(\n\tclient: pg.Client,\n\tinTurn: Turns,\n\tnamed: NamedSavepoint[],\n): pg.PoolClient {\n\tlet depth = 0;\n\tconst query = client.query.bind(client) as (\n\t\t...args: unknown[]\n\t) => Promise<unknown>;\n\tconst wrapped = Object.create(client) as pg.PoolClient;\n\n\t// Ending one of the code's transactions ends every savepoint opened in it.\n\tconst closeTransaction = (level: number) => {\n\t\tfor (let i = named.length - 1; i >= 0; i--) {\n\t\t\tconst savepoint = named[i]!;\n\t\t\tif (savepoint.checkout === wrapped && savepoint.depth >= level) {\n\t\t\t\tnamed.splice(i, 1);\n\t\t\t}\n\t\t}\n\t};\n\n\tObject.assign(wrapped, {\n\t\tquery: (text: unknown, ...rest: unknown[]) => {\n\t\t\t// A cursor, a query config, a callback: not what Kysely sends for a\n\t\t\t// statement, and nothing to wrap.\n\t\t\tif (typeof text !== 'string' || typeof rest.at(-1) === 'function') {\n\t\t\t\treturn query(text, ...rest);\n\t\t\t}\n\t\t\tconst statement = text.trim().toLowerCase();\n\n\t\t\tif (statement === 'begin' || statement.startsWith('start transaction')) {\n\t\t\t\tconst savepoint = `test_sp_${++depth}`;\n\t\t\t\treturn inTurn(() => query(`SAVEPOINT ${savepoint}`));\n\t\t\t}\n\t\t\tif (statement === 'commit') {\n\t\t\t\tconst level = depth--;\n\t\t\t\tcloseTransaction(level);\n\t\t\t\treturn inTurn(() => query(`RELEASE SAVEPOINT test_sp_${level}`));\n\t\t\t}\n\t\t\tif (statement === 'rollback') {\n\t\t\t\tconst level = depth--;\n\t\t\t\tcloseTransaction(level);\n\t\t\t\treturn inTurn(() => query(`ROLLBACK TO SAVEPOINT test_sp_${level}`));\n\t\t\t}\n\n\t\t\tconst control = savepointControl(text);\n\t\t\tif (control) {\n\t\t\t\treturn inTurn(async () => {\n\t\t\t\t\tconst result = await query(text, ...rest);\n\t\t\t\t\ttrack(named, control, { checkout: wrapped, depth });\n\t\t\t\t\treturn result;\n\t\t\t\t});\n\t\t\t}\n\t\t\tif (depth > 0 || named.length > 0) {\n\t\t\t\treturn inTurn(() => query(text, ...rest));\n\t\t\t}\n\t\t\treturn inTurn(() => autocommitted(query, text, rest));\n\t\t},\n\t\trelease: () => {},\n\t});\n\treturn wrapped;\n}\n\n/** A savepoint the test or the code opened by name, and where it opened it. */\ninterface NamedSavepoint {\n\tname: string;\n\t/** The checkout, and its transaction depth, the savepoint was opened at. */\n\tcheckout: pg.PoolClient;\n\tdepth: number;\n}\n\n/** `SAVEPOINT x`, `RELEASE [SAVEPOINT] x`, `ROLLBACK [WORK] TO [SAVEPOINT] x`. */\ninterface SavepointControl {\n\taction: 'savepoint' | 'release' | 'rollback to';\n\tname: string;\n}\n\nconst IDENTIFIER = String.raw`(\"(?:[^\"]|\"\")+\"|[^\\s;\"]+)`;\nconst SAVEPOINT_CONTROL: [SavepointControl['action'], RegExp][] = [\n\t['savepoint', new RegExp(String.raw`^savepoint\\s+${IDENTIFIER}\\s*;?$`, 'i')],\n\t[\n\t\t'release',\n\t\tnew RegExp(\n\t\t\tString.raw`^release\\s+(?:savepoint\\s+)?${IDENTIFIER}\\s*;?$`,\n\t\t\t'i',\n\t\t),\n\t],\n\t[\n\t\t'rollback to',\n\t\tnew RegExp(\n\t\t\tString.raw`^rollback\\s+(?:(?:work|transaction)\\s+)?to\\s+(?:savepoint\\s+)?${IDENTIFIER}\\s*;?$`,\n\t\t\t'i',\n\t\t),\n\t],\n];\n\n/**\n * The savepoint a statement opens, releases or rolls back to, named as\n * Postgres names it: an unquoted name folded to lower case, a quoted one as\n * written.\n */\nfunction savepointControl(text: string): SavepointControl | undefined {\n\tconst statement = text.trim();\n\tfor (const [action, pattern] of SAVEPOINT_CONTROL) {\n\t\tconst match = pattern.exec(statement);\n\t\tif (!match) continue;\n\t\tconst identifier = match[1]!;\n\t\tconst name = identifier.startsWith('\"')\n\t\t\t? identifier.slice(1, -1).replaceAll('\"\"', '\"')\n\t\t\t: identifier.toLowerCase();\n\t\treturn { action, name };\n\t}\n\treturn undefined;\n}\n\n/**\n * Keep the named savepoints as Postgres does: `RELEASE x` ends x and every\n * savepoint after it; `ROLLBACK TO x` ends those after it and keeps x open.\n */\nfunction track(\n\tnamed: NamedSavepoint[],\n\tcontrol: SavepointControl,\n\tat: Omit<NamedSavepoint, 'name'>,\n): void {\n\tif (control.action === 'savepoint') {\n\t\tnamed.push({ name: control.name, ...at });\n\t\treturn;\n\t}\n\tconst index = named.findLastIndex((s) => s.name === control.name);\n\t// One opened before this connection was handed over, or by a name the\n\t// wrapper uses itself: nothing tracked to end.\n\tif (index === -1) return;\n\tnamed.splice(control.action === 'release' ? index : index + 1);\n}\n\n/** Runs work in the order it was asked for, each after the last has settled. */\ntype Turns = <T>(work: () => Promise<T>) => Promise<T>;\n\n/**\n * One statement at a time on a connection, in the order they were asked for.\n *\n * The connection runs them one at a time anyway; this keeps a statement and\n * the savepoint around it together, so nothing another request sends lands\n * between them — a `RELEASE` also ends every savepoint opened after its own.\n */\nfunction turns(): Turns {\n\tlet queue: Promise<unknown> = Promise.resolve();\n\treturn (work) => {\n\t\tconst turn = queue.then(work, work);\n\t\tqueue = turn.catch(() => {});\n\t\treturn turn;\n\t};\n}\n\n/**\n * A statement run outside any transaction the code opened, failing the way\n * it would deployed: on its own.\n *\n * Deployed, such a statement is its own transaction, so one that fails — a\n * duplicate key two concurrent requests both tried to insert — fails alone,\n * and code written for that (Better Auth's rate limiter reads the row the\n * other request inserted, and carries on) does carry on. Inside the test's\n * transaction a failed statement would abort everything after it, every\n * request of the test included. A savepoint around it puts back what\n * deployed would have.\n */\nasync function autocommitted(\n\tquery: (...args: unknown[]) => Promise<unknown>,\n\ttext: string,\n\trest: unknown[],\n): Promise<unknown> {\n\tawait query('SAVEPOINT test_statement');\n\ttry {\n\t\tconst result = await query(text, ...rest);\n\t\tawait query('RELEASE SAVEPOINT test_statement');\n\t\treturn result;\n\t} catch (error) {\n\t\tawait query('ROLLBACK TO SAVEPOINT test_statement');\n\t\tawait query('RELEASE SAVEPOINT test_statement');\n\t\tthrow error;\n\t}\n}\n\n/**\n * The transactions one test has opened, by database.\n *\n * `get` opens a database's transaction the first time it is asked for and\n * hands back the same one after that — including to a caller that asks while\n * it is still opening. `rollbackAll` ends every one of them.\n */\nexport class TransactionRegistry<TKey = string> {\n\tprivate readonly open = new Map<TKey, Promise<BoundTransaction>>();\n\n\tget(\n\t\tkey: TKey,\n\t\turl: string,\n\t\tconfig?: Omit<KyselyConfig, 'dialect'>,\n\t): Promise<Kysely<any>> {\n\t\tlet transaction = this.open.get(key);\n\t\tif (!transaction) {\n\t\t\ttransaction = openBoundTransaction(url, config);\n\t\t\tthis.open.set(key, transaction);\n\t\t}\n\t\treturn transaction.then(({ db }) => db);\n\t}\n\n\tasync rollbackAll(): Promise<void> {\n\t\tconst transactions = [...this.open.values()];\n\t\tthis.open.clear();\n\n\t\tconst results = await Promise.allSettled(\n\t\t\ttransactions.map(async (transaction) => (await transaction).rollback()),\n\t\t);\n\t\tconst failed = results.find(\n\t\t\t(result): result is PromiseRejectedResult => result.status === 'rejected',\n\t\t);\n\t\tif (failed) throw failed.reason;\n\t}\n}\n"],"mappings":"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"}