@commerce-ai-tool/server 2.4.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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/cart-actions.d.ts +27 -0
- package/dist/cart-actions.d.ts.map +1 -0
- package/dist/cart-session.d.ts +16 -0
- package/dist/cart-session.d.ts.map +1 -0
- package/dist/checkout-actions.d.ts +17 -0
- package/dist/checkout-actions.d.ts.map +1 -0
- package/dist/chunk-GMYUJ2J6.js +339 -0
- package/dist/chunk-GMYUJ2J6.js.map +1 -0
- package/dist/chunk-PPWA5L7U.js +1321 -0
- package/dist/chunk-PPWA5L7U.js.map +1 -0
- package/dist/chunk-UGRFHLDW.js +21 -0
- package/dist/chunk-UGRFHLDW.js.map +1 -0
- package/dist/express.cjs +1306 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +35 -0
- package/dist/express.d.ts +35 -0
- package/dist/express.d.ts.map +1 -0
- package/dist/express.js +164 -0
- package/dist/express.js.map +1 -0
- package/dist/flush.cjs +49 -0
- package/dist/flush.cjs.map +1 -0
- package/dist/flush.d.cts +2 -0
- package/dist/flush.d.ts +2 -0
- package/dist/flush.d.ts.map +1 -0
- package/dist/flush.js +11 -0
- package/dist/flush.js.map +1 -0
- package/dist/handler-response.d.ts +10 -0
- package/dist/handler-response.d.ts.map +1 -0
- package/dist/handlers.d.ts +29 -0
- package/dist/handlers.d.ts.map +1 -0
- package/dist/index.cjs +1613 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/login-rate-limit.d.ts +22 -0
- package/dist/login-rate-limit.d.ts.map +1 -0
- package/dist/next.cjs +1511 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +26 -0
- package/dist/next.d.ts +26 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +9 -0
- package/dist/next.js.map +1 -0
- package/dist/observability/flush.d.ts +14 -0
- package/dist/observability/flush.d.ts.map +1 -0
- package/dist/observability/langfuse.d.ts +17 -0
- package/dist/observability/langfuse.d.ts.map +1 -0
- package/dist/order-actions.d.ts +8 -0
- package/dist/order-actions.d.ts.map +1 -0
- package/dist/payment-actions.d.ts +12 -0
- package/dist/payment-actions.d.ts.map +1 -0
- package/dist/route-actions.d.ts +43 -0
- package/dist/route-actions.d.ts.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/utils/locale.d.ts +9 -0
- package/dist/utils/locale.d.ts.map +1 -0
- package/dist/utils/log-error.d.ts +3 -0
- package/dist/utils/log-error.d.ts.map +1 -0
- package/dist/utils/multipart.d.ts +14 -0
- package/dist/utils/multipart.d.ts.map +1 -0
- package/package.json +123 -0
package/dist/next.cjs
ADDED
|
@@ -0,0 +1,1511 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/next.ts
|
|
31
|
+
var next_exports = {};
|
|
32
|
+
__export(next_exports, {
|
|
33
|
+
createNextHandlers: () => createNextHandlers
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(next_exports);
|
|
36
|
+
|
|
37
|
+
// src/handler-response.ts
|
|
38
|
+
function jsonResponse(data, status = 200) {
|
|
39
|
+
return {
|
|
40
|
+
status,
|
|
41
|
+
headers: { "Content-Type": "application/json" },
|
|
42
|
+
body: JSON.stringify(data)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function errorResponse(message, status = 500) {
|
|
46
|
+
return jsonResponse({ error: message }, status);
|
|
47
|
+
}
|
|
48
|
+
function toWebResponse(handlerResponse) {
|
|
49
|
+
const headers = new Headers(handlerResponse.headers);
|
|
50
|
+
const body = typeof handlerResponse.body === "string" ? handlerResponse.body : new Uint8Array(handlerResponse.body);
|
|
51
|
+
return new Response(body, { status: handlerResponse.status, headers });
|
|
52
|
+
}
|
|
53
|
+
function toWebErrorResponse(message, status = 500) {
|
|
54
|
+
return Response.json({ error: message }, { status });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/route-actions.ts
|
|
58
|
+
var import_core3 = require("@commerce-ai-tool/core");
|
|
59
|
+
|
|
60
|
+
// src/cart-session.ts
|
|
61
|
+
var import_node_crypto = require("crypto");
|
|
62
|
+
var import_core = require("@commerce-ai-tool/core");
|
|
63
|
+
function readCartSessionHeader(headers) {
|
|
64
|
+
if (!headers) {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
if (typeof headers.get === "function") {
|
|
68
|
+
const value2 = headers.get(import_core.CART_SESSION_HEADER);
|
|
69
|
+
const trimmed2 = value2?.trim();
|
|
70
|
+
return trimmed2 || void 0;
|
|
71
|
+
}
|
|
72
|
+
const raw = headers[import_core.CART_SESSION_HEADER];
|
|
73
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
74
|
+
const trimmed = value?.trim();
|
|
75
|
+
return trimmed || void 0;
|
|
76
|
+
}
|
|
77
|
+
var CART_SESSION_TTL_SECONDS = 60 * 60 * 24 * 7;
|
|
78
|
+
var InvalidCartSessionError = class extends Error {
|
|
79
|
+
constructor(message = "Invalid cart session") {
|
|
80
|
+
super(message);
|
|
81
|
+
this.name = "InvalidCartSessionError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
function signCartSession(payload, secret, nowSeconds = Math.floor(Date.now() / 1e3)) {
|
|
85
|
+
const body = {
|
|
86
|
+
customerId: payload.customerId,
|
|
87
|
+
email: payload.email,
|
|
88
|
+
exp: nowSeconds + CART_SESSION_TTL_SECONDS
|
|
89
|
+
};
|
|
90
|
+
const encoded = Buffer.from(JSON.stringify(body), "utf8").toString("base64url");
|
|
91
|
+
const signature = (0, import_node_crypto.createHmac)("sha256", secret).update(encoded).digest("base64url");
|
|
92
|
+
return `${encoded}.${signature}`;
|
|
93
|
+
}
|
|
94
|
+
function verifyCartSession(token, secret) {
|
|
95
|
+
const [encoded, signature] = token.split(".");
|
|
96
|
+
if (!encoded || !signature) {
|
|
97
|
+
throw new InvalidCartSessionError();
|
|
98
|
+
}
|
|
99
|
+
const expected = (0, import_node_crypto.createHmac)("sha256", secret).update(encoded).digest("base64url");
|
|
100
|
+
const actualBuffer = Buffer.from(signature);
|
|
101
|
+
const expectedBuffer = Buffer.from(expected);
|
|
102
|
+
if (actualBuffer.length !== expectedBuffer.length || !(0, import_node_crypto.timingSafeEqual)(actualBuffer, expectedBuffer)) {
|
|
103
|
+
throw new InvalidCartSessionError();
|
|
104
|
+
}
|
|
105
|
+
let payload;
|
|
106
|
+
try {
|
|
107
|
+
payload = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
108
|
+
} catch {
|
|
109
|
+
throw new InvalidCartSessionError();
|
|
110
|
+
}
|
|
111
|
+
if (typeof payload.customerId !== "string" || !payload.customerId || typeof payload.email !== "string" || typeof payload.exp !== "number") {
|
|
112
|
+
throw new InvalidCartSessionError();
|
|
113
|
+
}
|
|
114
|
+
if (payload.exp < Math.floor(Date.now() / 1e3)) {
|
|
115
|
+
throw new InvalidCartSessionError("Cart session expired");
|
|
116
|
+
}
|
|
117
|
+
return payload;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/login-rate-limit.ts
|
|
121
|
+
var LOGIN_RATE_LIMIT_WINDOW_MS = 15 * 60 * 1e3;
|
|
122
|
+
var LOGIN_RATE_LIMIT_MAX_ATTEMPTS = 10;
|
|
123
|
+
var LOGIN_RATE_LIMIT_MESSAGE = "Too many login attempts. Try again later.";
|
|
124
|
+
var ORDER_RATE_LIMIT_WINDOW_MS = 15 * 60 * 1e3;
|
|
125
|
+
var ORDER_RATE_LIMIT_MAX_ATTEMPTS = 20;
|
|
126
|
+
var ORDER_RATE_LIMIT_MESSAGE = "Too many order attempts. Try again later.";
|
|
127
|
+
var PAYMENT_RATE_LIMIT_WINDOW_MS = 15 * 60 * 1e3;
|
|
128
|
+
var PAYMENT_RATE_LIMIT_MAX_ATTEMPTS = 20;
|
|
129
|
+
var PAYMENT_RATE_LIMIT_MESSAGE = "Too many payment attempts. Try again later.";
|
|
130
|
+
var TooManyRequestsError = class extends Error {
|
|
131
|
+
retryAfterSeconds;
|
|
132
|
+
constructor(retryAfterSeconds, message = LOGIN_RATE_LIMIT_MESSAGE) {
|
|
133
|
+
super(message);
|
|
134
|
+
this.name = "TooManyRequestsError";
|
|
135
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
function createLoginAttemptLimiter(options = {}) {
|
|
139
|
+
const windowMs = options.windowMs ?? LOGIN_RATE_LIMIT_WINDOW_MS;
|
|
140
|
+
const limit = options.limit ?? LOGIN_RATE_LIMIT_MAX_ATTEMPTS;
|
|
141
|
+
const attempts = /* @__PURE__ */ new Map();
|
|
142
|
+
return {
|
|
143
|
+
consume(key) {
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
const current = attempts.get(key);
|
|
146
|
+
if (!current || current.resetAt <= now) {
|
|
147
|
+
attempts.set(key, { count: 1, resetAt: now + windowMs });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (current.count >= limit) {
|
|
151
|
+
throw new TooManyRequestsError(Math.max(1, Math.ceil((current.resetAt - now) / 1e3)));
|
|
152
|
+
}
|
|
153
|
+
current.count += 1;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function clientKeyFromWebHeaders(headers) {
|
|
158
|
+
const forwarded = headers.get("x-forwarded-for");
|
|
159
|
+
if (forwarded) {
|
|
160
|
+
const first = forwarded.split(",")[0]?.trim();
|
|
161
|
+
if (first) {
|
|
162
|
+
return first;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const realIp = headers.get("x-real-ip")?.trim();
|
|
166
|
+
return realIp || "local";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/route-actions.ts
|
|
170
|
+
var import_core4 = require("@commerce-ai-tool/core");
|
|
171
|
+
|
|
172
|
+
// src/observability/langfuse.ts
|
|
173
|
+
var import_core2 = require("@commerce-ai-tool/core");
|
|
174
|
+
|
|
175
|
+
// src/observability/flush.ts
|
|
176
|
+
var FLUSH_GLOBAL_KEY = "__commerceAiToolLangfuseFlush__";
|
|
177
|
+
async function flushLangfuse() {
|
|
178
|
+
const hasKeys = Boolean(process.env.LANGFUSE_PUBLIC_KEY && process.env.LANGFUSE_SECRET_KEY);
|
|
179
|
+
const registeredFlush = globalThis[FLUSH_GLOBAL_KEY];
|
|
180
|
+
if (!hasKeys || !registeredFlush) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
await registeredFlush();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/observability/langfuse.ts
|
|
187
|
+
async function withRequestSpan(name, attributes, fn) {
|
|
188
|
+
return (0, import_core2.withPipelineSpan)(name, attributes, async (span) => {
|
|
189
|
+
try {
|
|
190
|
+
const result = await fn();
|
|
191
|
+
span?.update({ output: summarizeResult(result) });
|
|
192
|
+
return result;
|
|
193
|
+
} finally {
|
|
194
|
+
await flushLangfuse();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
async function withOptionalRequestSpan(enabled, name, attributes, fn) {
|
|
199
|
+
if (!enabled) {
|
|
200
|
+
return fn();
|
|
201
|
+
}
|
|
202
|
+
return withRequestSpan(name, attributes, fn);
|
|
203
|
+
}
|
|
204
|
+
function summarizeResult(result) {
|
|
205
|
+
if (!result || typeof result !== "object") {
|
|
206
|
+
return void 0;
|
|
207
|
+
}
|
|
208
|
+
const record = result;
|
|
209
|
+
if ("products" in record || "meta" in record) {
|
|
210
|
+
return {
|
|
211
|
+
total: record.meta?.total,
|
|
212
|
+
productCount: Array.isArray(record.products) ? record.products.length : void 0,
|
|
213
|
+
traceId: record.meta?.traceId,
|
|
214
|
+
hasTranscript: Boolean(record.transcript),
|
|
215
|
+
transcript: typeof record.transcript === "string" ? record.transcript : void 0,
|
|
216
|
+
searchTerms: record.meta?.searchTerms,
|
|
217
|
+
interpretation: record.meta?.queryInterpretation ?? record.interpretation
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (Buffer.isBuffer(result)) {
|
|
221
|
+
return { byteLength: result.byteLength };
|
|
222
|
+
}
|
|
223
|
+
return void 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/utils/log-error.ts
|
|
227
|
+
function isDevLoggingEnabled() {
|
|
228
|
+
return process.env.NODE_ENV !== "production" || process.env.CAT_DEBUG === "true";
|
|
229
|
+
}
|
|
230
|
+
function formatError(error) {
|
|
231
|
+
if (error instanceof Error) {
|
|
232
|
+
return error.message;
|
|
233
|
+
}
|
|
234
|
+
if (typeof error === "string") {
|
|
235
|
+
return error;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
return JSON.stringify(error);
|
|
239
|
+
} catch {
|
|
240
|
+
return String(error);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function logServerError(handler, error, context) {
|
|
244
|
+
if (!isDevLoggingEnabled()) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const prefix = `[commerce-ai-tool/server] ${handler}`;
|
|
248
|
+
console.error(`${prefix} failed: ${formatError(error)}`);
|
|
249
|
+
if (context && Object.keys(context).length > 0) {
|
|
250
|
+
console.error(`${prefix} context:`, context);
|
|
251
|
+
}
|
|
252
|
+
if (error instanceof Error && error.stack) {
|
|
253
|
+
console.error(error.stack);
|
|
254
|
+
}
|
|
255
|
+
if (error instanceof Error && error.cause) {
|
|
256
|
+
console.error(`${prefix} caused by:`, error.cause);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function logServerWarning(handler, message, context) {
|
|
260
|
+
if (!isDevLoggingEnabled()) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const prefix = `[commerce-ai-tool/server] ${handler}`;
|
|
264
|
+
console.warn(`${prefix}: ${message}`);
|
|
265
|
+
if (context && Object.keys(context).length > 0) {
|
|
266
|
+
console.warn(`${prefix} context:`, context);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// src/utils/locale.ts
|
|
271
|
+
function parseSearchLocaleOptions(fields) {
|
|
272
|
+
return {
|
|
273
|
+
queryLocale: fields.queryLocale ?? fields.locale,
|
|
274
|
+
catalogLocale: fields.catalogLocale,
|
|
275
|
+
locale: fields.locale
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/route-actions.ts
|
|
280
|
+
function parseOptionalBoolean(value) {
|
|
281
|
+
if (value === "true") {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
if (value === "false") {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
return void 0;
|
|
288
|
+
}
|
|
289
|
+
var ValidationError = class extends Error {
|
|
290
|
+
constructor(message) {
|
|
291
|
+
super(message);
|
|
292
|
+
this.name = "ValidationError";
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
function mapRouteError(error, context, fallback, extra) {
|
|
296
|
+
if (error instanceof import_core3.SearchTimeoutError) {
|
|
297
|
+
return { message: error.message, status: 504 };
|
|
298
|
+
}
|
|
299
|
+
if (error instanceof ValidationError || error instanceof import_core3.CheckoutIncompleteError) {
|
|
300
|
+
return { message: error.message, status: 400 };
|
|
301
|
+
}
|
|
302
|
+
if (error instanceof import_core3.MissingPriceError) {
|
|
303
|
+
return { message: error.message, status: 400 };
|
|
304
|
+
}
|
|
305
|
+
if (error instanceof import_core3.PaymentDeclinedError) {
|
|
306
|
+
return { message: error.message, status: 402 };
|
|
307
|
+
}
|
|
308
|
+
if (error instanceof import_core3.PaymentNotConfiguredError) {
|
|
309
|
+
return { message: error.message, status: 400 };
|
|
310
|
+
}
|
|
311
|
+
if (error instanceof import_core3.CartNotFoundError || error instanceof import_core3.OrderNotFoundError) {
|
|
312
|
+
return { message: error.message, status: 404 };
|
|
313
|
+
}
|
|
314
|
+
if (error instanceof import_core3.CartAccessDeniedError) {
|
|
315
|
+
return { message: error.message, status: 403 };
|
|
316
|
+
}
|
|
317
|
+
if (error instanceof import_core3.InvalidCredentialsError || error instanceof InvalidCartSessionError) {
|
|
318
|
+
return { message: error.message, status: 401 };
|
|
319
|
+
}
|
|
320
|
+
if (error instanceof TooManyRequestsError) {
|
|
321
|
+
return { message: error.message, status: 429 };
|
|
322
|
+
}
|
|
323
|
+
logServerError(context, error, extra);
|
|
324
|
+
return {
|
|
325
|
+
message: error instanceof Error ? error.message : fallback,
|
|
326
|
+
status: 500
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
async function executeSearch(server, body) {
|
|
330
|
+
if (!body.query?.trim()) {
|
|
331
|
+
throw new ValidationError("query is required");
|
|
332
|
+
}
|
|
333
|
+
if (body.refineQuery && (!body.searchTerms || body.searchTerms.length === 0)) {
|
|
334
|
+
throw new ValidationError("searchTerms are required for a refinement");
|
|
335
|
+
}
|
|
336
|
+
const localeOptions = parseSearchLocaleOptions(body);
|
|
337
|
+
(0, import_core3.logSearchTrace)("handler", {
|
|
338
|
+
type: "text",
|
|
339
|
+
query: body.query,
|
|
340
|
+
queryLocale: localeOptions.queryLocale,
|
|
341
|
+
catalogLocale: localeOptions.catalogLocale
|
|
342
|
+
});
|
|
343
|
+
return withRequestSpan(
|
|
344
|
+
"commerce-ai.search",
|
|
345
|
+
{
|
|
346
|
+
input: { query: body.query, refineQuery: body.refineQuery },
|
|
347
|
+
metadata: {
|
|
348
|
+
searchType: "text",
|
|
349
|
+
queryLocale: localeOptions.queryLocale ?? "",
|
|
350
|
+
catalogLocale: localeOptions.catalogLocale ?? ""
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
() => server.orchestrator.searchByText({
|
|
354
|
+
query: body.query,
|
|
355
|
+
...localeOptions,
|
|
356
|
+
limit: body.limit,
|
|
357
|
+
filters: body.filters,
|
|
358
|
+
searchTerms: body.searchTerms,
|
|
359
|
+
sort: body.sort,
|
|
360
|
+
refineQuery: body.refineQuery,
|
|
361
|
+
includeFacets: body.includeFacets,
|
|
362
|
+
suggestedFacets: body.suggestedFacets,
|
|
363
|
+
enableMissions: body.enableMissions
|
|
364
|
+
})
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
async function executeFacetSchema(server, options) {
|
|
368
|
+
if (!server.orchestrator.getFacetSchema) {
|
|
369
|
+
throw new Error("Facet schema is not available");
|
|
370
|
+
}
|
|
371
|
+
return server.orchestrator.getFacetSchema(options);
|
|
372
|
+
}
|
|
373
|
+
async function executeSearchSuggestions(server, body) {
|
|
374
|
+
const normalizedQuery = (0, import_core4.normalizeSuggestionsPrefix)(body.query ?? "");
|
|
375
|
+
if (!normalizedQuery) {
|
|
376
|
+
throw new ValidationError("query must be at least 2 characters");
|
|
377
|
+
}
|
|
378
|
+
const localeOptions = parseSearchLocaleOptions(body);
|
|
379
|
+
(0, import_core3.logSearchTrace)("handler", {
|
|
380
|
+
type: "suggest",
|
|
381
|
+
query: normalizedQuery,
|
|
382
|
+
queryLocale: localeOptions.queryLocale,
|
|
383
|
+
catalogLocale: localeOptions.catalogLocale
|
|
384
|
+
});
|
|
385
|
+
return withOptionalRequestSpan(
|
|
386
|
+
(0, import_core2.shouldTraceSuggestions)(),
|
|
387
|
+
"commerce-ai.search.suggestions",
|
|
388
|
+
{
|
|
389
|
+
input: { query: normalizedQuery },
|
|
390
|
+
metadata: {
|
|
391
|
+
searchType: "suggest",
|
|
392
|
+
queryLocale: localeOptions.queryLocale ?? "",
|
|
393
|
+
catalogLocale: localeOptions.catalogLocale ?? ""
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
async () => {
|
|
397
|
+
const result = await server.orchestrator.suggestByText({
|
|
398
|
+
query: normalizedQuery,
|
|
399
|
+
...localeOptions,
|
|
400
|
+
limit: (0, import_core4.clampSuggestionsLimit)(body.limit)
|
|
401
|
+
});
|
|
402
|
+
if (!(0, import_core2.shouldTraceSuggestions)() && result.aiFallbackUsed && (0, import_core2.isLangfuseEnabled)()) {
|
|
403
|
+
await flushLangfuse();
|
|
404
|
+
}
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
async function executeSearchVoice(server, fields, file) {
|
|
410
|
+
if (!file) {
|
|
411
|
+
throw new ValidationError("audio file is required");
|
|
412
|
+
}
|
|
413
|
+
const localeOptions = parseSearchLocaleOptions(fields);
|
|
414
|
+
(0, import_core3.logSearchTrace)("handler", {
|
|
415
|
+
type: "voice",
|
|
416
|
+
mimeType: file.mimeType,
|
|
417
|
+
queryLocale: localeOptions.queryLocale,
|
|
418
|
+
catalogLocale: localeOptions.catalogLocale
|
|
419
|
+
});
|
|
420
|
+
return withRequestSpan(
|
|
421
|
+
"commerce-ai.search.voice",
|
|
422
|
+
{
|
|
423
|
+
input: { audio: (0, import_core2.redactBinaryInput)(file.mimeType, file.buffer) },
|
|
424
|
+
metadata: {
|
|
425
|
+
searchType: "voice",
|
|
426
|
+
queryLocale: localeOptions.queryLocale ?? "",
|
|
427
|
+
catalogLocale: localeOptions.catalogLocale ?? ""
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
async () => {
|
|
431
|
+
const result = await server.orchestrator.searchByVoice(
|
|
432
|
+
new Uint8Array(file.buffer),
|
|
433
|
+
file.mimeType,
|
|
434
|
+
{
|
|
435
|
+
...localeOptions,
|
|
436
|
+
limit: fields.limit ? Number(fields.limit) : void 0,
|
|
437
|
+
enableTts: fields.enableTts !== "false",
|
|
438
|
+
enableMissions: parseOptionalBoolean(fields.enableMissions)
|
|
439
|
+
}
|
|
440
|
+
);
|
|
441
|
+
const blockingTts = fields.blockingTts === "true";
|
|
442
|
+
let audioSummary;
|
|
443
|
+
if (blockingTts && result.ttsText) {
|
|
444
|
+
try {
|
|
445
|
+
const audio = await server.synthesizeSpeech(result.ttsText);
|
|
446
|
+
audioSummary = audio.toString("base64");
|
|
447
|
+
} catch (error) {
|
|
448
|
+
logServerWarning("searchVoice", "TTS summary skipped", {
|
|
449
|
+
reason: error instanceof Error ? error.message : "unknown"
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return {
|
|
454
|
+
transcript: result.transcript,
|
|
455
|
+
enhancedQuery: result.enhancedQuery,
|
|
456
|
+
products: result.products,
|
|
457
|
+
meta: result.meta,
|
|
458
|
+
mission: result.mission,
|
|
459
|
+
ttsText: result.ttsText,
|
|
460
|
+
audioSummary,
|
|
461
|
+
ttsPending: Boolean(result.ttsText && !audioSummary)
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
async function executeSearchImage(server, fields, file) {
|
|
467
|
+
if (!file) {
|
|
468
|
+
throw new ValidationError("image file is required");
|
|
469
|
+
}
|
|
470
|
+
const localeOptions = parseSearchLocaleOptions(fields);
|
|
471
|
+
(0, import_core3.logSearchTrace)("handler", {
|
|
472
|
+
type: "image",
|
|
473
|
+
mimeType: file.mimeType,
|
|
474
|
+
queryLocale: localeOptions.queryLocale,
|
|
475
|
+
catalogLocale: localeOptions.catalogLocale
|
|
476
|
+
});
|
|
477
|
+
return withRequestSpan(
|
|
478
|
+
"commerce-ai.search.image",
|
|
479
|
+
{
|
|
480
|
+
input: { image: (0, import_core2.redactBinaryInput)(file.mimeType, file.buffer) },
|
|
481
|
+
metadata: {
|
|
482
|
+
searchType: "image",
|
|
483
|
+
queryLocale: localeOptions.queryLocale ?? "",
|
|
484
|
+
catalogLocale: localeOptions.catalogLocale ?? ""
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
() => server.orchestrator.searchByImage(new Uint8Array(file.buffer), file.mimeType, {
|
|
488
|
+
...localeOptions,
|
|
489
|
+
limit: fields.limit ? Number(fields.limit) : void 0,
|
|
490
|
+
enableMissions: parseOptionalBoolean(fields.enableMissions)
|
|
491
|
+
})
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
async function executeTts(server, text) {
|
|
495
|
+
if (!text.trim()) {
|
|
496
|
+
throw new ValidationError("text is required");
|
|
497
|
+
}
|
|
498
|
+
return withRequestSpan(
|
|
499
|
+
"commerce-ai.tts",
|
|
500
|
+
{
|
|
501
|
+
input: { textLength: text.length },
|
|
502
|
+
metadata: { searchType: "tts" }
|
|
503
|
+
},
|
|
504
|
+
() => server.synthesizeSpeech(text)
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/cart-actions.ts
|
|
509
|
+
var import_core5 = require("@commerce-ai-tool/core");
|
|
510
|
+
var ANONYMOUS_ID_MAX_LENGTH = 128;
|
|
511
|
+
function requiredAnonymousId(value) {
|
|
512
|
+
const anonymousId = requiredString(value, "anonymousId");
|
|
513
|
+
if (anonymousId.length > ANONYMOUS_ID_MAX_LENGTH) {
|
|
514
|
+
throw new ValidationError("anonymousId is too long");
|
|
515
|
+
}
|
|
516
|
+
return anonymousId;
|
|
517
|
+
}
|
|
518
|
+
function optionalAnonymousId(value) {
|
|
519
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
520
|
+
return void 0;
|
|
521
|
+
}
|
|
522
|
+
if (value.trim().length > ANONYMOUS_ID_MAX_LENGTH) {
|
|
523
|
+
throw new ValidationError("anonymousId is too long");
|
|
524
|
+
}
|
|
525
|
+
return value.trim();
|
|
526
|
+
}
|
|
527
|
+
function requiredString(value, field) {
|
|
528
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
529
|
+
throw new ValidationError(`${field} is required`);
|
|
530
|
+
}
|
|
531
|
+
return value.trim();
|
|
532
|
+
}
|
|
533
|
+
function optionalString(value) {
|
|
534
|
+
if (typeof value !== "string") {
|
|
535
|
+
return void 0;
|
|
536
|
+
}
|
|
537
|
+
const trimmed = value.trim();
|
|
538
|
+
return trimmed || void 0;
|
|
539
|
+
}
|
|
540
|
+
function optionalNumber(value) {
|
|
541
|
+
if (value === void 0 || value === null || value === "") {
|
|
542
|
+
return void 0;
|
|
543
|
+
}
|
|
544
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
545
|
+
if (!Number.isFinite(parsed)) {
|
|
546
|
+
throw new ValidationError("quantity must be a number");
|
|
547
|
+
}
|
|
548
|
+
return parsed;
|
|
549
|
+
}
|
|
550
|
+
function resolveLocale(server, catalogLocale) {
|
|
551
|
+
return catalogLocale?.trim() || server.cartDefaults.catalogLocale;
|
|
552
|
+
}
|
|
553
|
+
function resolveCartIdentity(server, sessionToken, anonymousId) {
|
|
554
|
+
const token = optionalString(sessionToken);
|
|
555
|
+
if (token) {
|
|
556
|
+
const session = verifyCartSession(token, server.cartSessionSecret);
|
|
557
|
+
return { customerId: session.customerId };
|
|
558
|
+
}
|
|
559
|
+
return { anonymousId: requiredAnonymousId(anonymousId) };
|
|
560
|
+
}
|
|
561
|
+
async function executeGetCart(server, body) {
|
|
562
|
+
const identity = resolveCartIdentity(server, body.sessionToken, body.anonymousId);
|
|
563
|
+
const locale = resolveLocale(server, body.catalogLocale);
|
|
564
|
+
const cart = identity.customerId ? await server.commercetools.getCustomerCart(identity.customerId, locale) : await server.commercetools.getCart(identity.anonymousId ?? "", locale);
|
|
565
|
+
return { cart };
|
|
566
|
+
}
|
|
567
|
+
async function executeAddToCart(server, body) {
|
|
568
|
+
const identity = resolveCartIdentity(server, body.sessionToken, body.anonymousId);
|
|
569
|
+
const sku = optionalString(body.sku);
|
|
570
|
+
const productId = optionalString(body.productId);
|
|
571
|
+
if (!sku && !productId) {
|
|
572
|
+
throw new ValidationError("sku or productId is required");
|
|
573
|
+
}
|
|
574
|
+
const quantity = optionalNumber(body.quantity) ?? 1;
|
|
575
|
+
if (quantity < 1) {
|
|
576
|
+
throw new ValidationError("quantity must be at least 1");
|
|
577
|
+
}
|
|
578
|
+
const cart = await server.commercetools.addToCart({
|
|
579
|
+
anonymousId: identity.anonymousId,
|
|
580
|
+
customerId: identity.customerId,
|
|
581
|
+
sku,
|
|
582
|
+
productId,
|
|
583
|
+
variantId: body.variantId,
|
|
584
|
+
quantity,
|
|
585
|
+
currency: optionalString(body.currency) ?? server.cartDefaults.currency,
|
|
586
|
+
country: optionalString(body.country) ?? server.cartDefaults.country,
|
|
587
|
+
catalogLocale: resolveLocale(server, body.catalogLocale),
|
|
588
|
+
cartId: optionalString(body.cartId)
|
|
589
|
+
});
|
|
590
|
+
return { cart };
|
|
591
|
+
}
|
|
592
|
+
var MAX_ADD_ITEMS = 20;
|
|
593
|
+
async function executeAddItemsToCart(server, body) {
|
|
594
|
+
const identity = resolveCartIdentity(server, body.sessionToken, body.anonymousId);
|
|
595
|
+
if (!Array.isArray(body.items) || body.items.length === 0) {
|
|
596
|
+
throw new ValidationError("items is required");
|
|
597
|
+
}
|
|
598
|
+
if (body.items.length > MAX_ADD_ITEMS) {
|
|
599
|
+
throw new ValidationError(`items must not exceed ${MAX_ADD_ITEMS}`);
|
|
600
|
+
}
|
|
601
|
+
const items = body.items.map((item, index) => {
|
|
602
|
+
const sku = optionalString(item.sku);
|
|
603
|
+
const productId = optionalString(item.productId);
|
|
604
|
+
if (!sku && !productId) {
|
|
605
|
+
throw new ValidationError(`items[${index}]: sku or productId is required`);
|
|
606
|
+
}
|
|
607
|
+
const quantity = optionalNumber(item.quantity) ?? 1;
|
|
608
|
+
if (quantity < 1) {
|
|
609
|
+
throw new ValidationError(`items[${index}]: quantity must be at least 1`);
|
|
610
|
+
}
|
|
611
|
+
if (quantity > import_core5.MAX_LINE_ITEM_QUANTITY) {
|
|
612
|
+
throw new ValidationError(
|
|
613
|
+
`items[${index}]: quantity must be at most ${import_core5.MAX_LINE_ITEM_QUANTITY}`
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
sku,
|
|
618
|
+
productId,
|
|
619
|
+
variantId: item.variantId,
|
|
620
|
+
quantity
|
|
621
|
+
};
|
|
622
|
+
});
|
|
623
|
+
const cart = await server.commercetools.addItemsToCart({
|
|
624
|
+
anonymousId: identity.anonymousId,
|
|
625
|
+
customerId: identity.customerId,
|
|
626
|
+
items,
|
|
627
|
+
currency: optionalString(body.currency) ?? server.cartDefaults.currency,
|
|
628
|
+
country: optionalString(body.country) ?? server.cartDefaults.country,
|
|
629
|
+
catalogLocale: resolveLocale(server, body.catalogLocale),
|
|
630
|
+
cartId: optionalString(body.cartId)
|
|
631
|
+
});
|
|
632
|
+
return { cart };
|
|
633
|
+
}
|
|
634
|
+
async function executeRemoveFromCart(server, body) {
|
|
635
|
+
const identity = resolveCartIdentity(server, body.sessionToken, body.anonymousId);
|
|
636
|
+
const lineItemId = requiredString(body.lineItemId, "lineItemId");
|
|
637
|
+
const cart = await server.commercetools.removeLineItem({
|
|
638
|
+
anonymousId: identity.anonymousId,
|
|
639
|
+
customerId: identity.customerId,
|
|
640
|
+
lineItemId,
|
|
641
|
+
cartId: optionalString(body.cartId),
|
|
642
|
+
cartVersion: body.cartVersion,
|
|
643
|
+
catalogLocale: resolveLocale(server, body.catalogLocale)
|
|
644
|
+
});
|
|
645
|
+
return { cart };
|
|
646
|
+
}
|
|
647
|
+
async function executeUpdateCartQuantity(server, body) {
|
|
648
|
+
const identity = resolveCartIdentity(server, body.sessionToken, body.anonymousId);
|
|
649
|
+
const lineItemId = requiredString(body.lineItemId, "lineItemId");
|
|
650
|
+
const quantity = optionalNumber(body.quantity);
|
|
651
|
+
if (quantity === void 0 || quantity < 0) {
|
|
652
|
+
throw new ValidationError("quantity must be a non-negative number");
|
|
653
|
+
}
|
|
654
|
+
const cart = await server.commercetools.changeLineItemQuantity({
|
|
655
|
+
anonymousId: identity.anonymousId,
|
|
656
|
+
customerId: identity.customerId,
|
|
657
|
+
lineItemId,
|
|
658
|
+
quantity,
|
|
659
|
+
cartId: optionalString(body.cartId),
|
|
660
|
+
cartVersion: body.cartVersion,
|
|
661
|
+
catalogLocale: resolveLocale(server, body.catalogLocale)
|
|
662
|
+
});
|
|
663
|
+
return { cart };
|
|
664
|
+
}
|
|
665
|
+
async function executeLogin(server, body) {
|
|
666
|
+
const email = requiredString(body.email, "email");
|
|
667
|
+
const password = requiredString(body.password, "password");
|
|
668
|
+
const locale = resolveLocale(server, body.catalogLocale);
|
|
669
|
+
const result = await server.commercetools.loginAndMerge({
|
|
670
|
+
email,
|
|
671
|
+
password,
|
|
672
|
+
anonymousId: optionalAnonymousId(body.anonymousId),
|
|
673
|
+
cartId: optionalString(body.cartId),
|
|
674
|
+
catalogLocale: locale
|
|
675
|
+
});
|
|
676
|
+
const sessionToken = signCartSession(
|
|
677
|
+
{ customerId: result.customer.id, email: result.customer.email },
|
|
678
|
+
server.cartSessionSecret
|
|
679
|
+
);
|
|
680
|
+
return {
|
|
681
|
+
cart: result.cart,
|
|
682
|
+
customer: result.customer,
|
|
683
|
+
sessionToken
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
async function executeLogout() {
|
|
687
|
+
return { cart: null, customer: null };
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/checkout-actions.ts
|
|
691
|
+
function parseAddress(value, field) {
|
|
692
|
+
if (!value || typeof value !== "object") {
|
|
693
|
+
throw new ValidationError(`${field} is required`);
|
|
694
|
+
}
|
|
695
|
+
const address = value;
|
|
696
|
+
const country = requiredString(address.country, `${field}.country`).toUpperCase();
|
|
697
|
+
if (!/^[A-Z]{2}$/.test(country)) {
|
|
698
|
+
throw new ValidationError(`${field}.country must be an ISO 3166-1 alpha-2 code`);
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
firstName: requiredString(address.firstName, `${field}.firstName`),
|
|
702
|
+
lastName: requiredString(address.lastName, `${field}.lastName`),
|
|
703
|
+
streetName: requiredString(address.streetName, `${field}.streetName`),
|
|
704
|
+
additionalStreetInfo: optionalString(address.additionalStreetInfo),
|
|
705
|
+
postalCode: requiredString(address.postalCode, `${field}.postalCode`),
|
|
706
|
+
city: requiredString(address.city, `${field}.city`),
|
|
707
|
+
region: optionalString(address.region),
|
|
708
|
+
country,
|
|
709
|
+
email: optionalString(address.email),
|
|
710
|
+
phone: optionalString(address.phone)
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function checkoutIdentity(server, body) {
|
|
714
|
+
return {
|
|
715
|
+
...resolveCartIdentity(server, body.sessionToken, body.anonymousId),
|
|
716
|
+
cartId: optionalString(body.cartId),
|
|
717
|
+
catalogLocale: resolveLocale(server, body.catalogLocale)
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
async function executeSetCartAddresses(server, body) {
|
|
721
|
+
const cart = await server.commercetools.setCartAddresses({
|
|
722
|
+
...checkoutIdentity(server, body),
|
|
723
|
+
shippingAddress: parseAddress(body.shippingAddress, "shippingAddress"),
|
|
724
|
+
billingAddress: body.billingAddress ? parseAddress(body.billingAddress, "billingAddress") : void 0
|
|
725
|
+
});
|
|
726
|
+
return { cart };
|
|
727
|
+
}
|
|
728
|
+
async function executeGetShippingMethods(server, body) {
|
|
729
|
+
const shippingMethods = await server.commercetools.getShippingMethods(
|
|
730
|
+
checkoutIdentity(server, body)
|
|
731
|
+
);
|
|
732
|
+
return { shippingMethods };
|
|
733
|
+
}
|
|
734
|
+
async function executeSetShippingMethod(server, body) {
|
|
735
|
+
const cart = await server.commercetools.setShippingMethod({
|
|
736
|
+
...checkoutIdentity(server, body),
|
|
737
|
+
shippingMethodId: requiredString(body.shippingMethodId, "shippingMethodId")
|
|
738
|
+
});
|
|
739
|
+
return { cart };
|
|
740
|
+
}
|
|
741
|
+
async function executeCreateOrder(server, body) {
|
|
742
|
+
const order = await server.commercetools.createOrder({
|
|
743
|
+
...checkoutIdentity(server, body),
|
|
744
|
+
orderNumber: optionalString(body.orderNumber)
|
|
745
|
+
});
|
|
746
|
+
return { order };
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/payment-actions.ts
|
|
750
|
+
function paymentIdentity(server, body) {
|
|
751
|
+
return {
|
|
752
|
+
...resolveCartIdentity(server, body.sessionToken, body.anonymousId),
|
|
753
|
+
cartId: optionalString(body.cartId),
|
|
754
|
+
catalogLocale: resolveLocale(server, body.catalogLocale)
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
async function executeGetPaymentMethods(server, body) {
|
|
758
|
+
const identity = paymentIdentity(server, body);
|
|
759
|
+
const paymentMethods = await server.commercetools.listPaymentMethods({
|
|
760
|
+
locale: identity.catalogLocale,
|
|
761
|
+
country: server.cartDefaults.country
|
|
762
|
+
});
|
|
763
|
+
return { paymentMethods };
|
|
764
|
+
}
|
|
765
|
+
async function executeAuthorizePayment(server, body) {
|
|
766
|
+
return server.commercetools.authorizePayment({
|
|
767
|
+
...paymentIdentity(server, body),
|
|
768
|
+
method: requiredString(body.method, "method"),
|
|
769
|
+
orderNumber: optionalString(body.orderNumber)
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/order-actions.ts
|
|
774
|
+
async function executeGetOrder(server, body) {
|
|
775
|
+
const identity = resolveCartIdentity(server, body.sessionToken, body.anonymousId);
|
|
776
|
+
const catalogLocale = resolveLocale(server, optionalString(body.catalogLocale));
|
|
777
|
+
const anonymousId = identity.anonymousId ?? optionalString(body.anonymousId);
|
|
778
|
+
const orderNumber = optionalString(body.orderNumber);
|
|
779
|
+
if (!orderNumber) {
|
|
780
|
+
const orders = await server.commercetools.listOrders({
|
|
781
|
+
anonymousId,
|
|
782
|
+
customerId: identity.customerId,
|
|
783
|
+
catalogLocale
|
|
784
|
+
});
|
|
785
|
+
return { orders };
|
|
786
|
+
}
|
|
787
|
+
const order = await server.commercetools.getOrder({
|
|
788
|
+
orderNumber,
|
|
789
|
+
anonymousId,
|
|
790
|
+
customerId: identity.customerId,
|
|
791
|
+
catalogLocale
|
|
792
|
+
});
|
|
793
|
+
return { order };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/utils/multipart.ts
|
|
797
|
+
var import_busboy = __toESM(require("busboy"), 1);
|
|
798
|
+
async function parseMultipart(req) {
|
|
799
|
+
return new Promise((resolve, reject) => {
|
|
800
|
+
const fields = {};
|
|
801
|
+
let file;
|
|
802
|
+
const busboy = (0, import_busboy.default)({ headers: req.headers });
|
|
803
|
+
const chunks = [];
|
|
804
|
+
busboy.on("field", (name, value) => {
|
|
805
|
+
fields[name] = value;
|
|
806
|
+
});
|
|
807
|
+
busboy.on("file", (_name, stream, info) => {
|
|
808
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
809
|
+
stream.on("end", () => {
|
|
810
|
+
file = {
|
|
811
|
+
buffer: Buffer.concat(chunks),
|
|
812
|
+
mimeType: info.mimeType,
|
|
813
|
+
filename: info.filename
|
|
814
|
+
};
|
|
815
|
+
});
|
|
816
|
+
});
|
|
817
|
+
busboy.on("finish", () => resolve({ fields, file }));
|
|
818
|
+
busboy.on("error", reject);
|
|
819
|
+
req.pipe(busboy);
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
async function readJsonBody(req) {
|
|
823
|
+
const chunks = [];
|
|
824
|
+
for await (const chunk of req) {
|
|
825
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
826
|
+
}
|
|
827
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
828
|
+
return JSON.parse(raw);
|
|
829
|
+
}
|
|
830
|
+
async function parseMultipartRequest(req) {
|
|
831
|
+
const formData = await req.formData();
|
|
832
|
+
const fields = {};
|
|
833
|
+
let file;
|
|
834
|
+
for (const [key, value] of formData.entries()) {
|
|
835
|
+
if (typeof value === "string") {
|
|
836
|
+
fields[key] = value;
|
|
837
|
+
continue;
|
|
838
|
+
}
|
|
839
|
+
const buffer = Buffer.from(await value.arrayBuffer());
|
|
840
|
+
file = {
|
|
841
|
+
buffer,
|
|
842
|
+
mimeType: value.type || "application/octet-stream",
|
|
843
|
+
filename: value.name
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
return { fields, file };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/handlers.ts
|
|
850
|
+
function createHandlers(server) {
|
|
851
|
+
return {
|
|
852
|
+
async health() {
|
|
853
|
+
return jsonResponse({ status: "ok" });
|
|
854
|
+
},
|
|
855
|
+
async search(req) {
|
|
856
|
+
try {
|
|
857
|
+
const body = await readJsonBody(req);
|
|
858
|
+
const result = await executeSearch(server, body);
|
|
859
|
+
return jsonResponse(result);
|
|
860
|
+
} catch (error) {
|
|
861
|
+
const mapped = mapRouteError(error, "search", "Search failed");
|
|
862
|
+
return errorResponse(mapped.message, mapped.status);
|
|
863
|
+
}
|
|
864
|
+
},
|
|
865
|
+
async facetSchema() {
|
|
866
|
+
try {
|
|
867
|
+
return jsonResponse(await executeFacetSchema(server, void 0));
|
|
868
|
+
} catch (error) {
|
|
869
|
+
const mapped = mapRouteError(error, "facetSchema", "Facet schema failed");
|
|
870
|
+
return errorResponse(mapped.message, mapped.status);
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
async searchSuggestions(req) {
|
|
874
|
+
try {
|
|
875
|
+
const body = await readJsonBody(req);
|
|
876
|
+
const result = await executeSearchSuggestions(server, body);
|
|
877
|
+
return jsonResponse(result);
|
|
878
|
+
} catch (error) {
|
|
879
|
+
const mapped = mapRouteError(error, "searchSuggestions", "Suggestions failed");
|
|
880
|
+
return errorResponse(mapped.message, mapped.status);
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
async searchVoice(req) {
|
|
884
|
+
let fields = {};
|
|
885
|
+
let file;
|
|
886
|
+
try {
|
|
887
|
+
const parsed = await parseMultipart(req);
|
|
888
|
+
fields = parsed.fields;
|
|
889
|
+
file = parsed.file;
|
|
890
|
+
const result = await executeSearchVoice(server, fields, file);
|
|
891
|
+
return jsonResponse(result);
|
|
892
|
+
} catch (error) {
|
|
893
|
+
const mapped = mapRouteError(error, "searchVoice", "Voice search failed", {
|
|
894
|
+
queryLocale: fields.queryLocale ?? fields.locale,
|
|
895
|
+
catalogLocale: fields.catalogLocale,
|
|
896
|
+
mimeType: file?.mimeType,
|
|
897
|
+
size: file?.buffer.length
|
|
898
|
+
});
|
|
899
|
+
return errorResponse(mapped.message, mapped.status);
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
async searchImage(req) {
|
|
903
|
+
try {
|
|
904
|
+
const { fields, file } = await parseMultipart(req);
|
|
905
|
+
const result = await executeSearchImage(server, fields, file);
|
|
906
|
+
return jsonResponse(result);
|
|
907
|
+
} catch (error) {
|
|
908
|
+
const mapped = mapRouteError(error, "searchImage", "Image search failed");
|
|
909
|
+
return errorResponse(mapped.message, mapped.status);
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
async tts(req) {
|
|
913
|
+
try {
|
|
914
|
+
const body = await readJsonBody(req);
|
|
915
|
+
const audio = await executeTts(server, body.text);
|
|
916
|
+
return {
|
|
917
|
+
status: 200,
|
|
918
|
+
headers: { "Content-Type": "audio/mpeg" },
|
|
919
|
+
body: audio
|
|
920
|
+
};
|
|
921
|
+
} catch (error) {
|
|
922
|
+
const mapped = mapRouteError(error, "tts", "TTS failed");
|
|
923
|
+
return errorResponse(mapped.message, mapped.status);
|
|
924
|
+
}
|
|
925
|
+
},
|
|
926
|
+
async getCart(req) {
|
|
927
|
+
try {
|
|
928
|
+
const query = parseRequestQuery(req);
|
|
929
|
+
const result = await executeGetCart(server, {
|
|
930
|
+
anonymousId: query.anonymousId ?? "",
|
|
931
|
+
catalogLocale: query.catalogLocale,
|
|
932
|
+
sessionToken: readCartSessionHeader(req.headers)
|
|
933
|
+
});
|
|
934
|
+
return jsonResponse(result);
|
|
935
|
+
} catch (error) {
|
|
936
|
+
const mapped = mapRouteError(error, "getCart", "Get cart failed");
|
|
937
|
+
return errorResponse(mapped.message, mapped.status);
|
|
938
|
+
}
|
|
939
|
+
},
|
|
940
|
+
async addToCart(req) {
|
|
941
|
+
try {
|
|
942
|
+
const body = await readJsonBody(req);
|
|
943
|
+
const result = await executeAddToCart(server, body);
|
|
944
|
+
return jsonResponse(result);
|
|
945
|
+
} catch (error) {
|
|
946
|
+
const mapped = mapRouteError(error, "addToCart", "Add to cart failed");
|
|
947
|
+
return errorResponse(mapped.message, mapped.status);
|
|
948
|
+
}
|
|
949
|
+
},
|
|
950
|
+
async addItemsToCart(req) {
|
|
951
|
+
try {
|
|
952
|
+
const body = await readJsonBody(req);
|
|
953
|
+
const result = await executeAddItemsToCart(server, body);
|
|
954
|
+
return jsonResponse(result);
|
|
955
|
+
} catch (error) {
|
|
956
|
+
const mapped = mapRouteError(error, "addItemsToCart", "Add items to cart failed");
|
|
957
|
+
return errorResponse(mapped.message, mapped.status);
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
async removeFromCart(req) {
|
|
961
|
+
try {
|
|
962
|
+
const body = await readJsonBody(req);
|
|
963
|
+
const result = await executeRemoveFromCart(server, body);
|
|
964
|
+
return jsonResponse(result);
|
|
965
|
+
} catch (error) {
|
|
966
|
+
const mapped = mapRouteError(error, "removeFromCart", "Remove from cart failed");
|
|
967
|
+
return errorResponse(mapped.message, mapped.status);
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
async updateCartQuantity(req) {
|
|
971
|
+
try {
|
|
972
|
+
const body = await readJsonBody(req);
|
|
973
|
+
const result = await executeUpdateCartQuantity(server, body);
|
|
974
|
+
return jsonResponse(result);
|
|
975
|
+
} catch (error) {
|
|
976
|
+
const mapped = mapRouteError(error, "updateCartQuantity", "Update cart quantity failed");
|
|
977
|
+
return errorResponse(mapped.message, mapped.status);
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
async setCartAddresses(req) {
|
|
981
|
+
try {
|
|
982
|
+
const body = await readJsonBody(req);
|
|
983
|
+
return jsonResponse(await executeSetCartAddresses(server, body));
|
|
984
|
+
} catch (error) {
|
|
985
|
+
const mapped = mapRouteError(error, "setCartAddresses", "Set cart addresses failed");
|
|
986
|
+
return errorResponse(mapped.message, mapped.status);
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
async getShippingMethods(req) {
|
|
990
|
+
try {
|
|
991
|
+
const query = parseRequestQuery(req);
|
|
992
|
+
return jsonResponse(
|
|
993
|
+
await executeGetShippingMethods(server, {
|
|
994
|
+
anonymousId: query.anonymousId,
|
|
995
|
+
cartId: query.cartId,
|
|
996
|
+
catalogLocale: query.catalogLocale,
|
|
997
|
+
sessionToken: readCartSessionHeader(req.headers)
|
|
998
|
+
})
|
|
999
|
+
);
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
const mapped = mapRouteError(
|
|
1002
|
+
error,
|
|
1003
|
+
"getShippingMethods",
|
|
1004
|
+
"Get shipping methods failed"
|
|
1005
|
+
);
|
|
1006
|
+
return errorResponse(mapped.message, mapped.status);
|
|
1007
|
+
}
|
|
1008
|
+
},
|
|
1009
|
+
async setShippingMethod(req) {
|
|
1010
|
+
try {
|
|
1011
|
+
const body = await readJsonBody(req);
|
|
1012
|
+
return jsonResponse(await executeSetShippingMethod(server, body));
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
const mapped = mapRouteError(
|
|
1015
|
+
error,
|
|
1016
|
+
"setShippingMethod",
|
|
1017
|
+
"Set shipping method failed"
|
|
1018
|
+
);
|
|
1019
|
+
return errorResponse(mapped.message, mapped.status);
|
|
1020
|
+
}
|
|
1021
|
+
},
|
|
1022
|
+
async createOrder(req) {
|
|
1023
|
+
try {
|
|
1024
|
+
const body = await readJsonBody(req);
|
|
1025
|
+
return jsonResponse(await executeCreateOrder(server, body));
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
const mapped = mapRouteError(error, "createOrder", "Create order failed");
|
|
1028
|
+
return errorResponse(mapped.message, mapped.status);
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
async getPaymentMethods(req) {
|
|
1032
|
+
try {
|
|
1033
|
+
const query = parseRequestQuery(req);
|
|
1034
|
+
return jsonResponse(
|
|
1035
|
+
await executeGetPaymentMethods(server, {
|
|
1036
|
+
anonymousId: query.anonymousId,
|
|
1037
|
+
cartId: query.cartId,
|
|
1038
|
+
catalogLocale: query.catalogLocale,
|
|
1039
|
+
sessionToken: readCartSessionHeader(req.headers)
|
|
1040
|
+
})
|
|
1041
|
+
);
|
|
1042
|
+
} catch (error) {
|
|
1043
|
+
const mapped = mapRouteError(
|
|
1044
|
+
error,
|
|
1045
|
+
"getPaymentMethods",
|
|
1046
|
+
"Get payment methods failed"
|
|
1047
|
+
);
|
|
1048
|
+
return errorResponse(mapped.message, mapped.status);
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
async authorizePayment(req) {
|
|
1052
|
+
try {
|
|
1053
|
+
const body = await readJsonBody(req);
|
|
1054
|
+
return jsonResponse(await executeAuthorizePayment(server, body));
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
const mapped = mapRouteError(error, "authorizePayment", "Payment failed");
|
|
1057
|
+
return errorResponse(mapped.message, mapped.status);
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
async getOrder(req) {
|
|
1061
|
+
try {
|
|
1062
|
+
const query = parseRequestQuery(req);
|
|
1063
|
+
return jsonResponse(
|
|
1064
|
+
await executeGetOrder(server, {
|
|
1065
|
+
orderNumber: query.orderNumber ?? "",
|
|
1066
|
+
anonymousId: query.anonymousId,
|
|
1067
|
+
catalogLocale: query.catalogLocale,
|
|
1068
|
+
sessionToken: readCartSessionHeader(req.headers)
|
|
1069
|
+
})
|
|
1070
|
+
);
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
const mapped = mapRouteError(error, "getOrder", "Get order failed");
|
|
1073
|
+
return errorResponse(mapped.message, mapped.status);
|
|
1074
|
+
}
|
|
1075
|
+
},
|
|
1076
|
+
async login(req) {
|
|
1077
|
+
try {
|
|
1078
|
+
const body = await readJsonBody(req);
|
|
1079
|
+
const result = await executeLogin(server, body);
|
|
1080
|
+
return jsonResponse(result);
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
const mapped = mapRouteError(error, "login", "Login failed");
|
|
1083
|
+
return errorResponse(mapped.message, mapped.status);
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
1086
|
+
async logout() {
|
|
1087
|
+
try {
|
|
1088
|
+
const result = await executeLogout();
|
|
1089
|
+
return jsonResponse(result);
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
const mapped = mapRouteError(error, "logout", "Logout failed");
|
|
1092
|
+
return errorResponse(mapped.message, mapped.status);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
function parseRequestQuery(req) {
|
|
1098
|
+
const query = {};
|
|
1099
|
+
const url = new URL(req.url ?? "", "http://localhost");
|
|
1100
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
1101
|
+
query[key] = value;
|
|
1102
|
+
}
|
|
1103
|
+
return query;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// src/server.ts
|
|
1107
|
+
var import_elevenlabs_js = require("@elevenlabs/elevenlabs-js");
|
|
1108
|
+
var import_core6 = require("@commerce-ai-tool/core");
|
|
1109
|
+
function createCommerceAIServer(options) {
|
|
1110
|
+
const { config } = options;
|
|
1111
|
+
const voiceMode = resolveVoiceMode(config);
|
|
1112
|
+
const elevenlabs = config.elevenlabs ? createElevenLabsClient(config.elevenlabs) : null;
|
|
1113
|
+
const commercetools = (0, import_core6.createCommercetoolsClient)(config.commercetools, {
|
|
1114
|
+
payments: config.payments
|
|
1115
|
+
});
|
|
1116
|
+
const orchestrator = (0, import_core6.createSearchOrchestrator)({
|
|
1117
|
+
config,
|
|
1118
|
+
commercetoolsClient: commercetools,
|
|
1119
|
+
transcribeAudio: voiceMode === "elevenlabs-stt" && elevenlabs ? async (audio, mimeType) => withElevenLabsSpan(
|
|
1120
|
+
"elevenlabs.stt",
|
|
1121
|
+
{ audio: (0, import_core6.redactBinaryInput)(mimeType, audio) },
|
|
1122
|
+
() => transcribeWithElevenLabs(elevenlabs, audio, mimeType, config)
|
|
1123
|
+
) : void 0
|
|
1124
|
+
});
|
|
1125
|
+
return {
|
|
1126
|
+
orchestrator,
|
|
1127
|
+
commercetools,
|
|
1128
|
+
cartDefaults: {
|
|
1129
|
+
currency: config.defaults?.currency ?? "EUR",
|
|
1130
|
+
country: config.defaults?.country,
|
|
1131
|
+
catalogLocale: config.defaults?.catalogLocale ?? config.defaults?.locale ?? "en"
|
|
1132
|
+
},
|
|
1133
|
+
cartSessionSecret: process.env.CAT_CART_SESSION_SECRET?.trim() || config.commercetools.clientSecret,
|
|
1134
|
+
async transcribeAudio(audio, mimeType) {
|
|
1135
|
+
if (!elevenlabs) {
|
|
1136
|
+
throw new Error("ElevenLabs is not configured");
|
|
1137
|
+
}
|
|
1138
|
+
return withElevenLabsSpan(
|
|
1139
|
+
"elevenlabs.stt",
|
|
1140
|
+
{ audio: (0, import_core6.redactBinaryInput)(mimeType, audio) },
|
|
1141
|
+
() => transcribeWithElevenLabs(elevenlabs, audio, mimeType, config)
|
|
1142
|
+
);
|
|
1143
|
+
},
|
|
1144
|
+
async synthesizeSpeech(text) {
|
|
1145
|
+
if (!elevenlabs) {
|
|
1146
|
+
throw new Error("ElevenLabs is not configured");
|
|
1147
|
+
}
|
|
1148
|
+
return withElevenLabsSpan("elevenlabs.tts", { input: { textLength: text.length } }, async () => {
|
|
1149
|
+
const voiceId = config.elevenlabs?.ttsVoiceId ?? "JBFqnCBsd6RMkjVDRZzb";
|
|
1150
|
+
const audio = await elevenlabs.textToSpeech.convert(voiceId, {
|
|
1151
|
+
text,
|
|
1152
|
+
modelId: config.elevenlabs?.ttsModel ?? "eleven_multilingual_v2",
|
|
1153
|
+
outputFormat: "mp3_44100_128"
|
|
1154
|
+
});
|
|
1155
|
+
const chunks = [];
|
|
1156
|
+
for await (const chunk of audio) {
|
|
1157
|
+
chunks.push(chunk);
|
|
1158
|
+
}
|
|
1159
|
+
return Buffer.concat(chunks);
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
async function withElevenLabsSpan(name, attributes, fn) {
|
|
1165
|
+
return (0, import_core6.withPipelineSpan)(
|
|
1166
|
+
name,
|
|
1167
|
+
{
|
|
1168
|
+
input: attributes.input ?? attributes.audio,
|
|
1169
|
+
metadata: { vendor: "elevenlabs" }
|
|
1170
|
+
},
|
|
1171
|
+
async (span) => {
|
|
1172
|
+
const result = await fn();
|
|
1173
|
+
if (typeof result === "string") {
|
|
1174
|
+
span?.update({ output: { transcriptLength: result.length } });
|
|
1175
|
+
} else if (Buffer.isBuffer(result)) {
|
|
1176
|
+
span?.update({ output: { byteLength: result.byteLength } });
|
|
1177
|
+
}
|
|
1178
|
+
return result;
|
|
1179
|
+
}
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
function createElevenLabsClient(config) {
|
|
1183
|
+
return new import_elevenlabs_js.ElevenLabsClient({ apiKey: config.apiKey });
|
|
1184
|
+
}
|
|
1185
|
+
async function transcribeWithElevenLabs(elevenlabs, audio, mimeType, config) {
|
|
1186
|
+
const blob = new Blob([audio], { type: mimeType });
|
|
1187
|
+
const result = await elevenlabs.speechToText.convert({
|
|
1188
|
+
file: blob,
|
|
1189
|
+
modelId: getSttModelId(config.elevenlabs?.sttModel)
|
|
1190
|
+
});
|
|
1191
|
+
if (typeof result === "string") {
|
|
1192
|
+
return result;
|
|
1193
|
+
}
|
|
1194
|
+
if ("text" in result && typeof result.text === "string") {
|
|
1195
|
+
return result.text;
|
|
1196
|
+
}
|
|
1197
|
+
throw new Error("Failed to transcribe audio");
|
|
1198
|
+
}
|
|
1199
|
+
function resolveVoiceMode(config) {
|
|
1200
|
+
if (config.voiceMode) {
|
|
1201
|
+
return config.voiceMode;
|
|
1202
|
+
}
|
|
1203
|
+
if (config.ai.provider === "openrouter") {
|
|
1204
|
+
return "openrouter-audio";
|
|
1205
|
+
}
|
|
1206
|
+
return "elevenlabs-stt";
|
|
1207
|
+
}
|
|
1208
|
+
function getSttModelId(model) {
|
|
1209
|
+
return model === "scribe_v2" ? "scribe_v2" : "scribe_v2";
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
// src/next.ts
|
|
1213
|
+
var loginLimiter = createLoginAttemptLimiter();
|
|
1214
|
+
var orderLimiter = createLoginAttemptLimiter({
|
|
1215
|
+
windowMs: ORDER_RATE_LIMIT_WINDOW_MS,
|
|
1216
|
+
limit: ORDER_RATE_LIMIT_MAX_ATTEMPTS
|
|
1217
|
+
});
|
|
1218
|
+
var paymentLimiter = createLoginAttemptLimiter({
|
|
1219
|
+
windowMs: PAYMENT_RATE_LIMIT_WINDOW_MS,
|
|
1220
|
+
limit: PAYMENT_RATE_LIMIT_MAX_ATTEMPTS
|
|
1221
|
+
});
|
|
1222
|
+
function createNextHandlers(config) {
|
|
1223
|
+
const server = createCommerceAIServer({ config });
|
|
1224
|
+
const handlers = createHandlers(server);
|
|
1225
|
+
return {
|
|
1226
|
+
health: async () => toWebResponse(await handlers.health()),
|
|
1227
|
+
facetSchema: async () => {
|
|
1228
|
+
try {
|
|
1229
|
+
return Response.json(await executeFacetSchema(server, void 0));
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
const mapped = mapRouteError(error, "facetSchema", "Facet schema failed");
|
|
1232
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1233
|
+
}
|
|
1234
|
+
},
|
|
1235
|
+
search: async (req) => {
|
|
1236
|
+
try {
|
|
1237
|
+
const body = await req.json();
|
|
1238
|
+
const result = await executeSearch(server, body);
|
|
1239
|
+
return Response.json(result);
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
const mapped = mapRouteError(error, "search", "Search failed");
|
|
1242
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1243
|
+
}
|
|
1244
|
+
},
|
|
1245
|
+
searchSuggestions: async (req) => {
|
|
1246
|
+
try {
|
|
1247
|
+
const body = await req.json();
|
|
1248
|
+
const result = await executeSearchSuggestions(server, body);
|
|
1249
|
+
return Response.json(result);
|
|
1250
|
+
} catch (error) {
|
|
1251
|
+
const mapped = mapRouteError(error, "searchSuggestions", "Suggestions failed");
|
|
1252
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1253
|
+
}
|
|
1254
|
+
},
|
|
1255
|
+
searchVoice: async (req) => {
|
|
1256
|
+
let fields = {};
|
|
1257
|
+
let file;
|
|
1258
|
+
try {
|
|
1259
|
+
const parsed = await parseMultipartRequest(req);
|
|
1260
|
+
fields = parsed.fields;
|
|
1261
|
+
file = parsed.file;
|
|
1262
|
+
const result = await executeSearchVoice(server, fields, file);
|
|
1263
|
+
return Response.json(result);
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
const mapped = mapRouteError(error, "searchVoice", "Voice search failed", {
|
|
1266
|
+
queryLocale: fields.queryLocale ?? fields.locale,
|
|
1267
|
+
catalogLocale: fields.catalogLocale,
|
|
1268
|
+
mimeType: file?.mimeType,
|
|
1269
|
+
size: file?.buffer.length
|
|
1270
|
+
});
|
|
1271
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1272
|
+
}
|
|
1273
|
+
},
|
|
1274
|
+
searchImage: async (req) => {
|
|
1275
|
+
try {
|
|
1276
|
+
const { fields, file } = await parseMultipartRequest(req);
|
|
1277
|
+
const result = await executeSearchImage(server, fields, file);
|
|
1278
|
+
return Response.json(result);
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
const mapped = mapRouteError(error, "searchImage", "Image search failed");
|
|
1281
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1282
|
+
}
|
|
1283
|
+
},
|
|
1284
|
+
tts: async (req) => {
|
|
1285
|
+
try {
|
|
1286
|
+
const body = await req.json();
|
|
1287
|
+
const audio = await executeTts(server, body.text);
|
|
1288
|
+
return new Response(audio, {
|
|
1289
|
+
headers: { "Content-Type": "audio/mpeg" }
|
|
1290
|
+
});
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
const mapped = mapRouteError(error, "tts", "TTS failed");
|
|
1293
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
getCart: async (req) => {
|
|
1297
|
+
try {
|
|
1298
|
+
const url = new URL(req.url);
|
|
1299
|
+
const result = await executeGetCart(server, {
|
|
1300
|
+
anonymousId: url.searchParams.get("anonymousId") ?? "",
|
|
1301
|
+
catalogLocale: url.searchParams.get("catalogLocale") ?? void 0,
|
|
1302
|
+
sessionToken: readCartSessionHeader(req.headers)
|
|
1303
|
+
});
|
|
1304
|
+
return Response.json(result);
|
|
1305
|
+
} catch (error) {
|
|
1306
|
+
const mapped = mapRouteError(error, "getCart", "Get cart failed");
|
|
1307
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1308
|
+
}
|
|
1309
|
+
},
|
|
1310
|
+
addToCart: async (req) => {
|
|
1311
|
+
try {
|
|
1312
|
+
const result = await executeAddToCart(server, await req.json());
|
|
1313
|
+
return Response.json(result);
|
|
1314
|
+
} catch (error) {
|
|
1315
|
+
const mapped = mapRouteError(error, "addToCart", "Add to cart failed");
|
|
1316
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1317
|
+
}
|
|
1318
|
+
},
|
|
1319
|
+
addItemsToCart: async (req) => {
|
|
1320
|
+
try {
|
|
1321
|
+
const result = await executeAddItemsToCart(
|
|
1322
|
+
server,
|
|
1323
|
+
await req.json()
|
|
1324
|
+
);
|
|
1325
|
+
return Response.json(result);
|
|
1326
|
+
} catch (error) {
|
|
1327
|
+
const mapped = mapRouteError(error, "addItemsToCart", "Add items to cart failed");
|
|
1328
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1329
|
+
}
|
|
1330
|
+
},
|
|
1331
|
+
removeFromCart: async (req) => {
|
|
1332
|
+
try {
|
|
1333
|
+
const result = await executeRemoveFromCart(server, await req.json());
|
|
1334
|
+
return Response.json(result);
|
|
1335
|
+
} catch (error) {
|
|
1336
|
+
const mapped = mapRouteError(error, "removeFromCart", "Remove from cart failed");
|
|
1337
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
updateCartQuantity: async (req) => {
|
|
1341
|
+
try {
|
|
1342
|
+
const result = await executeUpdateCartQuantity(
|
|
1343
|
+
server,
|
|
1344
|
+
await req.json()
|
|
1345
|
+
);
|
|
1346
|
+
return Response.json(result);
|
|
1347
|
+
} catch (error) {
|
|
1348
|
+
const mapped = mapRouteError(error, "updateCartQuantity", "Update cart quantity failed");
|
|
1349
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
setCartAddresses: async (req) => {
|
|
1353
|
+
try {
|
|
1354
|
+
const result = await executeSetCartAddresses(
|
|
1355
|
+
server,
|
|
1356
|
+
await req.json()
|
|
1357
|
+
);
|
|
1358
|
+
return Response.json(result);
|
|
1359
|
+
} catch (error) {
|
|
1360
|
+
const mapped = mapRouteError(error, "setCartAddresses", "Set cart addresses failed");
|
|
1361
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1362
|
+
}
|
|
1363
|
+
},
|
|
1364
|
+
getShippingMethods: async (req) => {
|
|
1365
|
+
try {
|
|
1366
|
+
const url = new URL(req.url);
|
|
1367
|
+
const result = await executeGetShippingMethods(server, {
|
|
1368
|
+
anonymousId: url.searchParams.get("anonymousId") ?? void 0,
|
|
1369
|
+
cartId: url.searchParams.get("cartId") ?? void 0,
|
|
1370
|
+
catalogLocale: url.searchParams.get("catalogLocale") ?? void 0,
|
|
1371
|
+
sessionToken: readCartSessionHeader(req.headers)
|
|
1372
|
+
});
|
|
1373
|
+
return Response.json(result);
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
const mapped = mapRouteError(
|
|
1376
|
+
error,
|
|
1377
|
+
"getShippingMethods",
|
|
1378
|
+
"Get shipping methods failed"
|
|
1379
|
+
);
|
|
1380
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1381
|
+
}
|
|
1382
|
+
},
|
|
1383
|
+
setShippingMethod: async (req) => {
|
|
1384
|
+
try {
|
|
1385
|
+
const result = await executeSetShippingMethod(
|
|
1386
|
+
server,
|
|
1387
|
+
await req.json()
|
|
1388
|
+
);
|
|
1389
|
+
return Response.json(result);
|
|
1390
|
+
} catch (error) {
|
|
1391
|
+
const mapped = mapRouteError(
|
|
1392
|
+
error,
|
|
1393
|
+
"setShippingMethod",
|
|
1394
|
+
"Set shipping method failed"
|
|
1395
|
+
);
|
|
1396
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1397
|
+
}
|
|
1398
|
+
},
|
|
1399
|
+
createOrder: async (req) => {
|
|
1400
|
+
try {
|
|
1401
|
+
orderLimiter.consume(clientKeyFromWebHeaders(req.headers));
|
|
1402
|
+
const result = await executeCreateOrder(
|
|
1403
|
+
server,
|
|
1404
|
+
await req.json()
|
|
1405
|
+
);
|
|
1406
|
+
return Response.json(result);
|
|
1407
|
+
} catch (error) {
|
|
1408
|
+
if (error instanceof TooManyRequestsError) {
|
|
1409
|
+
return Response.json(
|
|
1410
|
+
{ error: ORDER_RATE_LIMIT_MESSAGE },
|
|
1411
|
+
{
|
|
1412
|
+
status: 429,
|
|
1413
|
+
headers: { "Retry-After": String(error.retryAfterSeconds) }
|
|
1414
|
+
}
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
const mapped = mapRouteError(error, "createOrder", "Create order failed");
|
|
1418
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1419
|
+
}
|
|
1420
|
+
},
|
|
1421
|
+
getPaymentMethods: async (req) => {
|
|
1422
|
+
try {
|
|
1423
|
+
const url = new URL(req.url);
|
|
1424
|
+
const result = await executeGetPaymentMethods(server, {
|
|
1425
|
+
anonymousId: url.searchParams.get("anonymousId") ?? void 0,
|
|
1426
|
+
cartId: url.searchParams.get("cartId") ?? void 0,
|
|
1427
|
+
catalogLocale: url.searchParams.get("catalogLocale") ?? void 0,
|
|
1428
|
+
sessionToken: readCartSessionHeader(req.headers)
|
|
1429
|
+
});
|
|
1430
|
+
return Response.json(result);
|
|
1431
|
+
} catch (error) {
|
|
1432
|
+
const mapped = mapRouteError(
|
|
1433
|
+
error,
|
|
1434
|
+
"getPaymentMethods",
|
|
1435
|
+
"Get payment methods failed"
|
|
1436
|
+
);
|
|
1437
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
authorizePayment: async (req) => {
|
|
1441
|
+
try {
|
|
1442
|
+
paymentLimiter.consume(clientKeyFromWebHeaders(req.headers));
|
|
1443
|
+
const result = await executeAuthorizePayment(
|
|
1444
|
+
server,
|
|
1445
|
+
await req.json()
|
|
1446
|
+
);
|
|
1447
|
+
return Response.json(result);
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
if (error instanceof TooManyRequestsError) {
|
|
1450
|
+
return Response.json(
|
|
1451
|
+
{ error: PAYMENT_RATE_LIMIT_MESSAGE },
|
|
1452
|
+
{
|
|
1453
|
+
status: 429,
|
|
1454
|
+
headers: { "Retry-After": String(error.retryAfterSeconds) }
|
|
1455
|
+
}
|
|
1456
|
+
);
|
|
1457
|
+
}
|
|
1458
|
+
const mapped = mapRouteError(error, "authorizePayment", "Payment failed");
|
|
1459
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1460
|
+
}
|
|
1461
|
+
},
|
|
1462
|
+
getOrder: async (req) => {
|
|
1463
|
+
try {
|
|
1464
|
+
const url = new URL(req.url);
|
|
1465
|
+
const result = await executeGetOrder(server, {
|
|
1466
|
+
orderNumber: url.searchParams.get("orderNumber") ?? "",
|
|
1467
|
+
anonymousId: url.searchParams.get("anonymousId") ?? void 0,
|
|
1468
|
+
catalogLocale: url.searchParams.get("catalogLocale") ?? void 0,
|
|
1469
|
+
sessionToken: readCartSessionHeader(req.headers)
|
|
1470
|
+
});
|
|
1471
|
+
return Response.json(result);
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
const mapped = mapRouteError(error, "getOrder", "Get order failed");
|
|
1474
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1475
|
+
}
|
|
1476
|
+
},
|
|
1477
|
+
login: async (req) => {
|
|
1478
|
+
try {
|
|
1479
|
+
loginLimiter.consume(clientKeyFromWebHeaders(req.headers));
|
|
1480
|
+
const result = await executeLogin(server, await req.json());
|
|
1481
|
+
return Response.json(result);
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
if (error instanceof TooManyRequestsError) {
|
|
1484
|
+
return Response.json(
|
|
1485
|
+
{ error: error.message },
|
|
1486
|
+
{
|
|
1487
|
+
status: 429,
|
|
1488
|
+
headers: { "Retry-After": String(error.retryAfterSeconds) }
|
|
1489
|
+
}
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
const mapped = mapRouteError(error, "login", "Login failed");
|
|
1493
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1494
|
+
}
|
|
1495
|
+
},
|
|
1496
|
+
logout: async () => {
|
|
1497
|
+
try {
|
|
1498
|
+
const result = await executeLogout();
|
|
1499
|
+
return Response.json(result);
|
|
1500
|
+
} catch (error) {
|
|
1501
|
+
const mapped = mapRouteError(error, "logout", "Logout failed");
|
|
1502
|
+
return toWebErrorResponse(mapped.message, mapped.status);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1508
|
+
0 && (module.exports = {
|
|
1509
|
+
createNextHandlers
|
|
1510
|
+
});
|
|
1511
|
+
//# sourceMappingURL=next.cjs.map
|