@surfjs/next 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/index.cjs +243 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +47 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +238 -0
- package/dist/index.js.map +1 -0
- package/dist/pages.cjs +211 -0
- package/dist/pages.cjs.map +1 -0
- package/dist/pages.d.cts +56 -0
- package/dist/pages.d.ts +56 -0
- package/dist/pages.js +206 -0
- package/dist/pages.js.map +1 -0
- package/dist/shared-CckJeURD.d.cts +22 -0
- package/dist/shared-CckJeURD.d.ts +22 -0
- package/package.json +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @surfjs/next
|
|
2
|
+
|
|
3
|
+
Next.js adapter for [Surf.js](https://surf.codes) — supports both **App Router** (route handlers) and **Pages Router** (API routes).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @surfjs/next @surfjs/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## App Router (Recommended)
|
|
12
|
+
|
|
13
|
+
Create a catch-all route handler at `app/api/surf/[...slug]/route.ts`:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createSurf } from '@surfjs/core';
|
|
17
|
+
import { createSurfRouteHandler } from '@surfjs/next';
|
|
18
|
+
|
|
19
|
+
const surf = createSurf({
|
|
20
|
+
name: 'my-app',
|
|
21
|
+
commands: {
|
|
22
|
+
hello: {
|
|
23
|
+
description: 'Say hello',
|
|
24
|
+
params: { name: { type: 'string', required: true } },
|
|
25
|
+
run: ({ name }) => ({ message: `Hello, ${name}!` }),
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const { GET, POST } = createSurfRouteHandler(surf);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The handler serves:
|
|
34
|
+
- `GET /api/surf/.well-known/surf.json` — Surf manifest
|
|
35
|
+
- `POST /api/surf/surf/execute` — Execute commands
|
|
36
|
+
- `POST /api/surf/surf/pipeline` — Execute pipelines
|
|
37
|
+
- `POST /api/surf/surf/session/start` — Start sessions
|
|
38
|
+
- `POST /api/surf/surf/session/end` — End sessions
|
|
39
|
+
|
|
40
|
+
### Edge Runtime
|
|
41
|
+
|
|
42
|
+
The App Router handler is fully edge-compatible — no Node.js APIs are used. Add the edge runtime directive if desired:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
export const runtime = 'edge';
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Pages Router
|
|
49
|
+
|
|
50
|
+
Create a catch-all API route at `pages/api/surf/[...slug].ts`:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import { createSurf } from '@surfjs/core';
|
|
54
|
+
import { createSurfApiHandler } from '@surfjs/next/pages';
|
|
55
|
+
|
|
56
|
+
const surf = createSurf({
|
|
57
|
+
name: 'my-app',
|
|
58
|
+
commands: {
|
|
59
|
+
hello: {
|
|
60
|
+
description: 'Say hello',
|
|
61
|
+
params: { name: { type: 'string', required: true } },
|
|
62
|
+
run: ({ name }) => ({ message: `Hello, ${name}!` }),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export default createSurfApiHandler(surf);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- ✅ App Router (route handlers) with edge runtime support
|
|
73
|
+
- ✅ Pages Router (API routes) with Node.js runtime
|
|
74
|
+
- ✅ SSE streaming for both routers
|
|
75
|
+
- ✅ Session management
|
|
76
|
+
- ✅ Pipeline execution
|
|
77
|
+
- ✅ Bearer token auth extraction
|
|
78
|
+
- ✅ Client IP extraction (`x-forwarded-for`, `x-real-ip`)
|
|
79
|
+
- ✅ ETag caching for manifest
|
|
80
|
+
- ✅ CORS headers
|
|
81
|
+
- ✅ Consistent error code → HTTP status mapping
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@surfjs/core');
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
|
|
7
|
+
// src/shared.ts
|
|
8
|
+
function getErrorStatus(code) {
|
|
9
|
+
switch (code) {
|
|
10
|
+
case "UNKNOWN_COMMAND":
|
|
11
|
+
return 404;
|
|
12
|
+
case "INVALID_PARAMS":
|
|
13
|
+
return 400;
|
|
14
|
+
case "AUTH_REQUIRED":
|
|
15
|
+
return 401;
|
|
16
|
+
case "AUTH_FAILED":
|
|
17
|
+
return 403;
|
|
18
|
+
case "SESSION_EXPIRED":
|
|
19
|
+
return 410;
|
|
20
|
+
case "RATE_LIMITED":
|
|
21
|
+
return 429;
|
|
22
|
+
case "NOT_SUPPORTED":
|
|
23
|
+
return 501;
|
|
24
|
+
default:
|
|
25
|
+
return 500;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function extractAuth(authHeader) {
|
|
29
|
+
if (!authHeader) return void 0;
|
|
30
|
+
return authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
|
|
31
|
+
}
|
|
32
|
+
function extractIp(forwardedFor, realIp) {
|
|
33
|
+
if (forwardedFor) return forwardedFor.split(",")[0]?.trim();
|
|
34
|
+
return realIp ?? void 0;
|
|
35
|
+
}
|
|
36
|
+
var CORS_HEADERS = {
|
|
37
|
+
"Access-Control-Allow-Origin": "*"
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/index.ts
|
|
41
|
+
function createSurfRouteHandler(surf, options = {}) {
|
|
42
|
+
const registry = surf.commands;
|
|
43
|
+
const sessions = surf.sessions;
|
|
44
|
+
const basePath = options.basePath ?? "/api/surf";
|
|
45
|
+
function getPathname(request) {
|
|
46
|
+
const req = request;
|
|
47
|
+
if (req.nextUrl) return req.nextUrl.pathname;
|
|
48
|
+
return new URL(request.url).pathname;
|
|
49
|
+
}
|
|
50
|
+
function resolveRoute(request, slug) {
|
|
51
|
+
if (slug && slug.length > 0) {
|
|
52
|
+
return "/" + slug.join("/");
|
|
53
|
+
}
|
|
54
|
+
const pathname = getPathname(request);
|
|
55
|
+
const stripped = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
|
|
56
|
+
return stripped || "/";
|
|
57
|
+
}
|
|
58
|
+
function jsonResponse(body, status, extraHeaders) {
|
|
59
|
+
return new Response(JSON.stringify(body), {
|
|
60
|
+
status,
|
|
61
|
+
headers: {
|
|
62
|
+
"Content-Type": "application/json",
|
|
63
|
+
...CORS_HEADERS,
|
|
64
|
+
...extraHeaders
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function GET(request, context) {
|
|
69
|
+
const { slug } = await context.params;
|
|
70
|
+
const route = resolveRoute(request, slug);
|
|
71
|
+
if (route === "/.well-known/surf.json" || route === "/") {
|
|
72
|
+
const manifestData = surf.manifest();
|
|
73
|
+
const etag = `"${manifestData.checksum}"`;
|
|
74
|
+
if (request.headers.get("if-none-match") === etag) {
|
|
75
|
+
return new Response(null, {
|
|
76
|
+
status: 304,
|
|
77
|
+
headers: { "ETag": etag, "Cache-Control": "public, max-age=300", ...CORS_HEADERS }
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return jsonResponse(manifestData, 200, {
|
|
81
|
+
"ETag": etag,
|
|
82
|
+
"Cache-Control": "public, max-age=300"
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
return jsonResponse(
|
|
86
|
+
{ ok: false, error: { code: "NOT_FOUND", message: `Unknown route: GET ${route}` } },
|
|
87
|
+
404
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
async function POST(request, context) {
|
|
91
|
+
const { slug } = await context.params;
|
|
92
|
+
const route = resolveRoute(request, slug);
|
|
93
|
+
const auth = extractAuth(request.headers.get("authorization"));
|
|
94
|
+
const ip = extractIp(
|
|
95
|
+
request.headers.get("x-forwarded-for"),
|
|
96
|
+
request.headers.get("x-real-ip")
|
|
97
|
+
);
|
|
98
|
+
if (route === "/surf/execute" || route === "/execute") {
|
|
99
|
+
let body;
|
|
100
|
+
try {
|
|
101
|
+
body = await request.json();
|
|
102
|
+
} catch {
|
|
103
|
+
return jsonResponse(
|
|
104
|
+
{ ok: false, error: { code: "INVALID_PARAMS", message: "Invalid JSON body" } },
|
|
105
|
+
400
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (!body?.command || typeof body.command !== "string") {
|
|
109
|
+
return jsonResponse(
|
|
110
|
+
{ ok: false, error: { code: "INVALID_PARAMS", message: "Missing command field" } },
|
|
111
|
+
400
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
let sessionState;
|
|
115
|
+
if (body.sessionId) {
|
|
116
|
+
const session = await sessions.get(body.sessionId);
|
|
117
|
+
if (session) sessionState = session.state;
|
|
118
|
+
}
|
|
119
|
+
const command = registry.get(body.command);
|
|
120
|
+
const wantsStream = body.stream === true && command?.stream === true;
|
|
121
|
+
if (wantsStream) {
|
|
122
|
+
const stream = new ReadableStream({
|
|
123
|
+
async start(controller) {
|
|
124
|
+
const encoder = new TextEncoder();
|
|
125
|
+
const write = (data) => {
|
|
126
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
|
|
127
|
+
|
|
128
|
+
`));
|
|
129
|
+
};
|
|
130
|
+
const sseContext = {
|
|
131
|
+
sessionId: body.sessionId,
|
|
132
|
+
auth,
|
|
133
|
+
ip,
|
|
134
|
+
state: sessionState,
|
|
135
|
+
requestId: body.requestId,
|
|
136
|
+
emit: (data) => {
|
|
137
|
+
write({ type: "chunk", data });
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
try {
|
|
141
|
+
const response2 = await registry.execute(body.command, body.params, sseContext);
|
|
142
|
+
if (response2.ok) {
|
|
143
|
+
write({ type: "done", result: response2.result });
|
|
144
|
+
} else {
|
|
145
|
+
write({ type: "error", error: { code: response2.error.code, message: response2.error.message } });
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
write({
|
|
149
|
+
type: "error",
|
|
150
|
+
error: {
|
|
151
|
+
code: "INTERNAL_ERROR",
|
|
152
|
+
message: e instanceof Error ? e.message : "Unknown error"
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
} finally {
|
|
156
|
+
controller.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return new Response(stream, {
|
|
161
|
+
status: 200,
|
|
162
|
+
headers: {
|
|
163
|
+
"Content-Type": "text/event-stream",
|
|
164
|
+
"Cache-Control": "no-cache",
|
|
165
|
+
"Connection": "keep-alive",
|
|
166
|
+
...CORS_HEADERS
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
const response = await registry.execute(body.command, body.params, {
|
|
171
|
+
sessionId: body.sessionId,
|
|
172
|
+
auth,
|
|
173
|
+
ip,
|
|
174
|
+
state: sessionState,
|
|
175
|
+
requestId: body.requestId
|
|
176
|
+
});
|
|
177
|
+
if (body.sessionId && response.ok && response.state) {
|
|
178
|
+
await sessions.update(body.sessionId, response.state);
|
|
179
|
+
}
|
|
180
|
+
const statusCode = response.ok ? 200 : getErrorStatus(response.error.code);
|
|
181
|
+
const headers = {};
|
|
182
|
+
if (!response.ok && response.error.code === "RATE_LIMITED") {
|
|
183
|
+
const retryMs = response.error.details?.["retryAfterMs"] ?? 0;
|
|
184
|
+
headers["Retry-After"] = String(Math.ceil(retryMs / 1e3));
|
|
185
|
+
}
|
|
186
|
+
return jsonResponse(response, statusCode, headers);
|
|
187
|
+
}
|
|
188
|
+
if (route === "/surf/pipeline" || route === "/pipeline") {
|
|
189
|
+
let body;
|
|
190
|
+
try {
|
|
191
|
+
body = await request.json();
|
|
192
|
+
} catch {
|
|
193
|
+
return jsonResponse(
|
|
194
|
+
{ ok: false, error: { code: "INVALID_PARAMS", message: "Invalid JSON body" } },
|
|
195
|
+
400
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
const result = await core.executePipeline(
|
|
200
|
+
body,
|
|
201
|
+
registry,
|
|
202
|
+
sessions,
|
|
203
|
+
auth
|
|
204
|
+
);
|
|
205
|
+
return jsonResponse(result, 200);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
return jsonResponse(
|
|
208
|
+
{
|
|
209
|
+
ok: false,
|
|
210
|
+
error: { code: "INTERNAL_ERROR", message: e instanceof Error ? e.message : "Unknown error" }
|
|
211
|
+
},
|
|
212
|
+
500
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (route === "/surf/session/start" || route === "/session/start") {
|
|
217
|
+
const session = await sessions.create();
|
|
218
|
+
return jsonResponse({ ok: true, sessionId: session.id }, 200);
|
|
219
|
+
}
|
|
220
|
+
if (route === "/surf/session/end" || route === "/session/end") {
|
|
221
|
+
try {
|
|
222
|
+
const body = await request.json();
|
|
223
|
+
if (body?.sessionId) {
|
|
224
|
+
await sessions.destroy(body.sessionId);
|
|
225
|
+
}
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
return jsonResponse({ ok: true }, 200);
|
|
229
|
+
}
|
|
230
|
+
return jsonResponse(
|
|
231
|
+
{ ok: false, error: { code: "NOT_FOUND", message: `Unknown route: POST ${route}` } },
|
|
232
|
+
404
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return { GET, POST };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
exports.createSurfRouteHandler = createSurfRouteHandler;
|
|
239
|
+
exports.extractAuth = extractAuth;
|
|
240
|
+
exports.extractIp = extractIp;
|
|
241
|
+
exports.getErrorStatus = getErrorStatus;
|
|
242
|
+
//# sourceMappingURL=index.cjs.map
|
|
243
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared.ts","../src/index.ts"],"names":["response","executePipeline"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { SurfInstance } from '@surfjs/core';\n\n/**\n * Surf error code to HTTP status code mapping.\n * Consistent with fastify and hono adapters.\n */\nexport function getErrorStatus(code: string): number {\n switch (code) {\n case 'UNKNOWN_COMMAND': return 404;\n case 'INVALID_PARAMS': return 400;\n case 'AUTH_REQUIRED': return 401;\n case 'AUTH_FAILED': return 403;\n case 'SESSION_EXPIRED': return 410;\n case 'RATE_LIMITED': return 429;\n case 'NOT_SUPPORTED': return 501;\n default: return 500;\n }\n}\n\n/**\n * Extract Bearer token from an Authorization header value.\n *\n * @param authHeader - Raw Authorization header value\n * @returns The token string, or `undefined` if not present\n */\nexport function extractAuth(authHeader: string | null | undefined): string | undefined {\n if (!authHeader) return undefined;\n return authHeader.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;\n}\n\n/**\n * Extract client IP from forwarding headers.\n *\n * @param forwardedFor - `x-forwarded-for` header value\n * @param realIp - `x-real-ip` header value\n * @returns The client IP string, or `undefined` if not determinable\n */\nexport function extractIp(\n forwardedFor: string | null | undefined,\n realIp: string | null | undefined,\n): string | undefined {\n if (forwardedFor) return forwardedFor.split(',')[0]?.trim();\n return realIp ?? undefined;\n}\n\n/**\n * CORS headers applied to all Surf responses.\n */\nexport const CORS_HEADERS: Record<string, string> = {\n 'Access-Control-Allow-Origin': '*',\n};\n\n/**\n * Common types re-exported for internal use.\n */\nexport type { SurfInstance };\n","import type { SurfInstance } from '@surfjs/core';\nimport type {\n ExecuteRequest,\n PipelineRequest,\n SurfResponse,\n} from '@surfjs/core';\nimport { executePipeline } from '@surfjs/core';\nimport {\n getErrorStatus,\n extractAuth,\n extractIp,\n CORS_HEADERS,\n} from './shared.js';\n\n/**\n * Next.js App Router request type (web-standard Request with nextUrl).\n * We use the global `Request` type for edge compatibility\n * and avoid importing Next.js types at runtime.\n */\ntype NextRequest = Request & { nextUrl?: URL };\n\n/**\n * Creates App Router route handlers for Surf.js.\n *\n * Returns `GET` and `POST` handlers for use in a Next.js catch-all route file:\n * `app/api/surf/[...slug]/route.ts`\n *\n * @example\n * ```ts\n * // app/api/surf/[...slug]/route.ts\n * import { createSurf } from '@surfjs/core';\n * import { createSurfRouteHandler } from '@surfjs/next';\n *\n * const surf = createSurf({ name: 'my-app', commands: { ... } });\n * export const { GET, POST } = createSurfRouteHandler(surf);\n * ```\n *\n * @example\n * ```ts\n * // With a custom base path\n * export const { GET, POST } = createSurfRouteHandler(surf, {\n * basePath: '/api/surf',\n * });\n * ```\n *\n * @param surf - A `SurfInstance` created via `createSurf()`\n * @param options - Optional configuration\n * @returns An object with `GET` and `POST` handlers\n */\nexport function createSurfRouteHandler(\n surf: SurfInstance,\n options: { basePath?: string } = {},\n): {\n GET: (request: Request, context: { params: Promise<{ slug?: string[] }> }) => Promise<Response>;\n POST: (request: Request, context: { params: Promise<{ slug?: string[] }> }) => Promise<Response>;\n} {\n const registry = surf.commands;\n const sessions = surf.sessions;\n const basePath = options.basePath ?? '/api/surf';\n\n function getPathname(request: Request): string {\n const req = request as NextRequest;\n if (req.nextUrl) return req.nextUrl.pathname;\n return new URL(request.url).pathname;\n }\n\n function resolveRoute(request: Request, slug: string[] | undefined): string {\n if (slug && slug.length > 0) {\n return '/' + slug.join('/');\n }\n // Fallback: extract from pathname\n const pathname = getPathname(request);\n const stripped = pathname.startsWith(basePath)\n ? pathname.slice(basePath.length)\n : pathname;\n return stripped || '/';\n }\n\n function jsonResponse(body: unknown, status: number, extraHeaders?: Record<string, string>): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: {\n 'Content-Type': 'application/json',\n ...CORS_HEADERS,\n ...extraHeaders,\n },\n });\n }\n\n // ─── GET handler ─────────────────────────────────────────────────────\n async function GET(\n request: Request,\n context: { params: Promise<{ slug?: string[] }> },\n ): Promise<Response> {\n const { slug } = await context.params;\n const route = resolveRoute(request, slug);\n\n // /.well-known/surf.json\n if (route === '/.well-known/surf.json' || route === '/') {\n const manifestData = surf.manifest();\n const etag = `\"${manifestData.checksum}\"`;\n\n if (request.headers.get('if-none-match') === etag) {\n return new Response(null, {\n status: 304,\n headers: { 'ETag': etag, 'Cache-Control': 'public, max-age=300', ...CORS_HEADERS },\n });\n }\n\n return jsonResponse(manifestData, 200, {\n 'ETag': etag,\n 'Cache-Control': 'public, max-age=300',\n });\n }\n\n return jsonResponse(\n { ok: false, error: { code: 'NOT_FOUND', message: `Unknown route: GET ${route}` } },\n 404,\n );\n }\n\n // ─── POST handler ────────────────────────────────────────────────────\n async function POST(\n request: Request,\n context: { params: Promise<{ slug?: string[] }> },\n ): Promise<Response> {\n const { slug } = await context.params;\n const route = resolveRoute(request, slug);\n const auth = extractAuth(request.headers.get('authorization'));\n const ip = extractIp(\n request.headers.get('x-forwarded-for'),\n request.headers.get('x-real-ip'),\n );\n\n // ─── POST /surf/execute ──────────────────────────────────────────\n if (route === '/surf/execute' || route === '/execute') {\n let body: ExecuteRequest;\n try {\n body = await request.json() as ExecuteRequest;\n } catch {\n return jsonResponse(\n { ok: false, error: { code: 'INVALID_PARAMS', message: 'Invalid JSON body' } },\n 400,\n );\n }\n\n if (!body?.command || typeof body.command !== 'string') {\n return jsonResponse(\n { ok: false, error: { code: 'INVALID_PARAMS', message: 'Missing command field' } },\n 400,\n );\n }\n\n let sessionState: Record<string, unknown> | undefined;\n if (body.sessionId) {\n const session = await sessions.get(body.sessionId);\n if (session) sessionState = session.state;\n }\n\n const command = registry.get(body.command);\n const wantsStream = body.stream === true && command?.stream === true;\n\n if (wantsStream) {\n // SSE streaming via ReadableStream (edge-compatible)\n const stream = new ReadableStream({\n async start(controller) {\n const encoder = new TextEncoder();\n const write = (data: unknown) => {\n controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\\n\\n`));\n };\n\n const sseContext = {\n sessionId: body.sessionId,\n auth,\n ip,\n state: sessionState,\n requestId: body.requestId,\n emit: (data: unknown) => {\n write({ type: 'chunk', data });\n },\n };\n\n try {\n const response: SurfResponse = await registry.execute(body.command, body.params, sseContext);\n if (response.ok) {\n write({ type: 'done', result: response.result });\n } else {\n write({ type: 'error', error: { code: response.error.code, message: response.error.message } });\n }\n } catch (e) {\n write({\n type: 'error',\n error: {\n code: 'INTERNAL_ERROR',\n message: e instanceof Error ? e.message : 'Unknown error',\n },\n });\n } finally {\n controller.close();\n }\n },\n });\n\n return new Response(stream, {\n status: 200,\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n ...CORS_HEADERS,\n },\n });\n }\n\n // Standard JSON response\n const response: SurfResponse = await registry.execute(body.command, body.params, {\n sessionId: body.sessionId,\n auth,\n ip,\n state: sessionState,\n requestId: body.requestId,\n });\n\n if (body.sessionId && response.ok && response.state) {\n await sessions.update(body.sessionId, response.state);\n }\n\n const statusCode = response.ok ? 200 : getErrorStatus(response.error.code);\n const headers: Record<string, string> = {};\n\n if (!response.ok && response.error.code === 'RATE_LIMITED') {\n const retryMs = (response.error.details?.['retryAfterMs'] as number | undefined) ?? 0;\n headers['Retry-After'] = String(Math.ceil(retryMs / 1000));\n }\n\n return jsonResponse(response, statusCode, headers);\n }\n\n // ─── POST /surf/pipeline ─────────────────────────────────────────\n if (route === '/surf/pipeline' || route === '/pipeline') {\n let body: PipelineRequest;\n try {\n body = await request.json() as PipelineRequest;\n } catch {\n return jsonResponse(\n { ok: false, error: { code: 'INVALID_PARAMS', message: 'Invalid JSON body' } },\n 400,\n );\n }\n\n try {\n const result = await executePipeline(\n body,\n registry as Parameters<typeof executePipeline>[1],\n sessions as Parameters<typeof executePipeline>[2],\n auth,\n );\n return jsonResponse(result, 200);\n } catch (e) {\n return jsonResponse(\n {\n ok: false,\n error: { code: 'INTERNAL_ERROR', message: e instanceof Error ? e.message : 'Unknown error' },\n },\n 500,\n );\n }\n }\n\n // ─── POST /surf/session/start ────────────────────────────────────\n if (route === '/surf/session/start' || route === '/session/start') {\n const session = await sessions.create();\n return jsonResponse({ ok: true, sessionId: session.id }, 200);\n }\n\n // ─── POST /surf/session/end ──────────────────────────────────────\n if (route === '/surf/session/end' || route === '/session/end') {\n try {\n const body = await request.json() as { sessionId?: string };\n if (body?.sessionId) {\n await sessions.destroy(body.sessionId);\n }\n } catch {\n // Ignore parse errors for session end\n }\n return jsonResponse({ ok: true }, 200);\n }\n\n return jsonResponse(\n { ok: false, error: { code: 'NOT_FOUND', message: `Unknown route: POST ${route}` } },\n 404,\n );\n }\n\n return { GET, POST };\n}\n\nexport { getErrorStatus, extractAuth, extractIp } from './shared.js';\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SurfInstance } from '@surfjs/core';
|
|
2
|
+
export { e as extractAuth, a as extractIp, g as getErrorStatus } from './shared-CckJeURD.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates App Router route handlers for Surf.js.
|
|
6
|
+
*
|
|
7
|
+
* Returns `GET` and `POST` handlers for use in a Next.js catch-all route file:
|
|
8
|
+
* `app/api/surf/[...slug]/route.ts`
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // app/api/surf/[...slug]/route.ts
|
|
13
|
+
* import { createSurf } from '@surfjs/core';
|
|
14
|
+
* import { createSurfRouteHandler } from '@surfjs/next';
|
|
15
|
+
*
|
|
16
|
+
* const surf = createSurf({ name: 'my-app', commands: { ... } });
|
|
17
|
+
* export const { GET, POST } = createSurfRouteHandler(surf);
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* // With a custom base path
|
|
23
|
+
* export const { GET, POST } = createSurfRouteHandler(surf, {
|
|
24
|
+
* basePath: '/api/surf',
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @param surf - A `SurfInstance` created via `createSurf()`
|
|
29
|
+
* @param options - Optional configuration
|
|
30
|
+
* @returns An object with `GET` and `POST` handlers
|
|
31
|
+
*/
|
|
32
|
+
declare function createSurfRouteHandler(surf: SurfInstance, options?: {
|
|
33
|
+
basePath?: string;
|
|
34
|
+
}): {
|
|
35
|
+
GET: (request: Request, context: {
|
|
36
|
+
params: Promise<{
|
|
37
|
+
slug?: string[];
|
|
38
|
+
}>;
|
|
39
|
+
}) => Promise<Response>;
|
|
40
|
+
POST: (request: Request, context: {
|
|
41
|
+
params: Promise<{
|
|
42
|
+
slug?: string[];
|
|
43
|
+
}>;
|
|
44
|
+
}) => Promise<Response>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export { createSurfRouteHandler };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SurfInstance } from '@surfjs/core';
|
|
2
|
+
export { e as extractAuth, a as extractIp, g as getErrorStatus } from './shared-CckJeURD.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates App Router route handlers for Surf.js.
|
|
6
|
+
*
|
|
7
|
+
* Returns `GET` and `POST` handlers for use in a Next.js catch-all route file:
|
|
8
|
+
* `app/api/surf/[...slug]/route.ts`
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* // app/api/surf/[...slug]/route.ts
|
|
13
|
+
* import { createSurf } from '@surfjs/core';
|
|
14
|
+
* import { createSurfRouteHandler } from '@surfjs/next';
|
|
15
|
+
*
|
|
16
|
+
* const surf = createSurf({ name: 'my-app', commands: { ... } });
|
|
17
|
+
* export const { GET, POST } = createSurfRouteHandler(surf);
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* // With a custom base path
|
|
23
|
+
* export const { GET, POST } = createSurfRouteHandler(surf, {
|
|
24
|
+
* basePath: '/api/surf',
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @param surf - A `SurfInstance` created via `createSurf()`
|
|
29
|
+
* @param options - Optional configuration
|
|
30
|
+
* @returns An object with `GET` and `POST` handlers
|
|
31
|
+
*/
|
|
32
|
+
declare function createSurfRouteHandler(surf: SurfInstance, options?: {
|
|
33
|
+
basePath?: string;
|
|
34
|
+
}): {
|
|
35
|
+
GET: (request: Request, context: {
|
|
36
|
+
params: Promise<{
|
|
37
|
+
slug?: string[];
|
|
38
|
+
}>;
|
|
39
|
+
}) => Promise<Response>;
|
|
40
|
+
POST: (request: Request, context: {
|
|
41
|
+
params: Promise<{
|
|
42
|
+
slug?: string[];
|
|
43
|
+
}>;
|
|
44
|
+
}) => Promise<Response>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export { createSurfRouteHandler };
|