@adobe-commerce/aio-toolkit 1.0.13 → 1.0.15
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 +116 -0
- package/README.md +122 -9
- package/dist/aio-toolkit-cursor-context/bin/cli.js +1089 -0
- package/dist/aio-toolkit-cursor-context/bin/cli.js.map +1 -0
- package/dist/index.d.mts +43 -16
- package/dist/index.d.ts +43 -16
- package/dist/index.js +1030 -166
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1034 -167
- package/dist/index.mjs.map +1 -1
- package/files/cursor-context/commands/aio-toolkit-create-event-consumer-action.md +562 -0
- package/files/cursor-context/commands/aio-toolkit-create-graphql-action.md +531 -0
- package/files/cursor-context/commands/aio-toolkit-create-runtime-action.md +293 -0
- package/files/cursor-context/commands/aio-toolkit-create-webhook-action.md +439 -0
- package/files/cursor-context/rules/aio-toolkit-create-adobe-commerce-client.mdc +1321 -0
- package/files/cursor-context/rules/aio-toolkit-oop-best-practices.mdc +331 -0
- package/files/cursor-context/rules/aio-toolkit-setup-new-relic-telemetry.mdc +354 -0
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1321,7 +1321,96 @@ var new_relic_default = NewRelicTelemetry;
|
|
|
1321
1321
|
// src/framework/telemetry/index.ts
|
|
1322
1322
|
var _Telemetry = class _Telemetry {
|
|
1323
1323
|
/**
|
|
1324
|
-
* Create a
|
|
1324
|
+
* Create a new Telemetry instance
|
|
1325
|
+
*
|
|
1326
|
+
* Initializes the telemetry manager with optional runtime parameters.
|
|
1327
|
+
* Parameters can be set later using setParams() if not provided during construction.
|
|
1328
|
+
*
|
|
1329
|
+
* @param params - Optional runtime parameters containing telemetry configuration
|
|
1330
|
+
*
|
|
1331
|
+
* @example
|
|
1332
|
+
* ```typescript
|
|
1333
|
+
* const telemetry = new Telemetry({
|
|
1334
|
+
* ENABLE_TELEMETRY: true,
|
|
1335
|
+
* LOG_LEVEL: 'debug',
|
|
1336
|
+
* action_type: 'webhook'
|
|
1337
|
+
* });
|
|
1338
|
+
* ```
|
|
1339
|
+
*/
|
|
1340
|
+
constructor(params = void 0) {
|
|
1341
|
+
/**
|
|
1342
|
+
* Runtime parameters containing telemetry configuration
|
|
1343
|
+
*
|
|
1344
|
+
* Stores the action parameters including telemetry flags (ENABLE_TELEMETRY),
|
|
1345
|
+
* provider configuration (NEW_RELIC_*), log levels, and request context.
|
|
1346
|
+
*
|
|
1347
|
+
* @private
|
|
1348
|
+
*/
|
|
1349
|
+
this.params = void 0;
|
|
1350
|
+
/**
|
|
1351
|
+
* Logger instance
|
|
1352
|
+
*
|
|
1353
|
+
* Stores the logger instance for the telemetry instance.
|
|
1354
|
+
*
|
|
1355
|
+
* @private
|
|
1356
|
+
*/
|
|
1357
|
+
this.logger = void 0;
|
|
1358
|
+
this.params = params;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Get the current runtime parameters
|
|
1362
|
+
*
|
|
1363
|
+
* Returns the parameters stored in this telemetry instance, or an empty object
|
|
1364
|
+
* if no parameters have been set. These parameters control telemetry behavior
|
|
1365
|
+
* and provide context for logging and tracing.
|
|
1366
|
+
*
|
|
1367
|
+
* @returns Runtime parameters object (never undefined, returns empty object if not set)
|
|
1368
|
+
*
|
|
1369
|
+
* @example
|
|
1370
|
+
* ```typescript
|
|
1371
|
+
* const currentParams = telemetry.getParams();
|
|
1372
|
+
* if (currentParams.ENABLE_TELEMETRY) {
|
|
1373
|
+
* // telemetry is enabled
|
|
1374
|
+
* }
|
|
1375
|
+
* ```
|
|
1376
|
+
*/
|
|
1377
|
+
getParams() {
|
|
1378
|
+
return this.params || {};
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Set or update the runtime parameters
|
|
1382
|
+
*
|
|
1383
|
+
* Updates the parameters used by this telemetry instance. This is typically
|
|
1384
|
+
* called by the initialize() method when the action receives runtime parameters,
|
|
1385
|
+
* but can be used to update configuration at any time.
|
|
1386
|
+
*
|
|
1387
|
+
* @param params - New runtime parameters to store
|
|
1388
|
+
*
|
|
1389
|
+
* @example
|
|
1390
|
+
* ```typescript
|
|
1391
|
+
* telemetry.setParams({
|
|
1392
|
+
* ENABLE_TELEMETRY: true,
|
|
1393
|
+
* NEW_RELIC_TELEMETRY: true,
|
|
1394
|
+
* LOG_LEVEL: 'info'
|
|
1395
|
+
* });
|
|
1396
|
+
* const logger = telemetry.createLogger('my-action');
|
|
1397
|
+
* ```
|
|
1398
|
+
*/
|
|
1399
|
+
setParams(params) {
|
|
1400
|
+
this.params = params;
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Get the logger instance
|
|
1404
|
+
*
|
|
1405
|
+
* Returns the logger instance for the telemetry instance.
|
|
1406
|
+
*
|
|
1407
|
+
* @returns Logger instance
|
|
1408
|
+
*/
|
|
1409
|
+
getLogger() {
|
|
1410
|
+
return this.logger;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Create a logger with standard configuration and automatic metadata injection
|
|
1325
1414
|
*
|
|
1326
1415
|
* This method creates a structured logger and wraps it to automatically add
|
|
1327
1416
|
* contextual metadata to all log calls:
|
|
@@ -1333,50 +1422,30 @@ var _Telemetry = class _Telemetry {
|
|
|
1333
1422
|
* automatically appear in all logs sent to New Relic if ENABLE_TELEMETRY is true.
|
|
1334
1423
|
*
|
|
1335
1424
|
* @param name - Logger name (typically action name)
|
|
1336
|
-
* @param params - Runtime parameters containing LOG_LEVEL, optional ENABLE_TELEMETRY, ENVIRONMENT, action_type, and __ow_headers
|
|
1337
1425
|
* @returns Configured logger instance with automatic metadata injection
|
|
1338
1426
|
*
|
|
1339
|
-
* @example
|
|
1340
|
-
* ```typescript
|
|
1341
|
-
* const logger = Telemetry.createLogger("my-action", params);
|
|
1342
|
-
* logger.info("Processing started");
|
|
1343
|
-
* // Logs: "Processing started"
|
|
1344
|
-
* ```
|
|
1345
|
-
*
|
|
1346
|
-
* @example JSON object message with automatic metadata
|
|
1427
|
+
* @example
|
|
1347
1428
|
* ```typescript
|
|
1348
|
-
* const logger =
|
|
1349
|
-
*
|
|
1350
|
-
*
|
|
1351
|
-
* __ow_headers: { 'x-adobe-commerce-request-id': 'req-123' }
|
|
1352
|
-
* });
|
|
1353
|
-
* logger.info({
|
|
1354
|
-
* message: "User action completed",
|
|
1355
|
-
* user_id: "123"
|
|
1356
|
-
* });
|
|
1357
|
-
* // In New Relic: {
|
|
1358
|
-
* // message: "User action completed",
|
|
1359
|
-
* // user_id: "123",
|
|
1360
|
-
* // "x-adobe-commerce-request-id": "req-123",
|
|
1361
|
-
* // "action.type": "webhook",
|
|
1362
|
-
* // environment: "development",
|
|
1363
|
-
* // ...
|
|
1364
|
-
* // }
|
|
1429
|
+
* const logger = telemetry.createLogger('my-action');
|
|
1430
|
+
* logger.info({ message: 'User action completed', user_id: '123' });
|
|
1431
|
+
* // In New Relic: includes message, user_id, request-id, action.type, environment
|
|
1365
1432
|
* ```
|
|
1366
1433
|
*/
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
level: params
|
|
1370
|
-
}) : (0, import_aio_lib_telemetry3.getLogger)(name, {
|
|
1371
|
-
level: params.LOG_LEVEL || "info"
|
|
1434
|
+
createLogger(name) {
|
|
1435
|
+
let baseLogger = import_aio_sdk.Core.Logger(name, {
|
|
1436
|
+
level: this.params?.LOG_LEVEL || "info"
|
|
1372
1437
|
});
|
|
1438
|
+
if (this.params?.ENABLE_TELEMETRY === true) {
|
|
1439
|
+
const helpers = (0, import_aio_lib_telemetry3.getInstrumentationHelpers)();
|
|
1440
|
+
baseLogger = helpers.logger;
|
|
1441
|
+
}
|
|
1373
1442
|
const metadata = {};
|
|
1374
|
-
const headers = params
|
|
1443
|
+
const headers = this.params?.__ow_headers;
|
|
1375
1444
|
const requestId = headers?.["x-adobe-commerce-request-id"];
|
|
1376
1445
|
if (requestId && requestId !== "") {
|
|
1377
1446
|
metadata["x-adobe-commerce-request-id"] = requestId;
|
|
1378
1447
|
}
|
|
1379
|
-
const actionType = params
|
|
1448
|
+
const actionType = this.params?.action_type;
|
|
1380
1449
|
if (actionType && actionType !== "") {
|
|
1381
1450
|
metadata["action.type"] = actionType;
|
|
1382
1451
|
}
|
|
@@ -1413,10 +1482,11 @@ var _Telemetry = class _Telemetry {
|
|
|
1413
1482
|
}
|
|
1414
1483
|
}, "error")
|
|
1415
1484
|
};
|
|
1416
|
-
|
|
1485
|
+
this.logger = {
|
|
1417
1486
|
...baseLogger,
|
|
1418
1487
|
...wrapper
|
|
1419
1488
|
};
|
|
1489
|
+
return this.logger;
|
|
1420
1490
|
}
|
|
1421
1491
|
/**
|
|
1422
1492
|
* Extract structured error information for logging
|
|
@@ -1430,13 +1500,13 @@ var _Telemetry = class _Telemetry {
|
|
|
1430
1500
|
* @example
|
|
1431
1501
|
* ```typescript
|
|
1432
1502
|
* try {
|
|
1433
|
-
*
|
|
1503
|
+
* await processOrder(orderId);
|
|
1434
1504
|
* } catch (error) {
|
|
1435
|
-
* logger.error(
|
|
1505
|
+
* logger.error({ message: 'Operation failed', ...telemetry.formatError(error) });
|
|
1436
1506
|
* }
|
|
1437
1507
|
* ```
|
|
1438
1508
|
*/
|
|
1439
|
-
|
|
1509
|
+
formatError(error) {
|
|
1440
1510
|
if (error instanceof Error) {
|
|
1441
1511
|
return {
|
|
1442
1512
|
error_name: error.name,
|
|
@@ -1446,6 +1516,77 @@ var _Telemetry = class _Telemetry {
|
|
|
1446
1516
|
}
|
|
1447
1517
|
return { error: String(error) };
|
|
1448
1518
|
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Get the current OpenTelemetry span for adding attributes and events
|
|
1521
|
+
*
|
|
1522
|
+
* This method provides access to the currently active span, allowing you to
|
|
1523
|
+
* add custom attributes and events for enhanced observability in New Relic.
|
|
1524
|
+
*
|
|
1525
|
+
* **IMPORTANT**: This must be called within an active trace context
|
|
1526
|
+
* (i.e., inside a function that has been wrapped by Telemetry.initialize()).
|
|
1527
|
+
* If called outside an active context or if ENABLE_TELEMETRY is false, it returns null.
|
|
1528
|
+
*
|
|
1529
|
+
* @returns The current span object, or null if no active span exists
|
|
1530
|
+
*
|
|
1531
|
+
* @example
|
|
1532
|
+
* ```typescript
|
|
1533
|
+
* const processOrder = async (orderId: string) => {
|
|
1534
|
+
* const span = telemetry.getCurrentSpan();
|
|
1535
|
+
* if (span) {
|
|
1536
|
+
* span.setAttribute('order.id', orderId);
|
|
1537
|
+
* span.setAttribute('order.status', 'processing');
|
|
1538
|
+
* span.addEvent('processing-started');
|
|
1539
|
+
* }
|
|
1540
|
+
* // ... process order logic ...
|
|
1541
|
+
* };
|
|
1542
|
+
* ```
|
|
1543
|
+
*/
|
|
1544
|
+
getCurrentSpan() {
|
|
1545
|
+
if (this.params?.ENABLE_TELEMETRY === true) {
|
|
1546
|
+
const helpers = (0, import_aio_lib_telemetry3.getInstrumentationHelpers)();
|
|
1547
|
+
return helpers?.currentSpan || null;
|
|
1548
|
+
}
|
|
1549
|
+
return null;
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Instrument a function with OpenTelemetry for distributed tracing
|
|
1553
|
+
*
|
|
1554
|
+
* This method wraps any function with OpenTelemetry instrumentation,
|
|
1555
|
+
* creating a child span in the current trace context. Use this to create
|
|
1556
|
+
* detailed spans for specific operations within your action.
|
|
1557
|
+
*
|
|
1558
|
+
* **IMPORTANT**: The instrumented function must be called within an active trace context
|
|
1559
|
+
* (i.e., inside a function that has been wrapped by Telemetry.initialize()).
|
|
1560
|
+
* If called outside an active context, the span will not be created or exported.
|
|
1561
|
+
*
|
|
1562
|
+
* @param spanName - Name for the span (appears in New Relic as the span name)
|
|
1563
|
+
* @param fn - The function to instrument (sync or async)
|
|
1564
|
+
* @returns The instrumented function that creates a child span when called
|
|
1565
|
+
*
|
|
1566
|
+
* @example
|
|
1567
|
+
* ```typescript
|
|
1568
|
+
* const fetchUserData = telemetry.instrument(
|
|
1569
|
+
* 'fetch-user-data',
|
|
1570
|
+
* async (userId: string) => {
|
|
1571
|
+
* const span = telemetry.getCurrentSpan();
|
|
1572
|
+
* if (span) {
|
|
1573
|
+
* span.setAttribute('user.id', userId);
|
|
1574
|
+
* span.addEvent('fetching-started');
|
|
1575
|
+
* }
|
|
1576
|
+
* const response = await fetch(`/api/users/${userId}`);
|
|
1577
|
+
* return response.json();
|
|
1578
|
+
* }
|
|
1579
|
+
* );
|
|
1580
|
+
* const userData = await fetchUserData('123');
|
|
1581
|
+
* ```
|
|
1582
|
+
*/
|
|
1583
|
+
instrument(spanName, fn) {
|
|
1584
|
+
return (0, import_aio_lib_telemetry3.instrument)(fn, {
|
|
1585
|
+
spanConfig: {
|
|
1586
|
+
spanName
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1449
1590
|
/**
|
|
1450
1591
|
* Initialize telemetry for a runtime action with provider fallback chain
|
|
1451
1592
|
*
|
|
@@ -1463,60 +1604,20 @@ var _Telemetry = class _Telemetry {
|
|
|
1463
1604
|
* is returned to alert you of the misconfiguration. This prevents silently running
|
|
1464
1605
|
* without telemetry when you expect it to be working.
|
|
1465
1606
|
*
|
|
1466
|
-
* Environment Configuration:
|
|
1467
|
-
* Pass params.ENVIRONMENT to set the environment field in all logs.
|
|
1468
|
-
* This can be set via environment variables in your action configuration.
|
|
1469
|
-
*
|
|
1470
1607
|
* @param action - The runtime action function to instrument
|
|
1471
1608
|
* @returns The instrumented action ready for export
|
|
1472
1609
|
*
|
|
1473
|
-
* @example
|
|
1474
|
-
* ```typescript
|
|
1475
|
-
* // Environment:
|
|
1476
|
-
* // ENABLE_TELEMETRY=true
|
|
1477
|
-
* // NEW_RELIC_TELEMETRY=true
|
|
1478
|
-
* // NEW_RELIC_SERVICE_NAME=my-service
|
|
1479
|
-
* // NEW_RELIC_LICENSE_KEY=xxxxx
|
|
1480
|
-
*
|
|
1481
|
-
* export const main = Telemetry.initialize(myAction);
|
|
1482
|
-
* // ✅ Uses New Relic telemetry
|
|
1483
|
-
* ```
|
|
1484
|
-
*
|
|
1485
|
-
* @example New Relic enabled but misconfigured - Returns error response
|
|
1486
|
-
* ```typescript
|
|
1487
|
-
* // Environment:
|
|
1488
|
-
* // ENABLE_TELEMETRY=true
|
|
1489
|
-
* // NEW_RELIC_TELEMETRY=true
|
|
1490
|
-
* // NEW_RELIC_SERVICE_NAME=my-service
|
|
1491
|
-
* // Missing NEW_RELIC_LICENSE_KEY!
|
|
1492
|
-
*
|
|
1493
|
-
* export const main = Telemetry.initialize(myAction);
|
|
1494
|
-
* // ❌ Returns { error: { statusCode: 500, body: { error: "Telemetry configuration error: NEW_RELIC_LICENSE_KEY is required" } } }
|
|
1495
|
-
* // This is intentional - you want to know your telemetry config is broken!
|
|
1496
|
-
* ```
|
|
1497
|
-
*
|
|
1498
|
-
* @example Multi-provider fallback
|
|
1499
|
-
* ```typescript
|
|
1500
|
-
* // Environment:
|
|
1501
|
-
* // ENABLE_TELEMETRY=true
|
|
1502
|
-
* // NEW_RELIC_TELEMETRY=false // Skip New Relic
|
|
1503
|
-
* // GRAFANA_TELEMETRY=true // Use Grafana instead
|
|
1504
|
-
*
|
|
1505
|
-
* export const main = Telemetry.initialize(myAction);
|
|
1506
|
-
* // ✅ Skips New Relic, tries Grafana (when implemented)
|
|
1507
|
-
* ```
|
|
1508
|
-
*
|
|
1509
|
-
* @example No telemetry
|
|
1610
|
+
* @example
|
|
1510
1611
|
* ```typescript
|
|
1511
|
-
*
|
|
1512
|
-
*
|
|
1513
|
-
*
|
|
1514
|
-
*
|
|
1515
|
-
* // ✅ Returns original action without instrumentation
|
|
1612
|
+
* const telemetry = new Telemetry();
|
|
1613
|
+
* export const main = telemetry.initialize(myAction);
|
|
1614
|
+
* // Environment: ENABLE_TELEMETRY=true, NEW_RELIC_TELEMETRY=true
|
|
1615
|
+
* // Result: Uses New Relic telemetry with full distributed tracing
|
|
1516
1616
|
* ```
|
|
1517
1617
|
*/
|
|
1518
|
-
|
|
1618
|
+
initialize(action) {
|
|
1519
1619
|
return async (params) => {
|
|
1620
|
+
this.setParams(params);
|
|
1520
1621
|
const newRelicTelemetry = new new_relic_default();
|
|
1521
1622
|
if (newRelicTelemetry.canInitialize(params)) {
|
|
1522
1623
|
try {
|
|
@@ -1591,7 +1692,7 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1591
1692
|
* @param requiredHeaders - Required header names (case-insensitive)
|
|
1592
1693
|
* @param action - User-defined action function that receives:
|
|
1593
1694
|
* - params: All request parameters including __ow_* runtime parameters
|
|
1594
|
-
* - ctx: Context object with logger and
|
|
1695
|
+
* - ctx: Context object with logger, headers, and telemetry
|
|
1595
1696
|
* @returns Wrapped action function ready to be exported as Adobe I/O Runtime entrypoint
|
|
1596
1697
|
*
|
|
1597
1698
|
* @example With All Options
|
|
@@ -1601,10 +1702,16 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1601
1702
|
* [HttpMethod.POST],
|
|
1602
1703
|
* ['orderId', 'customerId'],
|
|
1603
1704
|
* ['authorization', 'x-api-key'],
|
|
1604
|
-
* async (params, { logger, headers }) => {
|
|
1705
|
+
* async (params, { logger, headers, telemetry }) => {
|
|
1605
1706
|
* const { orderId, customerId } = params;
|
|
1606
1707
|
* logger.info({ message: 'Processing order', orderId, customerId });
|
|
1607
1708
|
*
|
|
1709
|
+
* // Access current span for custom instrumentation
|
|
1710
|
+
* const span = telemetry.getCurrentSpan();
|
|
1711
|
+
* if (span) {
|
|
1712
|
+
* span.setAttribute('order.id', orderId);
|
|
1713
|
+
* }
|
|
1714
|
+
*
|
|
1608
1715
|
* // Your business logic here
|
|
1609
1716
|
* const result = await processOrderLogic(orderId, customerId);
|
|
1610
1717
|
*
|
|
@@ -1627,11 +1734,13 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1627
1734
|
static execute(name = "main", httpMethods = [], requiredParams = [], requiredHeaders = [], action = async (_params) => {
|
|
1628
1735
|
return { statusCode: 200 /* OK */, body: {} };
|
|
1629
1736
|
}) {
|
|
1737
|
+
const telemetry = new telemetry_default();
|
|
1630
1738
|
const runtimeAction = /* @__PURE__ */ __name(async (params) => {
|
|
1631
1739
|
if (!params.action_type) {
|
|
1632
1740
|
params.action_type = _RuntimeAction.getActionType();
|
|
1633
1741
|
}
|
|
1634
|
-
|
|
1742
|
+
telemetry.setParams(params);
|
|
1743
|
+
const logger = telemetry.createLogger(name);
|
|
1635
1744
|
try {
|
|
1636
1745
|
logger.debug({
|
|
1637
1746
|
message: `${_RuntimeAction.getActionTypeName()} execution started`
|
|
@@ -1648,18 +1757,26 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1648
1757
|
body: params.__ow_body || {}
|
|
1649
1758
|
})
|
|
1650
1759
|
);
|
|
1651
|
-
const validationError = _RuntimeAction.
|
|
1760
|
+
const validationError = _RuntimeAction.validateRequestWithInstrumentation(
|
|
1761
|
+
name,
|
|
1652
1762
|
params,
|
|
1653
1763
|
requiredParams,
|
|
1654
1764
|
requiredHeaders,
|
|
1655
1765
|
httpMethods,
|
|
1656
|
-
|
|
1657
|
-
|
|
1766
|
+
telemetry,
|
|
1767
|
+
logger
|
|
1658
1768
|
);
|
|
1659
1769
|
if (validationError) {
|
|
1660
1770
|
return validationError;
|
|
1661
1771
|
}
|
|
1662
|
-
const result = await
|
|
1772
|
+
const result = await _RuntimeAction.executeActionWithInstrumentation(
|
|
1773
|
+
name,
|
|
1774
|
+
params.__ow_headers || {},
|
|
1775
|
+
params,
|
|
1776
|
+
action,
|
|
1777
|
+
telemetry,
|
|
1778
|
+
logger
|
|
1779
|
+
);
|
|
1663
1780
|
logger.debug(
|
|
1664
1781
|
JSON.stringify({
|
|
1665
1782
|
message: `${_RuntimeAction.getActionTypeName()} execution completed`,
|
|
@@ -1683,7 +1800,108 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1683
1800
|
return response_default.error(500 /* INTERNAL_ERROR */, "server error");
|
|
1684
1801
|
}
|
|
1685
1802
|
}, "runtimeAction");
|
|
1686
|
-
return
|
|
1803
|
+
return telemetry.initialize(runtimeAction);
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Executes user's action with OpenTelemetry instrumentation
|
|
1807
|
+
*
|
|
1808
|
+
* This method wraps the action execution with distributed tracing instrumentation,
|
|
1809
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
1810
|
+
*
|
|
1811
|
+
* @param name - Action name used for span naming
|
|
1812
|
+
* @param headers - Request headers
|
|
1813
|
+
* @param params - Request parameters
|
|
1814
|
+
* @param action - The user's action function to execute
|
|
1815
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
1816
|
+
* @param logger - Logger instance
|
|
1817
|
+
* @returns Promise resolving to the action result
|
|
1818
|
+
*
|
|
1819
|
+
* @private
|
|
1820
|
+
*/
|
|
1821
|
+
static async executeActionWithInstrumentation(name, headers, params, action, telemetry, logger) {
|
|
1822
|
+
const instrumentedAction = telemetry.instrument(
|
|
1823
|
+
`runtime.action.${name}.execute`,
|
|
1824
|
+
async (actionName, actionHeaders, actionParams, actionFn, actionTelemetry, actionLogger) => {
|
|
1825
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
1826
|
+
if (span) {
|
|
1827
|
+
span.setAttribute("action.name", actionName);
|
|
1828
|
+
span.setAttribute("action.type", actionParams.action_type || "unknown");
|
|
1829
|
+
span.setAttribute("action.has_headers", !!actionHeaders);
|
|
1830
|
+
span.addEvent("action-execution-started");
|
|
1831
|
+
}
|
|
1832
|
+
const result = await actionFn(actionParams, {
|
|
1833
|
+
logger: actionLogger,
|
|
1834
|
+
headers: actionHeaders,
|
|
1835
|
+
telemetry: actionTelemetry
|
|
1836
|
+
});
|
|
1837
|
+
if (span) {
|
|
1838
|
+
const statusCode = "statusCode" in result ? result.statusCode : result.error.statusCode;
|
|
1839
|
+
span.setAttribute("action.response_status", statusCode);
|
|
1840
|
+
span.setAttribute("action.has_error", "error" in result);
|
|
1841
|
+
span.addEvent("action-execution-completed", {
|
|
1842
|
+
statusCode
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
return result;
|
|
1846
|
+
}
|
|
1847
|
+
);
|
|
1848
|
+
return instrumentedAction(name, headers, params, action, telemetry, logger);
|
|
1849
|
+
}
|
|
1850
|
+
/**
|
|
1851
|
+
* Validates incoming request with OpenTelemetry instrumentation
|
|
1852
|
+
*
|
|
1853
|
+
* This method wraps the validation logic with distributed tracing instrumentation,
|
|
1854
|
+
* creating a span that tracks validation metrics and results in New Relic.
|
|
1855
|
+
*
|
|
1856
|
+
* @param name - Action name used for span naming
|
|
1857
|
+
* @param params - Request parameters including __ow_* runtime parameters
|
|
1858
|
+
* @param requiredParams - List of required parameter names to validate
|
|
1859
|
+
* @param requiredHeaders - List of required header names to validate (case-insensitive)
|
|
1860
|
+
* @param httpMethods - Allowed HTTP methods. Empty array skips HTTP method validation
|
|
1861
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
1862
|
+
* @param logger - Logger instance for error logging
|
|
1863
|
+
* @returns RuntimeActionResponseType with error details if validation fails, null if valid
|
|
1864
|
+
*
|
|
1865
|
+
* @private
|
|
1866
|
+
*/
|
|
1867
|
+
static validateRequestWithInstrumentation(name, params, requiredParams, requiredHeaders, httpMethods, telemetry, logger) {
|
|
1868
|
+
const instrumentedValidate = telemetry.instrument(
|
|
1869
|
+
`runtime.action.${name}.validate`,
|
|
1870
|
+
(actionName, actionParams, actionRequiredParams, actionRequiredHeaders, actionHttpMethods, actionTelemetry, actionLogger) => {
|
|
1871
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
1872
|
+
if (span) {
|
|
1873
|
+
span.setAttribute("validation.required_params", actionRequiredParams.join(","));
|
|
1874
|
+
span.setAttribute("validation.required_headers", actionRequiredHeaders.join(","));
|
|
1875
|
+
span.setAttribute("validation.allowed_methods", actionHttpMethods.join(","));
|
|
1876
|
+
span.setAttribute("validation.request_method", actionParams.__ow_method || "unknown");
|
|
1877
|
+
}
|
|
1878
|
+
const validationResult = _RuntimeAction.validateRequest(
|
|
1879
|
+
actionName,
|
|
1880
|
+
actionParams,
|
|
1881
|
+
actionRequiredParams,
|
|
1882
|
+
actionRequiredHeaders,
|
|
1883
|
+
actionHttpMethods,
|
|
1884
|
+
actionLogger
|
|
1885
|
+
);
|
|
1886
|
+
if (span) {
|
|
1887
|
+
span.setAttribute("validation.passed", validationResult === null);
|
|
1888
|
+
if (validationResult) {
|
|
1889
|
+
const statusCode = "statusCode" in validationResult ? validationResult.statusCode : validationResult.error.statusCode;
|
|
1890
|
+
span.setAttribute("validation.error_code", statusCode);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
return validationResult;
|
|
1894
|
+
}
|
|
1895
|
+
);
|
|
1896
|
+
return instrumentedValidate(
|
|
1897
|
+
name,
|
|
1898
|
+
params,
|
|
1899
|
+
requiredParams,
|
|
1900
|
+
requiredHeaders,
|
|
1901
|
+
httpMethods,
|
|
1902
|
+
telemetry,
|
|
1903
|
+
logger
|
|
1904
|
+
);
|
|
1687
1905
|
}
|
|
1688
1906
|
/**
|
|
1689
1907
|
* Validates incoming request against required parameters, headers, and HTTP methods
|
|
@@ -1693,12 +1911,12 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1693
1911
|
* 2. Checks for missing required headers (case-insensitive)
|
|
1694
1912
|
* 3. Validates the HTTP method against allowed methods list
|
|
1695
1913
|
*
|
|
1914
|
+
* @param _name - Action name for logging (currently unused)
|
|
1696
1915
|
* @param params - Request parameters including __ow_* runtime parameters
|
|
1697
1916
|
* @param requiredParams - List of required parameter names to validate
|
|
1698
1917
|
* @param requiredHeaders - List of required header names to validate (case-insensitive)
|
|
1699
1918
|
* @param httpMethods - Allowed HTTP methods. Empty array skips HTTP method validation
|
|
1700
1919
|
* @param logger - Logger instance for error logging (child logger with request ID if present)
|
|
1701
|
-
* @param name - Action name for logging
|
|
1702
1920
|
* @returns RuntimeActionResponseType with error details if validation fails, null if valid
|
|
1703
1921
|
*
|
|
1704
1922
|
* @private
|
|
@@ -1718,7 +1936,7 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1718
1936
|
* }
|
|
1719
1937
|
* ```
|
|
1720
1938
|
*/
|
|
1721
|
-
static validateRequest(params, requiredParams, requiredHeaders, httpMethods, logger
|
|
1939
|
+
static validateRequest(_name, params, requiredParams, requiredHeaders, httpMethods, logger) {
|
|
1722
1940
|
const errorMessage = validator_default.checkMissingRequestInputs(params, requiredParams, requiredHeaders) ?? "";
|
|
1723
1941
|
if (errorMessage) {
|
|
1724
1942
|
logger.error({
|
|
@@ -1798,11 +2016,15 @@ var _EventConsumerAction = class _EventConsumerAction {
|
|
|
1798
2016
|
* 'webhook-processor',
|
|
1799
2017
|
* ['eventType', 'eventData'],
|
|
1800
2018
|
* ['x-webhook-signature'],
|
|
1801
|
-
* async (params, { logger, headers }) => {
|
|
2019
|
+
* async (params, { logger, headers, telemetry }) => {
|
|
1802
2020
|
* logger.info({
|
|
1803
2021
|
* message: 'Processing webhook',
|
|
1804
2022
|
* eventType: params.eventType
|
|
1805
2023
|
* });
|
|
2024
|
+
* const span = telemetry.getCurrentSpan();
|
|
2025
|
+
* if (span) {
|
|
2026
|
+
* span.setAttribute('event.type', params.eventType);
|
|
2027
|
+
* }
|
|
1806
2028
|
* // Process event...
|
|
1807
2029
|
* return { statusCode: 200, body: { processed: true } };
|
|
1808
2030
|
* }
|
|
@@ -1812,9 +2034,10 @@ var _EventConsumerAction = class _EventConsumerAction {
|
|
|
1812
2034
|
static execute(name = "main", requiredParams = [], requiredHeaders = [], action = async (_params) => {
|
|
1813
2035
|
return { statusCode: 200 /* OK */, body: {} };
|
|
1814
2036
|
}) {
|
|
2037
|
+
const telemetry = new telemetry_default();
|
|
1815
2038
|
const eventConsumerAction = /* @__PURE__ */ __name(async (params) => {
|
|
1816
2039
|
params.action_type = "event-consumer-action";
|
|
1817
|
-
const logger =
|
|
2040
|
+
const logger = telemetry.createLogger(name);
|
|
1818
2041
|
try {
|
|
1819
2042
|
logger.debug({
|
|
1820
2043
|
message: "Event consumer action execution started"
|
|
@@ -1827,7 +2050,13 @@ var _EventConsumerAction = class _EventConsumerAction {
|
|
|
1827
2050
|
message: "Event consumer action parameters received",
|
|
1828
2051
|
parameters: params
|
|
1829
2052
|
});
|
|
1830
|
-
const errorMessage =
|
|
2053
|
+
const errorMessage = _EventConsumerAction.validateWithInstrumentation(
|
|
2054
|
+
name,
|
|
2055
|
+
params,
|
|
2056
|
+
requiredParams,
|
|
2057
|
+
requiredHeaders,
|
|
2058
|
+
telemetry
|
|
2059
|
+
);
|
|
1831
2060
|
if (errorMessage) {
|
|
1832
2061
|
logger.error({
|
|
1833
2062
|
message: "Event consumer action validation failed",
|
|
@@ -1835,7 +2064,14 @@ var _EventConsumerAction = class _EventConsumerAction {
|
|
|
1835
2064
|
});
|
|
1836
2065
|
return response_default.error(400 /* BAD_REQUEST */, errorMessage);
|
|
1837
2066
|
}
|
|
1838
|
-
const result = await
|
|
2067
|
+
const result = await _EventConsumerAction.executeActionWithInstrumentation(
|
|
2068
|
+
name,
|
|
2069
|
+
params.__ow_headers || {},
|
|
2070
|
+
params,
|
|
2071
|
+
action,
|
|
2072
|
+
telemetry,
|
|
2073
|
+
logger
|
|
2074
|
+
);
|
|
1839
2075
|
logger.debug({
|
|
1840
2076
|
message: "Event consumer action execution completed",
|
|
1841
2077
|
result
|
|
@@ -1844,16 +2080,109 @@ var _EventConsumerAction = class _EventConsumerAction {
|
|
|
1844
2080
|
} catch (error) {
|
|
1845
2081
|
if (error instanceof Error) {
|
|
1846
2082
|
logger.error({
|
|
2083
|
+
message: "Event consumer action execution failed",
|
|
1847
2084
|
error: error.message,
|
|
1848
2085
|
stack: error.stack
|
|
1849
2086
|
});
|
|
1850
2087
|
} else {
|
|
1851
|
-
logger.error({
|
|
2088
|
+
logger.error({
|
|
2089
|
+
message: "Event consumer action execution failed",
|
|
2090
|
+
error
|
|
2091
|
+
});
|
|
1852
2092
|
}
|
|
1853
2093
|
return response_default.error(500 /* INTERNAL_ERROR */, "server error");
|
|
1854
2094
|
}
|
|
1855
2095
|
}, "eventConsumerAction");
|
|
1856
|
-
return
|
|
2096
|
+
return telemetry.initialize(eventConsumerAction);
|
|
2097
|
+
}
|
|
2098
|
+
/**
|
|
2099
|
+
* Validates event consumer request with OpenTelemetry instrumentation
|
|
2100
|
+
*
|
|
2101
|
+
* This method wraps the validation logic with distributed tracing instrumentation,
|
|
2102
|
+
* creating a span that tracks validation metrics and results in New Relic.
|
|
2103
|
+
*
|
|
2104
|
+
* @param name - Action name used for span naming
|
|
2105
|
+
* @param params - Request parameters
|
|
2106
|
+
* @param requiredParams - List of required parameter names to validate
|
|
2107
|
+
* @param requiredHeaders - List of required header names to validate
|
|
2108
|
+
* @returns Error message if validation fails, empty string if successful
|
|
2109
|
+
*
|
|
2110
|
+
* @private
|
|
2111
|
+
*/
|
|
2112
|
+
static validateWithInstrumentation(name, params, requiredParams, requiredHeaders, telemetry) {
|
|
2113
|
+
const instrumentedValidate = telemetry.instrument(
|
|
2114
|
+
`event.consumer.action.${name}.validate`,
|
|
2115
|
+
(actionName, actionParams, actionRequiredParams, actionRequiredHeaders, actionTelemetry) => {
|
|
2116
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2117
|
+
if (span) {
|
|
2118
|
+
span.setAttribute(
|
|
2119
|
+
"event.consumer.validation.required_params",
|
|
2120
|
+
actionRequiredParams.join(",")
|
|
2121
|
+
);
|
|
2122
|
+
span.setAttribute(
|
|
2123
|
+
"event.consumer.validation.required_headers",
|
|
2124
|
+
actionRequiredHeaders.join(",")
|
|
2125
|
+
);
|
|
2126
|
+
}
|
|
2127
|
+
const errorMessage = validator_default.checkMissingRequestInputs(
|
|
2128
|
+
actionParams,
|
|
2129
|
+
actionRequiredParams,
|
|
2130
|
+
actionRequiredHeaders
|
|
2131
|
+
) || "";
|
|
2132
|
+
if (span) {
|
|
2133
|
+
span.setAttribute("event.consumer.validation.passed", errorMessage === "");
|
|
2134
|
+
if (errorMessage) {
|
|
2135
|
+
span.setAttribute("event.consumer.validation.error", errorMessage);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
return errorMessage;
|
|
2139
|
+
}
|
|
2140
|
+
);
|
|
2141
|
+
return instrumentedValidate(name, params, requiredParams, requiredHeaders, telemetry);
|
|
2142
|
+
}
|
|
2143
|
+
/**
|
|
2144
|
+
* Executes event consumer action with OpenTelemetry instrumentation
|
|
2145
|
+
*
|
|
2146
|
+
* This method wraps the action execution with distributed tracing instrumentation,
|
|
2147
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
2148
|
+
*
|
|
2149
|
+
* @param name - Action name used for span naming
|
|
2150
|
+
* @param headers - Request headers
|
|
2151
|
+
* @param params - Request parameters
|
|
2152
|
+
* @param action - The user's action function to execute
|
|
2153
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
2154
|
+
* @param logger - Logger instance
|
|
2155
|
+
* @returns Promise resolving to the action result
|
|
2156
|
+
*
|
|
2157
|
+
* @private
|
|
2158
|
+
*/
|
|
2159
|
+
static async executeActionWithInstrumentation(name, headers, params, action, telemetry, logger) {
|
|
2160
|
+
const instrumentedAction = telemetry.instrument(
|
|
2161
|
+
`event.consumer.action.${name}.execute`,
|
|
2162
|
+
async (actionName, actionHeaders, actionParams, actionFn, actionTelemetry, actionLogger) => {
|
|
2163
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2164
|
+
if (span) {
|
|
2165
|
+
span.setAttribute("event.consumer.action.name", actionName);
|
|
2166
|
+
span.setAttribute("event.consumer.action.has_headers", !!actionHeaders);
|
|
2167
|
+
span.addEvent("event-consumer-execution-started");
|
|
2168
|
+
}
|
|
2169
|
+
const result = await actionFn(actionParams, {
|
|
2170
|
+
logger: actionLogger,
|
|
2171
|
+
headers: actionHeaders,
|
|
2172
|
+
telemetry: actionTelemetry
|
|
2173
|
+
});
|
|
2174
|
+
if (span) {
|
|
2175
|
+
const statusCode = "statusCode" in result ? result.statusCode : result.error.statusCode;
|
|
2176
|
+
span.setAttribute("event.consumer.action.response_status", statusCode);
|
|
2177
|
+
span.setAttribute("event.consumer.action.has_error", "error" in result);
|
|
2178
|
+
span.addEvent("event-consumer-execution-completed", {
|
|
2179
|
+
statusCode
|
|
2180
|
+
});
|
|
2181
|
+
}
|
|
2182
|
+
return result;
|
|
2183
|
+
}
|
|
2184
|
+
);
|
|
2185
|
+
return instrumentedAction(name, headers, params, action, telemetry, logger);
|
|
1857
2186
|
}
|
|
1858
2187
|
};
|
|
1859
2188
|
__name(_EventConsumerAction, "EventConsumerAction");
|
|
@@ -1863,6 +2192,116 @@ var event_consumer_action_default = EventConsumerAction;
|
|
|
1863
2192
|
// src/framework/graphql-action/index.ts
|
|
1864
2193
|
var import_graphql = require("graphql");
|
|
1865
2194
|
var _GraphQlAction = class _GraphQlAction {
|
|
2195
|
+
/**
|
|
2196
|
+
* Creates a GraphQL action handler with schema validation and telemetry
|
|
2197
|
+
*
|
|
2198
|
+
* This method wraps a GraphQL API with standardized runtime functionality:
|
|
2199
|
+
* 1. Builds and validates the GraphQL schema
|
|
2200
|
+
* 2. Parses incoming GraphQL queries
|
|
2201
|
+
* 3. Validates queries against the schema
|
|
2202
|
+
* 4. Optionally blocks introspection queries for security
|
|
2203
|
+
* 5. Executes queries with provided resolvers
|
|
2204
|
+
* 6. Integrates with OpenTelemetry for distributed tracing
|
|
2205
|
+
* 7. Provides structured logging with request ID correlation
|
|
2206
|
+
*
|
|
2207
|
+
* All operations are instrumented with OpenTelemetry spans for observability in New Relic.
|
|
2208
|
+
*
|
|
2209
|
+
* @param schema - GraphQL schema definition string (SDL format)
|
|
2210
|
+
* @param resolvers - Function that returns resolver map with access to logger, headers, and params
|
|
2211
|
+
* @param name - Action name for logging and telemetry spans (default: 'main')
|
|
2212
|
+
* @param disableIntrospection - If true, blocks introspection queries (__schema, __type) for security (default: false)
|
|
2213
|
+
* @returns Wrapped action function ready to be exported as Adobe I/O Runtime entrypoint
|
|
2214
|
+
*
|
|
2215
|
+
* @example Complete GraphQL API with authentication
|
|
2216
|
+
* ```typescript
|
|
2217
|
+
* const schema = `
|
|
2218
|
+
* type Query {
|
|
2219
|
+
* me: User
|
|
2220
|
+
* orders: [Order]
|
|
2221
|
+
* }
|
|
2222
|
+
*
|
|
2223
|
+
* type User {
|
|
2224
|
+
* id: ID!
|
|
2225
|
+
* email: String!
|
|
2226
|
+
* name: String!
|
|
2227
|
+
* }
|
|
2228
|
+
*
|
|
2229
|
+
* type Order {
|
|
2230
|
+
* id: ID!
|
|
2231
|
+
* total: Float!
|
|
2232
|
+
* items: [OrderItem]
|
|
2233
|
+
* }
|
|
2234
|
+
*
|
|
2235
|
+
* type OrderItem {
|
|
2236
|
+
* productId: ID!
|
|
2237
|
+
* quantity: Int!
|
|
2238
|
+
* price: Float!
|
|
2239
|
+
* }
|
|
2240
|
+
* `;
|
|
2241
|
+
*
|
|
2242
|
+
* const resolvers = async ({ logger, headers, params, telemetry }) => {
|
|
2243
|
+
* // Access authentication from headers
|
|
2244
|
+
* const userId = headers['x-user-id'];
|
|
2245
|
+
*
|
|
2246
|
+
* return {
|
|
2247
|
+
* me: async () => {
|
|
2248
|
+
* logger.info({ message: 'Fetching current user', userId });
|
|
2249
|
+
* const span = telemetry.getCurrentSpan();
|
|
2250
|
+
* if (span) {
|
|
2251
|
+
* span.setAttribute('user.id', userId);
|
|
2252
|
+
* }
|
|
2253
|
+
* return getUserById(userId);
|
|
2254
|
+
* },
|
|
2255
|
+
* orders: async () => {
|
|
2256
|
+
* logger.info({ message: 'Fetching user orders', userId });
|
|
2257
|
+
* return getOrdersByUserId(userId);
|
|
2258
|
+
* }
|
|
2259
|
+
* };
|
|
2260
|
+
* };
|
|
2261
|
+
*
|
|
2262
|
+
* // Disable introspection in production for security
|
|
2263
|
+
* const handler = GraphQlAction.execute(
|
|
2264
|
+
* schema,
|
|
2265
|
+
* resolvers,
|
|
2266
|
+
* 'customer-api',
|
|
2267
|
+
* process.env.NODE_ENV === 'production'
|
|
2268
|
+
* );
|
|
2269
|
+
*
|
|
2270
|
+
* export const main = handler;
|
|
2271
|
+
* ```
|
|
2272
|
+
*
|
|
2273
|
+
* @example GraphQL action with variables and operation names
|
|
2274
|
+
* ```typescript
|
|
2275
|
+
* // Client sends:
|
|
2276
|
+
* // POST /api/graphql
|
|
2277
|
+
* // {
|
|
2278
|
+
* // "query": "query GetUser($id: ID!) { user(id: $id) { name email } }",
|
|
2279
|
+
* // "variables": { "id": "123" },
|
|
2280
|
+
* // "operationName": "GetUser"
|
|
2281
|
+
* // }
|
|
2282
|
+
*
|
|
2283
|
+
* const schema = `
|
|
2284
|
+
* type Query {
|
|
2285
|
+
* user(id: ID!): User
|
|
2286
|
+
* }
|
|
2287
|
+
* type User {
|
|
2288
|
+
* id: ID!
|
|
2289
|
+
* name: String!
|
|
2290
|
+
* email: String!
|
|
2291
|
+
* }
|
|
2292
|
+
* `;
|
|
2293
|
+
*
|
|
2294
|
+
* const resolvers = async ({ logger, headers, params, telemetry }) => ({
|
|
2295
|
+
* user: async ({ id }) => {
|
|
2296
|
+
* logger.info({ message: 'Query executed', operationName: 'GetUser', userId: id });
|
|
2297
|
+
* return { id, name: 'John Doe', email: 'john@example.com' };
|
|
2298
|
+
* }
|
|
2299
|
+
* });
|
|
2300
|
+
*
|
|
2301
|
+
* const handler = GraphQlAction.execute(schema, resolvers, 'graphql-api');
|
|
2302
|
+
* export const main = handler;
|
|
2303
|
+
* ```
|
|
2304
|
+
*/
|
|
1866
2305
|
static execute(schema = `
|
|
1867
2306
|
type Query {
|
|
1868
2307
|
hello: String
|
|
@@ -1872,15 +2311,11 @@ var _GraphQlAction = class _GraphQlAction {
|
|
|
1872
2311
|
hello: /* @__PURE__ */ __name(() => "Hello World!", "hello")
|
|
1873
2312
|
};
|
|
1874
2313
|
}, name = "main", disableIntrospection = false) {
|
|
1875
|
-
runtime_action_default.setActionType("graphql-action");
|
|
1876
|
-
runtime_action_default.setActionTypeName("GraphQL action");
|
|
1877
2314
|
const callback = /* @__PURE__ */ __name(async (params, ctx) => {
|
|
1878
|
-
const { logger } = ctx;
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
} catch (error) {
|
|
1883
|
-
return response_default.error(400 /* BAD_REQUEST */, error.message);
|
|
2315
|
+
const { logger, telemetry } = ctx;
|
|
2316
|
+
const { schema: graphqlSchema, error: schemaError } = _GraphQlAction.buildSchemaWithInstrumentation(name, schema, telemetry);
|
|
2317
|
+
if (schemaError) {
|
|
2318
|
+
return response_default.error(400 /* BAD_REQUEST */, schemaError);
|
|
1884
2319
|
}
|
|
1885
2320
|
const graphqlResolvers = await resolvers({
|
|
1886
2321
|
...ctx,
|
|
@@ -1890,11 +2325,9 @@ var _GraphQlAction = class _GraphQlAction {
|
|
|
1890
2325
|
});
|
|
1891
2326
|
const context2 = {};
|
|
1892
2327
|
const query = params.query;
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
} catch (error) {
|
|
1897
|
-
return response_default.error(400 /* BAD_REQUEST */, error.message);
|
|
2328
|
+
const { parsed: parsedQuery, error: parseError } = _GraphQlAction.parseQueryWithInstrumentation(name, query, telemetry);
|
|
2329
|
+
if (parseError) {
|
|
2330
|
+
return response_default.error(400 /* BAD_REQUEST */, parseError);
|
|
1898
2331
|
}
|
|
1899
2332
|
logger.debug(
|
|
1900
2333
|
JSON.stringify({
|
|
@@ -1902,7 +2335,12 @@ var _GraphQlAction = class _GraphQlAction {
|
|
|
1902
2335
|
query: parsedQuery
|
|
1903
2336
|
})
|
|
1904
2337
|
);
|
|
1905
|
-
const validationErrors =
|
|
2338
|
+
const validationErrors = _GraphQlAction.validateQueryWithInstrumentation(
|
|
2339
|
+
name,
|
|
2340
|
+
graphqlSchema,
|
|
2341
|
+
parsedQuery,
|
|
2342
|
+
telemetry
|
|
2343
|
+
);
|
|
1906
2344
|
if (validationErrors.length) {
|
|
1907
2345
|
logger.error({
|
|
1908
2346
|
message: "GraphQL action query validation failed",
|
|
@@ -1917,10 +2355,10 @@ var _GraphQlAction = class _GraphQlAction {
|
|
|
1917
2355
|
logger.debug({
|
|
1918
2356
|
message: "GraphQL action introspection check disabled"
|
|
1919
2357
|
});
|
|
1920
|
-
const isIntrospectionQuery =
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2358
|
+
const isIntrospectionQuery = _GraphQlAction.checkIntrospectionWithInstrumentation(
|
|
2359
|
+
name,
|
|
2360
|
+
parsedQuery,
|
|
2361
|
+
telemetry
|
|
1924
2362
|
);
|
|
1925
2363
|
if (isIntrospectionQuery) {
|
|
1926
2364
|
logger.error({
|
|
@@ -1939,24 +2377,240 @@ var _GraphQlAction = class _GraphQlAction {
|
|
|
1939
2377
|
variables
|
|
1940
2378
|
});
|
|
1941
2379
|
return response_default.success(
|
|
1942
|
-
await
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
2380
|
+
await _GraphQlAction.executeGraphQLWithInstrumentation(
|
|
2381
|
+
name,
|
|
2382
|
+
{
|
|
2383
|
+
schema: graphqlSchema,
|
|
2384
|
+
source: query,
|
|
2385
|
+
rootValue: graphqlResolvers,
|
|
2386
|
+
contextValue: context2,
|
|
2387
|
+
variableValues: variables,
|
|
2388
|
+
operationName: params.operationName
|
|
2389
|
+
},
|
|
2390
|
+
telemetry
|
|
2391
|
+
)
|
|
1950
2392
|
);
|
|
1951
2393
|
}, "callback");
|
|
1952
|
-
|
|
2394
|
+
runtime_action_default.setActionType("graphql-action");
|
|
2395
|
+
runtime_action_default.setActionTypeName("GraphQL action");
|
|
2396
|
+
return runtime_action_default.execute(
|
|
1953
2397
|
`graphql-${name}`,
|
|
1954
2398
|
["GET" /* GET */, "POST" /* POST */],
|
|
1955
2399
|
["query"],
|
|
1956
2400
|
[],
|
|
1957
2401
|
callback
|
|
1958
2402
|
);
|
|
1959
|
-
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Builds GraphQL schema with OpenTelemetry instrumentation
|
|
2406
|
+
*
|
|
2407
|
+
* This method wraps the schema building logic with distributed tracing instrumentation,
|
|
2408
|
+
* creating a span that tracks schema metrics and results in New Relic.
|
|
2409
|
+
*
|
|
2410
|
+
* @param name - Action name used for span naming
|
|
2411
|
+
* @param schemaString - GraphQL schema definition string
|
|
2412
|
+
* @param params - Request parameters for telemetry context
|
|
2413
|
+
* @returns Object with built schema or error message
|
|
2414
|
+
*
|
|
2415
|
+
* @private
|
|
2416
|
+
*/
|
|
2417
|
+
static buildSchemaWithInstrumentation(name, schemaString, telemetry) {
|
|
2418
|
+
const instrumentedBuildSchema = telemetry.instrument(
|
|
2419
|
+
`graphql.action.${name}.build-schema`,
|
|
2420
|
+
(actionName, actionSchemaString, actionTelemetry) => {
|
|
2421
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2422
|
+
if (span) {
|
|
2423
|
+
span.setAttribute("graphql.schema.length", actionSchemaString.length);
|
|
2424
|
+
span.addEvent("schema-building-started");
|
|
2425
|
+
}
|
|
2426
|
+
try {
|
|
2427
|
+
const builtSchema = (0, import_graphql.buildSchema)(actionSchemaString);
|
|
2428
|
+
if (span) {
|
|
2429
|
+
span.setAttribute("graphql.schema.built", true);
|
|
2430
|
+
span.addEvent("schema-building-completed");
|
|
2431
|
+
}
|
|
2432
|
+
return { schema: builtSchema, error: null };
|
|
2433
|
+
} catch (error) {
|
|
2434
|
+
if (span) {
|
|
2435
|
+
span.setAttribute("graphql.schema.built", false);
|
|
2436
|
+
span.setAttribute("graphql.schema.error", error.message);
|
|
2437
|
+
}
|
|
2438
|
+
return { schema: null, error: error.message };
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
);
|
|
2442
|
+
return instrumentedBuildSchema(name, schemaString, telemetry);
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Parses GraphQL query with OpenTelemetry instrumentation
|
|
2446
|
+
*
|
|
2447
|
+
* This method wraps the query parsing logic with distributed tracing instrumentation,
|
|
2448
|
+
* creating a span that tracks parsing metrics and results in New Relic.
|
|
2449
|
+
*
|
|
2450
|
+
* @param name - Action name used for span naming
|
|
2451
|
+
* @param queryString - GraphQL query string to parse
|
|
2452
|
+
* @param params - Request parameters for telemetry context
|
|
2453
|
+
* @returns Object with parsed query document or error message
|
|
2454
|
+
*
|
|
2455
|
+
* @private
|
|
2456
|
+
*/
|
|
2457
|
+
static parseQueryWithInstrumentation(name, queryString, telemetry) {
|
|
2458
|
+
const instrumentedParseQuery = telemetry.instrument(
|
|
2459
|
+
`graphql.action.${name}.parse-query`,
|
|
2460
|
+
(actionName, actionQueryString, actionTelemetry) => {
|
|
2461
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2462
|
+
if (span) {
|
|
2463
|
+
span.setAttribute("graphql.query.length", actionQueryString.length);
|
|
2464
|
+
span.addEvent("query-parsing-started");
|
|
2465
|
+
}
|
|
2466
|
+
try {
|
|
2467
|
+
const parsed = (0, import_graphql.parse)(actionQueryString);
|
|
2468
|
+
if (span) {
|
|
2469
|
+
span.setAttribute("graphql.query.parsed", true);
|
|
2470
|
+
span.setAttribute("graphql.query.definitions_count", parsed.definitions.length);
|
|
2471
|
+
span.addEvent("query-parsing-completed");
|
|
2472
|
+
}
|
|
2473
|
+
return { parsed, error: null };
|
|
2474
|
+
} catch (error) {
|
|
2475
|
+
if (span) {
|
|
2476
|
+
span.setAttribute("graphql.query.parsed", false);
|
|
2477
|
+
span.setAttribute("graphql.query.error", error.message);
|
|
2478
|
+
}
|
|
2479
|
+
return { parsed: null, error: error.message };
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
);
|
|
2483
|
+
return instrumentedParseQuery(name, queryString, telemetry);
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* Validates GraphQL query with OpenTelemetry instrumentation
|
|
2487
|
+
*
|
|
2488
|
+
* This method wraps the query validation logic with distributed tracing instrumentation,
|
|
2489
|
+
* creating a span that tracks validation metrics and results in New Relic.
|
|
2490
|
+
*
|
|
2491
|
+
* @param name - Action name used for span naming
|
|
2492
|
+
* @param schema - GraphQL schema to validate against
|
|
2493
|
+
* @param parsedQuery - Parsed GraphQL query document
|
|
2494
|
+
* @param params - Request parameters for telemetry context
|
|
2495
|
+
* @returns Array of validation errors (empty if valid)
|
|
2496
|
+
*
|
|
2497
|
+
* @private
|
|
2498
|
+
*/
|
|
2499
|
+
static validateQueryWithInstrumentation(name, schema, parsedQuery, telemetry) {
|
|
2500
|
+
const instrumentedValidateQuery = telemetry.instrument(
|
|
2501
|
+
`graphql.action.${name}.validate-query`,
|
|
2502
|
+
(actionName, actionSchema, actionParsedQuery, actionTelemetry) => {
|
|
2503
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2504
|
+
if (span) {
|
|
2505
|
+
span.addEvent("query-validation-started");
|
|
2506
|
+
}
|
|
2507
|
+
const errors = (0, import_graphql.validate)(actionSchema, actionParsedQuery);
|
|
2508
|
+
if (span) {
|
|
2509
|
+
span.setAttribute("graphql.query.valid", errors.length === 0);
|
|
2510
|
+
span.setAttribute("graphql.query.validation_errors_count", errors.length);
|
|
2511
|
+
if (errors.length > 0) {
|
|
2512
|
+
span.setAttribute(
|
|
2513
|
+
"graphql.query.validation_errors",
|
|
2514
|
+
errors.map((e) => e.message).join("; ")
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
span.addEvent("query-validation-completed", {
|
|
2518
|
+
valid: errors.length === 0,
|
|
2519
|
+
errorCount: errors.length
|
|
2520
|
+
});
|
|
2521
|
+
}
|
|
2522
|
+
return errors;
|
|
2523
|
+
}
|
|
2524
|
+
);
|
|
2525
|
+
return instrumentedValidateQuery(name, schema, parsedQuery, telemetry);
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Checks for introspection query with OpenTelemetry instrumentation
|
|
2529
|
+
*
|
|
2530
|
+
* This method wraps the introspection detection logic with distributed tracing instrumentation,
|
|
2531
|
+
* creating a span that tracks whether an introspection query was detected in New Relic.
|
|
2532
|
+
*
|
|
2533
|
+
* @param name - Action name used for span naming
|
|
2534
|
+
* @param parsedQuery - Parsed GraphQL query document
|
|
2535
|
+
* @param params - Request parameters for telemetry context
|
|
2536
|
+
* @returns True if introspection query detected, false otherwise
|
|
2537
|
+
*
|
|
2538
|
+
* @private
|
|
2539
|
+
*/
|
|
2540
|
+
static checkIntrospectionWithInstrumentation(name, parsedQuery, telemetry) {
|
|
2541
|
+
const instrumentedIntrospectionCheck = telemetry.instrument(
|
|
2542
|
+
`graphql.action.${name}.introspection-check`,
|
|
2543
|
+
(actionName, actionParsedQuery, actionTelemetry) => {
|
|
2544
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2545
|
+
if (span) {
|
|
2546
|
+
span.setAttribute("graphql.introspection.disabled", true);
|
|
2547
|
+
span.addEvent("introspection-check-started");
|
|
2548
|
+
}
|
|
2549
|
+
const isIntrospection = actionParsedQuery.definitions.some(
|
|
2550
|
+
(definition) => definition.selectionSet.selections.some(
|
|
2551
|
+
(selection) => selection.name.value.startsWith("__")
|
|
2552
|
+
)
|
|
2553
|
+
);
|
|
2554
|
+
if (span) {
|
|
2555
|
+
span.setAttribute("graphql.introspection.detected", isIntrospection);
|
|
2556
|
+
span.addEvent("introspection-check-completed", {
|
|
2557
|
+
detected: isIntrospection
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
return isIntrospection;
|
|
2561
|
+
}
|
|
2562
|
+
);
|
|
2563
|
+
return instrumentedIntrospectionCheck(name, parsedQuery, telemetry);
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Executes GraphQL query with OpenTelemetry instrumentation
|
|
2567
|
+
*
|
|
2568
|
+
* This method wraps the GraphQL execution with distributed tracing instrumentation,
|
|
2569
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
2570
|
+
*
|
|
2571
|
+
* @param name - Action name used for span naming
|
|
2572
|
+
* @param executionParams - GraphQL execution parameters (schema, source, rootValue, etc.)
|
|
2573
|
+
* @param params - Request parameters for telemetry context
|
|
2574
|
+
* @returns Promise resolving to the GraphQL execution result
|
|
2575
|
+
*
|
|
2576
|
+
* @private
|
|
2577
|
+
*/
|
|
2578
|
+
static async executeGraphQLWithInstrumentation(name, executionParams, telemetry) {
|
|
2579
|
+
const instrumentedGraphQLExecution = telemetry.instrument(
|
|
2580
|
+
`graphql.action.${name}.execute`,
|
|
2581
|
+
async (actionName, actionExecutionParams, actionTelemetry) => {
|
|
2582
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2583
|
+
if (span) {
|
|
2584
|
+
span.setAttribute(
|
|
2585
|
+
"graphql.execution.operation_name",
|
|
2586
|
+
actionExecutionParams.operationName || "default"
|
|
2587
|
+
);
|
|
2588
|
+
span.setAttribute(
|
|
2589
|
+
"graphql.execution.has_variables",
|
|
2590
|
+
!!actionExecutionParams.variableValues
|
|
2591
|
+
);
|
|
2592
|
+
span.addEvent("graphql-execution-started");
|
|
2593
|
+
}
|
|
2594
|
+
const result = await (0, import_graphql.graphql)(actionExecutionParams);
|
|
2595
|
+
if (span) {
|
|
2596
|
+
span.setAttribute("graphql.execution.has_errors", !!result.errors);
|
|
2597
|
+
if (result.errors) {
|
|
2598
|
+
span.setAttribute("graphql.execution.errors_count", result.errors.length);
|
|
2599
|
+
span.setAttribute(
|
|
2600
|
+
"graphql.execution.errors",
|
|
2601
|
+
result.errors.map((e) => e.message).join("; ")
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
span.setAttribute("graphql.execution.has_data", !!result.data);
|
|
2605
|
+
span.addEvent("graphql-execution-completed", {
|
|
2606
|
+
hasErrors: !!result.errors,
|
|
2607
|
+
hasData: !!result.data
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
return result;
|
|
2611
|
+
}
|
|
2612
|
+
);
|
|
2613
|
+
return instrumentedGraphQLExecution(name, executionParams, telemetry);
|
|
1960
2614
|
}
|
|
1961
2615
|
};
|
|
1962
2616
|
__name(_GraphQlAction, "GraphQlAction");
|
|
@@ -2021,9 +2675,15 @@ var _OpenwhiskAction = class _OpenwhiskAction {
|
|
|
2021
2675
|
* ```typescript
|
|
2022
2676
|
* const handler = OpenwhiskAction.execute(
|
|
2023
2677
|
* 'processWebhook',
|
|
2024
|
-
* async (params, { logger, headers }) => {
|
|
2678
|
+
* async (params, { logger, headers, telemetry }) => {
|
|
2025
2679
|
* logger.info({ message: 'Webhook received', event: params.event });
|
|
2026
2680
|
*
|
|
2681
|
+
* // Access current span for custom instrumentation
|
|
2682
|
+
* const span = telemetry.getCurrentSpan();
|
|
2683
|
+
* if (span) {
|
|
2684
|
+
* span.setAttribute('webhook.event', params.event);
|
|
2685
|
+
* }
|
|
2686
|
+
*
|
|
2027
2687
|
* // Your webhook processing logic here
|
|
2028
2688
|
* const result = await processWebhookData(params);
|
|
2029
2689
|
*
|
|
@@ -2038,9 +2698,10 @@ var _OpenwhiskAction = class _OpenwhiskAction {
|
|
|
2038
2698
|
static execute(name = "main", action = async (_params) => {
|
|
2039
2699
|
return { statusCode: 200 /* OK */, body: {} };
|
|
2040
2700
|
}) {
|
|
2701
|
+
const telemetry = new telemetry_default();
|
|
2041
2702
|
const openwhiskAction = /* @__PURE__ */ __name(async (params) => {
|
|
2042
2703
|
params.action_type = "openwhisk-action";
|
|
2043
|
-
const logger =
|
|
2704
|
+
const logger = telemetry.createLogger(name);
|
|
2044
2705
|
try {
|
|
2045
2706
|
logger.debug({
|
|
2046
2707
|
message: "OpenWhisk action execution started"
|
|
@@ -2049,7 +2710,14 @@ var _OpenwhiskAction = class _OpenwhiskAction {
|
|
|
2049
2710
|
message: "OpenWhisk action parameters received",
|
|
2050
2711
|
params
|
|
2051
2712
|
});
|
|
2052
|
-
const result = await
|
|
2713
|
+
const result = await _OpenwhiskAction.executeActionWithInstrumentation(
|
|
2714
|
+
name,
|
|
2715
|
+
params.__ow_headers || {},
|
|
2716
|
+
params,
|
|
2717
|
+
action,
|
|
2718
|
+
telemetry,
|
|
2719
|
+
logger
|
|
2720
|
+
);
|
|
2053
2721
|
logger.debug({
|
|
2054
2722
|
message: "OpenWhisk action execution completed",
|
|
2055
2723
|
result
|
|
@@ -2071,7 +2739,51 @@ var _OpenwhiskAction = class _OpenwhiskAction {
|
|
|
2071
2739
|
return response_default.error(500 /* INTERNAL_ERROR */, "server error");
|
|
2072
2740
|
}
|
|
2073
2741
|
}, "openwhiskAction");
|
|
2074
|
-
return
|
|
2742
|
+
return telemetry.initialize(openwhiskAction);
|
|
2743
|
+
}
|
|
2744
|
+
/**
|
|
2745
|
+
* Executes OpenWhisk action with OpenTelemetry instrumentation
|
|
2746
|
+
*
|
|
2747
|
+
* This method wraps the action execution with distributed tracing instrumentation,
|
|
2748
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
2749
|
+
*
|
|
2750
|
+
* @param name - Action name used for span naming
|
|
2751
|
+
* @param headers - Request headers
|
|
2752
|
+
* @param params - Request parameters
|
|
2753
|
+
* @param action - The user's action function to execute
|
|
2754
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
2755
|
+
* @param logger - Logger instance
|
|
2756
|
+
* @returns Promise resolving to the action result
|
|
2757
|
+
*
|
|
2758
|
+
* @private
|
|
2759
|
+
*/
|
|
2760
|
+
static async executeActionWithInstrumentation(name, headers, params, action, telemetry, logger) {
|
|
2761
|
+
const instrumentedAction = telemetry.instrument(
|
|
2762
|
+
`openwhisk.action.${name}.execute`,
|
|
2763
|
+
async (actionName, actionHeaders, actionParams, actionFn, actionTelemetry, actionLogger) => {
|
|
2764
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2765
|
+
if (span) {
|
|
2766
|
+
span.setAttribute("openwhisk.action.name", actionName);
|
|
2767
|
+
span.setAttribute("openwhisk.action.has_headers", !!actionHeaders);
|
|
2768
|
+
span.addEvent("openwhisk-execution-started");
|
|
2769
|
+
}
|
|
2770
|
+
const result = await actionFn(actionParams, {
|
|
2771
|
+
logger: actionLogger,
|
|
2772
|
+
headers: actionHeaders,
|
|
2773
|
+
telemetry: actionTelemetry
|
|
2774
|
+
});
|
|
2775
|
+
if (span) {
|
|
2776
|
+
const statusCode = "statusCode" in result ? result.statusCode : result.error.statusCode;
|
|
2777
|
+
span.setAttribute("openwhisk.action.response_status", statusCode);
|
|
2778
|
+
span.setAttribute("openwhisk.action.has_error", "error" in result);
|
|
2779
|
+
span.addEvent("openwhisk-execution-completed", {
|
|
2780
|
+
statusCode
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
return result;
|
|
2784
|
+
}
|
|
2785
|
+
);
|
|
2786
|
+
return instrumentedAction(name, headers, params, action, telemetry, logger);
|
|
2075
2787
|
}
|
|
2076
2788
|
};
|
|
2077
2789
|
__name(_OpenwhiskAction, "OpenwhiskAction");
|
|
@@ -2595,41 +3307,17 @@ var _WebhookAction = class _WebhookAction {
|
|
|
2595
3307
|
*/
|
|
2596
3308
|
static execute(name = "webhook", requiredParams = [], requiredHeaders = [], signatureVerification = "disabled" /* DISABLED */, action = async () => response_default2.success()) {
|
|
2597
3309
|
const httpMethods = ["POST" /* POST */];
|
|
2598
|
-
const verifySignature = /* @__PURE__ */ __name(async (params) => {
|
|
2599
|
-
const signature = params.__ow_headers["x-adobe-commerce-webhook-signature"] || "";
|
|
2600
|
-
if (!signature) {
|
|
2601
|
-
return "Header `x-adobe-commerce-webhook-signature` not found. Make sure Webhooks signature is enabled in the Commerce instance.";
|
|
2602
|
-
}
|
|
2603
|
-
const body = params.__ow_body;
|
|
2604
|
-
if (!body) {
|
|
2605
|
-
return "Request body not found. Make sure the action is configured with `raw-http: true`.";
|
|
2606
|
-
}
|
|
2607
|
-
let publicKey = params.PUBLIC_KEY;
|
|
2608
|
-
if (!publicKey && params.PUBLIC_KEY_BASE64) {
|
|
2609
|
-
publicKey = atob(params.PUBLIC_KEY_BASE64);
|
|
2610
|
-
}
|
|
2611
|
-
if (!publicKey) {
|
|
2612
|
-
return "Public key not found. Make sure the action is configured with the input `PUBLIC_KEY` or `PUBLIC_KEY_BASE64` and it is defined in .env file.";
|
|
2613
|
-
}
|
|
2614
|
-
try {
|
|
2615
|
-
const verifier = import_crypto.default.createVerify("SHA256");
|
|
2616
|
-
verifier.update(body);
|
|
2617
|
-
const isSignatureValid = verifier.verify(publicKey, signature, "base64");
|
|
2618
|
-
if (!isSignatureValid) {
|
|
2619
|
-
return "The signature is invalid.";
|
|
2620
|
-
}
|
|
2621
|
-
} catch (error) {
|
|
2622
|
-
return "The signature is invalid.";
|
|
2623
|
-
}
|
|
2624
|
-
return null;
|
|
2625
|
-
}, "verifySignature");
|
|
2626
3310
|
const callback = /* @__PURE__ */ __name(async (params, ctx) => {
|
|
2627
|
-
const { logger } = ctx;
|
|
3311
|
+
const { logger, telemetry } = ctx;
|
|
2628
3312
|
if (signatureVerification === "enabled" /* ENABLED */) {
|
|
2629
|
-
const verificationErrorMessage = await
|
|
3313
|
+
const verificationErrorMessage = await _WebhookAction.verifySignatureWithInstrumentation(
|
|
3314
|
+
name,
|
|
3315
|
+
params,
|
|
3316
|
+
telemetry
|
|
3317
|
+
);
|
|
2630
3318
|
if (verificationErrorMessage) {
|
|
2631
3319
|
logger.error({
|
|
2632
|
-
message: "Webhook
|
|
3320
|
+
message: "Webhook action signature verification failed",
|
|
2633
3321
|
error: verificationErrorMessage
|
|
2634
3322
|
});
|
|
2635
3323
|
const verificationErrorResponse = response_default2.exception(verificationErrorMessage);
|
|
@@ -2640,7 +3328,13 @@ var _WebhookAction = class _WebhookAction {
|
|
|
2640
3328
|
...JSON.parse(atob(params.__ow_body))
|
|
2641
3329
|
};
|
|
2642
3330
|
}
|
|
2643
|
-
const errorMessage =
|
|
3331
|
+
const errorMessage = _WebhookAction.validateWithInstrumentation(
|
|
3332
|
+
name,
|
|
3333
|
+
params,
|
|
3334
|
+
requiredParams,
|
|
3335
|
+
requiredHeaders,
|
|
3336
|
+
telemetry
|
|
3337
|
+
);
|
|
2644
3338
|
if (errorMessage) {
|
|
2645
3339
|
logger.error({
|
|
2646
3340
|
message: "Webhook action validation failed",
|
|
@@ -2649,13 +3343,183 @@ var _WebhookAction = class _WebhookAction {
|
|
|
2649
3343
|
const errorMessageResponse = response_default2.exception(errorMessage);
|
|
2650
3344
|
return response_default.success(JSON.stringify(errorMessageResponse));
|
|
2651
3345
|
}
|
|
2652
|
-
const response = await
|
|
3346
|
+
const response = await _WebhookAction.executeActionWithInstrumentation(
|
|
3347
|
+
name,
|
|
3348
|
+
action,
|
|
3349
|
+
params,
|
|
3350
|
+
ctx
|
|
3351
|
+
);
|
|
2653
3352
|
return response_default.success(JSON.stringify(response));
|
|
2654
3353
|
}, "callback");
|
|
2655
3354
|
runtime_action_default.setActionType("webhook-action");
|
|
2656
3355
|
runtime_action_default.setActionTypeName("Webhook action");
|
|
2657
3356
|
return runtime_action_default.execute(name, httpMethods, [], [], callback);
|
|
2658
3357
|
}
|
|
3358
|
+
/**
|
|
3359
|
+
* Executes webhook action with OpenTelemetry instrumentation
|
|
3360
|
+
*
|
|
3361
|
+
* This method wraps the webhook action execution with distributed tracing instrumentation,
|
|
3362
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
3363
|
+
*
|
|
3364
|
+
* @param name - Webhook action name used for span naming
|
|
3365
|
+
* @param action - The webhook action function to execute
|
|
3366
|
+
* @param params - Request parameters
|
|
3367
|
+
* @param ctx - Context object with logger, headers, and telemetry
|
|
3368
|
+
* @returns Promise resolving to the webhook action response(s)
|
|
3369
|
+
*
|
|
3370
|
+
* @private
|
|
3371
|
+
*/
|
|
3372
|
+
static async executeActionWithInstrumentation(name, action, params, ctx) {
|
|
3373
|
+
const instrumentedWebhookAction = ctx.telemetry.instrument(
|
|
3374
|
+
`webhook.action.${name}.execute`,
|
|
3375
|
+
async (actionName, actionFn, actionParams, context2) => {
|
|
3376
|
+
const span = context2.telemetry.getCurrentSpan();
|
|
3377
|
+
if (span) {
|
|
3378
|
+
span.setAttribute("webhook.action.name", actionName);
|
|
3379
|
+
span.setAttribute("webhook.action.has_headers", !!context2.headers);
|
|
3380
|
+
span.addEvent("webhook-execution-started");
|
|
3381
|
+
}
|
|
3382
|
+
const response = await actionFn(actionParams, context2);
|
|
3383
|
+
if (span) {
|
|
3384
|
+
span.setAttribute("webhook.action.response_is_array", Array.isArray(response));
|
|
3385
|
+
if (Array.isArray(response)) {
|
|
3386
|
+
span.setAttribute("webhook.action.response_count", response.length);
|
|
3387
|
+
}
|
|
3388
|
+
span.addEvent("webhook-execution-completed", {
|
|
3389
|
+
isArray: Array.isArray(response),
|
|
3390
|
+
count: Array.isArray(response) ? response.length : 1
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
return response;
|
|
3394
|
+
}
|
|
3395
|
+
);
|
|
3396
|
+
return instrumentedWebhookAction(name, action, params, ctx);
|
|
3397
|
+
}
|
|
3398
|
+
/**
|
|
3399
|
+
* Verifies webhook signature with OpenTelemetry instrumentation
|
|
3400
|
+
*
|
|
3401
|
+
* This method wraps the signature verification logic with distributed tracing instrumentation,
|
|
3402
|
+
* creating a span that tracks verification metrics and results in New Relic.
|
|
3403
|
+
*
|
|
3404
|
+
* @param name - Webhook action name used for span naming
|
|
3405
|
+
* @param params - Request parameters including headers, body, and public key
|
|
3406
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
3407
|
+
* @returns Error message if verification fails, null if successful
|
|
3408
|
+
*
|
|
3409
|
+
* @private
|
|
3410
|
+
*/
|
|
3411
|
+
static async verifySignatureWithInstrumentation(name, params, telemetry) {
|
|
3412
|
+
const instrumentedVerifySignature = telemetry.instrument(
|
|
3413
|
+
`webhook.action.${name}.verify-signature`,
|
|
3414
|
+
async (actionName, actionParams, actionTelemetry) => {
|
|
3415
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
3416
|
+
if (span) {
|
|
3417
|
+
span.setAttribute("webhook.signature.enabled", true);
|
|
3418
|
+
span.setAttribute(
|
|
3419
|
+
"webhook.signature.header_present",
|
|
3420
|
+
!!actionParams.__ow_headers?.["x-adobe-commerce-webhook-signature"]
|
|
3421
|
+
);
|
|
3422
|
+
span.setAttribute("webhook.signature.has_body", !!actionParams.__ow_body);
|
|
3423
|
+
span.setAttribute(
|
|
3424
|
+
"webhook.signature.has_public_key",
|
|
3425
|
+
!!(actionParams.PUBLIC_KEY || actionParams.PUBLIC_KEY_BASE64)
|
|
3426
|
+
);
|
|
3427
|
+
span.addEvent("signature-verification-started");
|
|
3428
|
+
}
|
|
3429
|
+
const verificationError = await _WebhookAction.verifySignature(actionParams);
|
|
3430
|
+
if (span) {
|
|
3431
|
+
span.setAttribute("webhook.signature.valid", verificationError === null);
|
|
3432
|
+
if (verificationError) {
|
|
3433
|
+
span.setAttribute("webhook.signature.error", verificationError);
|
|
3434
|
+
}
|
|
3435
|
+
span.addEvent("signature-verification-completed", {
|
|
3436
|
+
valid: verificationError === null
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
return verificationError;
|
|
3440
|
+
}
|
|
3441
|
+
);
|
|
3442
|
+
return instrumentedVerifySignature(name, params, telemetry);
|
|
3443
|
+
}
|
|
3444
|
+
/**
|
|
3445
|
+
* Validates webhook parameters and headers with OpenTelemetry instrumentation
|
|
3446
|
+
*
|
|
3447
|
+
* This method wraps the validation logic with distributed tracing instrumentation,
|
|
3448
|
+
* creating a span that tracks validation metrics and results in New Relic.
|
|
3449
|
+
*
|
|
3450
|
+
* @param name - Webhook action name used for span naming
|
|
3451
|
+
* @param params - Request parameters
|
|
3452
|
+
* @param requiredParams - List of required parameter names to validate
|
|
3453
|
+
* @param requiredHeaders - List of required header names to validate
|
|
3454
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
3455
|
+
* @returns Error message if validation fails, empty string if successful
|
|
3456
|
+
*
|
|
3457
|
+
* @private
|
|
3458
|
+
*/
|
|
3459
|
+
static validateWithInstrumentation(name, params, requiredParams, requiredHeaders, telemetry) {
|
|
3460
|
+
const instrumentedValidate = telemetry.instrument(
|
|
3461
|
+
`webhook.action.${name}.validate`,
|
|
3462
|
+
(actionName, actionParams, actionRequiredParams, actionRequiredHeaders, actionTelemetry) => {
|
|
3463
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
3464
|
+
if (span) {
|
|
3465
|
+
span.setAttribute("webhook.validation.required_params", actionRequiredParams.join(","));
|
|
3466
|
+
span.setAttribute("webhook.validation.required_headers", actionRequiredHeaders.join(","));
|
|
3467
|
+
}
|
|
3468
|
+
const errorMessage = validator_default.checkMissingRequestInputs(
|
|
3469
|
+
actionParams,
|
|
3470
|
+
actionRequiredParams,
|
|
3471
|
+
actionRequiredHeaders
|
|
3472
|
+
) ?? "";
|
|
3473
|
+
if (span) {
|
|
3474
|
+
span.setAttribute("webhook.validation.passed", errorMessage === "");
|
|
3475
|
+
if (errorMessage) {
|
|
3476
|
+
span.setAttribute("webhook.validation.error", errorMessage);
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
return errorMessage;
|
|
3480
|
+
}
|
|
3481
|
+
);
|
|
3482
|
+
return instrumentedValidate(name, params, requiredParams, requiredHeaders, telemetry);
|
|
3483
|
+
}
|
|
3484
|
+
/**
|
|
3485
|
+
* Verify webhook signature using a public key and SHA256
|
|
3486
|
+
*
|
|
3487
|
+
* This method validates the authenticity of webhook requests by verifying
|
|
3488
|
+
* the signature against the request body using the provided public key.
|
|
3489
|
+
*
|
|
3490
|
+
* @param params - Request parameters including headers, body, and public key
|
|
3491
|
+
* @returns Error message if verification fails, null if successful
|
|
3492
|
+
*
|
|
3493
|
+
* @private
|
|
3494
|
+
*/
|
|
3495
|
+
static async verifySignature(params) {
|
|
3496
|
+
const signature = params.__ow_headers["x-adobe-commerce-webhook-signature"] || "";
|
|
3497
|
+
if (!signature) {
|
|
3498
|
+
return "Header `x-adobe-commerce-webhook-signature` not found. Make sure Webhooks signature is enabled in the Commerce instance.";
|
|
3499
|
+
}
|
|
3500
|
+
const body = params.__ow_body;
|
|
3501
|
+
if (!body) {
|
|
3502
|
+
return "Request body not found. Make sure the action is configured with `raw-http: true`.";
|
|
3503
|
+
}
|
|
3504
|
+
let publicKey = params.PUBLIC_KEY;
|
|
3505
|
+
if (!publicKey && params.PUBLIC_KEY_BASE64) {
|
|
3506
|
+
publicKey = atob(params.PUBLIC_KEY_BASE64);
|
|
3507
|
+
}
|
|
3508
|
+
if (!publicKey) {
|
|
3509
|
+
return "Public key not found. Make sure the action is configured with the input `PUBLIC_KEY` or `PUBLIC_KEY_BASE64` and it is defined in .env file.";
|
|
3510
|
+
}
|
|
3511
|
+
try {
|
|
3512
|
+
const verifier = import_crypto.default.createVerify("SHA256");
|
|
3513
|
+
verifier.update(body);
|
|
3514
|
+
const isSignatureValid = verifier.verify(publicKey, signature, "base64");
|
|
3515
|
+
if (!isSignatureValid) {
|
|
3516
|
+
return "The signature is invalid.";
|
|
3517
|
+
}
|
|
3518
|
+
} catch (error) {
|
|
3519
|
+
return "The signature is invalid.";
|
|
3520
|
+
}
|
|
3521
|
+
return null;
|
|
3522
|
+
}
|
|
2659
3523
|
};
|
|
2660
3524
|
__name(_WebhookAction, "WebhookAction");
|
|
2661
3525
|
var WebhookAction = _WebhookAction;
|