Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 0 additions & 78 deletions .github/copilot-instructions.md

This file was deleted.

32 changes: 32 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# VitNode AI Coding Agent Guidelines (Extended)

The repository is a monorepo for the VitNode framework, which includes a backend API, frontend documentation site, and shared packages.

## Main Rules

- Use React 19.2, Next.js 16, TypeScript 5.9, Hono.js 4,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This list item has a trailing comma. For clarity and grammatical correctness in documentation, it should be removed.

Suggested change
- Use React 19.2, Next.js 16, TypeScript 5.9, Hono.js 4,
- Use React 19.2, Next.js 16, TypeScript 5.9, Hono.js 4

- Use pnpm as the runtime environment,
- Use Tailwind CSS 4 for styling,

## API

- Please use as much as possible server-side API calls (server components, server actions),
- For client API calls, use `tanstack/react-query` hooks,

## TypeScript

- Avoid using `any` type,
- Do not nest ternary operators,
- Avoid creating `index.ts` files for exports, instead use explicit file names,
- Use only `const` for create functions, but `function` for pages and generic functions,
- Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator,

## React

- Don't use `React.FC`, instead explicitly type props
- Don't use `useMemo` and `useCallback`. Project has React Compiler

## Animations

- Use `motion/react` for animations and gestures,
- Use `translate` instead of `top`/`left` for moving elements, as it is more performant
2 changes: 2 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@hono/node-ws": "^1.2.0",
"@hono/zod-openapi": "^1.1.4",
"@hono/zod-validator": "^0.7.4",
"@vitnode/core": "workspace:*",
"@vitnode/blog": "workspace:*",
"drizzle-kit": "^0.31.7",
"drizzle-orm": "^0.44.7",
"hono": "^4.10.6",
Expand Down
25 changes: 24 additions & 1 deletion apps/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { serve } from "@hono/node-server";
import { OpenAPIHono } from "@hono/zod-openapi";
import { VitNodeAPI } from "@vitnode/core/api/config";
import { createNodeWebSocket } from "@hono/node-ws";

import { vitNodeApiConfig } from "./vitnode.api.config.js";

Expand All @@ -11,7 +12,28 @@ VitNodeAPI({
vitNodeApiConfig,
});

serve(
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });

const wsApp = app.get(
"/ws",
upgradeWebSocket(c => ({
onOpen(event, ws) {
const user = c.get("user");
console.log("Connection opened", event, ws, user);
},
onMessage(event, ws) {
console.log(`Message from client`, event.data);
ws.send("Hello from server!");
},
onClose: () => {
console.log("Connection closed");
},
})),
Comment on lines +19 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The WebSocket event handlers use console.log for logging. For better observability and to align with the rest of the application, please use the structured logger available via c.get('log'). This will also prevent logging potentially large or sensitive objects like event and ws directly to the console.

Suggested change
upgradeWebSocket(c => ({
onOpen(event, ws) {
const user = c.get("user");
console.log("Connection opened", event, ws, user);
},
onMessage(event, ws) {
console.log(`Message from client`, event.data);
ws.send("Hello from server!");
},
onClose: () => {
console.log("Connection closed");
},
})),
upgradeWebSocket(c => ({
onOpen(event, ws) {
const user = c.get("user");
c.get("log").info({ userId: user?.id }, "WebSocket connection opened");
},
onMessage(event, ws) {
c.get("log").info({ data: event.data }, "Message from client");
ws.send("Hello from server!");
},
onClose: () => {
c.get("log").info("WebSocket connection closed");
},
}))

);

export type WebSocketApp = typeof wsApp;

const server = serve(
{
fetch: app.fetch,
port: 8080,
Expand All @@ -25,3 +47,4 @@ serve(
);
},
);
injectWebSocket(server);
3 changes: 2 additions & 1 deletion apps/api/src/vitnode.api.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { buildApiConfig } from "@vitnode/core/vitnode.config";
import { NodemailerEmailAdapter } from "@vitnode/nodemailer";
import { config } from "dotenv";
import { drizzle } from "drizzle-orm/postgres-js";
import { blogApiPlugin } from "@vitnode/blog/config.api";

config({
quiet: true,
Expand All @@ -11,7 +12,7 @@ export const POSTGRES_URL =
process.env.POSTGRES_URL ?? "postgresql://root:root@localhost:5432/vitnode";

export const vitNodeApiConfig = buildApiConfig({
plugins: [],
plugins: [blogApiPlugin()],
pathToMessages: async path => await import(`./locales/${path}`),
dbProvider: drizzle({
connection: POSTGRES_URL,
Expand Down
10 changes: 9 additions & 1 deletion apps/docs/content/docs/dev/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ icon: Power

## Get started

import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Tab, Tabs } from "fumadocs-ui/components/tabs";

<Tabs groupId='package-manager' persist items={['bun', 'pnpm', 'npm']} label='Create a Plugin'>

Expand All @@ -31,3 +31,11 @@ npx create-vitnode-app@canary
## Why VitNode?

something here

## Types of Applications

In VitNode, you can create different types of applications to suit your needs:

- **Single App**: A standalone application that includes both frontend and backend components in `Next.js` with `Hono.js` routes,
- **Monorepo App**: A monorepo structure that allows you to manage multiple packages within a single repository, including frontend and backend components,
- **Only API**: An application focused solely on backend API development without any frontend components using `Hono.js`.
1 change: 1 addition & 0 deletions apps/docs/content/docs/dev/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"email",
"sso",
"cron",
"websocket",
"---Frontend---",
"layouts-and-pages",
"admin-page",
Expand Down
28 changes: 14 additions & 14 deletions apps/docs/content/docs/dev/sso/custom-adapter.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ import { Callout } from "fumadocs-ui/components/callout";
Let's start with the basics. Create a new file for your SSO provider:

```ts title="src/utils/sso/discord_api.ts"
import { SSOApiPlugin, getRedirectUri } from "@vitnode/core/api/models/sso";
import { SSOApiAdapter, getRedirectUri } from "@vitnode/core/api/models/sso";

export const DiscordSSOApiPlugin = ({
export const DiscordSSOApiAdapter = ({
clientId,
clientSecret,
}: {
clientId: string;
clientSecret: string;
}): SSOApiPlugin => {
}): SSOApiAdapter => {
const id = "discord";
const redirectUri = getRedirectUri(id);

Expand All @@ -42,15 +42,15 @@ This is like creating a blueprint for your SSO provider. The `id` will be used i
Now let's add the magic that sends users to Discord for login:

```ts title="src/utils/sso/discord_api.ts"
import { SSOApiPlugin, getRedirectUri } from "@vitnode/core/api/models/sso";
import { SSOApiAdapter, getRedirectUri } from "@vitnode/core/api/models/sso";

export const DiscordSSOApiPlugin = ({
export const DiscordSSOApiAdapter = ({
clientId,
clientSecret,
}: {
clientId: string;
clientSecret: string;
}): SSOApiPlugin => {
}): SSOApiAdapter => {
const id = "discord";
const redirectUri = getRedirectUri(id);

Expand Down Expand Up @@ -93,7 +93,7 @@ export const DiscordSSOApiPlugin = ({
After the user approves access, Discord sends us a code. Let's exchange it for an access token:

```ts title="src/utils/sso/discord_api.ts"
import { SSOApiPlugin, getRedirectUri } from "@vitnode/core/api/models/sso";
import { SSOApiAdapter, getRedirectUri } from "@vitnode/core/api/models/sso";
import { HTTPException } from "hono/http-exception";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { z } from "zod";
Expand All @@ -103,13 +103,13 @@ const tokenSchema = z.object({
token_type: z.string(),
});

export const DiscordSSOApiPlugin = ({
export const DiscordSSOApiAdapter = ({
clientId,
clientSecret,
}: {
clientId: string;
clientSecret: string;
}): SSOApiPlugin => {
}): SSOApiAdapter => {
const id = "discord";
const redirectUri = getRedirectUri(id);

Expand Down Expand Up @@ -204,7 +204,7 @@ export const DiscordSSOApiPlugin = ({
Finally, let's get the user's profile data using our shiny new access token:

```ts title="src/utils/sso/discord_api.ts"
import { SSOApiPlugin, getRedirectUri } from "@vitnode/core/api/models/sso";
import { SSOApiAdapter, getRedirectUri } from "@vitnode/core/api/models/sso";
import { HTTPException } from "hono/http-exception";
import { ContentfulStatusCode } from "hono/utils/http-status";
import { z } from "zod";
Expand All @@ -215,13 +215,13 @@ const userSchema = z.object({
username: z.string(),
});

export const DiscordSSOApiPlugin = ({
export const DiscordSSOApiAdapter = ({
clientId,
clientSecret,
}: {
clientId: string;
clientSecret: string;
}): SSOApiPlugin => {
}): SSOApiAdapter => {
const id = "discord";
const redirectUri = getRedirectUri(id);

Expand Down Expand Up @@ -324,7 +324,7 @@ Last step! Let's plug your new SSO provider into your app:
import { OpenAPIHono } from "@hono/zod-openapi";
import { handle } from "hono/vercel";
import { VitNodeAPI } from "@vitnode/core/api/config";
import { DiscordSSOApiPlugin } from "@/utils/sso/discord_api";
import { DiscordSSOApiAdapter } from "@/utils/sso/discord_api";

const app = new OpenAPIHono().basePath("/api");
VitNodeAPI({
Expand All @@ -334,7 +334,7 @@ VitNodeAPI({
// [!code ++]
ssoAdapters: [
// [!code ++]
DiscordSSOApiPlugin({
DiscordSSOApiAdapter({
// [!code ++]
clientId: process.env.DISCORD_CLIENT_ID,
// [!code ++]
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/content/docs/dev/sso/discord.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Add the Discord SSO plugin to your API routes.

```ts title="src/app/api/[...route]/route.ts"
// [!code ++]
import { DiscordSSOApiPlugin } from '@vitnode/core/api/plugins/sso/discord';
import { DiscordSSOApiAdapter } from "@vitnode/core/api/plugins/sso/discord";

VitNodeAPI({
app,
Expand All @@ -68,7 +68,7 @@ VitNodeAPI({
// [!code ++]
ssoAdapters: [
// [!code ++]
new DiscordSSOApiPlugin({
new DiscordSSOApiAdapter({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The DiscordSSOApiAdapter is a factory function, not a class constructor. Using the new keyword here is incorrect and will result in a TypeError. Please remove new to correctly call the function.

      DiscordSSOApiAdapter({

// [!code ++]
clientId: process.env.DISCORD_CLIENT_ID,
// [!code ++]
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/content/docs/dev/sso/facebook.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Add the Facebook SSO plugin to your API routes.

```ts title="src/app/api/[...route]/route.ts"
// [!code ++]
import { FacebookSSOApiPlugin } from '@vitnode/core/api/plugins/sso/facebook';
import { FacebookSSOApiAdapter } from '@vitnode/core/api/plugins/sso/facebook';

VitNodeAPI({
app,
Expand All @@ -62,7 +62,7 @@ VitNodeAPI({
// [!code ++]
ssoAdapters: [
// [!code ++]
new FacebookSSOApiPlugin({
new FacebookSSOApiAdapter({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The FacebookSSOApiAdapter is a factory function, not a class constructor. Using the new keyword here is incorrect and will result in a TypeError. Please remove new to correctly call the function.

      FacebookSSOApiAdapter({

// [!code ++]
clientId: process.env.FACEBOOK_CLIENT_ID,
// [!code ++]
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/content/docs/dev/sso/google.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Add the Discord SSO plugin to your API routes.

```ts title="src/app/api/[...route]/route.ts"
// [!code ++]
import { GoogleSSOApiPlugin } from '@vitnode/core/api/plugins/sso/google';
import { GoogleSSOApiAdapter } from "@vitnode/core/api/plugins/sso/google";

VitNodeAPI({
app,
Expand All @@ -123,7 +123,7 @@ VitNodeAPI({
// [!code ++]
ssoAdapters: [
// [!code ++]
new GoogleSSOApiPlugin({
new GoogleSSOApiAdapter({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The GoogleSSOApiAdapter is a factory function, not a class constructor. Using the new keyword here is incorrect and will result in a TypeError. Please remove new to correctly call the function.

      GoogleSSOApiAdapter({

// [!code ++]
clientId: process.env.GOOGLE_CLIENT_ID,
// [!code ++]
Expand Down
6 changes: 6 additions & 0 deletions apps/docs/content/docs/dev/websocket/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: WebSocket
description: xx
---

test
4 changes: 4 additions & 0 deletions apps/docs/content/docs/dev/websocket/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "WebSocket",
"pages": ["..."]
}
Loading
Loading