@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/pages.js ADDED
@@ -0,0 +1,206 @@
1
+ import { executePipeline } from '@surfjs/core';
2
+
3
+ // src/pages.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/pages.ts
39
+ function headerValue(val) {
40
+ if (Array.isArray(val)) return val[0];
41
+ return val;
42
+ }
43
+ function createSurfApiHandler(surf) {
44
+ const registry = surf.commands;
45
+ const sessions = surf.sessions;
46
+ function resolveRoute(query) {
47
+ const slugParam = query["slug"];
48
+ if (!slugParam) return "/";
49
+ const parts = Array.isArray(slugParam) ? slugParam : [slugParam];
50
+ return "/" + parts.join("/");
51
+ }
52
+ function sendJson(res, status, body, extraHeaders) {
53
+ for (const [k, v] of Object.entries(CORS_HEADERS)) {
54
+ res.setHeader(k, v);
55
+ }
56
+ if (extraHeaders) {
57
+ for (const [k, v] of Object.entries(extraHeaders)) {
58
+ res.setHeader(k, v);
59
+ }
60
+ }
61
+ res.status(status).json(body);
62
+ }
63
+ return async function surfApiHandler(req, res) {
64
+ const route = resolveRoute(req.query);
65
+ const method = (req.method ?? "GET").toUpperCase();
66
+ if (method === "GET" && (route === "/.well-known/surf.json" || route === "/")) {
67
+ const manifestData = surf.manifest();
68
+ const etag = `"${manifestData.checksum}"`;
69
+ if (headerValue(req.headers["if-none-match"]) === etag) {
70
+ res.setHeader("ETag", etag);
71
+ res.setHeader("Cache-Control", "public, max-age=300");
72
+ for (const [k, v] of Object.entries(CORS_HEADERS)) {
73
+ res.setHeader(k, v);
74
+ }
75
+ res.status(304).end();
76
+ return;
77
+ }
78
+ sendJson(res, 200, manifestData, {
79
+ "ETag": etag,
80
+ "Cache-Control": "public, max-age=300"
81
+ });
82
+ return;
83
+ }
84
+ if (method !== "POST") {
85
+ sendJson(res, 405, { ok: false, error: { code: "NOT_SUPPORTED", message: `Method ${method} not allowed` } });
86
+ return;
87
+ }
88
+ const auth = extractAuth(headerValue(req.headers["authorization"]));
89
+ const ip = extractIp(
90
+ headerValue(req.headers["x-forwarded-for"]),
91
+ headerValue(req.headers["x-real-ip"])
92
+ );
93
+ if (route === "/surf/execute" || route === "/execute") {
94
+ const body = req.body;
95
+ if (!body?.command || typeof body.command !== "string") {
96
+ sendJson(res, 400, { ok: false, error: { code: "INVALID_PARAMS", message: "Missing command field" } });
97
+ return;
98
+ }
99
+ let sessionState;
100
+ if (body.sessionId) {
101
+ const session = await sessions.get(body.sessionId);
102
+ if (session) sessionState = session.state;
103
+ }
104
+ const command = registry.get(body.command);
105
+ const wantsStream = body.stream === true && command?.stream === true;
106
+ if (wantsStream) {
107
+ res.writeHead(200, {
108
+ "Content-Type": "text/event-stream",
109
+ "Cache-Control": "no-cache",
110
+ "Connection": "keep-alive",
111
+ "Access-Control-Allow-Origin": "*"
112
+ });
113
+ if (res.flushHeaders) res.flushHeaders();
114
+ const write = (data) => {
115
+ res.write(`data: ${JSON.stringify(data)}
116
+
117
+ `);
118
+ };
119
+ const sseContext = {
120
+ sessionId: body.sessionId,
121
+ auth,
122
+ ip,
123
+ state: sessionState,
124
+ requestId: body.requestId,
125
+ emit: (data) => {
126
+ write({ type: "chunk", data });
127
+ }
128
+ };
129
+ try {
130
+ const response2 = await registry.execute(body.command, body.params, sseContext);
131
+ if (response2.ok) {
132
+ write({ type: "done", result: response2.result });
133
+ } else {
134
+ write({ type: "error", error: { code: response2.error.code, message: response2.error.message } });
135
+ }
136
+ } catch (e) {
137
+ write({
138
+ type: "error",
139
+ error: { code: "INTERNAL_ERROR", message: e instanceof Error ? e.message : "Unknown error" }
140
+ });
141
+ } finally {
142
+ res.end();
143
+ }
144
+ return;
145
+ }
146
+ const response = await registry.execute(body.command, body.params, {
147
+ sessionId: body.sessionId,
148
+ auth,
149
+ ip,
150
+ state: sessionState,
151
+ requestId: body.requestId
152
+ });
153
+ if (body.sessionId && response.ok && response.state) {
154
+ await sessions.update(body.sessionId, response.state);
155
+ }
156
+ const statusCode = response.ok ? 200 : getErrorStatus(response.error.code);
157
+ const extraHeaders = {};
158
+ if (!response.ok && response.error.code === "RATE_LIMITED") {
159
+ const retryMs = response.error.details?.["retryAfterMs"] ?? 0;
160
+ extraHeaders["Retry-After"] = String(Math.ceil(retryMs / 1e3));
161
+ }
162
+ sendJson(res, statusCode, response, extraHeaders);
163
+ return;
164
+ }
165
+ if (route === "/surf/pipeline" || route === "/pipeline") {
166
+ const body = req.body;
167
+ if (!body) {
168
+ sendJson(res, 400, { ok: false, error: { code: "INVALID_PARAMS", message: "Missing request body" } });
169
+ return;
170
+ }
171
+ try {
172
+ const result = await executePipeline(
173
+ body,
174
+ registry,
175
+ sessions,
176
+ auth
177
+ );
178
+ sendJson(res, 200, result);
179
+ } catch (e) {
180
+ sendJson(res, 500, {
181
+ ok: false,
182
+ error: { code: "INTERNAL_ERROR", message: e instanceof Error ? e.message : "Unknown error" }
183
+ });
184
+ }
185
+ return;
186
+ }
187
+ if (route === "/surf/session/start" || route === "/session/start") {
188
+ const session = await sessions.create();
189
+ sendJson(res, 200, { ok: true, sessionId: session.id });
190
+ return;
191
+ }
192
+ if (route === "/surf/session/end" || route === "/session/end") {
193
+ const body = req.body;
194
+ if (body?.sessionId) {
195
+ await sessions.destroy(body.sessionId);
196
+ }
197
+ sendJson(res, 200, { ok: true });
198
+ return;
199
+ }
200
+ sendJson(res, 404, { ok: false, error: { code: "NOT_FOUND", message: `Unknown route: POST ${route}` } });
201
+ };
202
+ }
203
+
204
+ export { createSurfApiHandler, extractAuth, extractIp, getErrorStatus };
205
+ //# sourceMappingURL=pages.js.map
206
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared.ts","../src/pages.ts"],"names":["response"],"mappings":"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","file":"pages.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 * 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,22 @@
1
+ /**
2
+ * Surf error code to HTTP status code mapping.
3
+ * Consistent with fastify and hono adapters.
4
+ */
5
+ declare function getErrorStatus(code: string): number;
6
+ /**
7
+ * Extract Bearer token from an Authorization header value.
8
+ *
9
+ * @param authHeader - Raw Authorization header value
10
+ * @returns The token string, or `undefined` if not present
11
+ */
12
+ declare function extractAuth(authHeader: string | null | undefined): string | undefined;
13
+ /**
14
+ * Extract client IP from forwarding headers.
15
+ *
16
+ * @param forwardedFor - `x-forwarded-for` header value
17
+ * @param realIp - `x-real-ip` header value
18
+ * @returns The client IP string, or `undefined` if not determinable
19
+ */
20
+ declare function extractIp(forwardedFor: string | null | undefined, realIp: string | null | undefined): string | undefined;
21
+
22
+ export { extractIp as a, extractAuth as e, getErrorStatus as g };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Surf error code to HTTP status code mapping.
3
+ * Consistent with fastify and hono adapters.
4
+ */
5
+ declare function getErrorStatus(code: string): number;
6
+ /**
7
+ * Extract Bearer token from an Authorization header value.
8
+ *
9
+ * @param authHeader - Raw Authorization header value
10
+ * @returns The token string, or `undefined` if not present
11
+ */
12
+ declare function extractAuth(authHeader: string | null | undefined): string | undefined;
13
+ /**
14
+ * Extract client IP from forwarding headers.
15
+ *
16
+ * @param forwardedFor - `x-forwarded-for` header value
17
+ * @param realIp - `x-real-ip` header value
18
+ * @returns The client IP string, or `undefined` if not determinable
19
+ */
20
+ declare function extractIp(forwardedFor: string | null | undefined, realIp: string | null | undefined): string | undefined;
21
+
22
+ export { extractIp as a, extractAuth as e, getErrorStatus as g };
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@surfjs/next",
3
+ "version": "0.2.0",
4
+ "description": "Next.js adapter for Surf.js — App Router + Pages Router support",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "./pages": {
21
+ "import": {
22
+ "types": "./dist/pages.d.ts",
23
+ "default": "./dist/pages.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/pages.d.cts",
27
+ "default": "./dist/pages.cjs"
28
+ }
29
+ }
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "dev": "tsup --watch",
37
+ "typecheck": "tsc --noEmit"
38
+ },
39
+ "peerDependencies": {
40
+ "@surfjs/core": ">=0.2.0",
41
+ "next": ">=13.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@surfjs/core": "workspace:*",
45
+ "@types/node": "^22.0.0",
46
+ "next": "^15.0.0",
47
+ "tsup": "^8.5.1",
48
+ "typescript": "^5.8.0"
49
+ },
50
+ "license": "MIT",
51
+ "keywords": [
52
+ "surf",
53
+ "next",
54
+ "nextjs",
55
+ "ai",
56
+ "agents",
57
+ "protocol",
58
+ "app-router",
59
+ "pages-router"
60
+ ],
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "https://github.com/hauselabs/surf.git",
64
+ "directory": "packages/next"
65
+ },
66
+ "homepage": "https://surf.codes",
67
+ "bugs": {
68
+ "url": "https://github.com/hauselabs/surf/issues"
69
+ },
70
+ "publishConfig": {
71
+ "access": "public"
72
+ }
73
+ }