@better-auth/core 1.5.0-beta.8 → 1.5.0-beta.9
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.
- package/.turbo/turbo-build.log +14 -12
- package/dist/context/global.mjs +1 -1
- package/dist/context/index.d.mts +2 -2
- package/dist/context/index.mjs +2 -2
- package/dist/context/transaction.d.mts +11 -3
- package/dist/context/transaction.mjs +52 -9
- package/dist/oauth2/validate-authorization-code.mjs +4 -4
- package/dist/social-providers/dropbox.mjs +1 -1
- package/dist/types/context.d.mts +1 -0
- package/dist/types/init-options.d.mts +19 -0
- package/dist/utils/ip.d.mts +54 -0
- package/dist/utils/ip.mjs +118 -0
- package/package.json +6 -6
- package/src/context/index.ts +1 -0
- package/src/context/transaction.ts +72 -9
- package/src/oauth2/validate-authorization-code.ts +7 -12
- package/src/oauth2/validate-token.test.ts +174 -0
- package/src/social-providers/dropbox.ts +1 -1
- package/src/types/context.ts +5 -1
- package/src/types/init-options.ts +19 -0
- package/src/utils/ip.test.ts +255 -0
- package/src/utils/ip.ts +211 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
|
|
2
|
-
> @better-auth/core@1.5.0-beta.
|
|
2
|
+
> @better-auth/core@1.5.0-beta.9 build /home/runner/work/better-auth/better-auth/packages/core
|
|
3
3
|
> tsdown
|
|
4
4
|
|
|
5
5
|
[34mℹ[39m tsdown [2mv0.19.0[22m powered by rolldown [2mv1.0.0-beta.59[22m
|
|
6
6
|
[34mℹ[39m config file: [4m/home/runner/work/better-auth/better-auth/packages/core/tsdown.config.ts[24m
|
|
7
|
-
[34mℹ[39m entry: [34msrc/index.ts, src/api/index.ts, src/async_hooks/index.ts, src/async_hooks/pure.index.ts, src/context/index.ts, src/db/index.ts, src/env/index.ts, src/error/index.ts, src/oauth2/index.ts, src/social-providers/index.ts, src/utils/deprecate.ts, src/utils/error-codes.ts, src/utils/id.ts, src/utils/json.ts, src/utils/string.ts, src/utils/url.ts, src/db/adapter/index.ts[39m
|
|
7
|
+
[34mℹ[39m entry: [34msrc/index.ts, src/api/index.ts, src/async_hooks/index.ts, src/async_hooks/pure.index.ts, src/context/index.ts, src/db/index.ts, src/env/index.ts, src/error/index.ts, src/oauth2/index.ts, src/social-providers/index.ts, src/utils/deprecate.ts, src/utils/error-codes.ts, src/utils/id.ts, src/utils/ip.ts, src/utils/json.ts, src/utils/string.ts, src/utils/url.ts, src/db/adapter/index.ts[39m
|
|
8
8
|
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
9
9
|
[34mℹ[39m Build start
|
|
10
|
+
[34mℹ[39m [2mdist/[22m[1mutils/ip.mjs[22m [2m 3.81 kB[22m [2m│ gzip: 1.29 kB[22m
|
|
10
11
|
[34mℹ[39m [2mdist/[22m[1msocial-providers/index.mjs[22m [2m 2.32 kB[22m [2m│ gzip: 0.72 kB[22m
|
|
11
12
|
[34mℹ[39m [2mdist/[22m[1mapi/index.mjs[22m [2m 1.27 kB[22m [2m│ gzip: 0.48 kB[22m
|
|
12
13
|
[34mℹ[39m [2mdist/[22m[1mutils/url.mjs[22m [2m 1.14 kB[22m [2m│ gzip: 0.51 kB[22m
|
|
13
14
|
[34mℹ[39m [2mdist/[22m[1masync_hooks/index.mjs[22m [2m 1.03 kB[22m [2m│ gzip: 0.54 kB[22m
|
|
14
15
|
[34mℹ[39m [2mdist/[22m[1masync_hooks/pure.index.mjs[22m [2m 0.99 kB[22m [2m│ gzip: 0.49 kB[22m
|
|
15
16
|
[34mℹ[39m [2mdist/[22m[1moauth2/index.mjs[22m [2m 0.87 kB[22m [2m│ gzip: 0.27 kB[22m
|
|
16
|
-
[34mℹ[39m [2mdist/[22m[1mcontext/index.mjs[22m [2m 0.
|
|
17
|
+
[34mℹ[39m [2mdist/[22m[1mcontext/index.mjs[22m [2m 0.85 kB[22m [2m│ gzip: 0.25 kB[22m
|
|
17
18
|
[34mℹ[39m [2mdist/[22m[1mdb/adapter/index.mjs[22m [2m 0.71 kB[22m [2m│ gzip: 0.22 kB[22m
|
|
18
19
|
[34mℹ[39m [2mdist/[22m[1merror/index.mjs[22m [2m 0.69 kB[22m [2m│ gzip: 0.33 kB[22m
|
|
19
20
|
[34mℹ[39m [2mdist/[22m[1mutils/json.mjs[22m [2m 0.59 kB[22m [2m│ gzip: 0.35 kB[22m
|
|
@@ -35,8 +36,8 @@
|
|
|
35
36
|
[34mℹ[39m [2mdist/[22msocial-providers/line.mjs [2m 3.57 kB[22m [2m│ gzip: 1.20 kB[22m
|
|
36
37
|
[34mℹ[39m [2mdist/[22msocial-providers/salesforce.mjs [2m 3.50 kB[22m [2m│ gzip: 1.12 kB[22m
|
|
37
38
|
[34mℹ[39m [2mdist/[22msocial-providers/microsoft-entra-id.mjs [2m 3.46 kB[22m [2m│ gzip: 1.22 kB[22m
|
|
39
|
+
[34mℹ[39m [2mdist/[22moauth2/validate-authorization-code.mjs [2m 2.80 kB[22m [2m│ gzip: 1.03 kB[22m
|
|
38
40
|
[34mℹ[39m [2mdist/[22msocial-providers/figma.mjs [2m 2.78 kB[22m [2m│ gzip: 0.99 kB[22m
|
|
39
|
-
[34mℹ[39m [2mdist/[22moauth2/validate-authorization-code.mjs [2m 2.76 kB[22m [2m│ gzip: 1.03 kB[22m
|
|
40
41
|
[34mℹ[39m [2mdist/[22msocial-providers/atlassian.mjs [2m 2.75 kB[22m [2m│ gzip: 0.99 kB[22m
|
|
41
42
|
[34mℹ[39m [2mdist/[22msocial-providers/twitter.mjs [2m 2.74 kB[22m [2m│ gzip: 0.92 kB[22m
|
|
42
43
|
[34mℹ[39m [2mdist/[22menv/color-depth.mjs [2m 2.72 kB[22m [2m│ gzip: 0.99 kB[22m
|
|
@@ -46,12 +47,13 @@
|
|
|
46
47
|
[34mℹ[39m [2mdist/[22msocial-providers/reddit.mjs [2m 2.63 kB[22m [2m│ gzip: 1.01 kB[22m
|
|
47
48
|
[34mℹ[39m [2mdist/[22msocial-providers/discord.mjs [2m 2.62 kB[22m [2m│ gzip: 1.04 kB[22m
|
|
48
49
|
[34mℹ[39m [2mdist/[22msocial-providers/github.mjs [2m 2.61 kB[22m [2m│ gzip: 0.91 kB[22m
|
|
50
|
+
[34mℹ[39m [2mdist/[22mcontext/transaction.mjs [2m 2.59 kB[22m [2m│ gzip: 0.83 kB[22m
|
|
49
51
|
[34mℹ[39m [2mdist/[22msocial-providers/vk.mjs [2m 2.59 kB[22m [2m│ gzip: 0.94 kB[22m
|
|
50
52
|
[34mℹ[39m [2mdist/[22menv/env-impl.mjs [2m 2.58 kB[22m [2m│ gzip: 0.90 kB[22m
|
|
51
53
|
[34mℹ[39m [2mdist/[22merror/codes.mjs [2m 2.56 kB[22m [2m│ gzip: 1.05 kB[22m
|
|
52
54
|
[34mℹ[39m [2mdist/[22menv/logger.mjs [2m 2.42 kB[22m [2m│ gzip: 0.94 kB[22m
|
|
53
55
|
[34mℹ[39m [2mdist/[22msocial-providers/linear.mjs [2m 2.42 kB[22m [2m│ gzip: 0.90 kB[22m
|
|
54
|
-
[34mℹ[39m [2mdist/[22msocial-providers/dropbox.mjs [2m 2.
|
|
56
|
+
[34mℹ[39m [2mdist/[22msocial-providers/dropbox.mjs [2m 2.35 kB[22m [2m│ gzip: 0.86 kB[22m
|
|
55
57
|
[34mℹ[39m [2mdist/[22msocial-providers/kakao.mjs [2m 2.31 kB[22m [2m│ gzip: 0.84 kB[22m
|
|
56
58
|
[34mℹ[39m [2mdist/[22msocial-providers/notion.mjs [2m 2.28 kB[22m [2m│ gzip: 0.86 kB[22m
|
|
57
59
|
[34mℹ[39m [2mdist/[22msocial-providers/zoom.mjs [2m 2.27 kB[22m [2m│ gzip: 0.89 kB[22m
|
|
@@ -70,7 +72,6 @@
|
|
|
70
72
|
[34mℹ[39m [2mdist/[22moauth2/create-authorization-url.mjs [2m 1.86 kB[22m [2m│ gzip: 0.73 kB[22m
|
|
71
73
|
[34mℹ[39m [2mdist/[22moauth2/client-credentials-token.mjs [2m 1.83 kB[22m [2m│ gzip: 0.77 kB[22m
|
|
72
74
|
[34mℹ[39m [2mdist/[22mcontext/request-state.mjs [2m 1.63 kB[22m [2m│ gzip: 0.59 kB[22m
|
|
73
|
-
[34mℹ[39m [2mdist/[22mcontext/transaction.mjs [2m 1.60 kB[22m [2m│ gzip: 0.55 kB[22m
|
|
74
75
|
[34mℹ[39m [2mdist/[22mdb/adapter/get-default-field-name.mjs [2m 1.41 kB[22m [2m│ gzip: 0.66 kB[22m
|
|
75
76
|
[34mℹ[39m [2mdist/[22mdb/adapter/get-default-model-name.mjs [2m 1.40 kB[22m [2m│ gzip: 0.65 kB[22m
|
|
76
77
|
[34mℹ[39m [2mdist/[22mcontext/endpoint-context.mjs [2m 1.31 kB[22m [2m│ gzip: 0.53 kB[22m
|
|
@@ -89,10 +90,11 @@
|
|
|
89
90
|
[34mℹ[39m [2mdist/[22m[32m[1msocial-providers/index.d.mts[22m[39m [2m45.59 kB[22m [2m│ gzip: 3.09 kB[22m
|
|
90
91
|
[34mℹ[39m [2mdist/[22m[32m[1mdb/adapter/index.d.mts[22m[39m [2m15.17 kB[22m [2m│ gzip: 3.60 kB[22m
|
|
91
92
|
[34mℹ[39m [2mdist/[22m[32m[1mapi/index.d.mts[22m[39m [2m 7.54 kB[22m [2m│ gzip: 1.45 kB[22m
|
|
93
|
+
[34mℹ[39m [2mdist/[22m[32m[1mutils/ip.d.mts[22m[39m [2m 1.58 kB[22m [2m│ gzip: 0.68 kB[22m
|
|
92
94
|
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.mts[22m[39m [2m 1.36 kB[22m [2m│ gzip: 0.38 kB[22m
|
|
93
95
|
[34mℹ[39m [2mdist/[22m[32m[1moauth2/index.d.mts[22m[39m [2m 1.05 kB[22m [2m│ gzip: 0.32 kB[22m
|
|
94
96
|
[34mℹ[39m [2mdist/[22m[32m[1mdb/index.d.mts[22m[39m [2m 1.04 kB[22m [2m│ gzip: 0.34 kB[22m
|
|
95
|
-
[34mℹ[39m [2mdist/[22m[32m[1mcontext/index.d.mts[22m[39m [2m 0.
|
|
97
|
+
[34mℹ[39m [2mdist/[22m[32m[1mcontext/index.d.mts[22m[39m [2m 0.97 kB[22m [2m│ gzip: 0.28 kB[22m
|
|
96
98
|
[34mℹ[39m [2mdist/[22m[32m[1mutils/error-codes.d.mts[22m[39m [2m 0.89 kB[22m [2m│ gzip: 0.45 kB[22m
|
|
97
99
|
[34mℹ[39m [2mdist/[22m[32m[1mutils/url.d.mts[22m[39m [2m 0.84 kB[22m [2m│ gzip: 0.40 kB[22m
|
|
98
100
|
[34mℹ[39m [2mdist/[22m[32m[1merror/index.d.mts[22m[39m [2m 0.75 kB[22m [2m│ gzip: 0.34 kB[22m
|
|
@@ -103,8 +105,8 @@
|
|
|
103
105
|
[34mℹ[39m [2mdist/[22m[32m[1mutils/string.d.mts[22m[39m [2m 0.14 kB[22m [2m│ gzip: 0.12 kB[22m
|
|
104
106
|
[34mℹ[39m [2mdist/[22m[32m[1mutils/json.d.mts[22m[39m [2m 0.13 kB[22m [2m│ gzip: 0.13 kB[22m
|
|
105
107
|
[34mℹ[39m [2mdist/[22m[32m[1mutils/id.d.mts[22m[39m [2m 0.12 kB[22m [2m│ gzip: 0.12 kB[22m
|
|
106
|
-
[34mℹ[39m [2mdist/[22m[32mtypes/init-options.d.mts[39m [2m38.
|
|
107
|
-
[34mℹ[39m [2mdist/[22m[32mtypes/context.d.mts[39m [2m10.
|
|
108
|
+
[34mℹ[39m [2mdist/[22m[32mtypes/init-options.d.mts[39m [2m38.97 kB[22m [2m│ gzip: 8.80 kB[22m
|
|
109
|
+
[34mℹ[39m [2mdist/[22m[32mtypes/context.d.mts[39m [2m10.15 kB[22m [2m│ gzip: 2.97 kB[22m
|
|
108
110
|
[34mℹ[39m [2mdist/[22m[32msocial-providers/zoom.d.mts[39m [2m 6.71 kB[22m [2m│ gzip: 2.29 kB[22m
|
|
109
111
|
[34mℹ[39m [2mdist/[22m[32moauth2/oauth-provider.d.mts[39m [2m 5.92 kB[22m [2m│ gzip: 1.67 kB[22m
|
|
110
112
|
[34mℹ[39m [2mdist/[22m[32msocial-providers/microsoft-entra-id.d.mts[39m [2m 5.59 kB[22m [2m│ gzip: 1.96 kB[22m
|
|
@@ -150,13 +152,13 @@
|
|
|
150
152
|
[34mℹ[39m [2mdist/[22m[32mcontext/request-state.d.mts[39m [2m 1.35 kB[22m [2m│ gzip: 0.59 kB[22m
|
|
151
153
|
[34mℹ[39m [2mdist/[22m[32mdb/adapter/factory.d.mts[39m [2m 1.30 kB[22m [2m│ gzip: 0.45 kB[22m
|
|
152
154
|
[34mℹ[39m [2mdist/[22m[32menv/env-impl.d.mts[39m [2m 1.26 kB[22m [2m│ gzip: 0.47 kB[22m
|
|
155
|
+
[34mℹ[39m [2mdist/[22m[32mcontext/transaction.d.mts[39m [2m 1.21 kB[22m [2m│ gzip: 0.51 kB[22m
|
|
153
156
|
[34mℹ[39m [2mdist/[22m[32moauth2/create-authorization-url.d.mts[39m [2m 1.05 kB[22m [2m│ gzip: 0.41 kB[22m
|
|
154
157
|
[34mℹ[39m [2mdist/[22m[32mdb/schema/account.d.mts[39m [2m 1.05 kB[22m [2m│ gzip: 0.43 kB[22m
|
|
155
158
|
[34mℹ[39m [2mdist/[22m[32mdb/adapter/get-id-field.d.mts[39m [2m 1.04 kB[22m [2m│ gzip: 0.47 kB[22m
|
|
156
159
|
[34mℹ[39m [2mdist/[22m[32moauth2/refresh-access-token.d.mts[39m [2m 1.01 kB[22m [2m│ gzip: 0.41 kB[22m
|
|
157
160
|
[34mℹ[39m [2mdist/[22m[32mcontext/endpoint-context.d.mts[39m [2m 0.99 kB[22m [2m│ gzip: 0.43 kB[22m
|
|
158
161
|
[34mℹ[39m [2mdist/[22m[32moauth2/client-credentials-token.d.mts[39m [2m 0.91 kB[22m [2m│ gzip: 0.36 kB[22m
|
|
159
|
-
[34mℹ[39m [2mdist/[22m[32mcontext/transaction.d.mts[39m [2m 0.86 kB[22m [2m│ gzip: 0.38 kB[22m
|
|
160
162
|
[34mℹ[39m [2mdist/[22m[32mtypes/index.d.mts[39m [2m 0.86 kB[22m [2m│ gzip: 0.34 kB[22m
|
|
161
163
|
[34mℹ[39m [2mdist/[22m[32mdb/schema/session.d.mts[39m [2m 0.75 kB[22m [2m│ gzip: 0.40 kB[22m
|
|
162
164
|
[34mℹ[39m [2mdist/[22m[32mdb/adapter/get-field-attributes.d.mts[39m [2m 0.72 kB[22m [2m│ gzip: 0.34 kB[22m
|
|
@@ -176,5 +178,5 @@
|
|
|
176
178
|
[34mℹ[39m [2mdist/[22m[32mdb/schema/shared.d.mts[39m [2m 0.25 kB[22m [2m│ gzip: 0.18 kB[22m
|
|
177
179
|
[34mℹ[39m [2mdist/[22m[32mcontext/global.d.mts[39m [2m 0.20 kB[22m [2m│ gzip: 0.16 kB[22m
|
|
178
180
|
[34mℹ[39m [2mdist/[22m[32menv/color-depth.d.mts[39m [2m 0.12 kB[22m [2m│ gzip: 0.11 kB[22m
|
|
179
|
-
[34mℹ[39m
|
|
180
|
-
[32m✔[39m Build complete in [
|
|
181
|
+
[34mℹ[39m 171 files, total: 454.58 kB
|
|
182
|
+
[32m✔[39m Build complete in [32m6303ms[39m
|
package/dist/context/global.mjs
CHANGED
package/dist/context/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AuthEndpointContext, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, runWithEndpointContext } from "./endpoint-context.mjs";
|
|
2
2
|
import { getBetterAuthVersion } from "./global.mjs";
|
|
3
3
|
import { RequestState, RequestStateWeakMap, defineRequestState, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, runWithRequestState } from "./request-state.mjs";
|
|
4
|
-
import { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, runWithAdapter, runWithTransaction } from "./transaction.mjs";
|
|
5
|
-
export { type AuthEndpointContext, type RequestState, type RequestStateWeakMap, defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction };
|
|
4
|
+
import { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, queueAfterTransactionHook, runWithAdapter, runWithTransaction } from "./transaction.mjs";
|
|
5
|
+
export { type AuthEndpointContext, type RequestState, type RequestStateWeakMap, defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, queueAfterTransactionHook, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction };
|
package/dist/context/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getBetterAuthVersion } from "./global.mjs";
|
|
2
2
|
import { getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, runWithEndpointContext } from "./endpoint-context.mjs";
|
|
3
3
|
import { defineRequestState, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, runWithRequestState } from "./request-state.mjs";
|
|
4
|
-
import { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, runWithAdapter, runWithTransaction } from "./transaction.mjs";
|
|
4
|
+
import { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, queueAfterTransactionHook, runWithAdapter, runWithTransaction } from "./transaction.mjs";
|
|
5
5
|
|
|
6
|
-
export { defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction };
|
|
6
|
+
export { defineRequestState, getBetterAuthVersion, getCurrentAdapter, getCurrentAuthContext, getCurrentAuthContextAsyncLocalStorage, getCurrentDBAdapterAsyncLocalStorage, getCurrentRequestState, getRequestStateAsyncLocalStorage, hasRequestState, queueAfterTransactionHook, runWithAdapter, runWithEndpointContext, runWithRequestState, runWithTransaction };
|
|
@@ -2,15 +2,23 @@ import { DBAdapter, DBTransactionAdapter } from "../db/adapter/index.mjs";
|
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
3
|
|
|
4
4
|
//#region src/context/transaction.d.ts
|
|
5
|
-
|
|
5
|
+
type HookContext = {
|
|
6
|
+
adapter: DBTransactionAdapter;
|
|
7
|
+
pendingHooks: Array<() => Promise<void>>;
|
|
8
|
+
};
|
|
6
9
|
/**
|
|
7
10
|
* This is for internal use only. Most users should use `getCurrentAdapter` instead.
|
|
8
11
|
*
|
|
9
12
|
* It is exposed for advanced use cases where you need direct access to the AsyncLocalStorage instance.
|
|
10
13
|
*/
|
|
11
|
-
declare const getCurrentDBAdapterAsyncLocalStorage: () => Promise<AsyncLocalStorage<
|
|
14
|
+
declare const getCurrentDBAdapterAsyncLocalStorage: () => Promise<AsyncLocalStorage<HookContext>>;
|
|
12
15
|
declare const getCurrentAdapter: (fallback: DBTransactionAdapter) => Promise<DBTransactionAdapter>;
|
|
13
16
|
declare const runWithAdapter: <R>(adapter: DBAdapter, fn: () => R) => Promise<R>;
|
|
14
17
|
declare const runWithTransaction: <R>(adapter: DBAdapter, fn: () => R) => Promise<R>;
|
|
18
|
+
/**
|
|
19
|
+
* Queue a hook to be executed after the current transaction commits.
|
|
20
|
+
* If not in a transaction, the hook will execute immediately.
|
|
21
|
+
*/
|
|
22
|
+
declare const queueAfterTransactionHook: (hook: () => Promise<void>) => Promise<void>;
|
|
15
23
|
//#endregion
|
|
16
|
-
export { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, runWithAdapter, runWithTransaction };
|
|
24
|
+
export { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, queueAfterTransactionHook, runWithAdapter, runWithTransaction };
|
|
@@ -20,16 +20,31 @@ const getCurrentDBAdapterAsyncLocalStorage = async () => {
|
|
|
20
20
|
};
|
|
21
21
|
const getCurrentAdapter = async (fallback) => {
|
|
22
22
|
return ensureAsyncStorage().then((als) => {
|
|
23
|
-
return als.getStore() || fallback;
|
|
23
|
+
return als.getStore()?.adapter || fallback;
|
|
24
24
|
}).catch(() => {
|
|
25
25
|
return fallback;
|
|
26
26
|
});
|
|
27
27
|
};
|
|
28
28
|
const runWithAdapter = async (adapter, fn) => {
|
|
29
|
-
let called =
|
|
30
|
-
return ensureAsyncStorage().then((als) => {
|
|
29
|
+
let called = false;
|
|
30
|
+
return ensureAsyncStorage().then(async (als) => {
|
|
31
31
|
called = true;
|
|
32
|
-
|
|
32
|
+
const pendingHooks = [];
|
|
33
|
+
let result;
|
|
34
|
+
let error;
|
|
35
|
+
let hasError = false;
|
|
36
|
+
try {
|
|
37
|
+
result = await als.run({
|
|
38
|
+
adapter,
|
|
39
|
+
pendingHooks
|
|
40
|
+
}, fn);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
error = err;
|
|
43
|
+
hasError = true;
|
|
44
|
+
}
|
|
45
|
+
for (const hook of pendingHooks) await hook();
|
|
46
|
+
if (hasError) throw error;
|
|
47
|
+
return result;
|
|
33
48
|
}).catch((err) => {
|
|
34
49
|
if (!called) return fn();
|
|
35
50
|
throw err;
|
|
@@ -37,16 +52,44 @@ const runWithAdapter = async (adapter, fn) => {
|
|
|
37
52
|
};
|
|
38
53
|
const runWithTransaction = async (adapter, fn) => {
|
|
39
54
|
let called = true;
|
|
40
|
-
return ensureAsyncStorage().then((als) => {
|
|
55
|
+
return ensureAsyncStorage().then(async (als) => {
|
|
41
56
|
called = true;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
const pendingHooks = [];
|
|
58
|
+
let result;
|
|
59
|
+
let error;
|
|
60
|
+
let hasError = false;
|
|
61
|
+
try {
|
|
62
|
+
result = await adapter.transaction(async (trx) => {
|
|
63
|
+
return als.run({
|
|
64
|
+
adapter: trx,
|
|
65
|
+
pendingHooks
|
|
66
|
+
}, fn);
|
|
67
|
+
});
|
|
68
|
+
} catch (e) {
|
|
69
|
+
hasError = true;
|
|
70
|
+
error = e;
|
|
71
|
+
}
|
|
72
|
+
for (const hook of pendingHooks) await hook();
|
|
73
|
+
if (hasError) throw error;
|
|
74
|
+
return result;
|
|
45
75
|
}).catch((err) => {
|
|
46
76
|
if (!called) return fn();
|
|
47
77
|
throw err;
|
|
48
78
|
});
|
|
49
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* Queue a hook to be executed after the current transaction commits.
|
|
82
|
+
* If not in a transaction, the hook will execute immediately.
|
|
83
|
+
*/
|
|
84
|
+
const queueAfterTransactionHook = async (hook) => {
|
|
85
|
+
return ensureAsyncStorage().then((als) => {
|
|
86
|
+
const store = als.getStore();
|
|
87
|
+
if (store) store.pendingHooks.push(hook);
|
|
88
|
+
else return hook();
|
|
89
|
+
}).catch(() => {
|
|
90
|
+
return hook();
|
|
91
|
+
});
|
|
92
|
+
};
|
|
50
93
|
|
|
51
94
|
//#endregion
|
|
52
|
-
export { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, runWithAdapter, runWithTransaction };
|
|
95
|
+
export { getCurrentAdapter, getCurrentDBAdapterAsyncLocalStorage, queueAfterTransactionHook, runWithAdapter, runWithTransaction };
|
|
@@ -2,7 +2,7 @@ import { getOAuth2Tokens } from "./utils.mjs";
|
|
|
2
2
|
import "./index.mjs";
|
|
3
3
|
import { base64 } from "@better-auth/utils/base64";
|
|
4
4
|
import { betterFetch } from "@better-fetch/fetch";
|
|
5
|
-
import { jwtVerify } from "jose";
|
|
5
|
+
import { decodeProtectedHeader, importJWK, jwtVerify } from "jose";
|
|
6
6
|
|
|
7
7
|
//#region src/oauth2/validate-authorization-code.ts
|
|
8
8
|
function createAuthorizationCodeRequest({ code, codeVerifier, redirectURI, options, authentication, deviceId, headers, additionalParams = {}, resource }) {
|
|
@@ -61,10 +61,10 @@ async function validateToken(token, jwksEndpoint) {
|
|
|
61
61
|
});
|
|
62
62
|
if (error) throw error;
|
|
63
63
|
const keys = data["keys"];
|
|
64
|
-
const header =
|
|
65
|
-
const key = keys.find((
|
|
64
|
+
const header = decodeProtectedHeader(token);
|
|
65
|
+
const key = keys.find((k) => k.kid === header.kid);
|
|
66
66
|
if (!key) throw new Error("Key not found");
|
|
67
|
-
return await jwtVerify(token, key);
|
|
67
|
+
return await jwtVerify(token, await importJWK(key, header.alg));
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
//#endregion
|
package/dist/types/context.d.mts
CHANGED
|
@@ -65,6 +65,7 @@ interface InternalAdapter<_Options extends BetterAuthOptions = BetterAuthOptions
|
|
|
65
65
|
deleteSessions(userIdOrSessionTokens: string | string[]): Promise<void>;
|
|
66
66
|
findOAuthUser(email: string, accountId: string, providerId: string): Promise<{
|
|
67
67
|
user: User;
|
|
68
|
+
linkedAccount: Account | null;
|
|
68
69
|
accounts: Account[];
|
|
69
70
|
} | null>;
|
|
70
71
|
findUserByEmail(email: string, options?: {
|
|
@@ -110,6 +110,25 @@ type BetterAuthAdvancedOptions = {
|
|
|
110
110
|
* ⚠︎ This is a security risk and it may expose your application to abuse
|
|
111
111
|
*/
|
|
112
112
|
disableIpTracking?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* IPv6 subnet prefix length for rate limiting.
|
|
115
|
+
*
|
|
116
|
+
* IPv6 addresses can be grouped by subnet to prevent attackers from
|
|
117
|
+
* bypassing rate limits by rotating through multiple addresses in
|
|
118
|
+
* their allocation.
|
|
119
|
+
*
|
|
120
|
+
* Common values:
|
|
121
|
+
* - 128 (default): Individual IPv6 address
|
|
122
|
+
* - 64: /64 subnet (typical home/business allocation)
|
|
123
|
+
* - 48: /48 subnet (larger network allocation)
|
|
124
|
+
* - 32: /32 subnet (ISP allocation)
|
|
125
|
+
*
|
|
126
|
+
* Note: This only affects IPv6 addresses. IPv4 addresses are always
|
|
127
|
+
* rate limited individually.
|
|
128
|
+
*
|
|
129
|
+
* @default 64 (/64 subnet)
|
|
130
|
+
*/
|
|
131
|
+
ipv6Subnet?: 128 | 64 | 48 | 32 | undefined;
|
|
113
132
|
} | undefined;
|
|
114
133
|
/**
|
|
115
134
|
* Use secure cookies
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
//#region src/utils/ip.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Normalizes an IP address for consistent rate limiting.
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Normalizes IPv6 to canonical lowercase form
|
|
7
|
+
* - Converts IPv4-mapped IPv6 to IPv4
|
|
8
|
+
* - Supports IPv6 subnet extraction
|
|
9
|
+
* - Handles all edge cases (::1, ::, etc.)
|
|
10
|
+
*/
|
|
11
|
+
interface NormalizeIPOptions {
|
|
12
|
+
/**
|
|
13
|
+
* For IPv6 addresses, extract the subnet prefix instead of full address.
|
|
14
|
+
* Common values: 32, 48, 64, 128 (default: 128 = full address)
|
|
15
|
+
*
|
|
16
|
+
* @default 128
|
|
17
|
+
*/
|
|
18
|
+
ipv6Subnet?: 128 | 64 | 48 | 32;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Checks if an IP is valid IPv4 or IPv6
|
|
22
|
+
*/
|
|
23
|
+
declare function isValidIP(ip: string): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Normalizes an IP address (IPv4 or IPv6) for consistent rate limiting.
|
|
26
|
+
*
|
|
27
|
+
* @param ip - The IP address to normalize
|
|
28
|
+
* @param options - Normalization options
|
|
29
|
+
* @returns Normalized IP address
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* normalizeIP("2001:DB8::1")
|
|
33
|
+
* // -> "2001:0db8:0000:0000:0000:0000:0000:0000"
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* normalizeIP("::ffff:192.0.2.1")
|
|
37
|
+
* // -> "192.0.2.1" (converted to IPv4)
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* normalizeIP("2001:db8::1", { ipv6Subnet: 64 })
|
|
41
|
+
* // -> "2001:0db8:0000:0000:0000:0000:0000:0000" (subnet /64)
|
|
42
|
+
*/
|
|
43
|
+
declare function normalizeIP(ip: string, options?: NormalizeIPOptions): string;
|
|
44
|
+
/**
|
|
45
|
+
* Creates a rate limit key from IP and path
|
|
46
|
+
* Uses a separator to prevent collision attacks
|
|
47
|
+
*
|
|
48
|
+
* @param ip - The IP address (should be normalized)
|
|
49
|
+
* @param path - The request path
|
|
50
|
+
* @returns Rate limit key
|
|
51
|
+
*/
|
|
52
|
+
declare function createRateLimitKey(ip: string, path: string): string;
|
|
53
|
+
//#endregion
|
|
54
|
+
export { createRateLimitKey, isValidIP, normalizeIP };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/ip.ts
|
|
4
|
+
/**
|
|
5
|
+
* Checks if an IP is valid IPv4 or IPv6
|
|
6
|
+
*/
|
|
7
|
+
function isValidIP(ip) {
|
|
8
|
+
return z.ipv4().safeParse(ip).success || z.ipv6().safeParse(ip).success;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Checks if an IP is IPv6
|
|
12
|
+
*/
|
|
13
|
+
function isIPv6(ip) {
|
|
14
|
+
return z.ipv6().safeParse(ip).success;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Converts IPv4-mapped IPv6 address to IPv4
|
|
18
|
+
* e.g., "::ffff:192.0.2.1" -> "192.0.2.1"
|
|
19
|
+
*/
|
|
20
|
+
function extractIPv4FromMapped(ipv6) {
|
|
21
|
+
const lower = ipv6.toLowerCase();
|
|
22
|
+
if (lower.startsWith("::ffff:")) {
|
|
23
|
+
const ipv4Part = lower.substring(7);
|
|
24
|
+
if (z.ipv4().safeParse(ipv4Part).success) return ipv4Part;
|
|
25
|
+
}
|
|
26
|
+
const parts = ipv6.split(":");
|
|
27
|
+
if (parts.length === 7 && parts[5]?.toLowerCase() === "ffff") {
|
|
28
|
+
const ipv4Part = parts[6];
|
|
29
|
+
if (ipv4Part && z.ipv4().safeParse(ipv4Part).success) return ipv4Part;
|
|
30
|
+
}
|
|
31
|
+
if (lower.includes("::ffff:") || lower.includes(":ffff:")) {
|
|
32
|
+
const groups = expandIPv6(ipv6);
|
|
33
|
+
if (groups.length === 8 && groups[0] === "0000" && groups[1] === "0000" && groups[2] === "0000" && groups[3] === "0000" && groups[4] === "0000" && groups[5] === "ffff" && groups[6] && groups[7]) return `${Number.parseInt(groups[6].substring(0, 2), 16)}.${Number.parseInt(groups[6].substring(2, 4), 16)}.${Number.parseInt(groups[7].substring(0, 2), 16)}.${Number.parseInt(groups[7].substring(2, 4), 16)}`;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Expands a compressed IPv6 address to full form
|
|
39
|
+
* e.g., "2001:db8::1" -> ["2001", "0db8", "0000", "0000", "0000", "0000", "0000", "0001"]
|
|
40
|
+
*/
|
|
41
|
+
function expandIPv6(ipv6) {
|
|
42
|
+
if (ipv6.includes("::")) {
|
|
43
|
+
const sides = ipv6.split("::");
|
|
44
|
+
const left = sides[0] ? sides[0].split(":") : [];
|
|
45
|
+
const right = sides[1] ? sides[1].split(":") : [];
|
|
46
|
+
const missingGroups = 8 - left.length - right.length;
|
|
47
|
+
const zeros = Array(missingGroups).fill("0000");
|
|
48
|
+
const paddedLeft = left.map((g) => g.padStart(4, "0"));
|
|
49
|
+
const paddedRight = right.map((g) => g.padStart(4, "0"));
|
|
50
|
+
return [
|
|
51
|
+
...paddedLeft,
|
|
52
|
+
...zeros,
|
|
53
|
+
...paddedRight
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
return ipv6.split(":").map((g) => g.padStart(4, "0"));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Normalizes an IPv6 address to canonical form
|
|
60
|
+
* e.g., "2001:DB8::1" -> "2001:0db8:0000:0000:0000:0000:0000:0001"
|
|
61
|
+
*/
|
|
62
|
+
function normalizeIPv6(ipv6, subnetPrefix) {
|
|
63
|
+
const groups = expandIPv6(ipv6);
|
|
64
|
+
if (subnetPrefix && subnetPrefix < 128) {
|
|
65
|
+
let bitsRemaining = subnetPrefix;
|
|
66
|
+
return groups.map((group) => {
|
|
67
|
+
if (bitsRemaining <= 0) return "0000";
|
|
68
|
+
if (bitsRemaining >= 16) {
|
|
69
|
+
bitsRemaining -= 16;
|
|
70
|
+
return group;
|
|
71
|
+
}
|
|
72
|
+
const masked = Number.parseInt(group, 16) & (65535 << 16 - bitsRemaining & 65535);
|
|
73
|
+
bitsRemaining = 0;
|
|
74
|
+
return masked.toString(16).padStart(4, "0");
|
|
75
|
+
}).join(":").toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
return groups.join(":").toLowerCase();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Normalizes an IP address (IPv4 or IPv6) for consistent rate limiting.
|
|
81
|
+
*
|
|
82
|
+
* @param ip - The IP address to normalize
|
|
83
|
+
* @param options - Normalization options
|
|
84
|
+
* @returns Normalized IP address
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* normalizeIP("2001:DB8::1")
|
|
88
|
+
* // -> "2001:0db8:0000:0000:0000:0000:0000:0000"
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* normalizeIP("::ffff:192.0.2.1")
|
|
92
|
+
* // -> "192.0.2.1" (converted to IPv4)
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* normalizeIP("2001:db8::1", { ipv6Subnet: 64 })
|
|
96
|
+
* // -> "2001:0db8:0000:0000:0000:0000:0000:0000" (subnet /64)
|
|
97
|
+
*/
|
|
98
|
+
function normalizeIP(ip, options = {}) {
|
|
99
|
+
if (z.ipv4().safeParse(ip).success) return ip.toLowerCase();
|
|
100
|
+
if (!isIPv6(ip)) return ip.toLowerCase();
|
|
101
|
+
const ipv4 = extractIPv4FromMapped(ip);
|
|
102
|
+
if (ipv4) return ipv4.toLowerCase();
|
|
103
|
+
return normalizeIPv6(ip, options.ipv6Subnet || 64);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Creates a rate limit key from IP and path
|
|
107
|
+
* Uses a separator to prevent collision attacks
|
|
108
|
+
*
|
|
109
|
+
* @param ip - The IP address (should be normalized)
|
|
110
|
+
* @param path - The request path
|
|
111
|
+
* @returns Rate limit key
|
|
112
|
+
*/
|
|
113
|
+
function createRateLimitKey(ip, path) {
|
|
114
|
+
return `${ip}|${path}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
//#endregion
|
|
118
|
+
export { createRateLimitKey, isValidIP, normalizeIP };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/core",
|
|
3
|
-
"version": "1.5.0-beta.
|
|
3
|
+
"version": "1.5.0-beta.9",
|
|
4
4
|
"description": "The most comprehensive authentication framework for TypeScript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -117,20 +117,20 @@
|
|
|
117
117
|
"devDependencies": {
|
|
118
118
|
"@better-auth/utils": "0.3.0",
|
|
119
119
|
"@better-fetch/fetch": "1.1.21",
|
|
120
|
-
"better-call": "1.
|
|
120
|
+
"better-call": "1.2.0",
|
|
121
121
|
"jose": "^6.1.0",
|
|
122
|
-
"kysely": "^0.28.
|
|
123
|
-
"nanostores": "^1.0
|
|
122
|
+
"kysely": "^0.28.10",
|
|
123
|
+
"nanostores": "^1.1.0",
|
|
124
124
|
"tsdown": "^0.19.0"
|
|
125
125
|
},
|
|
126
126
|
"dependencies": {
|
|
127
127
|
"@standard-schema/spec": "^1.0.0",
|
|
128
|
-
"zod": "^4.
|
|
128
|
+
"zod": "^4.3.5"
|
|
129
129
|
},
|
|
130
130
|
"peerDependencies": {
|
|
131
131
|
"@better-auth/utils": "0.3.0",
|
|
132
132
|
"@better-fetch/fetch": "1.1.21",
|
|
133
|
-
"better-call": "1.
|
|
133
|
+
"better-call": "1.2.0",
|
|
134
134
|
"jose": "^6.1.0",
|
|
135
135
|
"kysely": "^0.28.5",
|
|
136
136
|
"nanostores": "^1.0.1"
|
package/src/context/index.ts
CHANGED
|
@@ -3,6 +3,11 @@ import { getAsyncLocalStorage } from "@better-auth/core/async_hooks";
|
|
|
3
3
|
import type { DBAdapter, DBTransactionAdapter } from "../db/adapter";
|
|
4
4
|
import { __getBetterAuthGlobal } from "./global";
|
|
5
5
|
|
|
6
|
+
type HookContext = {
|
|
7
|
+
adapter: DBTransactionAdapter;
|
|
8
|
+
pendingHooks: Array<() => Promise<void>>;
|
|
9
|
+
};
|
|
10
|
+
|
|
6
11
|
const ensureAsyncStorage = async () => {
|
|
7
12
|
const betterAuthGlobal = __getBetterAuthGlobal();
|
|
8
13
|
if (!betterAuthGlobal.context.adapterAsyncStorage) {
|
|
@@ -10,7 +15,7 @@ const ensureAsyncStorage = async () => {
|
|
|
10
15
|
betterAuthGlobal.context.adapterAsyncStorage = new AsyncLocalStorage();
|
|
11
16
|
}
|
|
12
17
|
return betterAuthGlobal.context
|
|
13
|
-
.adapterAsyncStorage as AsyncLocalStorage<
|
|
18
|
+
.adapterAsyncStorage as AsyncLocalStorage<HookContext>;
|
|
14
19
|
};
|
|
15
20
|
|
|
16
21
|
/**
|
|
@@ -27,7 +32,8 @@ export const getCurrentAdapter = async (
|
|
|
27
32
|
): Promise<DBTransactionAdapter> => {
|
|
28
33
|
return ensureAsyncStorage()
|
|
29
34
|
.then((als) => {
|
|
30
|
-
|
|
35
|
+
const store = als.getStore();
|
|
36
|
+
return store?.adapter || fallback;
|
|
31
37
|
})
|
|
32
38
|
.catch(() => {
|
|
33
39
|
return fallback;
|
|
@@ -38,11 +44,28 @@ export const runWithAdapter = async <R>(
|
|
|
38
44
|
adapter: DBAdapter,
|
|
39
45
|
fn: () => R,
|
|
40
46
|
): Promise<R> => {
|
|
41
|
-
let called =
|
|
47
|
+
let called = false;
|
|
42
48
|
return ensureAsyncStorage()
|
|
43
|
-
.then((als) => {
|
|
49
|
+
.then(async (als) => {
|
|
44
50
|
called = true;
|
|
45
|
-
|
|
51
|
+
const pendingHooks: Array<() => Promise<void>> = [];
|
|
52
|
+
let result: Awaited<R>;
|
|
53
|
+
let error: unknown;
|
|
54
|
+
let hasError = false;
|
|
55
|
+
try {
|
|
56
|
+
result = await als.run({ adapter, pendingHooks }, fn);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
error = err;
|
|
59
|
+
hasError = true;
|
|
60
|
+
}
|
|
61
|
+
// Execute pending hooks after the function completes (even if it threw)
|
|
62
|
+
for (const hook of pendingHooks) {
|
|
63
|
+
await hook();
|
|
64
|
+
}
|
|
65
|
+
if (hasError) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
return result!;
|
|
46
69
|
})
|
|
47
70
|
.catch((err) => {
|
|
48
71
|
if (!called) {
|
|
@@ -58,11 +81,27 @@ export const runWithTransaction = async <R>(
|
|
|
58
81
|
): Promise<R> => {
|
|
59
82
|
let called = true;
|
|
60
83
|
return ensureAsyncStorage()
|
|
61
|
-
.then((als) => {
|
|
84
|
+
.then(async (als) => {
|
|
62
85
|
called = true;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
86
|
+
const pendingHooks: Array<() => Promise<void>> = [];
|
|
87
|
+
let result: Awaited<R>;
|
|
88
|
+
let error: unknown;
|
|
89
|
+
let hasError = false;
|
|
90
|
+
try {
|
|
91
|
+
result = await adapter.transaction(async (trx) => {
|
|
92
|
+
return als.run({ adapter: trx, pendingHooks }, fn);
|
|
93
|
+
});
|
|
94
|
+
} catch (e) {
|
|
95
|
+
hasError = true;
|
|
96
|
+
error = e;
|
|
97
|
+
}
|
|
98
|
+
for (const hook of pendingHooks) {
|
|
99
|
+
await hook();
|
|
100
|
+
}
|
|
101
|
+
if (hasError) {
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
return result!;
|
|
66
105
|
})
|
|
67
106
|
.catch((err) => {
|
|
68
107
|
if (!called) {
|
|
@@ -71,3 +110,27 @@ export const runWithTransaction = async <R>(
|
|
|
71
110
|
throw err;
|
|
72
111
|
});
|
|
73
112
|
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Queue a hook to be executed after the current transaction commits.
|
|
116
|
+
* If not in a transaction, the hook will execute immediately.
|
|
117
|
+
*/
|
|
118
|
+
export const queueAfterTransactionHook = async (
|
|
119
|
+
hook: () => Promise<void>,
|
|
120
|
+
): Promise<void> => {
|
|
121
|
+
return ensureAsyncStorage()
|
|
122
|
+
.then((als) => {
|
|
123
|
+
const store = als.getStore();
|
|
124
|
+
if (store) {
|
|
125
|
+
// We're in a transaction context, queue the hook
|
|
126
|
+
store.pendingHooks.push(hook);
|
|
127
|
+
} else {
|
|
128
|
+
// Not in a transaction, execute immediately
|
|
129
|
+
return hook();
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
.catch(() => {
|
|
133
|
+
// No async storage available, execute immediately
|
|
134
|
+
return hook();
|
|
135
|
+
});
|
|
136
|
+
};
|