@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/dist/index.js ADDED
@@ -0,0 +1,238 @@
1
+ import { executePipeline } from '@surfjs/core';
2
+
3
+ // src/index.ts
4
+
5
+ // src/shared.ts
6
+ function getErrorStatus(code) {
7
+ switch (code) {
8
+ case "UNKNOWN_COMMAND":
9
+ return 404;
10
+ case "INVALID_PARAMS":
11
+ return 400;
12
+ case "AUTH_REQUIRED":
13
+ return 401;
14
+ case "AUTH_FAILED":
15
+ return 403;
16
+ case "SESSION_EXPIRED":
17
+ return 410;
18
+ case "RATE_LIMITED":
19
+ return 429;
20
+ case "NOT_SUPPORTED":
21
+ return 501;
22
+ default:
23
+ return 500;
24
+ }
25
+ }
26
+ function extractAuth(authHeader) {
27
+ if (!authHeader) return void 0;
28
+ return authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
29
+ }
30
+ function extractIp(forwardedFor, realIp) {
31
+ if (forwardedFor) return forwardedFor.split(",")[0]?.trim();
32
+ return realIp ?? void 0;
33
+ }
34
+ var CORS_HEADERS = {
35
+ "Access-Control-Allow-Origin": "*"
36
+ };
37
+
38
+ // src/index.ts
39
+ function createSurfRouteHandler(surf, options = {}) {
40
+ const registry = surf.commands;
41
+ const sessions = surf.sessions;
42
+ const basePath = options.basePath ?? "/api/surf";
43
+ function getPathname(request) {
44
+ const req = request;
45
+ if (req.nextUrl) return req.nextUrl.pathname;
46
+ return new URL(request.url).pathname;
47
+ }
48
+ function resolveRoute(request, slug) {
49
+ if (slug && slug.length > 0) {
50
+ return "/" + slug.join("/");
51
+ }
52
+ const pathname = getPathname(request);
53
+ const stripped = pathname.startsWith(basePath) ? pathname.slice(basePath.length) : pathname;
54
+ return stripped || "/";
55
+ }
56
+ function jsonResponse(body, status, extraHeaders) {
57
+ return new Response(JSON.stringify(body), {
58
+ status,
59
+ headers: {
60
+ "Content-Type": "application/json",
61
+ ...CORS_HEADERS,
62
+ ...extraHeaders
63
+ }
64
+ });
65
+ }
66
+ async function GET(request, context) {
67
+ const { slug } = await context.params;
68
+ const route = resolveRoute(request, slug);
69
+ if (route === "/.well-known/surf.json" || route === "/") {
70
+ const manifestData = surf.manifest();
71
+ const etag = `"${manifestData.checksum}"`;
72
+ if (request.headers.get("if-none-match") === etag) {
73
+ return new Response(null, {
74
+ status: 304,
75
+ headers: { "ETag": etag, "Cache-Control": "public, max-age=300", ...CORS_HEADERS }
76
+ });
77
+ }
78
+ return jsonResponse(manifestData, 200, {
79
+ "ETag": etag,
80
+ "Cache-Control": "public, max-age=300"
81
+ });
82
+ }
83
+ return jsonResponse(
84
+ { ok: false, error: { code: "NOT_FOUND", message: `Unknown route: GET ${route}` } },
85
+ 404
86
+ );
87
+ }
88
+ async function POST(request, context) {
89
+ const { slug } = await context.params;
90
+ const route = resolveRoute(request, slug);
91
+ const auth = extractAuth(request.headers.get("authorization"));
92
+ const ip = extractIp(
93
+ request.headers.get("x-forwarded-for"),
94
+ request.headers.get("x-real-ip")
95
+ );
96
+ if (route === "/surf/execute" || route === "/execute") {
97
+ let body;
98
+ try {
99
+ body = await request.json();
100
+ } catch {
101
+ return jsonResponse(
102
+ { ok: false, error: { code: "INVALID_PARAMS", message: "Invalid JSON body" } },
103
+ 400
104
+ );
105
+ }
106
+ if (!body?.command || typeof body.command !== "string") {
107
+ return jsonResponse(
108
+ { ok: false, error: { code: "INVALID_PARAMS", message: "Missing command field" } },
109
+ 400
110
+ );
111
+ }
112
+ let sessionState;
113
+ if (body.sessionId) {
114
+ const session = await sessions.get(body.sessionId);
115
+ if (session) sessionState = session.state;
116
+ }
117
+ const command = registry.get(body.command);
118
+ const wantsStream = body.stream === true && command?.stream === true;
119
+ if (wantsStream) {
120
+ const stream = new ReadableStream({
121
+ async start(controller) {
122
+ const encoder = new TextEncoder();
123
+ const write = (data) => {
124
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
125
+
126
+ `));
127
+ };
128
+ const sseContext = {
129
+ sessionId: body.sessionId,
130
+ auth,
131
+ ip,
132
+ state: sessionState,
133
+ requestId: body.requestId,
134
+ emit: (data) => {
135
+ write({ type: "chunk", data });
136
+ }
137
+ };
138
+ try {
139
+ const response2 = await registry.execute(body.command, body.params, sseContext);
140
+ if (response2.ok) {
141
+ write({ type: "done", result: response2.result });
142
+ } else {
143
+ write({ type: "error", error: { code: response2.error.code, message: response2.error.message } });
144
+ }
145
+ } catch (e) {
146
+ write({
147
+ type: "error",
148
+ error: {
149
+ code: "INTERNAL_ERROR",
150
+ message: e instanceof Error ? e.message : "Unknown error"
151
+ }
152
+ });
153
+ } finally {
154
+ controller.close();
155
+ }
156
+ }
157
+ });
158
+ return new Response(stream, {
159
+ status: 200,
160
+ headers: {
161
+ "Content-Type": "text/event-stream",
162
+ "Cache-Control": "no-cache",
163
+ "Connection": "keep-alive",
164
+ ...CORS_HEADERS
165
+ }
166
+ });
167
+ }
168
+ const response = await registry.execute(body.command, body.params, {
169
+ sessionId: body.sessionId,
170
+ auth,
171
+ ip,
172
+ state: sessionState,
173
+ requestId: body.requestId
174
+ });
175
+ if (body.sessionId && response.ok && response.state) {
176
+ await sessions.update(body.sessionId, response.state);
177
+ }
178
+ const statusCode = response.ok ? 200 : getErrorStatus(response.error.code);
179
+ const headers = {};
180
+ if (!response.ok && response.error.code === "RATE_LIMITED") {
181
+ const retryMs = response.error.details?.["retryAfterMs"] ?? 0;
182
+ headers["Retry-After"] = String(Math.ceil(retryMs / 1e3));
183
+ }
184
+ return jsonResponse(response, statusCode, headers);
185
+ }
186
+ if (route === "/surf/pipeline" || route === "/pipeline") {
187
+ let body;
188
+ try {
189
+ body = await request.json();
190
+ } catch {
191
+ return jsonResponse(
192
+ { ok: false, error: { code: "INVALID_PARAMS", message: "Invalid JSON body" } },
193
+ 400
194
+ );
195
+ }
196
+ try {
197
+ const result = await executePipeline(
198
+ body,
199
+ registry,
200
+ sessions,
201
+ auth
202
+ );
203
+ return jsonResponse(result, 200);
204
+ } catch (e) {
205
+ return jsonResponse(
206
+ {
207
+ ok: false,
208
+ error: { code: "INTERNAL_ERROR", message: e instanceof Error ? e.message : "Unknown error" }
209
+ },
210
+ 500
211
+ );
212
+ }
213
+ }
214
+ if (route === "/surf/session/start" || route === "/session/start") {
215
+ const session = await sessions.create();
216
+ return jsonResponse({ ok: true, sessionId: session.id }, 200);
217
+ }
218
+ if (route === "/surf/session/end" || route === "/session/end") {
219
+ try {
220
+ const body = await request.json();
221
+ if (body?.sessionId) {
222
+ await sessions.destroy(body.sessionId);
223
+ }
224
+ } catch {
225
+ }
226
+ return jsonResponse({ ok: true }, 200);
227
+ }
228
+ return jsonResponse(
229
+ { ok: false, error: { code: "NOT_FOUND", message: `Unknown route: POST ${route}` } },
230
+ 404
231
+ );
232
+ }
233
+ return { GET, POST };
234
+ }
235
+
236
+ export { createSurfRouteHandler, extractAuth, extractIp, getErrorStatus };
237
+ //# sourceMappingURL=index.js.map
238
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared.ts","../src/index.ts"],"names":["response"],"mappings":"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","file":"index.js","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/pages.cjs ADDED
@@ -0,0 +1,211 @@
1
+ 'use strict';
2
+
3
+ var core = require('@surfjs/core');
4
+
5
+ // src/pages.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/pages.ts
41
+ function headerValue(val) {
42
+ if (Array.isArray(val)) return val[0];
43
+ return val;
44
+ }
45
+ function createSurfApiHandler(surf) {
46
+ const registry = surf.commands;
47
+ const sessions = surf.sessions;
48
+ function resolveRoute(query) {
49
+ const slugParam = query["slug"];
50
+ if (!slugParam) return "/";
51
+ const parts = Array.isArray(slugParam) ? slugParam : [slugParam];
52
+ return "/" + parts.join("/");
53
+ }
54
+ function sendJson(res, status, body, extraHeaders) {
55
+ for (const [k, v] of Object.entries(CORS_HEADERS)) {
56
+ res.setHeader(k, v);
57
+ }
58
+ if (extraHeaders) {
59
+ for (const [k, v] of Object.entries(extraHeaders)) {
60
+ res.setHeader(k, v);
61
+ }
62
+ }
63
+ res.status(status).json(body);
64
+ }
65
+ return async function surfApiHandler(req, res) {
66
+ const route = resolveRoute(req.query);
67
+ const method = (req.method ?? "GET").toUpperCase();
68
+ if (method === "GET" && (route === "/.well-known/surf.json" || route === "/")) {
69
+ const manifestData = surf.manifest();
70
+ const etag = `"${manifestData.checksum}"`;
71
+ if (headerValue(req.headers["if-none-match"]) === etag) {
72
+ res.setHeader("ETag", etag);
73
+ res.setHeader("Cache-Control", "public, max-age=300");
74
+ for (const [k, v] of Object.entries(CORS_HEADERS)) {
75
+ res.setHeader(k, v);
76
+ }
77
+ res.status(304).end();
78
+ return;
79
+ }
80
+ sendJson(res, 200, manifestData, {
81
+ "ETag": etag,
82
+ "Cache-Control": "public, max-age=300"
83
+ });
84
+ return;
85
+ }
86
+ if (method !== "POST") {
87
+ sendJson(res, 405, { ok: false, error: { code: "NOT_SUPPORTED", message: `Method ${method} not allowed` } });
88
+ return;
89
+ }
90
+ const auth = extractAuth(headerValue(req.headers["authorization"]));
91
+ const ip = extractIp(
92
+ headerValue(req.headers["x-forwarded-for"]),
93
+ headerValue(req.headers["x-real-ip"])
94
+ );
95
+ if (route === "/surf/execute" || route === "/execute") {
96
+ const body = req.body;
97
+ if (!body?.command || typeof body.command !== "string") {
98
+ sendJson(res, 400, { ok: false, error: { code: "INVALID_PARAMS", message: "Missing command field" } });
99
+ return;
100
+ }
101
+ let sessionState;
102
+ if (body.sessionId) {
103
+ const session = await sessions.get(body.sessionId);
104
+ if (session) sessionState = session.state;
105
+ }
106
+ const command = registry.get(body.command);
107
+ const wantsStream = body.stream === true && command?.stream === true;
108
+ if (wantsStream) {
109
+ res.writeHead(200, {
110
+ "Content-Type": "text/event-stream",
111
+ "Cache-Control": "no-cache",
112
+ "Connection": "keep-alive",
113
+ "Access-Control-Allow-Origin": "*"
114
+ });
115
+ if (res.flushHeaders) res.flushHeaders();
116
+ const write = (data) => {
117
+ res.write(`data: ${JSON.stringify(data)}
118
+
119
+ `);
120
+ };
121
+ const sseContext = {
122
+ sessionId: body.sessionId,
123
+ auth,
124
+ ip,
125
+ state: sessionState,
126
+ requestId: body.requestId,
127
+ emit: (data) => {
128
+ write({ type: "chunk", data });
129
+ }
130
+ };
131
+ try {
132
+ const response2 = await registry.execute(body.command, body.params, sseContext);
133
+ if (response2.ok) {
134
+ write({ type: "done", result: response2.result });
135
+ } else {
136
+ write({ type: "error", error: { code: response2.error.code, message: response2.error.message } });
137
+ }
138
+ } catch (e) {
139
+ write({
140
+ type: "error",
141
+ error: { code: "INTERNAL_ERROR", message: e instanceof Error ? e.message : "Unknown error" }
142
+ });
143
+ } finally {
144
+ res.end();
145
+ }
146
+ return;
147
+ }
148
+ const response = await registry.execute(body.command, body.params, {
149
+ sessionId: body.sessionId,
150
+ auth,
151
+ ip,
152
+ state: sessionState,
153
+ requestId: body.requestId
154
+ });
155
+ if (body.sessionId && response.ok && response.state) {
156
+ await sessions.update(body.sessionId, response.state);
157
+ }
158
+ const statusCode = response.ok ? 200 : getErrorStatus(response.error.code);
159
+ const extraHeaders = {};
160
+ if (!response.ok && response.error.code === "RATE_LIMITED") {
161
+ const retryMs = response.error.details?.["retryAfterMs"] ?? 0;
162
+ extraHeaders["Retry-After"] = String(Math.ceil(retryMs / 1e3));
163
+ }
164
+ sendJson(res, statusCode, response, extraHeaders);
165
+ return;
166
+ }
167
+ if (route === "/surf/pipeline" || route === "/pipeline") {
168
+ const body = req.body;
169
+ if (!body) {
170
+ sendJson(res, 400, { ok: false, error: { code: "INVALID_PARAMS", message: "Missing request body" } });
171
+ return;
172
+ }
173
+ try {
174
+ const result = await core.executePipeline(
175
+ body,
176
+ registry,
177
+ sessions,
178
+ auth
179
+ );
180
+ sendJson(res, 200, result);
181
+ } catch (e) {
182
+ sendJson(res, 500, {
183
+ ok: false,
184
+ error: { code: "INTERNAL_ERROR", message: e instanceof Error ? e.message : "Unknown error" }
185
+ });
186
+ }
187
+ return;
188
+ }
189
+ if (route === "/surf/session/start" || route === "/session/start") {
190
+ const session = await sessions.create();
191
+ sendJson(res, 200, { ok: true, sessionId: session.id });
192
+ return;
193
+ }
194
+ if (route === "/surf/session/end" || route === "/session/end") {
195
+ const body = req.body;
196
+ if (body?.sessionId) {
197
+ await sessions.destroy(body.sessionId);
198
+ }
199
+ sendJson(res, 200, { ok: true });
200
+ return;
201
+ }
202
+ sendJson(res, 404, { ok: false, error: { code: "NOT_FOUND", message: `Unknown route: POST ${route}` } });
203
+ };
204
+ }
205
+
206
+ exports.createSurfApiHandler = createSurfApiHandler;
207
+ exports.extractAuth = extractAuth;
208
+ exports.extractIp = extractIp;
209
+ exports.getErrorStatus = getErrorStatus;
210
+ //# sourceMappingURL=pages.cjs.map
211
+ //# sourceMappingURL=pages.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared.ts","../src/pages.ts"],"names":["response","executePipeline"],"mappings":"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","file":"pages.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 * Minimal Next.js Pages API request type.\n * Avoids importing `next` at runtime for portability.\n */\ninterface PagesApiRequest {\n method?: string;\n url?: string;\n headers: Record<string, string | string[] | undefined>;\n query: Record<string, string | string[] | undefined>;\n body?: unknown;\n}\n\n/**\n * Minimal Next.js Pages API response type.\n * Avoids importing `next` at runtime for portability.\n */\ninterface PagesApiResponse {\n setHeader(name: string, value: string | number): PagesApiResponse;\n status(code: number): PagesApiResponse;\n json(body: unknown): void;\n end(chunk?: string): void;\n write(chunk: string): boolean;\n writeHead(statusCode: number, headers?: Record<string, string>): void;\n flushHeaders?(): void;\n}\n\nfunction headerValue(val: string | string[] | undefined): string | undefined {\n if (Array.isArray(val)) return val[0];\n return val;\n}\n\n/**\n * Creates a Pages Router API handler for Surf.js.\n *\n * Returns a single handler for use in a Next.js catch-all API route:\n * `pages/api/surf/[...slug].ts`\n *\n * @example\n * ```ts\n * // pages/api/surf/[...slug].ts\n * import { createSurf } from '@surfjs/core';\n * import { createSurfApiHandler } from '@surfjs/next/pages';\n *\n * const surf = createSurf({ name: 'my-app', commands: { ... } });\n * export default createSurfApiHandler(surf);\n * ```\n *\n * @example\n * ```ts\n * // Disable body parsing for streaming support\n * export const config = { api: { bodyParser: true } };\n * export default createSurfApiHandler(surf);\n * ```\n *\n * @param surf - A `SurfInstance` created via `createSurf()`\n * @returns A Next.js Pages API handler function\n */\nexport function createSurfApiHandler(\n surf: SurfInstance,\n): (req: PagesApiRequest, res: PagesApiResponse) => Promise<void> {\n const registry = surf.commands;\n const sessions = surf.sessions;\n\n function resolveRoute(query: Record<string, string | string[] | undefined>): string {\n const slugParam = query['slug'];\n if (!slugParam) return '/';\n const parts = Array.isArray(slugParam) ? slugParam : [slugParam];\n return '/' + parts.join('/');\n }\n\n function sendJson(res: PagesApiResponse, status: number, body: unknown, extraHeaders?: Record<string, string>): void {\n for (const [k, v] of Object.entries(CORS_HEADERS)) {\n res.setHeader(k, v);\n }\n if (extraHeaders) {\n for (const [k, v] of Object.entries(extraHeaders)) {\n res.setHeader(k, v);\n }\n }\n res.status(status).json(body);\n }\n\n return async function surfApiHandler(req: PagesApiRequest, res: PagesApiResponse): Promise<void> {\n const route = resolveRoute(req.query);\n const method = (req.method ?? 'GET').toUpperCase();\n\n // ─── GET /.well-known/surf.json ──────────────────────────────────\n if (method === 'GET' && (route === '/.well-known/surf.json' || route === '/')) {\n const manifestData = surf.manifest();\n const etag = `\"${manifestData.checksum}\"`;\n\n if (headerValue(req.headers['if-none-match']) === etag) {\n res.setHeader('ETag', etag);\n res.setHeader('Cache-Control', 'public, max-age=300');\n for (const [k, v] of Object.entries(CORS_HEADERS)) {\n res.setHeader(k, v);\n }\n res.status(304).end();\n return;\n }\n\n sendJson(res, 200, manifestData, {\n 'ETag': etag,\n 'Cache-Control': 'public, max-age=300',\n });\n return;\n }\n\n // Only POST beyond this point\n if (method !== 'POST') {\n sendJson(res, 405, { ok: false, error: { code: 'NOT_SUPPORTED', message: `Method ${method} not allowed` } });\n return;\n }\n\n const auth = extractAuth(headerValue(req.headers['authorization']));\n const ip = extractIp(\n headerValue(req.headers['x-forwarded-for']),\n headerValue(req.headers['x-real-ip']),\n );\n\n // ─── POST /surf/execute ──────────────────────────────────────────\n if (route === '/surf/execute' || route === '/execute') {\n const body = req.body as ExecuteRequest | undefined;\n\n if (!body?.command || typeof body.command !== 'string') {\n sendJson(res, 400, { ok: false, error: { code: 'INVALID_PARAMS', message: 'Missing command field' } });\n return;\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 Node.js response\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'Access-Control-Allow-Origin': '*',\n });\n if (res.flushHeaders) res.flushHeaders();\n\n const write = (data: unknown) => {\n res.write(`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: { code: 'INTERNAL_ERROR', message: e instanceof Error ? e.message : 'Unknown error' },\n });\n } finally {\n res.end();\n }\n return;\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 extraHeaders: 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 extraHeaders['Retry-After'] = String(Math.ceil(retryMs / 1000));\n }\n\n sendJson(res, statusCode, response, extraHeaders);\n return;\n }\n\n // ─── POST /surf/pipeline ─────────────────────────────────────────\n if (route === '/surf/pipeline' || route === '/pipeline') {\n const body = req.body as PipelineRequest | undefined;\n\n if (!body) {\n sendJson(res, 400, { ok: false, error: { code: 'INVALID_PARAMS', message: 'Missing request body' } });\n return;\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 sendJson(res, 200, result);\n } catch (e) {\n sendJson(res, 500, {\n ok: false,\n error: { code: 'INTERNAL_ERROR', message: e instanceof Error ? e.message : 'Unknown error' },\n });\n }\n return;\n }\n\n // ─── POST /surf/session/start ────────────────────────────────────\n if (route === '/surf/session/start' || route === '/session/start') {\n const session = await sessions.create();\n sendJson(res, 200, { ok: true, sessionId: session.id });\n return;\n }\n\n // ─── POST /surf/session/end ──────────────────────────────────────\n if (route === '/surf/session/end' || route === '/session/end') {\n const body = req.body as { sessionId?: string } | undefined;\n if (body?.sessionId) {\n await sessions.destroy(body.sessionId);\n }\n sendJson(res, 200, { ok: true });\n return;\n }\n\n sendJson(res, 404, { ok: false, error: { code: 'NOT_FOUND', message: `Unknown route: POST ${route}` } });\n };\n}\n\nexport { getErrorStatus, extractAuth, extractIp } from './shared.js';\n"]}
@@ -0,0 +1,56 @@
1
+ import { SurfInstance } from '@surfjs/core';
2
+ export { e as extractAuth, a as extractIp, g as getErrorStatus } from './shared-CckJeURD.cjs';
3
+
4
+ /**
5
+ * Minimal Next.js Pages API request type.
6
+ * Avoids importing `next` at runtime for portability.
7
+ */
8
+ interface PagesApiRequest {
9
+ method?: string;
10
+ url?: string;
11
+ headers: Record<string, string | string[] | undefined>;
12
+ query: Record<string, string | string[] | undefined>;
13
+ body?: unknown;
14
+ }
15
+ /**
16
+ * Minimal Next.js Pages API response type.
17
+ * Avoids importing `next` at runtime for portability.
18
+ */
19
+ interface PagesApiResponse {
20
+ setHeader(name: string, value: string | number): PagesApiResponse;
21
+ status(code: number): PagesApiResponse;
22
+ json(body: unknown): void;
23
+ end(chunk?: string): void;
24
+ write(chunk: string): boolean;
25
+ writeHead(statusCode: number, headers?: Record<string, string>): void;
26
+ flushHeaders?(): void;
27
+ }
28
+ /**
29
+ * Creates a Pages Router API handler for Surf.js.
30
+ *
31
+ * Returns a single handler for use in a Next.js catch-all API route:
32
+ * `pages/api/surf/[...slug].ts`
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * // pages/api/surf/[...slug].ts
37
+ * import { createSurf } from '@surfjs/core';
38
+ * import { createSurfApiHandler } from '@surfjs/next/pages';
39
+ *
40
+ * const surf = createSurf({ name: 'my-app', commands: { ... } });
41
+ * export default createSurfApiHandler(surf);
42
+ * ```
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // Disable body parsing for streaming support
47
+ * export const config = { api: { bodyParser: true } };
48
+ * export default createSurfApiHandler(surf);
49
+ * ```
50
+ *
51
+ * @param surf - A `SurfInstance` created via `createSurf()`
52
+ * @returns A Next.js Pages API handler function
53
+ */
54
+ declare function createSurfApiHandler(surf: SurfInstance): (req: PagesApiRequest, res: PagesApiResponse) => Promise<void>;
55
+
56
+ export { createSurfApiHandler };
@@ -0,0 +1,56 @@
1
+ import { SurfInstance } from '@surfjs/core';
2
+ export { e as extractAuth, a as extractIp, g as getErrorStatus } from './shared-CckJeURD.js';
3
+
4
+ /**
5
+ * Minimal Next.js Pages API request type.
6
+ * Avoids importing `next` at runtime for portability.
7
+ */
8
+ interface PagesApiRequest {
9
+ method?: string;
10
+ url?: string;
11
+ headers: Record<string, string | string[] | undefined>;
12
+ query: Record<string, string | string[] | undefined>;
13
+ body?: unknown;
14
+ }
15
+ /**
16
+ * Minimal Next.js Pages API response type.
17
+ * Avoids importing `next` at runtime for portability.
18
+ */
19
+ interface PagesApiResponse {
20
+ setHeader(name: string, value: string | number): PagesApiResponse;
21
+ status(code: number): PagesApiResponse;
22
+ json(body: unknown): void;
23
+ end(chunk?: string): void;
24
+ write(chunk: string): boolean;
25
+ writeHead(statusCode: number, headers?: Record<string, string>): void;
26
+ flushHeaders?(): void;
27
+ }
28
+ /**
29
+ * Creates a Pages Router API handler for Surf.js.
30
+ *
31
+ * Returns a single handler for use in a Next.js catch-all API route:
32
+ * `pages/api/surf/[...slug].ts`
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * // pages/api/surf/[...slug].ts
37
+ * import { createSurf } from '@surfjs/core';
38
+ * import { createSurfApiHandler } from '@surfjs/next/pages';
39
+ *
40
+ * const surf = createSurf({ name: 'my-app', commands: { ... } });
41
+ * export default createSurfApiHandler(surf);
42
+ * ```
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * // Disable body parsing for streaming support
47
+ * export const config = { api: { bodyParser: true } };
48
+ * export default createSurfApiHandler(surf);
49
+ * ```
50
+ *
51
+ * @param surf - A `SurfInstance` created via `createSurf()`
52
+ * @returns A Next.js Pages API handler function
53
+ */
54
+ declare function createSurfApiHandler(surf: SurfInstance): (req: PagesApiRequest, res: PagesApiResponse) => Promise<void>;
55
+
56
+ export { createSurfApiHandler };