@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.mjs
CHANGED
|
@@ -558,7 +558,10 @@ var validator_default = Validator;
|
|
|
558
558
|
|
|
559
559
|
// src/framework/telemetry/index.ts
|
|
560
560
|
import { Core } from "@adobe/aio-sdk";
|
|
561
|
-
import {
|
|
561
|
+
import {
|
|
562
|
+
instrument as aioInstrument,
|
|
563
|
+
getInstrumentationHelpers
|
|
564
|
+
} from "@adobe/aio-lib-telemetry";
|
|
562
565
|
|
|
563
566
|
// src/framework/telemetry/new-relic/index.ts
|
|
564
567
|
import {
|
|
@@ -1254,7 +1257,96 @@ var new_relic_default = NewRelicTelemetry;
|
|
|
1254
1257
|
// src/framework/telemetry/index.ts
|
|
1255
1258
|
var _Telemetry = class _Telemetry {
|
|
1256
1259
|
/**
|
|
1257
|
-
* Create a
|
|
1260
|
+
* Create a new Telemetry instance
|
|
1261
|
+
*
|
|
1262
|
+
* Initializes the telemetry manager with optional runtime parameters.
|
|
1263
|
+
* Parameters can be set later using setParams() if not provided during construction.
|
|
1264
|
+
*
|
|
1265
|
+
* @param params - Optional runtime parameters containing telemetry configuration
|
|
1266
|
+
*
|
|
1267
|
+
* @example
|
|
1268
|
+
* ```typescript
|
|
1269
|
+
* const telemetry = new Telemetry({
|
|
1270
|
+
* ENABLE_TELEMETRY: true,
|
|
1271
|
+
* LOG_LEVEL: 'debug',
|
|
1272
|
+
* action_type: 'webhook'
|
|
1273
|
+
* });
|
|
1274
|
+
* ```
|
|
1275
|
+
*/
|
|
1276
|
+
constructor(params = void 0) {
|
|
1277
|
+
/**
|
|
1278
|
+
* Runtime parameters containing telemetry configuration
|
|
1279
|
+
*
|
|
1280
|
+
* Stores the action parameters including telemetry flags (ENABLE_TELEMETRY),
|
|
1281
|
+
* provider configuration (NEW_RELIC_*), log levels, and request context.
|
|
1282
|
+
*
|
|
1283
|
+
* @private
|
|
1284
|
+
*/
|
|
1285
|
+
this.params = void 0;
|
|
1286
|
+
/**
|
|
1287
|
+
* Logger instance
|
|
1288
|
+
*
|
|
1289
|
+
* Stores the logger instance for the telemetry instance.
|
|
1290
|
+
*
|
|
1291
|
+
* @private
|
|
1292
|
+
*/
|
|
1293
|
+
this.logger = void 0;
|
|
1294
|
+
this.params = params;
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Get the current runtime parameters
|
|
1298
|
+
*
|
|
1299
|
+
* Returns the parameters stored in this telemetry instance, or an empty object
|
|
1300
|
+
* if no parameters have been set. These parameters control telemetry behavior
|
|
1301
|
+
* and provide context for logging and tracing.
|
|
1302
|
+
*
|
|
1303
|
+
* @returns Runtime parameters object (never undefined, returns empty object if not set)
|
|
1304
|
+
*
|
|
1305
|
+
* @example
|
|
1306
|
+
* ```typescript
|
|
1307
|
+
* const currentParams = telemetry.getParams();
|
|
1308
|
+
* if (currentParams.ENABLE_TELEMETRY) {
|
|
1309
|
+
* // telemetry is enabled
|
|
1310
|
+
* }
|
|
1311
|
+
* ```
|
|
1312
|
+
*/
|
|
1313
|
+
getParams() {
|
|
1314
|
+
return this.params || {};
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Set or update the runtime parameters
|
|
1318
|
+
*
|
|
1319
|
+
* Updates the parameters used by this telemetry instance. This is typically
|
|
1320
|
+
* called by the initialize() method when the action receives runtime parameters,
|
|
1321
|
+
* but can be used to update configuration at any time.
|
|
1322
|
+
*
|
|
1323
|
+
* @param params - New runtime parameters to store
|
|
1324
|
+
*
|
|
1325
|
+
* @example
|
|
1326
|
+
* ```typescript
|
|
1327
|
+
* telemetry.setParams({
|
|
1328
|
+
* ENABLE_TELEMETRY: true,
|
|
1329
|
+
* NEW_RELIC_TELEMETRY: true,
|
|
1330
|
+
* LOG_LEVEL: 'info'
|
|
1331
|
+
* });
|
|
1332
|
+
* const logger = telemetry.createLogger('my-action');
|
|
1333
|
+
* ```
|
|
1334
|
+
*/
|
|
1335
|
+
setParams(params) {
|
|
1336
|
+
this.params = params;
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Get the logger instance
|
|
1340
|
+
*
|
|
1341
|
+
* Returns the logger instance for the telemetry instance.
|
|
1342
|
+
*
|
|
1343
|
+
* @returns Logger instance
|
|
1344
|
+
*/
|
|
1345
|
+
getLogger() {
|
|
1346
|
+
return this.logger;
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Create a logger with standard configuration and automatic metadata injection
|
|
1258
1350
|
*
|
|
1259
1351
|
* This method creates a structured logger and wraps it to automatically add
|
|
1260
1352
|
* contextual metadata to all log calls:
|
|
@@ -1266,50 +1358,30 @@ var _Telemetry = class _Telemetry {
|
|
|
1266
1358
|
* automatically appear in all logs sent to New Relic if ENABLE_TELEMETRY is true.
|
|
1267
1359
|
*
|
|
1268
1360
|
* @param name - Logger name (typically action name)
|
|
1269
|
-
* @param params - Runtime parameters containing LOG_LEVEL, optional ENABLE_TELEMETRY, ENVIRONMENT, action_type, and __ow_headers
|
|
1270
1361
|
* @returns Configured logger instance with automatic metadata injection
|
|
1271
1362
|
*
|
|
1272
|
-
* @example
|
|
1273
|
-
* ```typescript
|
|
1274
|
-
* const logger = Telemetry.createLogger("my-action", params);
|
|
1275
|
-
* logger.info("Processing started");
|
|
1276
|
-
* // Logs: "Processing started"
|
|
1277
|
-
* ```
|
|
1278
|
-
*
|
|
1279
|
-
* @example JSON object message with automatic metadata
|
|
1363
|
+
* @example
|
|
1280
1364
|
* ```typescript
|
|
1281
|
-
* const logger =
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1284
|
-
* __ow_headers: { 'x-adobe-commerce-request-id': 'req-123' }
|
|
1285
|
-
* });
|
|
1286
|
-
* logger.info({
|
|
1287
|
-
* message: "User action completed",
|
|
1288
|
-
* user_id: "123"
|
|
1289
|
-
* });
|
|
1290
|
-
* // In New Relic: {
|
|
1291
|
-
* // message: "User action completed",
|
|
1292
|
-
* // user_id: "123",
|
|
1293
|
-
* // "x-adobe-commerce-request-id": "req-123",
|
|
1294
|
-
* // "action.type": "webhook",
|
|
1295
|
-
* // environment: "development",
|
|
1296
|
-
* // ...
|
|
1297
|
-
* // }
|
|
1365
|
+
* const logger = telemetry.createLogger('my-action');
|
|
1366
|
+
* logger.info({ message: 'User action completed', user_id: '123' });
|
|
1367
|
+
* // In New Relic: includes message, user_id, request-id, action.type, environment
|
|
1298
1368
|
* ```
|
|
1299
1369
|
*/
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
level: params
|
|
1303
|
-
}) : getLogger(name, {
|
|
1304
|
-
level: params.LOG_LEVEL || "info"
|
|
1370
|
+
createLogger(name) {
|
|
1371
|
+
let baseLogger = Core.Logger(name, {
|
|
1372
|
+
level: this.params?.LOG_LEVEL || "info"
|
|
1305
1373
|
});
|
|
1374
|
+
if (this.params?.ENABLE_TELEMETRY === true) {
|
|
1375
|
+
const helpers = getInstrumentationHelpers();
|
|
1376
|
+
baseLogger = helpers.logger;
|
|
1377
|
+
}
|
|
1306
1378
|
const metadata = {};
|
|
1307
|
-
const headers = params
|
|
1379
|
+
const headers = this.params?.__ow_headers;
|
|
1308
1380
|
const requestId = headers?.["x-adobe-commerce-request-id"];
|
|
1309
1381
|
if (requestId && requestId !== "") {
|
|
1310
1382
|
metadata["x-adobe-commerce-request-id"] = requestId;
|
|
1311
1383
|
}
|
|
1312
|
-
const actionType = params
|
|
1384
|
+
const actionType = this.params?.action_type;
|
|
1313
1385
|
if (actionType && actionType !== "") {
|
|
1314
1386
|
metadata["action.type"] = actionType;
|
|
1315
1387
|
}
|
|
@@ -1346,10 +1418,11 @@ var _Telemetry = class _Telemetry {
|
|
|
1346
1418
|
}
|
|
1347
1419
|
}, "error")
|
|
1348
1420
|
};
|
|
1349
|
-
|
|
1421
|
+
this.logger = {
|
|
1350
1422
|
...baseLogger,
|
|
1351
1423
|
...wrapper
|
|
1352
1424
|
};
|
|
1425
|
+
return this.logger;
|
|
1353
1426
|
}
|
|
1354
1427
|
/**
|
|
1355
1428
|
* Extract structured error information for logging
|
|
@@ -1363,13 +1436,13 @@ var _Telemetry = class _Telemetry {
|
|
|
1363
1436
|
* @example
|
|
1364
1437
|
* ```typescript
|
|
1365
1438
|
* try {
|
|
1366
|
-
*
|
|
1439
|
+
* await processOrder(orderId);
|
|
1367
1440
|
* } catch (error) {
|
|
1368
|
-
* logger.error(
|
|
1441
|
+
* logger.error({ message: 'Operation failed', ...telemetry.formatError(error) });
|
|
1369
1442
|
* }
|
|
1370
1443
|
* ```
|
|
1371
1444
|
*/
|
|
1372
|
-
|
|
1445
|
+
formatError(error) {
|
|
1373
1446
|
if (error instanceof Error) {
|
|
1374
1447
|
return {
|
|
1375
1448
|
error_name: error.name,
|
|
@@ -1379,6 +1452,77 @@ var _Telemetry = class _Telemetry {
|
|
|
1379
1452
|
}
|
|
1380
1453
|
return { error: String(error) };
|
|
1381
1454
|
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Get the current OpenTelemetry span for adding attributes and events
|
|
1457
|
+
*
|
|
1458
|
+
* This method provides access to the currently active span, allowing you to
|
|
1459
|
+
* add custom attributes and events for enhanced observability in New Relic.
|
|
1460
|
+
*
|
|
1461
|
+
* **IMPORTANT**: This must be called within an active trace context
|
|
1462
|
+
* (i.e., inside a function that has been wrapped by Telemetry.initialize()).
|
|
1463
|
+
* If called outside an active context or if ENABLE_TELEMETRY is false, it returns null.
|
|
1464
|
+
*
|
|
1465
|
+
* @returns The current span object, or null if no active span exists
|
|
1466
|
+
*
|
|
1467
|
+
* @example
|
|
1468
|
+
* ```typescript
|
|
1469
|
+
* const processOrder = async (orderId: string) => {
|
|
1470
|
+
* const span = telemetry.getCurrentSpan();
|
|
1471
|
+
* if (span) {
|
|
1472
|
+
* span.setAttribute('order.id', orderId);
|
|
1473
|
+
* span.setAttribute('order.status', 'processing');
|
|
1474
|
+
* span.addEvent('processing-started');
|
|
1475
|
+
* }
|
|
1476
|
+
* // ... process order logic ...
|
|
1477
|
+
* };
|
|
1478
|
+
* ```
|
|
1479
|
+
*/
|
|
1480
|
+
getCurrentSpan() {
|
|
1481
|
+
if (this.params?.ENABLE_TELEMETRY === true) {
|
|
1482
|
+
const helpers = getInstrumentationHelpers();
|
|
1483
|
+
return helpers?.currentSpan || null;
|
|
1484
|
+
}
|
|
1485
|
+
return null;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Instrument a function with OpenTelemetry for distributed tracing
|
|
1489
|
+
*
|
|
1490
|
+
* This method wraps any function with OpenTelemetry instrumentation,
|
|
1491
|
+
* creating a child span in the current trace context. Use this to create
|
|
1492
|
+
* detailed spans for specific operations within your action.
|
|
1493
|
+
*
|
|
1494
|
+
* **IMPORTANT**: The instrumented function must be called within an active trace context
|
|
1495
|
+
* (i.e., inside a function that has been wrapped by Telemetry.initialize()).
|
|
1496
|
+
* If called outside an active context, the span will not be created or exported.
|
|
1497
|
+
*
|
|
1498
|
+
* @param spanName - Name for the span (appears in New Relic as the span name)
|
|
1499
|
+
* @param fn - The function to instrument (sync or async)
|
|
1500
|
+
* @returns The instrumented function that creates a child span when called
|
|
1501
|
+
*
|
|
1502
|
+
* @example
|
|
1503
|
+
* ```typescript
|
|
1504
|
+
* const fetchUserData = telemetry.instrument(
|
|
1505
|
+
* 'fetch-user-data',
|
|
1506
|
+
* async (userId: string) => {
|
|
1507
|
+
* const span = telemetry.getCurrentSpan();
|
|
1508
|
+
* if (span) {
|
|
1509
|
+
* span.setAttribute('user.id', userId);
|
|
1510
|
+
* span.addEvent('fetching-started');
|
|
1511
|
+
* }
|
|
1512
|
+
* const response = await fetch(`/api/users/${userId}`);
|
|
1513
|
+
* return response.json();
|
|
1514
|
+
* }
|
|
1515
|
+
* );
|
|
1516
|
+
* const userData = await fetchUserData('123');
|
|
1517
|
+
* ```
|
|
1518
|
+
*/
|
|
1519
|
+
instrument(spanName, fn) {
|
|
1520
|
+
return aioInstrument(fn, {
|
|
1521
|
+
spanConfig: {
|
|
1522
|
+
spanName
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1382
1526
|
/**
|
|
1383
1527
|
* Initialize telemetry for a runtime action with provider fallback chain
|
|
1384
1528
|
*
|
|
@@ -1396,60 +1540,20 @@ var _Telemetry = class _Telemetry {
|
|
|
1396
1540
|
* is returned to alert you of the misconfiguration. This prevents silently running
|
|
1397
1541
|
* without telemetry when you expect it to be working.
|
|
1398
1542
|
*
|
|
1399
|
-
* Environment Configuration:
|
|
1400
|
-
* Pass params.ENVIRONMENT to set the environment field in all logs.
|
|
1401
|
-
* This can be set via environment variables in your action configuration.
|
|
1402
|
-
*
|
|
1403
1543
|
* @param action - The runtime action function to instrument
|
|
1404
1544
|
* @returns The instrumented action ready for export
|
|
1405
1545
|
*
|
|
1406
|
-
* @example
|
|
1407
|
-
* ```typescript
|
|
1408
|
-
* // Environment:
|
|
1409
|
-
* // ENABLE_TELEMETRY=true
|
|
1410
|
-
* // NEW_RELIC_TELEMETRY=true
|
|
1411
|
-
* // NEW_RELIC_SERVICE_NAME=my-service
|
|
1412
|
-
* // NEW_RELIC_LICENSE_KEY=xxxxx
|
|
1413
|
-
*
|
|
1414
|
-
* export const main = Telemetry.initialize(myAction);
|
|
1415
|
-
* // ✅ Uses New Relic telemetry
|
|
1416
|
-
* ```
|
|
1417
|
-
*
|
|
1418
|
-
* @example New Relic enabled but misconfigured - Returns error response
|
|
1419
|
-
* ```typescript
|
|
1420
|
-
* // Environment:
|
|
1421
|
-
* // ENABLE_TELEMETRY=true
|
|
1422
|
-
* // NEW_RELIC_TELEMETRY=true
|
|
1423
|
-
* // NEW_RELIC_SERVICE_NAME=my-service
|
|
1424
|
-
* // Missing NEW_RELIC_LICENSE_KEY!
|
|
1425
|
-
*
|
|
1426
|
-
* export const main = Telemetry.initialize(myAction);
|
|
1427
|
-
* // ❌ Returns { error: { statusCode: 500, body: { error: "Telemetry configuration error: NEW_RELIC_LICENSE_KEY is required" } } }
|
|
1428
|
-
* // This is intentional - you want to know your telemetry config is broken!
|
|
1429
|
-
* ```
|
|
1430
|
-
*
|
|
1431
|
-
* @example Multi-provider fallback
|
|
1432
|
-
* ```typescript
|
|
1433
|
-
* // Environment:
|
|
1434
|
-
* // ENABLE_TELEMETRY=true
|
|
1435
|
-
* // NEW_RELIC_TELEMETRY=false // Skip New Relic
|
|
1436
|
-
* // GRAFANA_TELEMETRY=true // Use Grafana instead
|
|
1437
|
-
*
|
|
1438
|
-
* export const main = Telemetry.initialize(myAction);
|
|
1439
|
-
* // ✅ Skips New Relic, tries Grafana (when implemented)
|
|
1440
|
-
* ```
|
|
1441
|
-
*
|
|
1442
|
-
* @example No telemetry
|
|
1546
|
+
* @example
|
|
1443
1547
|
* ```typescript
|
|
1444
|
-
*
|
|
1445
|
-
*
|
|
1446
|
-
*
|
|
1447
|
-
*
|
|
1448
|
-
* // ✅ Returns original action without instrumentation
|
|
1548
|
+
* const telemetry = new Telemetry();
|
|
1549
|
+
* export const main = telemetry.initialize(myAction);
|
|
1550
|
+
* // Environment: ENABLE_TELEMETRY=true, NEW_RELIC_TELEMETRY=true
|
|
1551
|
+
* // Result: Uses New Relic telemetry with full distributed tracing
|
|
1449
1552
|
* ```
|
|
1450
1553
|
*/
|
|
1451
|
-
|
|
1554
|
+
initialize(action) {
|
|
1452
1555
|
return async (params) => {
|
|
1556
|
+
this.setParams(params);
|
|
1453
1557
|
const newRelicTelemetry = new new_relic_default();
|
|
1454
1558
|
if (newRelicTelemetry.canInitialize(params)) {
|
|
1455
1559
|
try {
|
|
@@ -1524,7 +1628,7 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1524
1628
|
* @param requiredHeaders - Required header names (case-insensitive)
|
|
1525
1629
|
* @param action - User-defined action function that receives:
|
|
1526
1630
|
* - params: All request parameters including __ow_* runtime parameters
|
|
1527
|
-
* - ctx: Context object with logger and
|
|
1631
|
+
* - ctx: Context object with logger, headers, and telemetry
|
|
1528
1632
|
* @returns Wrapped action function ready to be exported as Adobe I/O Runtime entrypoint
|
|
1529
1633
|
*
|
|
1530
1634
|
* @example With All Options
|
|
@@ -1534,10 +1638,16 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1534
1638
|
* [HttpMethod.POST],
|
|
1535
1639
|
* ['orderId', 'customerId'],
|
|
1536
1640
|
* ['authorization', 'x-api-key'],
|
|
1537
|
-
* async (params, { logger, headers }) => {
|
|
1641
|
+
* async (params, { logger, headers, telemetry }) => {
|
|
1538
1642
|
* const { orderId, customerId } = params;
|
|
1539
1643
|
* logger.info({ message: 'Processing order', orderId, customerId });
|
|
1540
1644
|
*
|
|
1645
|
+
* // Access current span for custom instrumentation
|
|
1646
|
+
* const span = telemetry.getCurrentSpan();
|
|
1647
|
+
* if (span) {
|
|
1648
|
+
* span.setAttribute('order.id', orderId);
|
|
1649
|
+
* }
|
|
1650
|
+
*
|
|
1541
1651
|
* // Your business logic here
|
|
1542
1652
|
* const result = await processOrderLogic(orderId, customerId);
|
|
1543
1653
|
*
|
|
@@ -1560,11 +1670,13 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1560
1670
|
static execute(name = "main", httpMethods = [], requiredParams = [], requiredHeaders = [], action = async (_params) => {
|
|
1561
1671
|
return { statusCode: 200 /* OK */, body: {} };
|
|
1562
1672
|
}) {
|
|
1673
|
+
const telemetry = new telemetry_default();
|
|
1563
1674
|
const runtimeAction = /* @__PURE__ */ __name(async (params) => {
|
|
1564
1675
|
if (!params.action_type) {
|
|
1565
1676
|
params.action_type = _RuntimeAction.getActionType();
|
|
1566
1677
|
}
|
|
1567
|
-
|
|
1678
|
+
telemetry.setParams(params);
|
|
1679
|
+
const logger = telemetry.createLogger(name);
|
|
1568
1680
|
try {
|
|
1569
1681
|
logger.debug({
|
|
1570
1682
|
message: `${_RuntimeAction.getActionTypeName()} execution started`
|
|
@@ -1581,18 +1693,26 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1581
1693
|
body: params.__ow_body || {}
|
|
1582
1694
|
})
|
|
1583
1695
|
);
|
|
1584
|
-
const validationError = _RuntimeAction.
|
|
1696
|
+
const validationError = _RuntimeAction.validateRequestWithInstrumentation(
|
|
1697
|
+
name,
|
|
1585
1698
|
params,
|
|
1586
1699
|
requiredParams,
|
|
1587
1700
|
requiredHeaders,
|
|
1588
1701
|
httpMethods,
|
|
1589
|
-
|
|
1590
|
-
|
|
1702
|
+
telemetry,
|
|
1703
|
+
logger
|
|
1591
1704
|
);
|
|
1592
1705
|
if (validationError) {
|
|
1593
1706
|
return validationError;
|
|
1594
1707
|
}
|
|
1595
|
-
const result = await
|
|
1708
|
+
const result = await _RuntimeAction.executeActionWithInstrumentation(
|
|
1709
|
+
name,
|
|
1710
|
+
params.__ow_headers || {},
|
|
1711
|
+
params,
|
|
1712
|
+
action,
|
|
1713
|
+
telemetry,
|
|
1714
|
+
logger
|
|
1715
|
+
);
|
|
1596
1716
|
logger.debug(
|
|
1597
1717
|
JSON.stringify({
|
|
1598
1718
|
message: `${_RuntimeAction.getActionTypeName()} execution completed`,
|
|
@@ -1616,7 +1736,108 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1616
1736
|
return response_default.error(500 /* INTERNAL_ERROR */, "server error");
|
|
1617
1737
|
}
|
|
1618
1738
|
}, "runtimeAction");
|
|
1619
|
-
return
|
|
1739
|
+
return telemetry.initialize(runtimeAction);
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* Executes user's action with OpenTelemetry instrumentation
|
|
1743
|
+
*
|
|
1744
|
+
* This method wraps the action execution with distributed tracing instrumentation,
|
|
1745
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
1746
|
+
*
|
|
1747
|
+
* @param name - Action name used for span naming
|
|
1748
|
+
* @param headers - Request headers
|
|
1749
|
+
* @param params - Request parameters
|
|
1750
|
+
* @param action - The user's action function to execute
|
|
1751
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
1752
|
+
* @param logger - Logger instance
|
|
1753
|
+
* @returns Promise resolving to the action result
|
|
1754
|
+
*
|
|
1755
|
+
* @private
|
|
1756
|
+
*/
|
|
1757
|
+
static async executeActionWithInstrumentation(name, headers, params, action, telemetry, logger) {
|
|
1758
|
+
const instrumentedAction = telemetry.instrument(
|
|
1759
|
+
`runtime.action.${name}.execute`,
|
|
1760
|
+
async (actionName, actionHeaders, actionParams, actionFn, actionTelemetry, actionLogger) => {
|
|
1761
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
1762
|
+
if (span) {
|
|
1763
|
+
span.setAttribute("action.name", actionName);
|
|
1764
|
+
span.setAttribute("action.type", actionParams.action_type || "unknown");
|
|
1765
|
+
span.setAttribute("action.has_headers", !!actionHeaders);
|
|
1766
|
+
span.addEvent("action-execution-started");
|
|
1767
|
+
}
|
|
1768
|
+
const result = await actionFn(actionParams, {
|
|
1769
|
+
logger: actionLogger,
|
|
1770
|
+
headers: actionHeaders,
|
|
1771
|
+
telemetry: actionTelemetry
|
|
1772
|
+
});
|
|
1773
|
+
if (span) {
|
|
1774
|
+
const statusCode = "statusCode" in result ? result.statusCode : result.error.statusCode;
|
|
1775
|
+
span.setAttribute("action.response_status", statusCode);
|
|
1776
|
+
span.setAttribute("action.has_error", "error" in result);
|
|
1777
|
+
span.addEvent("action-execution-completed", {
|
|
1778
|
+
statusCode
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
return result;
|
|
1782
|
+
}
|
|
1783
|
+
);
|
|
1784
|
+
return instrumentedAction(name, headers, params, action, telemetry, logger);
|
|
1785
|
+
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Validates incoming request with OpenTelemetry instrumentation
|
|
1788
|
+
*
|
|
1789
|
+
* This method wraps the validation logic with distributed tracing instrumentation,
|
|
1790
|
+
* creating a span that tracks validation metrics and results in New Relic.
|
|
1791
|
+
*
|
|
1792
|
+
* @param name - Action name used for span naming
|
|
1793
|
+
* @param params - Request parameters including __ow_* runtime parameters
|
|
1794
|
+
* @param requiredParams - List of required parameter names to validate
|
|
1795
|
+
* @param requiredHeaders - List of required header names to validate (case-insensitive)
|
|
1796
|
+
* @param httpMethods - Allowed HTTP methods. Empty array skips HTTP method validation
|
|
1797
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
1798
|
+
* @param logger - Logger instance for error logging
|
|
1799
|
+
* @returns RuntimeActionResponseType with error details if validation fails, null if valid
|
|
1800
|
+
*
|
|
1801
|
+
* @private
|
|
1802
|
+
*/
|
|
1803
|
+
static validateRequestWithInstrumentation(name, params, requiredParams, requiredHeaders, httpMethods, telemetry, logger) {
|
|
1804
|
+
const instrumentedValidate = telemetry.instrument(
|
|
1805
|
+
`runtime.action.${name}.validate`,
|
|
1806
|
+
(actionName, actionParams, actionRequiredParams, actionRequiredHeaders, actionHttpMethods, actionTelemetry, actionLogger) => {
|
|
1807
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
1808
|
+
if (span) {
|
|
1809
|
+
span.setAttribute("validation.required_params", actionRequiredParams.join(","));
|
|
1810
|
+
span.setAttribute("validation.required_headers", actionRequiredHeaders.join(","));
|
|
1811
|
+
span.setAttribute("validation.allowed_methods", actionHttpMethods.join(","));
|
|
1812
|
+
span.setAttribute("validation.request_method", actionParams.__ow_method || "unknown");
|
|
1813
|
+
}
|
|
1814
|
+
const validationResult = _RuntimeAction.validateRequest(
|
|
1815
|
+
actionName,
|
|
1816
|
+
actionParams,
|
|
1817
|
+
actionRequiredParams,
|
|
1818
|
+
actionRequiredHeaders,
|
|
1819
|
+
actionHttpMethods,
|
|
1820
|
+
actionLogger
|
|
1821
|
+
);
|
|
1822
|
+
if (span) {
|
|
1823
|
+
span.setAttribute("validation.passed", validationResult === null);
|
|
1824
|
+
if (validationResult) {
|
|
1825
|
+
const statusCode = "statusCode" in validationResult ? validationResult.statusCode : validationResult.error.statusCode;
|
|
1826
|
+
span.setAttribute("validation.error_code", statusCode);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
return validationResult;
|
|
1830
|
+
}
|
|
1831
|
+
);
|
|
1832
|
+
return instrumentedValidate(
|
|
1833
|
+
name,
|
|
1834
|
+
params,
|
|
1835
|
+
requiredParams,
|
|
1836
|
+
requiredHeaders,
|
|
1837
|
+
httpMethods,
|
|
1838
|
+
telemetry,
|
|
1839
|
+
logger
|
|
1840
|
+
);
|
|
1620
1841
|
}
|
|
1621
1842
|
/**
|
|
1622
1843
|
* Validates incoming request against required parameters, headers, and HTTP methods
|
|
@@ -1626,12 +1847,12 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1626
1847
|
* 2. Checks for missing required headers (case-insensitive)
|
|
1627
1848
|
* 3. Validates the HTTP method against allowed methods list
|
|
1628
1849
|
*
|
|
1850
|
+
* @param _name - Action name for logging (currently unused)
|
|
1629
1851
|
* @param params - Request parameters including __ow_* runtime parameters
|
|
1630
1852
|
* @param requiredParams - List of required parameter names to validate
|
|
1631
1853
|
* @param requiredHeaders - List of required header names to validate (case-insensitive)
|
|
1632
1854
|
* @param httpMethods - Allowed HTTP methods. Empty array skips HTTP method validation
|
|
1633
1855
|
* @param logger - Logger instance for error logging (child logger with request ID if present)
|
|
1634
|
-
* @param name - Action name for logging
|
|
1635
1856
|
* @returns RuntimeActionResponseType with error details if validation fails, null if valid
|
|
1636
1857
|
*
|
|
1637
1858
|
* @private
|
|
@@ -1651,7 +1872,7 @@ var _RuntimeAction = class _RuntimeAction {
|
|
|
1651
1872
|
* }
|
|
1652
1873
|
* ```
|
|
1653
1874
|
*/
|
|
1654
|
-
static validateRequest(params, requiredParams, requiredHeaders, httpMethods, logger
|
|
1875
|
+
static validateRequest(_name, params, requiredParams, requiredHeaders, httpMethods, logger) {
|
|
1655
1876
|
const errorMessage = validator_default.checkMissingRequestInputs(params, requiredParams, requiredHeaders) ?? "";
|
|
1656
1877
|
if (errorMessage) {
|
|
1657
1878
|
logger.error({
|
|
@@ -1731,11 +1952,15 @@ var _EventConsumerAction = class _EventConsumerAction {
|
|
|
1731
1952
|
* 'webhook-processor',
|
|
1732
1953
|
* ['eventType', 'eventData'],
|
|
1733
1954
|
* ['x-webhook-signature'],
|
|
1734
|
-
* async (params, { logger, headers }) => {
|
|
1955
|
+
* async (params, { logger, headers, telemetry }) => {
|
|
1735
1956
|
* logger.info({
|
|
1736
1957
|
* message: 'Processing webhook',
|
|
1737
1958
|
* eventType: params.eventType
|
|
1738
1959
|
* });
|
|
1960
|
+
* const span = telemetry.getCurrentSpan();
|
|
1961
|
+
* if (span) {
|
|
1962
|
+
* span.setAttribute('event.type', params.eventType);
|
|
1963
|
+
* }
|
|
1739
1964
|
* // Process event...
|
|
1740
1965
|
* return { statusCode: 200, body: { processed: true } };
|
|
1741
1966
|
* }
|
|
@@ -1745,9 +1970,10 @@ var _EventConsumerAction = class _EventConsumerAction {
|
|
|
1745
1970
|
static execute(name = "main", requiredParams = [], requiredHeaders = [], action = async (_params) => {
|
|
1746
1971
|
return { statusCode: 200 /* OK */, body: {} };
|
|
1747
1972
|
}) {
|
|
1973
|
+
const telemetry = new telemetry_default();
|
|
1748
1974
|
const eventConsumerAction = /* @__PURE__ */ __name(async (params) => {
|
|
1749
1975
|
params.action_type = "event-consumer-action";
|
|
1750
|
-
const logger =
|
|
1976
|
+
const logger = telemetry.createLogger(name);
|
|
1751
1977
|
try {
|
|
1752
1978
|
logger.debug({
|
|
1753
1979
|
message: "Event consumer action execution started"
|
|
@@ -1760,7 +1986,13 @@ var _EventConsumerAction = class _EventConsumerAction {
|
|
|
1760
1986
|
message: "Event consumer action parameters received",
|
|
1761
1987
|
parameters: params
|
|
1762
1988
|
});
|
|
1763
|
-
const errorMessage =
|
|
1989
|
+
const errorMessage = _EventConsumerAction.validateWithInstrumentation(
|
|
1990
|
+
name,
|
|
1991
|
+
params,
|
|
1992
|
+
requiredParams,
|
|
1993
|
+
requiredHeaders,
|
|
1994
|
+
telemetry
|
|
1995
|
+
);
|
|
1764
1996
|
if (errorMessage) {
|
|
1765
1997
|
logger.error({
|
|
1766
1998
|
message: "Event consumer action validation failed",
|
|
@@ -1768,7 +2000,14 @@ var _EventConsumerAction = class _EventConsumerAction {
|
|
|
1768
2000
|
});
|
|
1769
2001
|
return response_default.error(400 /* BAD_REQUEST */, errorMessage);
|
|
1770
2002
|
}
|
|
1771
|
-
const result = await
|
|
2003
|
+
const result = await _EventConsumerAction.executeActionWithInstrumentation(
|
|
2004
|
+
name,
|
|
2005
|
+
params.__ow_headers || {},
|
|
2006
|
+
params,
|
|
2007
|
+
action,
|
|
2008
|
+
telemetry,
|
|
2009
|
+
logger
|
|
2010
|
+
);
|
|
1772
2011
|
logger.debug({
|
|
1773
2012
|
message: "Event consumer action execution completed",
|
|
1774
2013
|
result
|
|
@@ -1777,16 +2016,109 @@ var _EventConsumerAction = class _EventConsumerAction {
|
|
|
1777
2016
|
} catch (error) {
|
|
1778
2017
|
if (error instanceof Error) {
|
|
1779
2018
|
logger.error({
|
|
2019
|
+
message: "Event consumer action execution failed",
|
|
1780
2020
|
error: error.message,
|
|
1781
2021
|
stack: error.stack
|
|
1782
2022
|
});
|
|
1783
2023
|
} else {
|
|
1784
|
-
logger.error({
|
|
2024
|
+
logger.error({
|
|
2025
|
+
message: "Event consumer action execution failed",
|
|
2026
|
+
error
|
|
2027
|
+
});
|
|
1785
2028
|
}
|
|
1786
2029
|
return response_default.error(500 /* INTERNAL_ERROR */, "server error");
|
|
1787
2030
|
}
|
|
1788
2031
|
}, "eventConsumerAction");
|
|
1789
|
-
return
|
|
2032
|
+
return telemetry.initialize(eventConsumerAction);
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Validates event consumer request with OpenTelemetry instrumentation
|
|
2036
|
+
*
|
|
2037
|
+
* This method wraps the validation logic with distributed tracing instrumentation,
|
|
2038
|
+
* creating a span that tracks validation metrics and results in New Relic.
|
|
2039
|
+
*
|
|
2040
|
+
* @param name - Action name used for span naming
|
|
2041
|
+
* @param params - Request parameters
|
|
2042
|
+
* @param requiredParams - List of required parameter names to validate
|
|
2043
|
+
* @param requiredHeaders - List of required header names to validate
|
|
2044
|
+
* @returns Error message if validation fails, empty string if successful
|
|
2045
|
+
*
|
|
2046
|
+
* @private
|
|
2047
|
+
*/
|
|
2048
|
+
static validateWithInstrumentation(name, params, requiredParams, requiredHeaders, telemetry) {
|
|
2049
|
+
const instrumentedValidate = telemetry.instrument(
|
|
2050
|
+
`event.consumer.action.${name}.validate`,
|
|
2051
|
+
(actionName, actionParams, actionRequiredParams, actionRequiredHeaders, actionTelemetry) => {
|
|
2052
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2053
|
+
if (span) {
|
|
2054
|
+
span.setAttribute(
|
|
2055
|
+
"event.consumer.validation.required_params",
|
|
2056
|
+
actionRequiredParams.join(",")
|
|
2057
|
+
);
|
|
2058
|
+
span.setAttribute(
|
|
2059
|
+
"event.consumer.validation.required_headers",
|
|
2060
|
+
actionRequiredHeaders.join(",")
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
const errorMessage = validator_default.checkMissingRequestInputs(
|
|
2064
|
+
actionParams,
|
|
2065
|
+
actionRequiredParams,
|
|
2066
|
+
actionRequiredHeaders
|
|
2067
|
+
) || "";
|
|
2068
|
+
if (span) {
|
|
2069
|
+
span.setAttribute("event.consumer.validation.passed", errorMessage === "");
|
|
2070
|
+
if (errorMessage) {
|
|
2071
|
+
span.setAttribute("event.consumer.validation.error", errorMessage);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
return errorMessage;
|
|
2075
|
+
}
|
|
2076
|
+
);
|
|
2077
|
+
return instrumentedValidate(name, params, requiredParams, requiredHeaders, telemetry);
|
|
2078
|
+
}
|
|
2079
|
+
/**
|
|
2080
|
+
* Executes event consumer action with OpenTelemetry instrumentation
|
|
2081
|
+
*
|
|
2082
|
+
* This method wraps the action execution with distributed tracing instrumentation,
|
|
2083
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
2084
|
+
*
|
|
2085
|
+
* @param name - Action name used for span naming
|
|
2086
|
+
* @param headers - Request headers
|
|
2087
|
+
* @param params - Request parameters
|
|
2088
|
+
* @param action - The user's action function to execute
|
|
2089
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
2090
|
+
* @param logger - Logger instance
|
|
2091
|
+
* @returns Promise resolving to the action result
|
|
2092
|
+
*
|
|
2093
|
+
* @private
|
|
2094
|
+
*/
|
|
2095
|
+
static async executeActionWithInstrumentation(name, headers, params, action, telemetry, logger) {
|
|
2096
|
+
const instrumentedAction = telemetry.instrument(
|
|
2097
|
+
`event.consumer.action.${name}.execute`,
|
|
2098
|
+
async (actionName, actionHeaders, actionParams, actionFn, actionTelemetry, actionLogger) => {
|
|
2099
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2100
|
+
if (span) {
|
|
2101
|
+
span.setAttribute("event.consumer.action.name", actionName);
|
|
2102
|
+
span.setAttribute("event.consumer.action.has_headers", !!actionHeaders);
|
|
2103
|
+
span.addEvent("event-consumer-execution-started");
|
|
2104
|
+
}
|
|
2105
|
+
const result = await actionFn(actionParams, {
|
|
2106
|
+
logger: actionLogger,
|
|
2107
|
+
headers: actionHeaders,
|
|
2108
|
+
telemetry: actionTelemetry
|
|
2109
|
+
});
|
|
2110
|
+
if (span) {
|
|
2111
|
+
const statusCode = "statusCode" in result ? result.statusCode : result.error.statusCode;
|
|
2112
|
+
span.setAttribute("event.consumer.action.response_status", statusCode);
|
|
2113
|
+
span.setAttribute("event.consumer.action.has_error", "error" in result);
|
|
2114
|
+
span.addEvent("event-consumer-execution-completed", {
|
|
2115
|
+
statusCode
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
return result;
|
|
2119
|
+
}
|
|
2120
|
+
);
|
|
2121
|
+
return instrumentedAction(name, headers, params, action, telemetry, logger);
|
|
1790
2122
|
}
|
|
1791
2123
|
};
|
|
1792
2124
|
__name(_EventConsumerAction, "EventConsumerAction");
|
|
@@ -1796,6 +2128,116 @@ var event_consumer_action_default = EventConsumerAction;
|
|
|
1796
2128
|
// src/framework/graphql-action/index.ts
|
|
1797
2129
|
import { graphql, buildSchema, parse, validate } from "graphql";
|
|
1798
2130
|
var _GraphQlAction = class _GraphQlAction {
|
|
2131
|
+
/**
|
|
2132
|
+
* Creates a GraphQL action handler with schema validation and telemetry
|
|
2133
|
+
*
|
|
2134
|
+
* This method wraps a GraphQL API with standardized runtime functionality:
|
|
2135
|
+
* 1. Builds and validates the GraphQL schema
|
|
2136
|
+
* 2. Parses incoming GraphQL queries
|
|
2137
|
+
* 3. Validates queries against the schema
|
|
2138
|
+
* 4. Optionally blocks introspection queries for security
|
|
2139
|
+
* 5. Executes queries with provided resolvers
|
|
2140
|
+
* 6. Integrates with OpenTelemetry for distributed tracing
|
|
2141
|
+
* 7. Provides structured logging with request ID correlation
|
|
2142
|
+
*
|
|
2143
|
+
* All operations are instrumented with OpenTelemetry spans for observability in New Relic.
|
|
2144
|
+
*
|
|
2145
|
+
* @param schema - GraphQL schema definition string (SDL format)
|
|
2146
|
+
* @param resolvers - Function that returns resolver map with access to logger, headers, and params
|
|
2147
|
+
* @param name - Action name for logging and telemetry spans (default: 'main')
|
|
2148
|
+
* @param disableIntrospection - If true, blocks introspection queries (__schema, __type) for security (default: false)
|
|
2149
|
+
* @returns Wrapped action function ready to be exported as Adobe I/O Runtime entrypoint
|
|
2150
|
+
*
|
|
2151
|
+
* @example Complete GraphQL API with authentication
|
|
2152
|
+
* ```typescript
|
|
2153
|
+
* const schema = `
|
|
2154
|
+
* type Query {
|
|
2155
|
+
* me: User
|
|
2156
|
+
* orders: [Order]
|
|
2157
|
+
* }
|
|
2158
|
+
*
|
|
2159
|
+
* type User {
|
|
2160
|
+
* id: ID!
|
|
2161
|
+
* email: String!
|
|
2162
|
+
* name: String!
|
|
2163
|
+
* }
|
|
2164
|
+
*
|
|
2165
|
+
* type Order {
|
|
2166
|
+
* id: ID!
|
|
2167
|
+
* total: Float!
|
|
2168
|
+
* items: [OrderItem]
|
|
2169
|
+
* }
|
|
2170
|
+
*
|
|
2171
|
+
* type OrderItem {
|
|
2172
|
+
* productId: ID!
|
|
2173
|
+
* quantity: Int!
|
|
2174
|
+
* price: Float!
|
|
2175
|
+
* }
|
|
2176
|
+
* `;
|
|
2177
|
+
*
|
|
2178
|
+
* const resolvers = async ({ logger, headers, params, telemetry }) => {
|
|
2179
|
+
* // Access authentication from headers
|
|
2180
|
+
* const userId = headers['x-user-id'];
|
|
2181
|
+
*
|
|
2182
|
+
* return {
|
|
2183
|
+
* me: async () => {
|
|
2184
|
+
* logger.info({ message: 'Fetching current user', userId });
|
|
2185
|
+
* const span = telemetry.getCurrentSpan();
|
|
2186
|
+
* if (span) {
|
|
2187
|
+
* span.setAttribute('user.id', userId);
|
|
2188
|
+
* }
|
|
2189
|
+
* return getUserById(userId);
|
|
2190
|
+
* },
|
|
2191
|
+
* orders: async () => {
|
|
2192
|
+
* logger.info({ message: 'Fetching user orders', userId });
|
|
2193
|
+
* return getOrdersByUserId(userId);
|
|
2194
|
+
* }
|
|
2195
|
+
* };
|
|
2196
|
+
* };
|
|
2197
|
+
*
|
|
2198
|
+
* // Disable introspection in production for security
|
|
2199
|
+
* const handler = GraphQlAction.execute(
|
|
2200
|
+
* schema,
|
|
2201
|
+
* resolvers,
|
|
2202
|
+
* 'customer-api',
|
|
2203
|
+
* process.env.NODE_ENV === 'production'
|
|
2204
|
+
* );
|
|
2205
|
+
*
|
|
2206
|
+
* export const main = handler;
|
|
2207
|
+
* ```
|
|
2208
|
+
*
|
|
2209
|
+
* @example GraphQL action with variables and operation names
|
|
2210
|
+
* ```typescript
|
|
2211
|
+
* // Client sends:
|
|
2212
|
+
* // POST /api/graphql
|
|
2213
|
+
* // {
|
|
2214
|
+
* // "query": "query GetUser($id: ID!) { user(id: $id) { name email } }",
|
|
2215
|
+
* // "variables": { "id": "123" },
|
|
2216
|
+
* // "operationName": "GetUser"
|
|
2217
|
+
* // }
|
|
2218
|
+
*
|
|
2219
|
+
* const schema = `
|
|
2220
|
+
* type Query {
|
|
2221
|
+
* user(id: ID!): User
|
|
2222
|
+
* }
|
|
2223
|
+
* type User {
|
|
2224
|
+
* id: ID!
|
|
2225
|
+
* name: String!
|
|
2226
|
+
* email: String!
|
|
2227
|
+
* }
|
|
2228
|
+
* `;
|
|
2229
|
+
*
|
|
2230
|
+
* const resolvers = async ({ logger, headers, params, telemetry }) => ({
|
|
2231
|
+
* user: async ({ id }) => {
|
|
2232
|
+
* logger.info({ message: 'Query executed', operationName: 'GetUser', userId: id });
|
|
2233
|
+
* return { id, name: 'John Doe', email: 'john@example.com' };
|
|
2234
|
+
* }
|
|
2235
|
+
* });
|
|
2236
|
+
*
|
|
2237
|
+
* const handler = GraphQlAction.execute(schema, resolvers, 'graphql-api');
|
|
2238
|
+
* export const main = handler;
|
|
2239
|
+
* ```
|
|
2240
|
+
*/
|
|
1799
2241
|
static execute(schema = `
|
|
1800
2242
|
type Query {
|
|
1801
2243
|
hello: String
|
|
@@ -1805,15 +2247,11 @@ var _GraphQlAction = class _GraphQlAction {
|
|
|
1805
2247
|
hello: /* @__PURE__ */ __name(() => "Hello World!", "hello")
|
|
1806
2248
|
};
|
|
1807
2249
|
}, name = "main", disableIntrospection = false) {
|
|
1808
|
-
runtime_action_default.setActionType("graphql-action");
|
|
1809
|
-
runtime_action_default.setActionTypeName("GraphQL action");
|
|
1810
2250
|
const callback = /* @__PURE__ */ __name(async (params, ctx) => {
|
|
1811
|
-
const { logger } = ctx;
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
} catch (error) {
|
|
1816
|
-
return response_default.error(400 /* BAD_REQUEST */, error.message);
|
|
2251
|
+
const { logger, telemetry } = ctx;
|
|
2252
|
+
const { schema: graphqlSchema, error: schemaError } = _GraphQlAction.buildSchemaWithInstrumentation(name, schema, telemetry);
|
|
2253
|
+
if (schemaError) {
|
|
2254
|
+
return response_default.error(400 /* BAD_REQUEST */, schemaError);
|
|
1817
2255
|
}
|
|
1818
2256
|
const graphqlResolvers = await resolvers({
|
|
1819
2257
|
...ctx,
|
|
@@ -1823,11 +2261,9 @@ var _GraphQlAction = class _GraphQlAction {
|
|
|
1823
2261
|
});
|
|
1824
2262
|
const context2 = {};
|
|
1825
2263
|
const query = params.query;
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
} catch (error) {
|
|
1830
|
-
return response_default.error(400 /* BAD_REQUEST */, error.message);
|
|
2264
|
+
const { parsed: parsedQuery, error: parseError } = _GraphQlAction.parseQueryWithInstrumentation(name, query, telemetry);
|
|
2265
|
+
if (parseError) {
|
|
2266
|
+
return response_default.error(400 /* BAD_REQUEST */, parseError);
|
|
1831
2267
|
}
|
|
1832
2268
|
logger.debug(
|
|
1833
2269
|
JSON.stringify({
|
|
@@ -1835,7 +2271,12 @@ var _GraphQlAction = class _GraphQlAction {
|
|
|
1835
2271
|
query: parsedQuery
|
|
1836
2272
|
})
|
|
1837
2273
|
);
|
|
1838
|
-
const validationErrors =
|
|
2274
|
+
const validationErrors = _GraphQlAction.validateQueryWithInstrumentation(
|
|
2275
|
+
name,
|
|
2276
|
+
graphqlSchema,
|
|
2277
|
+
parsedQuery,
|
|
2278
|
+
telemetry
|
|
2279
|
+
);
|
|
1839
2280
|
if (validationErrors.length) {
|
|
1840
2281
|
logger.error({
|
|
1841
2282
|
message: "GraphQL action query validation failed",
|
|
@@ -1850,10 +2291,10 @@ var _GraphQlAction = class _GraphQlAction {
|
|
|
1850
2291
|
logger.debug({
|
|
1851
2292
|
message: "GraphQL action introspection check disabled"
|
|
1852
2293
|
});
|
|
1853
|
-
const isIntrospectionQuery =
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
2294
|
+
const isIntrospectionQuery = _GraphQlAction.checkIntrospectionWithInstrumentation(
|
|
2295
|
+
name,
|
|
2296
|
+
parsedQuery,
|
|
2297
|
+
telemetry
|
|
1857
2298
|
);
|
|
1858
2299
|
if (isIntrospectionQuery) {
|
|
1859
2300
|
logger.error({
|
|
@@ -1872,24 +2313,240 @@ var _GraphQlAction = class _GraphQlAction {
|
|
|
1872
2313
|
variables
|
|
1873
2314
|
});
|
|
1874
2315
|
return response_default.success(
|
|
1875
|
-
await
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
2316
|
+
await _GraphQlAction.executeGraphQLWithInstrumentation(
|
|
2317
|
+
name,
|
|
2318
|
+
{
|
|
2319
|
+
schema: graphqlSchema,
|
|
2320
|
+
source: query,
|
|
2321
|
+
rootValue: graphqlResolvers,
|
|
2322
|
+
contextValue: context2,
|
|
2323
|
+
variableValues: variables,
|
|
2324
|
+
operationName: params.operationName
|
|
2325
|
+
},
|
|
2326
|
+
telemetry
|
|
2327
|
+
)
|
|
1883
2328
|
);
|
|
1884
2329
|
}, "callback");
|
|
1885
|
-
|
|
2330
|
+
runtime_action_default.setActionType("graphql-action");
|
|
2331
|
+
runtime_action_default.setActionTypeName("GraphQL action");
|
|
2332
|
+
return runtime_action_default.execute(
|
|
1886
2333
|
`graphql-${name}`,
|
|
1887
2334
|
["GET" /* GET */, "POST" /* POST */],
|
|
1888
2335
|
["query"],
|
|
1889
2336
|
[],
|
|
1890
2337
|
callback
|
|
1891
2338
|
);
|
|
1892
|
-
|
|
2339
|
+
}
|
|
2340
|
+
/**
|
|
2341
|
+
* Builds GraphQL schema with OpenTelemetry instrumentation
|
|
2342
|
+
*
|
|
2343
|
+
* This method wraps the schema building logic with distributed tracing instrumentation,
|
|
2344
|
+
* creating a span that tracks schema metrics and results in New Relic.
|
|
2345
|
+
*
|
|
2346
|
+
* @param name - Action name used for span naming
|
|
2347
|
+
* @param schemaString - GraphQL schema definition string
|
|
2348
|
+
* @param params - Request parameters for telemetry context
|
|
2349
|
+
* @returns Object with built schema or error message
|
|
2350
|
+
*
|
|
2351
|
+
* @private
|
|
2352
|
+
*/
|
|
2353
|
+
static buildSchemaWithInstrumentation(name, schemaString, telemetry) {
|
|
2354
|
+
const instrumentedBuildSchema = telemetry.instrument(
|
|
2355
|
+
`graphql.action.${name}.build-schema`,
|
|
2356
|
+
(actionName, actionSchemaString, actionTelemetry) => {
|
|
2357
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2358
|
+
if (span) {
|
|
2359
|
+
span.setAttribute("graphql.schema.length", actionSchemaString.length);
|
|
2360
|
+
span.addEvent("schema-building-started");
|
|
2361
|
+
}
|
|
2362
|
+
try {
|
|
2363
|
+
const builtSchema = buildSchema(actionSchemaString);
|
|
2364
|
+
if (span) {
|
|
2365
|
+
span.setAttribute("graphql.schema.built", true);
|
|
2366
|
+
span.addEvent("schema-building-completed");
|
|
2367
|
+
}
|
|
2368
|
+
return { schema: builtSchema, error: null };
|
|
2369
|
+
} catch (error) {
|
|
2370
|
+
if (span) {
|
|
2371
|
+
span.setAttribute("graphql.schema.built", false);
|
|
2372
|
+
span.setAttribute("graphql.schema.error", error.message);
|
|
2373
|
+
}
|
|
2374
|
+
return { schema: null, error: error.message };
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
);
|
|
2378
|
+
return instrumentedBuildSchema(name, schemaString, telemetry);
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* Parses GraphQL query with OpenTelemetry instrumentation
|
|
2382
|
+
*
|
|
2383
|
+
* This method wraps the query parsing logic with distributed tracing instrumentation,
|
|
2384
|
+
* creating a span that tracks parsing metrics and results in New Relic.
|
|
2385
|
+
*
|
|
2386
|
+
* @param name - Action name used for span naming
|
|
2387
|
+
* @param queryString - GraphQL query string to parse
|
|
2388
|
+
* @param params - Request parameters for telemetry context
|
|
2389
|
+
* @returns Object with parsed query document or error message
|
|
2390
|
+
*
|
|
2391
|
+
* @private
|
|
2392
|
+
*/
|
|
2393
|
+
static parseQueryWithInstrumentation(name, queryString, telemetry) {
|
|
2394
|
+
const instrumentedParseQuery = telemetry.instrument(
|
|
2395
|
+
`graphql.action.${name}.parse-query`,
|
|
2396
|
+
(actionName, actionQueryString, actionTelemetry) => {
|
|
2397
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2398
|
+
if (span) {
|
|
2399
|
+
span.setAttribute("graphql.query.length", actionQueryString.length);
|
|
2400
|
+
span.addEvent("query-parsing-started");
|
|
2401
|
+
}
|
|
2402
|
+
try {
|
|
2403
|
+
const parsed = parse(actionQueryString);
|
|
2404
|
+
if (span) {
|
|
2405
|
+
span.setAttribute("graphql.query.parsed", true);
|
|
2406
|
+
span.setAttribute("graphql.query.definitions_count", parsed.definitions.length);
|
|
2407
|
+
span.addEvent("query-parsing-completed");
|
|
2408
|
+
}
|
|
2409
|
+
return { parsed, error: null };
|
|
2410
|
+
} catch (error) {
|
|
2411
|
+
if (span) {
|
|
2412
|
+
span.setAttribute("graphql.query.parsed", false);
|
|
2413
|
+
span.setAttribute("graphql.query.error", error.message);
|
|
2414
|
+
}
|
|
2415
|
+
return { parsed: null, error: error.message };
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
);
|
|
2419
|
+
return instrumentedParseQuery(name, queryString, telemetry);
|
|
2420
|
+
}
|
|
2421
|
+
/**
|
|
2422
|
+
* Validates GraphQL query with OpenTelemetry instrumentation
|
|
2423
|
+
*
|
|
2424
|
+
* This method wraps the query validation logic with distributed tracing instrumentation,
|
|
2425
|
+
* creating a span that tracks validation metrics and results in New Relic.
|
|
2426
|
+
*
|
|
2427
|
+
* @param name - Action name used for span naming
|
|
2428
|
+
* @param schema - GraphQL schema to validate against
|
|
2429
|
+
* @param parsedQuery - Parsed GraphQL query document
|
|
2430
|
+
* @param params - Request parameters for telemetry context
|
|
2431
|
+
* @returns Array of validation errors (empty if valid)
|
|
2432
|
+
*
|
|
2433
|
+
* @private
|
|
2434
|
+
*/
|
|
2435
|
+
static validateQueryWithInstrumentation(name, schema, parsedQuery, telemetry) {
|
|
2436
|
+
const instrumentedValidateQuery = telemetry.instrument(
|
|
2437
|
+
`graphql.action.${name}.validate-query`,
|
|
2438
|
+
(actionName, actionSchema, actionParsedQuery, actionTelemetry) => {
|
|
2439
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2440
|
+
if (span) {
|
|
2441
|
+
span.addEvent("query-validation-started");
|
|
2442
|
+
}
|
|
2443
|
+
const errors = validate(actionSchema, actionParsedQuery);
|
|
2444
|
+
if (span) {
|
|
2445
|
+
span.setAttribute("graphql.query.valid", errors.length === 0);
|
|
2446
|
+
span.setAttribute("graphql.query.validation_errors_count", errors.length);
|
|
2447
|
+
if (errors.length > 0) {
|
|
2448
|
+
span.setAttribute(
|
|
2449
|
+
"graphql.query.validation_errors",
|
|
2450
|
+
errors.map((e) => e.message).join("; ")
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
span.addEvent("query-validation-completed", {
|
|
2454
|
+
valid: errors.length === 0,
|
|
2455
|
+
errorCount: errors.length
|
|
2456
|
+
});
|
|
2457
|
+
}
|
|
2458
|
+
return errors;
|
|
2459
|
+
}
|
|
2460
|
+
);
|
|
2461
|
+
return instrumentedValidateQuery(name, schema, parsedQuery, telemetry);
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Checks for introspection query with OpenTelemetry instrumentation
|
|
2465
|
+
*
|
|
2466
|
+
* This method wraps the introspection detection logic with distributed tracing instrumentation,
|
|
2467
|
+
* creating a span that tracks whether an introspection query was detected in New Relic.
|
|
2468
|
+
*
|
|
2469
|
+
* @param name - Action name used for span naming
|
|
2470
|
+
* @param parsedQuery - Parsed GraphQL query document
|
|
2471
|
+
* @param params - Request parameters for telemetry context
|
|
2472
|
+
* @returns True if introspection query detected, false otherwise
|
|
2473
|
+
*
|
|
2474
|
+
* @private
|
|
2475
|
+
*/
|
|
2476
|
+
static checkIntrospectionWithInstrumentation(name, parsedQuery, telemetry) {
|
|
2477
|
+
const instrumentedIntrospectionCheck = telemetry.instrument(
|
|
2478
|
+
`graphql.action.${name}.introspection-check`,
|
|
2479
|
+
(actionName, actionParsedQuery, actionTelemetry) => {
|
|
2480
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2481
|
+
if (span) {
|
|
2482
|
+
span.setAttribute("graphql.introspection.disabled", true);
|
|
2483
|
+
span.addEvent("introspection-check-started");
|
|
2484
|
+
}
|
|
2485
|
+
const isIntrospection = actionParsedQuery.definitions.some(
|
|
2486
|
+
(definition) => definition.selectionSet.selections.some(
|
|
2487
|
+
(selection) => selection.name.value.startsWith("__")
|
|
2488
|
+
)
|
|
2489
|
+
);
|
|
2490
|
+
if (span) {
|
|
2491
|
+
span.setAttribute("graphql.introspection.detected", isIntrospection);
|
|
2492
|
+
span.addEvent("introspection-check-completed", {
|
|
2493
|
+
detected: isIntrospection
|
|
2494
|
+
});
|
|
2495
|
+
}
|
|
2496
|
+
return isIntrospection;
|
|
2497
|
+
}
|
|
2498
|
+
);
|
|
2499
|
+
return instrumentedIntrospectionCheck(name, parsedQuery, telemetry);
|
|
2500
|
+
}
|
|
2501
|
+
/**
|
|
2502
|
+
* Executes GraphQL query with OpenTelemetry instrumentation
|
|
2503
|
+
*
|
|
2504
|
+
* This method wraps the GraphQL execution with distributed tracing instrumentation,
|
|
2505
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
2506
|
+
*
|
|
2507
|
+
* @param name - Action name used for span naming
|
|
2508
|
+
* @param executionParams - GraphQL execution parameters (schema, source, rootValue, etc.)
|
|
2509
|
+
* @param params - Request parameters for telemetry context
|
|
2510
|
+
* @returns Promise resolving to the GraphQL execution result
|
|
2511
|
+
*
|
|
2512
|
+
* @private
|
|
2513
|
+
*/
|
|
2514
|
+
static async executeGraphQLWithInstrumentation(name, executionParams, telemetry) {
|
|
2515
|
+
const instrumentedGraphQLExecution = telemetry.instrument(
|
|
2516
|
+
`graphql.action.${name}.execute`,
|
|
2517
|
+
async (actionName, actionExecutionParams, actionTelemetry) => {
|
|
2518
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2519
|
+
if (span) {
|
|
2520
|
+
span.setAttribute(
|
|
2521
|
+
"graphql.execution.operation_name",
|
|
2522
|
+
actionExecutionParams.operationName || "default"
|
|
2523
|
+
);
|
|
2524
|
+
span.setAttribute(
|
|
2525
|
+
"graphql.execution.has_variables",
|
|
2526
|
+
!!actionExecutionParams.variableValues
|
|
2527
|
+
);
|
|
2528
|
+
span.addEvent("graphql-execution-started");
|
|
2529
|
+
}
|
|
2530
|
+
const result = await graphql(actionExecutionParams);
|
|
2531
|
+
if (span) {
|
|
2532
|
+
span.setAttribute("graphql.execution.has_errors", !!result.errors);
|
|
2533
|
+
if (result.errors) {
|
|
2534
|
+
span.setAttribute("graphql.execution.errors_count", result.errors.length);
|
|
2535
|
+
span.setAttribute(
|
|
2536
|
+
"graphql.execution.errors",
|
|
2537
|
+
result.errors.map((e) => e.message).join("; ")
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
span.setAttribute("graphql.execution.has_data", !!result.data);
|
|
2541
|
+
span.addEvent("graphql-execution-completed", {
|
|
2542
|
+
hasErrors: !!result.errors,
|
|
2543
|
+
hasData: !!result.data
|
|
2544
|
+
});
|
|
2545
|
+
}
|
|
2546
|
+
return result;
|
|
2547
|
+
}
|
|
2548
|
+
);
|
|
2549
|
+
return instrumentedGraphQLExecution(name, executionParams, telemetry);
|
|
1893
2550
|
}
|
|
1894
2551
|
};
|
|
1895
2552
|
__name(_GraphQlAction, "GraphQlAction");
|
|
@@ -1954,9 +2611,15 @@ var _OpenwhiskAction = class _OpenwhiskAction {
|
|
|
1954
2611
|
* ```typescript
|
|
1955
2612
|
* const handler = OpenwhiskAction.execute(
|
|
1956
2613
|
* 'processWebhook',
|
|
1957
|
-
* async (params, { logger, headers }) => {
|
|
2614
|
+
* async (params, { logger, headers, telemetry }) => {
|
|
1958
2615
|
* logger.info({ message: 'Webhook received', event: params.event });
|
|
1959
2616
|
*
|
|
2617
|
+
* // Access current span for custom instrumentation
|
|
2618
|
+
* const span = telemetry.getCurrentSpan();
|
|
2619
|
+
* if (span) {
|
|
2620
|
+
* span.setAttribute('webhook.event', params.event);
|
|
2621
|
+
* }
|
|
2622
|
+
*
|
|
1960
2623
|
* // Your webhook processing logic here
|
|
1961
2624
|
* const result = await processWebhookData(params);
|
|
1962
2625
|
*
|
|
@@ -1971,9 +2634,10 @@ var _OpenwhiskAction = class _OpenwhiskAction {
|
|
|
1971
2634
|
static execute(name = "main", action = async (_params) => {
|
|
1972
2635
|
return { statusCode: 200 /* OK */, body: {} };
|
|
1973
2636
|
}) {
|
|
2637
|
+
const telemetry = new telemetry_default();
|
|
1974
2638
|
const openwhiskAction = /* @__PURE__ */ __name(async (params) => {
|
|
1975
2639
|
params.action_type = "openwhisk-action";
|
|
1976
|
-
const logger =
|
|
2640
|
+
const logger = telemetry.createLogger(name);
|
|
1977
2641
|
try {
|
|
1978
2642
|
logger.debug({
|
|
1979
2643
|
message: "OpenWhisk action execution started"
|
|
@@ -1982,7 +2646,14 @@ var _OpenwhiskAction = class _OpenwhiskAction {
|
|
|
1982
2646
|
message: "OpenWhisk action parameters received",
|
|
1983
2647
|
params
|
|
1984
2648
|
});
|
|
1985
|
-
const result = await
|
|
2649
|
+
const result = await _OpenwhiskAction.executeActionWithInstrumentation(
|
|
2650
|
+
name,
|
|
2651
|
+
params.__ow_headers || {},
|
|
2652
|
+
params,
|
|
2653
|
+
action,
|
|
2654
|
+
telemetry,
|
|
2655
|
+
logger
|
|
2656
|
+
);
|
|
1986
2657
|
logger.debug({
|
|
1987
2658
|
message: "OpenWhisk action execution completed",
|
|
1988
2659
|
result
|
|
@@ -2004,7 +2675,51 @@ var _OpenwhiskAction = class _OpenwhiskAction {
|
|
|
2004
2675
|
return response_default.error(500 /* INTERNAL_ERROR */, "server error");
|
|
2005
2676
|
}
|
|
2006
2677
|
}, "openwhiskAction");
|
|
2007
|
-
return
|
|
2678
|
+
return telemetry.initialize(openwhiskAction);
|
|
2679
|
+
}
|
|
2680
|
+
/**
|
|
2681
|
+
* Executes OpenWhisk action with OpenTelemetry instrumentation
|
|
2682
|
+
*
|
|
2683
|
+
* This method wraps the action execution with distributed tracing instrumentation,
|
|
2684
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
2685
|
+
*
|
|
2686
|
+
* @param name - Action name used for span naming
|
|
2687
|
+
* @param headers - Request headers
|
|
2688
|
+
* @param params - Request parameters
|
|
2689
|
+
* @param action - The user's action function to execute
|
|
2690
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
2691
|
+
* @param logger - Logger instance
|
|
2692
|
+
* @returns Promise resolving to the action result
|
|
2693
|
+
*
|
|
2694
|
+
* @private
|
|
2695
|
+
*/
|
|
2696
|
+
static async executeActionWithInstrumentation(name, headers, params, action, telemetry, logger) {
|
|
2697
|
+
const instrumentedAction = telemetry.instrument(
|
|
2698
|
+
`openwhisk.action.${name}.execute`,
|
|
2699
|
+
async (actionName, actionHeaders, actionParams, actionFn, actionTelemetry, actionLogger) => {
|
|
2700
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
2701
|
+
if (span) {
|
|
2702
|
+
span.setAttribute("openwhisk.action.name", actionName);
|
|
2703
|
+
span.setAttribute("openwhisk.action.has_headers", !!actionHeaders);
|
|
2704
|
+
span.addEvent("openwhisk-execution-started");
|
|
2705
|
+
}
|
|
2706
|
+
const result = await actionFn(actionParams, {
|
|
2707
|
+
logger: actionLogger,
|
|
2708
|
+
headers: actionHeaders,
|
|
2709
|
+
telemetry: actionTelemetry
|
|
2710
|
+
});
|
|
2711
|
+
if (span) {
|
|
2712
|
+
const statusCode = "statusCode" in result ? result.statusCode : result.error.statusCode;
|
|
2713
|
+
span.setAttribute("openwhisk.action.response_status", statusCode);
|
|
2714
|
+
span.setAttribute("openwhisk.action.has_error", "error" in result);
|
|
2715
|
+
span.addEvent("openwhisk-execution-completed", {
|
|
2716
|
+
statusCode
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
return result;
|
|
2720
|
+
}
|
|
2721
|
+
);
|
|
2722
|
+
return instrumentedAction(name, headers, params, action, telemetry, logger);
|
|
2008
2723
|
}
|
|
2009
2724
|
};
|
|
2010
2725
|
__name(_OpenwhiskAction, "OpenwhiskAction");
|
|
@@ -2528,41 +3243,17 @@ var _WebhookAction = class _WebhookAction {
|
|
|
2528
3243
|
*/
|
|
2529
3244
|
static execute(name = "webhook", requiredParams = [], requiredHeaders = [], signatureVerification = "disabled" /* DISABLED */, action = async () => response_default2.success()) {
|
|
2530
3245
|
const httpMethods = ["POST" /* POST */];
|
|
2531
|
-
const verifySignature = /* @__PURE__ */ __name(async (params) => {
|
|
2532
|
-
const signature = params.__ow_headers["x-adobe-commerce-webhook-signature"] || "";
|
|
2533
|
-
if (!signature) {
|
|
2534
|
-
return "Header `x-adobe-commerce-webhook-signature` not found. Make sure Webhooks signature is enabled in the Commerce instance.";
|
|
2535
|
-
}
|
|
2536
|
-
const body = params.__ow_body;
|
|
2537
|
-
if (!body) {
|
|
2538
|
-
return "Request body not found. Make sure the action is configured with `raw-http: true`.";
|
|
2539
|
-
}
|
|
2540
|
-
let publicKey = params.PUBLIC_KEY;
|
|
2541
|
-
if (!publicKey && params.PUBLIC_KEY_BASE64) {
|
|
2542
|
-
publicKey = atob(params.PUBLIC_KEY_BASE64);
|
|
2543
|
-
}
|
|
2544
|
-
if (!publicKey) {
|
|
2545
|
-
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.";
|
|
2546
|
-
}
|
|
2547
|
-
try {
|
|
2548
|
-
const verifier = crypto.createVerify("SHA256");
|
|
2549
|
-
verifier.update(body);
|
|
2550
|
-
const isSignatureValid = verifier.verify(publicKey, signature, "base64");
|
|
2551
|
-
if (!isSignatureValid) {
|
|
2552
|
-
return "The signature is invalid.";
|
|
2553
|
-
}
|
|
2554
|
-
} catch (error) {
|
|
2555
|
-
return "The signature is invalid.";
|
|
2556
|
-
}
|
|
2557
|
-
return null;
|
|
2558
|
-
}, "verifySignature");
|
|
2559
3246
|
const callback = /* @__PURE__ */ __name(async (params, ctx) => {
|
|
2560
|
-
const { logger } = ctx;
|
|
3247
|
+
const { logger, telemetry } = ctx;
|
|
2561
3248
|
if (signatureVerification === "enabled" /* ENABLED */) {
|
|
2562
|
-
const verificationErrorMessage = await
|
|
3249
|
+
const verificationErrorMessage = await _WebhookAction.verifySignatureWithInstrumentation(
|
|
3250
|
+
name,
|
|
3251
|
+
params,
|
|
3252
|
+
telemetry
|
|
3253
|
+
);
|
|
2563
3254
|
if (verificationErrorMessage) {
|
|
2564
3255
|
logger.error({
|
|
2565
|
-
message: "Webhook
|
|
3256
|
+
message: "Webhook action signature verification failed",
|
|
2566
3257
|
error: verificationErrorMessage
|
|
2567
3258
|
});
|
|
2568
3259
|
const verificationErrorResponse = response_default2.exception(verificationErrorMessage);
|
|
@@ -2573,7 +3264,13 @@ var _WebhookAction = class _WebhookAction {
|
|
|
2573
3264
|
...JSON.parse(atob(params.__ow_body))
|
|
2574
3265
|
};
|
|
2575
3266
|
}
|
|
2576
|
-
const errorMessage =
|
|
3267
|
+
const errorMessage = _WebhookAction.validateWithInstrumentation(
|
|
3268
|
+
name,
|
|
3269
|
+
params,
|
|
3270
|
+
requiredParams,
|
|
3271
|
+
requiredHeaders,
|
|
3272
|
+
telemetry
|
|
3273
|
+
);
|
|
2577
3274
|
if (errorMessage) {
|
|
2578
3275
|
logger.error({
|
|
2579
3276
|
message: "Webhook action validation failed",
|
|
@@ -2582,13 +3279,183 @@ var _WebhookAction = class _WebhookAction {
|
|
|
2582
3279
|
const errorMessageResponse = response_default2.exception(errorMessage);
|
|
2583
3280
|
return response_default.success(JSON.stringify(errorMessageResponse));
|
|
2584
3281
|
}
|
|
2585
|
-
const response = await
|
|
3282
|
+
const response = await _WebhookAction.executeActionWithInstrumentation(
|
|
3283
|
+
name,
|
|
3284
|
+
action,
|
|
3285
|
+
params,
|
|
3286
|
+
ctx
|
|
3287
|
+
);
|
|
2586
3288
|
return response_default.success(JSON.stringify(response));
|
|
2587
3289
|
}, "callback");
|
|
2588
3290
|
runtime_action_default.setActionType("webhook-action");
|
|
2589
3291
|
runtime_action_default.setActionTypeName("Webhook action");
|
|
2590
3292
|
return runtime_action_default.execute(name, httpMethods, [], [], callback);
|
|
2591
3293
|
}
|
|
3294
|
+
/**
|
|
3295
|
+
* Executes webhook action with OpenTelemetry instrumentation
|
|
3296
|
+
*
|
|
3297
|
+
* This method wraps the webhook action execution with distributed tracing instrumentation,
|
|
3298
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
3299
|
+
*
|
|
3300
|
+
* @param name - Webhook action name used for span naming
|
|
3301
|
+
* @param action - The webhook action function to execute
|
|
3302
|
+
* @param params - Request parameters
|
|
3303
|
+
* @param ctx - Context object with logger, headers, and telemetry
|
|
3304
|
+
* @returns Promise resolving to the webhook action response(s)
|
|
3305
|
+
*
|
|
3306
|
+
* @private
|
|
3307
|
+
*/
|
|
3308
|
+
static async executeActionWithInstrumentation(name, action, params, ctx) {
|
|
3309
|
+
const instrumentedWebhookAction = ctx.telemetry.instrument(
|
|
3310
|
+
`webhook.action.${name}.execute`,
|
|
3311
|
+
async (actionName, actionFn, actionParams, context2) => {
|
|
3312
|
+
const span = context2.telemetry.getCurrentSpan();
|
|
3313
|
+
if (span) {
|
|
3314
|
+
span.setAttribute("webhook.action.name", actionName);
|
|
3315
|
+
span.setAttribute("webhook.action.has_headers", !!context2.headers);
|
|
3316
|
+
span.addEvent("webhook-execution-started");
|
|
3317
|
+
}
|
|
3318
|
+
const response = await actionFn(actionParams, context2);
|
|
3319
|
+
if (span) {
|
|
3320
|
+
span.setAttribute("webhook.action.response_is_array", Array.isArray(response));
|
|
3321
|
+
if (Array.isArray(response)) {
|
|
3322
|
+
span.setAttribute("webhook.action.response_count", response.length);
|
|
3323
|
+
}
|
|
3324
|
+
span.addEvent("webhook-execution-completed", {
|
|
3325
|
+
isArray: Array.isArray(response),
|
|
3326
|
+
count: Array.isArray(response) ? response.length : 1
|
|
3327
|
+
});
|
|
3328
|
+
}
|
|
3329
|
+
return response;
|
|
3330
|
+
}
|
|
3331
|
+
);
|
|
3332
|
+
return instrumentedWebhookAction(name, action, params, ctx);
|
|
3333
|
+
}
|
|
3334
|
+
/**
|
|
3335
|
+
* Verifies webhook signature with OpenTelemetry instrumentation
|
|
3336
|
+
*
|
|
3337
|
+
* This method wraps the signature verification logic with distributed tracing instrumentation,
|
|
3338
|
+
* creating a span that tracks verification metrics and results in New Relic.
|
|
3339
|
+
*
|
|
3340
|
+
* @param name - Webhook action name used for span naming
|
|
3341
|
+
* @param params - Request parameters including headers, body, and public key
|
|
3342
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
3343
|
+
* @returns Error message if verification fails, null if successful
|
|
3344
|
+
*
|
|
3345
|
+
* @private
|
|
3346
|
+
*/
|
|
3347
|
+
static async verifySignatureWithInstrumentation(name, params, telemetry) {
|
|
3348
|
+
const instrumentedVerifySignature = telemetry.instrument(
|
|
3349
|
+
`webhook.action.${name}.verify-signature`,
|
|
3350
|
+
async (actionName, actionParams, actionTelemetry) => {
|
|
3351
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
3352
|
+
if (span) {
|
|
3353
|
+
span.setAttribute("webhook.signature.enabled", true);
|
|
3354
|
+
span.setAttribute(
|
|
3355
|
+
"webhook.signature.header_present",
|
|
3356
|
+
!!actionParams.__ow_headers?.["x-adobe-commerce-webhook-signature"]
|
|
3357
|
+
);
|
|
3358
|
+
span.setAttribute("webhook.signature.has_body", !!actionParams.__ow_body);
|
|
3359
|
+
span.setAttribute(
|
|
3360
|
+
"webhook.signature.has_public_key",
|
|
3361
|
+
!!(actionParams.PUBLIC_KEY || actionParams.PUBLIC_KEY_BASE64)
|
|
3362
|
+
);
|
|
3363
|
+
span.addEvent("signature-verification-started");
|
|
3364
|
+
}
|
|
3365
|
+
const verificationError = await _WebhookAction.verifySignature(actionParams);
|
|
3366
|
+
if (span) {
|
|
3367
|
+
span.setAttribute("webhook.signature.valid", verificationError === null);
|
|
3368
|
+
if (verificationError) {
|
|
3369
|
+
span.setAttribute("webhook.signature.error", verificationError);
|
|
3370
|
+
}
|
|
3371
|
+
span.addEvent("signature-verification-completed", {
|
|
3372
|
+
valid: verificationError === null
|
|
3373
|
+
});
|
|
3374
|
+
}
|
|
3375
|
+
return verificationError;
|
|
3376
|
+
}
|
|
3377
|
+
);
|
|
3378
|
+
return instrumentedVerifySignature(name, params, telemetry);
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3381
|
+
* Validates webhook parameters and headers with OpenTelemetry instrumentation
|
|
3382
|
+
*
|
|
3383
|
+
* This method wraps the validation logic with distributed tracing instrumentation,
|
|
3384
|
+
* creating a span that tracks validation metrics and results in New Relic.
|
|
3385
|
+
*
|
|
3386
|
+
* @param name - Webhook action name used for span naming
|
|
3387
|
+
* @param params - Request parameters
|
|
3388
|
+
* @param requiredParams - List of required parameter names to validate
|
|
3389
|
+
* @param requiredHeaders - List of required header names to validate
|
|
3390
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
3391
|
+
* @returns Error message if validation fails, empty string if successful
|
|
3392
|
+
*
|
|
3393
|
+
* @private
|
|
3394
|
+
*/
|
|
3395
|
+
static validateWithInstrumentation(name, params, requiredParams, requiredHeaders, telemetry) {
|
|
3396
|
+
const instrumentedValidate = telemetry.instrument(
|
|
3397
|
+
`webhook.action.${name}.validate`,
|
|
3398
|
+
(actionName, actionParams, actionRequiredParams, actionRequiredHeaders, actionTelemetry) => {
|
|
3399
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
3400
|
+
if (span) {
|
|
3401
|
+
span.setAttribute("webhook.validation.required_params", actionRequiredParams.join(","));
|
|
3402
|
+
span.setAttribute("webhook.validation.required_headers", actionRequiredHeaders.join(","));
|
|
3403
|
+
}
|
|
3404
|
+
const errorMessage = validator_default.checkMissingRequestInputs(
|
|
3405
|
+
actionParams,
|
|
3406
|
+
actionRequiredParams,
|
|
3407
|
+
actionRequiredHeaders
|
|
3408
|
+
) ?? "";
|
|
3409
|
+
if (span) {
|
|
3410
|
+
span.setAttribute("webhook.validation.passed", errorMessage === "");
|
|
3411
|
+
if (errorMessage) {
|
|
3412
|
+
span.setAttribute("webhook.validation.error", errorMessage);
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
return errorMessage;
|
|
3416
|
+
}
|
|
3417
|
+
);
|
|
3418
|
+
return instrumentedValidate(name, params, requiredParams, requiredHeaders, telemetry);
|
|
3419
|
+
}
|
|
3420
|
+
/**
|
|
3421
|
+
* Verify webhook signature using a public key and SHA256
|
|
3422
|
+
*
|
|
3423
|
+
* This method validates the authenticity of webhook requests by verifying
|
|
3424
|
+
* the signature against the request body using the provided public key.
|
|
3425
|
+
*
|
|
3426
|
+
* @param params - Request parameters including headers, body, and public key
|
|
3427
|
+
* @returns Error message if verification fails, null if successful
|
|
3428
|
+
*
|
|
3429
|
+
* @private
|
|
3430
|
+
*/
|
|
3431
|
+
static async verifySignature(params) {
|
|
3432
|
+
const signature = params.__ow_headers["x-adobe-commerce-webhook-signature"] || "";
|
|
3433
|
+
if (!signature) {
|
|
3434
|
+
return "Header `x-adobe-commerce-webhook-signature` not found. Make sure Webhooks signature is enabled in the Commerce instance.";
|
|
3435
|
+
}
|
|
3436
|
+
const body = params.__ow_body;
|
|
3437
|
+
if (!body) {
|
|
3438
|
+
return "Request body not found. Make sure the action is configured with `raw-http: true`.";
|
|
3439
|
+
}
|
|
3440
|
+
let publicKey = params.PUBLIC_KEY;
|
|
3441
|
+
if (!publicKey && params.PUBLIC_KEY_BASE64) {
|
|
3442
|
+
publicKey = atob(params.PUBLIC_KEY_BASE64);
|
|
3443
|
+
}
|
|
3444
|
+
if (!publicKey) {
|
|
3445
|
+
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.";
|
|
3446
|
+
}
|
|
3447
|
+
try {
|
|
3448
|
+
const verifier = crypto.createVerify("SHA256");
|
|
3449
|
+
verifier.update(body);
|
|
3450
|
+
const isSignatureValid = verifier.verify(publicKey, signature, "base64");
|
|
3451
|
+
if (!isSignatureValid) {
|
|
3452
|
+
return "The signature is invalid.";
|
|
3453
|
+
}
|
|
3454
|
+
} catch (error) {
|
|
3455
|
+
return "The signature is invalid.";
|
|
3456
|
+
}
|
|
3457
|
+
return null;
|
|
3458
|
+
}
|
|
2592
3459
|
};
|
|
2593
3460
|
__name(_WebhookAction, "WebhookAction");
|
|
2594
3461
|
var WebhookAction = _WebhookAction;
|