mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-13 19:20:04 +00:00
♻️ refactor(server): make Next a pure shell — (backend) routes forward to the Hono runtime (phase 2)
All 54 migrated (backend) route files become uniform fetchHonoRuntime forwarders: dev proxies to the standalone Hono server, production loads the vite-built apps/server dist in-process via runtime require — the backend dependency graph no longer passes through next build. Verified: after() semantics survive in-process dist loading (trace 201), SSE streams through the chain, full classic-dev e2e green. - devStartupSequence spawns the Hono dev server and sets LOBE_DEV_HONO_TARGET, so `bun run dev` = Hono + Next shell + Vite - root build/build:raw run build:hono first so the dist pairs with the Next build - hono-runtime client throws an actionable error when the dist is missing - route tests move next to their api-runtime handlers (apps/server/src/api-runtime/__tests__/, 8 files) - fix oidcProviderAPIHandler: restore the rejection path lost in the port — createNodeRequest failures surface as 500 again (regression test restored) - type re-exports (version/check-user/resolve-username) stay on the route files for client consumers; erased at build time
This commit is contained in:
+17
-6
@@ -26,16 +26,27 @@ apps/server/src/
|
||||
└── workflows-hono/ # /api/workflows/* Hono sub-app (agent-signal, memory, task)
|
||||
```
|
||||
|
||||
The package's exports resolve via the `@/server/*` alias (dual-path tsconfig: `apps/server/src/*` first, `src/server/*` fallback for the SSR-page helpers that still live there).
|
||||
The package's exports resolve via the `~server/*` alias (`apps/server/src/*`); `@/server/*` now refers only to the Next SSR-page helpers remaining in `src/server/`.
|
||||
|
||||
## Next as a Shell
|
||||
|
||||
Every `src/app/(backend)/**/route.ts` is a thin forwarder into this package:
|
||||
|
||||
- In dev, `fetchHonoRuntime` proxies each request to the standalone Hono server (`LOBE_DEV_HONO_TARGET`, default `http://localhost:3011`).
|
||||
- In production, it loads the vite-built `apps/server/dist/index.js` in-process via an opaque runtime `require` (override with `LOBE_HONO_DIST_ENTRY`), so the backend never passes through `next build`.
|
||||
- Route file paths, HTTP method export names, and segment config (`maxDuration`, `dynamic`) are contractual — the cloud repo re-exports them by path. `src/app/(backend)/route-shell.guard.test.ts` enforces that route files stay logic-free.
|
||||
- Exceptions: `webapi/revalidate` (Next ISR machinery) and `hono-runtime/[...path]` (the binding route itself).
|
||||
|
||||
The root `build` / `build:raw` scripts run `build:hono` first so the dist always pairs with the Next build.
|
||||
|
||||
## Dev Modes
|
||||
|
||||
| Mode | Command | Topology |
|
||||
| ------------------- | ----------------------- | ---------------------------------------- |
|
||||
| **Classic** | `bun run dev` | Next (`:3010`) + Vite (`:9876`) |
|
||||
| **Hono-Lite** (POC) | `bun run dev:hono-lite` | Hono (`:3011`) + Vite (`:9876`), no Next |
|
||||
| Mode | Command | Topology |
|
||||
| ------------------- | ----------------------- | ------------------------------------------------------ |
|
||||
| **Classic** | `bun run dev` | Hono (`:3011`) + Next shell (`:3010`) + Vite (`:9876`) |
|
||||
| **Hono-Lite** (POC) | `bun run dev:hono-lite` | Hono (`:3011`) + Vite (`:9876`), no Next |
|
||||
|
||||
Both modes coexist on this branch — pick whichever fits the task.
|
||||
Both modes coexist on this branch — pick whichever fits the task. Classic dev now spawns the Hono server automatically and points the Next API shells at it.
|
||||
|
||||
### Standalone Server (this package only)
|
||||
|
||||
|
||||
+24
-24
@@ -2,7 +2,7 @@
|
||||
import { NextRequest } from 'next/server';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { GET } from '../route';
|
||||
import { agentStreamAPIHandler } from '../agentStream';
|
||||
|
||||
// Mock dependencies first
|
||||
const mockStreamEventManager = {
|
||||
@@ -14,7 +14,7 @@ vi.mock('~server/modules/AgentRuntime', () => ({
|
||||
createStreamEventManager: vi.fn(() => mockStreamEventManager),
|
||||
}));
|
||||
|
||||
describe('/api/agent/stream route', () => {
|
||||
describe('agentStreamAPIHandler', () => {
|
||||
const MOCK_TIMESTAMP = 1758203237000;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -30,7 +30,7 @@ describe('/api/agent/stream route', () => {
|
||||
describe('GET handler', () => {
|
||||
it('should return 400 when operationId parameter is missing', async () => {
|
||||
const request = new NextRequest('https://test.com/api/agent/stream');
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
const data = await response.json();
|
||||
@@ -41,7 +41,7 @@ describe('/api/agent/stream route', () => {
|
||||
const request = new NextRequest(
|
||||
'https://test.com/api/agent/stream?operationId=test-operation',
|
||||
);
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get('Content-Type')).toBe('text/event-stream');
|
||||
@@ -62,7 +62,7 @@ describe('/api/agent/stream route', () => {
|
||||
'https://test.com/api/agent/stream?operationId=test-operation&lastEventId=123',
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
const decoder = new TextDecoder();
|
||||
const reader = response.body!.getReader();
|
||||
|
||||
@@ -133,7 +133,7 @@ describe('/api/agent/stream route', () => {
|
||||
];
|
||||
mockStreamEventManager.getStreamHistory.mockResolvedValue(mockEvents);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
const decoder = new TextDecoder();
|
||||
const reader = response.body!.getReader();
|
||||
|
||||
@@ -215,7 +215,7 @@ describe('/api/agent/stream route', () => {
|
||||
];
|
||||
mockStreamEventManager.getStreamHistory.mockResolvedValue(mockEvents);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
const decoder = new TextDecoder();
|
||||
const reader = response.body!.getReader();
|
||||
|
||||
@@ -285,7 +285,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
new Error('Redis connection failed'),
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
const decoder = new TextDecoder();
|
||||
const reader = response.body!.getReader();
|
||||
|
||||
@@ -347,7 +347,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
|
||||
mockStreamEventManager.subscribeStreamEvents.mockResolvedValue(undefined);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
@@ -373,7 +373,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
|
||||
mockStreamEventManager.subscribeStreamEvents.mockResolvedValue(undefined);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
@@ -394,7 +394,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
'https://test.com/api/agent/stream?operationId=test-operation',
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
const decoder = new TextDecoder();
|
||||
const reader = response.body!.getReader();
|
||||
|
||||
@@ -456,7 +456,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
},
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
// Verify the subscription was set up correctly
|
||||
expect(mockStreamEventManager.subscribeStreamEvents).toHaveBeenCalledWith(
|
||||
@@ -491,7 +491,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
},
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
// Verify we captured the callback
|
||||
expect(capturedCallback).toBeDefined();
|
||||
@@ -527,7 +527,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
},
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
// Verify we captured the callback
|
||||
expect(capturedCallback).toBeDefined();
|
||||
@@ -563,7 +563,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
},
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(capturedCallback).toBeDefined();
|
||||
expect(response.status).toBe(200);
|
||||
@@ -624,7 +624,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
},
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(capturedCallback).toBeDefined();
|
||||
expect(capturedSignal).toBeDefined();
|
||||
@@ -661,7 +661,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
},
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(capturedCallback).toBeDefined();
|
||||
@@ -695,7 +695,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
},
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(capturedCallback).toBeDefined();
|
||||
@@ -730,7 +730,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
},
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(capturedCallback).toBeDefined();
|
||||
@@ -777,7 +777,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
},
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(capturedCallback).toBeDefined();
|
||||
@@ -818,7 +818,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
`https://test.com/api/agent/stream?operationId=${operationId}`,
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
@@ -828,7 +828,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
'https://test.com/api/agent/stream?operationId=test&lastEventId=12345',
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
});
|
||||
@@ -838,7 +838,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
'https://test.com/api/agent/stream?operationId=test&includeHistory=false',
|
||||
);
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mockStreamEventManager.getStreamHistory).not.toHaveBeenCalled();
|
||||
@@ -847,7 +847,7 @@ data: {"type":"stream_end","timestamp":300,"operationId":"test-operation","data"
|
||||
it('should handle invalid URL gracefully', async () => {
|
||||
const request = new NextRequest('https://test.com/api/agent/stream?operationId=');
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await agentStreamAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
const data = await response.json();
|
||||
+6
-6
@@ -2,7 +2,7 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { GET, POST } from './route';
|
||||
import { betterAuthAPIHandler } from '../betterAuth';
|
||||
|
||||
type RouteHandler = (request: Request) => Promise<Response>;
|
||||
|
||||
@@ -29,7 +29,7 @@ const createPostRequest = (body: string, contentType = 'application/json') =>
|
||||
method: 'POST',
|
||||
}) as NextRequest;
|
||||
|
||||
describe('/api/auth/[...all] route', () => {
|
||||
describe('betterAuthAPIHandler', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.get.mockResolvedValue(Response.json({ ok: true }));
|
||||
@@ -37,7 +37,7 @@ describe('/api/auth/[...all] route', () => {
|
||||
});
|
||||
|
||||
it('returns 400 for malformed JSON auth requests before Better Auth handles them', async () => {
|
||||
const response = await POST(
|
||||
const response = await betterAuthAPIHandler(
|
||||
createPostRequest('{"email":"user@example.com","password":"secret",}'),
|
||||
);
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('/api/auth/[...all] route', () => {
|
||||
Response.json(await request.json()),
|
||||
);
|
||||
|
||||
const response = await POST(
|
||||
const response = await betterAuthAPIHandler(
|
||||
createPostRequest(JSON.stringify({ email: 'user@example.com', password: 'secret' })),
|
||||
);
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('/api/auth/[...all] route', () => {
|
||||
});
|
||||
|
||||
it('delegates non-JSON auth requests to Better Auth', async () => {
|
||||
const response = await POST(
|
||||
const response = await betterAuthAPIHandler(
|
||||
createPostRequest(
|
||||
'email=user%40example.com&password=secret',
|
||||
'application/x-www-form-urlencoded',
|
||||
@@ -80,7 +80,7 @@ describe('/api/auth/[...all] route', () => {
|
||||
it('delegates GET requests to Better Auth', async () => {
|
||||
const request = new Request('https://localhost/api/auth/get-session') as NextRequest;
|
||||
|
||||
const response = await GET(request);
|
||||
const response = await betterAuthAPIHandler(request);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(mocks.get).toHaveBeenCalledWith(request);
|
||||
+8
-12
@@ -7,7 +7,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { auth } from '@/auth';
|
||||
import { initModelRuntimeFromDB } from '~server/modules/ModelRuntime';
|
||||
|
||||
import { POST } from './route';
|
||||
import { chatAPIHandler } from '../chat';
|
||||
|
||||
vi.mock('@/app/(backend)/middleware/auth/utils', () => ({
|
||||
checkAuthMethod: vi.fn(),
|
||||
@@ -45,11 +45,9 @@ afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('POST handler', () => {
|
||||
describe('chatAPIHandler', () => {
|
||||
describe('init chat model', () => {
|
||||
it('should initialize ModelRuntime correctly with valid session', async () => {
|
||||
const mockParams = Promise.resolve({ provider: 'test-provider' });
|
||||
|
||||
const mockChatResponse = new Response(JSON.stringify({ success: true }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
@@ -60,7 +58,7 @@ describe('POST handler', () => {
|
||||
|
||||
vi.mocked(initModelRuntimeFromDB).mockResolvedValue(new ModelRuntime(mockRuntime));
|
||||
|
||||
await POST(request as unknown as Request, { params: mockParams });
|
||||
await chatAPIHandler(request as unknown as Request, { provider: 'test-provider' });
|
||||
|
||||
expect(initModelRuntimeFromDB).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
@@ -73,9 +71,7 @@ describe('POST handler', () => {
|
||||
it('should return Unauthorized error when no session exists', async () => {
|
||||
vi.mocked(auth.api.getSession).mockResolvedValue(null);
|
||||
|
||||
const mockParams = Promise.resolve({ provider: 'test-provider' });
|
||||
|
||||
const response = await POST(request, { params: mockParams });
|
||||
const response = await chatAPIHandler(request, { provider: 'test-provider' });
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
});
|
||||
@@ -83,7 +79,6 @@ describe('POST handler', () => {
|
||||
|
||||
describe('chat', () => {
|
||||
it('should correctly handle chat completion with valid payload', async () => {
|
||||
const mockParams = Promise.resolve({ provider: 'test-provider' });
|
||||
const mockChatPayload = { message: 'Hello, world!' };
|
||||
request = new Request(new URL('https://test.com'), {
|
||||
method: 'POST',
|
||||
@@ -98,7 +93,9 @@ describe('POST handler', () => {
|
||||
|
||||
vi.mocked(initModelRuntimeFromDB).mockResolvedValue(new ModelRuntime(mockRuntime));
|
||||
|
||||
const response = await POST(request as unknown as Request, { params: mockParams });
|
||||
const response = await chatAPIHandler(request as unknown as Request, {
|
||||
provider: 'test-provider',
|
||||
});
|
||||
|
||||
expect(response).toEqual(mockChatResponse);
|
||||
expect(mockRuntime.chat).toHaveBeenCalledWith(mockChatPayload, {
|
||||
@@ -108,7 +105,6 @@ describe('POST handler', () => {
|
||||
});
|
||||
|
||||
it('should return an error response when chat completion fails', async () => {
|
||||
const mockParams = Promise.resolve({ provider: 'test-provider' });
|
||||
const mockChatPayload = { message: 'Hello, world!' };
|
||||
request = new Request(new URL('https://test.com'), {
|
||||
method: 'POST',
|
||||
@@ -128,7 +124,7 @@ describe('POST handler', () => {
|
||||
|
||||
vi.mocked(initModelRuntimeFromDB).mockResolvedValue(new ModelRuntime(mockRuntime));
|
||||
|
||||
const response = await POST(request, { params: mockParams });
|
||||
const response = await chatAPIHandler(request, { provider: 'test-provider' });
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
expect(await response.json()).toEqual({
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { GET } from './route';
|
||||
import { connectorOAuthCallbackAPIHandler } from '../connectorOAuthCallback';
|
||||
|
||||
const { mockConsume, mockFindById, mockSync, mockUpdate } = vi.hoisted(() => ({
|
||||
mockConsume: vi.fn(),
|
||||
@@ -41,7 +41,7 @@ vi.mock('@/database/models/connectorTool', () => ({
|
||||
vi.mock('~server/services/connector/sync', () => ({ syncConnectorToolsById: mockSync }));
|
||||
|
||||
const makeReq = () =>
|
||||
({ nextUrl: { searchParams: new URLSearchParams('code=abc&state=xyz') } }) as any;
|
||||
new Request('https://app.example.com/oauth/connector/callback?code=abc&state=xyz');
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -62,11 +62,11 @@ beforeEach(() => {
|
||||
mockUpdate.mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
describe('connector OAuth callback', () => {
|
||||
describe('connectorOAuthCallbackAPIHandler', () => {
|
||||
it('reports synced:false when auth succeeds but tool sync fails', async () => {
|
||||
mockSync.mockRejectedValue(new Error('mcp down'));
|
||||
|
||||
const body = await (await GET(makeReq())).text();
|
||||
const body = await (await connectorOAuthCallbackAPIHandler(makeReq())).text();
|
||||
|
||||
expect(body).toContain('"success":true');
|
||||
expect(body).toContain('"synced":false');
|
||||
@@ -75,7 +75,7 @@ describe('connector OAuth callback', () => {
|
||||
it('reports synced:true when auth and tool sync both succeed', async () => {
|
||||
mockSync.mockResolvedValue({ toolCount: 5 });
|
||||
|
||||
const body = await (await GET(makeReq())).text();
|
||||
const body = await (await connectorOAuthCallbackAPIHandler(makeReq())).text();
|
||||
|
||||
expect(body).toContain('"success":true');
|
||||
expect(body).toContain('"synced":true');
|
||||
+4
-4
@@ -7,7 +7,7 @@ import type { FileItem } from '@/database/schemas';
|
||||
import { getServerDB } from '@/database/server';
|
||||
import { FileService } from '~server/services/file';
|
||||
|
||||
import { GET } from './route';
|
||||
import { fileProxyAPIHandler } from '../fileProxy';
|
||||
|
||||
const fileServiceMocks = vi.hoisted(() => {
|
||||
const instance = {
|
||||
@@ -35,7 +35,7 @@ vi.mock('~server/services/file', () => ({
|
||||
FileService: fileServiceMocks.FileService,
|
||||
}));
|
||||
|
||||
describe('file proxy route', () => {
|
||||
describe('fileProxyAPIHandler', () => {
|
||||
const db = {} as LobeChatDatabase;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -53,8 +53,8 @@ describe('file proxy route', () => {
|
||||
});
|
||||
|
||||
it('should redirect to a cached presigned preview URL instead of a public full file URL', async () => {
|
||||
const response = await GET(new Request('https://lobehub.com/f/file-id'), {
|
||||
params: Promise.resolve({ id: 'file-id' }),
|
||||
const response = await fileProxyAPIHandler(new Request('https://lobehub.com/f/file-id'), {
|
||||
id: 'file-id',
|
||||
});
|
||||
|
||||
expect(response.status).toBe(302);
|
||||
+4
-3
@@ -1,5 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { memoryExtractionBenchmarkLoCoMoWebhookAPIHandler } from '../memoryExtractionBenchmark';
|
||||
|
||||
const replaceParts = vi.fn();
|
||||
const upsertSource = vi.fn();
|
||||
const extractBenchmarkSource = vi.fn();
|
||||
@@ -27,7 +29,7 @@ vi.mock('@lobechat/memory-user-memory', () => ({
|
||||
BenchmarkLocomoContextProvider: vi.fn().mockImplementation((params) => params),
|
||||
}));
|
||||
|
||||
describe('benchmark LoCoMo memory extraction webhook', () => {
|
||||
describe('memoryExtractionBenchmarkLoCoMoWebhookAPIHandler', () => {
|
||||
beforeEach(() => {
|
||||
replaceParts.mockReset();
|
||||
upsertSource.mockReset();
|
||||
@@ -42,8 +44,7 @@ describe('benchmark LoCoMo memory extraction webhook', () => {
|
||||
});
|
||||
|
||||
it('returns per-session ingestion results and inserted part count', async () => {
|
||||
const { POST } = await import('../route');
|
||||
const response = await POST(
|
||||
const response = await memoryExtractionBenchmarkLoCoMoWebhookAPIHandler(
|
||||
new Request('http://localhost/api/webhooks/memory-extraction/benchmark-locomo', {
|
||||
body: JSON.stringify({
|
||||
sampleId: 'conv-26',
|
||||
+8
-20
@@ -7,7 +7,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { auth } from '@/auth';
|
||||
import { initModelRuntimeFromDB } from '~server/modules/ModelRuntime';
|
||||
|
||||
import { GET } from './route';
|
||||
import { modelsAPIHandler } from '../models';
|
||||
|
||||
vi.mock('@/app/(backend)/middleware/auth/utils', () => ({
|
||||
checkAuthMethod: vi.fn(),
|
||||
@@ -43,11 +43,9 @@ afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('GET handler', () => {
|
||||
describe('modelsAPIHandler', () => {
|
||||
describe('error handling', () => {
|
||||
it('should not expose stack trace when an Error is thrown', async () => {
|
||||
const mockParams = Promise.resolve({ provider: 'google' });
|
||||
|
||||
const errorWithStack = new Error('Something went wrong');
|
||||
errorWithStack.stack =
|
||||
'Error: Something went wrong\n at Object.<anonymous> (/path/to/file.ts:10:15)';
|
||||
@@ -59,7 +57,7 @@ describe('GET handler', () => {
|
||||
};
|
||||
vi.mocked(initModelRuntimeFromDB).mockResolvedValue(new ModelRuntime(mockRuntime));
|
||||
|
||||
const response = await GET(request, { params: mockParams });
|
||||
const response = await modelsAPIHandler(request, { provider: 'google' });
|
||||
const responseBody = await response.json();
|
||||
|
||||
expect(responseBody.body.error.name).toBe('Error');
|
||||
@@ -72,8 +70,6 @@ describe('GET handler', () => {
|
||||
});
|
||||
|
||||
it('should preserve error name for custom error types', async () => {
|
||||
const mockParams = Promise.resolve({ provider: 'google' });
|
||||
|
||||
class CustomError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
@@ -91,7 +87,7 @@ describe('GET handler', () => {
|
||||
};
|
||||
vi.mocked(initModelRuntimeFromDB).mockResolvedValue(new ModelRuntime(mockRuntime));
|
||||
|
||||
const response = await GET(request, { params: mockParams });
|
||||
const response = await modelsAPIHandler(request, { provider: 'google' });
|
||||
const responseBody = await response.json();
|
||||
|
||||
expect(responseBody.body.error.name).toBe('CustomError');
|
||||
@@ -100,8 +96,6 @@ describe('GET handler', () => {
|
||||
});
|
||||
|
||||
it('should pass through structured error objects as-is', async () => {
|
||||
const mockParams = Promise.resolve({ provider: 'google' });
|
||||
|
||||
const structuredError = {
|
||||
errorType: ChatErrorType.InternalServerError,
|
||||
error: { code: 'PROVIDER_ERROR', details: 'API limit exceeded' },
|
||||
@@ -114,7 +108,7 @@ describe('GET handler', () => {
|
||||
};
|
||||
vi.mocked(initModelRuntimeFromDB).mockResolvedValue(new ModelRuntime(mockRuntime));
|
||||
|
||||
const response = await GET(request, { params: mockParams });
|
||||
const response = await modelsAPIHandler(request, { provider: 'google' });
|
||||
const responseBody = await response.json();
|
||||
|
||||
expect(responseBody.body.error.code).toBe('PROVIDER_ERROR');
|
||||
@@ -122,8 +116,6 @@ describe('GET handler', () => {
|
||||
});
|
||||
|
||||
it('should return correct status code for errors', async () => {
|
||||
const mockParams = Promise.resolve({ provider: 'google' });
|
||||
|
||||
const mockRuntime: LobeRuntimeAI = {
|
||||
baseURL: 'abc',
|
||||
chat: vi.fn(),
|
||||
@@ -131,14 +123,12 @@ describe('GET handler', () => {
|
||||
};
|
||||
vi.mocked(initModelRuntimeFromDB).mockResolvedValue(new ModelRuntime(mockRuntime));
|
||||
|
||||
const response = await GET(request, { params: mockParams });
|
||||
const response = await modelsAPIHandler(request, { provider: 'google' });
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
});
|
||||
|
||||
it('should include provider in error response', async () => {
|
||||
const mockParams = Promise.resolve({ provider: 'openai' });
|
||||
|
||||
const mockRuntime: LobeRuntimeAI = {
|
||||
baseURL: 'abc',
|
||||
chat: vi.fn(),
|
||||
@@ -146,7 +136,7 @@ describe('GET handler', () => {
|
||||
};
|
||||
vi.mocked(initModelRuntimeFromDB).mockResolvedValue(new ModelRuntime(mockRuntime));
|
||||
|
||||
const response = await GET(request, { params: mockParams });
|
||||
const response = await modelsAPIHandler(request, { provider: 'openai' });
|
||||
const responseBody = await response.json();
|
||||
|
||||
expect(responseBody.body.provider).toBe('openai');
|
||||
@@ -155,8 +145,6 @@ describe('GET handler', () => {
|
||||
|
||||
describe('success cases', () => {
|
||||
it('should return model list on success', async () => {
|
||||
const mockParams = Promise.resolve({ provider: 'openai' });
|
||||
|
||||
const mockModelList = [
|
||||
{ id: 'gpt-4', name: 'GPT-4' },
|
||||
{ id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo' },
|
||||
@@ -169,7 +157,7 @@ describe('GET handler', () => {
|
||||
};
|
||||
vi.mocked(initModelRuntimeFromDB).mockResolvedValue(new ModelRuntime(mockRuntime));
|
||||
|
||||
const response = await GET(request, { params: mockParams });
|
||||
const response = await modelsAPIHandler(request, { provider: 'openai' });
|
||||
const responseBody = await response.json();
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
+31
-14
@@ -32,7 +32,22 @@ vi.mock('~server/services/oidc/oidcProvider', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('OIDC route', () => {
|
||||
const makeRequest = () =>
|
||||
new Request('https://example.com/oidc/token', {
|
||||
body: 'grant_type=refresh_token',
|
||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
||||
method: 'POST',
|
||||
}) as unknown as NextRequest;
|
||||
|
||||
const callWithTimeout = (handler: (req: NextRequest) => Promise<Response>, request: NextRequest) =>
|
||||
Promise.race([
|
||||
handler(request),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('OIDC route timed out')), 50),
|
||||
),
|
||||
]);
|
||||
|
||||
describe('oidcProviderAPIHandler', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -48,22 +63,24 @@ describe('OIDC route', () => {
|
||||
it('returns a 500 response when creating the Node request fails', async () => {
|
||||
mocks.createNodeRequest.mockRejectedValueOnce(new Error('body stream aborted'));
|
||||
|
||||
const { POST } = await import('./route');
|
||||
const request = new Request('https://example.com/oidc/token', {
|
||||
body: 'grant_type=refresh_token',
|
||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
||||
method: 'POST',
|
||||
}) as unknown as NextRequest;
|
||||
|
||||
const response = await Promise.race([
|
||||
POST(request),
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('OIDC route timed out')), 50),
|
||||
),
|
||||
]);
|
||||
const { oidcProviderAPIHandler } = await import('../oidc');
|
||||
const response = await callWithTimeout(oidcProviderAPIHandler, makeRequest());
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
await expect(response.text()).resolves.toContain('body stream aborted');
|
||||
expect(mocks.middleware).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns a 500 response when the OIDC provider flow fails', async () => {
|
||||
mocks.providerCallback.mockImplementationOnce(() => {
|
||||
throw new Error('callback exploded');
|
||||
});
|
||||
|
||||
const { oidcProviderAPIHandler } = await import('../oidc');
|
||||
const response = await callWithTimeout(oidcProviderAPIHandler, makeRequest());
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
await expect(response.text()).resolves.toContain('callback exploded');
|
||||
expect(mocks.middleware).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -339,7 +339,7 @@ export const oidcProviderAPIHandler = async (request: Request) => {
|
||||
}
|
||||
});
|
||||
providerLog('Middleware call initiated, waiting for its callback OR nodeResponse.end()...');
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
|
||||
providerLog('Promise surrounding middleware call resolved.');
|
||||
|
||||
+2
-2
@@ -39,13 +39,13 @@
|
||||
"apps/desktop/src/main"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "bun run build:spa && bun run build:spa:auth && bun run build:spa:copy && bun run build:next",
|
||||
"build": "bun run build:hono && bun run build:spa && bun run build:spa:auth && bun run build:spa:copy && bun run build:next",
|
||||
"build:analyze": "cross-env NODE_OPTIONS=--max-old-space-size=81920 next experimental-analyze",
|
||||
"build:docker": "pnpm run build:spa && pnpm run build:spa:mobile && pnpm run build:spa:auth && pnpm run build:spa:copy && cross-env NODE_OPTIONS=--max-old-space-size=8192 DOCKER=true next build",
|
||||
"build:hono": "pnpm --filter @lobechat/server build",
|
||||
"build:next": "cross-env NODE_OPTIONS=--max-old-space-size=7168 bun run build:next:raw",
|
||||
"build:next:raw": "next build",
|
||||
"build:raw": "bun run build:spa:raw && bun run build:spa:auth && bun run build:spa:copy && bun run build:next:raw",
|
||||
"build:raw": "bun run build:hono && bun run build:spa:raw && bun run build:spa:auth && bun run build:spa:copy && bun run build:next:raw",
|
||||
"build:spa": "cross-env NODE_OPTIONS=--max-old-space-size=8192 pnpm run build:spa:raw",
|
||||
"build:spa:auth": "rm -rf public/_spa-auth && cross-env NODE_OPTIONS=--max-old-space-size=8192 AUTH=true vite build",
|
||||
"build:spa:copy": "tsx scripts/copySpaBuild.mts && tsx scripts/generateSpaTemplates.mts",
|
||||
|
||||
@@ -42,8 +42,10 @@ let nextPort = 3010;
|
||||
let nextRootUrl = `http://${NEXT_HOST}:${nextPort}/`;
|
||||
let nextProcess: ChildProcess | undefined;
|
||||
let viteProcess: ChildProcess | undefined;
|
||||
let honoProcess: ChildProcess | undefined;
|
||||
let nextHandle: DevProcessHandle | undefined;
|
||||
let viteHandle: DevProcessHandle | undefined;
|
||||
let honoHandle: DevProcessHandle | undefined;
|
||||
let forceKillTimer: ReturnType<typeof setTimeout> | undefined;
|
||||
let shuttingDown = false;
|
||||
|
||||
@@ -171,11 +173,13 @@ const runNextBackgroundTasks = () => {
|
||||
const terminateChildren = () => {
|
||||
sendSignalToDevProcess(viteHandle, 'SIGTERM');
|
||||
sendSignalToDevProcess(nextHandle, 'SIGTERM');
|
||||
sendSignalToDevProcess(honoHandle, 'SIGTERM');
|
||||
};
|
||||
|
||||
const forceKillChildren = () => {
|
||||
sendSignalToDevProcess(viteHandle, 'SIGKILL');
|
||||
sendSignalToDevProcess(nextHandle, 'SIGKILL');
|
||||
sendSignalToDevProcess(honoHandle, 'SIGKILL');
|
||||
};
|
||||
|
||||
const clearForceKillTimer = () => {
|
||||
@@ -189,7 +193,8 @@ const hasChildSettled = (child?: ChildProcess) =>
|
||||
|
||||
const clearForceKillTimerWhenChildrenSettle = () => {
|
||||
if (!shuttingDown) return;
|
||||
if (hasChildSettled(nextProcess) && hasChildSettled(viteProcess)) clearForceKillTimer();
|
||||
if (hasChildSettled(nextProcess) && hasChildSettled(viteProcess) && hasChildSettled(honoProcess))
|
||||
clearForceKillTimer();
|
||||
};
|
||||
|
||||
const shutdownAll = (signal: NodeJS.Signals) => {
|
||||
@@ -209,7 +214,7 @@ const shutdownAll = (signal: NodeJS.Signals) => {
|
||||
}, FORCE_KILL_TIMEOUT_MS);
|
||||
};
|
||||
|
||||
const watchChildExit = (child: ChildProcess, name: 'next' | 'vite') => {
|
||||
const watchChildExit = (child: ChildProcess, name: 'hono' | 'next' | 'vite') => {
|
||||
child.once('exit', (code, signal) => {
|
||||
if (shuttingDown) {
|
||||
clearForceKillTimerWhenChildrenSettle();
|
||||
@@ -247,6 +252,15 @@ const main = async () => {
|
||||
forceKillChildren();
|
||||
});
|
||||
|
||||
// The (backend) routes are thin shells forwarding into the standalone Hono
|
||||
// runtime; point them at the dev Hono server and spawn it alongside Next.
|
||||
const honoPort = Number.parseInt(process.env.HONO_PORT || '', 10) || 3011;
|
||||
process.env.LOBE_DEV_HONO_TARGET ||= `http://localhost:${honoPort}`;
|
||||
|
||||
honoProcess = runNpmScript('dev:hono:server');
|
||||
honoHandle = createDevProcessHandle({ isWindows, pid: honoProcess.pid });
|
||||
watchChildExit(honoProcess, 'hono');
|
||||
|
||||
nextProcess = spawn('npx', ['next', 'dev', '-p', String(nextPort)], {
|
||||
detached: !isWindows,
|
||||
env: process.env,
|
||||
@@ -264,6 +278,7 @@ const main = async () => {
|
||||
await Promise.race([
|
||||
new Promise((resolve) => nextProcess?.once('exit', resolve)),
|
||||
new Promise((resolve) => viteProcess?.once('exit', resolve)),
|
||||
new Promise((resolve) => honoProcess?.once('exit', resolve)),
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import app from '~server/agent-hono';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (request: Request) => app.fetch(request);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { agentStreamAPIHandler } from '~server/api-runtime/agentStream';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const GET = (req: Request) => agentStreamAPIHandler(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { betterAuthAPIHandler } from '~server/api-runtime/betterAuth';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => betterAuthAPIHandler(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
export type { CheckUserResponseData } from '~server/api-runtime/auth';
|
||||
export { checkUserAPIHandler as POST } from '~server/api-runtime/auth';
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
export type { ResolveUsernameResponseData } from '~server/api-runtime/auth';
|
||||
export { resolveUsernameAPIHandler as POST } from '~server/api-runtime/auth';
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { agentTracingAPIHandler as GET } from '~server/api-runtime/dev';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { memoryUserMemoryBenchmarkLoCoMoDevAPIHandler as POST } from '~server/api-runtime/memoryBenchmarkDev';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { testPushAPIHandler as POST } from '~server/api-runtime/dev';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { openAPIHandler } from '~server/api-runtime/openapi';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => openAPIHandler(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
|
||||
export type { VersionResponseData } from '~server/api-runtime/version';
|
||||
export { versionAPIHandler as GET } from '~server/api-runtime/version';
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { casdoorWebhookAPIHandler as POST } from '~server/api-runtime/webhooks';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { logtoWebhookAPIHandler as POST } from '~server/api-runtime/webhooks';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { memoryExtractionBenchmarkLoCoMoWebhookAPIHandler as POST } from '~server/api-runtime/memoryExtractionBenchmark';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { memoryExtractionWebhookAPIHandler as POST } from '~server/api-runtime/memoryExtraction';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { memoryUserPersonaUpdateWritingWebhookAPIHandler as POST } from '~server/api-runtime/memoryExtraction';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
+5
-1
@@ -1 +1,5 @@
|
||||
export { memoryExtractChatTopicCancelWebhookAPIHandler as POST } from '~server/api-runtime/memoryExtraction';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { videoWebhookAPIHandler } from '~server/api-runtime/videoWebhook';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = async (req: Request, ctx: { params: Promise<{ provider: string }> }) =>
|
||||
videoWebhookAPIHandler(req, await ctx.params);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import app from '~server/workflows-hono';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = (request: Request) => app.fetch(request);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import workflowsApp from '~server/workflows-hono';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = (req: Request) => workflowsApp.fetch(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import workflowsApp from '~server/workflows-hono';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = (req: Request) => workflowsApp.fetch(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { agentEvalRunOnThreadCompleteAPIHandler as POST } from '~server/api-runtime/agentEvalRunWorkflow';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { agentEvalRunOnTrajectoryCompleteAPIHandler as POST } from '~server/api-runtime/agentEvalRunWorkflow';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import workflowsApp from '~server/workflows-hono';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = (req: Request) => workflowsApp.fetch(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import workflowsApp from '~server/workflows-hono';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = (req: Request) => workflowsApp.fetch(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import workflowsApp from '~server/workflows-hono';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = (req: Request) => workflowsApp.fetch(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import workflowsApp from '~server/workflows-hono';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = (req: Request) => workflowsApp.fetch(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import workflowsApp from '~server/workflows-hono';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = (req: Request) => workflowsApp.fetch(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import workflowsApp from '~server/workflows-hono';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = (req: Request) => workflowsApp.fetch(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { fileProxyAPIHandler } from '~server/api-runtime/fileProxy';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const GET = async (req: Request, ctx: { params: Promise<{ id: string }> }) =>
|
||||
fileProxyAPIHandler(req, await ctx.params);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { marketAgentAPIHandler } from '~server/api-runtime/market';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
type RouteContext = { params: Promise<{ segments?: string[] }> };
|
||||
|
||||
const handler = async (req: Request, ctx: RouteContext) =>
|
||||
marketAgentAPIHandler(req, await ctx.params);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { marketOIDCAPIHandler } from '~server/api-runtime/market';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
type RouteContext = { params: Promise<{ segments?: string[] }> };
|
||||
|
||||
const handler = async (req: Request, ctx: RouteContext) =>
|
||||
marketOIDCAPIHandler(req, await ctx.params);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { marketSocialAPIHandler } from '~server/api-runtime/market';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
type RouteContext = { params: Promise<{ segments?: string[] }> };
|
||||
|
||||
const handler = async (req: Request, ctx: RouteContext) =>
|
||||
marketSocialAPIHandler(req, await ctx.params);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { marketUserProfileAPIHandler } from '~server/api-runtime/market';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const GET = async (req: Request, ctx: { params: Promise<{ username: string }> }) =>
|
||||
marketUserProfileAPIHandler(req, await ctx.params);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { marketUserMeAPIHandler } from '~server/api-runtime/market';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const PUT = (req: Request) => marketUserMeAPIHandler(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const PUT = handler;
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { connectorOAuthCallbackAPIHandler as GET } from '~server/api-runtime/connectorOAuthCallback';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { oidcProviderAPIHandler } from '~server/api-runtime/oidc';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => oidcProviderAPIHandler(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { oidcCallbackDesktopAPIHandler } from '~server/api-runtime/oidc';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const GET = (req: Request) => oidcCallbackDesktopAPIHandler(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { oidcClearSessionAPIHandler as POST } from '~server/api-runtime/oidc';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { oidcConsentAPIHandler as POST } from '~server/api-runtime/oidc';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { oidcHandoffAPIHandler as GET } from '~server/api-runtime/oidc';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
export { asyncTRPCHandler as GET, asyncTRPCHandler as POST } from '~server/trpc-runtime/async';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
export { lambdaTRPCHandler as GET, lambdaTRPCHandler as POST } from '~server/trpc-runtime/lambda';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
export { mobileTRPCHandler as GET, mobileTRPCHandler as POST } from '~server/trpc-runtime/mobile';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,6 @@
|
||||
export { toolsTRPCHandler as GET, toolsTRPCHandler as POST } from '~server/trpc-runtime/tools';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { chatAPIHandler } from '~server/api-runtime/chat';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
// If user don't use fluid compute, will build failed
|
||||
// this enforce user to enable fluid compute
|
||||
export const maxDuration = 300;
|
||||
|
||||
export const POST = async (req: Request, ctx: { params: Promise<{ provider: string }> }) =>
|
||||
chatAPIHandler(req, await ctx.params);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { comfyUICreateImageAPIHandler } from '~server/api-runtime/createImage';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
export const maxDuration = 300;
|
||||
|
||||
export const POST = (req: Request) => comfyUICreateImageAPIHandler(req);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { pullModelsAPIHandler } from '~server/api-runtime/models';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = async (req: Request, ctx: { params: Promise<{ provider: string }> }) =>
|
||||
pullModelsAPIHandler(req, await ctx.params);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { modelsAPIHandler } from '~server/api-runtime/models';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const GET = async (req: Request, ctx: { params: Promise<{ provider: string }> }) =>
|
||||
modelsAPIHandler(req, await ctx.params);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { openAISTTAPIHandler as POST } from '~server/api-runtime/speech';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { traceAPIHandler } from '~server/api-runtime/trace';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const POST = (req: Request) => traceAPIHandler(req);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { edgeTTSAPIHandler as POST } from '~server/api-runtime/speech';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { microsoftTTSAPIHandler as POST } from '~server/api-runtime/speech';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { openAITTSAPIHandler as POST } from '~server/api-runtime/speech';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const POST = handler;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { userAvatarAPIHandler } from '~server/api-runtime/userAvatar';
|
||||
import { fetchHonoRuntime } from '@/server/hono-runtime/client';
|
||||
|
||||
export const GET = async (req: Request, ctx: { params: Promise<{ id: string; image: string }> }) =>
|
||||
userAvatarAPIHandler(req, await ctx.params);
|
||||
const handler = (req: Request) => fetchHonoRuntime(req);
|
||||
|
||||
export const GET = handler;
|
||||
|
||||
@@ -63,7 +63,20 @@ const loadProductionHonoApp = () => {
|
||||
|
||||
const entry =
|
||||
process.env.LOBE_HONO_DIST_ENTRY || path.join(process.cwd(), 'apps/server/dist/index.js');
|
||||
const module = loadExternalModule(entry) as HonoDistModule | HonoFetchApp;
|
||||
|
||||
let module: HonoDistModule | HonoFetchApp;
|
||||
try {
|
||||
module = loadExternalModule(entry) as HonoDistModule | HonoFetchApp;
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND') {
|
||||
throw new Error(
|
||||
`Hono dist entry not found at ${entry}. Build it with \`pnpm --filter @lobechat/server build\`, ` +
|
||||
'or in dev run `bun run dev` / set LOBE_DEV_HONO_TARGET to a running Hono server.',
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
const app = isHonoFetchApp(module)
|
||||
? module
|
||||
: isHonoFetchApp(module.default)
|
||||
|
||||
Reference in New Issue
Block a user