diff --git a/apps/docs/content/docs/dev/auth.mdx b/apps/docs/content/docs/dev/config/auth.mdx
similarity index 97%
rename from apps/docs/content/docs/dev/auth.mdx
rename to apps/docs/content/docs/dev/config/auth.mdx
index 686c371cb..83e5104be 100644
--- a/apps/docs/content/docs/dev/auth.mdx
+++ b/apps/docs/content/docs/dev/config/auth.mdx
@@ -13,9 +13,7 @@ You can configure the authentication settings in the `VitNodeAPI` function and `
For example you can change the expiration time of the cookie:
-import { TypeTable } from 'fumadocs-ui/components/type-table';
-
-```ts title="src/app/api/[...route]/route.ts"
+```ts title="src/vitnode.api.config.ts"
VitNodeAPI({
app,
plugins: [],
@@ -28,6 +26,8 @@ VitNodeAPI({
});
```
+import { TypeTable } from 'fumadocs-ui/components/type-table';
+
### Options
diff --git a/apps/docs/content/docs/dev/deployments/cloud/meta.json b/apps/docs/content/docs/dev/deployments/cloud/meta.json
new file mode 100644
index 000000000..9b083a7d9
--- /dev/null
+++ b/apps/docs/content/docs/dev/deployments/cloud/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Cloud",
+ "pages": ["..."]
+}
diff --git a/apps/docs/content/docs/dev/deployments/meta.json b/apps/docs/content/docs/dev/deployments/meta.json
new file mode 100644
index 000000000..35ccfdba5
--- /dev/null
+++ b/apps/docs/content/docs/dev/deployments/meta.json
@@ -0,0 +1,5 @@
+{
+ "title": "Deployments",
+ "icon": "HardDriveUpload",
+ "pages": ["..."]
+}
diff --git a/apps/docs/content/docs/dev/deployments/self-hosted/meta.json b/apps/docs/content/docs/dev/deployments/self-hosted/meta.json
new file mode 100644
index 000000000..5b6b3ae79
--- /dev/null
+++ b/apps/docs/content/docs/dev/deployments/self-hosted/meta.json
@@ -0,0 +1,4 @@
+{
+ "title": "Self-Hosted",
+ "pages": ["..."]
+}
diff --git a/apps/docs/content/docs/dev/meta.json b/apps/docs/content/docs/dev/meta.json
index 990412e35..8e654a7bb 100644
--- a/apps/docs/content/docs/dev/meta.json
+++ b/apps/docs/content/docs/dev/meta.json
@@ -6,11 +6,9 @@
"pages": [
"index",
"contribution",
- "structure",
"debugging",
"swagger",
- "---Framework---",
- "auth",
+ "deployments",
"---Plugins---",
"plugins",
"rest-api",
@@ -20,6 +18,8 @@
"layouts-and-pages",
"admin-page",
"i18n",
+ "---Framework---",
+ "config",
"---Advanced---",
"..."
]
diff --git a/apps/docs/src/app/global.css b/apps/docs/src/app/global.css
index a3f1e1bf4..83d5e327d 100644
--- a/apps/docs/src/app/global.css
+++ b/apps/docs/src/app/global.css
@@ -22,8 +22,8 @@
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
+ --border: oklch(0.9 0 0);
+ --input: oklch(0.9 0 0);
--ring: oklch(0.7 0.16 262.61);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
@@ -45,22 +45,23 @@
}
.dark {
- --background: oklch(0.12 0 0);
- --foreground: oklch(0.98 0.005 240);
- --card: oklch(0.18 0.005 240);
- --card-foreground: oklch(0.98 0.005 240);
- --popover: oklch(0.18 0.005 240);
- --popover-foreground: oklch(0.98 0.005 240);
+ --background: oklch(0.14 0 0);
+ --foreground: oklch(0.98 0 0);
+ --card: oklch(0.18 0 0);
+ --card-foreground: oklch(0.98 0 0);
+ --popover: oklch(0.18 0 0);
+ --popover-foreground: oklch(0.98 0 0);
--primary: oklch(0.51 0.16 262.61);
- --primary-foreground: oklch(0.99 0.005 240);
+ --primary-foreground: oklch(0.99 0.16 262.61);
--secondary: oklch(0.24 0.01 240);
--secondary-foreground: oklch(0.98 0.005 240);
- --muted: oklch(0.22 0.01 240);
+ --muted: oklch(0.22 0 0);
--muted-foreground: oklch(0.7 0 0);
- --accent: oklch(0.28 0.015 240);
+ --accent: oklch(0.28 0 0);
--destructive: oklch(0.704 0.191 22.216);
- --border: oklch(0.269 0.005 240);
- --input: oklch(0.269 0.005 240);
+ --destructive-foreground: oklch(0.704 0.191 22.216);
+ --border: oklch(0.24 0 0);
+ --input: oklch(0.24 0 0);
--ring: oklch(0.51 0.16 262.61);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
@@ -68,12 +69,12 @@
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: var(--card);
- --sidebar-foreground: oklch(0.98 0.005 240);
+ --sidebar-foreground: oklch(0.98 0 0);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
- --sidebar-accent: oklch(0.22 0.005 240);
- --sidebar-accent-foreground: oklch(0.98 0.005 240);
- --sidebar-border: oklch(0.269 0.005 240);
+ --sidebar-accent: oklch(0.22 0 0);
+ --sidebar-accent-foreground: oklch(0.98 0 0);
+ --sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.51 0.16 262.61);
--dev-color: oklch(0.75 0.18 50);
diff --git a/apps/web/src/app/global.css b/apps/web/src/app/global.css
index 957e4f23c..1457f4a03 100644
--- a/apps/web/src/app/global.css
+++ b/apps/web/src/app/global.css
@@ -24,8 +24,8 @@
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
- --border: oklch(0.922 0 0);
- --input: oklch(0.922 0 0);
+ --border: oklch(0.9 0 0);
+ --input: oklch(0.9 0 0);
--ring: oklch(0.7 0.16 262.61);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
@@ -43,22 +43,23 @@
}
.dark {
- --background: oklch(0.12 0 0);
- --foreground: oklch(0.98 0.005 240);
- --card: oklch(0.18 0.005 240);
- --card-foreground: oklch(0.98 0.005 240);
- --popover: oklch(0.18 0.005 240);
- --popover-foreground: oklch(0.98 0.005 240);
+ --background: oklch(0.14 0 0);
+ --foreground: oklch(0.98 0 0);
+ --card: oklch(0.18 0 0);
+ --card-foreground: oklch(0.98 0 0);
+ --popover: oklch(0.18 0 0);
+ --popover-foreground: oklch(0.98 0 0);
--primary: oklch(0.51 0.16 262.61);
- --primary-foreground: oklch(0.99 0.005 240);
+ --primary-foreground: oklch(0.99 0.16 262.61);
--secondary: oklch(0.24 0.01 240);
--secondary-foreground: oklch(0.98 0.005 240);
- --muted: oklch(0.22 0.01 240);
+ --muted: oklch(0.22 0 0);
--muted-foreground: oklch(0.7 0 0);
- --accent: oklch(0.28 0.015 240);
+ --accent: oklch(0.28 0 0);
--destructive: oklch(0.704 0.191 22.216);
- --border: oklch(0.269 0.005 240);
- --input: oklch(0.269 0.005 240);
+ --destructive-foreground: oklch(0.704 0.191 22.216);
+ --border: oklch(0.24 0 0);
+ --input: oklch(0.24 0 0);
--ring: oklch(0.51 0.16 262.61);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
@@ -66,12 +67,12 @@
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: var(--card);
- --sidebar-foreground: oklch(0.98 0.005 240);
+ --sidebar-foreground: oklch(0.98 0 0);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
- --sidebar-accent: oklch(0.22 0.005 240);
- --sidebar-accent-foreground: oklch(0.98 0.005 240);
- --sidebar-border: oklch(0.269 0.005 240);
+ --sidebar-accent: oklch(0.22 0 0);
+ --sidebar-accent-foreground: oklch(0.98 0 0);
+ --sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.51 0.16 262.61);
}
diff --git a/apps/web/src/vitnode.api.config.ts b/apps/web/src/vitnode.api.config.ts
index 748a4fa60..937195701 100644
--- a/apps/web/src/vitnode.api.config.ts
+++ b/apps/web/src/vitnode.api.config.ts
@@ -1,8 +1,8 @@
import { blogApiPlugin } from '@vitnode/blog/config.api';
-import { NodemailerEmailPlugin } from '@vitnode/core/api/plugins/email/nodemailer';
-import { DiscordSSOApiPlugin } from '@vitnode/core/api/plugins/sso/discord';
-import { FacebookSSOApiPlugin } from '@vitnode/core/api/plugins/sso/facebook';
-import { GoogleSSOApiPlugin } from '@vitnode/core/api/plugins/sso/google';
+import { NodemailerEmailAdapter } from '@vitnode/core/api/adapters/email/nodemailer';
+import { DiscordSSOApiPlugin } from '@vitnode/core/api/adapters/sso/discord';
+import { FacebookSSOApiPlugin } from '@vitnode/core/api/adapters/sso/facebook';
+import { GoogleSSOApiPlugin } from '@vitnode/core/api/adapters/sso/google';
import { buildApiConfig } from '@vitnode/core/vitnode.config';
import * as dotenv from 'dotenv';
import { drizzle } from 'drizzle-orm/postgres-js';
@@ -22,12 +22,16 @@ export const vitNodeApiConfig = buildApiConfig({
connection: POSTGRES_URL,
casing: 'camelCase',
}),
- // emailProvider: NodemailerEmailPlugin({
- // from: process.env.NODE_MAILER_FROM,
- // host: process.env.NODE_MAILER_HOST,
- // password: process.env.NODE_MAILER_PASSWORD,
- // user: process.env.NOD_EMAILER_USER,
- // }),
+ rateLimiter: {
+ points: 20, // 20 requests
+ duration: 60, // per 60 seconds
+ },
+ emailAdapter: NodemailerEmailAdapter({
+ from: process.env.NODE_MAILER_FROM,
+ host: process.env.NODE_MAILER_HOST,
+ password: process.env.NODE_MAILER_PASSWORD,
+ user: process.env.NOD_EMAILER_USER,
+ }),
authorization: {
ssoProviders: [
DiscordSSOApiPlugin({
diff --git a/packages/vitnode/package.json b/packages/vitnode/package.json
index 611ea329c..8bbbb9b57 100644
--- a/packages/vitnode/package.json
+++ b/packages/vitnode/package.json
@@ -19,21 +19,21 @@
"core"
],
"peerDependencies": {
+ "@hono/zod-openapi": "0.19.x",
+ "@swc/cli": "0.6.x",
+ "@swc/core": "1.12.x",
+ "@types/react": "19.1.x",
+ "@types/react-dom": "19.1.x",
"drizzle-kit": "0.31.x",
"drizzle-orm": "^0.44.x",
"hono": "4.8.x",
"next": "15.3.x",
+ "next-intl": "4.1.x",
"react": "19.1.x",
"react-dom": "19.1.x",
- "@swc/cli": "0.6.x",
- "@hono/zod-openapi": "0.19.x",
- "next-intl": "4.1.x",
"react-hook-form": "^7.0.0",
- "zod": "3.25.x",
- "@swc/core": "1.12.x",
- "@types/react": "19.1.x",
- "@types/react-dom": "19.1.x",
- "typescript": "^5.8.x"
+ "typescript": "^5.8.x",
+ "zod": "3.25.x"
},
"devDependencies": {
"@hono/zod-openapi": "^0.19.8",
@@ -119,6 +119,7 @@
"nodemailer": "^7.0.3",
"postgres": "^3.4.7",
"radix-ui": "^1.4.2",
+ "rate-limiter-flexible": "^7.1.1",
"react-scan": "^0.3.4",
"resend": "^4.6.0",
"tailwind-merge": "^3.3.1",
diff --git a/packages/vitnode/src/api/plugins/email/nodemailer.ts b/packages/vitnode/src/api/adapters/email/nodemailer.ts
similarity index 96%
rename from packages/vitnode/src/api/plugins/email/nodemailer.ts
rename to packages/vitnode/src/api/adapters/email/nodemailer.ts
index 24ca59b41..57e856203 100644
--- a/packages/vitnode/src/api/plugins/email/nodemailer.ts
+++ b/packages/vitnode/src/api/adapters/email/nodemailer.ts
@@ -2,7 +2,7 @@ import { createTransport } from 'nodemailer';
import type { EmailApiPlugin } from '@/api/models/email';
-export const NodemailerEmailPlugin = ({
+export const NodemailerEmailAdapter = ({
host = '',
port = 587,
secure = false,
diff --git a/packages/vitnode/src/api/plugins/email/resend.ts b/packages/vitnode/src/api/adapters/email/resend.ts
similarity index 95%
rename from packages/vitnode/src/api/plugins/email/resend.ts
rename to packages/vitnode/src/api/adapters/email/resend.ts
index d14fa6cfd..26dd0986d 100644
--- a/packages/vitnode/src/api/plugins/email/resend.ts
+++ b/packages/vitnode/src/api/adapters/email/resend.ts
@@ -2,7 +2,7 @@ import { Resend } from 'resend';
import type { EmailApiPlugin } from '@/api/models/email';
-export const ResendEmailPlugin = ({
+export const ResendEmailAdapter = ({
apiKey,
from,
}: {
diff --git a/packages/vitnode/src/api/plugins/sso/discord.ts b/packages/vitnode/src/api/adapters/sso/discord.ts
similarity index 100%
rename from packages/vitnode/src/api/plugins/sso/discord.ts
rename to packages/vitnode/src/api/adapters/sso/discord.ts
diff --git a/packages/vitnode/src/api/plugins/sso/facebook.ts b/packages/vitnode/src/api/adapters/sso/facebook.ts
similarity index 100%
rename from packages/vitnode/src/api/plugins/sso/facebook.ts
rename to packages/vitnode/src/api/adapters/sso/facebook.ts
diff --git a/packages/vitnode/src/api/plugins/sso/google.ts b/packages/vitnode/src/api/adapters/sso/google.ts
similarity index 100%
rename from packages/vitnode/src/api/plugins/sso/google.ts
rename to packages/vitnode/src/api/adapters/sso/google.ts
diff --git a/packages/vitnode/src/api/config.ts b/packages/vitnode/src/api/config.ts
index 719424908..13511351f 100644
--- a/packages/vitnode/src/api/config.ts
+++ b/packages/vitnode/src/api/config.ts
@@ -14,7 +14,8 @@ import { CONFIG_PLUGIN } from '@/config';
import {
globalAdminMiddleware,
globalMiddleware,
-} from './middlewares/global/global';
+} from './middlewares/global.middleware';
+import { rateLimiterMiddleware } from './middlewares/rate-limiter.middleware';
interface CORSOptions {
allowHeaders?: string[];
@@ -55,11 +56,12 @@ export function VitNodeAPI({
});
app.use(cors(corsOptions));
app.use(csrf(csrfOptions));
+ app.use('*', rateLimiterMiddleware(vitNodeApiConfig.rateLimiter));
app.get('/swagger', swaggerUI({ url: `/api/swagger/doc` }));
app.use(
'*',
globalMiddleware({
- emailProvider: vitNodeApiConfig.emailProvider,
+ emailAdapter: vitNodeApiConfig.emailAdapter,
metadata: vitNodeConfig.metadata,
authorization: vitNodeApiConfig.authorization,
dbProvider: vitNodeApiConfig.dbProvider,
diff --git a/packages/vitnode/src/api/lib/route.ts b/packages/vitnode/src/api/lib/route.ts
index 0854aedd2..a63e72d0b 100644
--- a/packages/vitnode/src/api/lib/route.ts
+++ b/packages/vitnode/src/api/lib/route.ts
@@ -5,7 +5,7 @@ import { createRoute as createRouteHono } from '@hono/zod-openapi';
import {
type EnvVitNode,
pluginMiddleware,
-} from '../middlewares/global/global';
+} from '../middlewares/global.middleware';
type RoutingPath
=
P extends `${infer Head}/{${infer Param}}${infer Tail}`
diff --git a/packages/vitnode/src/api/middlewares/global/global.ts b/packages/vitnode/src/api/middlewares/global.middleware.ts
similarity index 95%
rename from packages/vitnode/src/api/middlewares/global/global.ts
rename to packages/vitnode/src/api/middlewares/global.middleware.ts
index bad5dec0f..1edd9c5d8 100644
--- a/packages/vitnode/src/api/middlewares/global/global.ts
+++ b/packages/vitnode/src/api/middlewares/global.middleware.ts
@@ -8,7 +8,7 @@ import type { VitNodeApiConfig, VitNodeConfig } from '@/vitnode.config';
import { SessionModel } from '@/api/models/session';
import { SessionAdminModel } from '@/api/models/session-admin';
-import type { SSOApiPlugin } from '../../models/sso';
+import type { SSOApiPlugin } from '../models/sso';
export interface EnvVitNode extends Env {
Variables: EnvVariablesVitNode;
@@ -40,7 +40,7 @@ interface EnvVariablesVitNode {
deviceCookieName: string;
ssoProviders: SSOApiPlugin[];
};
- emailProvider?: EmailApiPlugin;
+ emailAdapter?: EmailApiPlugin;
metadata: {
shortTitle?: string;
title: string;
@@ -72,16 +72,16 @@ declare module 'hono' {
export const globalMiddleware = ({
authorization,
metadata,
- emailProvider,
+ emailAdapter,
dbProvider,
-}: Pick &
+}: Pick &
Pick) => {
return async (c: Context, next: Next) => {
c.set('db', dbProvider);
c.set('core', {
metadata,
- emailProvider,
+ emailAdapter,
authorization: {
cookieName: authorization?.cookieName ?? 'vitnode_auth',
cookie_expires:
diff --git a/packages/vitnode/src/api/middlewares/rate-limiter.middleware.ts b/packages/vitnode/src/api/middlewares/rate-limiter.middleware.ts
new file mode 100644
index 000000000..9d60ea859
--- /dev/null
+++ b/packages/vitnode/src/api/middlewares/rate-limiter.middleware.ts
@@ -0,0 +1,45 @@
+import type { Context, Next } from 'hono';
+
+import {
+ type IRateLimiterOptions,
+ type RateLimiterAbstract,
+ RateLimiterMemory,
+} from 'rate-limiter-flexible';
+
+const createRateLimiter = ({
+ keyPrefix,
+ ...options
+}: {
+ keyPrefix: string;
+} & Omit): RateLimiterAbstract => {
+ return new RateLimiterMemory({
+ keyPrefix,
+ points: options?.points ?? 20, // 20 requests
+ duration: options?.duration ?? 60, // per 60 seconds
+ ...options,
+ });
+};
+
+export const rateLimiterMiddleware = (
+ options?: Omit,
+) => {
+ const rateLimiter = createRateLimiter({
+ ...options,
+ keyPrefix: 'vitnode-api-rate-limiter',
+ });
+
+ return async (c: Context, next: Next) => {
+ const key =
+ c.req.header('x-forwarded-for') ??
+ c.req.raw.headers.get('x-real-ip') ??
+ '127.0.0.1';
+
+ try {
+ await rateLimiter.consume(key);
+
+ await next();
+ } catch {
+ return c.text('Too Many Requests', 429);
+ }
+ };
+};
diff --git a/packages/vitnode/src/api/models/email.ts b/packages/vitnode/src/api/models/email.ts
index 549c851e8..d0eb6f956 100644
--- a/packages/vitnode/src/api/models/email.ts
+++ b/packages/vitnode/src/api/models/email.ts
@@ -19,10 +19,6 @@ export class EmailModel {
protected readonly c: Context;
- isAvailable() {
- return !!this.c.get('core').emailProvider;
- }
-
async send(args: {
html: string;
replyTo?: string;
@@ -30,7 +26,7 @@ export class EmailModel {
to: string;
}) {
const core = this.c.get('core');
- const provider = core.emailProvider;
+ const provider = core.emailAdapter;
if (!provider) {
throw new HTTPException(500, {
message: 'Email provider not found',
diff --git a/packages/vitnode/src/api/models/user/sign-up.ts b/packages/vitnode/src/api/models/user/sign-up.ts
index 4907ae6e1..d5f3a6615 100644
--- a/packages/vitnode/src/api/models/user/sign-up.ts
+++ b/packages/vitnode/src/api/models/user/sign-up.ts
@@ -60,7 +60,7 @@ const getDefaultData = async (
return {
roleId: defaultRole.id,
- emailVerified: !c.get('core').emailProvider,
+ emailVerified: !c.get('core').emailAdapter,
};
};
diff --git a/packages/vitnode/src/api/modules/middleware/route.ts b/packages/vitnode/src/api/modules/middleware/route.ts
index e8f33690a..985038652 100644
--- a/packages/vitnode/src/api/modules/middleware/route.ts
+++ b/packages/vitnode/src/api/modules/middleware/route.ts
@@ -1,7 +1,6 @@
import { z } from 'zod';
import { buildRoute } from '@/api/lib/route';
-import { EmailModel } from '@/api/models/email';
import { CONFIG_PLUGIN } from '@/config';
export const routeMiddleware = buildRoute({
@@ -26,10 +25,9 @@ export const routeMiddleware = buildRoute({
},
handler: c => {
const sso = c.get('core').authorization.ssoProviders;
- const email = new EmailModel(c);
return c.json({
- isEmail: email.isAvailable(),
+ isEmail: !!c.get('core').emailAdapter,
sso: sso.map(s => ({ id: s.id, name: s.name })),
});
},
diff --git a/packages/vitnode/src/api/modules/users/routes/test.route.ts b/packages/vitnode/src/api/modules/users/routes/test.route.ts
index e441a8e5b..20b03143c 100644
--- a/packages/vitnode/src/api/modules/users/routes/test.route.ts
+++ b/packages/vitnode/src/api/modules/users/routes/test.route.ts
@@ -2,6 +2,7 @@ import { z } from 'zod';
import { buildRoute } from '@/api/lib/route';
import { CONFIG_PLUGIN } from '@/config';
+// import { EmailModel } from '../../../models/email';
export const testRoute = buildRoute({
...CONFIG_PLUGIN,
@@ -29,6 +30,12 @@ export const testRoute = buildRoute({
},
},
handler: c => {
+ // await new EmailModel(c).send({
+ // html: 'Test email
',
+ // to: 'ithereplay@gmail.com',
+ // subject: 'Test Email',
+ // });
+
return c.text('test');
},
});
diff --git a/packages/vitnode/src/vitnode.config.ts b/packages/vitnode/src/vitnode.config.ts
index 18ece55a0..cca032094 100644
--- a/packages/vitnode/src/vitnode.config.ts
+++ b/packages/vitnode/src/vitnode.config.ts
@@ -1,5 +1,6 @@
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
import type { ThemeProvider } from 'next-themes';
+import type { IRateLimiterOptions } from 'rate-limiter-flexible';
import type { BuildPluginApiReturn } from './api/lib/plugin';
import type { EmailApiPlugin } from './api/models/email';
@@ -47,8 +48,9 @@ export interface VitNodeApiConfig {
ssoProviders?: SSOApiPlugin[];
};
dbProvider: PostgresJsDatabase;
- emailProvider?: EmailApiPlugin;
+ emailAdapter?: EmailApiPlugin;
plugins: BuildPluginApiReturn[];
+ rateLimiter?: Omit;
}
export function buildConfig(
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 300124b94..e106e5f01 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -134,7 +134,7 @@ importers:
version: 15.3.4(@playwright/test@1.53.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next-intl:
specifier: ^4.1.0
- version: 4.1.0(next@15.3.4(@playwright/test@1.53.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
+ version: 4.1.0(next@15.3.4(@playwright/test@1.53.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
react:
specifier: ^19.1.0
version: 19.1.0
@@ -306,6 +306,9 @@ importers:
radix-ui:
specifier: ^1.4.2
version: 1.4.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ rate-limiter-flexible:
+ specifier: ^7.1.1
+ version: 7.1.1
react-scan:
specifier: ^0.3.4
version: 0.3.4(@types/react@19.1.8)(next@15.3.4(@babel/core@7.27.4)(@playwright/test@1.53.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(rollup@4.43.0)
@@ -465,7 +468,7 @@ importers:
version: 15.3.4(@playwright/test@1.53.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next-intl:
specifier: ^4.1.0
- version: 4.1.0(next@15.3.4(@playwright/test@1.53.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
+ version: 4.1.0(next@15.3.4(@playwright/test@1.53.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
react:
specifier: ^19.1.0
version: 19.1.0
@@ -5522,6 +5525,9 @@ packages:
'@types/react-dom':
optional: true
+ rate-limiter-flexible@7.1.1:
+ resolution: {integrity: sha512-lsYRcqRSJrKBNt6pMzBJTiCJP5KnwsGWdObMZxd19JFUJRntM+yuHs4/2bs6NZweSLgpsDcykvzyQaumoslWQg==}
+
react-dom@19.1.0:
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
peerDependencies:
@@ -11538,7 +11544,7 @@ snapshots:
optionalDependencies:
typescript: 5.8.3
- next-intl@4.1.0(next@15.3.4(@playwright/test@1.53.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3):
+ next-intl@4.1.0(next@15.3.4(@playwright/test@1.53.0)(babel-plugin-react-compiler@19.1.0-rc.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(typescript@5.8.3):
dependencies:
'@formatjs/intl-localematcher': 0.5.10
negotiator: 1.0.0
@@ -12032,6 +12038,8 @@ snapshots:
'@types/react': 19.1.8
'@types/react-dom': 19.1.6(@types/react@19.1.8)
+ rate-limiter-flexible@7.1.1: {}
+
react-dom@19.1.0(react@19.1.0):
dependencies:
react: 19.1.0