@anton.andrusenko/shopify-mcp-admin 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-PQKNBYJN.js → chunk-EQUN4XCH.js} +5 -2
- package/dist/{chunk-LMFNHULG.js → chunk-RBXQOPVF.js} +758 -50
- package/dist/dashboard/assets/index-ClITn1me.css +1 -0
- package/dist/dashboard/assets/index-Cvo1L2xM.js +126 -0
- package/dist/dashboard/assets/index-Cvo1L2xM.js.map +1 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/mcp-icon.svg +29 -31
- package/dist/index.js +781 -440
- package/dist/{mcp-auth-F25V6FEY.js → mcp-auth-CWOWKID3.js} +1 -1
- package/dist/{tools-HVUCP53D.js → tools-BCI3Z2AW.js} +1 -1
- package/package.json +10 -1
- package/dist/dashboard/assets/index-BfNrQS4y.js +0 -120
- package/dist/dashboard/assets/index-BfNrQS4y.js.map +0 -1
- package/dist/dashboard/assets/index-HBHxyHsM.css +0 -1
|
@@ -1308,12 +1308,704 @@ var zodToJsonSchema = (schema, options) => {
|
|
|
1308
1308
|
return combined;
|
|
1309
1309
|
};
|
|
1310
1310
|
|
|
1311
|
+
// src/logging/correlation.ts
|
|
1312
|
+
import { randomUUID } from "crypto";
|
|
1313
|
+
|
|
1314
|
+
// src/types/context.ts
|
|
1315
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
1316
|
+
function createSingleTenantContext(client, shopDomain) {
|
|
1317
|
+
return {
|
|
1318
|
+
client,
|
|
1319
|
+
shopDomain
|
|
1320
|
+
// userId and apiKeyId are undefined in single-tenant mode
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
function createMultiTenantContext(client, shopDomain, tenantId, apiKeyId, allowedShops, options) {
|
|
1324
|
+
return {
|
|
1325
|
+
client,
|
|
1326
|
+
shopDomain,
|
|
1327
|
+
userId: tenantId,
|
|
1328
|
+
// Map tenantId to userId for compatibility
|
|
1329
|
+
apiKeyId,
|
|
1330
|
+
tenantId,
|
|
1331
|
+
allowedShops,
|
|
1332
|
+
correlationId: options?.correlationId,
|
|
1333
|
+
oauthClientId: options?.oauthClientId,
|
|
1334
|
+
oauthClientName: options?.oauthClientName
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
var requestContextStorage = new AsyncLocalStorage();
|
|
1338
|
+
var fallbackContextStorage = /* @__PURE__ */ new Map();
|
|
1339
|
+
function setFallbackContext(key, context) {
|
|
1340
|
+
fallbackContextStorage.set(key, context);
|
|
1341
|
+
}
|
|
1342
|
+
function clearFallbackContext(key) {
|
|
1343
|
+
fallbackContextStorage.delete(key);
|
|
1344
|
+
}
|
|
1345
|
+
var currentContextKey;
|
|
1346
|
+
function setCurrentContextKey(key) {
|
|
1347
|
+
currentContextKey = key;
|
|
1348
|
+
}
|
|
1349
|
+
function getRequestContext() {
|
|
1350
|
+
const asyncContext = requestContextStorage.getStore();
|
|
1351
|
+
if (asyncContext) {
|
|
1352
|
+
return asyncContext;
|
|
1353
|
+
}
|
|
1354
|
+
if (currentContextKey) {
|
|
1355
|
+
return fallbackContextStorage.get(currentContextKey);
|
|
1356
|
+
}
|
|
1357
|
+
return void 0;
|
|
1358
|
+
}
|
|
1359
|
+
function isMultiTenantContext(context) {
|
|
1360
|
+
return "tenantId" in context && typeof context.tenantId === "string";
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// src/logging/correlation.ts
|
|
1364
|
+
var CORRELATION_ID_HEADER = "X-Correlation-ID";
|
|
1365
|
+
var UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1366
|
+
function generateCorrelationId() {
|
|
1367
|
+
return randomUUID();
|
|
1368
|
+
}
|
|
1369
|
+
function isValidCorrelationId(id) {
|
|
1370
|
+
return UUID_V4_REGEX.test(id);
|
|
1371
|
+
}
|
|
1372
|
+
function correlationMiddleware(req, res, next) {
|
|
1373
|
+
const headerValue = req.headers[CORRELATION_ID_HEADER.toLowerCase()];
|
|
1374
|
+
let correlationId;
|
|
1375
|
+
if (headerValue && isValidCorrelationId(headerValue)) {
|
|
1376
|
+
correlationId = headerValue;
|
|
1377
|
+
} else {
|
|
1378
|
+
correlationId = generateCorrelationId();
|
|
1379
|
+
}
|
|
1380
|
+
res.locals.correlationId = correlationId;
|
|
1381
|
+
if (typeof res.set === "function") {
|
|
1382
|
+
res.set(
|
|
1383
|
+
CORRELATION_ID_HEADER,
|
|
1384
|
+
correlationId
|
|
1385
|
+
);
|
|
1386
|
+
} else {
|
|
1387
|
+
res.setHeader(CORRELATION_ID_HEADER, correlationId);
|
|
1388
|
+
}
|
|
1389
|
+
next();
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// src/logging/structured-logger.ts
|
|
1393
|
+
import {
|
|
1394
|
+
pino as createPino,
|
|
1395
|
+
destination as pinoDestination,
|
|
1396
|
+
transport as pinoTransport
|
|
1397
|
+
} from "pino";
|
|
1398
|
+
|
|
1399
|
+
// src/monitoring/sentry.ts
|
|
1400
|
+
import * as Sentry from "@sentry/node";
|
|
1401
|
+
function initSentry(options) {
|
|
1402
|
+
const { dsn, environment = "production", release } = options;
|
|
1403
|
+
if (!dsn) {
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
Sentry.init({
|
|
1407
|
+
dsn,
|
|
1408
|
+
environment,
|
|
1409
|
+
// Sample 10% of transactions for performance monitoring (AC-13.7.4)
|
|
1410
|
+
tracesSampleRate: 0.1,
|
|
1411
|
+
// Release tracking for deployment correlation
|
|
1412
|
+
release: release || "unknown",
|
|
1413
|
+
// Sanitize sensitive data before sending to Sentry
|
|
1414
|
+
beforeSend(event) {
|
|
1415
|
+
if (event.request?.headers) {
|
|
1416
|
+
const sensitiveHeaders = [
|
|
1417
|
+
"authorization",
|
|
1418
|
+
"x-api-key",
|
|
1419
|
+
"cookie",
|
|
1420
|
+
"x-shopify-access-token",
|
|
1421
|
+
"x-shopify-hmac-sha256"
|
|
1422
|
+
];
|
|
1423
|
+
for (const header of sensitiveHeaders) {
|
|
1424
|
+
delete event.request.headers[header];
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
if (event.request?.data) {
|
|
1428
|
+
const data = event.request.data;
|
|
1429
|
+
const sensitiveKeys = ["token", "password", "secret", "key", "access_token"];
|
|
1430
|
+
for (const key of Object.keys(data)) {
|
|
1431
|
+
if (sensitiveKeys.some((sensitive) => key.toLowerCase().includes(sensitive))) {
|
|
1432
|
+
data[key] = "[REDACTED]";
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
if (event.message) {
|
|
1437
|
+
event.message = sanitizeErrorMessage(event.message);
|
|
1438
|
+
}
|
|
1439
|
+
if (event.exception?.values) {
|
|
1440
|
+
for (const exception of event.exception.values) {
|
|
1441
|
+
if (exception.value) {
|
|
1442
|
+
exception.value = sanitizeErrorMessage(exception.value);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return event;
|
|
1447
|
+
},
|
|
1448
|
+
// Integrations
|
|
1449
|
+
integrations: [
|
|
1450
|
+
// Capture uncaught exceptions and unhandled rejections
|
|
1451
|
+
Sentry.onUncaughtExceptionIntegration(),
|
|
1452
|
+
Sentry.onUnhandledRejectionIntegration(),
|
|
1453
|
+
// Capture HTTP request data
|
|
1454
|
+
Sentry.requestDataIntegration(),
|
|
1455
|
+
// Deduplicate similar errors
|
|
1456
|
+
Sentry.dedupeIntegration()
|
|
1457
|
+
]
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
function sanitizeErrorMessage(message) {
|
|
1461
|
+
const patterns = [
|
|
1462
|
+
/shpat_[a-zA-Z0-9]+/g,
|
|
1463
|
+
/shpua_[a-zA-Z0-9]+/g,
|
|
1464
|
+
/Bearer\s+[a-zA-Z0-9_-]+/g,
|
|
1465
|
+
/access_token[=:]\s*[a-zA-Z0-9_-]+/gi,
|
|
1466
|
+
/client_secret[=:]\s*[a-zA-Z0-9_-]+/gi,
|
|
1467
|
+
/sk_live_[a-zA-Z0-9_-]+/g
|
|
1468
|
+
];
|
|
1469
|
+
let sanitized = message;
|
|
1470
|
+
for (const pattern of patterns) {
|
|
1471
|
+
sanitized = sanitized.replace(pattern, "[REDACTED]");
|
|
1472
|
+
}
|
|
1473
|
+
return sanitized;
|
|
1474
|
+
}
|
|
1475
|
+
function sentryRequestMiddleware(req, res, next) {
|
|
1476
|
+
const context = getRequestContext();
|
|
1477
|
+
if (context) {
|
|
1478
|
+
if (context.correlationId) {
|
|
1479
|
+
Sentry.setTag("correlationId", context.correlationId);
|
|
1480
|
+
Sentry.setContext("request", {
|
|
1481
|
+
correlationId: context.correlationId
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
if (isMultiTenantContext(context) && context.tenantId) {
|
|
1485
|
+
Sentry.setTag("tenantId", context.tenantId);
|
|
1486
|
+
Sentry.setContext("tenant", {
|
|
1487
|
+
tenantId: context.tenantId
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
if (context.shopDomain) {
|
|
1491
|
+
Sentry.setTag("shopDomain", context.shopDomain);
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
Sentry.setContext("http", {
|
|
1495
|
+
method: req.method,
|
|
1496
|
+
url: req.url,
|
|
1497
|
+
path: req.path,
|
|
1498
|
+
query: req.query,
|
|
1499
|
+
userAgent: req.get("user-agent"),
|
|
1500
|
+
ip: req.ip
|
|
1501
|
+
});
|
|
1502
|
+
Sentry.addBreadcrumb({
|
|
1503
|
+
category: "http",
|
|
1504
|
+
message: `${req.method} ${req.path}`,
|
|
1505
|
+
level: "info",
|
|
1506
|
+
data: {
|
|
1507
|
+
method: req.method,
|
|
1508
|
+
path: req.path
|
|
1509
|
+
// statusCode will be updated when response finishes (see below)
|
|
1510
|
+
}
|
|
1511
|
+
});
|
|
1512
|
+
res.on("finish", () => {
|
|
1513
|
+
Sentry.addBreadcrumb({
|
|
1514
|
+
category: "http",
|
|
1515
|
+
message: `${req.method} ${req.path} - ${res.statusCode}`,
|
|
1516
|
+
level: res.statusCode >= 400 ? "error" : "info",
|
|
1517
|
+
data: {
|
|
1518
|
+
method: req.method,
|
|
1519
|
+
path: req.path,
|
|
1520
|
+
statusCode: res.statusCode
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1523
|
+
});
|
|
1524
|
+
next();
|
|
1525
|
+
}
|
|
1526
|
+
function sentryErrorHandler(error, req, _res, next) {
|
|
1527
|
+
Sentry.captureException(error, {
|
|
1528
|
+
tags: {
|
|
1529
|
+
path: req.path,
|
|
1530
|
+
method: req.method
|
|
1531
|
+
},
|
|
1532
|
+
extra: {
|
|
1533
|
+
url: req.url,
|
|
1534
|
+
query: req.query,
|
|
1535
|
+
body: req.body
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
next(error);
|
|
1539
|
+
}
|
|
1540
|
+
function captureError(error, context) {
|
|
1541
|
+
const requestContext = getRequestContext();
|
|
1542
|
+
Sentry.withScope((scope) => {
|
|
1543
|
+
if (requestContext?.correlationId) {
|
|
1544
|
+
scope.setTag("correlationId", requestContext.correlationId);
|
|
1545
|
+
scope.setContext("request", {
|
|
1546
|
+
correlationId: requestContext.correlationId
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
if (requestContext && isMultiTenantContext(requestContext) && requestContext.tenantId) {
|
|
1550
|
+
scope.setTag("tenantId", requestContext.tenantId);
|
|
1551
|
+
scope.setContext("tenant", {
|
|
1552
|
+
tenantId: requestContext.tenantId
|
|
1553
|
+
});
|
|
1554
|
+
}
|
|
1555
|
+
if (requestContext?.shopDomain) {
|
|
1556
|
+
scope.setTag("shopDomain", requestContext.shopDomain);
|
|
1557
|
+
}
|
|
1558
|
+
if (context) {
|
|
1559
|
+
scope.setContext("additional", context);
|
|
1560
|
+
}
|
|
1561
|
+
Sentry.captureException(error);
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// src/logging/structured-logger.ts
|
|
1566
|
+
var SANITIZATION_PATTERNS = [
|
|
1567
|
+
{ pattern: /shpat_[a-zA-Z0-9]+/g, replacement: "[REDACTED]" },
|
|
1568
|
+
{ pattern: /shpua_[a-zA-Z0-9]+/g, replacement: "[REDACTED]" },
|
|
1569
|
+
{ pattern: /Bearer\s+[a-zA-Z0-9_-]+/g, replacement: "Bearer [REDACTED]" },
|
|
1570
|
+
{ pattern: /access_token[=:]\s*[a-zA-Z0-9_-]+/gi, replacement: "access_token=[REDACTED]" },
|
|
1571
|
+
{ pattern: /client_secret[=:]\s*[a-zA-Z0-9_-]+/gi, replacement: "client_secret=[REDACTED]" },
|
|
1572
|
+
{ pattern: /sk_live_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" }
|
|
1573
|
+
];
|
|
1574
|
+
function sanitizeText(text) {
|
|
1575
|
+
let result = text;
|
|
1576
|
+
for (const { pattern, replacement } of SANITIZATION_PATTERNS) {
|
|
1577
|
+
result = result.replace(pattern, replacement);
|
|
1578
|
+
}
|
|
1579
|
+
return result;
|
|
1580
|
+
}
|
|
1581
|
+
function sanitizeObject(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
1582
|
+
if (typeof obj === "string") {
|
|
1583
|
+
return sanitizeText(obj);
|
|
1584
|
+
}
|
|
1585
|
+
if (obj === null || typeof obj !== "object") {
|
|
1586
|
+
return obj;
|
|
1587
|
+
}
|
|
1588
|
+
if (seen.has(obj)) {
|
|
1589
|
+
return "[Circular]";
|
|
1590
|
+
}
|
|
1591
|
+
seen.add(obj);
|
|
1592
|
+
if (Array.isArray(obj)) {
|
|
1593
|
+
return obj.map((item) => sanitizeObject(item, seen));
|
|
1594
|
+
}
|
|
1595
|
+
const result = {};
|
|
1596
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1597
|
+
result[key] = sanitizeObject(value, seen);
|
|
1598
|
+
}
|
|
1599
|
+
return result;
|
|
1600
|
+
}
|
|
1601
|
+
function getInstanceId() {
|
|
1602
|
+
return process.env.INSTANCE_ID || process.env.HOSTNAME || "unknown";
|
|
1603
|
+
}
|
|
1604
|
+
function getLogLevel() {
|
|
1605
|
+
const level = process.env.LOG_LEVEL?.toLowerCase();
|
|
1606
|
+
if (level === "debug" || level === "info" || level === "warn" || level === "error") {
|
|
1607
|
+
return level;
|
|
1608
|
+
}
|
|
1609
|
+
return "info";
|
|
1610
|
+
}
|
|
1611
|
+
function isDevelopment() {
|
|
1612
|
+
return process.env.NODE_ENV === "development";
|
|
1613
|
+
}
|
|
1614
|
+
function createTransport() {
|
|
1615
|
+
if (isDevelopment()) {
|
|
1616
|
+
try {
|
|
1617
|
+
return {
|
|
1618
|
+
target: "pino-pretty",
|
|
1619
|
+
options: {
|
|
1620
|
+
destination: 2,
|
|
1621
|
+
// stderr (fd 2)
|
|
1622
|
+
colorize: true,
|
|
1623
|
+
translateTime: "HH:MM:ss.l",
|
|
1624
|
+
ignore: "pid,hostname"
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
} catch {
|
|
1628
|
+
return void 0;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
return void 0;
|
|
1632
|
+
}
|
|
1633
|
+
function createBasePinoLogger() {
|
|
1634
|
+
const level = getLogLevel();
|
|
1635
|
+
const transport = createTransport();
|
|
1636
|
+
const instanceId = getInstanceId();
|
|
1637
|
+
const options = {
|
|
1638
|
+
level,
|
|
1639
|
+
// ISO 8601 timestamp format (AC-13.1.3)
|
|
1640
|
+
timestamp: () => `,"time":"${(/* @__PURE__ */ new Date()).toISOString()}"`,
|
|
1641
|
+
// Format level as string instead of number
|
|
1642
|
+
formatters: {
|
|
1643
|
+
level: (label) => ({ level: label })
|
|
1644
|
+
},
|
|
1645
|
+
// Base bindings (can be overridden by child loggers)
|
|
1646
|
+
// Story 13.5: Add instance ID for multi-instance deployment identification
|
|
1647
|
+
base: {
|
|
1648
|
+
instanceId
|
|
1649
|
+
// AC-13.5.1: Instance ID for load balancer distribution verification
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
if (transport) {
|
|
1653
|
+
return createPino(options, pinoTransport(transport));
|
|
1654
|
+
}
|
|
1655
|
+
return createPino(options, pinoDestination({ dest: 2, sync: false }));
|
|
1656
|
+
}
|
|
1657
|
+
var basePinoLogger = createBasePinoLogger();
|
|
1658
|
+
function createLogger(module) {
|
|
1659
|
+
function getContextFields() {
|
|
1660
|
+
const context = getRequestContext();
|
|
1661
|
+
const fields = { module };
|
|
1662
|
+
if (context) {
|
|
1663
|
+
if (context.correlationId) {
|
|
1664
|
+
fields.correlationId = context.correlationId;
|
|
1665
|
+
}
|
|
1666
|
+
if (context.shopDomain) {
|
|
1667
|
+
fields.shopDomain = context.shopDomain;
|
|
1668
|
+
}
|
|
1669
|
+
if (isMultiTenantContext(context)) {
|
|
1670
|
+
fields.tenantId = context.tenantId;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
return fields;
|
|
1674
|
+
}
|
|
1675
|
+
function mergeData(data) {
|
|
1676
|
+
const contextFields = getContextFields();
|
|
1677
|
+
if (data) {
|
|
1678
|
+
const sanitizedData = sanitizeObject(data);
|
|
1679
|
+
return { ...contextFields, ...sanitizedData };
|
|
1680
|
+
}
|
|
1681
|
+
return contextFields;
|
|
1682
|
+
}
|
|
1683
|
+
function formatError(error) {
|
|
1684
|
+
return {
|
|
1685
|
+
error: {
|
|
1686
|
+
name: error.name,
|
|
1687
|
+
message: sanitizeText(error.message),
|
|
1688
|
+
stack: error.stack ? sanitizeText(error.stack) : void 0
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
const logger2 = {
|
|
1693
|
+
debug: (msg, data) => {
|
|
1694
|
+
basePinoLogger.debug(mergeData(data), sanitizeText(msg));
|
|
1695
|
+
},
|
|
1696
|
+
info: (msg, data) => {
|
|
1697
|
+
basePinoLogger.info(mergeData(data), sanitizeText(msg));
|
|
1698
|
+
},
|
|
1699
|
+
warn: (msg, data) => {
|
|
1700
|
+
basePinoLogger.warn(mergeData(data), sanitizeText(msg));
|
|
1701
|
+
},
|
|
1702
|
+
error: (msg, error, data) => {
|
|
1703
|
+
const merged = mergeData(data);
|
|
1704
|
+
if (error) {
|
|
1705
|
+
Object.assign(merged, formatError(error));
|
|
1706
|
+
captureError(error, data);
|
|
1707
|
+
}
|
|
1708
|
+
basePinoLogger.error(merged, sanitizeText(msg));
|
|
1709
|
+
},
|
|
1710
|
+
child: (bindings) => {
|
|
1711
|
+
const childModule = bindings.module || module;
|
|
1712
|
+
const childLogger = createLogger(childModule);
|
|
1713
|
+
const originalMergeData = mergeData;
|
|
1714
|
+
const enhancedMergeData = (data) => {
|
|
1715
|
+
const base = originalMergeData(data);
|
|
1716
|
+
const sanitizedBindings = sanitizeObject(bindings);
|
|
1717
|
+
return { ...base, ...sanitizedBindings };
|
|
1718
|
+
};
|
|
1719
|
+
return {
|
|
1720
|
+
debug: (msg, data) => {
|
|
1721
|
+
basePinoLogger.debug(enhancedMergeData(data), sanitizeText(msg));
|
|
1722
|
+
},
|
|
1723
|
+
info: (msg, data) => {
|
|
1724
|
+
basePinoLogger.info(enhancedMergeData(data), sanitizeText(msg));
|
|
1725
|
+
},
|
|
1726
|
+
warn: (msg, data) => {
|
|
1727
|
+
basePinoLogger.warn(enhancedMergeData(data), sanitizeText(msg));
|
|
1728
|
+
},
|
|
1729
|
+
error: (msg, error, data) => {
|
|
1730
|
+
const merged = enhancedMergeData(data);
|
|
1731
|
+
if (error) {
|
|
1732
|
+
Object.assign(merged, formatError(error));
|
|
1733
|
+
}
|
|
1734
|
+
basePinoLogger.error(merged, sanitizeText(msg));
|
|
1735
|
+
},
|
|
1736
|
+
child: childLogger.child
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
};
|
|
1740
|
+
return logger2;
|
|
1741
|
+
}
|
|
1742
|
+
function flushLogs() {
|
|
1743
|
+
basePinoLogger.flush();
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/logging/tool-execution-logger.ts
|
|
1747
|
+
import { Prisma } from "@prisma/client";
|
|
1748
|
+
|
|
1749
|
+
// src/logging/sanitize.ts
|
|
1750
|
+
var MAX_STRING_LENGTH = 1e3;
|
|
1751
|
+
var MAX_ARRAY_ITEMS = 10;
|
|
1752
|
+
var MAX_RECURSION_DEPTH = 5;
|
|
1753
|
+
var MAX_OUTPUT_BYTES = 5e3;
|
|
1754
|
+
var TOKEN_PATTERNS = [
|
|
1755
|
+
// Shopify access tokens (shpat_xxx, shpua_xxx) - include underscores in the pattern
|
|
1756
|
+
{ pattern: /shpat_[a-zA-Z0-9_]+/g, replacement: "[REDACTED]" },
|
|
1757
|
+
{ pattern: /shpua_[a-zA-Z0-9_]+/g, replacement: "[REDACTED]" },
|
|
1758
|
+
// Bearer tokens
|
|
1759
|
+
{ pattern: /Bearer\s+[a-zA-Z0-9_.-]+/g, replacement: "Bearer [REDACTED]" },
|
|
1760
|
+
// OAuth access tokens (mcp_access_xxx)
|
|
1761
|
+
{ pattern: /mcp_access_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
1762
|
+
// Live/test API keys (sk_live_xxx, sk_test_xxx)
|
|
1763
|
+
{ pattern: /sk_live_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
1764
|
+
{ pattern: /sk_test_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
1765
|
+
// Generic access_token and client_secret patterns
|
|
1766
|
+
{ pattern: /access_token[=:]\s*[a-zA-Z0-9_.-]+/gi, replacement: "access_token=[REDACTED]" },
|
|
1767
|
+
{ pattern: /client_secret[=:]\s*[a-zA-Z0-9_.-]+/gi, replacement: "client_secret=[REDACTED]" }
|
|
1768
|
+
];
|
|
1769
|
+
function redactTokens(text) {
|
|
1770
|
+
let result = text;
|
|
1771
|
+
for (const { pattern, replacement } of TOKEN_PATTERNS) {
|
|
1772
|
+
result = result.replace(pattern, replacement);
|
|
1773
|
+
}
|
|
1774
|
+
return result;
|
|
1775
|
+
}
|
|
1776
|
+
function truncateString(text, maxLength = MAX_STRING_LENGTH) {
|
|
1777
|
+
if (text.length <= maxLength) {
|
|
1778
|
+
return text;
|
|
1779
|
+
}
|
|
1780
|
+
return `${text.substring(0, maxLength)}...[TRUNCATED]`;
|
|
1781
|
+
}
|
|
1782
|
+
function sanitizeParams(params, depth = 0) {
|
|
1783
|
+
if (depth > MAX_RECURSION_DEPTH) {
|
|
1784
|
+
return "[TRUNCATED]";
|
|
1785
|
+
}
|
|
1786
|
+
if (params === null || params === void 0) {
|
|
1787
|
+
return params;
|
|
1788
|
+
}
|
|
1789
|
+
if (typeof params === "string") {
|
|
1790
|
+
const redacted = redactTokens(params);
|
|
1791
|
+
return truncateString(redacted);
|
|
1792
|
+
}
|
|
1793
|
+
if (typeof params !== "object") {
|
|
1794
|
+
return params;
|
|
1795
|
+
}
|
|
1796
|
+
if (Array.isArray(params)) {
|
|
1797
|
+
const limited = params.slice(0, MAX_ARRAY_ITEMS);
|
|
1798
|
+
return limited.map((item) => sanitizeParams(item, depth + 1));
|
|
1799
|
+
}
|
|
1800
|
+
const result = {};
|
|
1801
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1802
|
+
result[key] = sanitizeParams(value, depth + 1);
|
|
1803
|
+
}
|
|
1804
|
+
return result;
|
|
1805
|
+
}
|
|
1806
|
+
function getByteSize(value) {
|
|
1807
|
+
try {
|
|
1808
|
+
return JSON.stringify(value).length;
|
|
1809
|
+
} catch {
|
|
1810
|
+
return 0;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
function truncateOutput(output, maxBytes = MAX_OUTPUT_BYTES) {
|
|
1814
|
+
const sanitized = sanitizeParams(output);
|
|
1815
|
+
const size = getByteSize(sanitized);
|
|
1816
|
+
if (size <= maxBytes) {
|
|
1817
|
+
return sanitized;
|
|
1818
|
+
}
|
|
1819
|
+
if (typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
|
|
1820
|
+
const obj = sanitized;
|
|
1821
|
+
if ("data" in obj || "errors" in obj) {
|
|
1822
|
+
const result = {};
|
|
1823
|
+
if (obj.errors) {
|
|
1824
|
+
result.errors = obj.errors;
|
|
1825
|
+
}
|
|
1826
|
+
if (obj.data) {
|
|
1827
|
+
result.data = "[TRUNCATED - output exceeded 5KB]";
|
|
1828
|
+
}
|
|
1829
|
+
if (obj.extensions && getByteSize(obj.extensions) < 200) {
|
|
1830
|
+
result.extensions = obj.extensions;
|
|
1831
|
+
}
|
|
1832
|
+
return result;
|
|
1833
|
+
}
|
|
1834
|
+
return {
|
|
1835
|
+
_truncated: true,
|
|
1836
|
+
_message: `Output truncated: ${size} bytes exceeded ${maxBytes} byte limit`,
|
|
1837
|
+
_keys: Object.keys(obj).slice(0, 10)
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
if (Array.isArray(sanitized)) {
|
|
1841
|
+
return {
|
|
1842
|
+
_truncated: true,
|
|
1843
|
+
_message: `Array truncated: ${sanitized.length} items, ${size} bytes exceeded ${maxBytes} byte limit`,
|
|
1844
|
+
_sample: sanitized.slice(0, 3)
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
return "[TRUNCATED - output exceeded 5KB]";
|
|
1848
|
+
}
|
|
1849
|
+
function sanitizeErrorMessage2(error) {
|
|
1850
|
+
if (error instanceof Error) {
|
|
1851
|
+
return truncateString(redactTokens(error.message), 500);
|
|
1852
|
+
}
|
|
1853
|
+
if (typeof error === "string") {
|
|
1854
|
+
return truncateString(redactTokens(error), 500);
|
|
1855
|
+
}
|
|
1856
|
+
return "Unknown error";
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// src/logging/tool-execution-logger.ts
|
|
1860
|
+
var logger = createLogger("logging/tool-execution-logger");
|
|
1861
|
+
var VALIDATION_ERROR_KEYWORDS = [
|
|
1862
|
+
"validation failed",
|
|
1863
|
+
"required",
|
|
1864
|
+
"must be",
|
|
1865
|
+
"expected",
|
|
1866
|
+
"cannot be empty",
|
|
1867
|
+
"is not allowed",
|
|
1868
|
+
"malformed",
|
|
1869
|
+
"parse error",
|
|
1870
|
+
"zod"
|
|
1871
|
+
];
|
|
1872
|
+
var AUTH_ERROR_KEYWORDS = [
|
|
1873
|
+
"unauthorized",
|
|
1874
|
+
"unauthenticated",
|
|
1875
|
+
"forbidden",
|
|
1876
|
+
"access denied",
|
|
1877
|
+
"not authorized",
|
|
1878
|
+
"authentication required",
|
|
1879
|
+
"authentication failed",
|
|
1880
|
+
"permission denied",
|
|
1881
|
+
"invalid api key",
|
|
1882
|
+
"invalid token",
|
|
1883
|
+
"token expired"
|
|
1884
|
+
];
|
|
1885
|
+
var TIMEOUT_KEYWORDS = ["timeout", "timed out", "deadline exceeded", "econnreset", "socket hang"];
|
|
1886
|
+
function classifyError(error) {
|
|
1887
|
+
let message;
|
|
1888
|
+
if (error instanceof Error) {
|
|
1889
|
+
message = error.message.toLowerCase();
|
|
1890
|
+
} else if (error !== null && typeof error === "object" && "message" in error && typeof error.message === "string") {
|
|
1891
|
+
message = error.message.toLowerCase();
|
|
1892
|
+
} else {
|
|
1893
|
+
message = String(error).toLowerCase();
|
|
1894
|
+
}
|
|
1895
|
+
const errorName = error instanceof Error ? error.name : typeof error === "object" && error !== null ? "Object" : "Error";
|
|
1896
|
+
const isToolError = error !== null && typeof error === "object" && "name" in error && error.name === "ToolError";
|
|
1897
|
+
if (AUTH_ERROR_KEYWORDS.some((kw) => message.includes(kw))) {
|
|
1898
|
+
return {
|
|
1899
|
+
status: "AUTH_ERROR",
|
|
1900
|
+
errorCode: isToolError ? "TOOL_AUTH_ERROR" : "AUTH_ERROR"
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
if (VALIDATION_ERROR_KEYWORDS.some((kw) => message.includes(kw))) {
|
|
1904
|
+
return {
|
|
1905
|
+
status: "VALIDATION_ERROR",
|
|
1906
|
+
errorCode: isToolError ? "TOOL_VALIDATION_ERROR" : "VALIDATION_ERROR"
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
if (isToolError) {
|
|
1910
|
+
return { status: "ERROR", errorCode: "TOOL_ERROR" };
|
|
1911
|
+
}
|
|
1912
|
+
if (TIMEOUT_KEYWORDS.some((kw) => message.includes(kw))) {
|
|
1913
|
+
return { status: "TIMEOUT", errorCode: "TIMEOUT" };
|
|
1914
|
+
}
|
|
1915
|
+
if (message.includes("graphql") || errorName === "GraphqlQueryError") {
|
|
1916
|
+
return { status: "ERROR", errorCode: "GRAPHQL_ERROR" };
|
|
1917
|
+
}
|
|
1918
|
+
return { status: "ERROR", errorCode: "UNKNOWN_ERROR" };
|
|
1919
|
+
}
|
|
1920
|
+
var ToolExecutionLogger = class {
|
|
1921
|
+
prisma;
|
|
1922
|
+
/**
|
|
1923
|
+
* Create a new ToolExecutionLogger
|
|
1924
|
+
*
|
|
1925
|
+
* @param prisma - Prisma client for database access
|
|
1926
|
+
*/
|
|
1927
|
+
constructor(prisma) {
|
|
1928
|
+
this.prisma = prisma;
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Log a tool execution (fire-and-forget)
|
|
1932
|
+
*
|
|
1933
|
+
* This method does NOT await the database insert. The insert runs
|
|
1934
|
+
* asynchronously and any errors are caught and logged, not thrown.
|
|
1935
|
+
*
|
|
1936
|
+
* AC-16.2.7: Fire-and-forget pattern with max 5ms overhead.
|
|
1937
|
+
*
|
|
1938
|
+
* @param entry - Tool execution entry to log
|
|
1939
|
+
*
|
|
1940
|
+
* @example
|
|
1941
|
+
* ```typescript
|
|
1942
|
+
* logger.logExecution({
|
|
1943
|
+
* tenantId: 'tenant-uuid',
|
|
1944
|
+
* toolName: 'create-product',
|
|
1945
|
+
* toolModule: 'products',
|
|
1946
|
+
* clientType: 'api_key',
|
|
1947
|
+
* apiKeyId: 'apikey-uuid',
|
|
1948
|
+
* inputParams: { title: 'New Product' },
|
|
1949
|
+
* output: { productId: '123' },
|
|
1950
|
+
* status: 'SUCCESS',
|
|
1951
|
+
* durationMs: 150,
|
|
1952
|
+
* });
|
|
1953
|
+
* // Returns immediately, database insert happens async
|
|
1954
|
+
* ```
|
|
1955
|
+
*/
|
|
1956
|
+
logExecution(entry) {
|
|
1957
|
+
if (!entry.tenantId) {
|
|
1958
|
+
logger.debug("Skipping tool execution log: no tenantId", { toolName: entry.toolName });
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
const sanitizedInput = entry.inputParams ? sanitizeParams(entry.inputParams) : void 0;
|
|
1962
|
+
const sanitizedOutput = entry.output ? truncateOutput(entry.output) : void 0;
|
|
1963
|
+
const sanitizedErrorMessage = entry.errorMessage ? sanitizeErrorMessage2(entry.errorMessage) : void 0;
|
|
1964
|
+
const logData = {
|
|
1965
|
+
tenant: { connect: { id: entry.tenantId } },
|
|
1966
|
+
toolName: entry.toolName,
|
|
1967
|
+
toolModule: entry.toolModule,
|
|
1968
|
+
sessionId: entry.sessionId,
|
|
1969
|
+
correlationId: entry.correlationId,
|
|
1970
|
+
clientType: entry.clientType,
|
|
1971
|
+
apiKey: entry.apiKeyId ? { connect: { id: entry.apiKeyId } } : void 0,
|
|
1972
|
+
oauthClientId: entry.oauthClientId,
|
|
1973
|
+
shop: entry.shopId ? { connect: { id: entry.shopId } } : void 0,
|
|
1974
|
+
shopDomain: entry.shopDomain,
|
|
1975
|
+
inputParams: sanitizedInput ?? Prisma.JsonNull,
|
|
1976
|
+
outputSummary: sanitizedOutput ?? Prisma.JsonNull,
|
|
1977
|
+
status: entry.status,
|
|
1978
|
+
errorCode: entry.errorCode,
|
|
1979
|
+
errorMessage: sanitizedErrorMessage,
|
|
1980
|
+
durationMs: entry.durationMs,
|
|
1981
|
+
ipAddress: entry.ipAddress,
|
|
1982
|
+
userAgent: entry.userAgent
|
|
1983
|
+
};
|
|
1984
|
+
this.prisma.toolExecutionLog.create({ data: logData }).catch((err) => {
|
|
1985
|
+
logger.error("Failed to log tool execution", err, {
|
|
1986
|
+
toolName: entry.toolName,
|
|
1987
|
+
tenantId: entry.tenantId?.substring(0, 8)
|
|
1988
|
+
});
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
var toolExecutionLoggerInstance = null;
|
|
1993
|
+
function getToolExecutionLogger(prisma) {
|
|
1994
|
+
if (!toolExecutionLoggerInstance) {
|
|
1995
|
+
if (!prisma) {
|
|
1996
|
+
throw new Error("ToolExecutionLogger not initialized. Call with Prisma client first.");
|
|
1997
|
+
}
|
|
1998
|
+
toolExecutionLoggerInstance = new ToolExecutionLogger(prisma);
|
|
1999
|
+
}
|
|
2000
|
+
return toolExecutionLoggerInstance;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
1311
2003
|
// src/shopify/client.ts
|
|
1312
2004
|
import "@shopify/shopify-api/adapters/node";
|
|
1313
2005
|
import { shopifyApi } from "@shopify/shopify-api";
|
|
1314
2006
|
|
|
1315
2007
|
// src/utils/errors.ts
|
|
1316
|
-
var
|
|
2008
|
+
var sanitizeErrorMessage3 = sanitizeLogMessage;
|
|
1317
2009
|
var ToolError = class _ToolError extends Error {
|
|
1318
2010
|
/** AI-friendly suggestion for error recovery */
|
|
1319
2011
|
suggestion;
|
|
@@ -1343,8 +2035,8 @@ function extractErrorMessage(error) {
|
|
|
1343
2035
|
}
|
|
1344
2036
|
function createToolError(error, suggestion) {
|
|
1345
2037
|
const message = extractErrorMessage(error);
|
|
1346
|
-
const safeMessage =
|
|
1347
|
-
const safeSuggestion =
|
|
2038
|
+
const safeMessage = sanitizeErrorMessage3(message);
|
|
2039
|
+
const safeSuggestion = sanitizeErrorMessage3(suggestion);
|
|
1348
2040
|
return {
|
|
1349
2041
|
isError: true,
|
|
1350
2042
|
content: [
|
|
@@ -1787,52 +2479,6 @@ async function validateShopifyToken(shopDomain, accessToken) {
|
|
|
1787
2479
|
}
|
|
1788
2480
|
}
|
|
1789
2481
|
|
|
1790
|
-
// src/types/context.ts
|
|
1791
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
1792
|
-
function createSingleTenantContext(client, shopDomain) {
|
|
1793
|
-
return {
|
|
1794
|
-
client,
|
|
1795
|
-
shopDomain
|
|
1796
|
-
// userId and apiKeyId are undefined in single-tenant mode
|
|
1797
|
-
};
|
|
1798
|
-
}
|
|
1799
|
-
function createMultiTenantContext(client, shopDomain, tenantId, apiKeyId, allowedShops) {
|
|
1800
|
-
return {
|
|
1801
|
-
client,
|
|
1802
|
-
shopDomain,
|
|
1803
|
-
userId: tenantId,
|
|
1804
|
-
// Map tenantId to userId for compatibility
|
|
1805
|
-
apiKeyId,
|
|
1806
|
-
tenantId,
|
|
1807
|
-
allowedShops
|
|
1808
|
-
};
|
|
1809
|
-
}
|
|
1810
|
-
var requestContextStorage = new AsyncLocalStorage();
|
|
1811
|
-
var fallbackContextStorage = /* @__PURE__ */ new Map();
|
|
1812
|
-
function setFallbackContext(key, context) {
|
|
1813
|
-
fallbackContextStorage.set(key, context);
|
|
1814
|
-
}
|
|
1815
|
-
function clearFallbackContext(key) {
|
|
1816
|
-
fallbackContextStorage.delete(key);
|
|
1817
|
-
}
|
|
1818
|
-
var currentContextKey;
|
|
1819
|
-
function setCurrentContextKey(key) {
|
|
1820
|
-
currentContextKey = key;
|
|
1821
|
-
}
|
|
1822
|
-
function getRequestContext() {
|
|
1823
|
-
const asyncContext = requestContextStorage.getStore();
|
|
1824
|
-
if (asyncContext) {
|
|
1825
|
-
return asyncContext;
|
|
1826
|
-
}
|
|
1827
|
-
if (currentContextKey) {
|
|
1828
|
-
return fallbackContextStorage.get(currentContextKey);
|
|
1829
|
-
}
|
|
1830
|
-
return void 0;
|
|
1831
|
-
}
|
|
1832
|
-
function isMultiTenantContext(context) {
|
|
1833
|
-
return "tenantId" in context && typeof context.tenantId === "string";
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
2482
|
// src/tools/registration.ts
|
|
1837
2483
|
var KEBAB_CASE_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
1838
2484
|
function deriveDefaultAnnotations(name) {
|
|
@@ -1888,18 +2534,73 @@ function validateInput(schema, input, toolName) {
|
|
|
1888
2534
|
}
|
|
1889
2535
|
return result.data;
|
|
1890
2536
|
}
|
|
2537
|
+
function logToolExecution(toolName, params, result, status, startTime, error) {
|
|
2538
|
+
let config;
|
|
2539
|
+
try {
|
|
2540
|
+
config = getConfig();
|
|
2541
|
+
} catch {
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
if (!isRemoteMode(config)) {
|
|
2545
|
+
log.debug(`Tool execution logging skipped in local mode: ${toolName}`);
|
|
2546
|
+
return;
|
|
2547
|
+
}
|
|
2548
|
+
const context = getRequestContext();
|
|
2549
|
+
if (!context || !isMultiTenantContext(context)) {
|
|
2550
|
+
log.debug(`Tool execution logging skipped: no multi-tenant context for ${toolName}`);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
const endTime = performance.now();
|
|
2554
|
+
const durationMs = Math.round(endTime - startTime);
|
|
2555
|
+
const multiTenantContext = context;
|
|
2556
|
+
const hasOAuthClient = !!multiTenantContext.oauthClientId;
|
|
2557
|
+
const clientType = hasOAuthClient ? "oauth_client" : "api_key";
|
|
2558
|
+
let errorClassification;
|
|
2559
|
+
if (typeof status === "object") {
|
|
2560
|
+
errorClassification = status;
|
|
2561
|
+
} else if (error) {
|
|
2562
|
+
errorClassification = classifyError(error);
|
|
2563
|
+
}
|
|
2564
|
+
const entry = {
|
|
2565
|
+
tenantId: multiTenantContext.tenantId,
|
|
2566
|
+
toolName,
|
|
2567
|
+
correlationId: context.correlationId,
|
|
2568
|
+
clientType,
|
|
2569
|
+
apiKeyId: !hasOAuthClient ? multiTenantContext.apiKeyId : void 0,
|
|
2570
|
+
oauthClientId: hasOAuthClient ? multiTenantContext.oauthClientId : void 0,
|
|
2571
|
+
shopDomain: context.shopDomain,
|
|
2572
|
+
inputParams: params,
|
|
2573
|
+
output: result,
|
|
2574
|
+
status: errorClassification ? errorClassification.status : "SUCCESS",
|
|
2575
|
+
errorCode: errorClassification?.errorCode,
|
|
2576
|
+
errorMessage: error instanceof Error ? error.message : error ? String(error) : void 0,
|
|
2577
|
+
durationMs
|
|
2578
|
+
};
|
|
2579
|
+
try {
|
|
2580
|
+
const logger2 = getToolExecutionLogger();
|
|
2581
|
+
logger2.logExecution(entry);
|
|
2582
|
+
} catch (logError) {
|
|
2583
|
+
log.debug(
|
|
2584
|
+
`Tool execution logger not available: ${logError instanceof Error ? logError.message : "unknown"}`
|
|
2585
|
+
);
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
1891
2588
|
function wrapToolHandler(toolName, schema, handler) {
|
|
1892
2589
|
return async (params) => {
|
|
2590
|
+
const startTime = performance.now();
|
|
1893
2591
|
log.debug(`Tool invoked: ${toolName}`);
|
|
1894
2592
|
try {
|
|
1895
2593
|
const validatedParams = validateInput(schema, params, toolName);
|
|
1896
2594
|
const result = await handler(validatedParams);
|
|
1897
2595
|
log.debug(`Tool ${toolName} completed successfully`);
|
|
2596
|
+
logToolExecution(toolName, params, result, "SUCCESS", startTime);
|
|
1898
2597
|
return createToolSuccess(result);
|
|
1899
2598
|
} catch (error) {
|
|
1900
2599
|
log.error(
|
|
1901
2600
|
`Tool ${toolName} failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1902
2601
|
);
|
|
2602
|
+
const errorClass = classifyError(error);
|
|
2603
|
+
logToolExecution(toolName, params, null, errorClass, startTime, error);
|
|
1903
2604
|
let suggestion;
|
|
1904
2605
|
if (error instanceof ToolError) {
|
|
1905
2606
|
suggestion = error.suggestion;
|
|
@@ -13981,7 +14682,10 @@ export {
|
|
|
13981
14682
|
setCurrentContextKey,
|
|
13982
14683
|
getRequestContext,
|
|
13983
14684
|
isMultiTenantContext,
|
|
13984
|
-
|
|
14685
|
+
initSentry,
|
|
14686
|
+
sentryRequestMiddleware,
|
|
14687
|
+
sentryErrorHandler,
|
|
14688
|
+
sanitizeErrorMessage3 as sanitizeErrorMessage,
|
|
13985
14689
|
createShopifyClient,
|
|
13986
14690
|
getShopifyClient,
|
|
13987
14691
|
validateShopifyToken,
|
|
@@ -13994,6 +14698,10 @@ export {
|
|
|
13994
14698
|
getStorePolicies,
|
|
13995
14699
|
getStoreAlerts,
|
|
13996
14700
|
getStoreInfo,
|
|
14701
|
+
generateCorrelationId,
|
|
14702
|
+
correlationMiddleware,
|
|
14703
|
+
createLogger,
|
|
14704
|
+
flushLogs,
|
|
13997
14705
|
deriveDefaultAnnotations,
|
|
13998
14706
|
validateToolName,
|
|
13999
14707
|
isValidToolName,
|