diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8476ab5e..21539301 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -425,6 +425,7 @@ jobs: | @fedify/express | ${{ steps.versioning.outputs.version }} | [JSR][jsr:@fedify/express] | [npm][npm:@fedify/express] | | @fedify/h3 | ${{ steps.versioning.outputs.version }} | [JSR][jsr:@fedify/h3] | [npm][npm:@fedify/h3] | | @fedify/nestjs | ${{ steps.versioning.outputs.version }} | | [npm][npm:@fedify/nestjs] | + | @fedify/next | ${{ steps.versioning.outputs.version }} | | [npm][npm:@fedify/next] | | @fedify/postgres | ${{ steps.versioning.outputs.version }} | [JSR][jsr:@fedify/postgres] | [npm][npm:@fedify/postgres] | | @fedify/redis | ${{ steps.versioning.outputs.version }} | [JSR][jsr:@fedify/redis] | [npm][npm:@fedify/redis] | | @fedify/sqlite | ${{ steps.versioning.outputs.version }} | [JSR][jsr:@fedify/sqlite] | [npm][npm:@fedify/sqlite] | diff --git a/.gitignore b/.gitignore index fb51c556..d73e0aea 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ node_modules/ repomix-output.xml t.ts t2.ts +.pnpm-store/ diff --git a/AGENTS.md b/AGENTS.md index a37dec4a..d8a19f66 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,6 +70,7 @@ The repository is organized as a monorepo with the following packages: - *packages/postgres/*: PostgreSQL drivers (@fedify/postgres) - *packages/redis/*: Redis drivers (@fedify/redis) - *packages/nestjs/*: NestJS integration (@fedify/nestjs) + - *packages/next/*: Next.js integration (@fedify/next) - *packages/sqlite/*: SQLite driver (@fedify/sqlite) - *packages/testing/*: Testing utilities (@fedify/testing) - *docs/*: Documentation built with Node.js and VitePress diff --git a/CHANGES.md b/CHANGES.md index 05899504..2233a690 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,21 @@ Version 1.9.0 To be released. +### @fedify/next + + - Created [Next.js] integration as the *@fedify/next* package. + [[#313] by Chanhaeng Lee] + +### @fedify/cli + + - Added `Next.js` option to `fedify init` command. This option allows users + to initialize a new Fedify project with Next.js integration. + [[#313] by Chanhaeng Lee] + + +[Next.js]: https://nextjs.org/ +[#313]: https://github.com/fedify-dev/fedify/issues/313 + Version 1.8.5 ------------- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc40970f..3be9b65e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -193,6 +193,7 @@ The repository is organized as a monorepo with the following packages: - *packages/postgres/*: PostgreSQL drivers (@fedify/postgres) for Fedify. - *packages/redis/*: Redis drivers (@fedify/redis) for Fedify. - *packages/nestjs/*: NestJS integration (@fedify/nestjs) for Fedify. + - *packages/next/*: Next.js integration (@fedify/next) for Fedify. - *packages/sqlite/*: SQLite driver (@fedify/sqlite) for Fedify. - *packages/testing/*: Testing utilities (@fedify/testing) for Fedify. - *docs/*: The Fedify docs. The docs are built with [Node.js] and diff --git a/docs/manual/integration.md b/docs/manual/integration.md index 8a06aa44..ea3da775 100644 --- a/docs/manual/integration.md +++ b/docs/manual/integration.md @@ -480,6 +480,81 @@ console.log("Elysia App Start!"); [Elysia]: https://elysiajs.com/ +Next.js +------- + +*This API is available since Fedify 1.9.0.* + +[Next.js] is a React framework that enables you to build server-rendered +and statically generated web applications. Fedify has the `@fedify/next` +module that provides a middleware to integrate Fedify with Next.js. Create +an app with the following command using the Fedify CLI: + +~~~~ sh +fedify init my-next-app + +? Choose the JavaScript runtime to use › Node.js +? Choose the package manager to use › npm +? Choose the web framework to integrate Fedify with › Next.js +? Choose the key–value store to use for caching › In-memory +? Choose the message queue to use for background jobs › In-process +✔ Would you like your code inside a `src/` directory? … No +✔ Would you like to customize the import alias (`@/*` by default)? … No +~~~~ + +Then you can see the Next.js boilerplate code in the `my-next-app` directory. +But if you created a Next.js app with `create-next-app` before, you'll see +some differences in the code. There is a `middleware.ts` file in the +`my-next-app` directory, which is the entry point to the Fedify middleware +from the Next.js framework: + + +~~~~ typescript +import { fedifyWith } from "@fedify/next"; +import federation from "./federation"; + +export default fedifyWith(federation)( +/* + function (request: Request) { + // If you need to handle other requests besides federation + // requests in middleware, you can do it here. + // If you handle only federation requests in middleware, + // you don't need this function. + return NextResponse.next(); + }, +*/ +) + +// This config needs because middleware process only requests with the +// "Accept" header matching the federation accept regex. +// More details: https://nextjs.org/docs/app/api-reference/file-conventions/middleware#config-object-optional +export const config = { + runtime: "nodejs", + matcher: [{ + source: "/:path*", + has: [ + { + type: "header", + key: "Accept", + value: ".*application\\\\/((jrd|activity|ld)\\\\+json|xrd\\\\+xml).*", + }, + ], + }], +}; +~~~~ + +As you can see in the comment, you can handle other requests besides +federation requests in the middleware. If you handle only federation requests +in the middleware, you can omit the function argument of `fedifyWith()`. +The `config` object is necessary to let Next.js know that the middleware +should process requests with the `Accept` header matching the federation +accept regex. This is because Next.js middleware processes only requests +with the `Accept` header matching the regex by default. More details can be found in the Next.js official documentation [`config` in `middleware.js`]. + +[Next.js]: https://nextjs.org/ +[`config` in `middleware.js`]: https://nextjs.org/docs/app/api-reference/file-conventions/middleware#config-object-optional + + Custom middleware ----------------- diff --git a/docs/package.json b/docs/package.json index 52fa6bee..40a872a7 100644 --- a/docs/package.json +++ b/docs/package.json @@ -8,6 +8,7 @@ "@fedify/fedify": "workspace:", "@fedify/h3": "workspace:", "@fedify/nestjs": "workspace:", + "@fedify/next": "workspace:", "@fedify/postgres": "workspace:", "@fedify/redis": "workspace:", "@fedify/sqlite": "workspace:", diff --git a/examples/next15-app-router/app/.well-known/[[...catchAll]]/route.ts b/examples/next15-app-router/app/.well-known/[[...catchAll]]/route.ts index 3591c4d7..267ec455 100644 --- a/examples/next15-app-router/app/.well-known/[[...catchAll]]/route.ts +++ b/examples/next15-app-router/app/.well-known/[[...catchAll]]/route.ts @@ -1,9 +1,9 @@ -import { fedifyRequestHanlder } from "~/shared/integrate-fedify"; +import { fedifyRequestHandler } from "~/shared/integrate-fedify"; export { - fedifyRequestHanlder as DELETE, - fedifyRequestHanlder as GET, - fedifyRequestHanlder as PATCH, - fedifyRequestHanlder as POST, - fedifyRequestHanlder as PUT, + fedifyRequestHandler as DELETE, + fedifyRequestHandler as GET, + fedifyRequestHandler as PATCH, + fedifyRequestHandler as POST, + fedifyRequestHandler as PUT, }; diff --git a/examples/next15-app-router/app/fedify-activity-handler/[[...catchAll]]/route.ts b/examples/next15-app-router/app/fedify-activity-handler/[[...catchAll]]/route.ts index 3591c4d7..267ec455 100644 --- a/examples/next15-app-router/app/fedify-activity-handler/[[...catchAll]]/route.ts +++ b/examples/next15-app-router/app/fedify-activity-handler/[[...catchAll]]/route.ts @@ -1,9 +1,9 @@ -import { fedifyRequestHanlder } from "~/shared/integrate-fedify"; +import { fedifyRequestHandler } from "~/shared/integrate-fedify"; export { - fedifyRequestHanlder as DELETE, - fedifyRequestHanlder as GET, - fedifyRequestHanlder as PATCH, - fedifyRequestHanlder as POST, - fedifyRequestHanlder as PUT, + fedifyRequestHandler as DELETE, + fedifyRequestHandler as GET, + fedifyRequestHandler as PATCH, + fedifyRequestHandler as POST, + fedifyRequestHandler as PUT, }; diff --git a/examples/next15-app-router/shared/integrate-fedify.ts b/examples/next15-app-router/shared/integrate-fedify.ts index 12715108..634e33b7 100644 --- a/examples/next15-app-router/shared/integrate-fedify.ts +++ b/examples/next15-app-router/shared/integrate-fedify.ts @@ -12,11 +12,11 @@ import { import { keyPairsStore, relationStore } from "~/data/store"; import { revalidatePath } from "next/cache"; -export const fedifyRequestHanlder = integrateFederation(() => {}); +export const fedifyRequestHandler = integrateFederation(() => {}); const routePrefix = `/fedify-activity-handler`; -const federation = createFederation({ +const federation = createFederation({ kv: new MemoryKvStore(), }); diff --git a/packages/cli/src/init.ts b/packages/cli/src/init.ts index 8a7cd3df..b598b1a9 100644 --- a/packages/cli/src/init.ts +++ b/packages/cli/src/init.ts @@ -14,6 +14,7 @@ const packagesMetaData: Record<`@fedify/${string}`, string> = { "@fedify/amqp": metadata.version, "@fedify/express": metadata.version, "@fedify/h3": metadata.version, + "@fedify/next": metadata.version, }; const logger = getLogger(["fedify", "cli", "init"]); @@ -85,7 +86,7 @@ const packageManagerLocations: Record = ), ); -type WebFramework = "fresh" | "hono" | "express" | "nitro"; +type WebFramework = "fresh" | "hono" | "express" | "nitro" | "next"; interface WebFrameworkInitializer { command?: [string, ...string[]] | [...string[], string]; @@ -435,6 +436,75 @@ To start the server, run the following command: Then, try look up an actor from your server: ${colors.bold(colors.green("fedify lookup http://localhost:3000/users/john"))} +`, + }), + }, + next: { + label: "Next.js", + runtimes: ["node"], + init: (_, __, packageManager) => ({ + label: "Next.js", + runtimes: ["node"], + command: [ + ...(packageManager === "npm" ? ["npx"] : [packageManager, "dlx"]), + "create-next-app@canary", + ".", + "--ts", + "--tailwind", + "--eslint", + "--app", + "--turbopack", + "--skip-install", + ], + dependencies: { + "@fedify/next": getLatestVersion("@fedify/next"), + }, + devDependencies: { + "@types/node": "^20.11.2", + }, + federationFile: "federation/index.ts", + loggingFile: "logging.ts", + files: { + "middleware.ts": ` +import { fedifyWith } from "@fedify/next"; +import federation from "./federation"; + +export default fedifyWith(federation)( +/* + function (request: Request) { + // If you need to handle other requests besides federation + // requests in middleware, you can do it here. + // If you handle only federation requests in middleware, + // you don't need this function. + return NextResponse.next(); + }, +*/ +) + +// This config needs because middleware process only requests with the +// "Accept" header matching the federation accept regex. +// More details: https://nextjs.org/docs/app/api-reference/file-conventions/middleware#config-object-optional +export const config = { + runtime: "nodejs", + matcher: [{ + source: "/:path*", + has: [ + { + type: "header", + key: "Accept", + value: ".*application\\\\/((jrd|activity|ld)\\\\+json|xrd\\\\+xml).*", + }, + ], + }], +}; +`, + }, + instruction: ` +To start the server, run the following command: + + ${colors.bold(colors.green(packageManager + " run dev"))} +Then, try look up an actor from your server: + ${colors.bold(colors.green("fedify lookup @john@localhost:3000"))} `, }), }, diff --git a/packages/fedify/README.md b/packages/fedify/README.md index 1e22993f..bbf217a0 100644 --- a/packages/fedify/README.md +++ b/packages/fedify/README.md @@ -99,6 +99,7 @@ Here is the list of packages: | [@fedify/express](/packages/express/) | [JSR][jsr:@fedify/express] | [npm][npm:@fedify/express] | Express integration | | [@fedify/h3](/packages/h3/) | [JSR][jsr:@fedify/h3] | [npm][npm:@fedify/h3] | H3 integration | | [@fedify/nestjs](/packages/nestjs/) | | [npm][npm:@fedify/nestjs] | NestJS integration | +| [@fedify/next](/packages/next/) | | | Next.js integration | | [@fedify/postgres](/packages/postgres/) | [JSR][jsr:@fedify/postgres] | [npm][npm:@fedify/postgres] | PostgreSQL driver | | [@fedify/redis](/packages/redis/) | [JSR][jsr:@fedify/redis] | [npm][npm:@fedify/redis] | Redis driver | | [@fedify/sqlite](/packages/sqlite/) | [JSR][jsr:@fedify/sqlite] | [npm][npm:@fedify/sqlite] | SQLite driver | diff --git a/packages/next/README.md b/packages/next/README.md new file mode 100644 index 00000000..0efcb035 --- /dev/null +++ b/packages/next/README.md @@ -0,0 +1,200 @@ + + +@fedify/next: Integrate Fedify with Next.js +=========================================== + +[![Follow @fedify@hollo.social][@fedify@hollo.social badge]][@fedify@hollo.social] + +This package provides a simple way to integrate [Fedify] with [Next.js]. + +> [!IMPORTANT] +> We recommend initializing your app using the `init` command of the +> [Fedify CLI] rather than installing this package directly. + +> [!IMPORTANT] +> This package runs Next.js middleware on the Node.js runtime. +> Therefore, you must use version 15.5 or later, or at least 15.4 canary. +> For more details, refer to the [official documentation of `middleware`]. + + + +### Usage + +~~~~ typescript +// --- middleware.ts --- +import { fedifyWith } from "@fedify/next"; +import { federation } from "./federation"; + +export default fedifyWith(federation)(); + +// This config must be defined on `middleware.ts`. +export const config = { + runtime: "nodejs", + matcher: [{ + source: "/:path*", + has: [ + { + type: "header", + key: "Accept", + value: ".*application\\/((jrd|activity|ld)\\+json|xrd\\+xml).*", + }, + ], + }], +}; +~~~~ + + +The integration code looks like this: + + +~~~~ typescript +/** + * Fedify with Next.js + * =================== + * + * This module provides a [Next.js] middleware to integrate with the Fedify. + * + * [Next.js]: https://nextjs.org/ + * + * @module + * @since 1.9.0 + */ +import type { Federation, FederationFetchOptions } from "@fedify/fedify"; +import { notFound } from "next/navigation"; +import { NextResponse } from "next/server"; +import { getXForwardedRequest } from "x-forwarded-fetch"; + +interface ContextDataFactory { + (request: Request): + | TContextData + | Promise; +} +type ErrorHandlers = Omit, "contextData">; + +/** + * Wrapper function for Next.js middleware to integrate with the + * {@link Federation} object. + * + * @template TContextData A type of the context data for the + * {@link Federation} object. + * @param federation A {@link Federation} object to integrate with Next.js. + * @param contextDataFactory A function to create a context data for the + * {@link Federation} object. + * @param errorHandlers A set of error handlers to handle errors during + * the federation fetch. + * @returns A Next.js middleware function to integrate with the + * {@link Federation} object. + * + * @example + * ```ts + * import { fedifyWith } from "@fedify/next"; + * import { federation } from "./federation"; + * + * export default fedifyWith(federation)( + * function (request: Request) { + * // You can add custom logic here for other requests + * // except federation requests. If there is no custom logic, + * // you can omit this function. + * } + * ) + * + * // This config makes middleware process only requests with the + * // "Accept" header matching the federation accept regex. + * // More details: https://nextjs.org/docs/app/api-reference/file-conventions/middleware#config-object-optional. + * export const config = { + * runtime: "nodejs", + * matcher: [{ + * source: "/:path*", + * has: [ + * { + * type: "header", + * key: "Accept", + * value: ".*application\\/((jrd|activity|ld)\\+json|xrd\\+xml).*", + * }, + * ], + * }], + * }; + * ``` + */ +export const fedifyWith = ( + federation: Federation, + contextDataFactory?: ContextDataFactory, + errorHandlers?: Partial, +) => +( + middleware: (request: Request) => unknown = + ((_: Request) => NextResponse.next()), +): (request: Request) => unknown => +async (request: Request) => { + if (hasFederationAcceptHeader(request)) { + return await integrateFederation( + federation, + contextDataFactory, + errorHandlers, + )(request); + } + return await middleware(request); +}; + +/** + * Check if the request has the "Accept" header matching the federation + * accept regex. + * + * @param {Request} request The request to check. + * @returns {boolean} `true` if the request has the "Accept" header matching + * the federation accept regex, `false` otherwise. + */ +export const hasFederationAcceptHeader = (request: Request): boolean => { + const acceptHeader = request.headers.get("Accept"); + // Check if the Accept header matches the federation accept regex. + // If the header is not present, return false. + return acceptHeader ? FEDERATION_ACCEPT_REGEX.test(acceptHeader) : false; +}; +const FEDERATION_ACCEPT_REGEX = + /.*application\/((jrd|activity|ld)\+json|xrd\+xml).*/; + +/** + * Create a Next.js handler to integrate with the {@link Federation} object. + * + * @template TContextData A type of the context data for the + * {@link Federation} object. + * @param federation A {@link Federation} object to integrate with Next.js. + * @param contextDataFactory A function to create a context data for the + * {@link Federation} object. + * @param errorHandlers A set of error handlers to handle errors during + * the federation fetch. + * @returns A Next.js handler. + */ +export function integrateFederation( + federation: Federation, + contextDataFactory: ContextDataFactory = () => + undefined as TContextData, + errorHandlers?: Partial, +) { + return async (request: Request) => { + const forwardedRequest = await getXForwardedRequest(request); + const contextData = await contextDataFactory(forwardedRequest); + return await federation.fetch( + forwardedRequest, + { + contextData, + onNotFound: notFound, + onNotAcceptable, + ...errorHandlers, + }, + ); + }; +} +const onNotAcceptable = () => + new Response("Not acceptable", { + status: 406, + headers: { "Content-Type": "text/plain", Vary: "Accept" }, + }); +~~~~ + +[@fedify@hollo.social badge]: https://fedi-badge.deno.dev/@fedify@hollo.social/followers.svg +[@fedify@hollo.social]: https://hollo.social/@fedify +[Fedify]: https://fedify.dev/ +[Next.js]: https://nextjs.org/ +[official documentation of `middleware`]: https://nextjs.org/docs/app/api-reference/file-conventions/middleware#runtime +[Fedify CLI]: https://www.npmjs.com/package/@fedify/cli diff --git a/packages/next/package.json b/packages/next/package.json new file mode 100644 index 00000000..d3940636 --- /dev/null +++ b/packages/next/package.json @@ -0,0 +1,65 @@ +{ + "name": "@fedify/next", + "version": "1.9.0", + "description": "Integrate Fedify with Next.js", + "keywords": [ + "Fedify", + "ActivityPub", + "Fediverse", + "Next", + "Next.js" + ], + "author": { + "name": "Chanhaeng Lee", + "email": "2chanhaeng@gmail.com", + "url": "https://chomu.dev" + }, + "homepage": "https://fedify.dev/", + "repository": { + "type": "git", + "url": "git+https://github.com/fedify-dev/fedify.git", + "directory": "packages/next" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/fedify-dev/fedify/issues" + }, + "funding": [ + "https://opencollective.com/fedify", + "https://github.com/sponsors/dahlia" + ], + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + } + }, + "./package.json": "./package.json" + }, + "files": [ + "dist/", + "package.json" + ], + "peerDependencies": { + "@fedify/fedify": "workspace:", + "next": "catalog:" + }, + "devDependencies": { + "tsdown": "catalog:", + "typescript": "catalog:" + }, + "scripts": { + "build": "tsdown", + "prepack": "tsdown", + "prepublish": "tsdown" + } +} diff --git a/packages/next/src/index.ts b/packages/next/src/index.ts new file mode 100644 index 00000000..2ed87da0 --- /dev/null +++ b/packages/next/src/index.ts @@ -0,0 +1,138 @@ +/** + * Fedify with Next.js + * =================== + * + * This module provides a [Next.js] middleware to integrate with the Fedify. + * + * [Next.js]: https://nextjs.org/ + * + * @module + * @since 1.9.0 + */ +import type { Federation, FederationFetchOptions } from "@fedify/fedify"; +import { notFound } from "next/navigation"; +import { NextResponse } from "next/server"; + +interface ContextDataFactory { + (request: Request): + | TContextData + | Promise; +} +type ErrorHandlers = Omit, "contextData">; + +/** + * Wrapper function for Next.js middleware to integrate with the + * {@link Federation} object. + * + * @template TContextData A type of the context data for the + * {@link Federation} object. + * @param federation A {@link Federation} object to integrate with Next.js. + * @param contextDataFactory A function to create a context data for the + * {@link Federation} object. + * @param errorHandlers A set of error handlers to handle errors during + * the federation fetch. + * @returns A Next.js middleware function to integrate with the + * {@link Federation} object. + * + * @example + * ```ts ignore + * import { fedifyWith } from "@fedify/next"; + * import { federation } from "./federation"; + * + * export default fedifyWith(federation)( + * function (request: Request) { + * // You can add custom logic here for other requests + * // except federation requests. If there is no custom logic, + * // you can omit this function. + * } + * ) + * + * // This config makes middleware process only requests with the + * // "Accept" header matching the federation accept regex. + * // More details: https://nextjs.org/docs/app/api-reference/file-conventions/middleware#config-object-optional. + * export const config = { + * runtime: "nodejs", + * matcher: [{ + * source: "/:path*", + * has: [ + * { + * type: "header", + * key: "Accept", + * value: ".*application\\/((jrd|activity|ld)\\+json|xrd\\+xml).*", + * }, + * ], + * }], + * }; + * ``` + */ +export const fedifyWith = ( + federation: Federation, + contextDataFactory?: ContextDataFactory, + errorHandlers?: Partial, +) => +( + middleware: (request: Request) => unknown = + ((_: Request) => NextResponse.next()), +): (request: Request) => unknown => +async (request: Request) => { + if (hasFederationAcceptHeader(request)) { + return await integrateFederation( + federation, + contextDataFactory, + errorHandlers, + )(request); + } + return await middleware(request); +}; + +/** + * Check if the request has the "Accept" header matching the federation + * accept regex. + * + * @param request The request to check. + * @returns `true` if the request has the "Accept" header matching + * the federation accept regex, `false` otherwise. + */ +export const hasFederationAcceptHeader = (request: Request): boolean => { + const acceptHeader = request.headers.get("Accept"); + // Check if the Accept header matches the federation accept regex. + // If the header is not present, return false. + return acceptHeader ? FEDERATION_ACCEPT_REGEX.test(acceptHeader) : false; +}; +const FEDERATION_ACCEPT_REGEX = + /.*application\/((jrd|activity|ld)\+json|xrd\+xml).*/; + +/** + * Create a Next.js handler to integrate with the {@link Federation} object. + * + * @template TContextData A type of the context data for the + * {@link Federation} object. + * @param federation A {@link Federation} object to integrate with Next.js. + * @param contextDataFactory A function to create a context data for the + * {@link Federation} object. + * @param errorHandlers A set of error handlers to handle errors during + * the federation fetch. + * @returns A Next.js handler. + */ +export function integrateFederation( + federation: Federation, + contextDataFactory: ContextDataFactory = () => + undefined as TContextData, + errorHandlers?: Partial, +) { + return async (request: Request) => + await federation.fetch( + request, + { + contextData: await contextDataFactory(request), + onNotFound: notFound, + onNotAcceptable, + ...errorHandlers, + }, + ); +} +const onNotAcceptable = () => + new Response("Not acceptable", { + status: 406, + headers: { "Content-Type": "text/plain", Vary: "Accept" }, + }); diff --git a/packages/next/tsdown.config.ts b/packages/next/tsdown.config.ts new file mode 100644 index 00000000..22c092b4 --- /dev/null +++ b/packages/next/tsdown.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + entry: ["src/index.ts"], + dts: true, + platform: "node", + format: ["esm"], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b6a88d5..d7345e39 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,9 @@ catalogs: ioredis: specifier: ^5.6.1 version: 5.6.1 + next: + specifier: ^15.4.2-canary + version: 15.4.6 postgres: specifier: ^3.4.7 version: 3.4.7 @@ -99,6 +102,9 @@ importers: '@fedify/nestjs': specifier: 'workspace:' version: link:../packages/nestjs + '@fedify/next': + specifier: 'workspace:' + version: link:../packages/next '@fedify/postgres': specifier: 'workspace:' version: link:../packages/postgres @@ -581,6 +587,22 @@ importers: specifier: 'catalog:' version: 5.8.3 + packages/next: + dependencies: + '@fedify/fedify': + specifier: 'workspace:' + version: link:../fedify + next: + specifier: 'catalog:' + version: 15.4.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + devDependencies: + tsdown: + specifier: 'catalog:' + version: 0.12.9(typescript@5.8.3) + typescript: + specifier: 'catalog:' + version: 5.8.3 + packages/postgres: dependencies: '@fedify/fedify': @@ -1835,6 +1857,9 @@ packages: '@next/env@15.3.1': resolution: {integrity: sha512-cwK27QdzrMblHSn9DZRV+DQscHXRuJv6MydlJRpFSqJWZrTYMLzKDeyueJNN9MGd8NNiUKzDQADAf+dMLXX7YQ==} + '@next/env@15.4.6': + resolution: {integrity: sha512-yHDKVTcHrZy/8TWhj0B23ylKv5ypocuCwey9ZqPyv4rPdUdRzpGCkSi03t04KBPyU96kxVtUqx6O3nE1kpxASQ==} + '@next/eslint-plugin-next@15.0.0-canary.136': resolution: {integrity: sha512-fcPDffjS87Jftv6rMuCwCUEE6qQ08fipZMJT4BQJx/yLNMnmbxA8RxU1IZv0nVFrjJM2yv6RkzeyvmvSobX7YQ==} @@ -1853,6 +1878,12 @@ packages: cpu: [arm64] os: [darwin] + '@next/swc-darwin-arm64@15.4.6': + resolution: {integrity: sha512-667R0RTP4DwxzmrqTs4Lr5dcEda9OxuZsVFsjVtxVMVhzSpo6nLclXejJVfQo2/g7/Z9qF3ETDmN3h65mTjpTQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-x64@14.2.30': resolution: {integrity: sha512-TyO7Wz1IKE2kGv8dwQ0bmPL3s44EKVencOqwIY69myoS3rdpO1NPg5xPM5ymKu7nfX4oYJrpMxv8G9iqLsnL4A==} engines: {node: '>= 10'} @@ -1865,6 +1896,12 @@ packages: cpu: [x64] os: [darwin] + '@next/swc-darwin-x64@15.4.6': + resolution: {integrity: sha512-KMSFoistFkaiQYVQQnaU9MPWtp/3m0kn2Xed1Ces5ll+ag1+rlac20sxG+MqhH2qYWX1O2GFOATQXEyxKiIscg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-linux-arm64-gnu@14.2.30': resolution: {integrity: sha512-I5lg1fgPJ7I5dk6mr3qCH1hJYKJu1FsfKSiTKoYwcuUf53HWTrEkwmMI0t5ojFKeA6Vu+SfT2zVy5NS0QLXV4Q==} engines: {node: '>= 10'} @@ -1877,6 +1914,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-gnu@15.4.6': + resolution: {integrity: sha512-PnOx1YdO0W7m/HWFeYd2A6JtBO8O8Eb9h6nfJia2Dw1sRHoHpNf6lN1U4GKFRzRDBi9Nq2GrHk9PF3Vmwf7XVw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-musl@14.2.30': resolution: {integrity: sha512-8GkNA+sLclQyxgzCDs2/2GSwBc92QLMrmYAmoP2xehe5MUKBLB2cgo34Yu242L1siSkwQkiV4YLdCnjwc/Micw==} engines: {node: '>= 10'} @@ -1889,6 +1932,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-musl@15.4.6': + resolution: {integrity: sha512-XBbuQddtY1p5FGPc2naMO0kqs4YYtLYK/8aPausI5lyOjr4J77KTG9mtlU4P3NwkLI1+OjsPzKVvSJdMs3cFaw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-x64-gnu@14.2.30': resolution: {integrity: sha512-8Ly7okjssLuBoe8qaRCcjGtcMsv79hwzn/63wNeIkzJVFVX06h5S737XNr7DZwlsbTBDOyI6qbL2BJB5n6TV/w==} engines: {node: '>= 10'} @@ -1901,6 +1950,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-gnu@15.4.6': + resolution: {integrity: sha512-+WTeK7Qdw82ez3U9JgD+igBAP75gqZ1vbK6R8PlEEuY0OIe5FuYXA4aTjL811kWPf7hNeslD4hHK2WoM9W0IgA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-musl@14.2.30': resolution: {integrity: sha512-dBmV1lLNeX4mR7uI7KNVHsGQU+OgTG5RGFPi3tBJpsKPvOPtg9poyav/BYWrB3GPQL4dW5YGGgalwZ79WukbKQ==} engines: {node: '>= 10'} @@ -1913,6 +1968,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-musl@15.4.6': + resolution: {integrity: sha512-XP824mCbgQsK20jlXKrUpZoh/iO3vUWhMpxCz8oYeagoiZ4V0TQiKy0ASji1KK6IAe3DYGfj5RfKP6+L2020OQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-win32-arm64-msvc@14.2.30': resolution: {integrity: sha512-6MMHi2Qc1Gkq+4YLXAgbYslE1f9zMGBikKMdmQRHXjkGPot1JY3n5/Qrbg40Uvbi8//wYnydPnyvNhI1DMUW1g==} engines: {node: '>= 10'} @@ -1925,6 +1986,12 @@ packages: cpu: [arm64] os: [win32] + '@next/swc-win32-arm64-msvc@15.4.6': + resolution: {integrity: sha512-FxrsenhUz0LbgRkNWx6FRRJIPe/MI1JRA4W4EPd5leXO00AZ6YU8v5vfx4MDXTvN77lM/EqsE3+6d2CIeF5NYg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-ia32-msvc@14.2.30': resolution: {integrity: sha512-pVZMnFok5qEX4RT59mK2hEVtJX+XFfak+/rjHpyFh7juiT52r177bfFKhnlafm0UOSldhXjj32b+LZIOdswGTg==} engines: {node: '>= 10'} @@ -1943,6 +2010,12 @@ packages: cpu: [x64] os: [win32] + '@next/swc-win32-x64-msvc@15.4.6': + resolution: {integrity: sha512-T4ufqnZ4u88ZheczkBTtOF+eKaM14V8kbjud/XrAakoM5DKQWjW09vD6B9fsdsWS2T7D5EY31hRHdta7QKWOng==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -5258,6 +5331,27 @@ packages: sass: optional: true + next@15.4.6: + resolution: {integrity: sha512-us++E/Q80/8+UekzB3SAGs71AlLDsadpFMXVNM/uQ0BMwsh9m3mr0UNQIfjKed8vpWXsASe+Qifrnu1oLIcKEQ==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -7413,6 +7507,8 @@ snapshots: '@next/env@15.3.1': {} + '@next/env@15.4.6': {} + '@next/eslint-plugin-next@15.0.0-canary.136': dependencies: fast-glob: 3.3.1 @@ -7427,42 +7523,63 @@ snapshots: '@next/swc-darwin-arm64@15.3.1': optional: true + '@next/swc-darwin-arm64@15.4.6': + optional: true + '@next/swc-darwin-x64@14.2.30': optional: true '@next/swc-darwin-x64@15.3.1': optional: true + '@next/swc-darwin-x64@15.4.6': + optional: true + '@next/swc-linux-arm64-gnu@14.2.30': optional: true '@next/swc-linux-arm64-gnu@15.3.1': optional: true + '@next/swc-linux-arm64-gnu@15.4.6': + optional: true + '@next/swc-linux-arm64-musl@14.2.30': optional: true '@next/swc-linux-arm64-musl@15.3.1': optional: true + '@next/swc-linux-arm64-musl@15.4.6': + optional: true + '@next/swc-linux-x64-gnu@14.2.30': optional: true '@next/swc-linux-x64-gnu@15.3.1': optional: true + '@next/swc-linux-x64-gnu@15.4.6': + optional: true + '@next/swc-linux-x64-musl@14.2.30': optional: true '@next/swc-linux-x64-musl@15.3.1': optional: true + '@next/swc-linux-x64-musl@15.4.6': + optional: true + '@next/swc-win32-arm64-msvc@14.2.30': optional: true '@next/swc-win32-arm64-msvc@15.3.1': optional: true + '@next/swc-win32-arm64-msvc@15.4.6': + optional: true + '@next/swc-win32-ia32-msvc@14.2.30': optional: true @@ -7472,6 +7589,9 @@ snapshots: '@next/swc-win32-x64-msvc@15.3.1': optional: true + '@next/swc-win32-x64-msvc@15.4.6': + optional: true + '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': @@ -9941,8 +10061,8 @@ snapshots: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -9981,7 +10101,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -9992,7 +10112,7 @@ snapshots: tinyglobby: 0.2.14 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -10011,14 +10131,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -10033,7 +10153,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -10044,7 +10164,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -11567,6 +11687,30 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@15.4.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + '@next/env': 15.4.6 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001727 + postcss: 8.4.31 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + styled-jsx: 5.1.6(react@19.1.0) + optionalDependencies: + '@next/swc-darwin-arm64': 15.4.6 + '@next/swc-darwin-x64': 15.4.6 + '@next/swc-linux-arm64-gnu': 15.4.6 + '@next/swc-linux-arm64-musl': 15.4.6 + '@next/swc-linux-x64-gnu': 15.4.6 + '@next/swc-linux-x64-musl': 15.4.6 + '@next/swc-win32-arm64-msvc': 15.4.6 + '@next/swc-win32-x64-msvc': 15.4.6 + '@opentelemetry/api': 1.9.0 + sharp: 0.34.3 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + node-domexception@1.0.0: {} node-fetch@3.3.2: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index de8c2d75..9493205f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,6 +5,7 @@ packages: - packages/elysia - packages/express - packages/nestjs +- packages/next - packages/h3 - packages/postgres - packages/redis @@ -34,6 +35,7 @@ catalog: express: ^4.0.0 h3: ^1.15.0 ioredis: ^5.6.1 + next: ^15.4.2-canary postgres: ^3.4.7 tsdown: ^0.12.9 typescript: ^5.8.3