@better-auth/core 1.7.0-beta.0 → 1.7.0-beta.10
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/dist/api/index.d.mts +44 -1
- package/dist/api/index.mjs +69 -4
- package/dist/context/global.mjs +1 -1
- package/dist/context/transaction.d.mts +7 -4
- package/dist/context/transaction.mjs +6 -3
- package/dist/db/adapter/factory.mjs +91 -3
- package/dist/db/adapter/get-id-field.mjs +1 -1
- package/dist/db/adapter/index.d.mts +87 -3
- package/dist/db/adapter/types.d.mts +1 -1
- package/dist/db/get-tables.mjs +2 -1
- package/dist/db/type.d.mts +17 -0
- package/dist/env/env-impl.mjs +1 -1
- package/dist/error/codes.d.mts +1 -0
- package/dist/error/codes.mjs +1 -0
- package/dist/error/index.d.mts +7 -0
- package/dist/index.d.mts +2 -2
- package/dist/instrumentation/api.mjs +12 -0
- package/dist/instrumentation/noop.mjs +42 -0
- package/dist/instrumentation/pure.index.d.mts +7 -0
- package/dist/instrumentation/pure.index.mjs +7 -0
- package/dist/instrumentation/tracer.mjs +6 -3
- package/dist/oauth2/authorization-params.d.mts +12 -0
- package/dist/oauth2/authorization-params.mjs +12 -0
- package/dist/oauth2/basic-credentials.d.mts +30 -0
- package/dist/oauth2/basic-credentials.mjs +64 -0
- package/dist/oauth2/client-assertion.d.mts +38 -22
- package/dist/oauth2/client-assertion.mjs +63 -28
- package/dist/oauth2/client-credentials-token.d.mts +19 -40
- package/dist/oauth2/client-credentials-token.mjs +20 -31
- package/dist/oauth2/create-authorization-url.d.mts +11 -1
- package/dist/oauth2/create-authorization-url.mjs +27 -7
- package/dist/oauth2/dpop.d.mts +142 -0
- package/dist/oauth2/dpop.mjs +246 -0
- package/dist/oauth2/index.d.mts +14 -9
- package/dist/oauth2/index.mjs +12 -8
- package/dist/oauth2/oauth-provider.d.mts +150 -10
- package/dist/oauth2/refresh-access-token.d.mts +20 -40
- package/dist/oauth2/refresh-access-token.mjs +38 -36
- package/dist/oauth2/reject-redirects.mjs +65 -0
- package/dist/oauth2/token-endpoint-auth.d.mts +17 -0
- package/dist/oauth2/token-endpoint-auth.mjs +89 -0
- package/dist/oauth2/utils.d.mts +23 -1
- package/dist/oauth2/utils.mjs +48 -2
- package/dist/oauth2/validate-authorization-code.d.mts +17 -52
- package/dist/oauth2/validate-authorization-code.mjs +28 -34
- package/dist/oauth2/verify-id-token.d.mts +27 -0
- package/dist/oauth2/verify-id-token.mjs +62 -0
- package/dist/oauth2/verify.d.mts +88 -15
- package/dist/oauth2/verify.mjs +203 -25
- package/dist/social-providers/apple.d.mts +16 -4
- package/dist/social-providers/apple.mjs +17 -24
- package/dist/social-providers/atlassian.d.mts +5 -1
- package/dist/social-providers/atlassian.mjs +6 -3
- package/dist/social-providers/cognito.d.mts +27 -3
- package/dist/social-providers/cognito.mjs +15 -25
- package/dist/social-providers/discord.d.mts +7 -3
- package/dist/social-providers/discord.mjs +16 -3
- package/dist/social-providers/dropbox.d.mts +5 -1
- package/dist/social-providers/dropbox.mjs +5 -4
- package/dist/social-providers/facebook.d.mts +24 -5
- package/dist/social-providers/facebook.mjs +54 -20
- package/dist/social-providers/figma.d.mts +5 -1
- package/dist/social-providers/figma.mjs +4 -3
- package/dist/social-providers/github.d.mts +6 -2
- package/dist/social-providers/github.mjs +5 -4
- package/dist/social-providers/gitlab.d.mts +5 -1
- package/dist/social-providers/gitlab.mjs +3 -2
- package/dist/social-providers/google.d.mts +53 -5
- package/dist/social-providers/google.mjs +69 -25
- package/dist/social-providers/huggingface.d.mts +5 -1
- package/dist/social-providers/huggingface.mjs +3 -2
- package/dist/social-providers/index.d.mts +221 -45
- package/dist/social-providers/index.mjs +2 -2
- package/dist/social-providers/kakao.d.mts +5 -1
- package/dist/social-providers/kakao.mjs +3 -2
- package/dist/social-providers/kick.d.mts +5 -1
- package/dist/social-providers/kick.mjs +3 -2
- package/dist/social-providers/line.d.mts +8 -2
- package/dist/social-providers/line.mjs +5 -6
- package/dist/social-providers/linear.d.mts +5 -1
- package/dist/social-providers/linear.mjs +3 -2
- package/dist/social-providers/linkedin.d.mts +7 -3
- package/dist/social-providers/linkedin.mjs +4 -3
- package/dist/social-providers/microsoft-entra-id.d.mts +41 -7
- package/dist/social-providers/microsoft-entra-id.mjs +37 -28
- package/dist/social-providers/naver.d.mts +5 -1
- package/dist/social-providers/naver.mjs +3 -2
- package/dist/social-providers/notion.d.mts +5 -1
- package/dist/social-providers/notion.mjs +5 -2
- package/dist/social-providers/paybin.d.mts +5 -1
- package/dist/social-providers/paybin.mjs +4 -3
- package/dist/social-providers/paypal.d.mts +6 -2
- package/dist/social-providers/paypal.mjs +19 -14
- package/dist/social-providers/polar.d.mts +5 -1
- package/dist/social-providers/polar.mjs +3 -2
- package/dist/social-providers/railway.d.mts +5 -1
- package/dist/social-providers/railway.mjs +3 -2
- package/dist/social-providers/reddit.d.mts +5 -1
- package/dist/social-providers/reddit.mjs +7 -5
- package/dist/social-providers/roblox.d.mts +6 -2
- package/dist/social-providers/roblox.mjs +12 -2
- package/dist/social-providers/salesforce.d.mts +5 -1
- package/dist/social-providers/salesforce.mjs +4 -3
- package/dist/social-providers/slack.d.mts +6 -2
- package/dist/social-providers/slack.mjs +11 -8
- package/dist/social-providers/spotify.d.mts +5 -1
- package/dist/social-providers/spotify.mjs +3 -2
- package/dist/social-providers/tiktok.d.mts +5 -1
- package/dist/social-providers/tiktok.mjs +14 -2
- package/dist/social-providers/twitch.d.mts +5 -1
- package/dist/social-providers/twitch.mjs +3 -2
- package/dist/social-providers/twitter.d.mts +4 -1
- package/dist/social-providers/twitter.mjs +2 -1
- package/dist/social-providers/vercel.d.mts +5 -1
- package/dist/social-providers/vercel.mjs +3 -2
- package/dist/social-providers/vk.d.mts +5 -1
- package/dist/social-providers/vk.mjs +3 -2
- package/dist/social-providers/wechat.d.mts +5 -1
- package/dist/social-providers/wechat.mjs +8 -2
- package/dist/social-providers/zoom.d.mts +5 -1
- package/dist/social-providers/zoom.mjs +10 -17
- package/dist/types/context.d.mts +55 -6
- package/dist/types/index.d.mts +1 -1
- package/dist/types/init-options.d.mts +182 -12
- package/dist/types/plugin-client.d.mts +12 -2
- package/dist/utils/async.d.mts +22 -0
- package/dist/utils/async.mjs +32 -0
- package/dist/utils/host.d.mts +147 -0
- package/dist/utils/host.mjs +298 -0
- package/dist/utils/ip.d.mts +28 -5
- package/dist/utils/ip.mjs +118 -4
- package/dist/utils/is-api-error.d.mts +6 -0
- package/dist/utils/is-api-error.mjs +8 -0
- package/dist/utils/redirect-uri.d.mts +20 -0
- package/dist/utils/redirect-uri.mjs +48 -0
- package/dist/utils/string.d.mts +5 -1
- package/dist/utils/string.mjs +20 -1
- package/dist/utils/url.d.mts +18 -1
- package/dist/utils/url.mjs +34 -4
- package/package.json +26 -16
- package/src/api/index.ts +121 -5
- package/src/context/transaction.ts +45 -12
- package/src/db/adapter/factory.ts +193 -7
- package/src/db/adapter/get-id-field.ts +2 -2
- package/src/db/adapter/index.ts +85 -2
- package/src/db/adapter/types.ts +2 -0
- package/src/db/get-tables.ts +9 -1
- package/src/db/schema/account.ts +4 -1
- package/src/db/schema/user.ts +3 -0
- package/src/db/type.ts +17 -0
- package/src/env/env-impl.ts +1 -2
- package/src/error/codes.ts +1 -0
- package/src/error/index.ts +9 -0
- package/src/instrumentation/api.ts +17 -0
- package/src/instrumentation/noop.ts +74 -0
- package/src/instrumentation/pure.index.ts +31 -0
- package/src/instrumentation/tracer.ts +8 -3
- package/src/oauth2/authorization-params.ts +28 -0
- package/src/oauth2/basic-credentials.ts +87 -0
- package/src/oauth2/client-assertion.ts +131 -58
- package/src/oauth2/client-credentials-token.ts +50 -74
- package/src/oauth2/create-authorization-url.ts +33 -7
- package/src/oauth2/dpop.ts +568 -0
- package/src/oauth2/index.ts +81 -11
- package/src/oauth2/oauth-provider.ts +159 -11
- package/src/oauth2/refresh-access-token.ts +82 -83
- package/src/oauth2/reject-redirects.ts +75 -0
- package/src/oauth2/token-endpoint-auth.ts +221 -0
- package/src/oauth2/utils.ts +72 -5
- package/src/oauth2/validate-authorization-code.ts +69 -89
- package/src/oauth2/verify-id-token.ts +115 -0
- package/src/oauth2/verify.ts +409 -78
- package/src/social-providers/apple.ts +30 -40
- package/src/social-providers/atlassian.ts +8 -1
- package/src/social-providers/cognito.ts +36 -39
- package/src/social-providers/discord.ts +22 -18
- package/src/social-providers/dropbox.ts +7 -5
- package/src/social-providers/facebook.ts +108 -52
- package/src/social-providers/figma.ts +8 -1
- package/src/social-providers/github.ts +5 -3
- package/src/social-providers/gitlab.ts +2 -0
- package/src/social-providers/google.ts +141 -39
- package/src/social-providers/huggingface.ts +8 -1
- package/src/social-providers/kakao.ts +2 -1
- package/src/social-providers/kick.ts +8 -1
- package/src/social-providers/line.ts +25 -27
- package/src/social-providers/linear.ts +8 -1
- package/src/social-providers/linkedin.ts +5 -3
- package/src/social-providers/microsoft-entra-id.ts +100 -55
- package/src/social-providers/naver.ts +2 -1
- package/src/social-providers/notion.ts +8 -1
- package/src/social-providers/paybin.ts +2 -0
- package/src/social-providers/paypal.ts +28 -17
- package/src/social-providers/polar.ts +8 -1
- package/src/social-providers/railway.ts +8 -1
- package/src/social-providers/reddit.ts +9 -4
- package/src/social-providers/roblox.ts +16 -11
- package/src/social-providers/salesforce.ts +8 -1
- package/src/social-providers/slack.ts +15 -9
- package/src/social-providers/spotify.ts +8 -1
- package/src/social-providers/tiktok.ts +22 -9
- package/src/social-providers/twitch.ts +2 -1
- package/src/social-providers/twitter.ts +1 -0
- package/src/social-providers/vercel.ts +8 -1
- package/src/social-providers/vk.ts +8 -1
- package/src/social-providers/wechat.ts +17 -2
- package/src/social-providers/zoom.ts +15 -19
- package/src/types/context.ts +57 -5
- package/src/types/index.ts +7 -0
- package/src/types/init-options.ts +204 -14
- package/src/types/plugin-client.ts +16 -2
- package/src/utils/async.ts +53 -0
- package/src/utils/host.ts +425 -0
- package/src/utils/ip.ts +197 -13
- package/src/utils/is-api-error.ts +10 -0
- package/src/utils/redirect-uri.ts +54 -0
- package/src/utils/string.ts +37 -0
- package/src/utils/url.ts +38 -4
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { createLogger, getColorDepth, TTY_COLORS } from "../../env";
|
|
2
|
-
import { BetterAuthError } from "../../error";
|
|
3
1
|
import {
|
|
4
2
|
ATTR_DB_COLLECTION_NAME,
|
|
5
3
|
ATTR_DB_OPERATION_NAME,
|
|
6
4
|
withSpan,
|
|
7
|
-
} from "
|
|
5
|
+
} from "@better-auth/core/instrumentation";
|
|
6
|
+
import { createLogger, getColorDepth, TTY_COLORS } from "../../env";
|
|
7
|
+
import { BetterAuthError } from "../../error";
|
|
8
8
|
import type { BetterAuthOptions } from "../../types";
|
|
9
9
|
import { safeJSONParse } from "../../utils/json";
|
|
10
10
|
import { getAuthTables } from "../get-tables";
|
|
@@ -29,15 +29,15 @@ import type {
|
|
|
29
29
|
} from "./types";
|
|
30
30
|
import { withApplyDefault } from "./utils";
|
|
31
31
|
|
|
32
|
+
export * from "./types";
|
|
32
33
|
export {
|
|
33
|
-
initGetDefaultModelName,
|
|
34
34
|
initGetDefaultFieldName,
|
|
35
|
-
|
|
36
|
-
initGetFieldName,
|
|
35
|
+
initGetDefaultModelName,
|
|
37
36
|
initGetFieldAttributes,
|
|
37
|
+
initGetFieldName,
|
|
38
38
|
initGetIdField,
|
|
39
|
+
initGetModelName,
|
|
39
40
|
};
|
|
40
|
-
export * from "./types";
|
|
41
41
|
|
|
42
42
|
let debugLogs: { instance: string; args: any[] }[] = [];
|
|
43
43
|
let transactionId = -1;
|
|
@@ -133,6 +133,16 @@ export const createAdapterFactory =
|
|
|
133
133
|
!config.debugLogs.deleteMany
|
|
134
134
|
) {
|
|
135
135
|
return;
|
|
136
|
+
} else if (
|
|
137
|
+
method === "consumeOne" &&
|
|
138
|
+
!config.debugLogs.consumeOne
|
|
139
|
+
) {
|
|
140
|
+
return;
|
|
141
|
+
} else if (
|
|
142
|
+
method === "incrementOne" &&
|
|
143
|
+
!config.debugLogs.incrementOne
|
|
144
|
+
) {
|
|
145
|
+
return;
|
|
136
146
|
} else if (method === "count" && !config.debugLogs.count) {
|
|
137
147
|
return;
|
|
138
148
|
}
|
|
@@ -485,6 +495,8 @@ export const createAdapterFactory =
|
|
|
485
495
|
| "updateMany"
|
|
486
496
|
| "delete"
|
|
487
497
|
| "deleteMany"
|
|
498
|
+
| "consumeOne"
|
|
499
|
+
| "incrementOne"
|
|
488
500
|
| "count";
|
|
489
501
|
}): W extends undefined ? undefined : CleanedWhere[] => {
|
|
490
502
|
if (!where) return undefined as any;
|
|
@@ -951,6 +963,11 @@ export const createAdapterFactory =
|
|
|
951
963
|
where: unsafeWhere,
|
|
952
964
|
action: "update",
|
|
953
965
|
});
|
|
966
|
+
// `update` targets a single row. Empty predicates have no
|
|
967
|
+
// target, so fail closed and leave bulk writes to `updateMany`.
|
|
968
|
+
if (where.length === 0) {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
954
971
|
debugLog(
|
|
955
972
|
{ method: "update" },
|
|
956
973
|
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 4)}`,
|
|
@@ -1051,6 +1068,14 @@ export const createAdapterFactory =
|
|
|
1051
1068
|
update: data,
|
|
1052
1069
|
}),
|
|
1053
1070
|
);
|
|
1071
|
+
if (
|
|
1072
|
+
typeof updatedCount !== "number" ||
|
|
1073
|
+
!Number.isFinite(updatedCount)
|
|
1074
|
+
) {
|
|
1075
|
+
throw new BetterAuthError(
|
|
1076
|
+
`Adapter "${config.adapterId}" updateMany must return a finite number affected row count.`,
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1054
1079
|
debugLog(
|
|
1055
1080
|
{ method: "updateMany" },
|
|
1056
1081
|
`${formatTransactionId(thisTransactionId)} ${formatStep(3, 4)}`,
|
|
@@ -1312,6 +1337,167 @@ export const createAdapterFactory =
|
|
|
1312
1337
|
);
|
|
1313
1338
|
return res;
|
|
1314
1339
|
},
|
|
1340
|
+
consumeOne: async <T>({
|
|
1341
|
+
model: unsafeModel,
|
|
1342
|
+
where: unsafeWhere,
|
|
1343
|
+
}: {
|
|
1344
|
+
model: string;
|
|
1345
|
+
where: Where[];
|
|
1346
|
+
}): Promise<T | null> => {
|
|
1347
|
+
transactionId++;
|
|
1348
|
+
const thisTransactionId = transactionId;
|
|
1349
|
+
const model = getModelName(unsafeModel);
|
|
1350
|
+
const where = transformWhereClause({
|
|
1351
|
+
model: unsafeModel,
|
|
1352
|
+
where: unsafeWhere,
|
|
1353
|
+
action: "consumeOne",
|
|
1354
|
+
});
|
|
1355
|
+
unsafeModel = getDefaultModelName(unsafeModel);
|
|
1356
|
+
debugLog(
|
|
1357
|
+
{ method: "consumeOne" },
|
|
1358
|
+
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 3)}`,
|
|
1359
|
+
`${formatMethod("consumeOne")} ${formatAction("ConsumeOne")}:`,
|
|
1360
|
+
{ model, where },
|
|
1361
|
+
);
|
|
1362
|
+
|
|
1363
|
+
if (typeof adapterInstance.consumeOne !== "function") {
|
|
1364
|
+
throw new BetterAuthError(
|
|
1365
|
+
`Adapter "${config.adapterId}" must implement consumeOne for atomic single-use credential consumption.`,
|
|
1366
|
+
);
|
|
1367
|
+
}
|
|
1368
|
+
const res = await withSpan(
|
|
1369
|
+
`db consumeOne ${model}`,
|
|
1370
|
+
{
|
|
1371
|
+
[ATTR_DB_OPERATION_NAME]: "consumeOne",
|
|
1372
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
1373
|
+
},
|
|
1374
|
+
() => adapterInstance.consumeOne<T>({ model, where }),
|
|
1375
|
+
);
|
|
1376
|
+
|
|
1377
|
+
debugLog(
|
|
1378
|
+
{ method: "consumeOne" },
|
|
1379
|
+
`${formatTransactionId(thisTransactionId)} ${formatStep(2, 3)}`,
|
|
1380
|
+
`${formatMethod("consumeOne")} ${formatAction("DB Result")}:`,
|
|
1381
|
+
{ model, data: res },
|
|
1382
|
+
);
|
|
1383
|
+
let transformed: any = res;
|
|
1384
|
+
if (!config.disableTransformOutput && res) {
|
|
1385
|
+
transformed = await transformOutput(
|
|
1386
|
+
res as Record<string, any>,
|
|
1387
|
+
unsafeModel,
|
|
1388
|
+
undefined,
|
|
1389
|
+
undefined,
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
debugLog(
|
|
1393
|
+
{ method: "consumeOne" },
|
|
1394
|
+
`${formatTransactionId(thisTransactionId)} ${formatStep(3, 3)}`,
|
|
1395
|
+
`${formatMethod("consumeOne")} ${formatAction("Parsed Result")}:`,
|
|
1396
|
+
{ model, data: transformed },
|
|
1397
|
+
);
|
|
1398
|
+
return transformed as T | null;
|
|
1399
|
+
},
|
|
1400
|
+
incrementOne: async <T>({
|
|
1401
|
+
model: unsafeModel,
|
|
1402
|
+
where: unsafeWhere,
|
|
1403
|
+
increment: unsafeIncrement,
|
|
1404
|
+
set: unsafeSet,
|
|
1405
|
+
}: {
|
|
1406
|
+
model: string;
|
|
1407
|
+
where: Where[];
|
|
1408
|
+
increment: Record<string, number>;
|
|
1409
|
+
set?: Record<string, unknown> | undefined;
|
|
1410
|
+
}): Promise<T | null> => {
|
|
1411
|
+
const hasIncrement = Object.keys(unsafeIncrement).length > 0;
|
|
1412
|
+
const hasSet = !!unsafeSet && Object.keys(unsafeSet).length > 0;
|
|
1413
|
+
if (!hasIncrement && !hasSet) {
|
|
1414
|
+
// An empty `increment` and empty `set` compiles to `UPDATE ... SET `
|
|
1415
|
+
// with no assignments, which is a syntax error on kysely, drizzle, and
|
|
1416
|
+
// Prisma. Fail fast with an actionable message instead.
|
|
1417
|
+
throw new BetterAuthError(
|
|
1418
|
+
"incrementOne requires a non-empty `increment` or `set`; both were empty.",
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
transactionId++;
|
|
1422
|
+
const thisTransactionId = transactionId;
|
|
1423
|
+
const model = getModelName(unsafeModel);
|
|
1424
|
+
const where = transformWhereClause({
|
|
1425
|
+
model: unsafeModel,
|
|
1426
|
+
where: unsafeWhere,
|
|
1427
|
+
action: "incrementOne",
|
|
1428
|
+
});
|
|
1429
|
+
unsafeModel = getDefaultModelName(unsafeModel);
|
|
1430
|
+
debugLog(
|
|
1431
|
+
{ method: "incrementOne" },
|
|
1432
|
+
`${formatTransactionId(thisTransactionId)} ${formatStep(1, 3)}`,
|
|
1433
|
+
`${formatMethod("incrementOne")} ${formatAction("IncrementOne")}:`,
|
|
1434
|
+
{ model, where, increment: unsafeIncrement, set: unsafeSet },
|
|
1435
|
+
);
|
|
1436
|
+
|
|
1437
|
+
if (typeof adapterInstance.incrementOne !== "function") {
|
|
1438
|
+
throw new BetterAuthError(
|
|
1439
|
+
`Adapter "${config.adapterId}" must implement incrementOne for atomic guarded counter updates.`,
|
|
1440
|
+
);
|
|
1441
|
+
}
|
|
1442
|
+
const mappedKeys = config.mapKeysTransformInput ?? {};
|
|
1443
|
+
const increment: Record<string, number> = {};
|
|
1444
|
+
for (const [field, delta] of Object.entries(unsafeIncrement)) {
|
|
1445
|
+
increment[
|
|
1446
|
+
mappedKeys[field] || getFieldName({ model: unsafeModel, field })
|
|
1447
|
+
] = delta;
|
|
1448
|
+
}
|
|
1449
|
+
let set: Record<string, unknown> | undefined;
|
|
1450
|
+
if (unsafeSet && !config.disableTransformInput) {
|
|
1451
|
+
set = await transformInput(unsafeSet, unsafeModel, "update");
|
|
1452
|
+
} else {
|
|
1453
|
+
set = unsafeSet;
|
|
1454
|
+
}
|
|
1455
|
+
if (
|
|
1456
|
+
Object.keys(increment).length === 0 &&
|
|
1457
|
+
(!set || Object.keys(set).length === 0)
|
|
1458
|
+
) {
|
|
1459
|
+
throw new BetterAuthError(
|
|
1460
|
+
"incrementOne resolved to an empty update: every increment/set field was unknown to the schema or transformed away.",
|
|
1461
|
+
);
|
|
1462
|
+
}
|
|
1463
|
+
const res = await withSpan(
|
|
1464
|
+
`db incrementOne ${model}`,
|
|
1465
|
+
{
|
|
1466
|
+
[ATTR_DB_OPERATION_NAME]: "incrementOne",
|
|
1467
|
+
[ATTR_DB_COLLECTION_NAME]: model,
|
|
1468
|
+
},
|
|
1469
|
+
() =>
|
|
1470
|
+
adapterInstance.incrementOne<T>({
|
|
1471
|
+
model,
|
|
1472
|
+
where,
|
|
1473
|
+
increment,
|
|
1474
|
+
set,
|
|
1475
|
+
}),
|
|
1476
|
+
);
|
|
1477
|
+
|
|
1478
|
+
debugLog(
|
|
1479
|
+
{ method: "incrementOne" },
|
|
1480
|
+
`${formatTransactionId(thisTransactionId)} ${formatStep(2, 3)}`,
|
|
1481
|
+
`${formatMethod("incrementOne")} ${formatAction("DB Result")}:`,
|
|
1482
|
+
{ model, data: res },
|
|
1483
|
+
);
|
|
1484
|
+
let transformed: any = res;
|
|
1485
|
+
if (!config.disableTransformOutput && res) {
|
|
1486
|
+
transformed = await transformOutput(
|
|
1487
|
+
res as Record<string, any>,
|
|
1488
|
+
unsafeModel,
|
|
1489
|
+
undefined,
|
|
1490
|
+
undefined,
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
debugLog(
|
|
1494
|
+
{ method: "incrementOne" },
|
|
1495
|
+
`${formatTransactionId(thisTransactionId)} ${formatStep(3, 3)}`,
|
|
1496
|
+
`${formatMethod("incrementOne")} ${formatAction("Parsed Result")}:`,
|
|
1497
|
+
{ model, data: transformed },
|
|
1498
|
+
);
|
|
1499
|
+
return transformed as T | null;
|
|
1500
|
+
},
|
|
1315
1501
|
count: async ({
|
|
1316
1502
|
model: unsafeModel,
|
|
1317
1503
|
where: unsafeWhere,
|
|
@@ -108,8 +108,6 @@ export const initGetIdField = ({
|
|
|
108
108
|
// if it's generated by us, then we should return the value as is.
|
|
109
109
|
if (shouldGenerateId && !forceAllowId) return value;
|
|
110
110
|
if (disableIdGeneration) return undefined;
|
|
111
|
-
// if DB will handle UUID generation, then we should return undefined.
|
|
112
|
-
if (supportsUUIDs) return undefined;
|
|
113
111
|
// if forceAllowId is true, it means we should be using the ID provided during the adapter call.
|
|
114
112
|
if (forceAllowId && typeof value === "string") {
|
|
115
113
|
const uuidRegex =
|
|
@@ -129,6 +127,8 @@ export const initGetIdField = ({
|
|
|
129
127
|
);
|
|
130
128
|
}
|
|
131
129
|
}
|
|
130
|
+
// if DB will handle UUID generation, then we should return undefined.
|
|
131
|
+
if (supportsUUIDs) return undefined;
|
|
132
132
|
// if the value is not a string, and the database doesn't support generating it's own UUIDs, then we should be generating the UUID.
|
|
133
133
|
if (typeof value !== "string" && !supportsUUIDs) {
|
|
134
134
|
return crypto.randomUUID();
|
package/src/db/adapter/index.ts
CHANGED
|
@@ -15,6 +15,8 @@ export type DBAdapterDebugLogOption =
|
|
|
15
15
|
findMany?: boolean | undefined;
|
|
16
16
|
delete?: boolean | undefined;
|
|
17
17
|
deleteMany?: boolean | undefined;
|
|
18
|
+
consumeOne?: boolean | undefined;
|
|
19
|
+
incrementOne?: boolean | undefined;
|
|
18
20
|
count?: boolean | undefined;
|
|
19
21
|
}
|
|
20
22
|
| {
|
|
@@ -211,6 +213,8 @@ export interface DBAdapterFactoryConfig<
|
|
|
211
213
|
| "updateMany"
|
|
212
214
|
| "delete"
|
|
213
215
|
| "deleteMany"
|
|
216
|
+
| "consumeOne"
|
|
217
|
+
| "incrementOne"
|
|
214
218
|
| "count";
|
|
215
219
|
/**
|
|
216
220
|
* The model name.
|
|
@@ -430,8 +434,14 @@ export type DBAdapter<Options extends BetterAuthOptions = BetterAuthOptions> = {
|
|
|
430
434
|
where?: Where[] | undefined;
|
|
431
435
|
}) => Promise<number>;
|
|
432
436
|
/**
|
|
433
|
-
*
|
|
434
|
-
*
|
|
437
|
+
* Update a single row matching the where clause.
|
|
438
|
+
*
|
|
439
|
+
* Returns the updated row, or `null` when no row matched. Empty `where`
|
|
440
|
+
* clauses return `null`; use `updateMany` for intentional bulk updates.
|
|
441
|
+
*
|
|
442
|
+
* This is not the race-safe primitive for guarded state transitions. Use
|
|
443
|
+
* `incrementOne` when the predicate is both selector and guard, and use
|
|
444
|
+
* `consumeOne` for single-use destructive reads.
|
|
435
445
|
*/
|
|
436
446
|
update: <T>(data: {
|
|
437
447
|
model: string;
|
|
@@ -445,6 +455,51 @@ export type DBAdapter<Options extends BetterAuthOptions = BetterAuthOptions> = {
|
|
|
445
455
|
}) => Promise<number>;
|
|
446
456
|
delete: <_T>(data: { model: string; where: Where[] }) => Promise<void>;
|
|
447
457
|
deleteMany: (data: { model: string; where: Where[] }) => Promise<number>;
|
|
458
|
+
/**
|
|
459
|
+
* Atomically consume a single row matching the where clause: delete it and
|
|
460
|
+
* return the deleted row, or return `null` if no row matched.
|
|
461
|
+
* Implementations MUST NOT delete any additional rows that also match a
|
|
462
|
+
* non-unique predicate.
|
|
463
|
+
*
|
|
464
|
+
* Under concurrent invocation against the same row, exactly one caller
|
|
465
|
+
* receives the row; subsequent racers receive `null`. This is the
|
|
466
|
+
* race-safe primitive for consuming single-use credentials
|
|
467
|
+
* (verification tokens, authorization codes, one-time tokens).
|
|
468
|
+
*
|
|
469
|
+
* Always defined on the factory-wrapped adapter. The underlying
|
|
470
|
+
* `CustomAdapter` must implement this natively; there is no portable
|
|
471
|
+
* fallback that can guarantee cross-process single-use semantics.
|
|
472
|
+
*/
|
|
473
|
+
consumeOne: <T>(data: { model: string; where: Where[] }) => Promise<T | null>;
|
|
474
|
+
/**
|
|
475
|
+
* Atomically apply signed numeric deltas to a single row matching the where
|
|
476
|
+
* clause. For each entry in `increment`, the operation applies
|
|
477
|
+
* `field = field + delta` in one atomic step; a negative delta decrements.
|
|
478
|
+
*
|
|
479
|
+
* The `where` clause is both the selector AND the guard: comparison
|
|
480
|
+
* operators are honored, so passing `{ field: "remaining", operator: "gt",
|
|
481
|
+
* value: 0 }` only mutates the row while `remaining` is still above zero.
|
|
482
|
+
* When the guard matches no row, the operation makes no change and returns
|
|
483
|
+
* `null`.
|
|
484
|
+
*
|
|
485
|
+
* The optional `set` map assigns absolute values to fields in the same
|
|
486
|
+
* atomic operation, alongside the increments.
|
|
487
|
+
*
|
|
488
|
+
* Returns the updated row, or `null` when the guard matched no row. Under
|
|
489
|
+
* concurrent invocation against the same row, this is the race-safe
|
|
490
|
+
* primitive for guarded counter updates (e.g. decrementing a remaining-uses
|
|
491
|
+
* counter only while it is still positive).
|
|
492
|
+
*
|
|
493
|
+
* Always defined on the factory-wrapped adapter. The underlying
|
|
494
|
+
* `CustomAdapter` must implement this natively; there is no portable
|
|
495
|
+
* fallback that can guarantee guarded counter semantics across runtimes.
|
|
496
|
+
*/
|
|
497
|
+
incrementOne: <T>(data: {
|
|
498
|
+
model: string;
|
|
499
|
+
where: Where[];
|
|
500
|
+
increment: Record<string, number>;
|
|
501
|
+
set?: Record<string, unknown> | undefined;
|
|
502
|
+
}) => Promise<T | null>;
|
|
448
503
|
/**
|
|
449
504
|
* Execute multiple operations in a transaction.
|
|
450
505
|
* If the adapter doesn't support transactions, operations will be executed sequentially.
|
|
@@ -531,6 +586,34 @@ export interface CustomAdapter {
|
|
|
531
586
|
model: string;
|
|
532
587
|
where: CleanedWhere[];
|
|
533
588
|
}) => Promise<number>;
|
|
589
|
+
/**
|
|
590
|
+
* Native atomic single-row consume.
|
|
591
|
+
* Implementing this method natively (e.g. `DELETE ... RETURNING *`,
|
|
592
|
+
* `findOneAndDelete`, `OUTPUT deleted.*`) gives one round trip and the
|
|
593
|
+
* strongest race-safety guarantee. Implementations must delete at most
|
|
594
|
+
* one matching row.
|
|
595
|
+
*/
|
|
596
|
+
consumeOne: <T>(data: {
|
|
597
|
+
model: string;
|
|
598
|
+
where: CleanedWhere[];
|
|
599
|
+
}) => Promise<T | null>;
|
|
600
|
+
/**
|
|
601
|
+
* Native atomic guarded counter mutation. Applies
|
|
602
|
+
* `field = field + delta` for each entry in `increment` (negative deltas
|
|
603
|
+
* decrement), with `where` acting as both selector and guard and `set`
|
|
604
|
+
* assigning absolute values in the same operation. Returns the updated row,
|
|
605
|
+
* or `null` when the guard matched no row.
|
|
606
|
+
*
|
|
607
|
+
* Implementing this natively (e.g. `UPDATE ... SET n = n + $delta WHERE ...
|
|
608
|
+
* RETURNING *`) gives one round trip and the strongest race-safety
|
|
609
|
+
* guarantee.
|
|
610
|
+
*/
|
|
611
|
+
incrementOne: <T>(data: {
|
|
612
|
+
model: string;
|
|
613
|
+
where: CleanedWhere[];
|
|
614
|
+
increment: Record<string, number>;
|
|
615
|
+
set?: Record<string, unknown> | undefined;
|
|
616
|
+
}) => Promise<T | null>;
|
|
534
617
|
count: ({
|
|
535
618
|
model,
|
|
536
619
|
where,
|
package/src/db/adapter/types.ts
CHANGED
|
@@ -122,6 +122,8 @@ export type AdapterFactoryCustomizeAdapterCreator = (config: {
|
|
|
122
122
|
| "updateMany"
|
|
123
123
|
| "delete"
|
|
124
124
|
| "deleteMany"
|
|
125
|
+
| "consumeOne"
|
|
126
|
+
| "incrementOne"
|
|
125
127
|
| "count";
|
|
126
128
|
}) => W extends undefined ? undefined : CleanedWhere[];
|
|
127
129
|
}) => CustomAdapter;
|
package/src/db/get-tables.ts
CHANGED
|
@@ -15,13 +15,19 @@ export const getAuthTables = (
|
|
|
15
15
|
...value.fields,
|
|
16
16
|
},
|
|
17
17
|
modelName: value.modelName || key,
|
|
18
|
+
disableMigrations:
|
|
19
|
+
value.disableMigration ?? acc[key]?.disableMigrations,
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
22
|
return acc;
|
|
21
23
|
},
|
|
22
24
|
{} as Record<
|
|
23
25
|
string,
|
|
24
|
-
{
|
|
26
|
+
{
|
|
27
|
+
fields: Record<string, DBFieldAttribute>;
|
|
28
|
+
modelName: string;
|
|
29
|
+
disableMigrations?: boolean | undefined;
|
|
30
|
+
}
|
|
25
31
|
>,
|
|
26
32
|
);
|
|
27
33
|
|
|
@@ -162,6 +168,8 @@ export const getAuthTables = (
|
|
|
162
168
|
},
|
|
163
169
|
email: {
|
|
164
170
|
type: "string",
|
|
171
|
+
// TODO(#9124): drop required+unique in v2; use a partial unique
|
|
172
|
+
// index where email is not null (see schema/user.ts).
|
|
165
173
|
unique: true,
|
|
166
174
|
required: true,
|
|
167
175
|
fieldName: options.user?.fields?.email || "email",
|
package/src/db/schema/account.ts
CHANGED
|
@@ -23,7 +23,10 @@ export const accountSchema = coreSchema.extend({
|
|
|
23
23
|
*/
|
|
24
24
|
refreshTokenExpiresAt: z.date().nullish(),
|
|
25
25
|
/**
|
|
26
|
-
* The scopes
|
|
26
|
+
* The set of OAuth scopes the user has granted to this account, stored
|
|
27
|
+
* as a comma-separated list. Represents the accumulated grant rather
|
|
28
|
+
* than the latest token's `scope` claim, since per RFC 6749 Section 1.5 a
|
|
29
|
+
* token's scope may be narrower than the user's grant.
|
|
27
30
|
*/
|
|
28
31
|
scope: z.string().nullish(),
|
|
29
32
|
/**
|
package/src/db/schema/user.ts
CHANGED
|
@@ -7,6 +7,9 @@ import type {
|
|
|
7
7
|
import { coreSchema } from "./shared";
|
|
8
8
|
|
|
9
9
|
export const userSchema = coreSchema.extend({
|
|
10
|
+
// TODO(#9124): widen to nullish in v2. OAuth providers (Discord phone-only,
|
|
11
|
+
// Apple subsequent sign-ins, etc.) can legitimately omit email; identity
|
|
12
|
+
// must key on (providerId, accountId) per OpenID Connect Core §5.7.
|
|
10
13
|
email: z.string().transform((val) => val.toLowerCase()),
|
|
11
14
|
emailVerified: z.boolean().default(false),
|
|
12
15
|
name: z.string(),
|
package/src/db/type.ts
CHANGED
|
@@ -311,6 +311,23 @@ export interface SecondaryStorage {
|
|
|
311
311
|
* @returns - Value of the key
|
|
312
312
|
*/
|
|
313
313
|
get: (key: string) => Awaitable<unknown>;
|
|
314
|
+
/**
|
|
315
|
+
* Atomically get a value and delete it from storage.
|
|
316
|
+
*/
|
|
317
|
+
getAndDelete: (key: string) => Awaitable<unknown>;
|
|
318
|
+
/**
|
|
319
|
+
* Atomically increment the counter at `key` by one, returning the
|
|
320
|
+
* post-increment value.
|
|
321
|
+
*
|
|
322
|
+
* When the key is absent, it is created with a value of `1` and the given
|
|
323
|
+
* `ttl` (in SECONDS). The TTL is applied only on creation; later increments
|
|
324
|
+
* never extend it, so the counter expires a fixed window after it was first
|
|
325
|
+
* created.
|
|
326
|
+
*
|
|
327
|
+
* Required so secondary-storage-backed rate limiting can enforce the limit
|
|
328
|
+
* in one distributed-safe operation.
|
|
329
|
+
*/
|
|
330
|
+
increment: (key: string, ttl: number) => Awaitable<number>;
|
|
314
331
|
set: (
|
|
315
332
|
/**
|
|
316
333
|
* Key to store
|
package/src/env/env-impl.ts
CHANGED
|
@@ -46,8 +46,7 @@ function toBoolean(val: boolean | string | undefined) {
|
|
|
46
46
|
return val ? val !== "false" : false;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export const nodeENV =
|
|
50
|
-
(typeof process !== "undefined" && process.env && process.env.NODE_ENV) || "";
|
|
49
|
+
export const nodeENV = env.NODE_ENV ?? "";
|
|
51
50
|
|
|
52
51
|
/** Detect if `NODE_ENV` environment variable is `production` */
|
|
53
52
|
export const isProduction = nodeENV === "production";
|
package/src/error/codes.ts
CHANGED
|
@@ -37,6 +37,7 @@ export const BASE_ERROR_CODES = defineErrorCodes({
|
|
|
37
37
|
USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL:
|
|
38
38
|
"User already exists. Use another email.",
|
|
39
39
|
EMAIL_CAN_NOT_BE_UPDATED: "Email can not be updated",
|
|
40
|
+
CHANGE_EMAIL_DISABLED: "Change email is disabled",
|
|
40
41
|
CREDENTIAL_ACCOUNT_NOT_FOUND: "Credential account not found",
|
|
41
42
|
SESSION_EXPIRED: "Session expired. Re-authenticate to perform this action.",
|
|
42
43
|
FAILED_TO_UNLINK_LAST_ACCOUNT: "You can't unlink your last account",
|
package/src/error/index.ts
CHANGED
|
@@ -11,7 +11,16 @@ export class BetterAuthError extends Error {
|
|
|
11
11
|
|
|
12
12
|
export { type APIErrorCode, BASE_ERROR_CODES } from "./codes";
|
|
13
13
|
|
|
14
|
+
type BaseAPIErrorInstance = InstanceType<typeof BaseAPIError>;
|
|
15
|
+
|
|
14
16
|
export class APIError extends BaseAPIError {
|
|
17
|
+
declare status: BaseAPIErrorInstance["status"];
|
|
18
|
+
declare body: BaseAPIErrorInstance["body"];
|
|
19
|
+
declare headers: BaseAPIErrorInstance["headers"];
|
|
20
|
+
declare statusCode: BaseAPIErrorInstance["statusCode"];
|
|
21
|
+
declare message: string;
|
|
22
|
+
declare errorStack: BaseAPIErrorInstance["errorStack"];
|
|
23
|
+
|
|
15
24
|
constructor(...args: ConstructorParameters<typeof BaseAPIError>) {
|
|
16
25
|
super(...args);
|
|
17
26
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { OpenTelemetryAPI } from "./noop";
|
|
2
|
+
import { noopOpenTelemetryAPI } from "./noop";
|
|
3
|
+
|
|
4
|
+
let openTelemetryAPIPromise: Promise<void> | undefined;
|
|
5
|
+
let openTelemetryAPI: OpenTelemetryAPI | undefined;
|
|
6
|
+
|
|
7
|
+
export function getOpenTelemetryAPI(): OpenTelemetryAPI {
|
|
8
|
+
if (!openTelemetryAPIPromise) {
|
|
9
|
+
openTelemetryAPIPromise = import("@opentelemetry/api")
|
|
10
|
+
.then((mod) => {
|
|
11
|
+
openTelemetryAPI = mod;
|
|
12
|
+
})
|
|
13
|
+
.catch(() => /* ignore failures */ undefined);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return openTelemetryAPI ?? noopOpenTelemetryAPI;
|
|
17
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Span, Tracer } from "@opentelemetry/api";
|
|
2
|
+
|
|
3
|
+
export type OpenTelemetryAPI = Pick<
|
|
4
|
+
typeof import("@opentelemetry/api"),
|
|
5
|
+
"trace" | "SpanStatusCode"
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
function createNoopSpan(): Span {
|
|
9
|
+
const span = {
|
|
10
|
+
end(): void {},
|
|
11
|
+
setAttribute(_key: string, _value: unknown): void {},
|
|
12
|
+
setStatus(_status: unknown): void {},
|
|
13
|
+
recordException(_exception: unknown): void {},
|
|
14
|
+
updateName(_name: string) {
|
|
15
|
+
return span;
|
|
16
|
+
},
|
|
17
|
+
} as unknown as Span;
|
|
18
|
+
return span;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createNoopTracer(noopSpan: Span): Tracer {
|
|
22
|
+
// OpenTelemetry `Tracer.startActiveSpan` has three overloads:
|
|
23
|
+
// (name, fn)
|
|
24
|
+
// (name, options, fn)
|
|
25
|
+
// (name, options, context, fn)
|
|
26
|
+
// The callback is always the last argument; fish it out by arity so a
|
|
27
|
+
// 2-arg call (options omitted) doesn't try to invoke `undefined`.
|
|
28
|
+
function startActiveSpan<F extends (span: Span) => unknown>(
|
|
29
|
+
_name: string,
|
|
30
|
+
fn: F,
|
|
31
|
+
): ReturnType<F>;
|
|
32
|
+
function startActiveSpan<F extends (span: Span) => unknown>(
|
|
33
|
+
_name: string,
|
|
34
|
+
_options: { attributes?: Record<string, string | number | boolean> },
|
|
35
|
+
fn: F,
|
|
36
|
+
): ReturnType<F>;
|
|
37
|
+
function startActiveSpan<F extends (span: Span) => unknown>(
|
|
38
|
+
_name: string,
|
|
39
|
+
_options: { attributes?: Record<string, string | number | boolean> },
|
|
40
|
+
_context: unknown,
|
|
41
|
+
fn: F,
|
|
42
|
+
): ReturnType<F>;
|
|
43
|
+
function startActiveSpan(_name: string, ...rest: Array<unknown>): unknown {
|
|
44
|
+
const fn = rest[rest.length - 1] as (span: Span) => unknown;
|
|
45
|
+
return fn(noopSpan);
|
|
46
|
+
}
|
|
47
|
+
return { startActiveSpan } as Tracer;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createNoopTraceAPI() {
|
|
51
|
+
const noopTracer = createNoopTracer(createNoopSpan());
|
|
52
|
+
return {
|
|
53
|
+
getTracer(_name?: string, _version?: string) {
|
|
54
|
+
return noopTracer;
|
|
55
|
+
},
|
|
56
|
+
getActiveSpan(): Span | undefined {
|
|
57
|
+
return undefined;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createNoopOpenTelemetryAPI(): OpenTelemetryAPI {
|
|
63
|
+
return {
|
|
64
|
+
SpanStatusCode: {
|
|
65
|
+
UNSET: 0,
|
|
66
|
+
OK: 1,
|
|
67
|
+
ERROR: 2,
|
|
68
|
+
},
|
|
69
|
+
trace: createNoopTraceAPI(),
|
|
70
|
+
} as OpenTelemetryAPI;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const noopOpenTelemetryAPI: OpenTelemetryAPI =
|
|
74
|
+
createNoopOpenTelemetryAPI();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Noop variant of `./instrumentation` for runtimes where the dynamic
|
|
3
|
+
* `import("@opentelemetry/api")` in `./api` throws synchronously instead of
|
|
4
|
+
* rejecting its returned promise. Convex's V8 isolate is the reproducer: bare
|
|
5
|
+
* specifiers are rejected at resolve time in `get-convex/convex-backend`
|
|
6
|
+
* `crates/isolate/src/request_scope.rs`, so the `.catch()` in
|
|
7
|
+
* `getOpenTelemetryAPI` never runs and every `withSpan` call surfaces an
|
|
8
|
+
* uncaught error.
|
|
9
|
+
*
|
|
10
|
+
* Public surface must stay identical to `./index` (enforced by `pure.test.ts`).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export * from "./attributes";
|
|
14
|
+
|
|
15
|
+
export function withSpan<T>(
|
|
16
|
+
name: string,
|
|
17
|
+
attributes: Record<string, string | number | boolean>,
|
|
18
|
+
fn: () => T,
|
|
19
|
+
): T;
|
|
20
|
+
export function withSpan<T>(
|
|
21
|
+
name: string,
|
|
22
|
+
attributes: Record<string, string | number | boolean>,
|
|
23
|
+
fn: () => Promise<T>,
|
|
24
|
+
): Promise<T>;
|
|
25
|
+
export function withSpan<T>(
|
|
26
|
+
_name: string,
|
|
27
|
+
_attributes: Record<string, string | number | boolean>,
|
|
28
|
+
fn: () => T | Promise<T>,
|
|
29
|
+
): T | Promise<T> {
|
|
30
|
+
return fn();
|
|
31
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { Span } from "@opentelemetry/api";
|
|
2
|
-
import {
|
|
2
|
+
import { getOpenTelemetryAPI } from "./api";
|
|
3
3
|
import { ATTR_HTTP_RESPONSE_STATUS_CODE } from "./attributes";
|
|
4
4
|
|
|
5
5
|
const INSTRUMENTATION_SCOPE = "better-auth";
|
|
6
6
|
const INSTRUMENTATION_VERSION = import.meta.env?.BETTER_AUTH_VERSION ?? "1.0.0";
|
|
7
7
|
|
|
8
|
-
const tracer = trace.getTracer(INSTRUMENTATION_SCOPE, INSTRUMENTATION_VERSION);
|
|
9
|
-
|
|
10
8
|
/**
|
|
11
9
|
* Better-auth uses `throw ctx.redirect(url)` for flow control (e.g. OAuth
|
|
12
10
|
* callbacks). These are APIErrors with 3xx status codes and should not be
|
|
@@ -27,6 +25,7 @@ function isRedirectError(err: unknown): boolean {
|
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
function endSpanWithError(span: Span, err: unknown) {
|
|
28
|
+
const { SpanStatusCode } = getOpenTelemetryAPI();
|
|
30
29
|
if (isRedirectError(err)) {
|
|
31
30
|
span.setAttribute(
|
|
32
31
|
ATTR_HTTP_RESPONSE_STATUS_CODE,
|
|
@@ -66,6 +65,12 @@ export function withSpan<T>(
|
|
|
66
65
|
attributes: Record<string, string | number | boolean>,
|
|
67
66
|
fn: () => T | Promise<T>,
|
|
68
67
|
): T | Promise<T> {
|
|
68
|
+
const { trace } = getOpenTelemetryAPI();
|
|
69
|
+
const tracer = trace.getTracer(
|
|
70
|
+
INSTRUMENTATION_SCOPE,
|
|
71
|
+
INSTRUMENTATION_VERSION,
|
|
72
|
+
);
|
|
73
|
+
|
|
69
74
|
return tracer.startActiveSpan(name, { attributes }, (span) => {
|
|
70
75
|
try {
|
|
71
76
|
const result = fn();
|