mirror of
https://github.com/stack-auth/stack.git
synced 2026-07-20 21:29:36 +08:00
Merge branch 'dev' into devin/1782934151-remove-product-level-free-trial
Some checks failed
DB migration compat / Check if migrations changed (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
Some checks failed
DB migration compat / Check if migrations changed (push) Has been cancelled
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
DB migration compat / No migration changes (skipped) (push) Has been cancelled
This commit is contained in:
commit
b055b94af6
5
.github/workflows/e2e-api-tests.yaml
vendored
5
.github/workflows/e2e-api-tests.yaml
vendored
@ -132,13 +132,10 @@ jobs:
|
||||
- name: Backfill Bulldozer from Postgres
|
||||
run: pnpm run db:backfill-bulldozer-from-prisma
|
||||
|
||||
# The built server (node dist/server.mjs) does not auto-load .env files the way
|
||||
# `next start` used to, so it must be wrapped in with-env:test to pick up
|
||||
# .env.test.local (mirrors the e2e-fallback-tests workflow).
|
||||
- name: Start stack-backend in background
|
||||
uses: JarvusInnovations/background-action@2428e7b970a846423095c79d43f759abf979a635 # v1.0.7
|
||||
with:
|
||||
run: pnpm -C apps/backend run with-env:test pnpm run start &
|
||||
run: pnpm run start:backend --log-order=stream &
|
||||
wait-on: |
|
||||
http://localhost:8102
|
||||
tail: true
|
||||
|
||||
@ -126,15 +126,10 @@ jobs:
|
||||
- name: Backfill Bulldozer from Postgres
|
||||
run: pnpm run db:backfill-bulldozer-from-prisma
|
||||
|
||||
# The built server (node dist/server.mjs) does not auto-load .env files the way
|
||||
# `next start` used to, so it must be wrapped in with-env:test to pick up
|
||||
# .env.test.local (mirrors the e2e-fallback-tests workflow). The job-level
|
||||
# NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=67 stays in effect because dotenv does not
|
||||
# override already-set process env vars.
|
||||
- name: Start stack-backend in background
|
||||
uses: JarvusInnovations/background-action@2428e7b970a846423095c79d43f759abf979a635 # v1.0.7
|
||||
with:
|
||||
run: pnpm -C apps/backend run with-env:test pnpm run start &
|
||||
run: pnpm run start:backend --log-order=stream &
|
||||
wait-on: |
|
||||
http://localhost:6702
|
||||
tail: true
|
||||
|
||||
2
.github/workflows/e2e-fallback-tests.yaml
vendored
2
.github/workflows/e2e-fallback-tests.yaml
vendored
@ -113,7 +113,7 @@ jobs:
|
||||
- name: Start stack-backend on fallback port (8110)
|
||||
uses: JarvusInnovations/background-action@2428e7b970a846423095c79d43f759abf979a635 # v1.0.7
|
||||
with:
|
||||
run: PORT=8110 pnpm -C apps/backend run with-env:test pnpm run start &
|
||||
run: pnpm -C apps/backend run with-env:test next start --port 8110 &
|
||||
wait-on: |
|
||||
http://localhost:8110
|
||||
tail: true
|
||||
|
||||
4
apps/backend/api/dist-vercel.d.ts
vendored
4
apps/backend/api/dist-vercel.d.ts
vendored
@ -1,4 +0,0 @@
|
||||
declare module "*/dist/vercel.mjs" {
|
||||
const app: typeof import("../src/server/vercel").default;
|
||||
export default app;
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import app from "../dist/vercel.mjs";
|
||||
|
||||
export const config = {
|
||||
runtime: "nodejs",
|
||||
maxDuration: 60,
|
||||
};
|
||||
|
||||
export default function handler(request: Request): Response | Promise<Response> {
|
||||
return app.handle(request);
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
// This file configures the initialization of Sentry on the client.
|
||||
// The config you add here will be used whenever a users loads a page in their browser.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/browser/
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { getBrowserCompatibilityReport } from "@hexclave/shared/dist/utils/browser-compat";
|
||||
import { sentryBaseConfig } from "@hexclave/shared/dist/utils/sentry";
|
||||
import { nicify } from "@hexclave/shared/dist/utils/strings";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
|
||||
Sentry.init({
|
||||
...sentryBaseConfig,
|
||||
|
||||
99
apps/backend/next.config.mjs
Normal file
99
apps/backend/next.config.mjs
Normal file
@ -0,0 +1,99 @@
|
||||
import { withSentryConfig } from "@sentry/nextjs";
|
||||
|
||||
const withConfiguredSentryConfig = (nextConfig) =>
|
||||
withSentryConfig(
|
||||
nextConfig,
|
||||
{
|
||||
// For all available options, see:
|
||||
// https://github.com/getsentry/sentry-webpack-plugin#options
|
||||
|
||||
org: process.env.SENTRY_ORG,
|
||||
project: process.env.SENTRY_PROJECT,
|
||||
|
||||
widenClientFileUpload: true,
|
||||
telemetry: false,
|
||||
},
|
||||
{
|
||||
// For all available options, see:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
||||
|
||||
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||
widenClientFileUpload: true,
|
||||
|
||||
// Transpiles SDK to be compatible with IE11 (increases bundle size)
|
||||
transpileClientSDK: true,
|
||||
|
||||
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
|
||||
// This can increase your server load as well as your hosting bill.
|
||||
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
|
||||
// side errors will fail.
|
||||
tunnelRoute: "/monitoring",
|
||||
|
||||
// Hides source maps from generated client bundles
|
||||
hideSourceMaps: true,
|
||||
|
||||
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||
disableLogger: true,
|
||||
|
||||
// Enables automatic instrumentation of Vercel Cron Monitors.
|
||||
// See the following for more information:
|
||||
// https://docs.sentry.io/product/crons/
|
||||
// https://vercel.com/docs/cron-jobs
|
||||
automaticVercelMonitors: true,
|
||||
}
|
||||
);
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
// optionally set output to "standalone" for Docker builds
|
||||
// https://nextjs.org/docs/pages/api-reference/next-config-js/output
|
||||
output: process.env.NEXT_CONFIG_OUTPUT,
|
||||
|
||||
// we're open-source, so we can provide source maps
|
||||
productionBrowserSourceMaps: true,
|
||||
poweredByHeader: false,
|
||||
|
||||
experimental: {
|
||||
serverMinification: false, // needs to be disabled for oidc-provider to work, which relies on the original constructor names
|
||||
},
|
||||
|
||||
serverExternalPackages: [
|
||||
'oidc-provider',
|
||||
],
|
||||
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: "/(.*)",
|
||||
headers: [
|
||||
{
|
||||
key: "Cross-Origin-Opener-Policy",
|
||||
value: "same-origin",
|
||||
},
|
||||
{
|
||||
key: "Permissions-Policy",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
key: "Referrer-Policy",
|
||||
value: "strict-origin-when-cross-origin",
|
||||
},
|
||||
{
|
||||
key: "X-Content-Type-Options",
|
||||
value: "nosniff",
|
||||
},
|
||||
{
|
||||
key: "X-Frame-Options",
|
||||
value: "SAMEORIGIN",
|
||||
},
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default withConfiguredSentryConfig(nextConfig);
|
||||
@ -5,20 +5,20 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"clean": "rimraf src/generated && rimraf dist && rimraf node_modules",
|
||||
"clean": "rimraf src/generated && rimraf .next && rimraf node_modules",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"with-env": "dotenv -c --",
|
||||
"with-env:dev": "dotenv -c development --",
|
||||
"with-env:prod": "dotenv -c production --",
|
||||
"with-env:test": "dotenv -c test --",
|
||||
"dev": "BACKEND_PORT=${STACK_DEV_FALLBACK_BACKEND:+${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}10} && BACKEND_PORT=${BACKEND_PORT:-${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}02} && concurrently -n \"dev,codegen,prisma-studio,email-queue,cron-jobs,bulldozer-studio\" -k \"STACK_DISABLE_REACT_ASYNC_DEBUG_INFO=${STACK_DISABLE_REACT_ASYNC_DEBUG_INFO:-true} NODE_ENV=development BACKEND_PORT=$BACKEND_PORT dotenv -c development -- tsx watch --clear-screen=false src/server/server.ts ${STACK_BACKEND_DEV_EXTRA_ARGS:-}\" \"pnpm run codegen:watch\" \"pnpm run prisma-studio\" \"pnpm run run-email-queue\" \"pnpm run run-cron-jobs\" \"pnpm run run-bulldozer-studio\"",
|
||||
"dev": "BACKEND_PORT=${STACK_DEV_FALLBACK_BACKEND:+${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}10} && BACKEND_PORT=${BACKEND_PORT:-${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}02} && concurrently -n \"dev,codegen,prisma-studio,email-queue,cron-jobs,bulldozer-studio\" -k \"STACK_DISABLE_REACT_ASYNC_DEBUG_INFO=${STACK_DISABLE_REACT_ASYNC_DEBUG_INFO:-true} next dev --port $BACKEND_PORT ${STACK_BACKEND_DEV_EXTRA_ARGS:-}\" \"pnpm run codegen:watch\" \"pnpm run prisma-studio\" \"pnpm run run-email-queue\" \"pnpm run run-cron-jobs\" \"pnpm run run-bulldozer-studio\"",
|
||||
"dev:inspect": "STACK_BACKEND_DEV_EXTRA_ARGS=\"--inspect\" pnpm run dev",
|
||||
"dev:profile": "STACK_BACKEND_DEV_EXTRA_ARGS=\"--experimental-cpu-prof\" pnpm run dev",
|
||||
"build": "pnpm run codegen && tsdown --config tsdown.config.ts",
|
||||
"docker-build": "pnpm run codegen && tsdown --config tsdown.config.ts",
|
||||
"build": "pnpm run codegen && next build",
|
||||
"docker-build": "pnpm run codegen && next build --experimental-build-mode compile",
|
||||
"build-self-host-migration-script": "tsdown --config scripts/db-migrations.tsdown.config.ts",
|
||||
|
||||
"start": "node dist/server.mjs",
|
||||
"analyze-bundle": "next experimental-analyze",
|
||||
"start": "next start --port ${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}02",
|
||||
"codegen-prisma": "HEXCLAVE_DATABASE_CONNECTION_STRING=\"${HEXCLAVE_DATABASE_CONNECTION_STRING:-${STACK_DATABASE_CONNECTION_STRING:-placeholder-database-connection-string}}\" pnpm run prisma generate",
|
||||
"codegen-prisma:watch": "HEXCLAVE_DATABASE_CONNECTION_STRING=\"${HEXCLAVE_DATABASE_CONNECTION_STRING:-${STACK_DATABASE_CONNECTION_STRING:-placeholder-database-connection-string}}\" pnpm run prisma generate --watch",
|
||||
"generate-private-sign-up-risk-engine": "pnpm run with-env tsx scripts/generate-private-sign-up-risk-engine.ts",
|
||||
@ -59,12 +59,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/mcp": "^1.0.21",
|
||||
"spacetimedb": "^2.1.0",
|
||||
"@aws-sdk/client-s3": "^3.855.0",
|
||||
"@clickhouse/client": "^1.14.0",
|
||||
"@elysiajs/node": "^1.4.5",
|
||||
"@hexclave/next": "workspace:*",
|
||||
"@hexclave/shared": "workspace:*",
|
||||
"@hexclave/shared-backend": "workspace:*",
|
||||
"@node-oauth/oauth2-server": "^5.1.0",
|
||||
"@openrouter/ai-sdk-provider": "2.2.3",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
@ -76,7 +73,6 @@
|
||||
"@opentelemetry/instrumentation": "^0.53.0",
|
||||
"@opentelemetry/resources": "^1.26.0",
|
||||
"@opentelemetry/sdk-logs": "^0.53.0",
|
||||
"@opentelemetry/sdk-node": "0.53.0",
|
||||
"@opentelemetry/sdk-trace-base": "^1.26.0",
|
||||
"@opentelemetry/sdk-trace-node": "^1.26.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.27.0",
|
||||
@ -85,13 +81,14 @@
|
||||
"@prisma/client": "^7.0.0",
|
||||
"@prisma/extension-read-replicas": "^0.5.0",
|
||||
"@prisma/instrumentation": "^7.0.0",
|
||||
"@sentry/browser": "^10.45.0",
|
||||
"@sentry/node": "^10.45.0",
|
||||
"@sentry/rollup-plugin": "^4.6.1",
|
||||
"@sentry/nextjs": "^10.45.0",
|
||||
"@simplewebauthn/server": "^13.3.0",
|
||||
"@sinclair/typebox": "^0.34.49",
|
||||
"@hexclave/next": "workspace:*",
|
||||
"@hexclave/shared": "workspace:*",
|
||||
"@hexclave/shared-backend": "workspace:*",
|
||||
"@upstash/qstash": "^2.8.2",
|
||||
"@vercel/functions": "^2.0.0",
|
||||
"@vercel/otel": "^1.10.4",
|
||||
"@vercel/sandbox": "^1.2.0",
|
||||
"ai": "^6.0.0",
|
||||
"cel-js": "^0.8.2",
|
||||
@ -99,7 +96,6 @@
|
||||
"diff": "^8.0.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-cli": "^7.3.0",
|
||||
"elysia": "1.4.28",
|
||||
"emailable": "^3.1.1",
|
||||
"freestyle": "^0.1.63",
|
||||
"jiti": "^2.6.1",
|
||||
@ -116,7 +112,6 @@
|
||||
"resend": "^6.0.1",
|
||||
"semver": "^7.6.3",
|
||||
"sharp": "^0.34.4",
|
||||
"spacetimedb": "^2.1.0",
|
||||
"stripe": "^18.3.0",
|
||||
"svix": "^1.89.0",
|
||||
"vite": "^6.1.0",
|
||||
|
||||
@ -55,9 +55,7 @@ export default defineConfig({
|
||||
inlineOnly: false,
|
||||
// Externalize Node.js builtins so they're imported rather than shimmed
|
||||
external: [...nodeBuiltins, ...externalPackages],
|
||||
// Docker builds the backend server into the same dist directory before this
|
||||
// script. Cleaning here deletes dist/server.mjs from the final image.
|
||||
clean: false,
|
||||
clean: true,
|
||||
// Use banner to add createRequire for CommonJS modules that use require() for builtins
|
||||
// The imported require is used by the shimmed __require2 function
|
||||
banner: {
|
||||
|
||||
@ -2,48 +2,12 @@ import { SmartRouter } from "@/smart-router";
|
||||
import { writeFileSyncIfChanged } from "@hexclave/shared/dist/utils/fs";
|
||||
import fs from "fs";
|
||||
|
||||
const httpMethodNames = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"] as const;
|
||||
|
||||
function stringCompare(a: string, b: string) {
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
}
|
||||
|
||||
function routeFilePathToImportPath(filePath: string) {
|
||||
return `@/${filePath.replace(/^src\//, "").replace(/\.(ts|tsx|js|jsx)$/, "")}`;
|
||||
}
|
||||
|
||||
function generateRouteModules(routes: Awaited<ReturnType<typeof SmartRouter.listRoutes>>) {
|
||||
const routeFiles = routes
|
||||
.filter(route => route.isRoute && /\/route\.(ts|tsx|js|jsx)$/.test(route.filePath))
|
||||
.sort((a, b) => stringCompare(a.normalizedPath, b.normalizedPath) || stringCompare(a.filePath, b.filePath));
|
||||
|
||||
const imports = routeFiles.map((route, index) => {
|
||||
return `import * as r${index} from ${JSON.stringify(routeFilePathToImportPath(route.filePath))};`;
|
||||
});
|
||||
|
||||
const entries = routeFiles.map((route, index) => {
|
||||
return ` { normalizedPath: ${JSON.stringify(route.normalizedPath)}, module: r${index} },`;
|
||||
});
|
||||
|
||||
return `import type { UnknownRouteModule } from "@/server/registry";
|
||||
|
||||
${imports.join("\n")}
|
||||
|
||||
export const httpMethodNames = ${JSON.stringify(httpMethodNames)} as const;
|
||||
|
||||
export const routeModules: readonly { normalizedPath: string, module: UnknownRouteModule }[] = [
|
||||
${entries.join("\n")}
|
||||
];
|
||||
`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const routes = await SmartRouter.listRoutes();
|
||||
const apiVersions = await SmartRouter.listApiVersions();
|
||||
fs.mkdirSync("src/generated", { recursive: true });
|
||||
writeFileSyncIfChanged("src/generated/routes.json", JSON.stringify(routes, null, 2));
|
||||
writeFileSyncIfChanged("src/generated/api-versions.json", JSON.stringify(apiVersions, null, 2));
|
||||
writeFileSyncIfChanged("src/generated/route-modules.ts", generateRouteModules(routes));
|
||||
console.log("Successfully updated route info");
|
||||
}
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
|
||||
@ -12,8 +12,8 @@ import { KnownErrors } from "@hexclave/shared/dist/known-errors";
|
||||
import { urlSchema, yupArray, yupNumber, yupObject, yupString, yupUnion } from "@hexclave/shared/dist/schema-fields";
|
||||
import { getNodeEnvironment } from "@hexclave/shared/dist/utils/env";
|
||||
import { StatusError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { cookies } from "@/lib/runtime/headers";
|
||||
import { redirect } from "@/lib/runtime/navigation";
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { generators } from "openid-client";
|
||||
import type { InferType, Schema } from "yup";
|
||||
|
||||
|
||||
@ -15,8 +15,8 @@ import { KnownError, KnownErrors } from "@hexclave/shared";
|
||||
import { yupMixed, yupNumber, yupObject, yupString } from "@hexclave/shared/dist/schema-fields";
|
||||
import { HexclaveAssertionError, StatusError, captureError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { deindent, extractScopes, mergeScopeStrings } from "@hexclave/shared/dist/utils/strings";
|
||||
import { cookies } from "@/lib/runtime/headers";
|
||||
import { redirect } from "@/lib/runtime/navigation";
|
||||
import { cookies } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { oauthResponseToSmartResponse } from "../../oauth-helpers";
|
||||
|
||||
/**
|
||||
|
||||
@ -3,8 +3,9 @@ import { getSoleTenancyFromProjectBranch } from "@/lib/tenancies";
|
||||
import { getPrismaClientForTenancy, globalPrismaClient } from "@/prisma-client";
|
||||
import { VerificationCodeType } from "@/generated/prisma/client";
|
||||
import { KnownErrors } from "@hexclave/shared/dist/known-errors";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const code = searchParams.get('code');
|
||||
if (!code || code.length !== 45)
|
||||
|
||||
@ -4,6 +4,7 @@ import { preprocessProxyBody } from "@/private";
|
||||
import { handleApiRequest } from "@/route-handlers/smart-route-handler";
|
||||
import { getEnvVariable } from "@hexclave/shared/dist/utils/env";
|
||||
import { StatusError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
const OPENROUTER_BASE_URL = "https://openrouter.ai/api";
|
||||
const OPENROUTER_DEFAULT_MODEL = "anthropic/claude-sonnet-4.6";
|
||||
@ -37,7 +38,7 @@ function sanitizeBody(raw: ArrayBuffer): Uint8Array {
|
||||
return new TextEncoder().encode(JSON.stringify(parsed));
|
||||
}
|
||||
|
||||
async function proxyToOpenRouter(req: Request, options: { params: Promise<{ path?: string[] }> }) {
|
||||
async function proxyToOpenRouter(req: NextRequest, options: { params: Promise<{ path?: string[] }> }) {
|
||||
const apiKey = getEnvVariable("STACK_OPENROUTER_API_KEY");
|
||||
const params = await options.params;
|
||||
const subpath = params.path?.join("/") ?? "";
|
||||
@ -48,7 +49,7 @@ async function proxyToOpenRouter(req: Request, options: { params: Promise<{ path
|
||||
: undefined;
|
||||
|
||||
if (apiKey === "FORWARD_TO_PRODUCTION") {
|
||||
const targetUrl = `${PRODUCTION_AI_PROXY_BASE_URL}/${subpath}${new URL(req.url).search}`;
|
||||
const targetUrl = `${PRODUCTION_AI_PROXY_BASE_URL}/${subpath}${req.nextUrl.search}`;
|
||||
const headers: Record<string, string> = {};
|
||||
if (contentType) {
|
||||
headers["Content-Type"] = contentType;
|
||||
@ -69,7 +70,7 @@ async function proxyToOpenRouter(req: Request, options: { params: Promise<{ path
|
||||
});
|
||||
}
|
||||
|
||||
const targetUrl = `${OPENROUTER_BASE_URL}/${subpath}${new URL(req.url).search}`;
|
||||
const targetUrl = `${OPENROUTER_BASE_URL}/${subpath}${req.nextUrl.search}`;
|
||||
const headers: Record<string, string> = {
|
||||
"Authorization": `Bearer ${apiKey}`,
|
||||
"anthropic-version": "2023-06-01",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
|
||||
import { yupNever, yupObject, yupString } from "@hexclave/shared/dist/schema-fields";
|
||||
import { HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { redirect } from "@/lib/runtime/navigation";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const GET = createSmartRouteHandler({
|
||||
metadata: {
|
||||
|
||||
@ -2,6 +2,7 @@ import { handleApiRequest } from "@/route-handlers/smart-route-handler";
|
||||
import { getEnvVariable } from "@hexclave/shared/dist/utils/env";
|
||||
import { HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { createNodeHttpServerDuplex } from "@hexclave/shared/dist/utils/node-http";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { createOidcProvider } from "../../../../idp";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@ -26,7 +27,7 @@ function getOidcCallbackPromise() {
|
||||
return _oidcCallbackPromiseCache;
|
||||
}
|
||||
|
||||
const handler = handleApiRequest(async (req: Request) => {
|
||||
const handler = handleApiRequest(async (req: NextRequest) => {
|
||||
const newUrl = req.url.replace(pathPrefix, "");
|
||||
if (newUrl === req.url) {
|
||||
throw new HexclaveAssertionError("No path prefix found in request URL. Is the pathPrefix correct?", { newUrl, url: req.url, pathPrefix });
|
||||
@ -59,7 +60,7 @@ const handler = handleApiRequest(async (req: Request) => {
|
||||
// filter out session cookies; we don't want to keep sessions open, every OAuth flow should start a new session
|
||||
headers = headers.filter(([k, v]) => k !== "set-cookie" || !v.toString().match(/^_session\.?/));
|
||||
|
||||
return new Response(body, {
|
||||
return new NextResponse(body, {
|
||||
headers: headers,
|
||||
status: {
|
||||
// our API never returns 301 or 302 by convention, so transform them to 307 or 308
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler";
|
||||
import { yupNever, yupObject, yupString } from "@hexclave/shared/dist/schema-fields";
|
||||
import { HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { redirect } from "@/lib/runtime/navigation";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export const GET = createSmartRouteHandler({
|
||||
metadata: {
|
||||
|
||||
@ -2,6 +2,7 @@ import { handleApiRequest } from "@/route-handlers/smart-route-handler";
|
||||
import { getEnvVariable } from "@hexclave/shared/dist/utils/env";
|
||||
import { HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { createNodeHttpServerDuplex } from "@hexclave/shared/dist/utils/node-http";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { createOidcProvider } from "../../../../idp";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@ -26,7 +27,7 @@ function getOidcCallbackPromise() {
|
||||
return _oidcCallbackPromiseCache;
|
||||
}
|
||||
|
||||
const handler = handleApiRequest(async (req: Request) => {
|
||||
const handler = handleApiRequest(async (req: NextRequest) => {
|
||||
const newUrl = req.url.replace(pathPrefix, "");
|
||||
if (newUrl === req.url) {
|
||||
throw new HexclaveAssertionError("No path prefix found in request URL. Is the pathPrefix correct?", { newUrl, url: req.url, pathPrefix });
|
||||
@ -59,7 +60,7 @@ const handler = handleApiRequest(async (req: Request) => {
|
||||
// filter out session cookies; we don't want to keep sessions open, every OAuth flow should start a new session
|
||||
headers = headers.filter(([k, v]) => k !== "set-cookie" || !v.toString().match(/^_session\.?/));
|
||||
|
||||
return new Response(body, {
|
||||
return new NextResponse(body, {
|
||||
headers: headers,
|
||||
status: {
|
||||
// our API never returns 301 or 302 by convention, so transform them to 307 or 308
|
||||
|
||||
@ -5,6 +5,8 @@ import { HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors";
|
||||
import * as fs from "fs/promises";
|
||||
import * as path from "path";
|
||||
|
||||
const REVALIDATE_SECONDS = 60 * 60;
|
||||
|
||||
type ChangeType = "major" | "minor" | "patch";
|
||||
|
||||
type ChangelogEntry = {
|
||||
@ -171,6 +173,9 @@ export const GET = createSmartRouteHandler({
|
||||
"Accept": "text/plain",
|
||||
"User-Agent": "stack-auth-backend-changelog",
|
||||
},
|
||||
next: {
|
||||
revalidate: REVALIDATE_SECONDS,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// redirect to projects/.well-known/[...route]?include_anonymous=true
|
||||
|
||||
import { yupNever, yupObject, yupString } from "@hexclave/shared/dist/schema-fields";
|
||||
import { redirect } from "@/lib/runtime/navigation";
|
||||
import { redirect } from "next/navigation";
|
||||
import { createSmartRouteHandler } from "../../../../../../route-handlers/smart-route-handler";
|
||||
|
||||
const handler = createSmartRouteHandler({
|
||||
|
||||
1555
apps/backend/src/app/dev-stats/page.tsx
Normal file
1555
apps/backend/src/app/dev-stats/page.tsx
Normal file
File diff suppressed because it is too large
Load Diff
23
apps/backend/src/app/global-error.tsx
Normal file
23
apps/backend/src/app/global-error.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { captureError } from "@hexclave/shared/dist/utils/errors";
|
||||
import Error from "next/error";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function GlobalError({ error }: any) {
|
||||
useEffect(() => {
|
||||
captureError("backend-global-error", error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
[An unhandled error occurred.]
|
||||
<Error
|
||||
statusCode={500}
|
||||
/>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
15
apps/backend/src/app/health/error-handler-debug/page.tsx
Normal file
15
apps/backend/src/app/health/error-handler-debug/page.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { throwErr } from "@hexclave/shared/dist/utils/errors";
|
||||
|
||||
|
||||
export default function Page() {
|
||||
return <div>
|
||||
This page is useful for testing error handling.<br />
|
||||
Your observability platform should pick up on the errors thrown below.<br />
|
||||
<button onClick={() => throwErr(`Client debug error thrown successfully!`)}>Throw client error</button>
|
||||
<button onClick={() => {
|
||||
console.log("Endpoint request", fetch("/health/error-handler-debug/endpoint"));
|
||||
}}>Throw server error</button>
|
||||
</div>;
|
||||
}
|
||||
@ -1,8 +1,9 @@
|
||||
import { globalPrismaClient } from "@/prisma-client";
|
||||
import { HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
export async function GET(req: Request) {
|
||||
if (new URL(req.url).searchParams.get("db")) {
|
||||
export async function GET(req: NextRequest) {
|
||||
if (req.nextUrl.searchParams.get("db")) {
|
||||
const project = await globalPrismaClient.project.findFirst({});
|
||||
|
||||
if (!project) {
|
||||
|
||||
22
apps/backend/src/app/layout.tsx
Normal file
22
apps/backend/src/app/layout.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import type { Metadata } from 'next';
|
||||
import React from 'react';
|
||||
import '../polyfills';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Hexclave API',
|
||||
description: 'API endpoint of Hexclave.',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode,
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body suppressHydrationWarning>
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
13
apps/backend/src/app/not-found.tsx
Normal file
13
apps/backend/src/app/not-found.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { connection } from "next/server";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
export const fetchCache = "force-no-store";
|
||||
export const revalidate = 0;
|
||||
|
||||
export default async function NotFound() {
|
||||
await connection(); // guarantees we will never prerender
|
||||
|
||||
return <div>
|
||||
404 Not Found
|
||||
</div>;
|
||||
}
|
||||
22
apps/backend/src/app/page.tsx
Normal file
22
apps/backend/src/app/page.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { getNodeEnvironment } from "@hexclave/shared/dist/utils/env";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div>
|
||||
Welcome to Hexclave's API endpoint.<br />
|
||||
<br />
|
||||
Were you looking for <Link href="https://app.hexclave.com">Hexclave's dashboard</Link> instead?<br />
|
||||
<br />
|
||||
You can also return to <Link href="https://hexclave.com">https://hexclave.com</Link>.<br />
|
||||
<br />
|
||||
<Link href="/api/v1">API v1</Link><br />
|
||||
{getNodeEnvironment() === "development" && (
|
||||
<>
|
||||
<br />
|
||||
<Link href="/dev-stats">Dev Stats</Link><br />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
import "@/instrument";
|
||||
import "@/polyfills";
|
||||
import { app } from "./server/app";
|
||||
import "./server/env-expand";
|
||||
|
||||
export default app;
|
||||
@ -1,82 +0,0 @@
|
||||
import { getEnvVariable, getNodeEnvironment } from "@hexclave/shared/dist/utils/env";
|
||||
import { sentryBaseConfig } from "@hexclave/shared/dist/utils/sentry";
|
||||
import { nicify } from "@hexclave/shared/dist/utils/strings";
|
||||
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
||||
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
||||
import { NodeSDK } from "@opentelemetry/sdk-node";
|
||||
import { PrismaInstrumentation } from "@prisma/instrumentation";
|
||||
import * as Sentry from "@sentry/node";
|
||||
import { initPerfStats } from "./lib/dev-perf-stats";
|
||||
|
||||
globalThis.global = globalThis;
|
||||
// The Elysia process is the Node runtime; set the marker before shared helpers that still ask for Next runtime metadata.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
process.env.NEXT_RUNTIME ??= "nodejs";
|
||||
|
||||
let registered = false;
|
||||
|
||||
export function registerBackendInstrumentation() {
|
||||
if (registered) {
|
||||
return;
|
||||
}
|
||||
registered = true;
|
||||
|
||||
const portPrefix = getEnvVariable("NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX", "81");
|
||||
const isDevelopment = getNodeEnvironment() === "development";
|
||||
|
||||
const sdk = new NodeSDK({
|
||||
serviceName: "stack-backend",
|
||||
instrumentations: [
|
||||
new PrismaInstrumentation(),
|
||||
...getNodeAutoInstrumentations({
|
||||
"@opentelemetry/instrumentation-http": {
|
||||
enabled: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
...isDevelopment ? {
|
||||
traceExporter: new OTLPTraceExporter({
|
||||
url: `http://localhost:${portPrefix}31/v1/traces`,
|
||||
}),
|
||||
} : {},
|
||||
});
|
||||
sdk.start();
|
||||
|
||||
process.title = `stack-backend:${portPrefix} (node/elysia)`;
|
||||
initPerfStats();
|
||||
|
||||
Sentry.init({
|
||||
...sentryBaseConfig,
|
||||
// We run our own OpenTelemetry NodeSDK above (for Prisma + the dev OTLP exporter), which already
|
||||
// registers the global trace/context/propagation APIs. Without this flag, @sentry/node (v10 is
|
||||
// OpenTelemetry-native) tries to register them again, logging "Attempted duplicate registration
|
||||
// of API: trace/propagation/context". Skipping Sentry's OTel setup lets the NodeSDK own it while
|
||||
// error capture continues to work normally. (Letting Sentry own OTel instead would require migrating
|
||||
// our OpenTelemetry deps from v1 to v2 to match @sentry/node v10.)
|
||||
skipOpenTelemetrySetup: true,
|
||||
dsn: getEnvVariable("NEXT_PUBLIC_SENTRY_DSN", ""),
|
||||
enabled: getNodeEnvironment() !== "development" && !getEnvVariable("CI", ""),
|
||||
beforeSend(event, hint) {
|
||||
const error = hint.originalException;
|
||||
let nicified;
|
||||
try {
|
||||
nicified = nicify(error, { maxDepth: 8 });
|
||||
} catch (e) {
|
||||
nicified = `Error occurred during nicification: ${e}`;
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
event.extra = {
|
||||
...event.extra,
|
||||
cause: error.cause,
|
||||
errorProps: {
|
||||
...error,
|
||||
},
|
||||
nicifiedError: nicified,
|
||||
};
|
||||
}
|
||||
return event;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
registerBackendInstrumentation();
|
||||
73
apps/backend/src/instrumentation.ts
Normal file
73
apps/backend/src/instrumentation.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
||||
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
||||
import { PrismaInstrumentation } from "@prisma/instrumentation";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { getEnvVariable, getNextRuntime, getNodeEnvironment } from "@hexclave/shared/dist/utils/env";
|
||||
import { sentryBaseConfig } from "@hexclave/shared/dist/utils/sentry";
|
||||
import { nicify } from "@hexclave/shared/dist/utils/strings";
|
||||
import { registerOTel } from '@vercel/otel';
|
||||
import { initPerfStats } from "./lib/dev-perf-stats";
|
||||
import "./polyfills";
|
||||
|
||||
// this is a hack for making prisma instrumentation work
|
||||
// somehow prisma instrumentation accesses global and it makes edge instrumentation complain
|
||||
globalThis.global = globalThis;
|
||||
|
||||
export async function register() {
|
||||
registerOTel({
|
||||
serviceName: 'stack-backend',
|
||||
instrumentations: [
|
||||
new PrismaInstrumentation(),
|
||||
...getNextRuntime() === "nodejs" ? getNodeAutoInstrumentations({
|
||||
'@opentelemetry/instrumentation-http': {
|
||||
enabled: false,
|
||||
},
|
||||
}) : [],
|
||||
],
|
||||
...getNodeEnvironment() === "development" && getNextRuntime() === "nodejs" ? {
|
||||
traceExporter: new OTLPTraceExporter({
|
||||
url: `http://localhost:${getEnvVariable("NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX", "81")}31/v1/traces`,
|
||||
}),
|
||||
} : {},
|
||||
});
|
||||
|
||||
if (getNextRuntime() === "nodejs") {
|
||||
(globalThis as any).process.title = `stack-backend:${getEnvVariable("NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX", "81")} (node/nextjs)`;
|
||||
|
||||
// Initialize performance stats collection in development
|
||||
initPerfStats();
|
||||
}
|
||||
|
||||
if (getNextRuntime() === "nodejs" || getNextRuntime() === "edge") {
|
||||
Sentry.init({
|
||||
...sentryBaseConfig,
|
||||
|
||||
dsn: getEnvVariable("NEXT_PUBLIC_SENTRY_DSN", ""),
|
||||
|
||||
enabled: getNodeEnvironment() !== "development" && !getEnvVariable("CI", ""),
|
||||
|
||||
// Add exception metadata to the event
|
||||
beforeSend(event, hint) {
|
||||
const error = hint.originalException;
|
||||
let nicified;
|
||||
try {
|
||||
nicified = nicify(error, { maxDepth: 8 });
|
||||
} catch (e) {
|
||||
nicified = `Error occurred during nicification: ${e}`;
|
||||
}
|
||||
if (error instanceof Error) {
|
||||
event.extra = {
|
||||
...event.extra,
|
||||
cause: error.cause,
|
||||
errorProps: {
|
||||
...error,
|
||||
},
|
||||
nicifiedError: nicified,
|
||||
};
|
||||
}
|
||||
return event;
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ import { getEnvVariable } from "@hexclave/shared/dist/utils/env";
|
||||
import { HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { isIpAddress } from "@hexclave/shared/dist/utils/ips";
|
||||
import { pick } from "@hexclave/shared/dist/utils/objects";
|
||||
import { headers } from "@/lib/runtime/headers";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
// An end user is a person sitting behind a computer screen.
|
||||
//
|
||||
|
||||
@ -1,128 +0,0 @@
|
||||
import { getRequestContext, ResponseCookieOptions } from "./request-context";
|
||||
|
||||
type CookieSetObject = ResponseCookieOptions & {
|
||||
name: string,
|
||||
value: string,
|
||||
};
|
||||
|
||||
type CookieDeleteObject = {
|
||||
name: string,
|
||||
};
|
||||
|
||||
function encodeCookieComponent(value: string) {
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
|
||||
function normalizeSameSite(sameSite: ResponseCookieOptions["sameSite"]) {
|
||||
if (sameSite === true) {
|
||||
return "Strict";
|
||||
}
|
||||
if (sameSite === "lax") {
|
||||
return "Lax";
|
||||
}
|
||||
if (sameSite === "strict") {
|
||||
return "Strict";
|
||||
}
|
||||
if (sameSite === "none") {
|
||||
return "None";
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function serializeSetCookie(name: string, value: string, options: ResponseCookieOptions = {}) {
|
||||
const parts = [`${encodeCookieComponent(name)}=${encodeCookieComponent(value)}`];
|
||||
|
||||
if (options.domain != null) {
|
||||
parts.push(`Domain=${options.domain}`);
|
||||
}
|
||||
parts.push(`Path=${options.path ?? "/"}`);
|
||||
const expires = options.expires != null
|
||||
? options.expires instanceof Date ? options.expires : new Date(options.expires)
|
||||
: options.maxAge != null ? new Date(Date.now() + options.maxAge * 1000) : undefined;
|
||||
if (expires != null) {
|
||||
parts.push(`Expires=${expires.toUTCString()}`);
|
||||
}
|
||||
if (options.maxAge != null) {
|
||||
parts.push(`Max-Age=${Math.trunc(options.maxAge)}`);
|
||||
}
|
||||
// Emit Secure before HttpOnly to match the attribute order the pre-ElysiaJS
|
||||
// (Next.js) backend produced. Cookie attribute order is semantically irrelevant
|
||||
// per RFC 6265, but the e2e suite asserts the exact Set-Cookie string for the
|
||||
// oauth-inner cookies (`...; Max-Age=N;( Secure;)? HttpOnly`), so keeping this
|
||||
// order avoids a spurious regression there.
|
||||
if (options.secure === true) {
|
||||
parts.push("Secure");
|
||||
}
|
||||
if (options.httpOnly === true) {
|
||||
parts.push("HttpOnly");
|
||||
}
|
||||
|
||||
const sameSite = normalizeSameSite(options.sameSite);
|
||||
if (sameSite != null) {
|
||||
parts.push(`SameSite=${sameSite}`);
|
||||
}
|
||||
if (options.priority != null) {
|
||||
parts.push(`Priority=${options.priority}`);
|
||||
}
|
||||
|
||||
return parts.join("; ");
|
||||
}
|
||||
|
||||
export async function headers() {
|
||||
return getRequestContext().headers;
|
||||
}
|
||||
|
||||
export async function cookies() {
|
||||
const context = getRequestContext();
|
||||
|
||||
return {
|
||||
get(name: string) {
|
||||
const pending = [...context.pendingSetCookies].reverse().find(cookie => cookie.name === name);
|
||||
if (pending != null) {
|
||||
return {
|
||||
name,
|
||||
value: pending.value,
|
||||
};
|
||||
}
|
||||
|
||||
const value = context.incomingCookies.get(name);
|
||||
return value == null ? undefined : {
|
||||
name,
|
||||
value,
|
||||
};
|
||||
},
|
||||
|
||||
set(nameOrCookie: string | CookieSetObject, value?: string, options?: ResponseCookieOptions) {
|
||||
const cookie = typeof nameOrCookie === "string"
|
||||
? {
|
||||
name: nameOrCookie,
|
||||
value: value ?? "",
|
||||
options: options ?? {},
|
||||
}
|
||||
: {
|
||||
name: nameOrCookie.name,
|
||||
value: nameOrCookie.value,
|
||||
options: nameOrCookie,
|
||||
};
|
||||
|
||||
context.pendingSetCookies.push(cookie);
|
||||
context.incomingCookies.set(cookie.name, cookie.value);
|
||||
},
|
||||
|
||||
delete(nameOrCookie: string | CookieDeleteObject) {
|
||||
const name = typeof nameOrCookie === "string" ? nameOrCookie : nameOrCookie.name;
|
||||
const cookie = {
|
||||
name,
|
||||
value: "",
|
||||
options: {
|
||||
expires: new Date(0),
|
||||
maxAge: 0,
|
||||
path: "/",
|
||||
},
|
||||
};
|
||||
context.pendingSetCookies = context.pendingSetCookies.filter(c => c.name !== name);
|
||||
context.deletedCookies.push(cookie);
|
||||
context.incomingCookies.delete(name);
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
export class NextRedirectError extends Error {
|
||||
digest = "NEXT_REDIRECT";
|
||||
redirectUrl: string;
|
||||
redirectStatus: 307 | 308;
|
||||
|
||||
constructor(url: string, status: 307 | 308) {
|
||||
super("NEXT_REDIRECT");
|
||||
this.redirectUrl = url;
|
||||
this.redirectStatus = status;
|
||||
}
|
||||
}
|
||||
|
||||
export class NextNotFoundError extends Error {
|
||||
digest = "NEXT_NOT_FOUND";
|
||||
|
||||
constructor() {
|
||||
super("NEXT_NOT_FOUND");
|
||||
}
|
||||
}
|
||||
|
||||
export const RedirectType = {
|
||||
push: "push",
|
||||
replace: "replace",
|
||||
} as const;
|
||||
|
||||
export function redirect(url: string, _type?: typeof RedirectType[keyof typeof RedirectType]): never {
|
||||
throw new NextRedirectError(url, 307);
|
||||
}
|
||||
|
||||
export function permanentRedirect(url: string): never {
|
||||
throw new NextRedirectError(url, 308);
|
||||
}
|
||||
|
||||
export function notFound(): never {
|
||||
throw new NextNotFoundError();
|
||||
}
|
||||
|
||||
export function usePathname(): never {
|
||||
throw new Error("next/navigation usePathname() was called in the backend runtime");
|
||||
}
|
||||
|
||||
export function useSearchParams(): never {
|
||||
throw new Error("next/navigation useSearchParams() was called in the backend runtime");
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
|
||||
export type CookieWrite = {
|
||||
name: string,
|
||||
value: string,
|
||||
options: ResponseCookieOptions,
|
||||
};
|
||||
|
||||
export type ResponseCookieOptions = {
|
||||
domain?: string,
|
||||
expires?: Date | number | string,
|
||||
httpOnly?: boolean,
|
||||
maxAge?: number,
|
||||
path?: string,
|
||||
priority?: "low" | "medium" | "high",
|
||||
sameSite?: boolean | "lax" | "strict" | "none",
|
||||
secure?: boolean,
|
||||
};
|
||||
|
||||
export type RequestContext = {
|
||||
headers: Headers,
|
||||
incomingCookies: Map<string, string>,
|
||||
pendingSetCookies: CookieWrite[],
|
||||
deletedCookies: CookieWrite[],
|
||||
};
|
||||
|
||||
export const requestContextALS = new AsyncLocalStorage<RequestContext>();
|
||||
|
||||
export function getRequestContext() {
|
||||
const context = requestContextALS.getStore();
|
||||
if (context == null) {
|
||||
throw new Error("Backend request context is only available while handling a backend request");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export function parseCookieHeader(cookieHeader: string | null) {
|
||||
const cookies = new Map<string, string>();
|
||||
if (cookieHeader == null || cookieHeader === "") {
|
||||
return cookies;
|
||||
}
|
||||
for (const part of cookieHeader.split(";")) {
|
||||
const separatorIndex = part.indexOf("=");
|
||||
if (separatorIndex < 0) {
|
||||
continue;
|
||||
}
|
||||
const name = part.slice(0, separatorIndex).trim();
|
||||
if (name === "") {
|
||||
continue;
|
||||
}
|
||||
const rawValue = part.slice(separatorIndex + 1).trim();
|
||||
cookies.set(name, rawValue);
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import * as Sentry from "@sentry/node";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { getEnvVariable, getNodeEnvironment } from "@hexclave/shared/dist/utils/env";
|
||||
import { captureError, registerErrorSink } from "@hexclave/shared/dist/utils/errors";
|
||||
import * as util from "util";
|
||||
|
||||
@ -5,6 +5,8 @@ import apiVersions from './generated/api-versions.json';
|
||||
import routes from './generated/routes.json';
|
||||
import './polyfills';
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { SmartRouter } from './smart-router';
|
||||
|
||||
const DEV_RATE_LIMIT_MAX_REQUESTS = 100;
|
||||
@ -63,7 +65,7 @@ const corsAllowedRequestHeadersWithAliases = withHexclaveHeaderAliases(corsAllow
|
||||
const corsAllowedResponseHeadersWithAliases = withHexclaveHeaderAliases(corsAllowedResponseHeaders);
|
||||
|
||||
// This function can be marked `async` if using `await` inside
|
||||
export async function proxy(request: Request) {
|
||||
export async function proxy(request: NextRequest) {
|
||||
const url = new URL(request.url);
|
||||
const delay = +getEnvVariable('STACK_ARTIFICIAL_DEVELOPMENT_DELAY_MS', '0');
|
||||
if (delay) {
|
||||
@ -96,7 +98,7 @@ export async function proxy(request: Request) {
|
||||
const waitMs = Math.max(0, DEV_RATE_LIMIT_WINDOW_MS - (now - devRateLimitTimestamps[0]));
|
||||
const retryAfterSeconds = Math.max(1, Math.ceil(waitMs / 1000));
|
||||
|
||||
const response = Response.json({
|
||||
const response = NextResponse.json({
|
||||
message: 'Artificial development rate limit triggered. Wait before retrying.',
|
||||
}, {
|
||||
status: 429,
|
||||
@ -171,11 +173,9 @@ export async function proxy(request: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
const newUrl = new URL(request.url);
|
||||
const newUrl = request.nextUrl.clone();
|
||||
newUrl.pathname = pathname;
|
||||
const rewriteHeaders = new Headers(responseInit?.headers);
|
||||
rewriteHeaders.set("x-middleware-rewrite", newUrl.toString());
|
||||
return new Response(null, { ...responseInit, headers: rewriteHeaders });
|
||||
return NextResponse.rewrite(newUrl, responseInit);
|
||||
}
|
||||
|
||||
// See "Matching Paths" below to learn more
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import "../polyfills";
|
||||
|
||||
import { yupArray, yupNumber, yupObject, yupString } from "@hexclave/shared/dist/schema-fields";
|
||||
import { NextRequest } from "next/server";
|
||||
import { createSmartRouteHandler } from "./smart-route-handler";
|
||||
|
||||
export function redirectHandler(redirectPath: string, statusCode: 303 | 307 | 308 = 307): (req: Request, options: any) => Promise<Response> {
|
||||
export function redirectHandler(redirectPath: string, statusCode: 303 | 307 | 308 = 307): (req: NextRequest, options: any) => Promise<Response> {
|
||||
return createSmartRouteHandler({
|
||||
request: yupObject({
|
||||
url: yupString().defined(),
|
||||
|
||||
@ -15,6 +15,7 @@ import { getEnvVariable, getNodeEnvironment } from "@hexclave/shared/dist/utils/
|
||||
import { HexclaveAssertionError, StatusError, captureError, throwErr } from "@hexclave/shared/dist/utils/errors";
|
||||
import { deindent } from "@hexclave/shared/dist/utils/strings";
|
||||
import { traceSpan, withTraceSpan } from "@hexclave/shared/dist/utils/telemetry";
|
||||
import { NextRequest } from "next/server";
|
||||
import * as yup from "yup";
|
||||
|
||||
const allowedMethods = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] as const;
|
||||
@ -69,7 +70,7 @@ export type MergeSmartRequest<T, MSQ = SmartRequest> =
|
||||
)
|
||||
);
|
||||
|
||||
async function validate<T>(obj: SmartRequest, schema: yup.Schema<T>, req: Request | null): Promise<T> {
|
||||
async function validate<T>(obj: SmartRequest, schema: yup.Schema<T>, req: NextRequest | null): Promise<T> {
|
||||
try {
|
||||
return await yupValidate(schema, obj, {
|
||||
abortEarly: false,
|
||||
@ -100,7 +101,7 @@ async function validate<T>(obj: SmartRequest, schema: yup.Schema<T>, req: Reques
|
||||
|
||||
throw new KnownErrors.SchemaError(
|
||||
deindent`
|
||||
Request validation failed on ${req.method} ${new URL(req.url).pathname}:
|
||||
Request validation failed on ${req.method} ${req.nextUrl.pathname}:
|
||||
${inners.map(e => deindent`
|
||||
- ${e.message}
|
||||
`).join("\n")}
|
||||
@ -113,7 +114,7 @@ async function validate<T>(obj: SmartRequest, schema: yup.Schema<T>, req: Reques
|
||||
}
|
||||
|
||||
|
||||
async function parseBody(req: Request, bodyBuffer: ArrayBuffer): Promise<SmartRequest["body"]> {
|
||||
async function parseBody(req: NextRequest, bodyBuffer: ArrayBuffer): Promise<SmartRequest["body"]> {
|
||||
const contentType = req.method === "GET" || req.method === "HEAD" ? undefined : req.headers.get("content-type")?.split(";")[0];
|
||||
|
||||
const getText = () => {
|
||||
@ -157,7 +158,7 @@ async function parseBody(req: Request, bodyBuffer: ArrayBuffer): Promise<SmartRe
|
||||
}
|
||||
}
|
||||
|
||||
const parseAuth = withTraceSpan('smart request parseAuth', async (req: Request): Promise<SmartRequestAuth | null> => {
|
||||
const parseAuth = withTraceSpan('smart request parseAuth', async (req: NextRequest): Promise<SmartRequestAuth | null> => {
|
||||
const projectId = req.headers.get("x-stack-project-id");
|
||||
const branchId = req.headers.get("x-stack-branch-id") ?? DEFAULT_BRANCH_ID;
|
||||
let requestType = req.headers.get("x-stack-access-type");
|
||||
@ -336,7 +337,7 @@ const parseAuth = withTraceSpan('smart request parseAuth', async (req: Request):
|
||||
};
|
||||
});
|
||||
|
||||
export async function createSmartRequest(req: Request, bodyBuffer: ArrayBuffer, options?: { params: Promise<Record<string, string>> }): Promise<SmartRequest> {
|
||||
export async function createSmartRequest(req: NextRequest, bodyBuffer: ArrayBuffer, options?: { params: Promise<Record<string, string>> }): Promise<SmartRequest> {
|
||||
return await traceSpan("creating smart request", async () => {
|
||||
const urlObject = new URL(req.url);
|
||||
const clientVersionMatch = req.headers.get("x-stack-client-version")?.match(/^(\w+)\s+(@[\w\/]+)@([\d.]+)$/);
|
||||
@ -362,6 +363,6 @@ export async function createSmartRequest(req: Request, bodyBuffer: ArrayBuffer,
|
||||
});
|
||||
}
|
||||
|
||||
export async function validateSmartRequest<T extends DeepPartialSmartRequestWithSentinel>(nextReq: Request | null, smartReq: SmartRequest, schema: yup.Schema<T>): Promise<T> {
|
||||
export async function validateSmartRequest<T extends DeepPartialSmartRequestWithSentinel>(nextReq: NextRequest | null, smartReq: SmartRequest, schema: yup.Schema<T>): Promise<T> {
|
||||
return await validate(smartReq, schema, nextReq);
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import { HexclaveAssertionError } from "@hexclave/shared/dist/utils/errors";
|
||||
import { Json } from "@hexclave/shared/dist/utils/json";
|
||||
import { deepPlainEquals } from "@hexclave/shared/dist/utils/objects";
|
||||
import { traceSpan } from "@hexclave/shared/dist/utils/telemetry";
|
||||
import { NextRequest } from "next/server";
|
||||
import * as yup from "yup";
|
||||
import "../polyfills";
|
||||
import { SmartRequest } from "./smart-request";
|
||||
@ -41,7 +42,7 @@ export type SmartResponse = {
|
||||
}
|
||||
);
|
||||
|
||||
export async function validateSmartResponse<T>(req: Request | null, smartReq: SmartRequest, obj: unknown, schema: yup.Schema<T>): Promise<T> {
|
||||
export async function validateSmartResponse<T>(req: NextRequest | null, smartReq: SmartRequest, obj: unknown, schema: yup.Schema<T>): Promise<T> {
|
||||
try {
|
||||
return await yupValidate(schema, obj, {
|
||||
abortEarly: false,
|
||||
@ -67,7 +68,7 @@ function isResponseBody(body: unknown): body is Response {
|
||||
return typeof body === "object" && body !== null && body instanceof Response;
|
||||
}
|
||||
|
||||
export async function createResponse<T extends SmartResponse>(req: Request | null, requestId: string, obj: T): Promise<Response> {
|
||||
export async function createResponse<T extends SmartResponse>(req: NextRequest | null, requestId: string, obj: T): Promise<Response> {
|
||||
return await traceSpan("creating HTTP response from smart response", async () => {
|
||||
let status = obj.statusCode;
|
||||
const headers = new Map<string, string[]>();
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import "../polyfills";
|
||||
|
||||
import { recordRequestStats } from "@/lib/dev-request-stats";
|
||||
import * as Sentry from "@sentry/node";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { EndpointDocumentation } from "@hexclave/shared/dist/crud";
|
||||
import { KnownError, KnownErrors } from "@hexclave/shared/dist/known-errors";
|
||||
import { generateSecureRandomString } from "@hexclave/shared/dist/utils/crypto";
|
||||
@ -9,6 +9,7 @@ import { getNodeEnvironment } from "@hexclave/shared/dist/utils/env";
|
||||
import { HexclaveAssertionError, StatusError, captureError, errorToNiceString } from "@hexclave/shared/dist/utils/errors";
|
||||
import { runAsynchronously, wait } from "@hexclave/shared/dist/utils/promises";
|
||||
import { traceSpan } from "@hexclave/shared/dist/utils/telemetry";
|
||||
import { NextRequest } from "next/server";
|
||||
import * as yup from "yup";
|
||||
import { DeepPartialSmartRequestWithSentinel, MergeSmartRequest, SmartRequest, createSmartRequest, validateSmartRequest } from "./smart-request";
|
||||
import { SmartResponse, createResponse, validateSmartResponse } from "./smart-response";
|
||||
@ -64,8 +65,8 @@ let concurrentRequestsInProcess = 0;
|
||||
* Catches any errors thrown in the handler and returns a 500 response with the thrown error message. Also logs the
|
||||
* request details.
|
||||
*/
|
||||
export function handleApiRequest(handler: (req: Request, options: any, requestId: string) => Promise<Response>): (req: Request, options: any) => Promise<Response> {
|
||||
return async (req: Request, options: any) => {
|
||||
export function handleApiRequest(handler: (req: NextRequest, options: any, requestId: string) => Promise<Response>): (req: NextRequest, options: any) => Promise<Response> {
|
||||
return async (req: NextRequest, options: any) => {
|
||||
concurrentRequestsInProcess++;
|
||||
try {
|
||||
const requestId = generateSecureRandomString(80);
|
||||
@ -79,13 +80,12 @@ export function handleApiRequest(handler: (req: Request, options: any, requestId
|
||||
"stack.process.concurrent-requests": concurrentRequestsInProcess,
|
||||
},
|
||||
}, async (span) => {
|
||||
const requestUrl = new URL(req.url);
|
||||
// Set Sentry scope to include request details
|
||||
Sentry.setContext("stack-request", {
|
||||
requestId: requestId,
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
query: Object.fromEntries(requestUrl.searchParams),
|
||||
query: Object.fromEntries(req.nextUrl.searchParams),
|
||||
headers: Object.fromEntries(req.headers),
|
||||
});
|
||||
|
||||
@ -121,11 +121,11 @@ export function handleApiRequest(handler: (req: Request, options: any, requestId
|
||||
...allowedLongRequestPaths,
|
||||
...allowedLongRequestPaths.map(path => path.replace(/^\/api\/latest\//, "/api/v1/")),
|
||||
];
|
||||
const warnAfterSeconds = allAllowedLongRequestPaths.includes(requestUrl.pathname) ? 240 : 12;
|
||||
const warnAfterSeconds = allAllowedLongRequestPaths.includes(req.nextUrl.pathname) ? 240 : 12;
|
||||
runAsynchronously(async () => {
|
||||
await wait(warnAfterSeconds * 1000);
|
||||
if (!hasRequestFinished) {
|
||||
captureError("request-timeout-watcher", new Error(`Request with ID ${requestId} to ${req.method} ${requestUrl.pathname} has been running for ${warnAfterSeconds} seconds. Try to keep requests short. The request may be cancelled by the serverless provider if it takes too long.`));
|
||||
captureError("request-timeout-watcher", new Error(`Request with ID ${requestId} to ${req.method} ${req.nextUrl.pathname} has been running for ${warnAfterSeconds} seconds. Try to keep requests short. The request may be cancelled by the serverless provider if it takes too long.`));
|
||||
}
|
||||
});
|
||||
|
||||
@ -135,10 +135,10 @@ export function handleApiRequest(handler: (req: Request, options: any, requestId
|
||||
const time = (performance.now() - timeStart);
|
||||
|
||||
// Record request stats for dev-stats page
|
||||
recordRequestStats(req.method, requestUrl.pathname, time);
|
||||
recordRequestStats(req.method, req.nextUrl.pathname, time);
|
||||
|
||||
if ([301, 302].includes(res.status)) {
|
||||
throw new HexclaveAssertionError("HTTP status codes 301 and 302 should not be returned by our APIs because the behavior for non-GET methods is inconsistent across implementations. Use 303 (to rewrite method to GET) or 307/308 (to preserve the original method and data) instead.", { status: res.status, url: requestUrl, req, res });
|
||||
throw new HexclaveAssertionError("HTTP status codes 301 and 302 should not be returned by our APIs because the behavior for non-GET methods is inconsistent across implementations. Use 303 (to rewrite method to GET) or 307/308 (to preserve the original method and data) instead.", { status: res.status, url: req.nextUrl, req, res });
|
||||
}
|
||||
if (!disableExtendedLogging) console.log(`[ RES] [${requestId}] ${req.method} ${censoredUrl}: ${res.status} (in ${time.toFixed(0)}ms)`);
|
||||
return res;
|
||||
@ -201,7 +201,7 @@ export type SmartRouteHandler<
|
||||
Req extends DeepPartialSmartRequestWithSentinel = DeepPartialSmartRequestWithSentinel,
|
||||
Res extends SmartResponse = SmartResponse,
|
||||
InitArgs extends [readonly OverloadParam[], SmartRouteHandlerOverloadGenerator<OverloadParam, Req, Res>] | [SmartRouteHandlerOverload<Req, Res>] = any,
|
||||
> = ((req: Request, options: any) => Promise<Response>) & {
|
||||
> = ((req: NextRequest, options: any) => Promise<Response>) & {
|
||||
overloads: Map<OverloadParam, SmartRouteHandlerOverload<Req, Res>>,
|
||||
invoke: (smartRequest: SmartRequest) => Promise<Res>,
|
||||
initArgs: InitArgs,
|
||||
@ -247,7 +247,7 @@ export function createSmartRouteHandler<
|
||||
throw new HexclaveAssertionError("Duplicate overload parameters");
|
||||
}
|
||||
|
||||
const invoke = async (nextRequest: Request | null, requestId: string, smartRequest: SmartRequest, shouldSetContext: boolean = false) => {
|
||||
const invoke = async (nextRequest: NextRequest | null, requestId: string, smartRequest: SmartRequest, shouldSetContext: boolean = false) => {
|
||||
const reqsParsed: [[Req, SmartRequest], SmartRouteHandlerOverload<Req, Res>][] = [];
|
||||
const reqsErrors: unknown[] = [];
|
||||
for (const [overloadParam, overload] of overloads.entries()) {
|
||||
|
||||
@ -1,312 +0,0 @@
|
||||
import { httpMethodNames } from "@/generated/route-modules";
|
||||
import { serializeSetCookie } from "@/lib/runtime/headers";
|
||||
import { NextNotFoundError } from "@/lib/runtime/navigation";
|
||||
import { parseCookieHeader, requestContextALS, type RequestContext } from "@/lib/runtime/request-context";
|
||||
import { node } from "@elysiajs/node";
|
||||
import { getEnvVariable, getNodeEnvironment } from "@hexclave/shared/dist/utils/env";
|
||||
import { Elysia } from "elysia";
|
||||
import { runRequestPipeline } from "./middleware";
|
||||
import { createBackendRequest } from "./backend-request";
|
||||
import { MalformedRouteParamError, matchRoute } from "./registry";
|
||||
|
||||
const globalSecurityHeaders = {
|
||||
"Cross-Origin-Opener-Policy": "same-origin",
|
||||
"Permissions-Policy": "",
|
||||
"Referrer-Policy": "strict-origin-when-cross-origin",
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
"X-Frame-Options": "SAMEORIGIN",
|
||||
"Content-Security-Policy": "",
|
||||
};
|
||||
const knownHttpMethods = new Set<string>(httpMethodNames);
|
||||
const developmentRequestStartTimes = new WeakMap<Request, number>();
|
||||
const shouldLogDevelopmentRequests = getNodeEnvironment() === "development";
|
||||
|
||||
export const app = new Elysia({
|
||||
adapter: node(),
|
||||
})
|
||||
.onRequest(({ request }) => {
|
||||
if (shouldLogDevelopmentRequests) {
|
||||
developmentRequestStartTimes.set(request, performance.now());
|
||||
}
|
||||
})
|
||||
.onAfterResponse(({ request, set }) => {
|
||||
if (!shouldLogDevelopmentRequests) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = developmentRequestStartTimes.get(request);
|
||||
const elapsedMilliseconds = startTime == null ? "unknown" : (performance.now() - startTime).toFixed(1);
|
||||
const pathname = new URL(request.url).pathname;
|
||||
console.log(`[Elysia] ${request.method} ${pathname} ${set.status} ${elapsedMilliseconds}ms`);
|
||||
})
|
||||
.get("/", () => htmlResponse(homeHtml()))
|
||||
.get("/dev-stats", () => htmlResponse(devStatsHtml()))
|
||||
.get("/health/error-handler-debug", () => htmlResponse(errorHandlerDebugHtml()))
|
||||
.post("/monitoring", async ({ request }) => withGlobalHeaders(await handleMonitoringTunnel(request)), {
|
||||
parse: "none",
|
||||
})
|
||||
.all("/*", async ({ request }) => await dispatch(request), {
|
||||
parse: "none",
|
||||
});
|
||||
|
||||
export async function dispatch(request: Request) {
|
||||
const pipeline = await runRequestPipeline(request);
|
||||
if (pipeline.shortCircuitResponse != null) {
|
||||
return withGlobalHeaders(pipeline.shortCircuitResponse);
|
||||
}
|
||||
|
||||
let match;
|
||||
try {
|
||||
match = matchRoute(pipeline.dispatchPath);
|
||||
} catch (error) {
|
||||
if (error instanceof MalformedRouteParamError) {
|
||||
return withGlobalHeaders(new Response("Bad Request", { status: 400 }));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
if (match == null) {
|
||||
return withGlobalHeaders(new Response("<div>404 Not Found</div>", {
|
||||
status: 404,
|
||||
headers: {
|
||||
"content-type": "text/html; charset=utf-8",
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
const method = request.method.toUpperCase();
|
||||
if (!isHttpMethod(method)) {
|
||||
return withGlobalHeaders(new Response(null, {
|
||||
status: 405,
|
||||
}));
|
||||
}
|
||||
const handler = match.methods.get(method) ?? (method === "HEAD" ? match.methods.get("GET") : undefined);
|
||||
if (handler == null) {
|
||||
return withGlobalHeaders(new Response(null, {
|
||||
status: 405,
|
||||
}));
|
||||
}
|
||||
|
||||
const backendRequest = createBackendRequest(request, pipeline.mergedHeaders, pipeline.originalUrl);
|
||||
const context: RequestContext = {
|
||||
headers: pipeline.mergedHeaders,
|
||||
incomingCookies: parseCookieHeader(pipeline.mergedHeaders.get("cookie")),
|
||||
pendingSetCookies: [],
|
||||
deletedCookies: [],
|
||||
};
|
||||
|
||||
const response = await requestContextALS.run(context, async () => {
|
||||
try {
|
||||
return await handler(backendRequest, {
|
||||
params: Promise.resolve(match.params),
|
||||
});
|
||||
} catch (error) {
|
||||
if (isRedirectError(error)) {
|
||||
return new Response(null, {
|
||||
status: error.redirectStatus,
|
||||
headers: {
|
||||
Location: error.redirectUrl,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (error instanceof NextNotFoundError) {
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
const finalResponse = method === "HEAD" ? new Response(null, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
}) : response;
|
||||
|
||||
for (const cookie of context.pendingSetCookies) {
|
||||
finalResponse.headers.append("Set-Cookie", serializeSetCookie(cookie.name, cookie.value, cookie.options));
|
||||
}
|
||||
for (const cookie of context.deletedCookies) {
|
||||
finalResponse.headers.append("Set-Cookie", serializeSetCookie(cookie.name, cookie.value, cookie.options));
|
||||
}
|
||||
if (pipeline.corsHeadersInit != null) {
|
||||
for (const [key, value] of Object.entries(pipeline.corsHeadersInit)) {
|
||||
finalResponse.headers.set(key, value);
|
||||
}
|
||||
}
|
||||
if (pipeline.middlewareRewrite != null) {
|
||||
finalResponse.headers.set("x-middleware-rewrite", pipeline.middlewareRewrite);
|
||||
}
|
||||
return withGlobalHeaders(finalResponse);
|
||||
}
|
||||
|
||||
function isRedirectError(error: unknown): error is { redirectStatus: 307 | 308, redirectUrl: string } {
|
||||
if (!(error instanceof Error) || !("digest" in error) || !("redirectUrl" in error) || !("redirectStatus" in error)) {
|
||||
return false;
|
||||
}
|
||||
return typeof error.digest === "string"
|
||||
&& error.digest.startsWith("NEXT_REDIRECT")
|
||||
&& typeof error.redirectUrl === "string"
|
||||
&& (error.redirectStatus === 307 || error.redirectStatus === 308);
|
||||
}
|
||||
|
||||
function withGlobalHeaders(response: Response) {
|
||||
for (const [key, value] of Object.entries(globalSecurityHeaders)) {
|
||||
response.headers.set(key, value);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
function htmlResponse(body: string, status = 200) {
|
||||
return withGlobalHeaders(new Response(body, {
|
||||
status,
|
||||
headers: {
|
||||
"content-type": "text/html; charset=utf-8",
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
function homeHtml() {
|
||||
const devStatsLink = getNodeEnvironment() === "development"
|
||||
? `<br><a href="/dev-stats">Dev Stats</a><br>`
|
||||
: "";
|
||||
return `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Hexclave API</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
Welcome to Hexclave's API endpoint.<br>
|
||||
<br>
|
||||
Were you looking for <a href="https://app.hexclave.com">Hexclave's dashboard</a> instead?<br>
|
||||
<br>
|
||||
You can also return to <a href="https://hexclave.com">https://hexclave.com</a>.<br>
|
||||
<br>
|
||||
<a href="/api/v1">API v1</a><br>
|
||||
${devStatsLink}
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
function devStatsHtml() {
|
||||
return `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Hexclave Backend Dev Stats</title>
|
||||
<style>
|
||||
body { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; margin: 24px; }
|
||||
button { font: inherit; padding: 4px 8px; }
|
||||
pre { background: #f6f8fa; border: 1px solid #d0d7de; padding: 12px; overflow: auto; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Dev Stats</h1>
|
||||
<button id="refresh">Refresh</button>
|
||||
<pre id="output">Loading...</pre>
|
||||
<script>
|
||||
async function refresh() {
|
||||
const output = document.getElementById("output");
|
||||
const response = await fetch("/dev-stats/api", { headers: { "accept": "application/json" } });
|
||||
output.textContent = JSON.stringify(await response.json(), null, 2);
|
||||
}
|
||||
document.getElementById("refresh").addEventListener("click", refresh);
|
||||
refresh();
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
function errorHandlerDebugHtml() {
|
||||
return `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Backend Error Debug</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
This page is useful for testing error handling.<br>
|
||||
Your observability platform should pick up on the errors thrown below.<br>
|
||||
<button id="client-error">Throw client error</button>
|
||||
<button id="server-error">Throw server error</button>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById("client-error").addEventListener("click", () => {
|
||||
throw new Error("Client debug error thrown successfully!");
|
||||
});
|
||||
document.getElementById("server-error").addEventListener("click", () => {
|
||||
fetch("/health/error-handler-debug/endpoint").then((response) => console.log("Endpoint response", response));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
async function handleMonitoringTunnel(request: Request) {
|
||||
const allowedDsn = getEnvVariable("NEXT_PUBLIC_SENTRY_DSN", "");
|
||||
if (allowedDsn === "") {
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
const envelope = await request.text();
|
||||
const firstLineEnd = envelope.indexOf("\n");
|
||||
const envelopeHeaderBytes = firstLineEnd === -1 ? envelope : envelope.slice(0, firstLineEnd);
|
||||
const envelopeDsn = getEnvelopeDsn(envelopeHeaderBytes);
|
||||
if (envelopeDsn !== allowedDsn) {
|
||||
return new Response("Invalid Sentry envelope DSN", {
|
||||
status: 400,
|
||||
headers: {
|
||||
"content-type": "text/plain; charset=utf-8",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const sentryDsnUrl = new URL(allowedDsn);
|
||||
const projectId = sentryDsnUrl.pathname.split("/").filter(Boolean).at(-1);
|
||||
if (projectId == null) {
|
||||
return new Response("Invalid configured Sentry DSN", {
|
||||
status: 500,
|
||||
headers: {
|
||||
"content-type": "text/plain; charset=utf-8",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const sentryEnvelopeUrl = new URL(`/api/${projectId}/envelope/`, sentryDsnUrl.origin);
|
||||
const sentryResponse = await fetch(sentryEnvelopeUrl, {
|
||||
method: "POST",
|
||||
body: envelope,
|
||||
headers: {
|
||||
"content-type": request.headers.get("content-type") ?? "application/x-sentry-envelope",
|
||||
},
|
||||
});
|
||||
return new Response(sentryResponse.body, {
|
||||
status: sentryResponse.status,
|
||||
statusText: sentryResponse.statusText,
|
||||
headers: sentryResponse.headers,
|
||||
});
|
||||
}
|
||||
|
||||
function getEnvelopeDsn(envelopeHeaderBytes: string) {
|
||||
let parsedEnvelopeHeader: unknown;
|
||||
try {
|
||||
parsedEnvelopeHeader = JSON.parse(envelopeHeaderBytes);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
parsedEnvelopeHeader == null
|
||||
|| typeof parsedEnvelopeHeader !== "object"
|
||||
|| !("dsn" in parsedEnvelopeHeader)
|
||||
|| typeof parsedEnvelopeHeader.dsn !== "string"
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return parsedEnvelopeHeader.dsn;
|
||||
}
|
||||
|
||||
function isHttpMethod(method: string): method is typeof httpMethodNames[number] {
|
||||
return knownHttpMethods.has(method);
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
type NodeRequestInit = RequestInit & {
|
||||
duplex?: "half",
|
||||
};
|
||||
|
||||
export function createBackendRequest(request: Request, headers: Headers, originalUrl: string): Request {
|
||||
const init: NodeRequestInit = {
|
||||
method: request.method,
|
||||
headers,
|
||||
};
|
||||
|
||||
if (request.method !== "GET" && request.method !== "HEAD") {
|
||||
init.body = request.body;
|
||||
init.duplex = "half";
|
||||
}
|
||||
|
||||
return new Request(originalUrl, init);
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
/* eslint-disable no-restricted-syntax -- This bootstrap normalizes process.env before getEnvVariable reads it. */
|
||||
|
||||
const envReferencePattern = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-(.*?))?\}|\$([A-Za-z_][A-Za-z0-9_]*)/g;
|
||||
|
||||
function expandEnvValue(value: string): string {
|
||||
return value.replace(envReferencePattern, (_match, bracedName: string | undefined, defaultValue: string | undefined, bareName: string | undefined) => {
|
||||
const name = bracedName ?? bareName;
|
||||
if (name == null) {
|
||||
return "";
|
||||
}
|
||||
const referencedValue = process.env[name];
|
||||
if (bracedName != null && defaultValue != null && (referencedValue == null || referencedValue === "")) {
|
||||
return defaultValue;
|
||||
}
|
||||
return referencedValue ?? "";
|
||||
});
|
||||
}
|
||||
|
||||
for (let iteration = 0; iteration < 10; iteration++) {
|
||||
let changed = false;
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (value == null || !envReferencePattern.test(value)) {
|
||||
envReferencePattern.lastIndex = 0;
|
||||
continue;
|
||||
}
|
||||
envReferencePattern.lastIndex = 0;
|
||||
const expandedValue = expandEnvValue(value);
|
||||
if (expandedValue !== value) {
|
||||
process.env[key] = expandedValue;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (!changed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1,244 +0,0 @@
|
||||
import apiVersions from "@/generated/api-versions.json";
|
||||
import routes from "@/generated/routes.json";
|
||||
import { SmartRouter } from "@/smart-router";
|
||||
import { getEnvVariable, getNodeEnvironment } from "@hexclave/shared/dist/utils/env";
|
||||
import { wait } from "@hexclave/shared/dist/utils/promises";
|
||||
|
||||
const DEV_RATE_LIMIT_MAX_REQUESTS = 100;
|
||||
const DEV_RATE_LIMIT_WINDOW_MS = 10_000;
|
||||
const devRateLimitMarks: number[] = [];
|
||||
|
||||
const corsAllowedRequestHeaders = [
|
||||
"content-type",
|
||||
"authorization",
|
||||
"x-stack-project-id",
|
||||
"x-stack-branch-id",
|
||||
"x-stack-override-error-status",
|
||||
"x-stack-random-nonce",
|
||||
"x-stack-client-version",
|
||||
"x-stack-disable-artificial-development-delay",
|
||||
"x-stack-access-type",
|
||||
"x-stack-publishable-client-key",
|
||||
"x-stack-secret-server-key",
|
||||
"x-stack-super-secret-admin-key",
|
||||
"x-stack-admin-access-token",
|
||||
"x-stack-refresh-token",
|
||||
"x-stack-access-token",
|
||||
"x-stack-allow-restricted-user",
|
||||
"x-stack-allow-anonymous-user",
|
||||
"baggage",
|
||||
"sentry-trace",
|
||||
"x-vercel-protection-bypass",
|
||||
"ngrok-skip-browser-warning",
|
||||
];
|
||||
|
||||
const corsAllowedResponseHeaders = [
|
||||
"content-type",
|
||||
"x-stack-actual-status",
|
||||
"x-stack-known-error",
|
||||
];
|
||||
|
||||
function withHexclaveHeaderAliases(headers: string[]): string[] {
|
||||
return headers.flatMap((header) => header.startsWith("x-stack-")
|
||||
? [header, `x-hexclave-${header.slice("x-stack-".length)}`]
|
||||
: [header]);
|
||||
}
|
||||
|
||||
const corsAllowedRequestHeadersWithAliases = withHexclaveHeaderAliases(corsAllowedRequestHeaders);
|
||||
const corsAllowedResponseHeadersWithAliases = withHexclaveHeaderAliases(corsAllowedResponseHeaders);
|
||||
|
||||
export type PipelineResult = {
|
||||
corsHeadersInit?: HeadersInit,
|
||||
dispatchPath: string,
|
||||
mergedHeaders: Headers,
|
||||
middlewareRewrite?: string,
|
||||
originalUrl: string,
|
||||
shortCircuitResponse?: Response,
|
||||
};
|
||||
|
||||
export async function runRequestPipeline(request: Request): Promise<PipelineResult> {
|
||||
const url = new URL(request.url);
|
||||
const mergedHeaders = mergeHexclaveHeaderAliases(request.headers);
|
||||
ensureForwardedForHeader(mergedHeaders, request);
|
||||
const delay = +getEnvVariable("STACK_ARTIFICIAL_DEVELOPMENT_DELAY_MS", "0");
|
||||
if (delay) {
|
||||
if (getNodeEnvironment().includes("production")) {
|
||||
throw new Error("STACK_ARTIFICIAL_DEVELOPMENT_DELAY_MS environment variable is only allowed in development");
|
||||
}
|
||||
if (!request.headers.get("x-stack-disable-artificial-development-delay")) {
|
||||
await wait(delay);
|
||||
}
|
||||
}
|
||||
|
||||
const isApiRequest = url.pathname.startsWith("/api/");
|
||||
const corsHeadersInit = isApiRequest ? {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
||||
"Access-Control-Max-Age": "86400",
|
||||
"Access-Control-Allow-Headers": corsAllowedRequestHeadersWithAliases.join(", "),
|
||||
"Access-Control-Expose-Headers": corsAllowedResponseHeadersWithAliases.join(", "),
|
||||
"Vary": corsAllowedRequestHeadersWithAliases.join(", "),
|
||||
} : undefined;
|
||||
|
||||
if (isApiRequest && !request.headers.get("x-stack-disable-artificial-development-delay") && getNodeEnvironment() === "development" && request.method !== "OPTIONS" && !request.url.includes(".well-known") && !request.url.includes("/api/latest/internal/external-db-sync/")) {
|
||||
const now = performance.now();
|
||||
while (devRateLimitMarks.length > 0 && now - devRateLimitMarks[0] > DEV_RATE_LIMIT_WINDOW_MS) {
|
||||
devRateLimitMarks.shift();
|
||||
}
|
||||
if (devRateLimitMarks.length >= DEV_RATE_LIMIT_MAX_REQUESTS) {
|
||||
const waitMs = Math.max(0, DEV_RATE_LIMIT_WINDOW_MS - (now - devRateLimitMarks[0]));
|
||||
const retryAfterSeconds = Math.max(1, Math.ceil(waitMs / 1000));
|
||||
|
||||
const response = Response.json({
|
||||
message: "Artificial development rate limit triggered. Wait before retrying.",
|
||||
}, {
|
||||
status: 429,
|
||||
});
|
||||
|
||||
if (Math.random() < 0.5 && corsHeadersInit) {
|
||||
for (const [key, value] of Object.entries(corsHeadersInit)) {
|
||||
response.headers.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.random() < 0.5) {
|
||||
response.headers.set("Retry-After", retryAfterSeconds.toString());
|
||||
}
|
||||
|
||||
return {
|
||||
corsHeadersInit,
|
||||
dispatchPath: url.pathname,
|
||||
mergedHeaders,
|
||||
originalUrl: request.url,
|
||||
shortCircuitResponse: response,
|
||||
};
|
||||
}
|
||||
devRateLimitMarks.push(now);
|
||||
}
|
||||
|
||||
if (request.method === "OPTIONS" && isApiRequest) {
|
||||
return {
|
||||
corsHeadersInit,
|
||||
dispatchPath: url.pathname,
|
||||
mergedHeaders,
|
||||
originalUrl: request.url,
|
||||
shortCircuitResponse: new Response(null, {
|
||||
headers: corsHeadersInit,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const dispatchPath = getDispatchPath(url.pathname);
|
||||
return {
|
||||
corsHeadersInit,
|
||||
dispatchPath,
|
||||
mergedHeaders,
|
||||
middlewareRewrite: dispatchPath === url.pathname ? undefined : dispatchPath,
|
||||
originalUrl: request.url,
|
||||
};
|
||||
}
|
||||
|
||||
function mergeHexclaveHeaderAliases(headers: Headers) {
|
||||
const newRequestHeaders = new Headers(headers);
|
||||
for (const [name, value] of headers) {
|
||||
if (name.startsWith("x-hexclave-")) {
|
||||
newRequestHeaders.set(`x-stack-${name.slice("x-hexclave-".length)}`, value);
|
||||
}
|
||||
}
|
||||
return newRequestHeaders;
|
||||
}
|
||||
|
||||
const clientIpForwardingHeaders = ["x-forwarded-for", "x-real-ip", "x-vercel-forwarded-for", "cf-connecting-ip"];
|
||||
|
||||
function ensureForwardedForHeader(headers: Headers, request: Request) {
|
||||
// Direct connections (local dev, proxy-less self-host) arrive without any forwarding
|
||||
// header, so getEndUserIp() finds no client IP and warns on every request. The Node
|
||||
// adapter still knows the socket's remote address, so synthesize x-forwarded-for from
|
||||
// it — mirroring what the old Next.js dev server did. Behind a real proxy (e.g. Vercel)
|
||||
// one of these headers is already set, so this stays a no-op there.
|
||||
if (clientIpForwardingHeaders.some((header) => headers.has(header))) {
|
||||
return;
|
||||
}
|
||||
const socketIp = readClientSocketIp(request);
|
||||
if (socketIp == null) {
|
||||
return;
|
||||
}
|
||||
headers.set("x-forwarded-for", normalizeClientIp(socketIp));
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
function readClientSocketIp(request: Request): string | undefined {
|
||||
// The Node adapter (srvx) augments the web Request with the resolved client IP and
|
||||
// the underlying Node socket, neither of which exist on the standard Request type.
|
||||
// Read them defensively at runtime instead of casting; on other runtimes (e.g. the
|
||||
// Vercel serverless entry) these are simply absent and we fall through to undefined.
|
||||
const directIp: unknown = Reflect.get(request, "ip");
|
||||
if (typeof directIp === "string" && directIp !== "") {
|
||||
return directIp;
|
||||
}
|
||||
const runtime: unknown = Reflect.get(request, "runtime");
|
||||
const node: unknown = isRecord(runtime) ? runtime.node : undefined;
|
||||
const req: unknown = isRecord(node) ? node.req : undefined;
|
||||
const socket: unknown = isRecord(req) ? req.socket : undefined;
|
||||
const remoteAddress: unknown = isRecord(socket) ? socket.remoteAddress : undefined;
|
||||
return typeof remoteAddress === "string" && remoteAddress !== "" ? remoteAddress : undefined;
|
||||
}
|
||||
|
||||
function normalizeClientIp(ip: string): string {
|
||||
// Node sockets report IPv4 clients as IPv4-mapped IPv6 (e.g. "::ffff:127.0.0.1").
|
||||
// Normalize to the plain IPv4 form so it matches what proxies put in x-forwarded-for.
|
||||
const mapped = ip.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
|
||||
return mapped ? mapped[1] : ip;
|
||||
}
|
||||
|
||||
import.meta.vitest?.test("ensureForwardedForHeader synthesizes the client IP for direct connections", ({ expect }) => {
|
||||
// Uses the adapter-provided client IP when no forwarding header is present.
|
||||
const fromIp = new Headers();
|
||||
ensureForwardedForHeader(fromIp, Object.assign(new Request("http://localhost/api/v1"), { ip: "203.0.113.7" }));
|
||||
expect(fromIp.get("x-forwarded-for")).toBe("203.0.113.7");
|
||||
|
||||
// Falls back to the raw Node socket and normalizes IPv4-mapped IPv6 to plain IPv4.
|
||||
const fromSocket = new Headers();
|
||||
ensureForwardedForHeader(fromSocket, Object.assign(new Request("http://localhost/api/v1"), {
|
||||
runtime: { node: { req: { socket: { remoteAddress: "::ffff:127.0.0.1" } } } },
|
||||
}));
|
||||
expect(fromSocket.get("x-forwarded-for")).toBe("127.0.0.1");
|
||||
|
||||
// Never overwrites an existing forwarding header (e.g. behind a trusted proxy like Vercel).
|
||||
const existing = new Headers({ "x-forwarded-for": "198.51.100.1" });
|
||||
ensureForwardedForHeader(existing, Object.assign(new Request("http://localhost/api/v1"), { ip: "203.0.113.7" }));
|
||||
expect(existing.get("x-forwarded-for")).toBe("198.51.100.1");
|
||||
|
||||
// No socket info available (e.g. an unusual runtime) → leaves headers untouched.
|
||||
const none = new Headers();
|
||||
ensureForwardedForHeader(none, new Request("http://localhost/api/v1"));
|
||||
expect(none.get("x-forwarded-for")).toBe(null);
|
||||
});
|
||||
|
||||
function getDispatchPath(originalPathname: string) {
|
||||
let pathname = originalPathname;
|
||||
outer: for (let i = 0; i < apiVersions.length - 1; i++) {
|
||||
const version = apiVersions[i];
|
||||
const nextVersion = apiVersions[i + 1];
|
||||
if (!nextVersion.migrationFolder) {
|
||||
throw new Error(`No migration folder found for version ${nextVersion.name}. This is a bug because every version except the first should have a migration folder.`);
|
||||
}
|
||||
if ((pathname + "/").startsWith(version.servedRoute + "/")) {
|
||||
const nextPathname = pathname.replace(version.servedRoute, nextVersion.servedRoute);
|
||||
const migrationPathname = nextPathname.replace(nextVersion.servedRoute, nextVersion.migrationFolder);
|
||||
for (const route of routes) {
|
||||
if (nextVersion.migrationFolder && (route.normalizedPath + "/").startsWith(nextVersion.migrationFolder + "/")) {
|
||||
if (SmartRouter.matchNormalizedPath(migrationPathname, route.normalizedPath)) {
|
||||
pathname = migrationPathname;
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
pathname = nextPathname;
|
||||
}
|
||||
}
|
||||
return pathname;
|
||||
}
|
||||
@ -1,126 +0,0 @@
|
||||
import { httpMethodNames, routeModules } from "@/generated/route-modules";
|
||||
import { SmartRouter } from "@/smart-router";
|
||||
|
||||
export type HttpMethod = typeof httpMethodNames[number];
|
||||
export type RouteParams = Record<string, string | string[]>;
|
||||
export type RouteHandlerOptions = { params: Promise<RouteParams> };
|
||||
export type RouteHandler = (request: Request, options: RouteHandlerOptions) => Promise<Response> | Response;
|
||||
export type UnknownRouteModule = Partial<Record<HttpMethod, unknown>>;
|
||||
type UnknownRouteFunction = (request: Request, options: RouteHandlerOptions) => unknown;
|
||||
|
||||
type RouteEntry = {
|
||||
methods: Map<HttpMethod, RouteHandler>,
|
||||
normalizedPath: string,
|
||||
specificity: number[],
|
||||
};
|
||||
|
||||
export type RouteMatch = {
|
||||
handler?: RouteHandler,
|
||||
methods: Map<HttpMethod, RouteHandler>,
|
||||
normalizedPath: string,
|
||||
params: Record<string, string | string[]>,
|
||||
};
|
||||
|
||||
export const routeRegistry = buildRouteRegistry();
|
||||
|
||||
export function matchRoute(dispatchPath: string): RouteMatch | undefined {
|
||||
for (const entry of routeRegistry) {
|
||||
const params = SmartRouter.matchNormalizedPath(dispatchPath, entry.normalizedPath);
|
||||
if (params !== false) {
|
||||
return {
|
||||
methods: entry.methods,
|
||||
normalizedPath: entry.normalizedPath,
|
||||
params: decodeRouteParams(params),
|
||||
};
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export class MalformedRouteParamError extends Error {
|
||||
constructor(param: string) {
|
||||
super(`Malformed percent-encoding in route parameter: ${param}`);
|
||||
this.name = "MalformedRouteParamError";
|
||||
}
|
||||
}
|
||||
|
||||
function strictDecodeURIComponent(value: string): string {
|
||||
try {
|
||||
return decodeURIComponent(value);
|
||||
} catch {
|
||||
throw new MalformedRouteParamError(value);
|
||||
}
|
||||
}
|
||||
|
||||
function decodeRouteParams(params: Record<string, string | string[]>): Record<string, string | string[]> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(params).map(([key, value]) => [
|
||||
key,
|
||||
Array.isArray(value) ? value.map(strictDecodeURIComponent) : strictDecodeURIComponent(value),
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function buildRouteRegistry() {
|
||||
return routeModules
|
||||
.map((route) => {
|
||||
const methods = new Map<HttpMethod, RouteHandler>();
|
||||
for (const method of httpMethodNames) {
|
||||
const handler = route.module[method];
|
||||
if (isRouteFunction(handler)) {
|
||||
methods.set(method, createRouteHandler(route.normalizedPath, method, handler));
|
||||
}
|
||||
}
|
||||
return {
|
||||
methods,
|
||||
normalizedPath: route.normalizedPath,
|
||||
specificity: getSpecificity(route.normalizedPath),
|
||||
};
|
||||
})
|
||||
.filter((route) => route.methods.size > 0)
|
||||
.sort(compareRouteEntries);
|
||||
}
|
||||
|
||||
function isRouteFunction(value: unknown): value is UnknownRouteFunction {
|
||||
return typeof value === "function";
|
||||
}
|
||||
|
||||
function createRouteHandler(normalizedPath: string, method: HttpMethod, handler: UnknownRouteFunction): RouteHandler {
|
||||
return async (request, options) => {
|
||||
const result = await handler(request, options);
|
||||
if (result instanceof Response) {
|
||||
return result;
|
||||
}
|
||||
throw new Error(`Route ${normalizedPath} ${method} did not return a Response`);
|
||||
};
|
||||
}
|
||||
|
||||
function compareRouteEntries(a: RouteEntry, b: RouteEntry) {
|
||||
const maxLength = Math.max(a.specificity.length, b.specificity.length);
|
||||
for (let i = 0; i < maxLength; i++) {
|
||||
const diff = (b.specificity[i] ?? 0) - (a.specificity[i] ?? 0);
|
||||
if (diff !== 0) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
return stringCompare(a.normalizedPath, b.normalizedPath);
|
||||
}
|
||||
|
||||
function getSpecificity(normalizedPath: string) {
|
||||
return normalizedPath.split("/").filter(Boolean).map((segment) => {
|
||||
if (segment.startsWith("[[...") && segment.endsWith("]]")) {
|
||||
return 0;
|
||||
}
|
||||
if (segment.startsWith("[...") && segment.endsWith("]")) {
|
||||
return 1;
|
||||
}
|
||||
if (segment.startsWith("[") && segment.endsWith("]")) {
|
||||
return 2;
|
||||
}
|
||||
return 3;
|
||||
});
|
||||
}
|
||||
|
||||
function stringCompare(a: string, b: string) {
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import "@/instrument";
|
||||
import "@/polyfills";
|
||||
import { getEnvVariable } from "@hexclave/shared/dist/utils/env";
|
||||
import { runAsynchronously } from "@hexclave/shared/dist/utils/promises";
|
||||
import { app } from "./app";
|
||||
import "./env-expand";
|
||||
|
||||
const portPrefix = getEnvVariable("NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX", "81");
|
||||
const port = Number(getEnvVariable("PORT", getEnvVariable("BACKEND_PORT", `${portPrefix}02`)));
|
||||
const hostname = getEnvVariable("HOSTNAME", "0.0.0.0");
|
||||
|
||||
app.listen({
|
||||
hostname,
|
||||
port,
|
||||
});
|
||||
|
||||
console.log(`Hexclave backend listening on http://${hostname}:${port}`);
|
||||
|
||||
process.once("SIGTERM", () => {
|
||||
process.exit(0);
|
||||
});
|
||||
@ -1,5 +0,0 @@
|
||||
import "@/instrument";
|
||||
import "@/polyfills";
|
||||
import { app } from "./app";
|
||||
|
||||
export default app;
|
||||
@ -103,9 +103,11 @@ function parseApiVersionStringToArray(version: string): [number, number] {
|
||||
|
||||
|
||||
function matchPath(path: string, toMatchWith: string): Record<string, string | string[]> | false {
|
||||
// get the relative part, and modify it to have a leading slash, without a trailing slash, without ./.., etc.
|
||||
const url = new URL(path + "/", "http://example.com");
|
||||
const modifiedPath = url.pathname.slice(1, -1);
|
||||
// Normalize to relative path segments without a trailing slash. Appending "/" before URL parsing
|
||||
// turns an already-trailing path into "//", which URL interprets as a protocol-relative URL during
|
||||
// the final recursive match and rejects because it has no host.
|
||||
const url = new URL(path === "" ? "/" : path, "http://example.com");
|
||||
const modifiedPath = url.pathname.slice(1).replace(/\/+$/, "");
|
||||
const modifiedToMatchWith = toMatchWith.slice(1);
|
||||
|
||||
if (modifiedPath === "" && modifiedToMatchWith === "") {
|
||||
@ -146,6 +148,20 @@ function matchPath(path: string, toMatchWith: string): Record<string, string | s
|
||||
}
|
||||
}
|
||||
|
||||
import.meta.vitest?.test("matches routes with trailing slashes", ({ expect }) => {
|
||||
expect({
|
||||
staticRoute: SmartRouter.matchNormalizedPath("/api/v1/", "/api/v1"),
|
||||
dynamicRoute: SmartRouter.matchNormalizedPath("/users/user-123/", "/users/[user_id]"),
|
||||
}).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dynamicRoute": {
|
||||
"user_id": "user-123",
|
||||
},
|
||||
"staticRoute": {},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
/**
|
||||
* Modified from: https://github.com/vercel/next.js/blob/6ff13369bb18045657d0f84ddc86b540340603a1/packages/next/src/shared/lib/router/utils/app-paths.ts#L23
|
||||
*/
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": false,
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
@ -17,6 +17,11 @@
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"noErrorTruncation": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
@ -28,15 +33,16 @@
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.?ts",
|
||||
"**/*.?tsx"
|
||||
"**/*.?tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"api",
|
||||
"node_modules",
|
||||
".next/dev",
|
||||
"dist"
|
||||
".next/dev"
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,110 +0,0 @@
|
||||
import { sentryRollupPlugin } from "@sentry/rollup-plugin";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { builtinModules } from "node:module";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { defineConfig, type Rolldown, type UserConfig } from "tsdown";
|
||||
// @ts-expect-error - this is a workspace tsdown helper imported from source.
|
||||
import { createBasePlugin } from "../../configs/tsdown/plugins.ts";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const backendDir = __dirname;
|
||||
|
||||
const packageJson = JSON.parse(readFileSync(resolve(backendDir, "package.json"), "utf-8"));
|
||||
|
||||
const externalPackages = [
|
||||
"@prisma/client",
|
||||
"@prisma/adapter-neon",
|
||||
"@prisma/adapter-pg",
|
||||
"@prisma/instrumentation",
|
||||
"@prisma/extension-read-replicas",
|
||||
"@prisma/client/runtime/library",
|
||||
"@prisma/client/runtime/client",
|
||||
"@prisma/client/runtime/edge",
|
||||
"bcrypt",
|
||||
"oidc-provider",
|
||||
"pg",
|
||||
"sharp",
|
||||
// @aws-sdk and @smithy use complex class hierarchies that rolldown mis-scopes
|
||||
// when bundled, emitting references to hoisted classes before they're defined
|
||||
// (e.g. "ReferenceError: StructureSchema$1 is not defined" when the server
|
||||
// bundle boots via `node dist/server.mjs`). Keep them external so Node resolves
|
||||
// them from node_modules at runtime, which is always present alongside the
|
||||
// bundle (Docker image, CI runner, Vercel NFT trace). Mirrors the same fix in
|
||||
// scripts/db-migrations.tsdown.config.ts.
|
||||
"@aws-sdk",
|
||||
"@smithy",
|
||||
];
|
||||
|
||||
const nodeBuiltins = builtinModules.flatMap((moduleName) => [moduleName, `node:${moduleName}`]);
|
||||
|
||||
const customNoExternal = new Set([
|
||||
...Object.keys(packageJson.dependencies).filter(
|
||||
(dep) => !externalPackages.some((externalPackage) => dep === externalPackage || dep.startsWith(externalPackage + "/"))
|
||||
),
|
||||
]);
|
||||
|
||||
function packageNameFromSpecifier(specifier: string) {
|
||||
if (specifier.startsWith("@")) {
|
||||
const parts = specifier.split("/");
|
||||
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
|
||||
}
|
||||
return specifier.split("/")[0] ?? specifier;
|
||||
}
|
||||
|
||||
function shouldBundleDependency(specifier: string) {
|
||||
return customNoExternal.has(packageNameFromSpecifier(specifier));
|
||||
}
|
||||
|
||||
const basePlugin: Rolldown.Plugin = createBasePlugin({});
|
||||
// Sentry release names may not contain slashes/whitespace, so sanitize the scoped package name.
|
||||
const sentryRelease = process.env.SENTRY_RELEASE ?? `${packageJson.name}@${packageJson.version}`.replace(/[/\s]/g, "-");
|
||||
const shouldUploadSourcemaps = process.env.SENTRY_ORG != null
|
||||
&& process.env.SENTRY_PROJECT != null
|
||||
&& process.env.SENTRY_AUTH_TOKEN != null;
|
||||
const plugins = [
|
||||
basePlugin,
|
||||
...(shouldUploadSourcemaps ? [
|
||||
sentryRollupPlugin({
|
||||
org: process.env.SENTRY_ORG,
|
||||
project: process.env.SENTRY_PROJECT,
|
||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||
release: {
|
||||
name: sentryRelease,
|
||||
},
|
||||
sourcemaps: {
|
||||
assets: resolve(backendDir, "dist/**/*.map"),
|
||||
},
|
||||
}),
|
||||
] : []),
|
||||
];
|
||||
|
||||
export default defineConfig({
|
||||
entry: [
|
||||
resolve(backendDir, "src/server/server.ts"),
|
||||
resolve(backendDir, "src/server/vercel.ts"),
|
||||
],
|
||||
format: ["esm"],
|
||||
outDir: resolve(backendDir, "dist"),
|
||||
target: "node22",
|
||||
platform: "node",
|
||||
noExternal: shouldBundleDependency,
|
||||
inlineOnly: false,
|
||||
external: [...nodeBuiltins, ...externalPackages],
|
||||
clean: true,
|
||||
minify: false,
|
||||
sourcemap: true,
|
||||
alias: {
|
||||
"@": resolve(backendDir, "src"),
|
||||
},
|
||||
banner: {
|
||||
js: `import { createRequire as __createRequire } from 'module';
|
||||
import { fileURLToPath as __fileURLToPath } from 'url';
|
||||
import { dirname as __dirname_fn } from 'path';
|
||||
const __filename = __fileURLToPath(import.meta.url);
|
||||
const __dirname = __dirname_fn(__filename);
|
||||
const require = __createRequire(import.meta.url);`,
|
||||
},
|
||||
plugins,
|
||||
} satisfies UserConfig);
|
||||
@ -1,9 +1,5 @@
|
||||
{
|
||||
"$schema": "https://openapi.vercel.sh/vercel.json",
|
||||
"framework": null,
|
||||
"rewrites": [
|
||||
{ "source": "/(.*)", "destination": "/api" }
|
||||
],
|
||||
"crons": [
|
||||
{
|
||||
"path": "/api/latest/internal/email-queue-step",
|
||||
|
||||
@ -17,7 +17,7 @@ export default mergeConfig(
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
'@': resolve(__dirname, './src')
|
||||
}
|
||||
},
|
||||
envDir: __dirname,
|
||||
|
||||
@ -10,6 +10,21 @@ describe("without project ID", () => {
|
||||
|
||||
it("should load", async ({ expect }) => {
|
||||
const response = await niceBackendFetch("/api/v1");
|
||||
expect(response).toMatchInlineSnapshot(`
|
||||
NiceResponse {
|
||||
"status": 200,
|
||||
"body": deindent\`
|
||||
Welcome to the Hexclave API endpoint! Please refer to the documentation at https://docs.hexclave.com.
|
||||
|
||||
Authentication: None
|
||||
\`,
|
||||
"headers": Headers { <some fields may have been hidden> },
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should load with a trailing slash", async ({ expect }) => {
|
||||
const response = await niceBackendFetch("/api/v1/");
|
||||
expect(response).toMatchInlineSnapshot(`
|
||||
NiceResponse {
|
||||
"status": 200,
|
||||
|
||||
@ -52,9 +52,11 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
|
||||
COPY --from=pruner /app/out/full/ .
|
||||
|
||||
# Docs are required for backend OpenAPI/codegen
|
||||
# Docs are required for the NextJS backend build
|
||||
COPY docs ./docs
|
||||
|
||||
ENV NEXT_CONFIG_OUTPUT=standalone
|
||||
|
||||
# Build backend only
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm turbo run docker-build --filter=@hexclave/backend...
|
||||
|
||||
@ -69,14 +71,10 @@ RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends openssl && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy the bundled Elysia server plus the pruned workspace/runtime deps that
|
||||
# tsdown externalizes for native packages, Prisma, OpenTelemetry, and SDK calls.
|
||||
COPY --from=builder --chown=node:node /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=node:node /app/package.json ./package.json
|
||||
COPY --from=builder --chown=node:node /app/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
COPY --from=builder --chown=node:node /app/apps/backend ./apps/backend
|
||||
COPY --from=builder --chown=node:node /app/packages ./packages
|
||||
COPY --from=builder --chown=node:node /app/configs ./configs
|
||||
# Copy Next.js standalone output — this includes a traced, minimal copy of
|
||||
# node_modules/ and packages/ (only the files the server actually imports).
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/.next/standalone ./
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/.next/static ./apps/backend/.next/static
|
||||
|
||||
# Prisma schema (needed at runtime by Prisma client)
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/prisma ./apps/backend/prisma
|
||||
@ -89,4 +87,4 @@ USER node
|
||||
|
||||
EXPOSE 8102
|
||||
|
||||
CMD ["node", "apps/backend/dist/server.mjs"]
|
||||
CMD ["node", "apps/backend/server.js"]
|
||||
|
||||
@ -49,13 +49,13 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||
# copy over the rest of the code for the build
|
||||
COPY --from=pruner /app/out/full/ .
|
||||
|
||||
# Docs are required for backend OpenAPI/codegen
|
||||
# docs are currently required for the NextJS backend build, but won't exist in the final image
|
||||
COPY docs ./docs
|
||||
|
||||
# Dashboard still needs standalone output for its Next.js build
|
||||
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
|
||||
ENV NEXT_CONFIG_OUTPUT=standalone
|
||||
|
||||
# Build the backend and dashboard
|
||||
# Build the backend NextJS app
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm turbo run docker-build --filter=@hexclave/backend... --filter=@hexclave/dashboard...
|
||||
|
||||
# Build the self-host seed script
|
||||
@ -74,7 +74,14 @@ RUN apt-get update && \
|
||||
apt-get install -y openssl postgresql-client socat && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
|
||||
# Copy built dashboard (standalone includes monorepo apps/ structure)
|
||||
# Copy built backend
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/.next/standalone ./
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/.next/static ./apps/backend/.next/static
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/prisma ./apps/backend/prisma
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/dist ./apps/backend/dist
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/node_modules ./apps/backend/node_modules
|
||||
|
||||
# Copy built dashboard
|
||||
COPY --from=builder --chown=node:node /app/apps/dashboard/.next/standalone ./
|
||||
COPY --from=builder --chown=node:node /app/apps/dashboard/.next/static ./apps/dashboard/.next/static
|
||||
COPY --from=builder --chown=node:node /app/apps/dashboard/public ./apps/dashboard/public
|
||||
@ -83,15 +90,6 @@ COPY --from=builder --chown=node:node /app/apps/dashboard/public ./apps/dashboar
|
||||
COPY --from=builder --chown=node:node /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=node:node /app/packages ./packages
|
||||
|
||||
# Copy the bundled Elysia backend LAST — the dashboard standalone output includes
|
||||
# a traced subset of apps/backend/ via pnpm workspace symlinks, and Docker COPY
|
||||
# creates opaque directory layers that hide files from earlier layers. By copying
|
||||
# the real backend dist after everything else, its files are guaranteed visible.
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/dist ./apps/backend/dist
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/prisma ./apps/backend/prisma
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/node_modules ./apps/backend/node_modules
|
||||
COPY --from=builder --chown=node:node /app/apps/backend/package.json ./apps/backend/package.json
|
||||
|
||||
# Add the entrypoint script
|
||||
COPY ./docker/server/entrypoint.sh .
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
@ -204,7 +204,7 @@ fi
|
||||
|
||||
echo "Starting backend on port $BACKEND_PORT..."
|
||||
cd "$WORK_DIR"
|
||||
PORT=$BACKEND_PORT HOSTNAME=0.0.0.0 node apps/backend/dist/server.mjs &
|
||||
PORT=$BACKEND_PORT HOSTNAME=0.0.0.0 node apps/backend/server.js &
|
||||
|
||||
echo "Starting dashboard on port $DASHBOARD_PORT..."
|
||||
PORT=$DASHBOARD_PORT HOSTNAME=0.0.0.0 node apps/dashboard/server.js &
|
||||
|
||||
426
pnpm-lock.yaml
426
pnpm-lock.yaml
@ -114,9 +114,6 @@ importers:
|
||||
'@clickhouse/client':
|
||||
specifier: ^1.14.0
|
||||
version: 1.16.0
|
||||
'@elysiajs/node':
|
||||
specifier: ^1.4.5
|
||||
version: 1.4.5(elysia@1.4.28(@sinclair/typebox@0.34.49)(exact-mirror@1.1.1)(file-type@22.0.1)(openapi-types@12.1.3)(typescript@6.0.3))
|
||||
'@hexclave/next':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/next
|
||||
@ -159,9 +156,6 @@ importers:
|
||||
'@opentelemetry/sdk-logs':
|
||||
specifier: ^0.53.0
|
||||
version: 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-node':
|
||||
specifier: 0.53.0
|
||||
version: 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base':
|
||||
specifier: ^1.26.0
|
||||
version: 1.26.0(@opentelemetry/api@1.9.0)
|
||||
@ -186,27 +180,21 @@ importers:
|
||||
'@prisma/instrumentation':
|
||||
specifier: ^7.0.0
|
||||
version: 7.1.0(@opentelemetry/api@1.9.0)
|
||||
'@sentry/browser':
|
||||
'@sentry/nextjs':
|
||||
specifier: ^10.45.0
|
||||
version: 10.45.0
|
||||
'@sentry/node':
|
||||
specifier: ^10.45.0
|
||||
version: 10.45.0
|
||||
'@sentry/rollup-plugin':
|
||||
specifier: ^4.6.1
|
||||
version: 4.9.1(rollup@4.57.1)
|
||||
version: 10.45.0(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))
|
||||
'@simplewebauthn/server':
|
||||
specifier: ^13.3.0
|
||||
version: 13.3.0
|
||||
'@sinclair/typebox':
|
||||
specifier: ^0.34.49
|
||||
version: 0.34.49
|
||||
'@upstash/qstash':
|
||||
specifier: ^2.8.2
|
||||
version: 2.8.2
|
||||
'@vercel/functions':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@aws-sdk/credential-provider-web-identity@3.972.27)
|
||||
'@vercel/otel':
|
||||
specifier: ^1.10.4
|
||||
version: 1.10.4(@opentelemetry/api-logs@0.53.0)(@opentelemetry/api@1.9.0)(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-logs@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))
|
||||
'@vercel/sandbox':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
@ -228,9 +216,6 @@ importers:
|
||||
dotenv-cli:
|
||||
specifier: ^7.3.0
|
||||
version: 7.4.1
|
||||
elysia:
|
||||
specifier: 1.4.28
|
||||
version: 1.4.28(@sinclair/typebox@0.34.49)(exact-mirror@1.1.1)(file-type@22.0.1)(openapi-types@12.1.3)(typescript@6.0.3)
|
||||
emailable:
|
||||
specifier: ^3.1.1
|
||||
version: 3.1.1
|
||||
@ -1673,10 +1658,10 @@ importers:
|
||||
version: link:../../packages/next
|
||||
'@supabase/ssr':
|
||||
specifier: latest
|
||||
version: 0.12.0(@supabase/supabase-js@2.110.0)
|
||||
version: 0.12.0(@supabase/supabase-js@2.108.2)
|
||||
'@supabase/supabase-js':
|
||||
specifier: latest
|
||||
version: 2.110.0
|
||||
version: 2.108.2
|
||||
jose:
|
||||
specifier: ^5.2.2
|
||||
version: 5.6.3
|
||||
@ -5866,12 +5851,6 @@ packages:
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.3.0
|
||||
|
||||
'@opentelemetry/exporter-logs-otlp-grpc@0.53.0':
|
||||
resolution: {integrity: sha512-x5ygAQgWAQOI+UOhyV3z9eW7QU2dCfnfOuIBiyYmC2AWr74f6x/3JBnP27IAcEx6aihpqBYWKnpoUTztkVPAZw==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
|
||||
'@opentelemetry/exporter-logs-otlp-http@0.200.0':
|
||||
resolution: {integrity: sha512-KfWw49htbGGp9s8N4KI8EQ9XuqKJ0VG+yVYVYFiCYSjEV32qpQ5qZ9UZBzOZ6xRb+E16SXOSCT3RkqBVSABZ+g==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
@ -5890,12 +5869,6 @@ packages:
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.3.0
|
||||
|
||||
'@opentelemetry/exporter-logs-otlp-http@0.53.0':
|
||||
resolution: {integrity: sha512-cSRKgD/n8rb+Yd+Cif6EnHEL/VZg1o8lEcEwFji1lwene6BdH51Zh3feAD9p2TyVoBKrl6Q9Zm2WltSp2k9gWQ==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
|
||||
'@opentelemetry/exporter-logs-otlp-proto@0.200.0':
|
||||
resolution: {integrity: sha512-GmahpUU/55hxfH4TP77ChOfftADsCq/nuri73I/AVLe2s4NIglvTsaACkFVZAVmnXXyPS00Fk3x27WS3yO07zA==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
@ -5908,12 +5881,6 @@ packages:
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.3.0
|
||||
|
||||
'@opentelemetry/exporter-logs-otlp-proto@0.53.0':
|
||||
resolution: {integrity: sha512-jhEcVL1deeWNmTUP05UZMriZPSWUBcfg94ng7JuBb1q2NExgnADQFl1VQQ+xo62/JepK+MxQe4xAwlsDQFbISA==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
|
||||
'@opentelemetry/exporter-metrics-otlp-grpc@0.200.0':
|
||||
resolution: {integrity: sha512-uHawPRvKIrhqH09GloTuYeq2BjyieYHIpiklOvxm9zhrCL2eRsnI/6g9v2BZTVtGp8tEgIa7rCQ6Ltxw6NBgew==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
@ -5974,12 +5941,6 @@ packages:
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.3.0
|
||||
|
||||
'@opentelemetry/exporter-trace-otlp-grpc@0.53.0':
|
||||
resolution: {integrity: sha512-m6KSh6OBDwfDjpzPVbuJbMgMbkoZfpxYH2r262KckgX9cMYvooWXEKzlJYsNDC6ADr28A1rtRoUVRwNfIN4tUg==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
|
||||
'@opentelemetry/exporter-trace-otlp-http@0.200.0':
|
||||
resolution: {integrity: sha512-Goi//m/7ZHeUedxTGVmEzH19NgqJY+Bzr6zXo1Rni1+hwqaksEyJ44gdlEMREu6dzX1DlAaH/qSykSVzdrdafA==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
@ -6010,18 +5971,6 @@ packages:
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.3.0
|
||||
|
||||
'@opentelemetry/exporter-trace-otlp-proto@0.53.0':
|
||||
resolution: {integrity: sha512-T/bdXslwRKj23S96qbvGtaYOdfyew3TjPEKOk5mHjkCmkVl1O9C/YMdejwSsdLdOq2YW30KjR9kVi0YMxZushQ==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
|
||||
'@opentelemetry/exporter-zipkin@1.26.0':
|
||||
resolution: {integrity: sha512-PW5R34n3SJHO4t0UetyHKiXL6LixIqWN6lWncg3eRXhKuT30x+b7m5sDJS0kEWRfHeS+kG7uCw2vBzmB2lk3Dw==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
|
||||
'@opentelemetry/exporter-zipkin@2.0.0':
|
||||
resolution: {integrity: sha512-icxaKZ+jZL/NHXX8Aru4HGsrdhK0MLcuRXkX5G5IRmCgoRLw+Br6I/nMVozX2xjGGwV7hw2g+4Slj8K7s4HbVg==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
@ -6491,12 +6440,6 @@ packages:
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.3.0
|
||||
|
||||
'@opentelemetry/otlp-grpc-exporter-base@0.53.0':
|
||||
resolution: {integrity: sha512-F7RCN8VN+lzSa4fGjewit8Z5fEUpY/lmMVy5EWn2ZpbAabg3EE3sCLuTNfOiooNGnmvzimUPruoeqeko/5/TzQ==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
|
||||
'@opentelemetry/otlp-transformer@0.200.0':
|
||||
resolution: {integrity: sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
@ -6675,6 +6618,12 @@ packages:
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.9.0 <1.10.0'
|
||||
|
||||
'@opentelemetry/sdk-metrics@2.8.0':
|
||||
resolution: {integrity: sha512-UDBGaj6W0Rgy5rTTaoxs8gVGF/aGkAKyjurJv7se6wjRxJu7FoquTLT/vt54DZfo4crbprYfhX/SOK9+BPw1qg==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.9.0 <1.10.0'
|
||||
|
||||
'@opentelemetry/sdk-node@0.200.0':
|
||||
resolution: {integrity: sha512-S/YSy9GIswnhYoDor1RusNkmRughipvTCOQrlF1dzI70yQaf68qgf5WMnzUxdlCl3/et/pvaO75xfPfuEmCK5A==}
|
||||
engines: {node: ^18.19.0 || >=20.6.0}
|
||||
@ -6687,12 +6636,6 @@ packages:
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.3.0 <1.10.0'
|
||||
|
||||
'@opentelemetry/sdk-node@0.53.0':
|
||||
resolution: {integrity: sha512-0hsxfq3BKy05xGktwG8YdGdxV978++x40EAKyKr1CaHZRh8uqVlXnclnl7OMi9xLMJEcXUw7lGhiRlArFcovyg==}
|
||||
engines: {node: '>=14'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': '>=1.3.0 <1.10.0'
|
||||
|
||||
'@opentelemetry/sdk-trace-base@1.26.0':
|
||||
resolution: {integrity: sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==}
|
||||
engines: {node: '>=14'}
|
||||
@ -8445,9 +8388,9 @@ packages:
|
||||
resolution: {integrity: sha512-OuxqBprXRyhe8Pkfyz/4yHQJc5c3lm+TmYWSSx8u48g5yKewSQDOxkiLU5pAk3WnbLPy8XwU/PN+2BG0YFU9Nw==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
'@sentry/babel-plugin-component-annotate@4.9.1':
|
||||
resolution: {integrity: sha512-0gEoi2Lb54MFYPOmdTfxlNKxI7kCOvNV7gP8lxMXJ7nCazF5OqOOZIVshfWjDLrc0QrSV6XdVvwPV9GDn4wBMg==}
|
||||
engines: {node: '>= 14'}
|
||||
'@sentry/babel-plugin-component-annotate@5.1.1':
|
||||
resolution: {integrity: sha512-x2wEpBHwsTyTF2rWsLKJlzrRF1TTIGOfX+ngdE+Yd5DBkoS58HwQv824QOviPGQRla4/ypISqAXzjdDPL/zalg==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
'@sentry/browser@10.11.0':
|
||||
resolution: {integrity: sha512-qemaKCJKJHHCyGBpdLq23xL5u9Xvir20XN7YFTnHcEq4Jvj0GoWsslxKi5cQB2JvpYn62WxTiDgVLeQlleZhSg==}
|
||||
@ -8461,9 +8404,9 @@ packages:
|
||||
resolution: {integrity: sha512-dmR4DJhJ4jqVWGWppuTL2blNFqOZZnt4aLkewbD1myFG3KVfUx8CrMQWEmGjkgPOtj5TO6xH9PyTJjXC6o5tnA==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
'@sentry/bundler-plugin-core@4.9.1':
|
||||
resolution: {integrity: sha512-moii+w7N8k8WdvkX7qCDY9iRBlhgHlhTHTUQwF2FNMhBHuqlNpVcSJJqJMjFUQcjYMBDrZgxhfKV18bt5ixwlQ==}
|
||||
engines: {node: '>= 14'}
|
||||
'@sentry/bundler-plugin-core@5.1.1':
|
||||
resolution: {integrity: sha512-F+itpwR9DyQR7gEkrXd2tigREPTvtF5lC8qu6e4anxXYRTui1+dVR0fXNwjpyAZMhIesLfXRN7WY7ggdj7hi0Q==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
'@sentry/cli-darwin@2.53.0':
|
||||
resolution: {integrity: sha512-NNPfpILMwKgpHiyJubHHuauMKltkrgLQ5tvMdxNpxY60jBNdo5VJtpESp4XmXlnidzV4j1z61V4ozU6ttDgt5Q==}
|
||||
@ -8583,6 +8526,12 @@ packages:
|
||||
peerDependencies:
|
||||
next: ^13.2.0 || ^14.0 || ^15.0.0-rc.0
|
||||
|
||||
'@sentry/nextjs@10.45.0':
|
||||
resolution: {integrity: sha512-4LE+UvnfdOYyG8YEb/9TWaJQzMPuGLlph/iqowvsMdxaW6la+mvADiuzNTXly4QfsjeD3KIb7dKlGTqiVV0Ttw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
next: ^13.2.0 || ^14.0 || ^15.0.0-rc.0 || ^16.0.0-0
|
||||
|
||||
'@sentry/node-core@10.11.0':
|
||||
resolution: {integrity: sha512-dkVZ06F+W5W0CsD47ATTTOTTocmccT/ezrF9idspQq+HVOcjoKSU60WpWo22NjtVNdSYKLnom0q1LKRoaRA/Ww==}
|
||||
engines: {node: '>=18'}
|
||||
@ -8656,22 +8605,32 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.14.0 || 17.x || 18.x || 19.x
|
||||
|
||||
'@sentry/rollup-plugin@4.9.1':
|
||||
resolution: {integrity: sha512-cjr/S4I9Xj4481K0o47PuteGgqFBs3hRRPOFs1Vz1Jql6VeCm5+ghbtsLGQMwB2lCOpWcYggdc03JK67UJT4ZQ==}
|
||||
engines: {node: '>= 14'}
|
||||
'@sentry/react@10.45.0':
|
||||
resolution: {integrity: sha512-jLezuxi4BUIU3raKyAPR5xMbQG/nhwnWmKo5p11NCbLmWzkS+lxoyDTUB4B8TAKZLfdtdkKLOn1S0tFc8vbUHw==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
rollup: '>=3.2.0'
|
||||
react: ^16.14.0 || 17.x || 18.x || 19.x
|
||||
|
||||
'@sentry/vercel-edge@10.11.0':
|
||||
resolution: {integrity: sha512-jAsJ8RbbF2JWj2wnXfd6BwWxCR6GBITMtlaoWc7pG22HknEtoH15dKsQC3Ew5r/KRcofr2e+ywdnBn5CPr1Pbg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry/vercel-edge@10.45.0':
|
||||
resolution: {integrity: sha512-sSF+Ex5NwT60gMinLcP/JNZb3cDaIv0mL1cRjfvN6zN2ZNEw0C9rhdgxa0EdD4G6PCHQ0XnCuAMDsfJ6gnRmfA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sentry/webpack-plugin@4.3.0':
|
||||
resolution: {integrity: sha512-K4nU1SheK/tvyakBws2zfd+MN6hzmpW+wPTbSbDWn1+WL9+g9hsPh8hjFFiVe47AhhUoUZ3YgiH2HyeHXjHflA==}
|
||||
engines: {node: '>= 14'}
|
||||
peerDependencies:
|
||||
webpack: '>=4.40.0'
|
||||
|
||||
'@sentry/webpack-plugin@5.1.1':
|
||||
resolution: {integrity: sha512-XgQg+t2aVrlQDfIiAEizqR/bsy6GtBygwgR+Kw11P/cYczj4W9PZ2IYqQEStBzHqnRTh5DbpyMcUNW2CujdA9A==}
|
||||
engines: {node: '>= 18'}
|
||||
peerDependencies:
|
||||
webpack: '>=5.0.0'
|
||||
|
||||
'@shikijs/core@3.14.0':
|
||||
resolution: {integrity: sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw==}
|
||||
|
||||
@ -9229,37 +9188,37 @@ packages:
|
||||
resolution: {integrity: sha512-SXuhqhuR5FXaYgKTXzZJeqtVA6JKb9IZWaGeEUxHHiOcFy2p51wccO72bYpXwoK4D5pzQOIYLTuAc7etxyMmwg==}
|
||||
engines: {node: '>=12.16'}
|
||||
|
||||
'@supabase/auth-js@2.110.0':
|
||||
resolution: {integrity: sha512-Mi288WCTp6wxMFCOu/UgzgHEXODjdl2uVTLqK11eanzGZaldU3RyP8Am+ZbNuVzFP+5+iOvppxzv7N5Ym84xTg==}
|
||||
engines: {node: '>=22.0.0'}
|
||||
'@supabase/auth-js@2.108.2':
|
||||
resolution: {integrity: sha512-tNaQmBgodDZwgB40mRwVbxFy8IDYwjdpcZ0BYrWiwlULCSQoJj4QoG4zgJT7QRPXcqipefNOzvO/qAu4dF98ag==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@supabase/functions-js@2.110.0':
|
||||
resolution: {integrity: sha512-Fde5wlY8ZZy+9yqrWlQHo8MacSyUBArBEtN2boB4thJQigPnQD/cc61qZN0n3I1L0gwhWtHYwIMnOBKxSvF6Hw==}
|
||||
engines: {node: '>=22.0.0'}
|
||||
'@supabase/functions-js@2.108.2':
|
||||
resolution: {integrity: sha512-RNUX8EiBy3iLwAX19jtRzLyePnl11/fHcgwDHLnpKcDSXt/5qBnh3LUwAtIjT21Q66QsmNUR2esrHziLCpNubw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@supabase/phoenix@0.4.4':
|
||||
resolution: {integrity: sha512-Gt0pqoXuIqX/8dvG0OKp/wMCobXNH3klNbUPBNyOfN0YA1IswrM3HyWFMOPk1Jy+BRaIyDPcFx4jLBwHNmlyfQ==}
|
||||
'@supabase/phoenix@0.4.2':
|
||||
resolution: {integrity: sha512-YSAGnmDAfuleFCVt3CeurQZAhxRfXWeZIIkwp7NhYzQ1UwW6ePSnzsFAiUm/mbCkfoCf70QQHKW/K6RKh52a4A==}
|
||||
|
||||
'@supabase/postgrest-js@2.110.0':
|
||||
resolution: {integrity: sha512-ZbC1QZL3jcvBUfVKjJbgRM27G4Mg3Zzqdm44m5pJafe1e52Cli793EOnwQucomBAGEUDd03Nzaf7XV3ji/XexQ==}
|
||||
engines: {node: '>=22.0.0'}
|
||||
'@supabase/postgrest-js@2.108.2':
|
||||
resolution: {integrity: sha512-GQ28/Y8hk3CFmkb3kXH1h/AQx6JIYSQfO0CJMRVBcEKZoNy6C45cXAZ4fcJvRC5Id0cs6xnkUV0+c0rIocigsw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@supabase/realtime-js@2.110.0':
|
||||
resolution: {integrity: sha512-Wn2AWpneZuDFTkp/65tqctvoh+3JvyTjMam8sTMqVWy5BgkU8zAvFwilPYPPPhkINeKF8NAJKP7FclJ2iGCUMw==}
|
||||
engines: {node: '>=22.0.0'}
|
||||
'@supabase/realtime-js@2.108.2':
|
||||
resolution: {integrity: sha512-aAGxCSUemZvQIibnCdvNvgaKib28I4rfrNjKbQ9cG1uBLwUsI7hVpGXgEbypCCDhLjQlDTAiJlu7rgljYUT73g==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@supabase/ssr@0.12.0':
|
||||
resolution: {integrity: sha512-d9XV5XzJvzzZbeAIM7fWTCUYxQJZ2Ru6ny3dJHmHGp/LIrJ+o9FpD7N9Rf/UhhWEvHXSoDe8SI32Z2ouOdMjBg==}
|
||||
peerDependencies:
|
||||
'@supabase/supabase-js': ^2.108.0
|
||||
|
||||
'@supabase/storage-js@2.110.0':
|
||||
resolution: {integrity: sha512-71+gU3HrhiylAhftY6FmO5PPdcsScnVcS766CVD+vTYK9qTDLbrx8FhgBYbqGm3iV/wkTfzrNJfjGsMeFRkJRQ==}
|
||||
engines: {node: '>=22.0.0'}
|
||||
'@supabase/storage-js@2.108.2':
|
||||
resolution: {integrity: sha512-TVZPQxXGxY2+A6yTtm77zUHsh70lBhYUEaJL8RQC+BghcX/ygiMG/rmXrNVBce30/WAeNPa8FiG8HbqlGeV05g==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@supabase/supabase-js@2.110.0':
|
||||
resolution: {integrity: sha512-8yI84VJiEVW4zxZpLUmxXmjzQ7O2St9X/ymzlBETDHTURPWG3LmvbSiibq+7dqAJmyoUfxZnSfXeM4HCM8s4XQ==}
|
||||
engines: {node: '>=22.0.0'}
|
||||
'@supabase/supabase-js@2.108.2':
|
||||
resolution: {integrity: sha512-hFhnPveb5JQg4a0QYicM0swT253YHMdfeRAl2BKHOlI5VAzuHxUGSr8RbwNLYNPauWOgQMS1H8sz8bvYlgwUfQ==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@swc/counter@0.1.3':
|
||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||
@ -10183,6 +10142,18 @@ packages:
|
||||
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
|
||||
engines: {node: '>= 20'}
|
||||
|
||||
'@vercel/otel@1.10.4':
|
||||
resolution: {integrity: sha512-X01cOLU2Aiku4y1Vn9HQ8VmkQEdIQiWhjHa7DEUspcNGNg0Xm7GYAU7ErZ0pH/5spsrvb2wpIIHSBFgs0M5p4g==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@opentelemetry/api': ^1.7.0
|
||||
'@opentelemetry/api-logs': '>=0.46.0 && <1.0.0'
|
||||
'@opentelemetry/instrumentation': '>=0.46.0 && <1.0.0'
|
||||
'@opentelemetry/resources': ^1.19.0
|
||||
'@opentelemetry/sdk-logs': '>=0.46.0 && <1.0.0'
|
||||
'@opentelemetry/sdk-metrics': ^1.19.0
|
||||
'@opentelemetry/sdk-trace-base': ^1.19.0
|
||||
|
||||
'@vercel/sandbox@1.2.0':
|
||||
resolution: {integrity: sha512-oq0d2xQuiDq8LyoZOZW+i+QmNkMQ0/ddEGHMukBsF+cxX7ohBEXOV/a0+SopBwEsKCjq9j55QFP56G3MU/iEjA==}
|
||||
|
||||
@ -13044,11 +13015,6 @@ packages:
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
hasBin: true
|
||||
|
||||
glob@10.5.0:
|
||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||
hasBin: true
|
||||
|
||||
glob@13.0.6:
|
||||
resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
@ -20400,12 +20366,6 @@ snapshots:
|
||||
elysia: 1.4.28(@sinclair/typebox@0.34.49)(exact-mirror@1.1.1)(file-type@22.0.1)(openapi-types@12.1.3)(typescript@5.9.3)
|
||||
srvx: 0.11.15
|
||||
|
||||
'@elysiajs/node@1.4.5(elysia@1.4.28(@sinclair/typebox@0.34.49)(exact-mirror@1.1.1)(file-type@22.0.1)(openapi-types@12.1.3)(typescript@6.0.3))':
|
||||
dependencies:
|
||||
crossws: 0.4.4(srvx@0.11.15)
|
||||
elysia: 1.4.28(@sinclair/typebox@0.34.49)(exact-mirror@1.1.1)(file-type@22.0.1)(openapi-types@12.1.3)(typescript@6.0.3)
|
||||
srvx: 0.11.15
|
||||
|
||||
'@emnapi/core@1.11.1':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.2.2
|
||||
@ -22568,15 +22528,6 @@ snapshots:
|
||||
'@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-logs': 0.213.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/exporter-logs-otlp-grpc@0.53.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@grpc/grpc-js': 1.14.3
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-grpc-exporter-base': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-transformer': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-logs': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/exporter-logs-otlp-http@0.200.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
@ -22604,15 +22555,6 @@ snapshots:
|
||||
'@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-logs': 0.213.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/exporter-logs-otlp-http@0.53.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/api-logs': 0.53.0
|
||||
'@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-exporter-base': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-transformer': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-logs': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/exporter-logs-otlp-proto@0.200.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
@ -22635,17 +22577,6 @@ snapshots:
|
||||
'@opentelemetry/sdk-logs': 0.213.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/exporter-logs-otlp-proto@0.53.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/api-logs': 0.53.0
|
||||
'@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-exporter-base': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-transformer': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-logs': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/exporter-metrics-otlp-grpc@0.200.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@grpc/grpc-js': 1.14.3
|
||||
@ -22745,16 +22676,6 @@ snapshots:
|
||||
'@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/exporter-trace-otlp-grpc@0.53.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@grpc/grpc-js': 1.14.3
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-grpc-exporter-base': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-transformer': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/exporter-trace-otlp-http@0.200.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
@ -22800,23 +22721,6 @@ snapshots:
|
||||
'@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 2.6.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/exporter-trace-otlp-proto@0.53.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-exporter-base': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-transformer': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/exporter-zipkin@1.26.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/semantic-conventions': 1.27.0
|
||||
|
||||
'@opentelemetry/exporter-zipkin@2.0.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
@ -23480,14 +23384,6 @@ snapshots:
|
||||
'@opentelemetry/otlp-exporter-base': 0.213.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-transformer': 0.213.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/otlp-grpc-exporter-base@0.53.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@grpc/grpc-js': 1.14.3
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-exporter-base': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/otlp-transformer': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/otlp-transformer@0.200.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
@ -23688,6 +23584,12 @@ snapshots:
|
||||
'@opentelemetry/core': 2.6.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 2.6.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/sdk-metrics@2.8.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@opentelemetry/sdk-node@0.200.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
@ -23746,28 +23648,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@opentelemetry/sdk-node@0.53.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/api-logs': 0.53.0
|
||||
'@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/exporter-logs-otlp-grpc': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/exporter-logs-otlp-http': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/exporter-logs-otlp-proto': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/exporter-trace-otlp-grpc': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/exporter-trace-otlp-http': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/exporter-trace-otlp-proto': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/exporter-zipkin': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-logs': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-metrics': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-node': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/semantic-conventions': 1.27.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
@ -26374,6 +26254,18 @@ snapshots:
|
||||
optionalDependencies:
|
||||
rollup: 4.50.1
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.1(rollup@4.57.1)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.0(rollup@4.57.1)
|
||||
commondir: 1.0.1
|
||||
estree-walker: 2.0.2
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
is-reference: 1.2.1
|
||||
magic-string: 0.30.17
|
||||
picomatch: 4.0.3
|
||||
optionalDependencies:
|
||||
rollup: 4.57.1
|
||||
|
||||
'@rollup/pluginutils@5.1.0(rollup@4.50.1)':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
@ -26382,6 +26274,14 @@ snapshots:
|
||||
optionalDependencies:
|
||||
rollup: 4.50.1
|
||||
|
||||
'@rollup/pluginutils@5.1.0(rollup@4.57.1)':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
estree-walker: 2.0.2
|
||||
picomatch: 2.3.1
|
||||
optionalDependencies:
|
||||
rollup: 4.57.1
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.34.8':
|
||||
optional: true
|
||||
|
||||
@ -26630,7 +26530,7 @@ snapshots:
|
||||
|
||||
'@sentry/babel-plugin-component-annotate@4.3.0': {}
|
||||
|
||||
'@sentry/babel-plugin-component-annotate@4.9.1': {}
|
||||
'@sentry/babel-plugin-component-annotate@5.1.1': {}
|
||||
|
||||
'@sentry/browser@10.11.0':
|
||||
dependencies:
|
||||
@ -26662,16 +26562,15 @@ snapshots:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@sentry/bundler-plugin-core@4.9.1':
|
||||
'@sentry/bundler-plugin-core@5.1.1':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@sentry/babel-plugin-component-annotate': 4.9.1
|
||||
'@sentry/babel-plugin-component-annotate': 5.1.1
|
||||
'@sentry/cli': 2.58.5
|
||||
dotenv: 16.6.1
|
||||
find-up: 5.0.0
|
||||
glob: 10.5.0
|
||||
magic-string: 0.30.8
|
||||
unplugin: 1.0.1
|
||||
glob: 13.0.6
|
||||
magic-string: 0.30.17
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
@ -26795,6 +26694,31 @@ snapshots:
|
||||
- supports-color
|
||||
- webpack
|
||||
|
||||
'@sentry/nextjs@10.45.0(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(next@16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(webpack@5.92.0(esbuild@0.24.2))':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/semantic-conventions': 1.40.0
|
||||
'@rollup/plugin-commonjs': 28.0.1(rollup@4.57.1)
|
||||
'@sentry-internal/browser-utils': 10.45.0
|
||||
'@sentry/bundler-plugin-core': 5.1.1
|
||||
'@sentry/core': 10.45.0
|
||||
'@sentry/node': 10.45.0
|
||||
'@sentry/opentelemetry': 10.45.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)
|
||||
'@sentry/react': 10.45.0(react@19.2.3)
|
||||
'@sentry/vercel-edge': 10.45.0
|
||||
'@sentry/webpack-plugin': 5.1.1(webpack@5.92.0(esbuild@0.24.2))
|
||||
next: 16.2.9(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
rollup: 4.57.1
|
||||
stacktrace-parser: 0.1.11
|
||||
transitivePeerDependencies:
|
||||
- '@opentelemetry/context-async-hooks'
|
||||
- '@opentelemetry/core'
|
||||
- '@opentelemetry/sdk-trace-base'
|
||||
- encoding
|
||||
- react
|
||||
- supports-color
|
||||
- webpack
|
||||
|
||||
'@sentry/node-core@10.11.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.203.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
@ -26920,6 +26844,15 @@ snapshots:
|
||||
'@opentelemetry/semantic-conventions': 1.40.0
|
||||
'@sentry/core': 10.11.0
|
||||
|
||||
'@sentry/opentelemetry@10.45.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/context-async-hooks': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/semantic-conventions': 1.40.0
|
||||
'@sentry/core': 10.45.0
|
||||
|
||||
'@sentry/opentelemetry@10.45.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.6.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.40.0)':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
@ -26936,14 +26869,11 @@ snapshots:
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 19.2.3
|
||||
|
||||
'@sentry/rollup-plugin@4.9.1(rollup@4.57.1)':
|
||||
'@sentry/react@10.45.0(react@19.2.3)':
|
||||
dependencies:
|
||||
'@sentry/bundler-plugin-core': 4.9.1
|
||||
rollup: 4.57.1
|
||||
unplugin: 1.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
'@sentry/browser': 10.45.0
|
||||
'@sentry/core': 10.45.0
|
||||
react: 19.2.3
|
||||
|
||||
'@sentry/vercel-edge@10.11.0':
|
||||
dependencies:
|
||||
@ -26951,6 +26881,12 @@ snapshots:
|
||||
'@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.0)
|
||||
'@sentry/core': 10.11.0
|
||||
|
||||
'@sentry/vercel-edge@10.45.0':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/resources': 2.8.0(@opentelemetry/api@1.9.0)
|
||||
'@sentry/core': 10.45.0
|
||||
|
||||
'@sentry/webpack-plugin@4.3.0(webpack@5.92.0(esbuild@0.24.2))':
|
||||
dependencies:
|
||||
'@sentry/bundler-plugin-core': 4.3.0
|
||||
@ -26961,6 +26897,15 @@ snapshots:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@sentry/webpack-plugin@5.1.1(webpack@5.92.0(esbuild@0.24.2))':
|
||||
dependencies:
|
||||
'@sentry/bundler-plugin-core': 5.1.1
|
||||
uuid: 9.0.1
|
||||
webpack: 5.92.0(esbuild@0.24.2)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@shikijs/core@3.14.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.14.0
|
||||
@ -27852,42 +27797,42 @@ snapshots:
|
||||
|
||||
'@stripe/stripe-js@7.7.0': {}
|
||||
|
||||
'@supabase/auth-js@2.110.0':
|
||||
'@supabase/auth-js@2.108.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@supabase/functions-js@2.110.0':
|
||||
'@supabase/functions-js@2.108.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@supabase/phoenix@0.4.4': {}
|
||||
'@supabase/phoenix@0.4.2': {}
|
||||
|
||||
'@supabase/postgrest-js@2.110.0':
|
||||
'@supabase/postgrest-js@2.108.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@supabase/realtime-js@2.110.0':
|
||||
'@supabase/realtime-js@2.108.2':
|
||||
dependencies:
|
||||
'@supabase/phoenix': 0.4.4
|
||||
'@supabase/phoenix': 0.4.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@supabase/ssr@0.12.0(@supabase/supabase-js@2.110.0)':
|
||||
'@supabase/ssr@0.12.0(@supabase/supabase-js@2.108.2)':
|
||||
dependencies:
|
||||
'@supabase/supabase-js': 2.110.0
|
||||
'@supabase/supabase-js': 2.108.2
|
||||
cookie: 1.0.2
|
||||
|
||||
'@supabase/storage-js@2.110.0':
|
||||
'@supabase/storage-js@2.108.2':
|
||||
dependencies:
|
||||
iceberg-js: 0.8.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@supabase/supabase-js@2.110.0':
|
||||
'@supabase/supabase-js@2.108.2':
|
||||
dependencies:
|
||||
'@supabase/auth-js': 2.110.0
|
||||
'@supabase/functions-js': 2.110.0
|
||||
'@supabase/postgrest-js': 2.110.0
|
||||
'@supabase/realtime-js': 2.110.0
|
||||
'@supabase/storage-js': 2.110.0
|
||||
'@supabase/auth-js': 2.108.2
|
||||
'@supabase/functions-js': 2.108.2
|
||||
'@supabase/postgrest-js': 2.108.2
|
||||
'@supabase/realtime-js': 2.108.2
|
||||
'@supabase/storage-js': 2.108.2
|
||||
|
||||
'@swc/counter@0.1.3': {}
|
||||
|
||||
@ -29314,6 +29259,16 @@ snapshots:
|
||||
|
||||
'@vercel/oidc@3.1.0': {}
|
||||
|
||||
'@vercel/otel@1.10.4(@opentelemetry/api-logs@0.53.0)(@opentelemetry/api@1.9.0)(@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@1.26.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-logs@0.53.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-metrics@2.8.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.26.0(@opentelemetry/api@1.9.0))':
|
||||
dependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@opentelemetry/api-logs': 0.53.0
|
||||
'@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/resources': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-logs': 0.53.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-metrics': 2.8.0(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 1.26.0(@opentelemetry/api@1.9.0)
|
||||
|
||||
'@vercel/sandbox@1.2.0':
|
||||
dependencies:
|
||||
'@vercel/oidc': 3.1.0
|
||||
@ -31313,18 +31268,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
elysia@1.4.28(@sinclair/typebox@0.34.49)(exact-mirror@1.1.1)(file-type@22.0.1)(openapi-types@12.1.3)(typescript@6.0.3):
|
||||
dependencies:
|
||||
'@sinclair/typebox': 0.34.49
|
||||
cookie: 1.1.1
|
||||
exact-mirror: 1.1.1
|
||||
fast-decode-uri-component: 1.0.1
|
||||
file-type: 22.0.1
|
||||
memoirist: 0.4.0
|
||||
openapi-types: 12.1.3
|
||||
optionalDependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
emailable@3.1.1:
|
||||
dependencies:
|
||||
axios: 1.7.7
|
||||
@ -33208,15 +33151,6 @@ snapshots:
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
|
||||
glob@10.5.0:
|
||||
dependencies:
|
||||
foreground-child: 3.3.1
|
||||
jackspeak: 3.4.3
|
||||
minimatch: 9.0.5
|
||||
minipass: 7.1.3
|
||||
package-json-from-dist: 1.0.1
|
||||
path-scurry: 1.11.1
|
||||
|
||||
glob@13.0.6:
|
||||
dependencies:
|
||||
minimatch: 10.2.4
|
||||
|
||||
Loading…
Reference in New Issue
Block a user