@stardeck-customer-apps/testing 0.20.0 → 0.22.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/SKILL.md +29 -5
- package/dist/index.d.mts +43 -1
- package/dist/index.d.ts +43 -1
- package/dist/index.js +411 -9
- package/dist/index.mjs +411 -9
- package/dist/next/headers-shim.js +5 -0
- package/dist/next/headers-shim.mjs +5 -0
- package/dist/setup.js +360 -9
- package/dist/setup.mjs +360 -9
- package/package.json +2 -2
package/dist/setup.mjs
CHANGED
|
@@ -52,6 +52,11 @@ var state = globalSingleton("state", () => ({
|
|
|
52
52
|
boltIntentCounter: 0,
|
|
53
53
|
boltCharges: [],
|
|
54
54
|
boltChargeCounter: 0,
|
|
55
|
+
directCharges: /* @__PURE__ */ new Map(),
|
|
56
|
+
directChargeCounter: 0,
|
|
57
|
+
cardTokens: /* @__PURE__ */ new Map(),
|
|
58
|
+
cardTokenCounter: 0,
|
|
59
|
+
cardTokenizationEnabled: true,
|
|
55
60
|
uploads: [],
|
|
56
61
|
uploadCounter: 0,
|
|
57
62
|
storageFiles: /* @__PURE__ */ new Map(),
|
|
@@ -1201,6 +1206,297 @@ async function handleIdentitiesRequest(request, subPath) {
|
|
|
1201
1206
|
return failure(`No identities simulator for ${method} .../identities${subPath}`, 404);
|
|
1202
1207
|
}
|
|
1203
1208
|
|
|
1209
|
+
// src/simulator/beam-direct-charges.ts
|
|
1210
|
+
var SIM_BEAM_HOST = "beam.stardeck.test";
|
|
1211
|
+
var SIM_BEAM_MERCHANT_ID = "sim_merchant";
|
|
1212
|
+
var SIM_BEAM_PUBLISHABLE_KEY = "pk_test_sim";
|
|
1213
|
+
var DIRECT_CHARGE_METHODS = /* @__PURE__ */ new Set([
|
|
1214
|
+
"CARD",
|
|
1215
|
+
"CARD_INSTALLMENTS",
|
|
1216
|
+
"CARD_TOKEN",
|
|
1217
|
+
"CARD_TOKEN_INSTALLMENTS",
|
|
1218
|
+
"QR_PROMPT_PAY",
|
|
1219
|
+
"ALIPAY",
|
|
1220
|
+
"ALIPAY_PLUS",
|
|
1221
|
+
"WECHAT_PAY",
|
|
1222
|
+
"TRUE_MONEY",
|
|
1223
|
+
"LINE_PAY",
|
|
1224
|
+
"SHOPEE_PAY",
|
|
1225
|
+
"KPLUS",
|
|
1226
|
+
"MAKE",
|
|
1227
|
+
"SCB_EASY",
|
|
1228
|
+
"KRUNGSRI_APP",
|
|
1229
|
+
"BANGKOK_BANK_APP",
|
|
1230
|
+
"SPAY_LATER"
|
|
1231
|
+
]);
|
|
1232
|
+
var TEST_CARDS = {
|
|
1233
|
+
"4111111111111111": { outcome: "SUCCEEDED" },
|
|
1234
|
+
"5372074248113841": { outcome: "SUCCEEDED" },
|
|
1235
|
+
"378282246310005": { outcome: "SUCCEEDED" },
|
|
1236
|
+
"4953261730509988": { outcome: "REDIRECT" },
|
|
1237
|
+
"4389783280144506": { outcome: "REDIRECT" },
|
|
1238
|
+
"4943129900084541": { outcome: "FAILED", failureCode: "CH_INSUFFICIENT_FUNDS" },
|
|
1239
|
+
"4111111111000025": { outcome: "FAILED", failureCode: "CH_CARD_DECLINED" },
|
|
1240
|
+
"4111111111000033": { outcome: "FAILED", failureCode: "CH_CARD_RESTRICTED" }
|
|
1241
|
+
};
|
|
1242
|
+
var SIM_QR_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
|
|
1243
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1244
|
+
function invalid(issues) {
|
|
1245
|
+
return json({ error: "Invalid request body", details: issues }, 400);
|
|
1246
|
+
}
|
|
1247
|
+
var DEVICE_TYPES = /* @__PURE__ */ new Set(["ANDROID", "IOS", "WEB"]);
|
|
1248
|
+
function issue(path, message, code = "custom") {
|
|
1249
|
+
return { path, message, code };
|
|
1250
|
+
}
|
|
1251
|
+
function validateChargeFields(body) {
|
|
1252
|
+
const issues = [];
|
|
1253
|
+
const isInt = (v) => typeof v === "number" && Number.isInteger(v);
|
|
1254
|
+
if (!isInt(body.amount) || body.amount < 1) {
|
|
1255
|
+
issues.push(issue(["amount"], "Number must be greater than 0", "too_small"));
|
|
1256
|
+
}
|
|
1257
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE.test(body.deploymentId)) {
|
|
1258
|
+
issues.push(issue(["deploymentId"], "Invalid deployment ID", "invalid_string"));
|
|
1259
|
+
}
|
|
1260
|
+
if (body.referenceId !== void 0 && (typeof body.referenceId !== "string" || body.referenceId.length > 100)) {
|
|
1261
|
+
issues.push(issue(["referenceId"], "Too long", "too_big"));
|
|
1262
|
+
}
|
|
1263
|
+
const expiry = body.expiryDurationInSec;
|
|
1264
|
+
if (expiry !== void 0 && (!isInt(expiry) || expiry < 60 || expiry > 3600)) {
|
|
1265
|
+
issues.push(issue(["expiryDurationInSec"], "Must be an integer from 60 to 3600", "too_big"));
|
|
1266
|
+
}
|
|
1267
|
+
if (body.returnUrl !== void 0 && (typeof body.returnUrl !== "string" || !URL.canParse(body.returnUrl))) {
|
|
1268
|
+
issues.push(issue(["returnUrl"], "Invalid URL", "invalid_format"));
|
|
1269
|
+
}
|
|
1270
|
+
if (body.deviceType !== void 0 && !DEVICE_TYPES.has(String(body.deviceType))) {
|
|
1271
|
+
issues.push(issue(["deviceType"], "Invalid enum value", "invalid_value"));
|
|
1272
|
+
}
|
|
1273
|
+
const key = body.idempotencyKey;
|
|
1274
|
+
if (key !== void 0 && (typeof key !== "string" || key.length < 1 || key.length > 255)) {
|
|
1275
|
+
issues.push(issue(["idempotencyKey"], "Must be 1 to 255 characters", "too_small"));
|
|
1276
|
+
}
|
|
1277
|
+
return issues;
|
|
1278
|
+
}
|
|
1279
|
+
function validateChargeMethod(body) {
|
|
1280
|
+
const method = body.paymentMethod;
|
|
1281
|
+
const type = method?.paymentMethodType;
|
|
1282
|
+
if (typeof type !== "string" || !DIRECT_CHARGE_METHODS.has(type)) {
|
|
1283
|
+
return [issue(["paymentMethod", "paymentMethodType"], "Invalid enum value", "invalid_value")];
|
|
1284
|
+
}
|
|
1285
|
+
const issues = [];
|
|
1286
|
+
const isToken = type === "CARD_TOKEN" || type === "CARD_TOKEN_INSTALLMENTS";
|
|
1287
|
+
const token = method?.cardToken;
|
|
1288
|
+
if (isToken && (typeof token?.cardTokenId !== "string" || token.cardTokenId === "")) {
|
|
1289
|
+
issues.push(
|
|
1290
|
+
issue(["paymentMethod", "cardToken"], `${type} requires paymentMethod.cardToken.cardTokenId`)
|
|
1291
|
+
);
|
|
1292
|
+
}
|
|
1293
|
+
if (token?.securityCode !== void 0 && !/^\d{3,4}$/.test(String(token.securityCode))) {
|
|
1294
|
+
issues.push(
|
|
1295
|
+
issue(
|
|
1296
|
+
["paymentMethod", "cardToken", "securityCode"],
|
|
1297
|
+
"Invalid security code",
|
|
1298
|
+
"invalid_format"
|
|
1299
|
+
)
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
if (type === "CARD_TOKEN_INSTALLMENTS" && ![3, 4, 6, 10].includes(method?.installmentPeriod)) {
|
|
1303
|
+
issues.push(
|
|
1304
|
+
issue(
|
|
1305
|
+
["paymentMethod", "installmentPeriod"],
|
|
1306
|
+
"CARD_TOKEN_INSTALLMENTS requires paymentMethod.installmentPeriod (3, 4, 6 or 10)"
|
|
1307
|
+
)
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
if (type !== "QR_PROMPT_PAY" && !body.returnUrl) {
|
|
1311
|
+
issues.push(
|
|
1312
|
+
issue(
|
|
1313
|
+
["returnUrl"],
|
|
1314
|
+
`${type} sends the shopper away to pay; returnUrl is required to bring them back`
|
|
1315
|
+
)
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
if (type === "CARD" || type === "CARD_INSTALLMENTS") {
|
|
1319
|
+
issues.push(
|
|
1320
|
+
issue(
|
|
1321
|
+
["paymentMethod", "paymentMethodType"],
|
|
1322
|
+
`${type} takes raw card data; tokenize the card in the browser and charge CARD_TOKEN instead`
|
|
1323
|
+
)
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
return issues;
|
|
1327
|
+
}
|
|
1328
|
+
function validateDirectCharge(body) {
|
|
1329
|
+
return [...validateChargeFields(body), ...validateChargeMethod(body)];
|
|
1330
|
+
}
|
|
1331
|
+
function chargeResponse(charge) {
|
|
1332
|
+
const response = {
|
|
1333
|
+
chargeId: charge.id,
|
|
1334
|
+
actionRequired: charge.actionRequired,
|
|
1335
|
+
paymentMethodType: charge.paymentMethodType
|
|
1336
|
+
};
|
|
1337
|
+
if (charge.actionRequired === "ENCODED_IMAGE") {
|
|
1338
|
+
response.encodedImage = {
|
|
1339
|
+
imageBase64Encoded: SIM_QR_PNG,
|
|
1340
|
+
rawData: `00020101021230SIM${charge.id}`,
|
|
1341
|
+
expiry: charge.expiresAt
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
if (charge.actionRequired === "REDIRECT") {
|
|
1345
|
+
response.redirect = { redirectUrl: charge.redirectUrl };
|
|
1346
|
+
response.redirectUrl = charge.redirectUrl;
|
|
1347
|
+
}
|
|
1348
|
+
return response;
|
|
1349
|
+
}
|
|
1350
|
+
function nextChargeId() {
|
|
1351
|
+
state.directChargeCounter += 1;
|
|
1352
|
+
return `ch_sim_${state.directChargeCounter}`;
|
|
1353
|
+
}
|
|
1354
|
+
function createCharge(body) {
|
|
1355
|
+
const method = body.paymentMethod;
|
|
1356
|
+
const type = method.paymentMethodType;
|
|
1357
|
+
const id = nextChargeId();
|
|
1358
|
+
const now3 = /* @__PURE__ */ new Date();
|
|
1359
|
+
const expirySec = typeof body.expiryDurationInSec === "number" ? body.expiryDurationInSec : 30 * 60;
|
|
1360
|
+
const returnUrl = typeof body.returnUrl === "string" ? body.returnUrl : null;
|
|
1361
|
+
let actionRequired = "REDIRECT";
|
|
1362
|
+
let status = "PENDING";
|
|
1363
|
+
let failureCode = null;
|
|
1364
|
+
let cardLast4 = null;
|
|
1365
|
+
if (type === "QR_PROMPT_PAY") {
|
|
1366
|
+
actionRequired = "ENCODED_IMAGE";
|
|
1367
|
+
} else if (type === "CARD_TOKEN" || type === "CARD_TOKEN_INSTALLMENTS") {
|
|
1368
|
+
const token = state.cardTokens.get(method.cardToken.cardTokenId);
|
|
1369
|
+
if (!token) {
|
|
1370
|
+
return json({ error: "Card token not found", code: "CARD_TOKEN_NOT_FOUND" }, 404);
|
|
1371
|
+
}
|
|
1372
|
+
cardLast4 = token.last4;
|
|
1373
|
+
const card = TEST_CARDS[token.pan] ?? {
|
|
1374
|
+
outcome: "FAILED",
|
|
1375
|
+
failureCode: "CH_PROCESSING_FAILED"
|
|
1376
|
+
};
|
|
1377
|
+
if (card.outcome === "REDIRECT") {
|
|
1378
|
+
actionRequired = "REDIRECT";
|
|
1379
|
+
} else {
|
|
1380
|
+
actionRequired = "NONE";
|
|
1381
|
+
status = card.outcome;
|
|
1382
|
+
failureCode = card.failureCode ?? null;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
const charge = {
|
|
1386
|
+
id,
|
|
1387
|
+
status,
|
|
1388
|
+
amount: body.amount,
|
|
1389
|
+
currency: String(body.currency ?? "THB"),
|
|
1390
|
+
paymentMethodType: type,
|
|
1391
|
+
referenceId: typeof body.referenceId === "string" ? body.referenceId : null,
|
|
1392
|
+
deviceType: typeof body.deviceType === "string" ? body.deviceType : null,
|
|
1393
|
+
returnUrl,
|
|
1394
|
+
actionRequired,
|
|
1395
|
+
redirectUrl: actionRequired === "REDIRECT" ? `https://${SIM_BEAM_HOST}/pay/${id}${returnUrl ? `?returnUrl=${encodeURIComponent(returnUrl)}` : ""}` : null,
|
|
1396
|
+
expiresAt: new Date(now3.getTime() + expirySec * 1e3).toISOString(),
|
|
1397
|
+
failureCode,
|
|
1398
|
+
cardLast4,
|
|
1399
|
+
idempotencyKey: typeof body.idempotencyKey === "string" ? body.idempotencyKey : null,
|
|
1400
|
+
createdAt: now3.toISOString()
|
|
1401
|
+
};
|
|
1402
|
+
state.directCharges.set(id, charge);
|
|
1403
|
+
return charge;
|
|
1404
|
+
}
|
|
1405
|
+
async function handleDirectChargeRequest(request, pathname) {
|
|
1406
|
+
if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/charges$/.test(pathname)) {
|
|
1407
|
+
const body = await readJsonBody(request);
|
|
1408
|
+
const issues = validateDirectCharge(body);
|
|
1409
|
+
if (issues.length > 0) return invalid(issues);
|
|
1410
|
+
const key = typeof body.idempotencyKey === "string" ? body.idempotencyKey : null;
|
|
1411
|
+
if (key) {
|
|
1412
|
+
const existing = [...state.directCharges.values()].find((c) => c.idempotencyKey === key);
|
|
1413
|
+
if (existing) return json(chargeResponse(existing));
|
|
1414
|
+
}
|
|
1415
|
+
const created = createCharge(body);
|
|
1416
|
+
if (created instanceof Response) return created;
|
|
1417
|
+
return json(chargeResponse(created));
|
|
1418
|
+
}
|
|
1419
|
+
const getMatch = pathname.match(/^\/api\/store\/beam\/[^/]+\/charges\/([^/]+)$/);
|
|
1420
|
+
if (request.method === "GET" && getMatch) {
|
|
1421
|
+
const charge = state.directCharges.get(decodeURIComponent(getMatch[1]));
|
|
1422
|
+
if (!charge) return json({ error: "Charge not found", code: "NOT_FOUND" }, 404);
|
|
1423
|
+
return json({ charge: toChargeDetail(charge) });
|
|
1424
|
+
}
|
|
1425
|
+
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/card-tokenization$/.test(pathname)) {
|
|
1426
|
+
if (!state.cardTokenizationEnabled) {
|
|
1427
|
+
return json(
|
|
1428
|
+
{
|
|
1429
|
+
error: "Add the Beam publishable key to this merchant account to take cards in your own checkout",
|
|
1430
|
+
code: "CARD_TOKENIZATION_NOT_CONFIGURED"
|
|
1431
|
+
},
|
|
1432
|
+
409
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
return json({
|
|
1436
|
+
merchantId: SIM_BEAM_MERCHANT_ID,
|
|
1437
|
+
publishableKey: SIM_BEAM_PUBLISHABLE_KEY,
|
|
1438
|
+
tokenizeUrl: `https://${SIM_BEAM_HOST}/client/v1/card-tokens`,
|
|
1439
|
+
isSandbox: true
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
return null;
|
|
1443
|
+
}
|
|
1444
|
+
function toChargeDetail(charge) {
|
|
1445
|
+
return {
|
|
1446
|
+
id: charge.id,
|
|
1447
|
+
chargeId: charge.id,
|
|
1448
|
+
status: charge.status,
|
|
1449
|
+
amount: charge.amount,
|
|
1450
|
+
currency: charge.currency,
|
|
1451
|
+
paymentMethodType: charge.paymentMethodType,
|
|
1452
|
+
...charge.referenceId !== null ? { referenceId: charge.referenceId } : {},
|
|
1453
|
+
...charge.failureCode ? { failureCode: charge.failureCode } : {},
|
|
1454
|
+
source: "API",
|
|
1455
|
+
createdAt: charge.createdAt
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
async function handleCardTokenRequest(request, url) {
|
|
1459
|
+
if (request.method !== "POST" || url.pathname !== "/client/v1/card-tokens") {
|
|
1460
|
+
return json({ error: `No Beam simulator for ${request.method} ${url.pathname}` }, 404);
|
|
1461
|
+
}
|
|
1462
|
+
const expected = `Basic ${btoa(`${SIM_BEAM_MERCHANT_ID}:${SIM_BEAM_PUBLISHABLE_KEY}`)}`;
|
|
1463
|
+
if (request.headers.get("Authorization") !== expected) {
|
|
1464
|
+
return json(
|
|
1465
|
+
{
|
|
1466
|
+
message: "invalid credentials",
|
|
1467
|
+
error: { errorCode: "INVALID_CREDENTIALS_ERROR", errorMessage: "invalid credentials" }
|
|
1468
|
+
},
|
|
1469
|
+
401
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
const body = await readJsonBody(request);
|
|
1473
|
+
const pan = typeof body.pan === "string" ? body.pan : "";
|
|
1474
|
+
const month = body.expiryMonth;
|
|
1475
|
+
const year = body.expiryYear;
|
|
1476
|
+
if (!/^\d{12,19}$/.test(pan) || typeof month !== "number" || month < 1 || month > 12 || typeof year !== "number" || year < 0 || year > 99) {
|
|
1477
|
+
return json(
|
|
1478
|
+
{
|
|
1479
|
+
message: "inputs are failing validation",
|
|
1480
|
+
error: {
|
|
1481
|
+
errorCode: "API_VALIDATION_ERROR",
|
|
1482
|
+
errorMessage: "inputs are failing validation"
|
|
1483
|
+
}
|
|
1484
|
+
},
|
|
1485
|
+
400
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
state.cardTokenCounter += 1;
|
|
1489
|
+
const token = {
|
|
1490
|
+
id: `ctok_sim_${state.cardTokenCounter}`,
|
|
1491
|
+
pan,
|
|
1492
|
+
last4: pan.slice(-4),
|
|
1493
|
+
expiryMonth: month,
|
|
1494
|
+
expiryYear: year
|
|
1495
|
+
};
|
|
1496
|
+
state.cardTokens.set(token.id, token);
|
|
1497
|
+
return json({ id: token.id });
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1204
1500
|
// src/simulator/payments.ts
|
|
1205
1501
|
import crypto5 from "crypto";
|
|
1206
1502
|
|
|
@@ -1228,7 +1524,7 @@ var BEAM_BOLT_PAYMENT_METHODS = [
|
|
|
1228
1524
|
"SPAY_LATER"
|
|
1229
1525
|
];
|
|
1230
1526
|
var BEAM_BOLT_INTENT_STATUSES = ["PENDING", "PAID", "FAILED", "CANCELED", "EXPIRED"];
|
|
1231
|
-
var
|
|
1527
|
+
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1232
1528
|
function payErr(error, status = 400, code, details) {
|
|
1233
1529
|
const body = { error };
|
|
1234
1530
|
if (code) body.code = code;
|
|
@@ -1242,6 +1538,12 @@ function paymentOperationForRequest(request, pathname) {
|
|
|
1242
1538
|
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/payment-links\/[^/]+$/.test(pathname)) {
|
|
1243
1539
|
return "beam.getPaymentLinkStatus";
|
|
1244
1540
|
}
|
|
1541
|
+
if (request.method === "POST" && /^\/api\/store\/beam\/[^/]+\/charges$/.test(pathname)) {
|
|
1542
|
+
return "beam.createDirectCharge";
|
|
1543
|
+
}
|
|
1544
|
+
if (request.method === "GET" && /^\/api\/store\/beam\/[^/]+\/charges\/[^/]+$/.test(pathname)) {
|
|
1545
|
+
return "beam.getCharge";
|
|
1546
|
+
}
|
|
1245
1547
|
if (request.method === "POST" && /^\/api\/store\/(?!beam\/)[^/]+\/checkout$/.test(pathname)) {
|
|
1246
1548
|
return "stripe.createCheckout";
|
|
1247
1549
|
}
|
|
@@ -1382,6 +1684,8 @@ async function handlePaymentsRequest(request, url) {
|
|
|
1382
1684
|
const pathname = url.pathname;
|
|
1383
1685
|
const injectedFailure = consumePaymentFailure(request, pathname);
|
|
1384
1686
|
if (injectedFailure) return injectedFailure;
|
|
1687
|
+
const directChargeResponse = await handleDirectChargeRequest(request, pathname);
|
|
1688
|
+
if (directChargeResponse) return directChargeResponse;
|
|
1385
1689
|
if (/\/billing-portal$/.test(pathname)) {
|
|
1386
1690
|
return payErr("Not found", 404, "NOT_FOUND");
|
|
1387
1691
|
}
|
|
@@ -1478,7 +1782,7 @@ async function handlePaymentsRequest(request, url) {
|
|
|
1478
1782
|
code: "too_small"
|
|
1479
1783
|
});
|
|
1480
1784
|
}
|
|
1481
|
-
if (typeof body.deploymentId !== "string" || !
|
|
1785
|
+
if (typeof body.deploymentId !== "string" || !UUID_RE2.test(body.deploymentId)) {
|
|
1482
1786
|
issues.push({
|
|
1483
1787
|
path: ["deploymentId"],
|
|
1484
1788
|
message: "Invalid uuid",
|
|
@@ -2223,6 +2527,7 @@ async function handleEdgeRequest(request, subPath) {
|
|
|
2223
2527
|
}
|
|
2224
2528
|
|
|
2225
2529
|
// src/simulator/starlens.ts
|
|
2530
|
+
var LATEST_VERSION = "v2";
|
|
2226
2531
|
function starlensFailure(error, status, code) {
|
|
2227
2532
|
return json({ success: false, error, code }, status);
|
|
2228
2533
|
}
|
|
@@ -2241,11 +2546,18 @@ function nullFlagged(output, issues) {
|
|
|
2241
2546
|
}
|
|
2242
2547
|
return copy;
|
|
2243
2548
|
}
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2549
|
+
function isInputShape(input) {
|
|
2550
|
+
if (!input || typeof input !== "object") return false;
|
|
2551
|
+
const i = input;
|
|
2552
|
+
return typeof i.url === "string" || typeof i.data === "string" && i.data.length > 0 && typeof i.mediaType === "string";
|
|
2553
|
+
}
|
|
2554
|
+
var NON_IMAGE_EXTENSION = /\.(pdf|mp3|m4a|wav|ogg|aac|flac|mp4|mov|webm|mkv)(\?|#|$)/i;
|
|
2555
|
+
function isCroppable(input) {
|
|
2556
|
+
if (typeof input.mediaType === "string") return input.mediaType.startsWith("image/");
|
|
2557
|
+
const name = typeof input.filename === "string" ? input.filename : input.url;
|
|
2558
|
+
return !(typeof name === "string" && NON_IMAGE_EXTENSION.test(name));
|
|
2559
|
+
}
|
|
2560
|
+
function validateStarlensBody(body) {
|
|
2249
2561
|
const inputs = Array.isArray(body.inputs) ? body.inputs : [];
|
|
2250
2562
|
if (inputs.length === 0) {
|
|
2251
2563
|
return starlensFailure(
|
|
@@ -2254,6 +2566,13 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2254
2566
|
"VALIDATION_ERROR"
|
|
2255
2567
|
);
|
|
2256
2568
|
}
|
|
2569
|
+
if (!inputs.every(isInputShape)) {
|
|
2570
|
+
return starlensFailure(
|
|
2571
|
+
"Validation failed \u2014 inputs: each input needs a url, or data with a mediaType",
|
|
2572
|
+
400,
|
|
2573
|
+
"VALIDATION_ERROR"
|
|
2574
|
+
);
|
|
2575
|
+
}
|
|
2257
2576
|
const schema = body.schema;
|
|
2258
2577
|
if (!schema || schema.type !== "object" || typeof schema.properties !== "object") {
|
|
2259
2578
|
return starlensFailure(
|
|
@@ -2262,8 +2581,35 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2262
2581
|
"VALIDATION_ERROR"
|
|
2263
2582
|
);
|
|
2264
2583
|
}
|
|
2584
|
+
const version = body.version ?? "latest";
|
|
2585
|
+
if (version !== "v1" && version !== "v2" && version !== "latest") {
|
|
2586
|
+
return starlensFailure("Validation failed \u2014 version: unknown version", 400, "VALIDATION_ERROR");
|
|
2587
|
+
}
|
|
2588
|
+
const effort = body.effort ?? "low";
|
|
2589
|
+
if (effort !== "low" && effort !== "high") {
|
|
2590
|
+
return starlensFailure("Validation failed \u2014 effort: low or high", 400, "VALIDATION_ERROR");
|
|
2591
|
+
}
|
|
2592
|
+
const resolved = version === "latest" ? LATEST_VERSION : version;
|
|
2593
|
+
if (effort === "high" && resolved === "v1") {
|
|
2594
|
+
return starlensFailure(
|
|
2595
|
+
`Starlens v1 does not support effort "high"; use version "v2"`,
|
|
2596
|
+
400,
|
|
2597
|
+
"unsupported_effort"
|
|
2598
|
+
);
|
|
2599
|
+
}
|
|
2600
|
+
return { version, resolved, effort, inputs, schema };
|
|
2601
|
+
}
|
|
2602
|
+
async function handleStarlensExtract(request, signedDeploymentId) {
|
|
2603
|
+
const body = await readJsonBody(request);
|
|
2604
|
+
const valid = validateStarlensBody(body);
|
|
2605
|
+
if (valid instanceof Response) return valid;
|
|
2606
|
+
const { version, resolved, effort, inputs, schema } = valid;
|
|
2607
|
+
if (signedDeploymentId !== null && body.deploymentId !== signedDeploymentId) {
|
|
2608
|
+
return starlensFailure("Deployment ID mismatch", 401, "AUTHENTICATION_ERROR");
|
|
2609
|
+
}
|
|
2265
2610
|
const call = {
|
|
2266
|
-
version
|
|
2611
|
+
version,
|
|
2612
|
+
effort,
|
|
2267
2613
|
inputs: inputs.map((i) => ({
|
|
2268
2614
|
name: typeof i.filename === "string" ? i.filename : typeof i.url === "string" ? i.url : "upload",
|
|
2269
2615
|
mediaType: typeof i.mediaType === "string" ? i.mediaType : null,
|
|
@@ -2285,8 +2631,10 @@ async function handleStarlensExtract(request, signedDeploymentId) {
|
|
|
2285
2631
|
return starlensFailure(reply.reject, 422, "unsupported_document");
|
|
2286
2632
|
}
|
|
2287
2633
|
const issues = reply.issues ?? [];
|
|
2634
|
+
const ran = reply.effort ?? (effort === "high" && !inputs.some(isCroppable) ? "low" : effort);
|
|
2288
2635
|
return success({
|
|
2289
|
-
version:
|
|
2636
|
+
version: resolved,
|
|
2637
|
+
effort: ran,
|
|
2290
2638
|
output: nullFlagged(reply.output, issues),
|
|
2291
2639
|
issues,
|
|
2292
2640
|
usage: { inputTokens: 0, outputTokens: 0, cost: 0 }
|
|
@@ -2362,6 +2710,9 @@ async function handleSimulatedRequest(request, url) {
|
|
|
2362
2710
|
if (isStorageHost) {
|
|
2363
2711
|
return handleStorageRequest(request, url);
|
|
2364
2712
|
}
|
|
2713
|
+
if (url.hostname === SIM_BEAM_HOST) {
|
|
2714
|
+
return handleCardTokenRequest(request, url);
|
|
2715
|
+
}
|
|
2365
2716
|
if (isEmail && request.method === "POST") {
|
|
2366
2717
|
return handleEmailSend(request);
|
|
2367
2718
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardeck-customer-apps/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Vitest test harness for Stardeck customer apps — in-process Postgres (PGlite) plus a control-plane simulator so the real Stardeck SDKs run unmodified in tests",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"build": "tsup src/index.ts src/config.ts src/setup.ts src/next/headers-shim.ts src/server-only-shim.ts --format cjs,esm --dts --no-splitting",
|
|
49
49
|
"dev": "tsup src/index.ts src/config.ts src/setup.ts src/next/headers-shim.ts src/server-only-shim.ts --format cjs,esm --dts --no-splitting --watch",
|
|
50
50
|
"format": "prettier --write . && oxlint . --fix",
|
|
51
|
-
"typecheck": "
|
|
51
|
+
"typecheck": "tsgo --noEmit && tsgo -p tsconfig.test.json",
|
|
52
52
|
"lint": "oxlint src/",
|
|
53
53
|
"test": "vitest run",
|
|
54
54
|
"test:watch": "vitest"
|