@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.
Files changed (184) hide show
  1. package/dist/bin.js +45 -84
  2. package/dist/client/factors/device.js +3 -8
  3. package/dist/client/factors/device.js.map +1 -1
  4. package/dist/component/account.d.ts +33 -33
  5. package/dist/component/account.d.ts.map +1 -1
  6. package/dist/component/account.js +6 -17
  7. package/dist/component/account.js.map +1 -1
  8. package/dist/component/batch.js +2 -5
  9. package/dist/component/batch.js.map +1 -1
  10. package/dist/component/connection/audit.d.ts +11 -11
  11. package/dist/component/connection/domain/verification.d.ts +15 -15
  12. package/dist/component/connection/domain.d.ts +18 -18
  13. package/dist/component/connection/domain.d.ts.map +1 -1
  14. package/dist/component/connection/domain.js +5 -13
  15. package/dist/component/connection/domain.js.map +1 -1
  16. package/dist/component/connection/saml/assertion.d.ts +4 -4
  17. package/dist/component/connection/saml/request.d.ts +7 -7
  18. package/dist/component/connection/scim/config.d.ts +11 -11
  19. package/dist/component/connection/scim/identity.d.ts +41 -41
  20. package/dist/component/connection/scim/identity.d.ts.map +1 -1
  21. package/dist/component/connection/scim/identity.js +24 -89
  22. package/dist/component/connection/scim/identity.js.map +1 -1
  23. package/dist/component/connection/secret.d.ts +13 -13
  24. package/dist/component/connection/webhook/delivery.d.ts +22 -22
  25. package/dist/component/connection/webhook/endpoint.d.ts +16 -16
  26. package/dist/component/connection.d.ts +26 -26
  27. package/dist/component/connection.d.ts.map +1 -1
  28. package/dist/component/connection.js +4 -9
  29. package/dist/component/connection.js.map +1 -1
  30. package/dist/component/context.d.ts +18 -18
  31. package/dist/component/convex.config.d.ts +2 -2
  32. package/dist/component/convex.config.d.ts.map +1 -1
  33. package/dist/component/crons.d.ts +2 -2
  34. package/dist/component/documents.d.ts +50 -50
  35. package/dist/component/event.d.ts +41 -41
  36. package/dist/component/event.d.ts.map +1 -1
  37. package/dist/component/event.js +5 -13
  38. package/dist/component/event.js.map +1 -1
  39. package/dist/component/factor/device.d.ts +21 -21
  40. package/dist/component/factor/device.d.ts.map +1 -1
  41. package/dist/component/factor/passkey.d.ts +76 -76
  42. package/dist/component/factor/passkey.d.ts.map +1 -1
  43. package/dist/component/factor/passkey.js +9 -31
  44. package/dist/component/factor/passkey.js.map +1 -1
  45. package/dist/component/factor/totp.d.ts +46 -46
  46. package/dist/component/factor/totp.d.ts.map +1 -1
  47. package/dist/component/factor/totp.js +4 -9
  48. package/dist/component/factor/totp.js.map +1 -1
  49. package/dist/component/group/active.d.ts +84 -84
  50. package/dist/component/group/active.d.ts.map +1 -1
  51. package/dist/component/group/active.js +3 -6
  52. package/dist/component/group/active.js.map +1 -1
  53. package/dist/component/group/invite.d.ts +29 -29
  54. package/dist/component/group/invite.d.ts.map +1 -1
  55. package/dist/component/group/invite.js +18 -64
  56. package/dist/component/group/invite.js.map +1 -1
  57. package/dist/component/group/member.d.ts +34 -34
  58. package/dist/component/group/member.d.ts.map +1 -1
  59. package/dist/component/group/member.js +3 -4
  60. package/dist/component/group/member.js.map +1 -1
  61. package/dist/component/group.d.ts +34 -34
  62. package/dist/component/group.d.ts.map +1 -1
  63. package/dist/component/group.js +4 -9
  64. package/dist/component/group.js.map +1 -1
  65. package/dist/component/http.d.ts +2 -2
  66. package/dist/component/limits.d.ts +4 -4
  67. package/dist/component/maintenance.d.ts +2 -2
  68. package/dist/component/model.d.ts +494 -494
  69. package/dist/component/oauth/client.d.ts +18 -18
  70. package/dist/component/oauth/code.d.ts +11 -11
  71. package/dist/component/oauth/refresh.d.ts +16 -16
  72. package/dist/component/schema.d.ts +673 -673
  73. package/dist/component/session.d.ts +26 -26
  74. package/dist/component/session.d.ts.map +1 -1
  75. package/dist/component/token/continuation.d.ts +18 -18
  76. package/dist/component/token/continuation.d.ts.map +1 -1
  77. package/dist/component/token/enrollment.d.ts +9 -9
  78. package/dist/component/token/pkce.d.ts +22 -22
  79. package/dist/component/token/refresh.d.ts +33 -33
  80. package/dist/component/token/verification.d.ts +30 -30
  81. package/dist/component/token/verification.d.ts.map +1 -1
  82. package/dist/component/user/email.d.ts +17 -17
  83. package/dist/component/user/email.d.ts.map +1 -1
  84. package/dist/component/user/email.js +9 -29
  85. package/dist/component/user/email.js.map +1 -1
  86. package/dist/component/user/key.d.ts +27 -27
  87. package/dist/component/user/key.d.ts.map +1 -1
  88. package/dist/component/user/key.js +5 -15
  89. package/dist/component/user/key.js.map +1 -1
  90. package/dist/component/user.d.ts +39 -39
  91. package/dist/component/user.d.ts.map +1 -1
  92. package/dist/component/user.js +3 -5
  93. package/dist/component/user.js.map +1 -1
  94. package/dist/providers/anonymous.d.ts +3 -3
  95. package/dist/providers/anonymous.d.ts.map +1 -1
  96. package/dist/providers/password.d.ts +40 -40
  97. package/dist/providers/password.d.ts.map +1 -1
  98. package/dist/providers/password.js +12 -41
  99. package/dist/providers/password.js.map +1 -1
  100. package/dist/server/auth.js +5 -17
  101. package/dist/server/auth.js.map +1 -1
  102. package/dist/server/connection/checks.d.ts +16 -0
  103. package/dist/server/connection/checks.d.ts.map +1 -0
  104. package/dist/server/connection/checks.js +44 -0
  105. package/dist/server/connection/checks.js.map +1 -0
  106. package/dist/server/connection/domain.d.ts +9 -43
  107. package/dist/server/connection/domain.d.ts.map +1 -1
  108. package/dist/server/connection/domain.js +26 -123
  109. package/dist/server/connection/domain.js.map +1 -1
  110. package/dist/server/connection/group/service.d.ts.map +1 -1
  111. package/dist/server/connection/group/service.js +7 -25
  112. package/dist/server/connection/group/service.js.map +1 -1
  113. package/dist/server/connection/http.js +1 -1
  114. package/dist/server/connection/policy/domain.js +1 -1
  115. package/dist/server/connection/provision.js +9 -39
  116. package/dist/server/connection/provision.js.map +1 -1
  117. package/dist/server/connection/webhook.js +1 -1
  118. package/dist/server/context.js +3 -9
  119. package/dist/server/context.js.map +1 -1
  120. package/dist/server/core.js +4 -14
  121. package/dist/server/core.js.map +1 -1
  122. package/dist/server/crypto.js +1 -1
  123. package/dist/server/domains/account.d.ts.map +1 -1
  124. package/dist/server/domains/account.js +3 -9
  125. package/dist/server/domains/account.js.map +1 -1
  126. package/dist/server/domains/key.js +9 -33
  127. package/dist/server/domains/key.js.map +1 -1
  128. package/dist/server/domains/member.js +4 -14
  129. package/dist/server/domains/member.js.map +1 -1
  130. package/dist/server/env.js +3 -9
  131. package/dist/server/env.js.map +1 -1
  132. package/dist/server/errors.js +13 -23
  133. package/dist/server/errors.js.map +1 -1
  134. package/dist/server/facade.d.ts.map +1 -1
  135. package/dist/server/facade.js +2 -5
  136. package/dist/server/facade.js.map +1 -1
  137. package/dist/server/http.js +2 -4
  138. package/dist/server/http.js.map +1 -1
  139. package/dist/server/identity/claims.js +3 -9
  140. package/dist/server/identity/claims.js.map +1 -1
  141. package/dist/server/mutations/account.js +3 -5
  142. package/dist/server/mutations/account.js.map +1 -1
  143. package/dist/server/mutations/code.js +4 -9
  144. package/dist/server/mutations/code.js.map +1 -1
  145. package/dist/server/mutations/enrollment.js +3 -5
  146. package/dist/server/mutations/enrollment.js.map +1 -1
  147. package/dist/server/mutations/oauth.js +5 -13
  148. package/dist/server/mutations/oauth.js.map +1 -1
  149. package/dist/server/mutations/register.js +7 -21
  150. package/dist/server/mutations/register.js.map +1 -1
  151. package/dist/server/mutations/signature.js +3 -5
  152. package/dist/server/mutations/signature.js.map +1 -1
  153. package/dist/server/oauth/code.d.ts.map +1 -1
  154. package/dist/server/oauth/code.js +2 -4
  155. package/dist/server/oauth/code.js.map +1 -1
  156. package/dist/server/oauth/handlers.js +5 -18
  157. package/dist/server/oauth/handlers.js.map +1 -1
  158. package/dist/server/redirects.js +5 -17
  159. package/dist/server/redirects.js.map +1 -1
  160. package/dist/server/runtime.d.ts +10 -10
  161. package/dist/server/runtime.d.ts.map +1 -1
  162. package/dist/server/runtime.js +6 -17
  163. package/dist/server/runtime.js.map +1 -1
  164. package/dist/server/secret.js +2 -5
  165. package/dist/server/secret.js.map +1 -1
  166. package/dist/server/services/factors.js +4 -14
  167. package/dist/server/services/factors.js.map +1 -1
  168. package/dist/server/services/providers.js +2 -6
  169. package/dist/server/services/providers.js.map +1 -1
  170. package/dist/server/signin/flow.js +2 -5
  171. package/dist/server/signin/flow.js.map +1 -1
  172. package/dist/server/token/refresh.js +3 -9
  173. package/dist/server/token/refresh.js.map +1 -1
  174. package/dist/server/totp.js +2 -1
  175. package/dist/server/totp.js.map +1 -1
  176. package/dist/server/user/account.js +4 -13
  177. package/dist/server/user/account.js.map +1 -1
  178. package/dist/server/validators.d.ts +52 -52
  179. package/dist/server/webauthn.js +17 -4
  180. package/dist/server/webauthn.js.map +1 -1
  181. package/dist/shared/errors.js +18 -2
  182. package/dist/shared/errors.js.map +1 -1
  183. package/dist/test.d.ts +614 -614
  184. 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 configureConvexConfig(config);
6513
- await initializeAuth(config);
6514
- await initializeHttp(config);
6515
- await initializeAuthConfig(config);
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
- async function configureConvexConfig(config) {
6839
- logStep(config, "Configure convex config file");
6840
- const sourceTemplate = `\
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
- const source = templateToSource(sourceTemplate);
6851
- const convexConfigPath = path.join(config.convexFolderPath, "convex.config");
6852
- const existingConfigPath = existingNonEmptySourcePath(convexConfigPath);
6853
- if (existingConfigPath !== null) if (doesAlreadyMatchTemplate(readFileSync(existingConfigPath, "utf8"), sourceTemplate)) O.success(`${existingConfigPath} is already set up.`);
6854
- else {
6855
- O.info(`You already have ${existingConfigPath}. Make sure it registers the auth component:`);
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
- const source = templateToSource(sourceTemplate);
6884
- const authPath = path.join(config.convexFolderPath, "auth");
6885
- const existingAuthPath = existingNonEmptySourcePath(authPath);
6886
- if (existingAuthPath !== null) if (doesAlreadyMatchTemplate(readFileSync(existingAuthPath, "utf8"), sourceTemplate)) O.success(`${existingAuthPath} is already set up.`);
6887
- else {
6888
- O.info(`You already have ${existingAuthPath}. Make sure it initializes auth with defineAuth:`);
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
- const source = templateToSource(sourceTemplate);
6911
- const httpPath = path.join(config.convexFolderPath, "http");
6912
- const existingPath = existingNonEmptySourcePath(httpPath);
6913
- if (existingPath !== null) if (doesAlreadyMatchTemplate(readFileSync(existingPath, "utf8"), sourceTemplate)) O.success(`${existingPath} is already set up.`);
6914
- else {
6915
- O.info(`You already have ${existingPath}. Make sure it mounts Convex Auth protocol routes:`);
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 authConfigPath = path.join(config.convexFolderPath, "auth.config");
6946
- const existingPath = existingNonEmptySourcePath(authConfigPath);
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 trusts env.CONVEX_SITE_URL as the Convex auth issuer:`);
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 ? `${authConfigPath}.ts` : `${authConfigPath}.js`;
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 new ConvexError({
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 new ConvexError({
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 new ConvexError({\n code: ErrorCode.DEVICE_CODE_EXPIRED,\n message: \"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 new ConvexError({\n code: ErrorCode.DEVICE_AUTHORIZATION_FAILED,\n message: error instanceof Error ? error.message : \"Invalid or expired code.\",\n });\n }\n },\n };\n}\n"],"mappings":";;;;AAOA,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,IAAI,YAAY;IACpB,MAAM,UAAU;IAChB,SAAS;IACV,CAAC;;EAGJ,QAAQ,OAAO,SAA0C;GACvD,MAAM,SAAyB;IAC7B,MAAM;IACN,UAAU,KAAK;IAChB;AAED,OAAI;AACF,UAAM,oBAAoB,OAAO;YAC1B,OAAO;AACd,UAAM,IAAI,YAAY;KACpB,MAAM,UAAU;KAChB,SAAS,iBAAiB,QAAQ,MAAM,UAAU;KACnD,CAAC;;;EAGP"}
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 convex_values2496 from "convex/values";
3
- import * as convex_server96 from "convex/server";
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: convex_server96.RegisteredMutation<"public", {
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: convex_values2496.GenericId<"Account">;
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: convex_values2496.GenericId<"User">;
39
+ userId: convex_values2499.GenericId<"User">;
40
40
  provider: string;
41
41
  providerAccountId: string;
42
42
  };
43
43
  user: {
44
- _id: convex_values2496.GenericId<"User">;
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?: convex_values2496.GenericId<"Group"> | undefined;
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: convex_server96.RegisteredMutation<"public", {
62
+ declare const completeCredentialsSignIn: convex_server89.RegisteredMutation<"public", {
63
63
  replaceSession?: {
64
- sessionId: convex_values2496.GenericId<"Session">;
65
- authenticatedUserId: convex_values2496.GenericId<"User">;
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: convex_values2496.GenericId<"Account">;
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: convex_server96.RegisteredQuery<"public", {
83
- id?: convex_values2496.GenericId<"Account"> | undefined;
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: convex_values2496.GenericId<"Account">;
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: convex_values2496.GenericId<"User">;
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: convex_server96.RegisteredQuery<"public", {
105
- userId?: convex_values2496.GenericId<"User"> | undefined;
104
+ declare const list: convex_server89.RegisteredQuery<"public", {
105
+ userId?: convex_values2499.GenericId<"User"> | undefined;
106
106
  provider?: string | undefined;
107
- userIds?: convex_values2496.GenericId<"User">[] | undefined;
107
+ userIds?: convex_values2499.GenericId<"User">[] | undefined;
108
108
  }, Promise<{
109
- _id: convex_values2496.GenericId<"Account">;
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: convex_values2496.GenericId<"User">;
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: convex_server96.RegisteredMutation<"public", {
120
+ declare const create: convex_server89.RegisteredMutation<"public", {
121
121
  extend?: any;
122
122
  secret?: string | undefined;
123
- userId: convex_values2496.GenericId<"User">;
123
+ userId: convex_values2499.GenericId<"User">;
124
124
  provider: string;
125
125
  providerAccountId: string;
126
- }, Promise<convex_values2496.GenericId<"Account">>>;
126
+ }, Promise<convex_values2499.GenericId<"Account">>>;
127
127
  /** Patch fields on an account. */
128
- declare const update: convex_server96.RegisteredMutation<"public", {
129
- id: convex_values2496.GenericId<"Account">;
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: convex_server96.RegisteredMutation<"public", {
145
- userId?: convex_values2496.GenericId<"User"> | undefined;
144
+ declare const remove: convex_server89.RegisteredMutation<"public", {
145
+ userId?: convex_values2499.GenericId<"User"> | undefined;
146
146
  requireOtherAccount?: boolean | undefined;
147
- id: convex_values2496.GenericId<"Account">;
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":";;;;;;;;KAuBK,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;WAmCV,iBAAA,CAAA,SAAA"}
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 { ConvexError, v } from "convex/values";
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 new ConvexError({
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 new ConvexError({
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 new ConvexError({
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 new ConvexError({
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":"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"}
@@ -1,15 +1,12 @@
1
1
  import { ErrorCode } from "../shared/codes.js";
2
- import { ConvexError } from "convex/values";
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 new ConvexError({
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 { ConvexError } from \"convex/values\";\n\nimport { ErrorCode } from \"../shared/codes\";\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 new ConvexError({\n code: ErrorCode.INVALID_PARAMETERS,\n message: `Batch selector \"${selector}\" accepts at most ${MAX_BATCH_SELECTOR_SIZE} IDs.`,\n });\n }\n}\n"],"mappings":";;;;;AAKA,MAAa,0BAA0B;;AAGvC,SAAgB,wBAAwB,KAAyB,UAAkB;AACjF,KAAI,IAAI,SAAS,wBACf,OAAM,IAAI,YAAY;EACpB,MAAM,UAAU;EAChB,SAAS,mBAAmB,SAAS,oBAAoB,wBAAwB;EAClF,CAAC"}
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"}