@lupinum/better-convex-nuxt 1.0.0-beta.4 → 1.0.0-beta.6

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/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=4.5.2 <5"
6
6
  },
7
- "version": "1.0.0-beta.4",
7
+ "version": "1.0.0-beta.6",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
@@ -42,7 +42,7 @@ import schema from './schema'
42
42
  import schemaMetadata from './schemaMetadata'
43
43
 
44
44
  // This module is inside the isolated betterAuth component, not the public app API.
45
- export const { assertProfile, consumeOne, count, create, deleteMany, deleteOne, findMany, findOne, incrementOne, rotateSigningKey, sessionAdmission, expireWorkforceSession, listWorkforceSessions, revokeAllWorkforceSessions, revokeWorkforceSession, touchWorkforceSession, updateMany, updateOne } = defineAuthAdapterFunctions({ metadata: schemaMetadata, schema })
45
+ export const { assertProfile, consumeOne, consumeRateLimit, count, create, deleteMany, deleteOne, findMany, findOne, incrementOne, rotateSigningKey, sessionAdmission, expireWorkforceSession, listWorkforceSessions, revokeAllWorkforceSessions, revokeWorkforceSession, touchWorkforceSession, updateMany, updateOne } = defineAuthAdapterFunctions({ metadata: schemaMetadata, schema })
46
46
  `,
47
47
  "convex/betterAuth/convex.config.ts": `import { defineComponent } from 'convex/server'
48
48
 
@@ -6,6 +6,9 @@ export interface DefineAuthAdapterFunctionsOptions<Schema extends SchemaDefiniti
6
6
  metadata: AuthSchemaMetadata;
7
7
  }
8
8
  export declare function defineAuthAdapterFunctions<Schema extends SchemaDefinition<any, any>>({ schema, metadata, }: DefineAuthAdapterFunctionsOptions<Schema>): {
9
+ migrateBeta3UserGeneration: import("convex/server").RegisteredMutation<"internal", {
10
+ mode: "forward" | "rollback";
11
+ }, Promise<import("../session-generation.js").Beta3UserGenerationMigrationResult>>;
9
12
  touchWorkforceSession: import("convex/server").RegisteredMutation<"public", {
10
13
  actor: {
11
14
  userId: string;
@@ -300,6 +303,18 @@ export declare function defineAuthAdapterFunctions<Schema extends SchemaDefiniti
300
303
  field: string;
301
304
  }[];
302
305
  }, Promise<Record<string, string | number | boolean | string[] | number[] | null> | null>>;
306
+ consumeRateLimit: import("convex/server").RegisteredMutation<"public", {
307
+ window: number;
308
+ key: string;
309
+ max: number;
310
+ retentionWindow: number;
311
+ }, Promise<{
312
+ allowed: boolean;
313
+ retryAfter: null;
314
+ } | {
315
+ allowed: boolean;
316
+ retryAfter: number;
317
+ }>>;
303
318
  incrementOne: import("convex/server").RegisteredMutation<"public", {
304
319
  set?: any;
305
320
  workforce?: {
@@ -12,6 +12,7 @@ import {
12
12
  normalizeSigningKeyCandidate,
13
13
  signingKeyCandidateValidator
14
14
  } from "../jwks-rotation.js";
15
+ import { migrateBeta3UserGeneration, sessionGenerationAuthority } from "../session-generation.js";
15
16
  import { createWorkforceAdapterPolicy } from "../workforce/adapter-policy.js";
16
17
  import { readAuthSessionAdmission } from "../workforce/admission.js";
17
18
  import {
@@ -78,6 +79,7 @@ const readArgs = {
78
79
  offset: v.optional(v.number())
79
80
  };
80
81
  const expireSessionReference = makeFunctionReference("adapter:expireWorkforceSession");
82
+ const pruneRateLimitsReference = makeFunctionReference("adapter:deleteMany");
81
83
  function assertRecord(value, code) {
82
84
  if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(code);
83
85
  }
@@ -222,6 +224,7 @@ export function defineAuthAdapterFunctions({
222
224
  }) {
223
225
  assertAuthSchemaMatchesMetadata(schema, metadata);
224
226
  const workforce = hasWorkforceSchema(metadata);
227
+ const generationAuthority = sessionGenerationAuthority(metadata);
225
228
  const workforcePolicy = createWorkforceAdapterPolicy(workforce, metadata);
226
229
  const relationships = createAuthRelationshipEngine({
227
230
  schema,
@@ -233,6 +236,19 @@ export function defineAuthAdapterFunctions({
233
236
  if (!workforce) throw new Error("AUTH_WORKFORCE_SCHEMA_REQUIRED");
234
237
  }
235
238
  return {
239
+ migrateBeta3UserGeneration: internalMutationGeneric({
240
+ args: {
241
+ mode: v.union(v.literal("forward"), v.literal("rollback"))
242
+ },
243
+ returns: v.object({
244
+ patched: v.number(),
245
+ scanned: v.number()
246
+ }),
247
+ handler: (ctx, args) => {
248
+ if (!generationAuthority) throw new Error("AUTH_SESSION_GENERATION_SCHEMA_INVALID");
249
+ return migrateBeta3UserGeneration(ctx, args.mode, generationAuthority);
250
+ }
251
+ }),
236
252
  touchWorkforceSession: mutationGeneric({
237
253
  args: { actor: workforceSessionActorValidator },
238
254
  returns: v.object({ expiresAt: v.number() }),
@@ -547,6 +563,74 @@ export function defineAuthAdapterFunctions({
547
563
  return toBetterAuthDocument(current);
548
564
  }
549
565
  }),
566
+ consumeRateLimit: mutationGeneric({
567
+ args: {
568
+ key: v.string(),
569
+ max: v.number(),
570
+ retentionWindow: v.number(),
571
+ window: v.number()
572
+ },
573
+ returns: v.object({
574
+ allowed: v.boolean(),
575
+ retryAfter: v.union(v.number(), v.null())
576
+ }),
577
+ handler: async (ctx, args) => {
578
+ const windowInMs = args.window * 1e3;
579
+ const retentionWindowInMs = args.retentionWindow * 1e3;
580
+ if (args.key.length === 0 || !Number.isSafeInteger(args.max) || args.max <= 0 || !Number.isFinite(args.window) || args.window <= 0 || !Number.isFinite(args.retentionWindow) || args.retentionWindow < args.window || !Number.isFinite(windowInMs) || !Number.isFinite(retentionWindowInMs)) {
581
+ throw new Error("AUTH_RATE_LIMIT_RULE_INVALID");
582
+ }
583
+ const now = Date.now();
584
+ const current = oneOrNull(
585
+ await findAuthRows(
586
+ ctx,
587
+ schema,
588
+ metadata,
589
+ { model: "rateLimit", where: [{ field: "key", value: args.key }] },
590
+ 2
591
+ ),
592
+ "AUTH_RATE_LIMIT"
593
+ );
594
+ if (!current) {
595
+ await ctx.db.insert(
596
+ "rateLimit",
597
+ { id: args.key, key: args.key, count: 1, lastRequest: now }
598
+ );
599
+ return { allowed: true, retryAfter: null };
600
+ }
601
+ if (typeof current.count !== "number" || !Number.isSafeInteger(current.count) || current.count < 0 || typeof current.lastRequest !== "number" || !Number.isFinite(current.lastRequest)) {
602
+ throw new Error("AUTH_RATE_LIMIT_ROW_INVALID");
603
+ }
604
+ if (now - current.lastRequest >= windowInMs) {
605
+ await ctx.db.patch(current._id, { count: 1, lastRequest: now });
606
+ await ctx.scheduler.runAfter(0, pruneRateLimitsReference, {
607
+ model: "rateLimit",
608
+ where: [
609
+ {
610
+ field: "lastRequest",
611
+ operator: "lt",
612
+ value: now - retentionWindowInMs
613
+ }
614
+ ]
615
+ });
616
+ return { allowed: true, retryAfter: null };
617
+ }
618
+ if (current.count >= args.max) {
619
+ return {
620
+ allowed: false,
621
+ retryAfter: Math.max(1, Math.ceil((current.lastRequest + windowInMs - now) / 1e3))
622
+ };
623
+ }
624
+ await ctx.db.patch(
625
+ current._id,
626
+ {
627
+ count: current.count + 1,
628
+ lastRequest: now
629
+ }
630
+ );
631
+ return { allowed: true, retryAfter: null };
632
+ }
633
+ }),
550
634
  incrementOne: mutationGeneric({
551
635
  returns: v.union(authDocumentValidator, v.null()),
552
636
  args: {
@@ -37,6 +37,15 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
37
37
  value: string | number | boolean | Array<string> | Array<number> | null;
38
38
  }>;
39
39
  }, Record<string, string | number | boolean | Array<string> | Array<number> | null> | null, Name>;
40
+ consumeRateLimit: FunctionReference<"mutation", "internal", {
41
+ key: string;
42
+ max: number;
43
+ retentionWindow: number;
44
+ window: number;
45
+ }, {
46
+ allowed: boolean;
47
+ retryAfter: number | null;
48
+ }, Name>;
40
49
  count: FunctionReference<"query", "internal", {
41
50
  model: string;
42
51
  where?: Array<{
@@ -13,7 +13,18 @@ export declare const assertProfile: import("convex/server").RegisteredQuery<"pub
13
13
  value: string | number | boolean | string[] | number[] | null;
14
14
  field: string;
15
15
  }[];
16
- }, Promise<Record<string, string | number | boolean | string[] | number[] | null> | null>>, count: import("convex/server").RegisteredQuery<"public", {
16
+ }, Promise<Record<string, string | number | boolean | string[] | number[] | null> | null>>, consumeRateLimit: import("convex/server").RegisteredMutation<"public", {
17
+ window: number;
18
+ key: string;
19
+ max: number;
20
+ retentionWindow: number;
21
+ }, Promise<{
22
+ allowed: boolean;
23
+ retryAfter: null;
24
+ } | {
25
+ allowed: boolean;
26
+ retryAfter: number;
27
+ }>>, count: import("convex/server").RegisteredQuery<"public", {
17
28
  where?: {
18
29
  mode?: "sensitive" | "insensitive" | undefined;
19
30
  operator?: "in" | "lt" | "lte" | "gt" | "gte" | "eq" | "not_in" | "ne" | "contains" | "starts_with" | "ends_with" | undefined;
@@ -4,6 +4,7 @@ import schemaMetadata from "./schemaMetadata.js";
4
4
  export const {
5
5
  assertProfile,
6
6
  consumeOne,
7
+ consumeRateLimit,
7
8
  count,
8
9
  create,
9
10
  deleteMany,
@@ -11,6 +11,7 @@ import { createOAuthOperator } from "./oauth-operator.js";
11
11
  import { requireAuthOrigin } from "./origin.js";
12
12
  import { convexAuth } from "./plugin.js";
13
13
  import { getConvexAuthProvider } from "./provider.js";
14
+ import { createConvexAuthRateLimitStorage } from "./rate-limit-storage.js";
14
15
  import { workforceSessionPolicy } from "./workforce/operations.js";
15
16
  import { createWorkforceSchemaPlugins } from "./workforce/profile.js";
16
17
  import { createWorkforceProviderHooks } from "./workforce/provider-hooks.js";
@@ -209,6 +210,15 @@ export function createBetterConvexAuthOwned(component, options = {}, extraPlugin
209
210
  ] : [],
210
211
  ...oauthProfile ? [createOAuthProvider(oauthProfile)] : []
211
212
  ];
213
+ const maximumConfiguredPluginRateLimitWindow = Math.max(
214
+ 0,
215
+ ...plugins.flatMap((plugin) => plugin.rateLimit?.map((rule) => rule.window) ?? [])
216
+ );
217
+ const rateLimitStorage = createConvexAuthRateLimitStorage(
218
+ ctx,
219
+ component,
220
+ maximumConfiguredPluginRateLimitWindow
221
+ );
212
222
  const auth = betterAuth({
213
223
  appName: options.appName,
214
224
  account: {
@@ -260,7 +270,12 @@ export function createBetterConvexAuthOwned(component, options = {}, extraPlugin
260
270
  },
261
271
  emailVerification: workforce ? { ...emailVerification, autoSignInAfterVerification: false } : emailVerification,
262
272
  plugins,
263
- rateLimit: { enabled: true, modelName: "rateLimit", storage: "database" },
273
+ rateLimit: {
274
+ customStorage: rateLimitStorage,
275
+ enabled: true,
276
+ modelName: "rateLimit",
277
+ storage: "database"
278
+ },
264
279
  session: {
265
280
  expiresIn: 7 * 24 * 60 * 60,
266
281
  updateAge: 24 * 60 * 60,
@@ -8,6 +8,7 @@ export { requireAuthOrigin } from './origin.js';
8
8
  export { requireWritableAuthCtx } from './context.js';
9
9
  export { convexAuth } from './plugin.js';
10
10
  export { getConvexAuthProvider } from './provider.js';
11
+ export { createConvexAuthRateLimitStorage } from './rate-limit-storage.js';
11
12
  export { createBetterAuthMcpAccessVerifier, verifyOAuthBearerToken } from './oauth-resource.js';
12
13
  export { createUserProjectionTriggers } from './user-projection.js';
13
14
  export type { AuthCtx, WritableAuthCtx } from './context.js';
@@ -6,5 +6,6 @@ export { requireAuthOrigin } from "./origin.js";
6
6
  export { requireWritableAuthCtx } from "./context.js";
7
7
  export { convexAuth } from "./plugin.js";
8
8
  export { getConvexAuthProvider } from "./provider.js";
9
+ export { createConvexAuthRateLimitStorage } from "./rate-limit-storage.js";
9
10
  export { createBetterAuthMcpAccessVerifier, verifyOAuthBearerToken } from "./oauth-resource.js";
10
11
  export { createUserProjectionTriggers } from "./user-projection.js";
@@ -37,6 +37,7 @@ import {
37
37
  validateOAuthProviderProfile
38
38
  } from "./oauth-security.js";
39
39
  import { normalizeAuthOrigin } from "./origin.js";
40
+ import { isConvexAuthRateLimitStorage } from "./rate-limit-storage.js";
40
41
  import { workforceSchemaPlugin } from "./workforce/schema.js";
41
42
  import { isFullWorkforceSession } from "./workforce/session-assurance.js";
42
43
  const forbiddenCustomClaims = /* @__PURE__ */ new Set(["aud", "exp", "iat", "iss", "jti", "nbf", "sub"]);
@@ -379,7 +380,7 @@ function hasSafeGlobalAuthRuntime(context) {
379
380
  const ipAddress = context.options.advanced?.ipAddress;
380
381
  const ipHeaders = ipAddress?.ipAddressHeaders;
381
382
  const trustedProxies = ipAddress?.trustedProxies;
382
- return context.options.rateLimit?.enabled === true && context.options.rateLimit.storage === "database" && context.options.rateLimit.modelName === "rateLimit" && context.options.rateLimit.customStorage === void 0 && context.options.rateLimit.customRules === void 0 && Array.isArray(ipHeaders) && ipHeaders.length === 1 && ipHeaders[0]?.toLowerCase() === VERIFIED_CLIENT_IP_HEADER && (ipAddress?.disableIpTracking === void 0 || ipAddress.disableIpTracking === false) && (trustedProxies === void 0 || Array.isArray(trustedProxies) && trustedProxies.length === 0) && (ipAddress?.ipv6Subnet === void 0 || ipAddress.ipv6Subnet === 64) && (context.options.advanced?.trustedProxyHeaders === void 0 || context.options.advanced.trustedProxyHeaders === false);
383
+ return context.options.rateLimit?.enabled === true && context.options.rateLimit.storage === "database" && context.options.rateLimit.modelName === "rateLimit" && isConvexAuthRateLimitStorage(context.options.rateLimit.customStorage) && context.options.rateLimit.customRules === void 0 && Array.isArray(ipHeaders) && ipHeaders.length === 1 && ipHeaders[0]?.toLowerCase() === VERIFIED_CLIENT_IP_HEADER && (ipAddress?.disableIpTracking === void 0 || ipAddress.disableIpTracking === false) && (trustedProxies === void 0 || Array.isArray(trustedProxies) && trustedProxies.length === 0) && (ipAddress?.ipv6Subnet === void 0 || ipAddress.ipv6Subnet === 64) && (context.options.advanced?.trustedProxyHeaders === void 0 || context.options.advanced.trustedProxyHeaders === false);
383
384
  }
384
385
  export function convexAuth(options) {
385
386
  validateSessionJwt(options);
@@ -0,0 +1,20 @@
1
+ import type { BetterAuthOptions } from 'better-auth';
2
+ import type { GenericDataModel } from 'convex/server';
3
+ import { type AuthCtx } from './context.js';
4
+ import type { AuthAdapterComponentApi } from './types.js';
5
+ type BetterAuthRateLimitStorage = NonNullable<NonNullable<BetterAuthOptions['rateLimit']>['customStorage']>;
6
+ export declare function isConvexAuthRateLimitStorage(storage: BetterAuthRateLimitStorage | undefined): boolean;
7
+ /**
8
+ * Test seam for constructing a trusted in-memory storage fixture.
9
+ *
10
+ * @internal
11
+ */
12
+ export declare function registerConvexAuthRateLimitStorageForTest(storage: BetterAuthRateLimitStorage): BetterAuthRateLimitStorage;
13
+ /**
14
+ * Create the one atomic Better Auth rate-limit store backed by the auth component.
15
+ * `maximumConfiguredRateLimitWindow` must include every custom plugin rule. The
16
+ * constructor owns the pinned Better Auth built-in 60-second floor. A final
17
+ * Convex OCC error is safe to retry because that mutation did not commit.
18
+ */
19
+ export declare function createConvexAuthRateLimitStorage<DataModel extends GenericDataModel>(ctx: AuthCtx<DataModel>, component: AuthAdapterComponentApi, maximumConfiguredRateLimitWindow: number): BetterAuthRateLimitStorage;
20
+ export {};
@@ -0,0 +1,46 @@
1
+ import { requireWritableAuthCtx } from "./context.js";
2
+ const AUTH_RATE_LIMIT_CONTENTION_MAX_RETRIES = 5;
3
+ const BETTER_AUTH_MAX_BUILT_IN_RATE_LIMIT_WINDOW_SECONDS = 60;
4
+ const ownedStorages = /* @__PURE__ */ new WeakSet();
5
+ function isFinalConvexContention(error) {
6
+ const message = error instanceof Error ? error.message : String(error);
7
+ return /documents? read from or written to/iu.test(message) && /changed while this mutation was being run and on every subsequent retry/iu.test(message);
8
+ }
9
+ function retryDelay(attempt) {
10
+ const delayMs = Math.min(5 * 2 ** attempt, 80) + Math.floor(Math.random() * 7);
11
+ return new Promise((resolve) => setTimeout(resolve, delayMs));
12
+ }
13
+ export function isConvexAuthRateLimitStorage(storage) {
14
+ return storage !== void 0 && ownedStorages.has(storage);
15
+ }
16
+ export function registerConvexAuthRateLimitStorageForTest(storage) {
17
+ ownedStorages.add(storage);
18
+ return storage;
19
+ }
20
+ export function createConvexAuthRateLimitStorage(ctx, component, maximumConfiguredRateLimitWindow) {
21
+ const storage = {
22
+ async consume(key, rule) {
23
+ requireWritableAuthCtx(ctx);
24
+ for (let attempt = 0; ; attempt += 1) {
25
+ try {
26
+ return await ctx.runMutation(component.adapter.consumeRateLimit, {
27
+ key,
28
+ max: rule.max,
29
+ retentionWindow: Math.max(
30
+ BETTER_AUTH_MAX_BUILT_IN_RATE_LIMIT_WINDOW_SECONDS,
31
+ maximumConfiguredRateLimitWindow,
32
+ rule.window
33
+ ),
34
+ window: rule.window
35
+ });
36
+ } catch (error) {
37
+ if ("db" in ctx || attempt >= AUTH_RATE_LIMIT_CONTENTION_MAX_RETRIES || !isFinalConvexContention(error)) {
38
+ throw error;
39
+ }
40
+ await retryDelay(attempt);
41
+ }
42
+ }
43
+ }
44
+ };
45
+ return registerConvexAuthRateLimitStorageForTest(storage);
46
+ }
@@ -4,6 +4,11 @@ import type { AuthWhere } from './adapter/query.js';
4
4
  type ReadCtx = GenericQueryCtx<GenericDataModel>;
5
5
  type WriteCtx = GenericMutationCtx<GenericDataModel>;
6
6
  type Row = Record<string, unknown>;
7
+ export type Beta3UserGenerationMigrationMode = 'forward' | 'rollback';
8
+ export interface Beta3UserGenerationMigrationResult {
9
+ patched: number;
10
+ scanned: number;
11
+ }
7
12
  export interface SessionGenerationAuthority {
8
13
  assuranceGenerationField: string;
9
14
  securityGenerationField: string;
@@ -13,6 +18,11 @@ export interface SessionGenerationAuthority {
13
18
  }
14
19
  export declare const canonicalSessionGenerationAuthority: SessionGenerationAuthority;
15
20
  export declare function sessionGenerationAuthority(metadata: AuthSchemaMetadata): SessionGenerationAuthority | null;
21
+ /**
22
+ * Temporary beta.3 cutover for small, pre-customer deployments.
23
+ * Legacy sessions must be deleted before this operator runs.
24
+ */
25
+ export declare function migrateBeta3UserGeneration(ctx: WriteCtx, mode: Beta3UserGenerationMigrationMode, authority?: SessionGenerationAuthority): Promise<Beta3UserGenerationMigrationResult>;
16
26
  /** Add component-owned generation fields before strict generated-schema normalization. */
17
27
  export declare function prepareSessionGenerationCreate(ctx: ReadCtx, model: string, input: unknown, authority?: SessionGenerationAuthority): Promise<unknown>;
18
28
  /** Generic adapter updates must never mint or move session-generation authority. */
@@ -33,6 +33,34 @@ function generation(value) {
33
33
  async function readUser(ctx, userId, authority) {
34
34
  return await ctx.db.query(authority.userModel).withIndex("id", (query) => query.eq("id", userId)).unique();
35
35
  }
36
+ export async function migrateBeta3UserGeneration(ctx, mode, authority = canonicalSessionGenerationAuthority) {
37
+ const sessions = await ctx.db.query(authority.sessionModel).take(1);
38
+ if (sessions.length !== 0) throw new Error("AUTH_BETA3_CUTOVER_SESSIONS_REMAIN");
39
+ const users = await ctx.db.query(authority.userModel).take(129);
40
+ if (users.length > 128) throw new Error("AUTH_BETA3_CUTOVER_USER_LIMIT");
41
+ let patched = 0;
42
+ for (const user of users) {
43
+ if (typeof user._id !== "string" || !user._id || typeof user.id !== "string" || !user.id) {
44
+ throw new Error("AUTH_BETA3_CUTOVER_USER_INVALID");
45
+ }
46
+ const current = user[authority.securityGenerationField];
47
+ if (current !== void 0 && (!generation(current) || current !== 0)) {
48
+ throw new Error("AUTH_BETA3_CUTOVER_GENERATION_INVALID");
49
+ }
50
+ if (mode === "forward" && current === void 0) {
51
+ await ctx.db.patch(authority.userModel, user._id, {
52
+ [authority.securityGenerationField]: 0
53
+ });
54
+ patched += 1;
55
+ } else if (mode === "rollback" && current === 0) {
56
+ await ctx.db.patch(authority.userModel, user._id, {
57
+ [authority.securityGenerationField]: void 0
58
+ });
59
+ patched += 1;
60
+ }
61
+ }
62
+ return { patched, scanned: users.length };
63
+ }
36
64
  export async function prepareSessionGenerationCreate(ctx, model, input, authority = canonicalSessionGenerationAuthority) {
37
65
  if (!input || typeof input !== "object" || Array.isArray(input)) return input;
38
66
  const row = input;
@@ -1,4 +1,4 @@
1
- const packedRuntimeFingerprint = "bcn-release-v1-dd08fa363fff64276636c0d6063a23b769019aaf4f0aaa67d5aec0876d2cbb62";
1
+ const packedRuntimeFingerprint = "bcn-release-v1-1a4ab089f02a76a2e4e1fa972a0160575dcecf5a3acc7f1958616549ddc3c81c";
2
2
  const packedRuntimeFingerprintPattern = /^bcn-release-v1-[0-9a-f]{64}$/u;
3
3
  export const PACKED_RUNTIME_FINGERPRINT_HEADER = "x-bcn-runtime-fingerprint";
4
4
  export function getPackedRuntimeFingerprint() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lupinum/better-convex-nuxt",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.6",
4
4
  "description": "Full-featured Convex integration for Nuxt with SSR, real-time subscriptions, and authentication",
5
5
  "keywords": [
6
6
  "authentication",
@@ -195,7 +195,7 @@
195
195
  },
196
196
  "dependencies": {
197
197
  "@better-auth/utils": "0.4.2",
198
- "@lupinum/better-convex-vue": "1.0.0-beta.4",
198
+ "@lupinum/better-convex-vue": "1.0.0-beta.6",
199
199
  "@nuxt/kit": "4.5.2",
200
200
  "convex-helpers": "0.1.114",
201
201
  "defu": "^6.1.5",
@@ -218,7 +218,7 @@
218
218
  "@nuxt/test-utils": "^4.0.3",
219
219
  "@types/node": "22.20.1",
220
220
  "@vitejs/plugin-vue": "^6.0.8",
221
- "@vitest/browser-playwright": "^4.1.8",
221
+ "@vitest/browser-playwright": "^4.1.11",
222
222
  "arktype": "2.2.3",
223
223
  "better-auth": "1.7.2",
224
224
  "changelogen": "0.6.2",
@@ -236,7 +236,7 @@
236
236
  "tar": "7.5.22",
237
237
  "typescript": "~5.9.3",
238
238
  "vite": "8.1.5",
239
- "vitest": "^4.1.8",
239
+ "vitest": "^4.1.11",
240
240
  "vitest-browser-vue": "^2.1.0",
241
241
  "vue-tsc": "^3.3.7",
242
242
  "yaml": "2.9.0",
@@ -148,9 +148,9 @@
148
148
  "targetPath": "src/runtime/convex-auth/adapter/define-functions.ts",
149
149
  "sourceCommit": "c628916b451a6b4cff0f5464f134475464b1a6da",
150
150
  "sourceSha256": "7f537e1081bc22ee50d15df7b24e73e8869aa78a17af4aca18a5b0d261b186e3",
151
- "targetSha256": "6fa58f0f62762254ddc829397b171a9277e4561131b8943a878d69938be673aa",
151
+ "targetSha256": "cfabe28cf39cfe31075eddd06c7690728419f2e2a497fd58cd8e830ba603168b",
152
152
  "classification": "adapted",
153
- "modificationSummary": "Rebuilt component functions with strict validation, indexed relationships, failure-atomic writes and triggers, single-snapshot counts, guarded consumption and increments, authenticated signing-key rotation, component-owned session-generation invalidation, and workforce profile, admission, expiry, management, credential, factor, and replay operations.",
153
+ "modificationSummary": "Rebuilt component functions with strict validation, indexed relationships, failure-atomic writes and triggers, single-snapshot counts, one atomic rate-limit consume operation, guarded consumption and increments, authenticated signing-key rotation, component-owned session-generation invalidation, a temporary internal beta.3 user-generation cutover operator, and workforce profile, admission, expiry, management, credential, factor, and replay operations.",
154
154
  "latestReviewedUpstreamPatch": "https://github.com/get-convex/better-auth/commit/c628916b451a6b4cff0f5464f134475464b1a6da",
155
155
  "noticeRequired": true
156
156
  },
@@ -203,9 +203,9 @@
203
203
  "targetPath": "src/runtime/convex-auth/session-generation.ts",
204
204
  "sourceCommit": "c628916b451a6b4cff0f5464f134475464b1a6da",
205
205
  "sourceSha256": "70daca2ff67e790be91a69c92e35b9c9c373ff5b23402d796a3a50429cc002bc",
206
- "targetSha256": "8fdbceb38b80030c86e6071592b23646ecd407804f38da7f9cf9ae2baff27fdc",
206
+ "targetSha256": "e7f27dcd27e5a47a095647b9b54cac1a8c66b233ccc0e92049fda197dd5b168e",
207
207
  "classification": "rewritten",
208
- "modificationSummary": "Added component-owned user and session generations so the provider's canonical delete-all selector atomically invalidates every earlier session without an unbounded collection scan; stale sessions fail adapter reads and backend admission until scheduled physical expiry.",
208
+ "modificationSummary": "Added component-owned user and session generations so the provider's canonical delete-all selector atomically invalidates every earlier session without an unbounded collection scan; stale sessions fail adapter reads and backend admission until scheduled physical expiry. Added a bounded beta.3 user-generation cutover helper that requires legacy sessions to be deleted while preserving user IDs, accounts, passwords, and application references.",
209
209
  "latestReviewedUpstreamPatch": "https://github.com/get-convex/better-auth/commit/c628916b451a6b4cff0f5464f134475464b1a6da",
210
210
  "noticeRequired": false
211
211
  },
@@ -225,9 +225,9 @@
225
225
  "targetPath": "src/runtime/convex-auth/component/_generated/component.ts",
226
226
  "sourceCommit": "c628916b451a6b4cff0f5464f134475464b1a6da",
227
227
  "sourceSha256": "e337fe2ab868c4ba560868d24bcc725a7ca7e5672505adf57df6e5b6e61dbf4e",
228
- "targetSha256": "48e41743290968fd5668172ce5244d22fc146d01ea38de1d5a77e3de323c3e61",
228
+ "targetSha256": "4746f502864388993e5cca6c795b8fc481865dc2a348dd328a5a89f9854dd98c",
229
229
  "classification": "regenerated",
230
- "modificationSummary": "Regenerated the public ComponentApi declaration including relationship-aware writes, guarded consumption and increments, signing-key lifecycle operations, schema-profile assertion, persisted-session admission, and workforce session management.",
230
+ "modificationSummary": "Regenerated the public ComponentApi declaration including relationship-aware writes, atomic rate-limit consumption, guarded consumption and increments, signing-key lifecycle operations, schema-profile assertion, persisted-session admission, and workforce session management.",
231
231
  "latestReviewedUpstreamPatch": "https://github.com/get-convex/better-auth/commit/c628916b451a6b4cff0f5464f134475464b1a6da",
232
232
  "noticeRequired": false
233
233
  },
@@ -258,9 +258,9 @@
258
258
  "targetPath": "src/runtime/convex-auth/component/adapter.ts",
259
259
  "sourceCommit": "c628916b451a6b4cff0f5464f134475464b1a6da",
260
260
  "sourceSha256": "1a373c633a7090ea60d0e0942a6cb0c6cff65c842c8ca8950e1546cfa50dcbe5",
261
- "targetSha256": "cc67ba07c454d6e1b2d669f94dbdabb0bbb2ae0a89f569cf1492ea250a08cc5c",
261
+ "targetSha256": "eebdfab80285ac45b78ce92f4d79abe67f405bf0ae5a2e190cf65f0f7febb416",
262
262
  "classification": "rewritten",
263
- "modificationSummary": "Reduced the component adapter to a thin assembly of the shared adapter functions and explicit workforce schema-profile and session-management functions, including scheduled expiry and atomic signing-key rotation.",
263
+ "modificationSummary": "Reduced the component adapter to a thin assembly of the shared adapter functions and explicit workforce schema-profile and session-management functions, including atomic rate-limit consumption, scheduled expiry, and atomic signing-key rotation.",
264
264
  "latestReviewedUpstreamPatch": "https://github.com/get-convex/better-auth/commit/c628916b451a6b4cff0f5464f134475464b1a6da",
265
265
  "noticeRequired": false
266
266
  },
@@ -335,9 +335,9 @@
335
335
  "targetPath": "src/runtime/convex-auth/create-better-convex-auth.ts",
336
336
  "sourceCommit": "c628916b451a6b4cff0f5464f134475464b1a6da",
337
337
  "sourceSha256": "610a4d7edfbb0fd3ab412d61e8103b9e416edaaf8e2719189c9cff345597150d",
338
- "targetSha256": "0ecea97ae78a7dc70136f8973c57895bf6099e3781ee1802a0d0dfb2a5cf7e34",
338
+ "targetSha256": "aa9e991096e808fc15a806da6604a47d2b9fb055051459490e0b7308aa7184ad",
339
339
  "classification": "rewritten",
340
- "modificationSummary": "Introduced one reviewed factory that owns adapter wiring, JWT and plugin ordering, route registration, keys, secrets, projections, and OAuth operations; admits fail-closed request-scoped email and user-creation policy, supports one fixed workforce password-and-TOTP profile, keeps arbitrary plugins limited to the internal loopback-only test constructor, and lets that constructor revalidate its boundary for every auth instance.",
340
+ "modificationSummary": "Introduced one reviewed factory that owns adapter wiring, JWT and plugin ordering, route registration, keys, secrets, projections, OAuth operations, and atomic database rate-limit storage; admits fail-closed request-scoped email and user-creation policy, supports one fixed workforce password-and-TOTP profile, keeps arbitrary plugins limited to the internal loopback-only test constructor, and lets that constructor revalidate its boundary for every auth instance.",
341
341
  "latestReviewedUpstreamPatch": "https://github.com/get-convex/better-auth/commit/c628916b451a6b4cff0f5464f134475464b1a6da",
342
342
  "noticeRequired": false
343
343
  },
@@ -346,9 +346,9 @@
346
346
  "targetPath": "src/runtime/convex-auth/index.ts",
347
347
  "sourceCommit": "c628916b451a6b4cff0f5464f134475464b1a6da",
348
348
  "sourceSha256": "300f9ebf00a0f236954653984709abfb70e93f1b1b9a37fabba4ee44661d2cf5",
349
- "targetSha256": "f78023311543387ddd6e0a6c3f42054060d67f0104bfbdae58f3974362b9bb7f",
349
+ "targetSha256": "eb1124a91e1f672ec5e9305ff6fed383d7302cd498c3d1b369fd7325414aa805",
350
350
  "classification": "rewritten",
351
- "modificationSummary": "Replaced the compatibility barrel with the fixed narrow Better Convex Nuxt API, including the reviewed auth factory, maintained component, provider, OAuth resource verifier and operator, workforce schema-options helper, and rebuildable user projection.",
351
+ "modificationSummary": "Replaced the compatibility barrel with the fixed narrow Better Convex Nuxt API, including the reviewed auth factory, maintained component, atomic rate-limit storage constructor, provider, OAuth resource verifier and operator, workforce schema-options helper, and rebuildable user projection.",
352
352
  "latestReviewedUpstreamPatch": "https://github.com/get-convex/better-auth/commit/c628916b451a6b4cff0f5464f134475464b1a6da",
353
353
  "noticeRequired": false
354
354
  },
@@ -363,14 +363,25 @@
363
363
  "latestReviewedUpstreamPatch": "https://github.com/get-convex/better-auth/commit/c628916b451a6b4cff0f5464f134475464b1a6da",
364
364
  "noticeRequired": false
365
365
  },
366
+ {
367
+ "sourcePath": "src/auth-options.ts",
368
+ "targetPath": "src/runtime/convex-auth/rate-limit-storage.ts",
369
+ "sourceCommit": "c628916b451a6b4cff0f5464f134475464b1a6da",
370
+ "sourceSha256": "36b0218291f4b1ab577d2161a277e95fd62ce7e55e32e6f4d43ae41e001e7478",
371
+ "targetSha256": "36b67e5c5a4870a001fa98e3be72dc29e6a4d43c96f9dffa2ba629c15679f36e",
372
+ "classification": "rewritten",
373
+ "modificationSummary": "Added one package-owned Better Auth custom storage boundary that delegates the complete rate-limit decision and counter update to one atomic component mutation and retries only exact final uncommitted Convex contention from an action context with a fixed bound.",
374
+ "latestReviewedUpstreamPatch": "https://github.com/get-convex/better-auth/commit/c628916b451a6b4cff0f5464f134475464b1a6da",
375
+ "noticeRequired": false
376
+ },
366
377
  {
367
378
  "sourcePath": "src/plugins/convex/index.ts",
368
379
  "targetPath": "src/runtime/convex-auth/plugin.ts",
369
380
  "sourceCommit": "c628916b451a6b4cff0f5464f134475464b1a6da",
370
381
  "sourceSha256": "48ab81541ecb0e5545bd74fb78ca426c0d695bbb156e03e6a402063255ef428a",
371
- "targetSha256": "59f55ce50a43e094f90c1e9af4aa53a84f9082bcaf1e7f0f819bd0b02169bcb7",
382
+ "targetSha256": "a7852a3af404fb6d4b214646be6d49635b6044798a1e0d3d0f947a5f02ff3e0a",
372
383
  "classification": "rewritten",
373
- "modificationSummary": "Rewrote the Convex plugin as the composition, request-guard, and session-issuance owner; exact OAuth Provider compatibility and operator calls live behind fail-closed guards, while the fixed workforce profile adds provider request hooks and requires current full TOTP assurance before session-token issuance.",
384
+ "modificationSummary": "Rewrote the Convex plugin as the composition, request-guard, and session-issuance owner; exact OAuth Provider compatibility, package-owned atomic rate-limit storage, and operator calls live behind fail-closed guards, while the fixed workforce profile adds provider request hooks and requires current full TOTP assurance before session-token issuance.",
374
385
  "latestReviewedUpstreamPatch": "https://github.com/get-convex/better-auth/commit/c628916b451a6b4cff0f5464f134475464b1a6da",
375
386
  "noticeRequired": false
376
387
  },