@spfn/core 0.1.0-alpha.86 → 0.2.0-beta.1
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 +1046 -384
- package/dist/boss-D-fGtVgM.d.ts +187 -0
- package/dist/cache/index.d.ts +13 -33
- package/dist/cache/index.js +14 -703
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +167 -17
- package/dist/codegen/index.js +76 -1419
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +1191 -0
- package/dist/config/index.js +264 -0
- package/dist/config/index.js.map +1 -0
- package/dist/db/index.d.ts +728 -59
- package/dist/db/index.js +1028 -1225
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +579 -308
- package/dist/env/index.js +438 -930
- package/dist/env/index.js.map +1 -1
- package/dist/errors/index.d.ts +417 -29
- package/dist/errors/index.js +359 -98
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +108 -0
- package/dist/event/index.js +122 -0
- package/dist/event/index.js.map +1 -0
- package/dist/job/index.d.ts +172 -0
- package/dist/job/index.js +361 -0
- package/dist/job/index.js.map +1 -0
- package/dist/logger/index.d.ts +20 -79
- package/dist/logger/index.js +82 -387
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +2 -11
- package/dist/middleware/index.js +49 -703
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +120 -0
- package/dist/nextjs/index.js +416 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/{client/nextjs/index.d.ts → nextjs/server.d.ts} +288 -262
- package/dist/nextjs/server.js +568 -0
- package/dist/nextjs/server.js.map +1 -0
- package/dist/route/index.d.ts +667 -25
- package/dist/route/index.js +437 -1287
- package/dist/route/index.js.map +1 -1
- package/dist/route/types.d.ts +38 -0
- package/dist/route/types.js +3 -0
- package/dist/route/types.js.map +1 -0
- package/dist/server/index.d.ts +201 -67
- package/dist/server/index.js +921 -3182
- package/dist/server/index.js.map +1 -1
- package/dist/types-BGl4QL1w.d.ts +77 -0
- package/dist/types-DRG2XMTR.d.ts +157 -0
- package/package.json +56 -48
- package/dist/auto-loader-JFaZ9gON.d.ts +0 -80
- package/dist/client/index.d.ts +0 -358
- package/dist/client/index.js +0 -357
- package/dist/client/index.js.map +0 -1
- package/dist/client/nextjs/index.js +0 -371
- package/dist/client/nextjs/index.js.map +0 -1
- package/dist/codegen/generators/index.d.ts +0 -19
- package/dist/codegen/generators/index.js +0 -1404
- package/dist/codegen/generators/index.js.map +0 -1
- package/dist/database-errors-BNNmLTJE.d.ts +0 -86
- package/dist/events/index.d.ts +0 -183
- package/dist/events/index.js +0 -77
- package/dist/events/index.js.map +0 -1
- package/dist/index-DHiAqhKv.d.ts +0 -101
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -3674
- package/dist/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -121
- package/dist/types/index.js +0 -38
- package/dist/types/index.js.map +0 -1
- package/dist/types-BXibIEyj.d.ts +0 -60
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { env } from '@spfn/core/config';
|
|
3
|
+
import { logger } from '@spfn/core/logger';
|
|
4
|
+
|
|
5
|
+
// src/nextjs/proxy/rpc.ts
|
|
6
|
+
|
|
7
|
+
// src/nextjs/shared.ts
|
|
8
|
+
function buildUrlWithParams(path, params) {
|
|
9
|
+
let url = path;
|
|
10
|
+
for (const [key, value] of Object.entries(params)) {
|
|
11
|
+
url = url.replace(`:${key}`, encodeURIComponent(String(value)));
|
|
12
|
+
}
|
|
13
|
+
return url;
|
|
14
|
+
}
|
|
15
|
+
function buildQueryString(query) {
|
|
16
|
+
if (Object.keys(query).length === 0) {
|
|
17
|
+
return "";
|
|
18
|
+
}
|
|
19
|
+
const searchParams = new URLSearchParams();
|
|
20
|
+
for (const [key, value] of Object.entries(query)) {
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
value.forEach((v) => searchParams.append(key, String(v)));
|
|
23
|
+
} else {
|
|
24
|
+
searchParams.append(key, String(value));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return `?${searchParams.toString()}`;
|
|
28
|
+
}
|
|
29
|
+
async function parseResponseBody(response) {
|
|
30
|
+
const contentType = response.headers.get("content-type");
|
|
31
|
+
if (contentType?.includes("application/json")) {
|
|
32
|
+
const text = await response.text();
|
|
33
|
+
return text ? JSON.parse(text) : null;
|
|
34
|
+
} else {
|
|
35
|
+
return await response.text();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/nextjs/proxy/interceptors/helpers.ts
|
|
40
|
+
function matchPath(path, pattern) {
|
|
41
|
+
if (pattern === "*") {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (pattern instanceof RegExp) {
|
|
45
|
+
return pattern.test(path);
|
|
46
|
+
}
|
|
47
|
+
const regexPattern = pattern.replace(/\*/g, ".*").replace(/:[^/]+/g, "[^/]+").replace(/\//g, "\\/");
|
|
48
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
49
|
+
return regex.test(path);
|
|
50
|
+
}
|
|
51
|
+
function matchMethod(method, pattern) {
|
|
52
|
+
if (!pattern) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (typeof pattern === "string") {
|
|
56
|
+
return method.toUpperCase() === pattern.toUpperCase();
|
|
57
|
+
}
|
|
58
|
+
return pattern.some((m) => m.toUpperCase() === method.toUpperCase());
|
|
59
|
+
}
|
|
60
|
+
function filterMatchingInterceptors(rules, path, method) {
|
|
61
|
+
return rules.filter((rule) => {
|
|
62
|
+
return matchPath(path, rule.pathPattern) && matchMethod(method, rule.method);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async function executeRequestInterceptors(context, interceptors) {
|
|
66
|
+
let index = 0;
|
|
67
|
+
const next = async () => {
|
|
68
|
+
if (index >= interceptors.length) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const interceptor = interceptors[index];
|
|
72
|
+
index++;
|
|
73
|
+
await interceptor(context, next);
|
|
74
|
+
};
|
|
75
|
+
await next();
|
|
76
|
+
}
|
|
77
|
+
async function executeResponseInterceptors(context, interceptors) {
|
|
78
|
+
let index = 0;
|
|
79
|
+
const next = async () => {
|
|
80
|
+
if (index >= interceptors.length) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const interceptor = interceptors[index];
|
|
84
|
+
index++;
|
|
85
|
+
await interceptor(context, next);
|
|
86
|
+
};
|
|
87
|
+
await next();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/nextjs/proxy/interceptors/registry.ts
|
|
91
|
+
var InterceptorRegistry = class {
|
|
92
|
+
interceptors = /* @__PURE__ */ new Map();
|
|
93
|
+
/**
|
|
94
|
+
* Register interceptors for a package
|
|
95
|
+
*
|
|
96
|
+
* @param packageName - Unique package identifier (e.g., 'auth', 'storage')
|
|
97
|
+
* @param interceptors - Array of interceptor rules
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* registerInterceptors('auth', [
|
|
102
|
+
* {
|
|
103
|
+
* pathPattern: '/_auth/*',
|
|
104
|
+
* request: async (ctx, next) => { ... }
|
|
105
|
+
* }
|
|
106
|
+
* ]);
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
register(packageName, interceptors) {
|
|
110
|
+
if (!this.interceptors.has(packageName)) {
|
|
111
|
+
this.interceptors.set(packageName, interceptors);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Get all registered interceptors
|
|
116
|
+
*
|
|
117
|
+
* @param exclude - Package names to exclude
|
|
118
|
+
* @returns Flat array of all interceptor rules
|
|
119
|
+
*/
|
|
120
|
+
getAll(exclude = []) {
|
|
121
|
+
const all = [];
|
|
122
|
+
for (const [packageName, interceptors] of this.interceptors.entries()) {
|
|
123
|
+
if (!exclude.includes(packageName)) {
|
|
124
|
+
all.push(...interceptors);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return all;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get interceptors for specific package
|
|
131
|
+
*
|
|
132
|
+
* @param packageName - Package identifier
|
|
133
|
+
* @returns Interceptor rules or undefined
|
|
134
|
+
*/
|
|
135
|
+
get(packageName) {
|
|
136
|
+
return this.interceptors.get(packageName);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get list of registered package names
|
|
140
|
+
*/
|
|
141
|
+
getPackageNames() {
|
|
142
|
+
return Array.from(this.interceptors.keys());
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Check if package has registered interceptors
|
|
146
|
+
*/
|
|
147
|
+
has(packageName) {
|
|
148
|
+
return this.interceptors.has(packageName);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Unregister interceptors for a package
|
|
152
|
+
*
|
|
153
|
+
* @param packageName - Package identifier
|
|
154
|
+
*/
|
|
155
|
+
unregister(packageName) {
|
|
156
|
+
this.interceptors.delete(packageName);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Clear all registered interceptors
|
|
160
|
+
*
|
|
161
|
+
* Useful for testing
|
|
162
|
+
*/
|
|
163
|
+
clear() {
|
|
164
|
+
this.interceptors.clear();
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Get total count of registered interceptors
|
|
168
|
+
*/
|
|
169
|
+
count() {
|
|
170
|
+
let total = 0;
|
|
171
|
+
for (const interceptors of this.interceptors.values()) {
|
|
172
|
+
total += interceptors.length;
|
|
173
|
+
}
|
|
174
|
+
return total;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var interceptorRegistry = (() => {
|
|
178
|
+
if (!globalThis.__SPFN_INTERCEPTOR_REGISTRY__) {
|
|
179
|
+
globalThis.__SPFN_INTERCEPTOR_REGISTRY__ = new InterceptorRegistry();
|
|
180
|
+
}
|
|
181
|
+
return globalThis.__SPFN_INTERCEPTOR_REGISTRY__;
|
|
182
|
+
})();
|
|
183
|
+
function registerInterceptors(packageName, interceptors) {
|
|
184
|
+
interceptorRegistry.register(packageName, interceptors);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/nextjs/proxy/helpers.ts
|
|
188
|
+
function buildProxyHeaders(sourceHeaders, defaultHeaders) {
|
|
189
|
+
const headers = new Headers();
|
|
190
|
+
const headersToForward2 = [
|
|
191
|
+
"content-type",
|
|
192
|
+
"authorization",
|
|
193
|
+
"cookie",
|
|
194
|
+
"user-agent",
|
|
195
|
+
"accept",
|
|
196
|
+
"accept-language"
|
|
197
|
+
];
|
|
198
|
+
for (const header of headersToForward2) {
|
|
199
|
+
const value = sourceHeaders instanceof Headers ? sourceHeaders.get(header) : sourceHeaders[header];
|
|
200
|
+
if (value) {
|
|
201
|
+
headers.set(header, value);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const [key, value] of Object.entries(defaultHeaders)) {
|
|
205
|
+
headers.set(key, value);
|
|
206
|
+
}
|
|
207
|
+
return headers;
|
|
208
|
+
}
|
|
209
|
+
function parseCookies(cookieHeader) {
|
|
210
|
+
const cookiesMap = /* @__PURE__ */ new Map();
|
|
211
|
+
if (!cookieHeader) {
|
|
212
|
+
return cookiesMap;
|
|
213
|
+
}
|
|
214
|
+
const cookiePairs = cookieHeader.split(";").map((c) => c.trim());
|
|
215
|
+
for (const pair of cookiePairs) {
|
|
216
|
+
const [name, ...valueParts] = pair.split("=");
|
|
217
|
+
if (name && valueParts.length > 0) {
|
|
218
|
+
const value = valueParts.join("=");
|
|
219
|
+
cookiesMap.set(name.trim(), value.trim());
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return cookiesMap;
|
|
223
|
+
}
|
|
224
|
+
function parseCookiesFromNextRequest(request) {
|
|
225
|
+
const cookiesMap = /* @__PURE__ */ new Map();
|
|
226
|
+
for (const cookie of request.cookies.getAll()) {
|
|
227
|
+
cookiesMap.set(cookie.name, cookie.value);
|
|
228
|
+
}
|
|
229
|
+
const cookieHeader = request.headers.get("cookie");
|
|
230
|
+
if (cookieHeader) {
|
|
231
|
+
const parsed = parseCookies(cookieHeader);
|
|
232
|
+
for (const [name, value] of parsed.entries()) {
|
|
233
|
+
cookiesMap.set(name, value);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return cookiesMap;
|
|
237
|
+
}
|
|
238
|
+
var optionMappings = [
|
|
239
|
+
{ key: "httpOnly", format: (v) => v ? "HttpOnly" : null },
|
|
240
|
+
{ key: "secure", format: (v) => v ? "Secure" : null },
|
|
241
|
+
{ key: "sameSite", format: (v) => v ? `SameSite=${v}` : null },
|
|
242
|
+
{ key: "maxAge", format: (v) => v !== void 0 ? `Max-Age=${v}` : null },
|
|
243
|
+
{ key: "path", format: (v) => v ? `Path=${v}` : null },
|
|
244
|
+
{ key: "domain", format: (v) => v ? `Domain=${v}` : null }
|
|
245
|
+
];
|
|
246
|
+
function buildSetCookieHeader(cookie) {
|
|
247
|
+
const parts = [`${cookie.name}=${cookie.value}`];
|
|
248
|
+
const options = cookie.options || {};
|
|
249
|
+
for (const { key, format } of optionMappings) {
|
|
250
|
+
const value = options[key];
|
|
251
|
+
if (value !== void 0 && value !== false) {
|
|
252
|
+
const formatted = format(value);
|
|
253
|
+
if (formatted) {
|
|
254
|
+
parts.push(formatted);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return parts.join("; ");
|
|
259
|
+
}
|
|
260
|
+
function buildErrorResponse(errorType, message, debug, error) {
|
|
261
|
+
return {
|
|
262
|
+
error: errorType,
|
|
263
|
+
message,
|
|
264
|
+
...debug && error?.stack && { stack: error.stack }
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
var headersToForward = [
|
|
268
|
+
"content-type",
|
|
269
|
+
"cache-control",
|
|
270
|
+
"set-cookie",
|
|
271
|
+
"etag",
|
|
272
|
+
"last-modified"
|
|
273
|
+
];
|
|
274
|
+
function forwardResponseHeaders(sourceHeaders, targetHeaders) {
|
|
275
|
+
for (const header of headersToForward) {
|
|
276
|
+
const value = sourceHeaders.get(header);
|
|
277
|
+
if (value) {
|
|
278
|
+
targetHeaders.set(header, value);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function collectInterceptors(autoDiscoverInterceptors, disableAutoInterceptors, configInterceptors, registry) {
|
|
283
|
+
const allInterceptors = [];
|
|
284
|
+
if (autoDiscoverInterceptors) {
|
|
285
|
+
const registeredInterceptors = registry.getAll(disableAutoInterceptors || []);
|
|
286
|
+
allInterceptors.push(...registeredInterceptors);
|
|
287
|
+
}
|
|
288
|
+
if (configInterceptors) {
|
|
289
|
+
allInterceptors.push(...configInterceptors);
|
|
290
|
+
}
|
|
291
|
+
return allInterceptors;
|
|
292
|
+
}
|
|
293
|
+
function buildRequestContext(path, method, headers, body, searchParams, cookiesMap, request) {
|
|
294
|
+
return {
|
|
295
|
+
path: `/${path}`,
|
|
296
|
+
method,
|
|
297
|
+
headers: Object.fromEntries(headers.entries()),
|
|
298
|
+
body,
|
|
299
|
+
query: Object.fromEntries(searchParams.entries()),
|
|
300
|
+
cookies: cookiesMap,
|
|
301
|
+
request,
|
|
302
|
+
metadata: {}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function buildResponseContext(path, method, requestHeaders, requestBody, response, responseBody, requestMetadata) {
|
|
306
|
+
return {
|
|
307
|
+
path: `/${path}`,
|
|
308
|
+
method,
|
|
309
|
+
request: {
|
|
310
|
+
headers: Object.fromEntries(requestHeaders.entries()),
|
|
311
|
+
body: requestBody
|
|
312
|
+
},
|
|
313
|
+
response: {
|
|
314
|
+
status: response.status,
|
|
315
|
+
statusText: response.statusText,
|
|
316
|
+
headers: response.headers,
|
|
317
|
+
body: responseBody
|
|
318
|
+
},
|
|
319
|
+
setCookies: [],
|
|
320
|
+
metadata: requestMetadata
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/nextjs/proxy/rpc.ts
|
|
325
|
+
var rpcLogger = logger.child("@spfn/core:rpc-proxy");
|
|
326
|
+
function isRouteDef(value) {
|
|
327
|
+
return value !== null && typeof value === "object" && "handler" in value && "method" in value && "path" in value;
|
|
328
|
+
}
|
|
329
|
+
function isRouter(value) {
|
|
330
|
+
return value !== null && typeof value === "object" && "routes" in value && "_routes" in value;
|
|
331
|
+
}
|
|
332
|
+
function getRouteByPath(router, routePath) {
|
|
333
|
+
const parts = routePath.split(".");
|
|
334
|
+
let current = router.routes;
|
|
335
|
+
for (const part of parts) {
|
|
336
|
+
if (!current || typeof current !== "object") {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
const next = current[part];
|
|
340
|
+
if (isRouter(next)) {
|
|
341
|
+
current = next.routes;
|
|
342
|
+
} else if (isRouteDef(next)) {
|
|
343
|
+
return next;
|
|
344
|
+
} else {
|
|
345
|
+
current = next;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (isRouteDef(current)) {
|
|
349
|
+
return current;
|
|
350
|
+
}
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
function createRpcProxy(config) {
|
|
354
|
+
const {
|
|
355
|
+
router,
|
|
356
|
+
apiUrl = env.SPFN_API_URL || "http://localhost:8790",
|
|
357
|
+
debug = env.NODE_ENV === "development",
|
|
358
|
+
timeout = 3e4,
|
|
359
|
+
headers: defaultHeaders = {},
|
|
360
|
+
interceptors,
|
|
361
|
+
autoDiscoverInterceptors = true,
|
|
362
|
+
disableAutoInterceptors
|
|
363
|
+
} = config;
|
|
364
|
+
const packageRouters = router._packageRouters || [];
|
|
365
|
+
async function handleRpc(request, context) {
|
|
366
|
+
const startTime = Date.now();
|
|
367
|
+
const params = await context.params;
|
|
368
|
+
try {
|
|
369
|
+
const routeName = params.routeName;
|
|
370
|
+
if (!routeName) {
|
|
371
|
+
return NextResponse.json(
|
|
372
|
+
buildErrorResponse("Bad Request", "Missing routeName parameter", debug),
|
|
373
|
+
{ status: 400 }
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
let input = {};
|
|
377
|
+
if (request.method === "GET") {
|
|
378
|
+
const inputParam = request.nextUrl.searchParams.get("input");
|
|
379
|
+
if (inputParam) {
|
|
380
|
+
try {
|
|
381
|
+
input = JSON.parse(decodeURIComponent(inputParam));
|
|
382
|
+
} catch {
|
|
383
|
+
return NextResponse.json(
|
|
384
|
+
buildErrorResponse("Bad Request", "Invalid input parameter", debug),
|
|
385
|
+
{ status: 400 }
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
try {
|
|
391
|
+
input = await request.json();
|
|
392
|
+
} catch {
|
|
393
|
+
return NextResponse.json(
|
|
394
|
+
buildErrorResponse("Bad Request", "Invalid JSON body", debug),
|
|
395
|
+
{ status: 400 }
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
let routeDef = getRouteByPath(router, routeName);
|
|
400
|
+
if (!routeDef && packageRouters.length > 0) {
|
|
401
|
+
for (const pkgRouter of packageRouters) {
|
|
402
|
+
routeDef = getRouteByPath(pkgRouter, routeName);
|
|
403
|
+
if (routeDef) {
|
|
404
|
+
if (debug) {
|
|
405
|
+
rpcLogger.debug(`Route "${routeName}" found in package router`);
|
|
406
|
+
}
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (!routeDef) {
|
|
412
|
+
rpcLogger.warn(`Route not found: ${routeName}`);
|
|
413
|
+
return NextResponse.json(
|
|
414
|
+
buildErrorResponse("Not Found", `Route "${routeName}" not found in router`, debug),
|
|
415
|
+
{ status: 404 }
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
const { method: targetMethod, path: targetPath } = routeDef;
|
|
419
|
+
if (!targetMethod || !targetPath) {
|
|
420
|
+
rpcLogger.warn(`Route "${routeName}" is missing method or path`);
|
|
421
|
+
return NextResponse.json(
|
|
422
|
+
buildErrorResponse("Internal Error", `Route "${routeName}" is misconfigured`, debug),
|
|
423
|
+
{ status: 500 }
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
const inputParams = input.params || {};
|
|
427
|
+
const inputQuery = input.query || {};
|
|
428
|
+
const inputBody = input.body;
|
|
429
|
+
const resolvedPath = buildUrlWithParams(targetPath, inputParams);
|
|
430
|
+
const queryString = buildQueryString(inputQuery);
|
|
431
|
+
const targetUrl = `${apiUrl}${resolvedPath}${queryString}`;
|
|
432
|
+
if (debug) {
|
|
433
|
+
rpcLogger.debug("\u2192 RPC request", {
|
|
434
|
+
routeName,
|
|
435
|
+
targetMethod,
|
|
436
|
+
targetPath: resolvedPath,
|
|
437
|
+
targetUrl,
|
|
438
|
+
hasBody: !!inputBody
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
const headers = buildProxyHeaders(request.headers, defaultHeaders);
|
|
442
|
+
const fetchOptions = {
|
|
443
|
+
method: targetMethod,
|
|
444
|
+
headers
|
|
445
|
+
};
|
|
446
|
+
if (["POST", "PUT", "PATCH"].includes(targetMethod) && inputBody) {
|
|
447
|
+
fetchOptions.body = JSON.stringify(inputBody);
|
|
448
|
+
}
|
|
449
|
+
const allInterceptors = collectInterceptors(autoDiscoverInterceptors, disableAutoInterceptors, interceptors, interceptorRegistry);
|
|
450
|
+
const matchingInterceptors = filterMatchingInterceptors(allInterceptors, resolvedPath, targetMethod);
|
|
451
|
+
if (debug && matchingInterceptors.length > 0) {
|
|
452
|
+
rpcLogger.debug(`\u{1F3AF} Found ${matchingInterceptors.length} matching interceptors for ${targetMethod} ${resolvedPath}`);
|
|
453
|
+
}
|
|
454
|
+
const cookiesMap = parseCookiesFromNextRequest(request);
|
|
455
|
+
const requestCtx = buildRequestContext(
|
|
456
|
+
resolvedPath.slice(1),
|
|
457
|
+
// Remove leading slash
|
|
458
|
+
targetMethod,
|
|
459
|
+
headers,
|
|
460
|
+
inputBody,
|
|
461
|
+
new URLSearchParams(queryString.slice(1)),
|
|
462
|
+
// Remove leading ?
|
|
463
|
+
cookiesMap,
|
|
464
|
+
request
|
|
465
|
+
);
|
|
466
|
+
const requestInterceptorsToRun = matchingInterceptors.map((r) => r.request).filter((i) => !!i);
|
|
467
|
+
if (requestInterceptorsToRun.length > 0) {
|
|
468
|
+
await executeRequestInterceptors(requestCtx, requestInterceptorsToRun);
|
|
469
|
+
for (const [key, value] of Object.entries(requestCtx.headers)) {
|
|
470
|
+
headers.set(key, value);
|
|
471
|
+
}
|
|
472
|
+
if (requestCtx.body) {
|
|
473
|
+
fetchOptions.body = JSON.stringify(requestCtx.body);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const controller = new AbortController();
|
|
477
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
478
|
+
try {
|
|
479
|
+
const response = await fetch(targetUrl, {
|
|
480
|
+
...fetchOptions,
|
|
481
|
+
signal: controller.signal
|
|
482
|
+
});
|
|
483
|
+
clearTimeout(timeoutId);
|
|
484
|
+
let body = await parseResponseBody(response);
|
|
485
|
+
const responseCtx = buildResponseContext(
|
|
486
|
+
resolvedPath.slice(1),
|
|
487
|
+
targetMethod,
|
|
488
|
+
headers,
|
|
489
|
+
inputBody,
|
|
490
|
+
response,
|
|
491
|
+
body,
|
|
492
|
+
requestCtx.metadata
|
|
493
|
+
);
|
|
494
|
+
const responseInterceptorsToRun = matchingInterceptors.map((r) => r.response).filter((i) => !!i);
|
|
495
|
+
if (responseInterceptorsToRun.length > 0) {
|
|
496
|
+
await executeResponseInterceptors(responseCtx, responseInterceptorsToRun);
|
|
497
|
+
body = responseCtx.response.body;
|
|
498
|
+
}
|
|
499
|
+
const duration = Date.now() - startTime;
|
|
500
|
+
if (debug) {
|
|
501
|
+
rpcLogger.debug("\u2190 RPC response", {
|
|
502
|
+
routeName,
|
|
503
|
+
status: responseCtx.response.status,
|
|
504
|
+
duration: `${duration}ms`
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
const nextResponse = NextResponse.json(body, {
|
|
508
|
+
status: responseCtx.response.status,
|
|
509
|
+
statusText: responseCtx.response.statusText
|
|
510
|
+
});
|
|
511
|
+
forwardResponseHeaders(response.headers, nextResponse.headers);
|
|
512
|
+
for (const cookie of responseCtx.setCookies) {
|
|
513
|
+
const setCookieHeader = buildSetCookieHeader(cookie);
|
|
514
|
+
nextResponse.headers.append("Set-Cookie", setCookieHeader);
|
|
515
|
+
if (debug) {
|
|
516
|
+
rpcLogger.debug("\u{1F36A} Set-Cookie header added", {
|
|
517
|
+
name: cookie.name
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return nextResponse;
|
|
522
|
+
} catch (error) {
|
|
523
|
+
clearTimeout(timeoutId);
|
|
524
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
525
|
+
rpcLogger.error("Request timeout", {
|
|
526
|
+
routeName,
|
|
527
|
+
targetUrl,
|
|
528
|
+
timeout
|
|
529
|
+
});
|
|
530
|
+
return NextResponse.json(
|
|
531
|
+
buildErrorResponse("Gateway Timeout", `Request timed out after ${timeout}ms`, debug, error),
|
|
532
|
+
{ status: 504 }
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
const fetchErr = error;
|
|
536
|
+
rpcLogger.error("Fetch error", {
|
|
537
|
+
routeName,
|
|
538
|
+
targetUrl,
|
|
539
|
+
error: fetchErr.message
|
|
540
|
+
});
|
|
541
|
+
return NextResponse.json(
|
|
542
|
+
buildErrorResponse("Bad Gateway", fetchErr.message || "Failed to connect to backend", debug, fetchErr),
|
|
543
|
+
{ status: 502 }
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
} catch (error) {
|
|
547
|
+
const duration = Date.now() - startTime;
|
|
548
|
+
const err = error;
|
|
549
|
+
rpcLogger.error("RPC proxy error", {
|
|
550
|
+
error: err.message,
|
|
551
|
+
stack: err.stack,
|
|
552
|
+
duration: `${duration}ms`
|
|
553
|
+
});
|
|
554
|
+
return NextResponse.json(
|
|
555
|
+
buildErrorResponse("Internal Server Error", err.message || "Unknown error", debug, err),
|
|
556
|
+
{ status: 500 }
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
GET: (req, context) => handleRpc(req, context),
|
|
562
|
+
POST: (req, context) => handleRpc(req, context)
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export { createRpcProxy, executeRequestInterceptors, executeResponseInterceptors, filterMatchingInterceptors, interceptorRegistry, matchMethod, matchPath, registerInterceptors };
|
|
567
|
+
//# sourceMappingURL=server.js.map
|
|
568
|
+
//# sourceMappingURL=server.js.map
|