@lastbrain/ai-ui-core 1.0.29 → 1.0.30
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.
|
@@ -16,50 +16,47 @@ export interface LBProxyConfig {
|
|
|
16
16
|
sessionCookieName?: string;
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
* Crée un handler de proxy pour Next.js App Router
|
|
19
|
+
* Crée un handler de proxy unifié pour Next.js App Router
|
|
20
20
|
*
|
|
21
21
|
* Usage:
|
|
22
22
|
* ```typescript
|
|
23
|
-
* // app/api/
|
|
23
|
+
* // app/api/lastbrain/[...path]/route.ts
|
|
24
24
|
* import { createLBProxyHandler } from "@lastbrain/ai-ui-core";
|
|
25
25
|
*
|
|
26
|
-
* const { GET, POST, PUT, DELETE } = createLBProxyHandler({
|
|
26
|
+
* export const { GET, POST, PUT, DELETE, PATCH } = createLBProxyHandler({
|
|
27
27
|
* baseUrl: process.env.LB_BASE_URL,
|
|
28
|
-
* apiKey: process.env.LB_API_KEY,
|
|
29
28
|
* });
|
|
30
|
-
*
|
|
31
|
-
* export { GET, POST, PUT, DELETE };
|
|
32
29
|
* ```
|
|
33
30
|
*/
|
|
34
31
|
export declare function createLBProxyHandler(config?: LBProxyConfig): {
|
|
35
|
-
GET: (request: NextRequest,
|
|
32
|
+
GET: (request: NextRequest, context?: {
|
|
36
33
|
params: Promise<{
|
|
37
|
-
path
|
|
34
|
+
path?: string[];
|
|
38
35
|
}>;
|
|
39
36
|
}) => Promise<NextResponse>;
|
|
40
|
-
POST: (request: NextRequest,
|
|
37
|
+
POST: (request: NextRequest, context?: {
|
|
41
38
|
params: Promise<{
|
|
42
|
-
path
|
|
39
|
+
path?: string[];
|
|
43
40
|
}>;
|
|
44
41
|
}) => Promise<NextResponse>;
|
|
45
|
-
PUT: (request: NextRequest,
|
|
42
|
+
PUT: (request: NextRequest, context?: {
|
|
46
43
|
params: Promise<{
|
|
47
|
-
path
|
|
44
|
+
path?: string[];
|
|
48
45
|
}>;
|
|
49
46
|
}) => Promise<NextResponse>;
|
|
50
|
-
DELETE: (request: NextRequest,
|
|
47
|
+
DELETE: (request: NextRequest, context?: {
|
|
51
48
|
params: Promise<{
|
|
52
|
-
path
|
|
49
|
+
path?: string[];
|
|
53
50
|
}>;
|
|
54
51
|
}) => Promise<NextResponse>;
|
|
55
|
-
PATCH: (request: NextRequest,
|
|
52
|
+
PATCH: (request: NextRequest, context?: {
|
|
56
53
|
params: Promise<{
|
|
57
|
-
path
|
|
54
|
+
path?: string[];
|
|
58
55
|
}>;
|
|
59
56
|
}) => Promise<NextResponse>;
|
|
60
57
|
};
|
|
61
58
|
/**
|
|
62
|
-
* Helper pour
|
|
59
|
+
* Helper pour ajouter ou mettre à jour le cookie de session
|
|
63
60
|
*/
|
|
64
61
|
export declare function setLBSessionCookie(response: NextResponse, sessionToken: string, expiresIn?: number): NextResponse;
|
|
65
62
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lb-proxy.d.ts","sourceRoot":"","sources":["../../../src/route-handlers/nextjs/lb-proxy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"lb-proxy.d.ts","sourceRoot":"","sources":["../../../src/route-handlers/nextjs/lb-proxy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+BAA+B;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,aAAkB;mBAWlD,WAAW,YACV;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAA;KAAE,KACjD,OAAO,CAAC,YAAY,CAAC;oBAFb,WAAW,YACV;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAA;KAAE,KACjD,OAAO,CAAC,YAAY,CAAC;mBAFb,WAAW,YACV;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAA;KAAE,KACjD,OAAO,CAAC,YAAY,CAAC;sBAFb,WAAW,YACV;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAA;KAAE,KACjD,OAAO,CAAC,YAAY,CAAC;qBAFb,WAAW,YACV;QAAE,MAAM,EAAE,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;SAAE,CAAC,CAAA;KAAE,KACjD,OAAO,CAAC,YAAY,CAAC;EAiHzB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,SAAS,GAAE,MAAe,GACzB,YAAY,CAYd;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,YAAY,GAAG,YAAY,CAGzE"}
|
|
@@ -4,83 +4,126 @@
|
|
|
4
4
|
* avec support de LB_API_KEY ou session cookie
|
|
5
5
|
*/
|
|
6
6
|
import { NextResponse } from "next/server";
|
|
7
|
-
import { createLBClient } from "../../client/lb-client";
|
|
8
7
|
/**
|
|
9
|
-
* Crée un handler de proxy pour Next.js App Router
|
|
8
|
+
* Crée un handler de proxy unifié pour Next.js App Router
|
|
10
9
|
*
|
|
11
10
|
* Usage:
|
|
12
11
|
* ```typescript
|
|
13
|
-
* // app/api/
|
|
12
|
+
* // app/api/lastbrain/[...path]/route.ts
|
|
14
13
|
* import { createLBProxyHandler } from "@lastbrain/ai-ui-core";
|
|
15
14
|
*
|
|
16
|
-
* const { GET, POST, PUT, DELETE } = createLBProxyHandler({
|
|
15
|
+
* export const { GET, POST, PUT, DELETE, PATCH } = createLBProxyHandler({
|
|
17
16
|
* baseUrl: process.env.LB_BASE_URL,
|
|
18
|
-
* apiKey: process.env.LB_API_KEY,
|
|
19
17
|
* });
|
|
20
|
-
*
|
|
21
|
-
* export { GET, POST, PUT, DELETE };
|
|
22
18
|
* ```
|
|
23
19
|
*/
|
|
24
20
|
export function createLBProxyHandler(config = {}) {
|
|
25
|
-
const { baseUrl = process.env.LB_BASE_URL || "https://
|
|
26
|
-
const client = createLBClient({ baseUrl, apiKey });
|
|
21
|
+
const { baseUrl = process.env.LB_BASE_URL || "https://lastbrain.io", apiKey = process.env.LB_API_KEY, sessionCookieName = "lb_session", } = config;
|
|
27
22
|
/**
|
|
28
|
-
* Handler
|
|
23
|
+
* Handler unique pour toutes les méthodes HTTP et tous les paths
|
|
29
24
|
*/
|
|
30
|
-
async function
|
|
25
|
+
async function handler(request, context) {
|
|
31
26
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
// Extraire le path depuis l'URL ou les params
|
|
28
|
+
let path = "";
|
|
29
|
+
if (context?.params) {
|
|
30
|
+
const resolvedParams = await context.params;
|
|
31
|
+
path = resolvedParams.path?.join("/") || "";
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
// Fallback: extraire depuis l'URL
|
|
35
|
+
const url = new URL(request.url);
|
|
36
|
+
const pathMatch = url.pathname.match(/\/api\/lastbrain\/(.*)/);
|
|
37
|
+
path = pathMatch ? pathMatch[1] : "";
|
|
38
|
+
}
|
|
39
|
+
// Routes publiques qui ne nécessitent pas d'authentification
|
|
40
|
+
const publicPaths = [
|
|
41
|
+
"auth/login",
|
|
42
|
+
"auth/session/create",
|
|
43
|
+
"public/user/api-keys",
|
|
44
|
+
];
|
|
45
|
+
const isPublicPath = publicPaths.some((p) => path.startsWith(p));
|
|
46
|
+
// Construire l'URL complète vers LastBrain
|
|
47
|
+
const targetUrl = `${baseUrl}/api/ai/${path}`;
|
|
37
48
|
// Préparer les headers
|
|
38
|
-
const headers = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
headers.Authorization = `Bearer ${apiKey}`;
|
|
49
|
+
const headers = {};
|
|
50
|
+
// Copier les headers pertinents de la requête originale
|
|
51
|
+
const contentType = request.headers.get("content-type");
|
|
52
|
+
if (contentType) {
|
|
53
|
+
headers["Content-Type"] = contentType;
|
|
44
54
|
}
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
const authorization = request.headers.get("authorization");
|
|
56
|
+
if (authorization) {
|
|
57
|
+
headers["Authorization"] = authorization;
|
|
47
58
|
}
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
// Authentification : LB_API_KEY ou session cookie
|
|
60
|
+
if (!authorization) {
|
|
61
|
+
if (apiKey) {
|
|
62
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Lire le cookie de session
|
|
66
|
+
const sessionToken = request.cookies.get(sessionCookieName)?.value;
|
|
67
|
+
if (sessionToken) {
|
|
68
|
+
headers["Authorization"] = `Bearer ${sessionToken}`;
|
|
69
|
+
}
|
|
70
|
+
else if (!isPublicPath) {
|
|
71
|
+
// Pas d'auth disponible et route protégée
|
|
72
|
+
return NextResponse.json({
|
|
73
|
+
error: "No authentication provided",
|
|
74
|
+
hint: "Set LB_API_KEY env variable or login via /api/lastbrain/auth/login",
|
|
75
|
+
}, { status: 401 });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
50
78
|
}
|
|
51
79
|
// Lire le body si c'est POST/PUT/PATCH
|
|
52
80
|
let body = undefined;
|
|
53
81
|
if (["POST", "PUT", "PATCH"].includes(request.method)) {
|
|
54
|
-
|
|
82
|
+
const text = await request.text();
|
|
83
|
+
if (text) {
|
|
84
|
+
body = text;
|
|
85
|
+
}
|
|
55
86
|
}
|
|
56
87
|
// Faire la requête au backend LastBrain
|
|
57
|
-
const response = await fetch(
|
|
88
|
+
const response = await fetch(targetUrl, {
|
|
58
89
|
method: request.method,
|
|
59
90
|
headers,
|
|
60
|
-
body
|
|
91
|
+
body,
|
|
61
92
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
93
|
+
// Lire la réponse
|
|
94
|
+
const responseText = await response.text();
|
|
95
|
+
let data;
|
|
96
|
+
try {
|
|
97
|
+
data = responseText ? JSON.parse(responseText) : {};
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
data = { error: "Invalid JSON response", raw: responseText };
|
|
101
|
+
}
|
|
102
|
+
// Créer la réponse Next.js
|
|
103
|
+
const nextResponse = NextResponse.json(data, { status: response.status });
|
|
104
|
+
// Copier les cookies de la réponse (notamment lb_session)
|
|
105
|
+
const setCookie = response.headers.get("set-cookie");
|
|
106
|
+
if (setCookie) {
|
|
107
|
+
nextResponse.headers.set("set-cookie", setCookie);
|
|
65
108
|
}
|
|
66
|
-
return
|
|
109
|
+
return nextResponse;
|
|
67
110
|
}
|
|
68
111
|
catch (error) {
|
|
69
112
|
console.error("[LB Proxy] Error:", error);
|
|
70
113
|
return NextResponse.json({ error: error instanceof Error ? error.message : "Proxy error" }, { status: 500 });
|
|
71
114
|
}
|
|
72
115
|
}
|
|
73
|
-
// Retourner les handlers pour
|
|
116
|
+
// Retourner un objet avec les handlers pour chaque méthode HTTP
|
|
74
117
|
return {
|
|
75
|
-
GET:
|
|
76
|
-
POST:
|
|
77
|
-
PUT:
|
|
78
|
-
DELETE:
|
|
79
|
-
PATCH:
|
|
118
|
+
GET: handler,
|
|
119
|
+
POST: handler,
|
|
120
|
+
PUT: handler,
|
|
121
|
+
DELETE: handler,
|
|
122
|
+
PATCH: handler,
|
|
80
123
|
};
|
|
81
124
|
}
|
|
82
125
|
/**
|
|
83
|
-
* Helper pour
|
|
126
|
+
* Helper pour ajouter ou mettre à jour le cookie de session
|
|
84
127
|
*/
|
|
85
128
|
export function setLBSessionCookie(response, sessionToken, expiresIn = 259200 // 72h en secondes
|
|
86
129
|
) {
|
package/package.json
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { NextRequest, NextResponse } from "next/server";
|
|
8
|
-
import { createLBClient } from "../../client/lb-client";
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Configuration du proxy LastBrain
|
|
@@ -20,82 +19,127 @@ export interface LBProxyConfig {
|
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
/**
|
|
23
|
-
* Crée un handler de proxy pour Next.js App Router
|
|
22
|
+
* Crée un handler de proxy unifié pour Next.js App Router
|
|
24
23
|
*
|
|
25
24
|
* Usage:
|
|
26
25
|
* ```typescript
|
|
27
|
-
* // app/api/
|
|
26
|
+
* // app/api/lastbrain/[...path]/route.ts
|
|
28
27
|
* import { createLBProxyHandler } from "@lastbrain/ai-ui-core";
|
|
29
28
|
*
|
|
30
|
-
* const { GET, POST, PUT, DELETE } = createLBProxyHandler({
|
|
29
|
+
* export const { GET, POST, PUT, DELETE, PATCH } = createLBProxyHandler({
|
|
31
30
|
* baseUrl: process.env.LB_BASE_URL,
|
|
32
|
-
* apiKey: process.env.LB_API_KEY,
|
|
33
31
|
* });
|
|
34
|
-
*
|
|
35
|
-
* export { GET, POST, PUT, DELETE };
|
|
36
32
|
* ```
|
|
37
33
|
*/
|
|
38
34
|
export function createLBProxyHandler(config: LBProxyConfig = {}) {
|
|
39
35
|
const {
|
|
40
|
-
baseUrl = process.env.LB_BASE_URL || "https://
|
|
36
|
+
baseUrl = process.env.LB_BASE_URL || "https://lastbrain.io",
|
|
41
37
|
apiKey = process.env.LB_API_KEY,
|
|
42
38
|
sessionCookieName = "lb_session",
|
|
43
39
|
} = config;
|
|
44
40
|
|
|
45
|
-
const client = createLBClient({ baseUrl, apiKey });
|
|
46
|
-
|
|
47
41
|
/**
|
|
48
|
-
* Handler
|
|
42
|
+
* Handler unique pour toutes les méthodes HTTP et tous les paths
|
|
49
43
|
*/
|
|
50
|
-
async function
|
|
44
|
+
async function handler(
|
|
51
45
|
request: NextRequest,
|
|
52
|
-
|
|
46
|
+
context?: { params: Promise<{ path?: string[] }> }
|
|
53
47
|
): Promise<NextResponse> {
|
|
54
48
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
// Extraire le path depuis l'URL ou les params
|
|
50
|
+
let path = "";
|
|
51
|
+
if (context?.params) {
|
|
52
|
+
const resolvedParams = await context.params;
|
|
53
|
+
path = resolvedParams.path?.join("/") || "";
|
|
54
|
+
} else {
|
|
55
|
+
// Fallback: extraire depuis l'URL
|
|
56
|
+
const url = new URL(request.url);
|
|
57
|
+
const pathMatch = url.pathname.match(/\/api\/lastbrain\/(.*)/);
|
|
58
|
+
path = pathMatch ? pathMatch[1] : "";
|
|
59
|
+
}
|
|
58
60
|
|
|
59
|
-
//
|
|
60
|
-
const
|
|
61
|
+
// Routes publiques qui ne nécessitent pas d'authentification
|
|
62
|
+
const publicPaths = [
|
|
63
|
+
"auth/login",
|
|
64
|
+
"auth/session/create",
|
|
65
|
+
"public/user/api-keys",
|
|
66
|
+
];
|
|
67
|
+
const isPublicPath = publicPaths.some((p) => path.startsWith(p));
|
|
68
|
+
|
|
69
|
+
// Construire l'URL complète vers LastBrain
|
|
70
|
+
const targetUrl = `${baseUrl}/api/ai/${path}`;
|
|
61
71
|
|
|
62
72
|
// Préparer les headers
|
|
63
|
-
const headers: Record<string, string> = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
const headers: Record<string, string> = {};
|
|
74
|
+
|
|
75
|
+
// Copier les headers pertinents de la requête originale
|
|
76
|
+
const contentType = request.headers.get("content-type");
|
|
77
|
+
if (contentType) {
|
|
78
|
+
headers["Content-Type"] = contentType;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const authorization = request.headers.get("authorization");
|
|
82
|
+
if (authorization) {
|
|
83
|
+
headers["Authorization"] = authorization;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Authentification : LB_API_KEY ou session cookie
|
|
87
|
+
if (!authorization) {
|
|
88
|
+
if (apiKey) {
|
|
89
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
90
|
+
} else {
|
|
91
|
+
// Lire le cookie de session
|
|
92
|
+
const sessionToken = request.cookies.get(sessionCookieName)?.value;
|
|
93
|
+
if (sessionToken) {
|
|
94
|
+
headers["Authorization"] = `Bearer ${sessionToken}`;
|
|
95
|
+
} else if (!isPublicPath) {
|
|
96
|
+
// Pas d'auth disponible et route protégée
|
|
97
|
+
return NextResponse.json(
|
|
98
|
+
{
|
|
99
|
+
error: "No authentication provided",
|
|
100
|
+
hint: "Set LB_API_KEY env variable or login via /api/lastbrain/auth/login",
|
|
101
|
+
},
|
|
102
|
+
{ status: 401 }
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
77
106
|
}
|
|
78
107
|
|
|
79
108
|
// Lire le body si c'est POST/PUT/PATCH
|
|
80
|
-
let body:
|
|
109
|
+
let body: string | undefined = undefined;
|
|
81
110
|
if (["POST", "PUT", "PATCH"].includes(request.method)) {
|
|
82
|
-
|
|
111
|
+
const text = await request.text();
|
|
112
|
+
if (text) {
|
|
113
|
+
body = text;
|
|
114
|
+
}
|
|
83
115
|
}
|
|
84
116
|
|
|
85
117
|
// Faire la requête au backend LastBrain
|
|
86
|
-
const response = await fetch(
|
|
118
|
+
const response = await fetch(targetUrl, {
|
|
87
119
|
method: request.method,
|
|
88
120
|
headers,
|
|
89
|
-
body
|
|
121
|
+
body,
|
|
90
122
|
});
|
|
91
123
|
|
|
92
|
-
|
|
124
|
+
// Lire la réponse
|
|
125
|
+
const responseText = await response.text();
|
|
126
|
+
let data: any;
|
|
127
|
+
try {
|
|
128
|
+
data = responseText ? JSON.parse(responseText) : {};
|
|
129
|
+
} catch {
|
|
130
|
+
data = { error: "Invalid JSON response", raw: responseText };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Créer la réponse Next.js
|
|
134
|
+
const nextResponse = NextResponse.json(data, { status: response.status });
|
|
93
135
|
|
|
94
|
-
|
|
95
|
-
|
|
136
|
+
// Copier les cookies de la réponse (notamment lb_session)
|
|
137
|
+
const setCookie = response.headers.get("set-cookie");
|
|
138
|
+
if (setCookie) {
|
|
139
|
+
nextResponse.headers.set("set-cookie", setCookie);
|
|
96
140
|
}
|
|
97
141
|
|
|
98
|
-
return
|
|
142
|
+
return nextResponse;
|
|
99
143
|
} catch (error) {
|
|
100
144
|
console.error("[LB Proxy] Error:", error);
|
|
101
145
|
return NextResponse.json(
|
|
@@ -105,18 +149,18 @@ export function createLBProxyHandler(config: LBProxyConfig = {}) {
|
|
|
105
149
|
}
|
|
106
150
|
}
|
|
107
151
|
|
|
108
|
-
// Retourner les handlers pour
|
|
152
|
+
// Retourner un objet avec les handlers pour chaque méthode HTTP
|
|
109
153
|
return {
|
|
110
|
-
GET:
|
|
111
|
-
POST:
|
|
112
|
-
PUT:
|
|
113
|
-
DELETE:
|
|
114
|
-
PATCH:
|
|
154
|
+
GET: handler,
|
|
155
|
+
POST: handler,
|
|
156
|
+
PUT: handler,
|
|
157
|
+
DELETE: handler,
|
|
158
|
+
PATCH: handler,
|
|
115
159
|
};
|
|
116
160
|
}
|
|
117
161
|
|
|
118
162
|
/**
|
|
119
|
-
* Helper pour
|
|
163
|
+
* Helper pour ajouter ou mettre à jour le cookie de session
|
|
120
164
|
*/
|
|
121
165
|
export function setLBSessionCookie(
|
|
122
166
|
response: NextResponse,
|