@estifanos-sh/convex-auth 0.0.9 → 0.0.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/bin.js +45 -84
- package/dist/client/factors/device.js +3 -8
- package/dist/client/factors/device.js.map +1 -1
- package/dist/component/account.d.ts +33 -33
- package/dist/component/account.d.ts.map +1 -1
- package/dist/component/account.js +6 -17
- package/dist/component/account.js.map +1 -1
- package/dist/component/batch.js +2 -5
- package/dist/component/batch.js.map +1 -1
- package/dist/component/connection/audit.d.ts +11 -11
- package/dist/component/connection/domain/verification.d.ts +15 -15
- package/dist/component/connection/domain.d.ts +18 -18
- package/dist/component/connection/domain.d.ts.map +1 -1
- package/dist/component/connection/domain.js +5 -13
- package/dist/component/connection/domain.js.map +1 -1
- package/dist/component/connection/saml/assertion.d.ts +4 -4
- package/dist/component/connection/saml/request.d.ts +7 -7
- package/dist/component/connection/scim/config.d.ts +11 -11
- package/dist/component/connection/scim/identity.d.ts +41 -41
- package/dist/component/connection/scim/identity.d.ts.map +1 -1
- package/dist/component/connection/scim/identity.js +24 -89
- package/dist/component/connection/scim/identity.js.map +1 -1
- package/dist/component/connection/secret.d.ts +13 -13
- package/dist/component/connection/webhook/delivery.d.ts +22 -22
- package/dist/component/connection/webhook/endpoint.d.ts +16 -16
- package/dist/component/connection.d.ts +26 -26
- package/dist/component/connection.d.ts.map +1 -1
- package/dist/component/connection.js +4 -9
- package/dist/component/connection.js.map +1 -1
- package/dist/component/context.d.ts +18 -18
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/convex.config.d.ts.map +1 -1
- package/dist/component/crons.d.ts +2 -2
- package/dist/component/documents.d.ts +50 -50
- package/dist/component/event.d.ts +41 -41
- package/dist/component/event.d.ts.map +1 -1
- package/dist/component/event.js +5 -13
- package/dist/component/event.js.map +1 -1
- package/dist/component/factor/device.d.ts +21 -21
- package/dist/component/factor/device.d.ts.map +1 -1
- package/dist/component/factor/passkey.d.ts +76 -76
- package/dist/component/factor/passkey.d.ts.map +1 -1
- package/dist/component/factor/passkey.js +9 -31
- package/dist/component/factor/passkey.js.map +1 -1
- package/dist/component/factor/totp.d.ts +46 -46
- package/dist/component/factor/totp.d.ts.map +1 -1
- package/dist/component/factor/totp.js +4 -9
- package/dist/component/factor/totp.js.map +1 -1
- package/dist/component/group/active.d.ts +84 -84
- package/dist/component/group/active.d.ts.map +1 -1
- package/dist/component/group/active.js +3 -6
- package/dist/component/group/active.js.map +1 -1
- package/dist/component/group/invite.d.ts +29 -29
- package/dist/component/group/invite.d.ts.map +1 -1
- package/dist/component/group/invite.js +18 -64
- package/dist/component/group/invite.js.map +1 -1
- package/dist/component/group/member.d.ts +34 -34
- package/dist/component/group/member.d.ts.map +1 -1
- package/dist/component/group/member.js +3 -4
- package/dist/component/group/member.js.map +1 -1
- package/dist/component/group.d.ts +34 -34
- package/dist/component/group.d.ts.map +1 -1
- package/dist/component/group.js +4 -9
- package/dist/component/group.js.map +1 -1
- package/dist/component/http.d.ts +2 -2
- package/dist/component/limits.d.ts +4 -4
- package/dist/component/maintenance.d.ts +2 -2
- package/dist/component/model.d.ts +494 -494
- package/dist/component/oauth/client.d.ts +18 -18
- package/dist/component/oauth/code.d.ts +11 -11
- package/dist/component/oauth/refresh.d.ts +16 -16
- package/dist/component/schema.d.ts +673 -673
- package/dist/component/session.d.ts +26 -26
- package/dist/component/session.d.ts.map +1 -1
- package/dist/component/token/continuation.d.ts +18 -18
- package/dist/component/token/continuation.d.ts.map +1 -1
- package/dist/component/token/enrollment.d.ts +9 -9
- package/dist/component/token/pkce.d.ts +22 -22
- package/dist/component/token/refresh.d.ts +33 -33
- package/dist/component/token/verification.d.ts +30 -30
- package/dist/component/token/verification.d.ts.map +1 -1
- package/dist/component/user/email.d.ts +17 -17
- package/dist/component/user/email.d.ts.map +1 -1
- package/dist/component/user/email.js +9 -29
- package/dist/component/user/email.js.map +1 -1
- package/dist/component/user/key.d.ts +27 -27
- package/dist/component/user/key.d.ts.map +1 -1
- package/dist/component/user/key.js +5 -15
- package/dist/component/user/key.js.map +1 -1
- package/dist/component/user.d.ts +39 -39
- package/dist/component/user.d.ts.map +1 -1
- package/dist/component/user.js +3 -5
- package/dist/component/user.js.map +1 -1
- package/dist/providers/anonymous.d.ts +3 -3
- package/dist/providers/anonymous.d.ts.map +1 -1
- package/dist/providers/password.d.ts +40 -40
- package/dist/providers/password.d.ts.map +1 -1
- package/dist/providers/password.js +12 -41
- package/dist/providers/password.js.map +1 -1
- package/dist/server/auth.js +5 -17
- package/dist/server/auth.js.map +1 -1
- package/dist/server/connection/checks.d.ts +16 -0
- package/dist/server/connection/checks.d.ts.map +1 -0
- package/dist/server/connection/checks.js +44 -0
- package/dist/server/connection/checks.js.map +1 -0
- package/dist/server/connection/domain.d.ts +9 -43
- package/dist/server/connection/domain.d.ts.map +1 -1
- package/dist/server/connection/domain.js +26 -123
- package/dist/server/connection/domain.js.map +1 -1
- package/dist/server/connection/group/service.d.ts.map +1 -1
- package/dist/server/connection/group/service.js +7 -25
- package/dist/server/connection/group/service.js.map +1 -1
- package/dist/server/connection/http.js +1 -1
- package/dist/server/connection/policy/domain.js +1 -1
- package/dist/server/connection/provision.js +9 -39
- package/dist/server/connection/provision.js.map +1 -1
- package/dist/server/connection/webhook.js +1 -1
- package/dist/server/context.js +3 -9
- package/dist/server/context.js.map +1 -1
- package/dist/server/core.js +4 -14
- package/dist/server/core.js.map +1 -1
- package/dist/server/crypto.js +1 -1
- package/dist/server/domains/account.d.ts.map +1 -1
- package/dist/server/domains/account.js +3 -9
- package/dist/server/domains/account.js.map +1 -1
- package/dist/server/domains/key.js +9 -33
- package/dist/server/domains/key.js.map +1 -1
- package/dist/server/domains/member.js +4 -14
- package/dist/server/domains/member.js.map +1 -1
- package/dist/server/env.js +3 -9
- package/dist/server/env.js.map +1 -1
- package/dist/server/errors.js +13 -23
- package/dist/server/errors.js.map +1 -1
- package/dist/server/facade.d.ts.map +1 -1
- package/dist/server/facade.js +2 -5
- package/dist/server/facade.js.map +1 -1
- package/dist/server/http.js +2 -4
- package/dist/server/http.js.map +1 -1
- package/dist/server/identity/claims.js +3 -9
- package/dist/server/identity/claims.js.map +1 -1
- package/dist/server/mutations/account.js +3 -5
- package/dist/server/mutations/account.js.map +1 -1
- package/dist/server/mutations/code.js +4 -9
- package/dist/server/mutations/code.js.map +1 -1
- package/dist/server/mutations/enrollment.js +3 -5
- package/dist/server/mutations/enrollment.js.map +1 -1
- package/dist/server/mutations/oauth.js +5 -13
- package/dist/server/mutations/oauth.js.map +1 -1
- package/dist/server/mutations/register.js +7 -21
- package/dist/server/mutations/register.js.map +1 -1
- package/dist/server/mutations/signature.js +3 -5
- package/dist/server/mutations/signature.js.map +1 -1
- package/dist/server/oauth/code.d.ts.map +1 -1
- package/dist/server/oauth/code.js +2 -4
- package/dist/server/oauth/code.js.map +1 -1
- package/dist/server/oauth/handlers.js +5 -18
- package/dist/server/oauth/handlers.js.map +1 -1
- package/dist/server/redirects.js +5 -17
- package/dist/server/redirects.js.map +1 -1
- package/dist/server/runtime.d.ts +10 -10
- package/dist/server/runtime.d.ts.map +1 -1
- package/dist/server/runtime.js +6 -17
- package/dist/server/runtime.js.map +1 -1
- package/dist/server/secret.js +2 -5
- package/dist/server/secret.js.map +1 -1
- package/dist/server/services/factors.js +4 -14
- package/dist/server/services/factors.js.map +1 -1
- package/dist/server/services/providers.js +2 -6
- package/dist/server/services/providers.js.map +1 -1
- package/dist/server/signin/flow.js +2 -5
- package/dist/server/signin/flow.js.map +1 -1
- package/dist/server/token/refresh.js +3 -9
- package/dist/server/token/refresh.js.map +1 -1
- package/dist/server/totp.js +2 -1
- package/dist/server/totp.js.map +1 -1
- package/dist/server/user/account.js +4 -13
- package/dist/server/user/account.js.map +1 -1
- package/dist/server/validators.d.ts +52 -52
- package/dist/server/webauthn.js +17 -4
- package/dist/server/webauthn.js.map +1 -1
- package/dist/shared/errors.js +18 -2
- package/dist/shared/errors.js.map +1 -1
- package/dist/test.d.ts +614 -614
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -6509,10 +6509,10 @@ async function runSetup(options) {
|
|
|
6509
6509
|
await configureAppUrl(config, options.appUrl);
|
|
6510
6510
|
await configureKeys(config);
|
|
6511
6511
|
await modifyTsConfig(config);
|
|
6512
|
-
await
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6512
|
+
for (const file of SCAFFOLDED_FILES) await scaffoldFile({
|
|
6513
|
+
config,
|
|
6514
|
+
...file
|
|
6515
|
+
});
|
|
6516
6516
|
if (options.variables !== void 0) await configureOtherVariables(config, options.variables);
|
|
6517
6517
|
else printFinalSuccessMessage(config);
|
|
6518
6518
|
gt("Done! Happy building.");
|
|
@@ -6835,9 +6835,17 @@ function addCompilerOption(tsconfig, existingValue, pattern, optionAndValue) {
|
|
|
6835
6835
|
if (existingValue === void 0) return tsconfig.replace(compilerOptionsPattern, `$1${optionAndValue},$2`);
|
|
6836
6836
|
else return tsconfig.replace(pattern, optionAndValue);
|
|
6837
6837
|
}
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6838
|
+
/**
|
|
6839
|
+
* `sourceTemplate` does double duty: `$$` markers are stripped to produce the
|
|
6840
|
+
* file that gets written, and become `.*` wildcards in the "already set up?"
|
|
6841
|
+
* match. Editing a template changes both.
|
|
6842
|
+
*/
|
|
6843
|
+
const SCAFFOLDED_FILES = [
|
|
6844
|
+
{
|
|
6845
|
+
step: "Configure convex config file",
|
|
6846
|
+
basename: "convex.config",
|
|
6847
|
+
guidance: "registers the auth component",
|
|
6848
|
+
sourceTemplate: `\
|
|
6841
6849
|
import { defineApp } from "convex/server";
|
|
6842
6850
|
import auth from "@estifanos-sh/convex-auth/convex.config";
|
|
6843
6851
|
|
|
@@ -6846,30 +6854,13 @@ const app = defineApp();
|
|
|
6846
6854
|
app.use(auth);
|
|
6847
6855
|
|
|
6848
6856
|
export default app;
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
O.message(indent(`\n${source}\n`));
|
|
6857
|
-
const ready = await ot({ message: "Ready to continue?" });
|
|
6858
|
-
handleCancel(ready);
|
|
6859
|
-
if (!ready) {
|
|
6860
|
-
pt("Setup cancelled.");
|
|
6861
|
-
process$1.exit(1);
|
|
6862
|
-
}
|
|
6863
|
-
}
|
|
6864
|
-
else {
|
|
6865
|
-
const newConfigPath = config.usesTypeScript ? `${convexConfigPath}.ts` : `${convexConfigPath}.js`;
|
|
6866
|
-
writeFileSync(newConfigPath, source);
|
|
6867
|
-
O.success(`Created ${newConfigPath}`);
|
|
6868
|
-
}
|
|
6869
|
-
}
|
|
6870
|
-
async function initializeAuth(config) {
|
|
6871
|
-
logStep(config, "Initialize auth file");
|
|
6872
|
-
const sourceTemplate = `\
|
|
6857
|
+
`
|
|
6858
|
+
},
|
|
6859
|
+
{
|
|
6860
|
+
step: "Initialize auth file",
|
|
6861
|
+
basename: "auth",
|
|
6862
|
+
guidance: "initializes auth with defineAuth",
|
|
6863
|
+
sourceTemplate: `\
|
|
6873
6864
|
import { defineAuth } from "@estifanos-sh/convex-auth/server";
|
|
6874
6865
|
import { components } from "./_generated/api";
|
|
6875
6866
|
|
|
@@ -6879,57 +6870,23 @@ const auth = defineAuth(components.auth, {$$
|
|
|
6879
6870
|
|
|
6880
6871
|
export { auth };
|
|
6881
6872
|
export const { signIn, signOut, store } = auth;
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
O.message(indent(`\n${source}\n`));
|
|
6890
|
-
const ready = await ot({ message: "Ready to continue?" });
|
|
6891
|
-
handleCancel(ready);
|
|
6892
|
-
if (!ready) {
|
|
6893
|
-
pt("Setup cancelled.");
|
|
6894
|
-
process$1.exit(1);
|
|
6895
|
-
}
|
|
6896
|
-
}
|
|
6897
|
-
else {
|
|
6898
|
-
const newAuthPath = config.usesTypeScript ? `${authPath}.ts` : `${authPath}.js`;
|
|
6899
|
-
writeFileSync(newAuthPath, source);
|
|
6900
|
-
O.success(`Created ${newAuthPath}`);
|
|
6901
|
-
}
|
|
6902
|
-
}
|
|
6903
|
-
async function initializeHttp(config) {
|
|
6904
|
-
logStep(config, "Initialize HTTP auth routes");
|
|
6905
|
-
const sourceTemplate = `\
|
|
6873
|
+
`
|
|
6874
|
+
},
|
|
6875
|
+
{
|
|
6876
|
+
step: "Initialize HTTP auth routes",
|
|
6877
|
+
basename: "http",
|
|
6878
|
+
guidance: "mounts Convex Auth protocol routes",
|
|
6879
|
+
sourceTemplate: `\
|
|
6906
6880
|
import { auth } from "./auth";
|
|
6907
6881
|
|
|
6908
6882
|
export default auth.http();
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
O.message(indent(`\n${source}\n`));
|
|
6917
|
-
const ready = await ot({ message: "Ready to continue?" });
|
|
6918
|
-
handleCancel(ready);
|
|
6919
|
-
if (!ready) {
|
|
6920
|
-
pt("Setup cancelled.");
|
|
6921
|
-
process$1.exit(1);
|
|
6922
|
-
}
|
|
6923
|
-
}
|
|
6924
|
-
else {
|
|
6925
|
-
const newPath = config.usesTypeScript ? `${httpPath}.ts` : `${httpPath}.js`;
|
|
6926
|
-
writeFileSync(newPath, source);
|
|
6927
|
-
O.success(`Created ${newPath}`);
|
|
6928
|
-
}
|
|
6929
|
-
}
|
|
6930
|
-
async function initializeAuthConfig(config) {
|
|
6931
|
-
logStep(config, "Initialize auth.config file");
|
|
6932
|
-
const sourceTemplate = `\
|
|
6883
|
+
`
|
|
6884
|
+
},
|
|
6885
|
+
{
|
|
6886
|
+
step: "Initialize auth.config file",
|
|
6887
|
+
basename: "auth.config",
|
|
6888
|
+
guidance: "trusts env.CONVEX_SITE_URL as the Convex auth issuer",
|
|
6889
|
+
sourceTemplate: `\
|
|
6933
6890
|
import { env } from "./_generated/server";$$
|
|
6934
6891
|
$$
|
|
6935
6892
|
export default {$$
|
|
@@ -6940,13 +6897,17 @@ export default {$$
|
|
|
6940
6897
|
},$$
|
|
6941
6898
|
],$$
|
|
6942
6899
|
};
|
|
6943
|
-
|
|
6900
|
+
`
|
|
6901
|
+
}
|
|
6902
|
+
];
|
|
6903
|
+
async function scaffoldFile({ config, step, basename, sourceTemplate, guidance }) {
|
|
6904
|
+
logStep(config, step);
|
|
6944
6905
|
const source = templateToSource(sourceTemplate);
|
|
6945
|
-
const
|
|
6946
|
-
const existingPath = existingNonEmptySourcePath(
|
|
6906
|
+
const targetPath = path.join(config.convexFolderPath, basename);
|
|
6907
|
+
const existingPath = existingNonEmptySourcePath(targetPath);
|
|
6947
6908
|
if (existingPath !== null) if (doesAlreadyMatchTemplate(readFileSync(existingPath, "utf8"), sourceTemplate)) O.success(`${existingPath} is already set up.`);
|
|
6948
6909
|
else {
|
|
6949
|
-
O.info(`You already have ${existingPath}. Make sure it
|
|
6910
|
+
O.info(`You already have ${existingPath}. Make sure it ${guidance}:`);
|
|
6950
6911
|
O.message(indent(`\n${source}\n`));
|
|
6951
6912
|
const ready = await ot({ message: "Ready to continue?" });
|
|
6952
6913
|
handleCancel(ready);
|
|
@@ -6956,7 +6917,7 @@ export default {$$
|
|
|
6956
6917
|
}
|
|
6957
6918
|
}
|
|
6958
6919
|
else {
|
|
6959
|
-
const newPath = config.usesTypeScript ? `${
|
|
6920
|
+
const newPath = config.usesTypeScript ? `${targetPath}.ts` : `${targetPath}.js`;
|
|
6960
6921
|
writeFileSync(newPath, source);
|
|
6961
6922
|
O.success(`Created ${newPath}`);
|
|
6962
6923
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ErrorCode } from "../../shared/codes.js";
|
|
2
|
+
import { convexError } from "../../shared/errors.js";
|
|
2
3
|
import { ConvexError } from "convex/values";
|
|
3
4
|
|
|
4
5
|
//#region src/client/factors/device.ts
|
|
@@ -73,10 +74,7 @@ function createDeviceClient(deps) {
|
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
|
-
throw
|
|
77
|
-
code: ErrorCode.DEVICE_CODE_EXPIRED,
|
|
78
|
-
message: "Device code expired before authorization was completed."
|
|
79
|
-
});
|
|
77
|
+
throw convexError(ErrorCode.DEVICE_CODE_EXPIRED, "Device code expired before authorization was completed.");
|
|
80
78
|
},
|
|
81
79
|
verify: async (opts) => {
|
|
82
80
|
const params = {
|
|
@@ -86,10 +84,7 @@ function createDeviceClient(deps) {
|
|
|
86
84
|
try {
|
|
87
85
|
await requestDeviceSignIn(params);
|
|
88
86
|
} catch (error) {
|
|
89
|
-
throw
|
|
90
|
-
code: ErrorCode.DEVICE_AUTHORIZATION_FAILED,
|
|
91
|
-
message: error instanceof Error ? error.message : "Invalid or expired code."
|
|
92
|
-
});
|
|
87
|
+
throw convexError(ErrorCode.DEVICE_AUTHORIZATION_FAILED, error instanceof Error ? error.message : "Invalid or expired code.");
|
|
93
88
|
}
|
|
94
89
|
}
|
|
95
90
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device.js","names":[],"sources":["../../../src/client/factors/device.ts"],"sourcesContent":["import { ConvexError } from \"convex/values\";\n\nimport type { DeviceClient, DevicePollParams, FactorDeps, SignInActionResult } from \"../core/types\";\nimport type { AuthTokens } from \"../../shared/results\";\nimport type { AuthParameters } from \"../../shared/results\";\nimport { ErrorCode } from \"../../shared/codes\";\n\nfunction isSignedInResult(\n result: SignInActionResult,\n): result is Extract<SignInActionResult, { kind: \"signedIn\" }> {\n return result.kind === \"signedIn\";\n}\n\n/** @internal */\nexport function createDeviceClient(deps: FactorDeps): DeviceClient {\n const { proxy, convex, requireApiRefs, proxyFetch, setTokenAndMaybeWait } = deps;\n\n const requestDeviceSignIn = async (params: AuthParameters): Promise<SignInActionResult> => {\n return (\n proxy\n ? await proxyFetch({\n action: \"auth:signIn\",\n args: { request: { provider: \"device\", params } },\n })\n : await convex.action(requireApiRefs().signIn, {\n request: { provider: \"device\", params },\n })\n ) as SignInActionResult;\n };\n\n return {\n poll: async (opts: DevicePollParams): Promise<void> => {\n const { code, signal } = opts;\n const MAX_POLL_DURATION_MS = 30 * 60 * 1000;\n const SLOW_DOWN_INCREMENT_MS = 5 * 1000;\n let currentIntervalMs = code.interval * 1000;\n const startedAt = Date.now();\n const expiresAt = Math.min(\n startedAt + code.expiresIn * 1000,\n startedAt + MAX_POLL_DURATION_MS,\n );\n\n while (Date.now() < expiresAt) {\n if (signal?.aborted) return;\n await new Promise((resolve) => setTimeout(resolve, currentIntervalMs));\n if (signal?.aborted) return;\n\n const params: AuthParameters = {\n flow: \"poll\",\n deviceCode: code.deviceCode,\n };\n\n let pollResult: SignInActionResult | null;\n try {\n pollResult = await requestDeviceSignIn(params);\n } catch (error) {\n if (error instanceof ConvexError) {\n const errorCode = (error.data as { code?: unknown } | undefined)?.code;\n if (errorCode === \"DEVICE_AUTHORIZATION_PENDING\") {\n continue;\n }\n if (errorCode === \"DEVICE_SLOW_DOWN\") {\n currentIntervalMs += SLOW_DOWN_INCREMENT_MS;\n continue;\n }\n }\n throw error;\n }\n\n if (pollResult === null) {\n continue;\n }\n\n if (isSignedInResult(pollResult) && pollResult.session) {\n if (proxy) {\n await setTokenAndMaybeWait({\n shouldStore: false,\n tokens: pollResult.session === null ? null : { token: pollResult.session.token },\n waitForHandshake: true,\n context: { provider: \"device\", flow: \"poll\" },\n });\n } else {\n await setTokenAndMaybeWait({\n shouldStore: true,\n tokens: (pollResult.session as AuthTokens | null) ?? null,\n waitForHandshake: true,\n context: { provider: \"device\", flow: \"poll\" },\n });\n }\n return;\n }\n }\n\n throw
|
|
1
|
+
{"version":3,"file":"device.js","names":[],"sources":["../../../src/client/factors/device.ts"],"sourcesContent":["import { ConvexError } from \"convex/values\";\n\nimport type { DeviceClient, DevicePollParams, FactorDeps, SignInActionResult } from \"../core/types\";\nimport type { AuthTokens } from \"../../shared/results\";\nimport type { AuthParameters } from \"../../shared/results\";\nimport { ErrorCode } from \"../../shared/codes\";\nimport { convexError } from \"../../shared/errors\";\n\nfunction isSignedInResult(\n result: SignInActionResult,\n): result is Extract<SignInActionResult, { kind: \"signedIn\" }> {\n return result.kind === \"signedIn\";\n}\n\n/** @internal */\nexport function createDeviceClient(deps: FactorDeps): DeviceClient {\n const { proxy, convex, requireApiRefs, proxyFetch, setTokenAndMaybeWait } = deps;\n\n const requestDeviceSignIn = async (params: AuthParameters): Promise<SignInActionResult> => {\n return (\n proxy\n ? await proxyFetch({\n action: \"auth:signIn\",\n args: { request: { provider: \"device\", params } },\n })\n : await convex.action(requireApiRefs().signIn, {\n request: { provider: \"device\", params },\n })\n ) as SignInActionResult;\n };\n\n return {\n poll: async (opts: DevicePollParams): Promise<void> => {\n const { code, signal } = opts;\n const MAX_POLL_DURATION_MS = 30 * 60 * 1000;\n const SLOW_DOWN_INCREMENT_MS = 5 * 1000;\n let currentIntervalMs = code.interval * 1000;\n const startedAt = Date.now();\n const expiresAt = Math.min(\n startedAt + code.expiresIn * 1000,\n startedAt + MAX_POLL_DURATION_MS,\n );\n\n while (Date.now() < expiresAt) {\n if (signal?.aborted) return;\n await new Promise((resolve) => setTimeout(resolve, currentIntervalMs));\n if (signal?.aborted) return;\n\n const params: AuthParameters = {\n flow: \"poll\",\n deviceCode: code.deviceCode,\n };\n\n let pollResult: SignInActionResult | null;\n try {\n pollResult = await requestDeviceSignIn(params);\n } catch (error) {\n if (error instanceof ConvexError) {\n const errorCode = (error.data as { code?: unknown } | undefined)?.code;\n if (errorCode === \"DEVICE_AUTHORIZATION_PENDING\") {\n continue;\n }\n if (errorCode === \"DEVICE_SLOW_DOWN\") {\n currentIntervalMs += SLOW_DOWN_INCREMENT_MS;\n continue;\n }\n }\n throw error;\n }\n\n if (pollResult === null) {\n continue;\n }\n\n if (isSignedInResult(pollResult) && pollResult.session) {\n if (proxy) {\n await setTokenAndMaybeWait({\n shouldStore: false,\n tokens: pollResult.session === null ? null : { token: pollResult.session.token },\n waitForHandshake: true,\n context: { provider: \"device\", flow: \"poll\" },\n });\n } else {\n await setTokenAndMaybeWait({\n shouldStore: true,\n tokens: (pollResult.session as AuthTokens | null) ?? null,\n waitForHandshake: true,\n context: { provider: \"device\", flow: \"poll\" },\n });\n }\n return;\n }\n }\n\n throw convexError(\n ErrorCode.DEVICE_CODE_EXPIRED,\n \"Device code expired before authorization was completed.\",\n );\n },\n\n verify: async (opts: { code: string }): Promise<void> => {\n const params: AuthParameters = {\n flow: \"verify\",\n userCode: opts.code,\n };\n\n try {\n await requestDeviceSignIn(params);\n } catch (error) {\n throw convexError(\n ErrorCode.DEVICE_AUTHORIZATION_FAILED,\n error instanceof Error ? error.message : \"Invalid or expired code.\",\n );\n }\n },\n };\n}\n"],"mappings":";;;;;AAQA,SAAS,iBACP,QAC6D;AAC7D,QAAO,OAAO,SAAS;;;AAIzB,SAAgB,mBAAmB,MAAgC;CACjE,MAAM,EAAE,OAAO,QAAQ,gBAAgB,YAAY,yBAAyB;CAE5E,MAAM,sBAAsB,OAAO,WAAwD;AACzF,SACE,QACI,MAAM,WAAW;GACf,QAAQ;GACR,MAAM,EAAE,SAAS;IAAE,UAAU;IAAU;IAAQ,EAAE;GAClD,CAAC,GACF,MAAM,OAAO,OAAO,gBAAgB,CAAC,QAAQ,EAC3C,SAAS;GAAE,UAAU;GAAU;GAAQ,EACxC,CAAC;;AAIV,QAAO;EACL,MAAM,OAAO,SAA0C;GACrD,MAAM,EAAE,MAAM,WAAW;GACzB,MAAM,uBAAuB,OAAU;GACvC,MAAM,yBAAyB,IAAI;GACnC,IAAI,oBAAoB,KAAK,WAAW;GACxC,MAAM,YAAY,KAAK,KAAK;GAC5B,MAAM,YAAY,KAAK,IACrB,YAAY,KAAK,YAAY,KAC7B,YAAY,qBACb;AAED,UAAO,KAAK,KAAK,GAAG,WAAW;AAC7B,QAAI,QAAQ,QAAS;AACrB,UAAM,IAAI,SAAS,YAAY,WAAW,SAAS,kBAAkB,CAAC;AACtE,QAAI,QAAQ,QAAS;IAErB,MAAM,SAAyB;KAC7B,MAAM;KACN,YAAY,KAAK;KAClB;IAED,IAAI;AACJ,QAAI;AACF,kBAAa,MAAM,oBAAoB,OAAO;aACvC,OAAO;AACd,SAAI,iBAAiB,aAAa;MAChC,MAAM,YAAa,MAAM,MAAyC;AAClE,UAAI,cAAc,+BAChB;AAEF,UAAI,cAAc,oBAAoB;AACpC,4BAAqB;AACrB;;;AAGJ,WAAM;;AAGR,QAAI,eAAe,KACjB;AAGF,QAAI,iBAAiB,WAAW,IAAI,WAAW,SAAS;AACtD,SAAI,MACF,OAAM,qBAAqB;MACzB,aAAa;MACb,QAAQ,WAAW,YAAY,OAAO,OAAO,EAAE,OAAO,WAAW,QAAQ,OAAO;MAChF,kBAAkB;MAClB,SAAS;OAAE,UAAU;OAAU,MAAM;OAAQ;MAC9C,CAAC;SAEF,OAAM,qBAAqB;MACzB,aAAa;MACb,QAAS,WAAW,WAAiC;MACrD,kBAAkB;MAClB,SAAS;OAAE,UAAU;OAAU,MAAM;OAAQ;MAC9C,CAAC;AAEJ;;;AAIJ,SAAM,YACJ,UAAU,qBACV,0DACD;;EAGH,QAAQ,OAAO,SAA0C;GACvD,MAAM,SAAyB;IAC7B,MAAM;IACN,UAAU,KAAK;IAChB;AAED,OAAI;AACF,UAAM,oBAAoB,OAAO;YAC1B,OAAO;AACd,UAAM,YACJ,UAAU,6BACV,iBAAiB,QAAQ,MAAM,UAAU,2BAC1C;;;EAGN"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SessionRows } from "./session.js";
|
|
2
|
-
import * as
|
|
3
|
-
import * as
|
|
2
|
+
import * as convex_values2499 from "convex/values";
|
|
3
|
+
import * as convex_server89 from "convex/server";
|
|
4
4
|
|
|
5
5
|
//#region src/component/account.d.ts
|
|
6
6
|
declare namespace account_d_exports {
|
|
@@ -10,7 +10,7 @@ type AcceptedCredentialsSignIn = Omit<SessionRows, "epoch"> & {
|
|
|
10
10
|
status: "accepted";
|
|
11
11
|
};
|
|
12
12
|
/** Reserve a password attempt and load all sign-in state in one transaction. */
|
|
13
|
-
declare const beginCredentialsSignIn:
|
|
13
|
+
declare const beginCredentialsSignIn: convex_server89.RegisteredMutation<"public", {
|
|
14
14
|
provider: string;
|
|
15
15
|
providerAccountId: string;
|
|
16
16
|
limitIdentifier: string;
|
|
@@ -30,43 +30,43 @@ declare const beginCredentialsSignIn: convex_server96.RegisteredMutation<"public
|
|
|
30
30
|
} | {
|
|
31
31
|
status: "ready";
|
|
32
32
|
account: {
|
|
33
|
-
_id:
|
|
33
|
+
_id: convex_values2499.GenericId<"Account">;
|
|
34
34
|
_creationTime: number;
|
|
35
35
|
extend?: any;
|
|
36
36
|
secret?: string | undefined;
|
|
37
37
|
emailVerified?: string | undefined;
|
|
38
38
|
phoneVerified?: string | undefined;
|
|
39
|
-
userId:
|
|
39
|
+
userId: convex_values2499.GenericId<"User">;
|
|
40
40
|
provider: string;
|
|
41
41
|
providerAccountId: string;
|
|
42
42
|
};
|
|
43
43
|
user: {
|
|
44
|
-
_id:
|
|
44
|
+
_id: convex_values2499.GenericId<"User">;
|
|
45
45
|
_creationTime: number;
|
|
46
46
|
email?: string | undefined;
|
|
47
|
-
phone?: string | undefined;
|
|
48
47
|
extend?: any;
|
|
49
48
|
name?: string | undefined;
|
|
49
|
+
image?: string | undefined;
|
|
50
|
+
phone?: string | undefined;
|
|
50
51
|
firstName?: string | undefined;
|
|
51
52
|
lastName?: string | undefined;
|
|
52
|
-
image?: string | undefined;
|
|
53
53
|
emailVerificationTime?: number | undefined;
|
|
54
54
|
phoneVerificationTime?: number | undefined;
|
|
55
55
|
isAnonymous?: boolean | undefined;
|
|
56
|
-
lastActiveGroup?:
|
|
56
|
+
lastActiveGroup?: convex_values2499.GenericId<"Group"> | undefined;
|
|
57
57
|
sessionEpoch: number;
|
|
58
58
|
};
|
|
59
59
|
hasTotp: boolean;
|
|
60
60
|
}>>;
|
|
61
61
|
/** Refund a verified password attempt and optionally issue its session atomically. */
|
|
62
|
-
declare const completeCredentialsSignIn:
|
|
62
|
+
declare const completeCredentialsSignIn: convex_server89.RegisteredMutation<"public", {
|
|
63
63
|
replaceSession?: {
|
|
64
|
-
sessionId:
|
|
65
|
-
authenticatedUserId:
|
|
64
|
+
sessionId: convex_values2499.GenericId<"Session">;
|
|
65
|
+
authenticatedUserId: convex_values2499.GenericId<"User">;
|
|
66
66
|
} | undefined;
|
|
67
67
|
sessionExpirationTime: number;
|
|
68
68
|
refreshTokenExpirationTime: number;
|
|
69
|
-
accountId:
|
|
69
|
+
accountId: convex_values2499.GenericId<"Account">;
|
|
70
70
|
limitIdentifier: string;
|
|
71
71
|
issueSession: boolean;
|
|
72
72
|
generateTokens: boolean;
|
|
@@ -79,18 +79,18 @@ declare const completeCredentialsSignIn: convex_server96.RegisteredMutation<"pub
|
|
|
79
79
|
* Read an account by id, or by `{ provider, providerAccountId }` when both
|
|
80
80
|
* are given. Returns `null` when no selector matches.
|
|
81
81
|
*/
|
|
82
|
-
declare const get:
|
|
83
|
-
id?:
|
|
82
|
+
declare const get: convex_server89.RegisteredQuery<"public", {
|
|
83
|
+
id?: convex_values2499.GenericId<"Account"> | undefined;
|
|
84
84
|
provider?: string | undefined;
|
|
85
85
|
providerAccountId?: string | undefined;
|
|
86
86
|
}, Promise<{
|
|
87
|
-
_id:
|
|
87
|
+
_id: convex_values2499.GenericId<"Account">;
|
|
88
88
|
_creationTime: number;
|
|
89
89
|
extend?: any;
|
|
90
90
|
secret?: string | undefined;
|
|
91
91
|
emailVerified?: string | undefined;
|
|
92
92
|
phoneVerified?: string | undefined;
|
|
93
|
-
userId:
|
|
93
|
+
userId: convex_values2499.GenericId<"User">;
|
|
94
94
|
provider: string;
|
|
95
95
|
providerAccountId: string;
|
|
96
96
|
} | null>>;
|
|
@@ -101,39 +101,39 @@ declare const get: convex_server96.RegisteredQuery<"public", {
|
|
|
101
101
|
* public server facade projects these raw records into safe capability
|
|
102
102
|
* summaries before exposing them to application code.
|
|
103
103
|
*/
|
|
104
|
-
declare const list:
|
|
105
|
-
userId?:
|
|
104
|
+
declare const list: convex_server89.RegisteredQuery<"public", {
|
|
105
|
+
userId?: convex_values2499.GenericId<"User"> | undefined;
|
|
106
106
|
provider?: string | undefined;
|
|
107
|
-
userIds?:
|
|
107
|
+
userIds?: convex_values2499.GenericId<"User">[] | undefined;
|
|
108
108
|
}, Promise<{
|
|
109
|
-
_id:
|
|
109
|
+
_id: convex_values2499.GenericId<"Account">;
|
|
110
110
|
_creationTime: number;
|
|
111
111
|
extend?: any;
|
|
112
112
|
secret?: string | undefined;
|
|
113
113
|
emailVerified?: string | undefined;
|
|
114
114
|
phoneVerified?: string | undefined;
|
|
115
|
-
userId:
|
|
115
|
+
userId: convex_values2499.GenericId<"User">;
|
|
116
116
|
provider: string;
|
|
117
117
|
providerAccountId: string;
|
|
118
118
|
}[]>>;
|
|
119
119
|
/** Insert a new account. */
|
|
120
|
-
declare const create:
|
|
120
|
+
declare const create: convex_server89.RegisteredMutation<"public", {
|
|
121
121
|
extend?: any;
|
|
122
122
|
secret?: string | undefined;
|
|
123
|
-
userId:
|
|
123
|
+
userId: convex_values2499.GenericId<"User">;
|
|
124
124
|
provider: string;
|
|
125
125
|
providerAccountId: string;
|
|
126
|
-
}, Promise<
|
|
126
|
+
}, Promise<convex_values2499.GenericId<"Account">>>;
|
|
127
127
|
/** Patch fields on an account. */
|
|
128
|
-
declare const update:
|
|
129
|
-
id:
|
|
128
|
+
declare const update: convex_server89.RegisteredMutation<"public", {
|
|
129
|
+
id: convex_values2499.GenericId<"Account">;
|
|
130
130
|
patch: {
|
|
131
|
-
userId?: convex_values2496.GenericId<"User"> | undefined;
|
|
132
131
|
extend?: any;
|
|
133
|
-
provider?: string | undefined;
|
|
134
|
-
providerAccountId?: string | undefined;
|
|
135
132
|
secret?: string | undefined;
|
|
136
133
|
emailVerified?: string | undefined;
|
|
134
|
+
userId?: convex_values2499.GenericId<"User"> | undefined;
|
|
135
|
+
provider?: string | undefined;
|
|
136
|
+
providerAccountId?: string | undefined;
|
|
137
137
|
phoneVerified?: string | undefined;
|
|
138
138
|
};
|
|
139
139
|
}, Promise<null>>;
|
|
@@ -141,10 +141,10 @@ declare const update: convex_server96.RegisteredMutation<"public", {
|
|
|
141
141
|
* Delete an account. When `requireOtherAccount` is set, refuses to delete the
|
|
142
142
|
* user's last remaining account so they are never left with none.
|
|
143
143
|
*/
|
|
144
|
-
declare const remove:
|
|
145
|
-
userId?:
|
|
144
|
+
declare const remove: convex_server89.RegisteredMutation<"public", {
|
|
145
|
+
userId?: convex_values2499.GenericId<"User"> | undefined;
|
|
146
146
|
requireOtherAccount?: boolean | undefined;
|
|
147
|
-
id:
|
|
147
|
+
id: convex_values2499.GenericId<"Account">;
|
|
148
148
|
}, Promise<null>>;
|
|
149
149
|
//#endregion
|
|
150
150
|
export { account_d_exports };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account.d.ts","names":[],"sources":["../../src/component/account.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"account.d.ts","names":[],"sources":["../../src/component/account.ts"],"mappings":";;;;;;;;KAwBK,yBAAA,GAA4B,IAAA,CAAK,WAAA;EAA0B,MAAA;AAAA;;cAGnD,sBAAA,kBAAsB,kBAAA;;;;;;;;;;;;;;;;;;;;SAuDjC,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAGW,yBAAA,kBAAyB,kBAAA;;eA+CpC,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;cAMW,GAAA,kBAAG,eAAA;OAmBd,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;;;;;;;cASW,IAAA,kBAAI,eAAA;WAgCf,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;;cAGW,MAAA,kBAAM,kBAAA;;;UAqCjB,iBAAA,CAAA,SAAA;;;;;cAGW,MAAA,kBAAM,kBAAA;MAkBjB,iBAAA,CAAA,SAAA;;;;;;;;;;;;;;AA7FF;cAmGM,MAAA,kBAAM,kBAAA;WA6BV,iBAAA,CAAA,SAAA"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ErrorCode } from "../shared/codes.js";
|
|
2
|
+
import { convexError } from "../shared/errors.js";
|
|
2
3
|
import { mutation, query } from "./_generated/server.js";
|
|
3
4
|
import { recordSignInLimit, resetSignInLimit } from "./limits.js";
|
|
4
5
|
import { vAccountDoc, vUserDoc } from "./documents.js";
|
|
5
6
|
import { createSessionRows, vSessionReplacement } from "./session.js";
|
|
6
|
-
import {
|
|
7
|
+
import { v } from "convex/values";
|
|
7
8
|
|
|
8
9
|
//#region src/component/account.ts
|
|
9
10
|
/**
|
|
@@ -131,10 +132,7 @@ const list = query({
|
|
|
131
132
|
const userIds = args.userId === void 0 ? args.userIds ?? [] : [args.userId];
|
|
132
133
|
const uniqueUserIds = Array.from(new Set(userIds));
|
|
133
134
|
const provider = args.provider;
|
|
134
|
-
if (uniqueUserIds.length > ACCOUNT_LIST_USER_BATCH) throw
|
|
135
|
-
code: ErrorCode.INVALID_PARAMETERS,
|
|
136
|
-
message: `At most ${ACCOUNT_LIST_USER_BATCH} users can be read at once.`
|
|
137
|
-
});
|
|
135
|
+
if (uniqueUserIds.length > ACCOUNT_LIST_USER_BATCH) throw convexError(ErrorCode.INVALID_PARAMETERS, `At most ${ACCOUNT_LIST_USER_BATCH} users can be read at once.`);
|
|
138
136
|
return (await Promise.all(uniqueUserIds.map(async (userId) => provider === void 0 ? await ctx.db.query("Account").withIndex("user_id_provider", (q) => q.eq("userId", userId)).take(ACCOUNT_LIST_BATCH) : await ctx.db.query("Account").withIndex("user_id_provider", (q) => q.eq("userId", userId).eq("provider", provider)).take(ACCOUNT_LIST_BATCH)))).flat();
|
|
139
137
|
}
|
|
140
138
|
});
|
|
@@ -151,10 +149,7 @@ const create = mutation({
|
|
|
151
149
|
handler: async (ctx, args) => {
|
|
152
150
|
const existing = await ctx.db.query("Account").withIndex("provider_account_id", (q) => q.eq("provider", args.provider).eq("providerAccountId", args.providerAccountId)).first();
|
|
153
151
|
if (existing !== null) {
|
|
154
|
-
if (existing.userId !== args.userId) throw
|
|
155
|
-
code: ErrorCode.ACCOUNT_ALREADY_LINKED,
|
|
156
|
-
message: "This account is already linked to another user."
|
|
157
|
-
});
|
|
152
|
+
if (existing.userId !== args.userId) throw convexError(ErrorCode.ACCOUNT_ALREADY_LINKED, "This account is already linked to another user.");
|
|
158
153
|
return existing._id;
|
|
159
154
|
}
|
|
160
155
|
return await ctx.db.insert("Account", args);
|
|
@@ -193,20 +188,14 @@ const remove = mutation({
|
|
|
193
188
|
returns: v.null(),
|
|
194
189
|
handler: async (ctx, { id: accountId, userId, requireOtherAccount }) => {
|
|
195
190
|
const doc = await ctx.db.get("Account", accountId);
|
|
196
|
-
if (doc === null || userId !== void 0 && doc.userId !== userId) throw
|
|
197
|
-
code: ErrorCode.ACCOUNT_NOT_FOUND,
|
|
198
|
-
message: "Account not found."
|
|
199
|
-
});
|
|
191
|
+
if (doc === null || userId !== void 0 && doc.userId !== userId) throw convexError(ErrorCode.ACCOUNT_NOT_FOUND, "Account not found.");
|
|
200
192
|
if (requireOtherAccount === true) {
|
|
201
193
|
let otherFound = false;
|
|
202
194
|
for await (const sibling of ctx.db.query("Account").withIndex("user_id_provider", (q) => q.eq("userId", doc.userId))) if (sibling._id !== accountId) {
|
|
203
195
|
otherFound = true;
|
|
204
196
|
break;
|
|
205
197
|
}
|
|
206
|
-
if (!otherFound) throw
|
|
207
|
-
code: ErrorCode.INVALID_PARAMETERS,
|
|
208
|
-
message: "The provided parameters are invalid."
|
|
209
|
-
});
|
|
198
|
+
if (!otherFound) throw convexError(ErrorCode.INVALID_PARAMETERS, "The provided parameters are invalid.");
|
|
210
199
|
}
|
|
211
200
|
await ctx.db.delete("Account", accountId);
|
|
212
201
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account.js","names":[],"sources":["../../src/component/account.ts"],"sourcesContent":["/**\n * `component.account.*` — provider-linked auth accounts.\n *\n * Reads collapse into one overloaded `get`; `list`\n * takes the owning `userId`.\n *\n * @module\n */\n\nimport { ConvexError, v } from \"convex/values\";\nimport { ErrorCode } from \"../shared/codes\";\n\nimport { recordSignInLimit, resetSignInLimit } from \"./limits\";\nimport { mutation, query } from \"./_generated/server\";\nimport { vAccountDoc, vUserDoc } from \"./documents\";\nimport { createSessionRows, type SessionRows, vSessionReplacement } from \"./session\";\n\n/** Maximum linked accounts returned for one user by the bounded read surface. */\nconst ACCOUNT_LIST_BATCH = 128;\n\n/** Maximum users accepted by one batched account read. */\nconst ACCOUNT_LIST_USER_BATCH = 100;\n\ntype AcceptedCredentialsSignIn = Omit<SessionRows, \"epoch\"> & { status: \"accepted\" };\n\n/** Reserve a password attempt and load all sign-in state in one transaction. */\nexport const beginCredentialsSignIn = mutation({\n args: {\n provider: v.string(),\n providerAccountId: v.string(),\n limitIdentifier: v.string(),\n maxAttemptsPerHour: v.number(),\n reserveAttempt: v.boolean(),\n includeTotp: v.boolean(),\n },\n returns: v.union(\n v.object({ status: v.literal(\"invalid\") }),\n v.object({ status: v.literal(\"limited\") }),\n v.object({\n status: v.literal(\"ready\"),\n account: vAccountDoc,\n user: vUserDoc,\n hasTotp: v.boolean(),\n }),\n ),\n handler: async (ctx, args) => {\n const limit = args.reserveAttempt\n ? await recordSignInLimit(ctx, {\n identifier: args.limitIdentifier,\n maxAttemptsPerHour: args.maxAttemptsPerHour,\n })\n : { ok: true as const, retryAfter: undefined };\n if (!limit.ok) return { status: \"limited\" as const };\n\n const account = await ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (q) =>\n q.eq(\"provider\", args.provider).eq(\"providerAccountId\", args.providerAccountId),\n )\n .unique();\n if (account === null) return { status: \"invalid\" as const };\n\n const [user, totp] = await Promise.all([\n ctx.db.get(\"User\", account.userId),\n args.includeTotp\n ? ctx.db\n .query(\"TotpFactor\")\n .withIndex(\"user_id_verified\", (q) =>\n q.eq(\"userId\", account.userId).eq(\"verified\", true),\n )\n .first()\n : Promise.resolve(null),\n ]);\n if (user === null) return { status: \"invalid\" as const };\n return {\n status: \"ready\" as const,\n account,\n user,\n hasTotp: totp !== null,\n };\n },\n});\n\n/** Refund a verified password attempt and optionally issue its session atomically. */\nexport const completeCredentialsSignIn = mutation({\n args: {\n accountId: v.id(\"Account\"),\n limitIdentifier: v.string(),\n issueSession: v.boolean(),\n generateTokens: v.boolean(),\n replaceSession: v.optional(vSessionReplacement),\n sessionExpirationTime: v.number(),\n refreshTokenExpirationTime: v.number(),\n },\n returns: v.union(\n v.object({ status: v.literal(\"rejected\") }),\n v.object({ status: v.literal(\"reset\") }),\n v.object({\n status: v.literal(\"accepted\"),\n user: vUserDoc,\n sessionId: v.id(\"Session\"),\n sessionExpirationTime: v.number(),\n refreshTokenId: v.optional(v.id(\"RefreshToken\")),\n replacedSessionId: v.optional(v.id(\"Session\")),\n }),\n ),\n handler: async (ctx, args) => {\n const account = await ctx.db.get(\"Account\", args.accountId);\n if (account === null) return { status: \"rejected\" as const };\n await resetSignInLimit(ctx, args.limitIdentifier);\n if (!args.issueSession) return { status: \"reset\" as const };\n\n const created = await createSessionRows(ctx, {\n userId: account.userId,\n replaceSession: args.replaceSession,\n sessionExpirationTime: args.sessionExpirationTime,\n refreshTokenExpirationTime: args.generateTokens ? args.refreshTokenExpirationTime : undefined,\n });\n if (created === null) return { status: \"rejected\" as const };\n const result: AcceptedCredentialsSignIn = {\n status: \"accepted\" as const,\n user: created.user,\n sessionId: created.sessionId,\n sessionExpirationTime: created.sessionExpirationTime,\n };\n if (created.refreshTokenId !== undefined) result.refreshTokenId = created.refreshTokenId;\n if (created.replacedSessionId !== undefined) {\n result.replacedSessionId = created.replacedSessionId;\n }\n return result;\n },\n});\n\n/**\n * Read an account by id, or by `{ provider, providerAccountId }` when both\n * are given. Returns `null` when no selector matches.\n */\nexport const get = query({\n args: {\n id: v.optional(v.id(\"Account\")),\n provider: v.optional(v.string()),\n providerAccountId: v.optional(v.string()),\n },\n returns: v.union(vAccountDoc, v.null()),\n handler: async (ctx, args) => {\n if (args.provider !== undefined && args.providerAccountId !== undefined) {\n return await ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (q) =>\n q.eq(\"provider\", args.provider!).eq(\"providerAccountId\", args.providerAccountId!),\n )\n .unique();\n }\n if (args.id === undefined) return null;\n return await ctx.db.get(\"Account\", args.id);\n },\n});\n\n/**\n * List the accounts owned by one user, or by a bounded batch of users.\n *\n * The batch form serves one component crossing for app-owned user lists. The\n * public server facade projects these raw records into safe capability\n * summaries before exposing them to application code.\n */\nexport const list = query({\n args: {\n userId: v.optional(v.id(\"User\")),\n userIds: v.optional(v.array(v.id(\"User\"))),\n provider: v.optional(v.string()),\n },\n returns: v.array(vAccountDoc),\n handler: async (ctx, args) => {\n const userIds = args.userId === undefined ? (args.userIds ?? []) : [args.userId];\n const uniqueUserIds = Array.from(new Set(userIds));\n const provider = args.provider;\n if (uniqueUserIds.length > ACCOUNT_LIST_USER_BATCH) {\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message: `At most ${ACCOUNT_LIST_USER_BATCH} users can be read at once.`,\n });\n }\n const accounts = await Promise.all(\n uniqueUserIds.map(async (userId) =>\n provider === undefined\n ? await ctx.db\n .query(\"Account\")\n .withIndex(\"user_id_provider\", (q) => q.eq(\"userId\", userId))\n .take(ACCOUNT_LIST_BATCH)\n : await ctx.db\n .query(\"Account\")\n .withIndex(\"user_id_provider\", (q) => q.eq(\"userId\", userId).eq(\"provider\", provider))\n .take(ACCOUNT_LIST_BATCH),\n ),\n );\n return accounts.flat();\n },\n});\n\n/** Insert a new account. */\nexport const create = mutation({\n args: {\n userId: v.id(\"User\"),\n provider: v.string(),\n providerAccountId: v.string(),\n secret: v.optional(v.string()),\n extend: v.optional(v.any()),\n },\n returns: v.id(\"Account\"),\n handler: async (ctx, args) => {\n // Dedup on the (provider, providerAccountId) identity. A bare insert let two\n // concurrent or retried provisioning ceremonies — SCIM POST retries, a\n // passkey double-submit — create duplicate Account rows, which then made\n // `account.get(...).unique()` throw on every future lookup (a permanent\n // sign-in lockout / rogue credential attribution). The index-range read plus\n // insert in this single mutation is OCC-atomic: a racing insert into the same\n // range conflicts and retries, then observes the winner's row below.\n const existing = await ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (q) =>\n q.eq(\"provider\", args.provider).eq(\"providerAccountId\", args.providerAccountId),\n )\n .first();\n if (existing !== null) {\n if (existing.userId !== args.userId) {\n // Already linked to a different user — reject rather than silently\n // attribute the identity (e.g. an attacker registering a passkey\n // credentialId that already belongs to a victim).\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_ALREADY_LINKED,\n message: \"This account is already linked to another user.\",\n });\n }\n return existing._id;\n }\n return await ctx.db.insert(\"Account\", args);\n },\n});\n\n/** Patch fields on an account. */\nexport const update = mutation({\n args: {\n id: v.id(\"Account\"),\n patch: v.object({\n userId: v.optional(v.id(\"User\")),\n provider: v.optional(v.string()),\n providerAccountId: v.optional(v.string()),\n secret: v.optional(v.string()),\n emailVerified: v.optional(v.string()),\n phoneVerified: v.optional(v.string()),\n extend: v.optional(v.any()),\n }),\n },\n returns: v.null(),\n handler: async (ctx, { id: accountId, patch }) => {\n await ctx.db.patch(\"Account\", accountId, patch);\n return null;\n },\n});\n\n/**\n * Delete an account. When `requireOtherAccount` is set, refuses to delete the\n * user's last remaining account so they are never left with none.\n */\nconst remove = mutation({\n args: {\n id: v.id(\"Account\"),\n userId: v.optional(v.id(\"User\")),\n requireOtherAccount: v.optional(v.boolean()),\n },\n returns: v.null(),\n handler: async (ctx, { id: accountId, userId, requireOtherAccount }) => {\n const doc = await ctx.db.get(\"Account\", accountId);\n if (doc === null || (userId !== undefined && doc.userId !== userId)) {\n throw new ConvexError({\n code: ErrorCode.ACCOUNT_NOT_FOUND,\n message: \"Account not found.\",\n });\n }\n if (requireOtherAccount === true) {\n let otherFound = false;\n for await (const sibling of ctx.db\n .query(\"Account\")\n .withIndex(\"user_id_provider\", (q) => q.eq(\"userId\", doc.userId))) {\n if (sibling._id !== accountId) {\n otherFound = true;\n break;\n }\n }\n if (!otherFound) {\n throw new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message: \"The provided parameters are invalid.\",\n });\n }\n }\n await ctx.db.delete(\"Account\", accountId);\n return null;\n },\n});\n\nexport { remove };\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"account.js","names":[],"sources":["../../src/component/account.ts"],"sourcesContent":["/**\n * `component.account.*` — provider-linked auth accounts.\n *\n * Reads collapse into one overloaded `get`; `list`\n * takes the owning `userId`.\n *\n * @module\n */\n\nimport { v } from \"convex/values\";\nimport { ErrorCode } from \"../shared/codes\";\nimport { convexError } from \"../shared/errors\";\n\nimport { recordSignInLimit, resetSignInLimit } from \"./limits\";\nimport { mutation, query } from \"./_generated/server\";\nimport { vAccountDoc, vUserDoc } from \"./documents\";\nimport { createSessionRows, type SessionRows, vSessionReplacement } from \"./session\";\n\n/** Maximum linked accounts returned for one user by the bounded read surface. */\nconst ACCOUNT_LIST_BATCH = 128;\n\n/** Maximum users accepted by one batched account read. */\nconst ACCOUNT_LIST_USER_BATCH = 100;\n\ntype AcceptedCredentialsSignIn = Omit<SessionRows, \"epoch\"> & { status: \"accepted\" };\n\n/** Reserve a password attempt and load all sign-in state in one transaction. */\nexport const beginCredentialsSignIn = mutation({\n args: {\n provider: v.string(),\n providerAccountId: v.string(),\n limitIdentifier: v.string(),\n maxAttemptsPerHour: v.number(),\n reserveAttempt: v.boolean(),\n includeTotp: v.boolean(),\n },\n returns: v.union(\n v.object({ status: v.literal(\"invalid\") }),\n v.object({ status: v.literal(\"limited\") }),\n v.object({\n status: v.literal(\"ready\"),\n account: vAccountDoc,\n user: vUserDoc,\n hasTotp: v.boolean(),\n }),\n ),\n handler: async (ctx, args) => {\n const limit = args.reserveAttempt\n ? await recordSignInLimit(ctx, {\n identifier: args.limitIdentifier,\n maxAttemptsPerHour: args.maxAttemptsPerHour,\n })\n : { ok: true as const, retryAfter: undefined };\n if (!limit.ok) return { status: \"limited\" as const };\n\n const account = await ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (q) =>\n q.eq(\"provider\", args.provider).eq(\"providerAccountId\", args.providerAccountId),\n )\n .unique();\n if (account === null) return { status: \"invalid\" as const };\n\n const [user, totp] = await Promise.all([\n ctx.db.get(\"User\", account.userId),\n args.includeTotp\n ? ctx.db\n .query(\"TotpFactor\")\n .withIndex(\"user_id_verified\", (q) =>\n q.eq(\"userId\", account.userId).eq(\"verified\", true),\n )\n .first()\n : Promise.resolve(null),\n ]);\n if (user === null) return { status: \"invalid\" as const };\n return {\n status: \"ready\" as const,\n account,\n user,\n hasTotp: totp !== null,\n };\n },\n});\n\n/** Refund a verified password attempt and optionally issue its session atomically. */\nexport const completeCredentialsSignIn = mutation({\n args: {\n accountId: v.id(\"Account\"),\n limitIdentifier: v.string(),\n issueSession: v.boolean(),\n generateTokens: v.boolean(),\n replaceSession: v.optional(vSessionReplacement),\n sessionExpirationTime: v.number(),\n refreshTokenExpirationTime: v.number(),\n },\n returns: v.union(\n v.object({ status: v.literal(\"rejected\") }),\n v.object({ status: v.literal(\"reset\") }),\n v.object({\n status: v.literal(\"accepted\"),\n user: vUserDoc,\n sessionId: v.id(\"Session\"),\n sessionExpirationTime: v.number(),\n refreshTokenId: v.optional(v.id(\"RefreshToken\")),\n replacedSessionId: v.optional(v.id(\"Session\")),\n }),\n ),\n handler: async (ctx, args) => {\n const account = await ctx.db.get(\"Account\", args.accountId);\n if (account === null) return { status: \"rejected\" as const };\n await resetSignInLimit(ctx, args.limitIdentifier);\n if (!args.issueSession) return { status: \"reset\" as const };\n\n const created = await createSessionRows(ctx, {\n userId: account.userId,\n replaceSession: args.replaceSession,\n sessionExpirationTime: args.sessionExpirationTime,\n refreshTokenExpirationTime: args.generateTokens ? args.refreshTokenExpirationTime : undefined,\n });\n if (created === null) return { status: \"rejected\" as const };\n const result: AcceptedCredentialsSignIn = {\n status: \"accepted\" as const,\n user: created.user,\n sessionId: created.sessionId,\n sessionExpirationTime: created.sessionExpirationTime,\n };\n if (created.refreshTokenId !== undefined) result.refreshTokenId = created.refreshTokenId;\n if (created.replacedSessionId !== undefined) {\n result.replacedSessionId = created.replacedSessionId;\n }\n return result;\n },\n});\n\n/**\n * Read an account by id, or by `{ provider, providerAccountId }` when both\n * are given. Returns `null` when no selector matches.\n */\nexport const get = query({\n args: {\n id: v.optional(v.id(\"Account\")),\n provider: v.optional(v.string()),\n providerAccountId: v.optional(v.string()),\n },\n returns: v.union(vAccountDoc, v.null()),\n handler: async (ctx, args) => {\n if (args.provider !== undefined && args.providerAccountId !== undefined) {\n return await ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (q) =>\n q.eq(\"provider\", args.provider!).eq(\"providerAccountId\", args.providerAccountId!),\n )\n .unique();\n }\n if (args.id === undefined) return null;\n return await ctx.db.get(\"Account\", args.id);\n },\n});\n\n/**\n * List the accounts owned by one user, or by a bounded batch of users.\n *\n * The batch form serves one component crossing for app-owned user lists. The\n * public server facade projects these raw records into safe capability\n * summaries before exposing them to application code.\n */\nexport const list = query({\n args: {\n userId: v.optional(v.id(\"User\")),\n userIds: v.optional(v.array(v.id(\"User\"))),\n provider: v.optional(v.string()),\n },\n returns: v.array(vAccountDoc),\n handler: async (ctx, args) => {\n const userIds = args.userId === undefined ? (args.userIds ?? []) : [args.userId];\n const uniqueUserIds = Array.from(new Set(userIds));\n const provider = args.provider;\n if (uniqueUserIds.length > ACCOUNT_LIST_USER_BATCH) {\n throw convexError(\n ErrorCode.INVALID_PARAMETERS,\n `At most ${ACCOUNT_LIST_USER_BATCH} users can be read at once.`,\n );\n }\n const accounts = await Promise.all(\n uniqueUserIds.map(async (userId) =>\n provider === undefined\n ? await ctx.db\n .query(\"Account\")\n .withIndex(\"user_id_provider\", (q) => q.eq(\"userId\", userId))\n .take(ACCOUNT_LIST_BATCH)\n : await ctx.db\n .query(\"Account\")\n .withIndex(\"user_id_provider\", (q) => q.eq(\"userId\", userId).eq(\"provider\", provider))\n .take(ACCOUNT_LIST_BATCH),\n ),\n );\n return accounts.flat();\n },\n});\n\n/** Insert a new account. */\nexport const create = mutation({\n args: {\n userId: v.id(\"User\"),\n provider: v.string(),\n providerAccountId: v.string(),\n secret: v.optional(v.string()),\n extend: v.optional(v.any()),\n },\n returns: v.id(\"Account\"),\n handler: async (ctx, args) => {\n // Dedup on the (provider, providerAccountId) identity. A bare insert let two\n // concurrent or retried provisioning ceremonies — SCIM POST retries, a\n // passkey double-submit — create duplicate Account rows, which then made\n // `account.get(...).unique()` throw on every future lookup (a permanent\n // sign-in lockout / rogue credential attribution). The index-range read plus\n // insert in this single mutation is OCC-atomic: a racing insert into the same\n // range conflicts and retries, then observes the winner's row below.\n const existing = await ctx.db\n .query(\"Account\")\n .withIndex(\"provider_account_id\", (q) =>\n q.eq(\"provider\", args.provider).eq(\"providerAccountId\", args.providerAccountId),\n )\n .first();\n if (existing !== null) {\n if (existing.userId !== args.userId) {\n // Already linked to a different user — reject rather than silently\n // attribute the identity (e.g. an attacker registering a passkey\n // credentialId that already belongs to a victim).\n throw convexError(\n ErrorCode.ACCOUNT_ALREADY_LINKED,\n \"This account is already linked to another user.\",\n );\n }\n return existing._id;\n }\n return await ctx.db.insert(\"Account\", args);\n },\n});\n\n/** Patch fields on an account. */\nexport const update = mutation({\n args: {\n id: v.id(\"Account\"),\n patch: v.object({\n userId: v.optional(v.id(\"User\")),\n provider: v.optional(v.string()),\n providerAccountId: v.optional(v.string()),\n secret: v.optional(v.string()),\n emailVerified: v.optional(v.string()),\n phoneVerified: v.optional(v.string()),\n extend: v.optional(v.any()),\n }),\n },\n returns: v.null(),\n handler: async (ctx, { id: accountId, patch }) => {\n await ctx.db.patch(\"Account\", accountId, patch);\n return null;\n },\n});\n\n/**\n * Delete an account. When `requireOtherAccount` is set, refuses to delete the\n * user's last remaining account so they are never left with none.\n */\nconst remove = mutation({\n args: {\n id: v.id(\"Account\"),\n userId: v.optional(v.id(\"User\")),\n requireOtherAccount: v.optional(v.boolean()),\n },\n returns: v.null(),\n handler: async (ctx, { id: accountId, userId, requireOtherAccount }) => {\n const doc = await ctx.db.get(\"Account\", accountId);\n if (doc === null || (userId !== undefined && doc.userId !== userId)) {\n throw convexError(ErrorCode.ACCOUNT_NOT_FOUND, \"Account not found.\");\n }\n if (requireOtherAccount === true) {\n let otherFound = false;\n for await (const sibling of ctx.db\n .query(\"Account\")\n .withIndex(\"user_id_provider\", (q) => q.eq(\"userId\", doc.userId))) {\n if (sibling._id !== accountId) {\n otherFound = true;\n break;\n }\n }\n if (!otherFound) {\n throw convexError(ErrorCode.INVALID_PARAMETERS, \"The provided parameters are invalid.\");\n }\n }\n await ctx.db.delete(\"Account\", accountId);\n return null;\n },\n});\n\nexport { remove };\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,MAAM,qBAAqB;;AAG3B,MAAM,0BAA0B;;AAKhC,MAAa,yBAAyB,SAAS;CAC7C,MAAM;EACJ,UAAU,EAAE,QAAQ;EACpB,mBAAmB,EAAE,QAAQ;EAC7B,iBAAiB,EAAE,QAAQ;EAC3B,oBAAoB,EAAE,QAAQ;EAC9B,gBAAgB,EAAE,SAAS;EAC3B,aAAa,EAAE,SAAS;EACzB;CACD,SAAS,EAAE,MACT,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,UAAU,EAAE,CAAC,EAC1C,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,UAAU,EAAE,CAAC,EAC1C,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,QAAQ;EAC1B,SAAS;EACT,MAAM;EACN,SAAS,EAAE,SAAS;EACrB,CAAC,CACH;CACD,SAAS,OAAO,KAAK,SAAS;AAO5B,MAAI,EANU,KAAK,iBACf,MAAM,kBAAkB,KAAK;GAC3B,YAAY,KAAK;GACjB,oBAAoB,KAAK;GAC1B,CAAC,GACF;GAAE,IAAI;GAAe,YAAY;GAAW,EACrC,GAAI,QAAO,EAAE,QAAQ,WAAoB;EAEpD,MAAM,UAAU,MAAM,IAAI,GACvB,MAAM,UAAU,CAChB,UAAU,wBAAwB,MACjC,EAAE,GAAG,YAAY,KAAK,SAAS,CAAC,GAAG,qBAAqB,KAAK,kBAAkB,CAChF,CACA,QAAQ;AACX,MAAI,YAAY,KAAM,QAAO,EAAE,QAAQ,WAAoB;EAE3D,MAAM,CAAC,MAAM,QAAQ,MAAM,QAAQ,IAAI,CACrC,IAAI,GAAG,IAAI,QAAQ,QAAQ,OAAO,EAClC,KAAK,cACD,IAAI,GACD,MAAM,aAAa,CACnB,UAAU,qBAAqB,MAC9B,EAAE,GAAG,UAAU,QAAQ,OAAO,CAAC,GAAG,YAAY,KAAK,CACpD,CACA,OAAO,GACV,QAAQ,QAAQ,KAAK,CAC1B,CAAC;AACF,MAAI,SAAS,KAAM,QAAO,EAAE,QAAQ,WAAoB;AACxD,SAAO;GACL,QAAQ;GACR;GACA;GACA,SAAS,SAAS;GACnB;;CAEJ,CAAC;;AAGF,MAAa,4BAA4B,SAAS;CAChD,MAAM;EACJ,WAAW,EAAE,GAAG,UAAU;EAC1B,iBAAiB,EAAE,QAAQ;EAC3B,cAAc,EAAE,SAAS;EACzB,gBAAgB,EAAE,SAAS;EAC3B,gBAAgB,EAAE,SAAS,oBAAoB;EAC/C,uBAAuB,EAAE,QAAQ;EACjC,4BAA4B,EAAE,QAAQ;EACvC;CACD,SAAS,EAAE,MACT,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,WAAW,EAAE,CAAC,EAC3C,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,QAAQ,EAAE,CAAC,EACxC,EAAE,OAAO;EACP,QAAQ,EAAE,QAAQ,WAAW;EAC7B,MAAM;EACN,WAAW,EAAE,GAAG,UAAU;EAC1B,uBAAuB,EAAE,QAAQ;EACjC,gBAAgB,EAAE,SAAS,EAAE,GAAG,eAAe,CAAC;EAChD,mBAAmB,EAAE,SAAS,EAAE,GAAG,UAAU,CAAC;EAC/C,CAAC,CACH;CACD,SAAS,OAAO,KAAK,SAAS;EAC5B,MAAM,UAAU,MAAM,IAAI,GAAG,IAAI,WAAW,KAAK,UAAU;AAC3D,MAAI,YAAY,KAAM,QAAO,EAAE,QAAQ,YAAqB;AAC5D,QAAM,iBAAiB,KAAK,KAAK,gBAAgB;AACjD,MAAI,CAAC,KAAK,aAAc,QAAO,EAAE,QAAQ,SAAkB;EAE3D,MAAM,UAAU,MAAM,kBAAkB,KAAK;GAC3C,QAAQ,QAAQ;GAChB,gBAAgB,KAAK;GACrB,uBAAuB,KAAK;GAC5B,4BAA4B,KAAK,iBAAiB,KAAK,6BAA6B;GACrF,CAAC;AACF,MAAI,YAAY,KAAM,QAAO,EAAE,QAAQ,YAAqB;EAC5D,MAAM,SAAoC;GACxC,QAAQ;GACR,MAAM,QAAQ;GACd,WAAW,QAAQ;GACnB,uBAAuB,QAAQ;GAChC;AACD,MAAI,QAAQ,mBAAmB,OAAW,QAAO,iBAAiB,QAAQ;AAC1E,MAAI,QAAQ,sBAAsB,OAChC,QAAO,oBAAoB,QAAQ;AAErC,SAAO;;CAEV,CAAC;;;;;AAMF,MAAa,MAAM,MAAM;CACvB,MAAM;EACJ,IAAI,EAAE,SAAS,EAAE,GAAG,UAAU,CAAC;EAC/B,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC;EAChC,mBAAmB,EAAE,SAAS,EAAE,QAAQ,CAAC;EAC1C;CACD,SAAS,EAAE,MAAM,aAAa,EAAE,MAAM,CAAC;CACvC,SAAS,OAAO,KAAK,SAAS;AAC5B,MAAI,KAAK,aAAa,UAAa,KAAK,sBAAsB,OAC5D,QAAO,MAAM,IAAI,GACd,MAAM,UAAU,CAChB,UAAU,wBAAwB,MACjC,EAAE,GAAG,YAAY,KAAK,SAAU,CAAC,GAAG,qBAAqB,KAAK,kBAAmB,CAClF,CACA,QAAQ;AAEb,MAAI,KAAK,OAAO,OAAW,QAAO;AAClC,SAAO,MAAM,IAAI,GAAG,IAAI,WAAW,KAAK,GAAG;;CAE9C,CAAC;;;;;;;;AASF,MAAa,OAAO,MAAM;CACxB,MAAM;EACJ,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;EAChC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC;EAC1C,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC;EACjC;CACD,SAAS,EAAE,MAAM,YAAY;CAC7B,SAAS,OAAO,KAAK,SAAS;EAC5B,MAAM,UAAU,KAAK,WAAW,SAAa,KAAK,WAAW,EAAE,GAAI,CAAC,KAAK,OAAO;EAChF,MAAM,gBAAgB,MAAM,KAAK,IAAI,IAAI,QAAQ,CAAC;EAClD,MAAM,WAAW,KAAK;AACtB,MAAI,cAAc,SAAS,wBACzB,OAAM,YACJ,UAAU,oBACV,WAAW,wBAAwB,6BACpC;AAeH,UAbiB,MAAM,QAAQ,IAC7B,cAAc,IAAI,OAAO,WACvB,aAAa,SACT,MAAM,IAAI,GACP,MAAM,UAAU,CAChB,UAAU,qBAAqB,MAAM,EAAE,GAAG,UAAU,OAAO,CAAC,CAC5D,KAAK,mBAAmB,GAC3B,MAAM,IAAI,GACP,MAAM,UAAU,CAChB,UAAU,qBAAqB,MAAM,EAAE,GAAG,UAAU,OAAO,CAAC,GAAG,YAAY,SAAS,CAAC,CACrF,KAAK,mBAAmB,CAChC,CACF,EACe,MAAM;;CAEzB,CAAC;;AAGF,MAAa,SAAS,SAAS;CAC7B,MAAM;EACJ,QAAQ,EAAE,GAAG,OAAO;EACpB,UAAU,EAAE,QAAQ;EACpB,mBAAmB,EAAE,QAAQ;EAC7B,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC;EAC9B,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC;EAC5B;CACD,SAAS,EAAE,GAAG,UAAU;CACxB,SAAS,OAAO,KAAK,SAAS;EAQ5B,MAAM,WAAW,MAAM,IAAI,GACxB,MAAM,UAAU,CAChB,UAAU,wBAAwB,MACjC,EAAE,GAAG,YAAY,KAAK,SAAS,CAAC,GAAG,qBAAqB,KAAK,kBAAkB,CAChF,CACA,OAAO;AACV,MAAI,aAAa,MAAM;AACrB,OAAI,SAAS,WAAW,KAAK,OAI3B,OAAM,YACJ,UAAU,wBACV,kDACD;AAEH,UAAO,SAAS;;AAElB,SAAO,MAAM,IAAI,GAAG,OAAO,WAAW,KAAK;;CAE9C,CAAC;;AAGF,MAAa,SAAS,SAAS;CAC7B,MAAM;EACJ,IAAI,EAAE,GAAG,UAAU;EACnB,OAAO,EAAE,OAAO;GACd,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;GAChC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC;GAChC,mBAAmB,EAAE,SAAS,EAAE,QAAQ,CAAC;GACzC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC;GAC9B,eAAe,EAAE,SAAS,EAAE,QAAQ,CAAC;GACrC,eAAe,EAAE,SAAS,EAAE,QAAQ,CAAC;GACrC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC;GAC5B,CAAC;EACH;CACD,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,IAAI,WAAW,YAAY;AAChD,QAAM,IAAI,GAAG,MAAM,WAAW,WAAW,MAAM;AAC/C,SAAO;;CAEV,CAAC;;;;;AAMF,MAAM,SAAS,SAAS;CACtB,MAAM;EACJ,IAAI,EAAE,GAAG,UAAU;EACnB,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;EAChC,qBAAqB,EAAE,SAAS,EAAE,SAAS,CAAC;EAC7C;CACD,SAAS,EAAE,MAAM;CACjB,SAAS,OAAO,KAAK,EAAE,IAAI,WAAW,QAAQ,0BAA0B;EACtE,MAAM,MAAM,MAAM,IAAI,GAAG,IAAI,WAAW,UAAU;AAClD,MAAI,QAAQ,QAAS,WAAW,UAAa,IAAI,WAAW,OAC1D,OAAM,YAAY,UAAU,mBAAmB,qBAAqB;AAEtE,MAAI,wBAAwB,MAAM;GAChC,IAAI,aAAa;AACjB,cAAW,MAAM,WAAW,IAAI,GAC7B,MAAM,UAAU,CAChB,UAAU,qBAAqB,MAAM,EAAE,GAAG,UAAU,IAAI,OAAO,CAAC,CACjE,KAAI,QAAQ,QAAQ,WAAW;AAC7B,iBAAa;AACb;;AAGJ,OAAI,CAAC,WACH,OAAM,YAAY,UAAU,oBAAoB,uCAAuC;;AAG3F,QAAM,IAAI,GAAG,OAAO,WAAW,UAAU;AACzC,SAAO;;CAEV,CAAC"}
|
package/dist/component/batch.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { ErrorCode } from "../shared/codes.js";
|
|
2
|
-
import {
|
|
2
|
+
import { convexError } from "../shared/errors.js";
|
|
3
3
|
|
|
4
4
|
//#region src/component/batch.ts
|
|
5
5
|
/** Maximum IDs accepted by a single batched component read. @internal */
|
|
6
6
|
const MAX_BATCH_SELECTOR_SIZE = 100;
|
|
7
7
|
/** @internal Reject an oversized batch before it can start database work. */
|
|
8
8
|
function assertBatchSelectorSize(ids, selector) {
|
|
9
|
-
if (ids.length > MAX_BATCH_SELECTOR_SIZE) throw
|
|
10
|
-
code: ErrorCode.INVALID_PARAMETERS,
|
|
11
|
-
message: `Batch selector "${selector}" accepts at most ${MAX_BATCH_SELECTOR_SIZE} IDs.`
|
|
12
|
-
});
|
|
9
|
+
if (ids.length > MAX_BATCH_SELECTOR_SIZE) throw convexError(ErrorCode.INVALID_PARAMETERS, `Batch selector "${selector}" accepts at most ${MAX_BATCH_SELECTOR_SIZE} IDs.`);
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch.js","names":[],"sources":["../../src/component/batch.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"batch.js","names":[],"sources":["../../src/component/batch.ts"],"sourcesContent":["import { ErrorCode } from \"../shared/codes\";\nimport { convexError } from \"../shared/errors\";\n\n/** Maximum IDs accepted by a single batched component read. @internal */\nexport const MAX_BATCH_SELECTOR_SIZE = 100;\n\n/** @internal Reject an oversized batch before it can start database work. */\nexport function assertBatchSelectorSize(ids: readonly unknown[], selector: string) {\n if (ids.length > MAX_BATCH_SELECTOR_SIZE) {\n throw convexError(\n ErrorCode.INVALID_PARAMETERS,\n `Batch selector \"${selector}\" accepts at most ${MAX_BATCH_SELECTOR_SIZE} IDs.`,\n );\n }\n}\n"],"mappings":";;;;;AAIA,MAAa,0BAA0B;;AAGvC,SAAgB,wBAAwB,KAAyB,UAAkB;AACjF,KAAI,IAAI,SAAS,wBACf,OAAM,YACJ,UAAU,oBACV,mBAAmB,SAAS,oBAAoB,wBAAwB,OACzE"}
|