@adobe-commerce/aio-toolkit 1.2.0 → 1.2.1
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 +49 -0
- package/README.md +172 -8
- package/dist/index.d.mts +89 -62
- package/dist/index.d.ts +89 -62
- package/dist/index.js +1700 -1464
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1699 -1464
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2326,1780 +2326,2014 @@ __name(_FileRepository, "FileRepository");
|
|
|
2326
2326
|
var FileRepository = _FileRepository;
|
|
2327
2327
|
var file_repository_default = FileRepository;
|
|
2328
2328
|
|
|
2329
|
-
// src/framework/
|
|
2330
|
-
import {
|
|
2331
|
-
import { CloudEvent } from "cloudevents";
|
|
2332
|
-
import { v4 as uuidv4 } from "uuid";
|
|
2329
|
+
// src/framework/abdb/collection/index.ts
|
|
2330
|
+
import { init as initDb, DbError } from "@adobe/aio-lib-db";
|
|
2333
2331
|
|
|
2334
|
-
// src/framework/
|
|
2335
|
-
var
|
|
2332
|
+
// src/framework/abdb/column/types.ts
|
|
2333
|
+
var AbdbColumnType = /* @__PURE__ */ ((AbdbColumnType2) => {
|
|
2334
|
+
AbdbColumnType2["STRING"] = "STRING";
|
|
2335
|
+
AbdbColumnType2["NUMBER"] = "NUMBER";
|
|
2336
|
+
AbdbColumnType2["BOOLEAN"] = "BOOLEAN";
|
|
2337
|
+
AbdbColumnType2["DATETIME"] = "DATETIME";
|
|
2338
|
+
return AbdbColumnType2;
|
|
2339
|
+
})(AbdbColumnType || {});
|
|
2340
|
+
|
|
2341
|
+
// src/framework/abdb/column/index.ts
|
|
2342
|
+
var ISO_8601_DATETIME = /^\d{4}-\d{2}-\d{2}([Tt ]\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}(:?\d{2})?)?)?$/;
|
|
2343
|
+
var _AbdbColumn = class _AbdbColumn {
|
|
2336
2344
|
/**
|
|
2337
|
-
* @param
|
|
2345
|
+
* @param options - Field definition
|
|
2346
|
+
* @throws {Error} When `name` is missing or blank, or `type` is missing / not a member of {@link AbdbColumnType}
|
|
2338
2347
|
*/
|
|
2339
|
-
constructor(
|
|
2340
|
-
|
|
2348
|
+
constructor(options) {
|
|
2349
|
+
if (!options?.name?.trim()) {
|
|
2350
|
+
throw new Error('AbdbColumn: "name" is required and cannot be empty');
|
|
2351
|
+
}
|
|
2352
|
+
if (!options.type || !Object.values(AbdbColumnType).includes(options.type)) {
|
|
2353
|
+
throw new Error('AbdbColumn: "type" is required');
|
|
2354
|
+
}
|
|
2355
|
+
this._name = options.name.trim();
|
|
2356
|
+
this._type = options.type;
|
|
2357
|
+
this._description = void 0;
|
|
2358
|
+
this._isRequired = options.isRequired ?? false;
|
|
2359
|
+
if (options.description !== void 0) {
|
|
2360
|
+
const trimmed = options.description.trim();
|
|
2361
|
+
if (trimmed.length > 0) {
|
|
2362
|
+
this._description = trimmed;
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
Object.freeze(this);
|
|
2341
2366
|
}
|
|
2342
2367
|
/**
|
|
2343
|
-
*
|
|
2344
|
-
* @param message - Debug message to log
|
|
2345
|
-
* @param args - Additional arguments to pass to logger
|
|
2368
|
+
* Returns the trimmed field name.
|
|
2346
2369
|
*/
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
this.logger.debug(message, ...args);
|
|
2350
|
-
}
|
|
2370
|
+
getName() {
|
|
2371
|
+
return this._name;
|
|
2351
2372
|
}
|
|
2352
2373
|
/**
|
|
2353
|
-
*
|
|
2354
|
-
* @param message - Info message to log
|
|
2355
|
-
* @param args - Additional arguments to pass to logger
|
|
2374
|
+
* Returns the expected value type for this column.
|
|
2356
2375
|
*/
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
this.logger.info(message, ...args);
|
|
2360
|
-
}
|
|
2376
|
+
getType() {
|
|
2377
|
+
return this._type;
|
|
2361
2378
|
}
|
|
2362
2379
|
/**
|
|
2363
|
-
*
|
|
2364
|
-
*
|
|
2365
|
-
* @
|
|
2380
|
+
* Returns the optional description, if one was provided and non-whitespace.
|
|
2381
|
+
*
|
|
2382
|
+
* @returns Description string, or `undefined` when not set
|
|
2366
2383
|
*/
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
this.logger.error(message, ...args);
|
|
2370
|
-
}
|
|
2384
|
+
getDescription() {
|
|
2385
|
+
return this._description;
|
|
2371
2386
|
}
|
|
2372
2387
|
/**
|
|
2373
|
-
*
|
|
2374
|
-
*
|
|
2388
|
+
* Returns whether a value is mandatory for {@link AbdbColumn#validate}.
|
|
2389
|
+
*
|
|
2390
|
+
* @returns `true` if constructed with `isRequired: true`; otherwise `false`
|
|
2375
2391
|
*/
|
|
2376
|
-
|
|
2377
|
-
return this.
|
|
2392
|
+
getIsRequired() {
|
|
2393
|
+
return this._isRequired;
|
|
2378
2394
|
}
|
|
2379
|
-
};
|
|
2380
|
-
__name(_CustomLogger, "CustomLogger");
|
|
2381
|
-
var CustomLogger = _CustomLogger;
|
|
2382
|
-
var custom_logger_default = CustomLogger;
|
|
2383
|
-
|
|
2384
|
-
// src/framework/publish-event/index.ts
|
|
2385
|
-
var _PublishEvent = class _PublishEvent {
|
|
2386
2395
|
/**
|
|
2387
|
-
*
|
|
2388
|
-
*
|
|
2389
|
-
* @param imsOrgId - Adobe IMS Organization ID
|
|
2390
|
-
* @param apiKey - Adobe API Key (Client ID)
|
|
2391
|
-
* @param accessToken - Adobe Access Token
|
|
2392
|
-
* @param logger - Optional logger instance
|
|
2396
|
+
* Serializes this column to a plain object for JSON (config files, APIs, persistence).
|
|
2393
2397
|
*/
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2398
|
+
toJSON() {
|
|
2399
|
+
const json = {
|
|
2400
|
+
name: this._name,
|
|
2401
|
+
type: this._type
|
|
2402
|
+
};
|
|
2403
|
+
if (this._description !== void 0) {
|
|
2404
|
+
json.description = this._description;
|
|
2400
2405
|
}
|
|
2401
|
-
if (
|
|
2402
|
-
|
|
2406
|
+
if (this._isRequired) {
|
|
2407
|
+
json.isRequired = true;
|
|
2403
2408
|
}
|
|
2404
|
-
|
|
2405
|
-
this.apiKey = apiKey;
|
|
2406
|
-
this.accessToken = accessToken;
|
|
2407
|
-
this.customLogger = new custom_logger_default(logger);
|
|
2408
|
-
this.customLogger.debug("PublishEvent initialized with valid configuration");
|
|
2409
|
+
return json;
|
|
2409
2410
|
}
|
|
2410
2411
|
/**
|
|
2411
|
-
*
|
|
2412
|
+
* Creates a new column with the same or overridden options. Does not mutate this instance.
|
|
2412
2413
|
*
|
|
2413
|
-
*
|
|
2414
|
-
* @param eventCode - The event type identifier (e.g., 'commerce.order.created')
|
|
2415
|
-
* @param payload - The event payload data
|
|
2416
|
-
* @param eventId - Optional custom event ID; if not provided, a UUID will be generated
|
|
2417
|
-
* @param subject - Optional subject for the event
|
|
2418
|
-
* @returns Promise<PublishEventResult> - The publish result
|
|
2414
|
+
* Passing `description: ' '` (blank / whitespace) clears the description on the new column.
|
|
2419
2415
|
*
|
|
2420
|
-
* @
|
|
2416
|
+
* @param overrides - Partial options; omitted keys keep current values
|
|
2417
|
+
* @returns A new {@link AbdbColumn} instance
|
|
2421
2418
|
*/
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
throw new Error("eventCode is required and cannot be empty");
|
|
2429
|
-
}
|
|
2430
|
-
if (payload === null || payload === void 0) {
|
|
2431
|
-
throw new Error("payload is required");
|
|
2432
|
-
}
|
|
2433
|
-
this.customLogger.info(`Publishing event to provider: ${providerId}`);
|
|
2434
|
-
eventId = eventId ?? uuidv4();
|
|
2435
|
-
const cloudEvent = new CloudEvent({
|
|
2436
|
-
id: eventId,
|
|
2437
|
-
source: `urn:uuid:${providerId}`,
|
|
2438
|
-
datacontenttype: "application/json",
|
|
2439
|
-
type: eventCode,
|
|
2440
|
-
data: payload,
|
|
2441
|
-
...subject && { subject }
|
|
2442
|
-
});
|
|
2443
|
-
this.customLogger.debug(`Constructed CloudEvent with ID: ${eventId}`);
|
|
2444
|
-
const eventsClient = await Events.init(this.imsOrgId, this.apiKey, this.accessToken);
|
|
2445
|
-
this.customLogger.debug("Adobe I/O Events client initialized successfully");
|
|
2446
|
-
await eventsClient.publishEvent(cloudEvent);
|
|
2447
|
-
const publishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2448
|
-
this.customLogger.info(`Event published successfully with ID: ${eventId}`);
|
|
2449
|
-
return {
|
|
2450
|
-
eventId,
|
|
2451
|
-
status: "published",
|
|
2452
|
-
publishedAt
|
|
2453
|
-
};
|
|
2454
|
-
} catch (error) {
|
|
2455
|
-
this.customLogger.error(`Failed to publish event: ${error.message}`);
|
|
2456
|
-
return {
|
|
2457
|
-
eventId: uuidv4(),
|
|
2458
|
-
// Generate ID for tracking even failed events
|
|
2459
|
-
status: "failed",
|
|
2460
|
-
publishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2461
|
-
error: error.message
|
|
2462
|
-
};
|
|
2419
|
+
clone(overrides = {}) {
|
|
2420
|
+
const name = overrides.name ?? this._name;
|
|
2421
|
+
const type = overrides.type ?? this._type;
|
|
2422
|
+
const isRequired = overrides.isRequired ?? this._isRequired;
|
|
2423
|
+
if (overrides.description !== void 0) {
|
|
2424
|
+
return new _AbdbColumn({ name, type, description: overrides.description, isRequired });
|
|
2463
2425
|
}
|
|
2426
|
+
if (this._description !== void 0) {
|
|
2427
|
+
return new _AbdbColumn({ name, type, description: this._description, isRequired });
|
|
2428
|
+
}
|
|
2429
|
+
return new _AbdbColumn({ name, type, isRequired });
|
|
2464
2430
|
}
|
|
2465
|
-
};
|
|
2466
|
-
__name(_PublishEvent, "PublishEvent");
|
|
2467
|
-
var PublishEvent = _PublishEvent;
|
|
2468
|
-
var publish_event_default = PublishEvent;
|
|
2469
|
-
|
|
2470
|
-
// src/framework/webhook-action/response/types.ts
|
|
2471
|
-
var WebhookActionOperation = /* @__PURE__ */ ((WebhookActionOperation2) => {
|
|
2472
|
-
WebhookActionOperation2["SUCCESS"] = "success";
|
|
2473
|
-
WebhookActionOperation2["EXCEPTION"] = "exception";
|
|
2474
|
-
WebhookActionOperation2["ADD"] = "add";
|
|
2475
|
-
WebhookActionOperation2["REPLACE"] = "replace";
|
|
2476
|
-
WebhookActionOperation2["REMOVE"] = "remove";
|
|
2477
|
-
return WebhookActionOperation2;
|
|
2478
|
-
})(WebhookActionOperation || {});
|
|
2479
|
-
|
|
2480
|
-
// src/framework/webhook-action/response/index.ts
|
|
2481
|
-
var _WebhookActionResponse = class _WebhookActionResponse {
|
|
2482
2431
|
/**
|
|
2483
|
-
*
|
|
2484
|
-
*
|
|
2485
|
-
* Use this method when the webhook has been processed without errors and
|
|
2486
|
-
* no modifications to the payload are needed.
|
|
2432
|
+
* Validates a payload value against this column's requiredness and {@link AbdbColumnType}.
|
|
2487
2433
|
*
|
|
2488
|
-
*
|
|
2434
|
+
* **Missing values** (`undefined`, `null`, or empty / whitespace-only strings) are handled first:
|
|
2435
|
+
* if `isRequired` is `false`, validation succeeds; if `true`, throws.
|
|
2489
2436
|
*
|
|
2490
|
-
*
|
|
2491
|
-
*
|
|
2492
|
-
*
|
|
2493
|
-
*
|
|
2494
|
-
*
|
|
2437
|
+
* **Type rules** (when a non-missing value is present):
|
|
2438
|
+
* - `STRING`: primitive string
|
|
2439
|
+
* - `NUMBER`: finite number, or a non-empty string whose trim parses to a finite number via `Number`
|
|
2440
|
+
* - `BOOLEAN`: primitive boolean, or non-empty trimmed string `"true"` / `"false"` (case-insensitive)
|
|
2441
|
+
* - `DATETIME`: valid `Date`, finite numeric timestamp (ms), or non-empty trimmed ISO 8601 date/datetime string
|
|
2495
2442
|
*
|
|
2496
|
-
*
|
|
2497
|
-
*
|
|
2498
|
-
* statusCode: 200,
|
|
2499
|
-
* body: WebhookActionResponse.success()
|
|
2500
|
-
* };
|
|
2501
|
-
* });
|
|
2502
|
-
* ```
|
|
2443
|
+
* @param value - Document field or API payload value
|
|
2444
|
+
* @throws {Error} When required and missing, or when the value does not match the column type
|
|
2503
2445
|
*/
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
* Creates an exception response to report an error during webhook processing.
|
|
2511
|
-
*
|
|
2512
|
-
* Use this method to notify Adobe Commerce that an error occurred while
|
|
2513
|
-
* processing the webhook. This helps with debugging and error tracking.
|
|
2514
|
-
*
|
|
2515
|
-
* @param message - Optional error message describing what went wrong
|
|
2516
|
-
* @param exceptionClass - Optional exception class name for categorization (e.g., 'Magento\\Framework\\Exception\\LocalizedException')
|
|
2517
|
-
* @returns An exception response object
|
|
2518
|
-
*
|
|
2519
|
-
* @example
|
|
2520
|
-
* ```typescript
|
|
2521
|
-
* const handler = WebhookAction.execute('validate-product', [], [], async (params) => {
|
|
2522
|
-
* const product = await findProduct(params.sku);
|
|
2523
|
-
*
|
|
2524
|
-
* if (!product) {
|
|
2525
|
-
* return {
|
|
2526
|
-
* statusCode: 404,
|
|
2527
|
-
* body: WebhookActionResponse.exception(
|
|
2528
|
-
* `Product with SKU ${params.sku} not found`,
|
|
2529
|
-
* 'Magento\\Framework\\Exception\\NoSuchEntityException'
|
|
2530
|
-
* )
|
|
2531
|
-
* };
|
|
2532
|
-
* }
|
|
2533
|
-
*
|
|
2534
|
-
* return { statusCode: 200, body: WebhookActionResponse.success() };
|
|
2535
|
-
* });
|
|
2536
|
-
* ```
|
|
2537
|
-
*/
|
|
2538
|
-
static exception(message, exceptionClass) {
|
|
2539
|
-
const response = {
|
|
2540
|
-
op: "exception" /* EXCEPTION */
|
|
2541
|
-
};
|
|
2542
|
-
if (message !== void 0) {
|
|
2543
|
-
response.message = message;
|
|
2446
|
+
validate(value) {
|
|
2447
|
+
if (this._isMissingValue(value)) {
|
|
2448
|
+
if (this._isRequired) {
|
|
2449
|
+
throw new Error(`AbdbColumn: "${this._name}" is required`);
|
|
2450
|
+
}
|
|
2451
|
+
return;
|
|
2544
2452
|
}
|
|
2545
|
-
|
|
2546
|
-
|
|
2453
|
+
switch (this._type) {
|
|
2454
|
+
case "STRING" /* STRING */:
|
|
2455
|
+
this._validateStringValue(value);
|
|
2456
|
+
return;
|
|
2457
|
+
case "NUMBER" /* NUMBER */:
|
|
2458
|
+
this._validateNumberValue(value);
|
|
2459
|
+
return;
|
|
2460
|
+
case "BOOLEAN" /* BOOLEAN */:
|
|
2461
|
+
this._validateBooleanValue(value);
|
|
2462
|
+
return;
|
|
2463
|
+
case "DATETIME" /* DATETIME */:
|
|
2464
|
+
this._validateDateTimeValue(value);
|
|
2465
|
+
return;
|
|
2466
|
+
default: {
|
|
2467
|
+
const _unreachable = this._type;
|
|
2468
|
+
throw new Error(`AbdbColumn: unhandled type "${_unreachable}"`);
|
|
2469
|
+
}
|
|
2547
2470
|
}
|
|
2548
|
-
return response;
|
|
2549
2471
|
}
|
|
2550
2472
|
/**
|
|
2551
|
-
*
|
|
2552
|
-
*
|
|
2553
|
-
* Use this method to inject additional data into the webhook payload that
|
|
2554
|
-
* will be processed by Adobe Commerce. The data is added at the specified
|
|
2555
|
-
* path using dot notation.
|
|
2556
|
-
*
|
|
2557
|
-
* @param path - Dot-notation path where the value should be added (e.g., 'order.items', 'customer.addresses')
|
|
2558
|
-
* @param value - The value to add at the specified path
|
|
2559
|
-
* @param instance - Optional instance identifier for tracking or reference purposes
|
|
2560
|
-
* @returns An add response object
|
|
2473
|
+
* Whether `value` counts as absent for requiredness checks (before type validation).
|
|
2561
2474
|
*
|
|
2562
|
-
* @
|
|
2563
|
-
* ```typescript
|
|
2564
|
-
* const handler = WebhookAction.execute('enrich-order', [], [], async (params) => {
|
|
2565
|
-
* // Add loyalty points to the order
|
|
2566
|
-
* return {
|
|
2567
|
-
* statusCode: 200,
|
|
2568
|
-
* body: WebhookActionResponse.add(
|
|
2569
|
-
* 'order.loyalty',
|
|
2570
|
-
* { points: 150, tier: 'gold' },
|
|
2571
|
-
* params.order.id
|
|
2572
|
-
* )
|
|
2573
|
-
* };
|
|
2574
|
-
* });
|
|
2575
|
-
* ```
|
|
2475
|
+
* @returns `true` for `undefined`, `null`, or a string that is empty after trim
|
|
2576
2476
|
*/
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
path,
|
|
2581
|
-
value
|
|
2582
|
-
};
|
|
2583
|
-
if (instance !== void 0) {
|
|
2584
|
-
response.instance = instance;
|
|
2477
|
+
_isMissingValue(value) {
|
|
2478
|
+
if (value === void 0 || value === null) {
|
|
2479
|
+
return true;
|
|
2585
2480
|
}
|
|
2586
|
-
return
|
|
2481
|
+
return typeof value === "string" && value.trim() === "";
|
|
2587
2482
|
}
|
|
2588
2483
|
/**
|
|
2589
|
-
*
|
|
2590
|
-
*
|
|
2591
|
-
* Use this method to modify existing fields in the webhook payload.
|
|
2592
|
-
* The existing value at the specified path will be replaced with the new value.
|
|
2593
|
-
*
|
|
2594
|
-
* @param path - Dot-notation path to the field that should be replaced (e.g., 'product.price', 'order.status')
|
|
2595
|
-
* @param value - The new value to replace the existing value
|
|
2596
|
-
* @param instance - Optional instance identifier for tracking or reference purposes
|
|
2597
|
-
* @returns A replace response object
|
|
2598
|
-
*
|
|
2599
|
-
* @example
|
|
2600
|
-
* ```typescript
|
|
2601
|
-
* const handler = WebhookAction.execute('adjust-price', [], [], async (params) => {
|
|
2602
|
-
* // Apply dynamic pricing
|
|
2603
|
-
* const newPrice = await calculateDiscountedPrice(params.product.price);
|
|
2604
|
-
*
|
|
2605
|
-
* return {
|
|
2606
|
-
* statusCode: 200,
|
|
2607
|
-
* body: WebhookActionResponse.replace(
|
|
2608
|
-
* 'product.price',
|
|
2609
|
-
* newPrice,
|
|
2610
|
-
* params.product.id
|
|
2611
|
-
* )
|
|
2612
|
-
* };
|
|
2613
|
-
* });
|
|
2614
|
-
* ```
|
|
2484
|
+
* @throws {Error} When `value` is not a primitive string
|
|
2615
2485
|
*/
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
path,
|
|
2620
|
-
value
|
|
2621
|
-
};
|
|
2622
|
-
if (instance !== void 0) {
|
|
2623
|
-
response.instance = instance;
|
|
2486
|
+
_validateStringValue(value) {
|
|
2487
|
+
if (typeof value !== "string") {
|
|
2488
|
+
throw new Error(`AbdbColumn: "${this._name}" expects string, got ${typeof value}`);
|
|
2624
2489
|
}
|
|
2625
|
-
return response;
|
|
2626
2490
|
}
|
|
2627
2491
|
/**
|
|
2628
|
-
*
|
|
2629
|
-
*
|
|
2630
|
-
* Use this method to remove fields from the webhook payload before it's
|
|
2631
|
-
* processed by Adobe Commerce. This is useful for filtering sensitive data
|
|
2632
|
-
* or removing unnecessary information.
|
|
2633
|
-
*
|
|
2634
|
-
* @param path - Dot-notation path to the field that should be removed (e.g., 'items.0', 'customer.internal_notes')
|
|
2635
|
-
* @returns A remove response object
|
|
2636
|
-
*
|
|
2637
|
-
* @example
|
|
2638
|
-
* ```typescript
|
|
2639
|
-
* const handler = WebhookAction.execute('sanitize-customer', [], [], async (params) => {
|
|
2640
|
-
* // Remove internal notes before processing
|
|
2641
|
-
* return {
|
|
2642
|
-
* statusCode: 200,
|
|
2643
|
-
* body: WebhookActionResponse.remove('customer.internal_notes')
|
|
2644
|
-
* };
|
|
2645
|
-
* });
|
|
2646
|
-
* ```
|
|
2647
|
-
*
|
|
2648
|
-
* @example
|
|
2649
|
-
* ```typescript
|
|
2650
|
-
* // Remove an item from an array
|
|
2651
|
-
* return {
|
|
2652
|
-
* statusCode: 200,
|
|
2653
|
-
* body: WebhookActionResponse.remove('order.items.2')
|
|
2654
|
-
* };
|
|
2655
|
-
* ```
|
|
2492
|
+
* @throws {Error} When not a finite number or numeric string
|
|
2656
2493
|
*/
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2494
|
+
_validateNumberValue(value) {
|
|
2495
|
+
if (typeof value === "number") {
|
|
2496
|
+
if (Number.isFinite(value)) {
|
|
2497
|
+
return;
|
|
2498
|
+
}
|
|
2499
|
+
throw new Error(`AbdbColumn: "${this._name}" expects a finite number, got ${String(value)}`);
|
|
2500
|
+
}
|
|
2501
|
+
if (typeof value === "string") {
|
|
2502
|
+
const trimmed = value.trim();
|
|
2503
|
+
const n = Number(trimmed);
|
|
2504
|
+
if (Number.isFinite(n)) {
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
throw new Error(
|
|
2508
|
+
`AbdbColumn: "${this._name}" expects a finite number or numeric string, got ${JSON.stringify(value)}`
|
|
2509
|
+
);
|
|
2510
|
+
}
|
|
2511
|
+
throw new Error(
|
|
2512
|
+
`AbdbColumn: "${this._name}" expects a finite number or numeric string, got ${typeof value}`
|
|
2513
|
+
);
|
|
2662
2514
|
}
|
|
2663
|
-
};
|
|
2664
|
-
__name(_WebhookActionResponse, "WebhookActionResponse");
|
|
2665
|
-
var WebhookActionResponse = _WebhookActionResponse;
|
|
2666
|
-
var response_default2 = WebhookActionResponse;
|
|
2667
|
-
|
|
2668
|
-
// src/framework/webhook-action/types.ts
|
|
2669
|
-
var SignatureVerification = /* @__PURE__ */ ((SignatureVerification2) => {
|
|
2670
|
-
SignatureVerification2["ENABLED"] = "enabled";
|
|
2671
|
-
SignatureVerification2["DISABLED"] = "disabled";
|
|
2672
|
-
return SignatureVerification2;
|
|
2673
|
-
})(SignatureVerification || {});
|
|
2674
|
-
|
|
2675
|
-
// src/framework/webhook-action/index.ts
|
|
2676
|
-
import crypto from "crypto";
|
|
2677
|
-
var _WebhookAction = class _WebhookAction {
|
|
2678
2515
|
/**
|
|
2679
|
-
*
|
|
2680
|
-
*
|
|
2681
|
-
* @param name - Name of the webhook action
|
|
2682
|
-
* @param requiredParams - Required parameters in the webhook payload
|
|
2683
|
-
* @param requiredHeaders - Required headers (e.g., signature headers)
|
|
2684
|
-
* @param signatureVerification - Enable/disable signature verification
|
|
2685
|
-
* @param action - Webhook action function returning WebhookActionResponse
|
|
2686
|
-
* @returns Function that handles the webhook HTTP request
|
|
2516
|
+
* @throws {Error} When not a boolean or `"true"` / `"false"` string
|
|
2687
2517
|
*/
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
telemetry
|
|
2697
|
-
);
|
|
2698
|
-
if (verificationErrorMessage) {
|
|
2699
|
-
logger.error({
|
|
2700
|
-
message: "Webhook action signature verification failed",
|
|
2701
|
-
error: verificationErrorMessage
|
|
2702
|
-
});
|
|
2703
|
-
const verificationErrorResponse = response_default2.exception(verificationErrorMessage);
|
|
2704
|
-
return response_default.success(JSON.stringify(verificationErrorResponse));
|
|
2705
|
-
}
|
|
2706
|
-
params = {
|
|
2707
|
-
...params,
|
|
2708
|
-
...JSON.parse(atob(params.__ow_body))
|
|
2709
|
-
};
|
|
2710
|
-
}
|
|
2711
|
-
const errorMessage = _WebhookAction.validateWithInstrumentation(
|
|
2712
|
-
name,
|
|
2713
|
-
params,
|
|
2714
|
-
requiredParams,
|
|
2715
|
-
requiredHeaders,
|
|
2716
|
-
telemetry
|
|
2717
|
-
);
|
|
2718
|
-
if (errorMessage) {
|
|
2719
|
-
logger.error({
|
|
2720
|
-
message: "Webhook action validation failed",
|
|
2721
|
-
error: errorMessage
|
|
2722
|
-
});
|
|
2723
|
-
const errorMessageResponse = response_default2.exception(errorMessage);
|
|
2724
|
-
return response_default.success(JSON.stringify(errorMessageResponse));
|
|
2518
|
+
_validateBooleanValue(value) {
|
|
2519
|
+
if (typeof value === "boolean") {
|
|
2520
|
+
return;
|
|
2521
|
+
}
|
|
2522
|
+
if (typeof value === "string") {
|
|
2523
|
+
const t = value.trim().toLowerCase();
|
|
2524
|
+
if (t === "true" || t === "false") {
|
|
2525
|
+
return;
|
|
2725
2526
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
action,
|
|
2729
|
-
params,
|
|
2730
|
-
ctx
|
|
2527
|
+
throw new Error(
|
|
2528
|
+
`AbdbColumn: "${this._name}" expects boolean or "true"/"false" string, got ${JSON.stringify(value)}`
|
|
2731
2529
|
);
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
return runtime_action_default.execute(name, httpMethods, [], [], callback);
|
|
2530
|
+
}
|
|
2531
|
+
throw new Error(
|
|
2532
|
+
`AbdbColumn: "${this._name}" expects boolean or "true"/"false" string, got ${typeof value}`
|
|
2533
|
+
);
|
|
2737
2534
|
}
|
|
2738
2535
|
/**
|
|
2739
|
-
*
|
|
2740
|
-
*
|
|
2741
|
-
* This method wraps the webhook action execution with distributed tracing instrumentation,
|
|
2742
|
-
* creating a span that tracks execution metrics and results in New Relic.
|
|
2743
|
-
*
|
|
2744
|
-
* @param name - Webhook action name used for span naming
|
|
2745
|
-
* @param action - The webhook action function to execute
|
|
2746
|
-
* @param params - Request parameters
|
|
2747
|
-
* @param ctx - Context object with logger, headers, and telemetry
|
|
2748
|
-
* @returns Promise resolving to the webhook action response(s)
|
|
2749
|
-
*
|
|
2750
|
-
* @private
|
|
2536
|
+
* @throws {Error} When not a valid `Date`, finite timestamp, or ISO 8601 string
|
|
2751
2537
|
*/
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
})
|
|
2772
|
-
|
|
2773
|
-
|
|
2538
|
+
_validateDateTimeValue(value) {
|
|
2539
|
+
if (value instanceof Date) {
|
|
2540
|
+
if (!Number.isNaN(value.getTime())) {
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
throw new Error(`AbdbColumn: "${this._name}" expects a valid Date`);
|
|
2544
|
+
}
|
|
2545
|
+
if (typeof value === "number") {
|
|
2546
|
+
if (Number.isFinite(value)) {
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
throw new Error(
|
|
2550
|
+
`AbdbColumn: "${this._name}" expects a finite numeric timestamp, got ${value}`
|
|
2551
|
+
);
|
|
2552
|
+
}
|
|
2553
|
+
if (typeof value === "string") {
|
|
2554
|
+
const s = value.trim();
|
|
2555
|
+
if (!ISO_8601_DATETIME.test(s)) {
|
|
2556
|
+
throw new Error(
|
|
2557
|
+
`AbdbColumn: "${this._name}" expects ISO 8601 datetime string, got ${JSON.stringify(value)}`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
const t = Date.parse(s);
|
|
2561
|
+
if (Number.isNaN(t)) {
|
|
2562
|
+
throw new Error(
|
|
2563
|
+
`AbdbColumn: "${this._name}" expects ISO 8601 datetime string, got ${JSON.stringify(value)}`
|
|
2564
|
+
);
|
|
2774
2565
|
}
|
|
2566
|
+
return;
|
|
2567
|
+
}
|
|
2568
|
+
throw new Error(
|
|
2569
|
+
`AbdbColumn: "${this._name}" expects Date, finite numeric timestamp, or ISO 8601 datetime string, got ${typeof value}`
|
|
2775
2570
|
);
|
|
2776
|
-
return instrumentedWebhookAction(name, action, params, ctx);
|
|
2777
2571
|
}
|
|
2572
|
+
};
|
|
2573
|
+
__name(_AbdbColumn, "AbdbColumn");
|
|
2574
|
+
var AbdbColumn = _AbdbColumn;
|
|
2575
|
+
var column_default = AbdbColumn;
|
|
2576
|
+
|
|
2577
|
+
// src/framework/abdb/collection/index.ts
|
|
2578
|
+
var _AbdbCollection = class _AbdbCollection {
|
|
2778
2579
|
/**
|
|
2779
|
-
*
|
|
2780
|
-
*
|
|
2781
|
-
* This method wraps the signature verification logic with distributed tracing instrumentation,
|
|
2782
|
-
* creating a span that tracks verification metrics and results in New Relic.
|
|
2783
|
-
*
|
|
2784
|
-
* @param name - Webhook action name used for span naming
|
|
2785
|
-
* @param params - Request parameters including headers, body, and public key
|
|
2786
|
-
* @param telemetry - Telemetry instance for instrumentation
|
|
2787
|
-
* @returns Error message if verification fails, null if successful
|
|
2580
|
+
* Creates a collection and optionally configures it in a callback (e.g. chained {@link addColumn} calls).
|
|
2788
2581
|
*
|
|
2789
|
-
* @
|
|
2582
|
+
* @param name - Raw collection name; trimmed and validated
|
|
2583
|
+
* @param callback - Optional function invoked with `this` for fluent setup
|
|
2584
|
+
* @throws {Error} When `name` is empty, whitespace-only, or contains invalid characters
|
|
2790
2585
|
*/
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
}
|
|
2815
|
-
span.addEvent("signature-verification-completed", {
|
|
2816
|
-
valid: verificationError === null
|
|
2817
|
-
});
|
|
2818
|
-
}
|
|
2819
|
-
return verificationError;
|
|
2586
|
+
constructor(name, callback) {
|
|
2587
|
+
this._name = this._validateCollectionName(name);
|
|
2588
|
+
this._columns = /* @__PURE__ */ new Map();
|
|
2589
|
+
if (callback) {
|
|
2590
|
+
callback(this);
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* Returns this collection's name.
|
|
2595
|
+
*/
|
|
2596
|
+
getName() {
|
|
2597
|
+
return this._name;
|
|
2598
|
+
}
|
|
2599
|
+
addColumn(name, type, descriptionOrOptions, isRequired) {
|
|
2600
|
+
const trimmed = this._validateColumnName(name);
|
|
2601
|
+
if (this._columns.has(trimmed)) {
|
|
2602
|
+
throw new Error(`AbdbCollection: duplicate column name "${trimmed}"`);
|
|
2603
|
+
}
|
|
2604
|
+
const columnOptions = { name: trimmed, type };
|
|
2605
|
+
if (typeof descriptionOrOptions === "string") {
|
|
2606
|
+
columnOptions.description = descriptionOrOptions;
|
|
2607
|
+
if (isRequired !== void 0) {
|
|
2608
|
+
columnOptions.isRequired = isRequired;
|
|
2820
2609
|
}
|
|
2821
|
-
)
|
|
2822
|
-
|
|
2610
|
+
} else if (descriptionOrOptions !== void 0) {
|
|
2611
|
+
if (descriptionOrOptions.description !== void 0) {
|
|
2612
|
+
columnOptions.description = descriptionOrOptions.description;
|
|
2613
|
+
}
|
|
2614
|
+
if (descriptionOrOptions.isRequired !== void 0) {
|
|
2615
|
+
columnOptions.isRequired = descriptionOrOptions.isRequired;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
this._columns.set(trimmed, new column_default(columnOptions));
|
|
2619
|
+
return this;
|
|
2823
2620
|
}
|
|
2824
2621
|
/**
|
|
2825
|
-
*
|
|
2622
|
+
* Returns a defensive copy of columns in insertion order.
|
|
2623
|
+
*/
|
|
2624
|
+
getColumns() {
|
|
2625
|
+
return Array.from(this._columns.values());
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* Returns the column registered under `name`, or `undefined` if no such column exists.
|
|
2826
2629
|
*
|
|
2827
|
-
*
|
|
2828
|
-
*
|
|
2630
|
+
* @param name - Column name to look up (exact match after trimming)
|
|
2631
|
+
* @returns The {@link AbdbColumn} instance, or `undefined`
|
|
2632
|
+
*/
|
|
2633
|
+
getColumn(name) {
|
|
2634
|
+
return this._columns.get(name.trim());
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Returns `true` when a column with the given name has been registered.
|
|
2829
2638
|
*
|
|
2830
|
-
* @param name -
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2639
|
+
* @param name - Column name to check (exact match after trimming)
|
|
2640
|
+
*/
|
|
2641
|
+
hasColumn(name) {
|
|
2642
|
+
return this._columns.has(name.trim());
|
|
2643
|
+
}
|
|
2644
|
+
/**
|
|
2645
|
+
* Validates a document-style object against this collection's columns. Throws on the **first** failing
|
|
2646
|
+
* column. Use {@link validateAll} when you need all errors reported at once.
|
|
2836
2647
|
*
|
|
2837
|
-
*
|
|
2648
|
+
* Missing keys are read as `undefined`, so a **required** column whose key is absent fails validation.
|
|
2649
|
+
* Extra keys not matching any column name are ignored.
|
|
2650
|
+
*
|
|
2651
|
+
* @param record - Key/value payload whose keys are column names
|
|
2652
|
+
* @throws {Error} When any column validation fails (requiredness or type)
|
|
2838
2653
|
*/
|
|
2839
|
-
|
|
2840
|
-
const
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
const span = actionTelemetry.getCurrentSpan();
|
|
2844
|
-
if (span) {
|
|
2845
|
-
span.setAttribute("webhook.validation.required_params", actionRequiredParams.join(","));
|
|
2846
|
-
span.setAttribute("webhook.validation.required_headers", actionRequiredHeaders.join(","));
|
|
2847
|
-
}
|
|
2848
|
-
const errorMessage = validator_default.checkMissingRequestInputs(
|
|
2849
|
-
actionParams,
|
|
2850
|
-
actionRequiredParams,
|
|
2851
|
-
actionRequiredHeaders
|
|
2852
|
-
) ?? "";
|
|
2853
|
-
if (span) {
|
|
2854
|
-
span.setAttribute("webhook.validation.passed", errorMessage === "");
|
|
2855
|
-
if (errorMessage) {
|
|
2856
|
-
span.setAttribute("webhook.validation.error", errorMessage);
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
return errorMessage;
|
|
2860
|
-
}
|
|
2861
|
-
);
|
|
2862
|
-
return instrumentedValidate(name, params, requiredParams, requiredHeaders, telemetry);
|
|
2654
|
+
validate(record) {
|
|
2655
|
+
for (const col of this._columns.values()) {
|
|
2656
|
+
col.validate(record[col.getName()]);
|
|
2657
|
+
}
|
|
2863
2658
|
}
|
|
2864
2659
|
/**
|
|
2865
|
-
*
|
|
2660
|
+
* Validates a document-style object against **all** columns and collects every error instead of
|
|
2661
|
+
* stopping at the first failure. Useful at API boundaries where reporting all problems at once
|
|
2662
|
+
* gives a better developer or end-user experience.
|
|
2866
2663
|
*
|
|
2867
|
-
*
|
|
2868
|
-
* the signature against the request body using the provided public key.
|
|
2664
|
+
* Returns an empty array when the record is fully valid.
|
|
2869
2665
|
*
|
|
2870
|
-
* @param
|
|
2871
|
-
* @returns
|
|
2666
|
+
* @param record - Key/value payload whose keys are column names
|
|
2667
|
+
* @returns Array of validation error messages, one per failing column (insertion order)
|
|
2872
2668
|
*
|
|
2873
|
-
* @
|
|
2669
|
+
* @example
|
|
2670
|
+
* ```typescript
|
|
2671
|
+
* const errors = orders.validateAll(payload);
|
|
2672
|
+
* if (errors.length > 0) {
|
|
2673
|
+
* return { status: 400, body: { errors } };
|
|
2674
|
+
* }
|
|
2675
|
+
* ```
|
|
2874
2676
|
*/
|
|
2875
|
-
|
|
2876
|
-
const
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
}
|
|
2884
|
-
let publicKey = params.PUBLIC_KEY;
|
|
2885
|
-
if (!publicKey && params.PUBLIC_KEY_BASE64) {
|
|
2886
|
-
publicKey = atob(params.PUBLIC_KEY_BASE64);
|
|
2887
|
-
}
|
|
2888
|
-
if (!publicKey) {
|
|
2889
|
-
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.";
|
|
2677
|
+
validateAll(record) {
|
|
2678
|
+
const errors = [];
|
|
2679
|
+
for (const col of this._columns.values()) {
|
|
2680
|
+
try {
|
|
2681
|
+
col.validate(record[col.getName()]);
|
|
2682
|
+
} catch (e) {
|
|
2683
|
+
errors.push(e instanceof Error ? e.message : String(e));
|
|
2684
|
+
}
|
|
2890
2685
|
}
|
|
2686
|
+
return errors;
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Connects to the database (via `@adobe/aio-lib-db`), runs `callback` with the selected DB **collection**
|
|
2690
|
+
* handle and the **client**, then closes the client in a `finally` block.
|
|
2691
|
+
*
|
|
2692
|
+
* **Errors:** `DbError` instances are rethrown as `AbdbCollection: database error: …`;
|
|
2693
|
+
* any other value is wrapped as `AbdbCollection: unexpected error: …`.
|
|
2694
|
+
*
|
|
2695
|
+
* @param callback - Receives `(collection, client)` after connect
|
|
2696
|
+
* @param token - IMS access token for `initDb`
|
|
2697
|
+
* @param region - Data region (default: `'amer'`)
|
|
2698
|
+
* @returns Promise resolving to the callback's return value
|
|
2699
|
+
* @throws {Error} On init, connect, `DbError`, or callback failure
|
|
2700
|
+
*/
|
|
2701
|
+
async run(callback, token, region = "amer") {
|
|
2702
|
+
let client;
|
|
2891
2703
|
try {
|
|
2892
|
-
const
|
|
2893
|
-
|
|
2894
|
-
const
|
|
2895
|
-
|
|
2896
|
-
return "The signature is invalid.";
|
|
2897
|
-
}
|
|
2704
|
+
const db = await initDb({ token, region });
|
|
2705
|
+
client = await db.connect();
|
|
2706
|
+
const collection = await client.collection(this._name);
|
|
2707
|
+
return await callback(collection, client);
|
|
2898
2708
|
} catch (error) {
|
|
2899
|
-
|
|
2709
|
+
if (error instanceof DbError) {
|
|
2710
|
+
throw new Error(`AbdbCollection: database error: ${error.message}`);
|
|
2711
|
+
}
|
|
2712
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2713
|
+
throw new Error(`AbdbCollection: unexpected error: ${detail}`);
|
|
2714
|
+
} finally {
|
|
2715
|
+
await client?.close();
|
|
2900
2716
|
}
|
|
2901
|
-
return null;
|
|
2902
2717
|
}
|
|
2903
|
-
};
|
|
2904
|
-
__name(_WebhookAction, "WebhookAction");
|
|
2905
|
-
var WebhookAction = _WebhookAction;
|
|
2906
|
-
var webhook_action_default = WebhookAction;
|
|
2907
|
-
|
|
2908
|
-
// src/framework/ims-token/index.ts
|
|
2909
|
-
import { State } from "@adobe/aio-sdk";
|
|
2910
|
-
|
|
2911
|
-
// src/commerce/adobe-auth/index.ts
|
|
2912
|
-
import { context, getToken } from "@adobe/aio-lib-ims";
|
|
2913
|
-
var _AdobeAuth = class _AdobeAuth {
|
|
2914
2718
|
/**
|
|
2915
|
-
*
|
|
2719
|
+
* Validates and normalizes the collection identifier used at construction.
|
|
2916
2720
|
*
|
|
2917
|
-
* @
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2721
|
+
* @throws {Error} If the name is empty or not `[a-zA-Z0-9_]`
|
|
2722
|
+
*/
|
|
2723
|
+
_validateCollectionName(name) {
|
|
2724
|
+
return this._validateIdentifier(
|
|
2725
|
+
name,
|
|
2726
|
+
'AbdbCollection: "name" is required and cannot be empty',
|
|
2727
|
+
"AbdbCollection: name must contain only alphanumeric characters and underscores"
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
/**
|
|
2731
|
+
* Validates and normalizes a column name before {@link addColumn} stores it.
|
|
2925
2732
|
*
|
|
2926
|
-
* @
|
|
2927
|
-
* const token = await AdobeAuth.getToken(
|
|
2928
|
-
* 'your-client-id',
|
|
2929
|
-
* 'your-client-secret',
|
|
2930
|
-
* 'your-technical-account-id',
|
|
2931
|
-
* 'your-technical-account-email',
|
|
2932
|
-
* 'your-ims-org-id',
|
|
2933
|
-
* ['AdobeID', 'openid', 'adobeio_api']
|
|
2934
|
-
* );
|
|
2733
|
+
* @throws {Error} If the name is empty or not `[a-zA-Z0-9_]`
|
|
2935
2734
|
*/
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2735
|
+
_validateColumnName(name) {
|
|
2736
|
+
return this._validateIdentifier(
|
|
2737
|
+
name,
|
|
2738
|
+
'AbdbCollection: column "name" is required and cannot be empty',
|
|
2739
|
+
"AbdbCollection: column name must contain only alphanumeric characters and underscores"
|
|
2740
|
+
);
|
|
2741
|
+
}
|
|
2742
|
+
/**
|
|
2743
|
+
* Shared validation for collection and column identifiers.
|
|
2744
|
+
*
|
|
2745
|
+
* @param raw - Unvalidated string
|
|
2746
|
+
* @param emptyMessage - Thrown when `raw` is missing or whitespace-only
|
|
2747
|
+
* @param invalidMessage - Thrown when the trimmed value is not alphanumeric with underscores
|
|
2748
|
+
* @returns The trimmed identifier
|
|
2749
|
+
*/
|
|
2750
|
+
_validateIdentifier(raw, emptyMessage, invalidMessage) {
|
|
2751
|
+
if (!raw || raw.trim() === "") {
|
|
2752
|
+
throw new Error(emptyMessage);
|
|
2753
|
+
}
|
|
2754
|
+
const trimmed = raw.trim();
|
|
2755
|
+
if (!/^[a-zA-Z0-9_]+$/.test(trimmed)) {
|
|
2756
|
+
throw new Error(invalidMessage);
|
|
2757
|
+
}
|
|
2758
|
+
return trimmed;
|
|
2948
2759
|
}
|
|
2949
2760
|
};
|
|
2950
|
-
__name(
|
|
2951
|
-
var
|
|
2952
|
-
var
|
|
2761
|
+
__name(_AbdbCollection, "AbdbCollection");
|
|
2762
|
+
var AbdbCollection = _AbdbCollection;
|
|
2763
|
+
var collection_default = AbdbCollection;
|
|
2953
2764
|
|
|
2954
|
-
// src/
|
|
2955
|
-
var
|
|
2765
|
+
// src/framework/repository/abdb-repository/index.ts
|
|
2766
|
+
var _AbdbRepository = class _AbdbRepository {
|
|
2956
2767
|
/**
|
|
2957
|
-
*
|
|
2958
|
-
*
|
|
2768
|
+
* @param collection - Schema and DB-connection wrapper for the target collection
|
|
2769
|
+
* @param token - IMS access token forwarded to every `collection.run()` call
|
|
2770
|
+
* @param region - Data region forwarded to every `collection.run()` call (default: `'amer'`)
|
|
2771
|
+
* @throws {Error} When `collection` is not an {@link AbdbCollection} instance, or `token` is missing
|
|
2772
|
+
*/
|
|
2773
|
+
constructor(collection, token, region = "amer") {
|
|
2774
|
+
if (!(collection instanceof collection_default)) {
|
|
2775
|
+
throw new Error('AbdbRepository: "collection" must be an AbdbCollection instance');
|
|
2776
|
+
}
|
|
2777
|
+
if (!token || typeof token !== "string" || !token.trim()) {
|
|
2778
|
+
throw new Error('AbdbRepository: "token" is required');
|
|
2779
|
+
}
|
|
2780
|
+
if (!collection.hasColumn("_created_at")) {
|
|
2781
|
+
collection.addColumn("_created_at", "DATETIME" /* DATETIME */, "Record creation timestamp");
|
|
2782
|
+
}
|
|
2783
|
+
if (!collection.hasColumn("_updated_at")) {
|
|
2784
|
+
collection.addColumn("_updated_at", "DATETIME" /* DATETIME */, "Record last-updated timestamp");
|
|
2785
|
+
}
|
|
2786
|
+
this._collection = collection;
|
|
2787
|
+
this._token = token;
|
|
2788
|
+
this._region = region;
|
|
2789
|
+
}
|
|
2790
|
+
/**
|
|
2791
|
+
* Returns the name of the underlying collection.
|
|
2792
|
+
*/
|
|
2793
|
+
getName() {
|
|
2794
|
+
return this._collection.getName();
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Returns the underlying {@link AbdbCollection} instance.
|
|
2798
|
+
*/
|
|
2799
|
+
getCollection() {
|
|
2800
|
+
return this._collection;
|
|
2801
|
+
}
|
|
2802
|
+
/**
|
|
2803
|
+
* Returns all documents matching `filter` as an array.
|
|
2804
|
+
* Passing no filter (or an empty object) returns every document in the collection.
|
|
2959
2805
|
*
|
|
2960
|
-
* @param
|
|
2961
|
-
* @returns
|
|
2806
|
+
* @param filter - Optional query filter (default: `{}`)
|
|
2807
|
+
* @returns Array of matched documents (may be empty)
|
|
2808
|
+
*/
|
|
2809
|
+
async find(filter = {}) {
|
|
2810
|
+
return this._collection.run(
|
|
2811
|
+
async (collection) => {
|
|
2812
|
+
return collection.find(filter).toArray();
|
|
2813
|
+
},
|
|
2814
|
+
this._token,
|
|
2815
|
+
this._region
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* Returns the first document matching `filter`, or `null` if none found.
|
|
2962
2820
|
*
|
|
2963
|
-
* @
|
|
2964
|
-
*
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2821
|
+
* @param filter - Query filter (e.g. `{ _id: 'abc' }` or `{ email: 'a@b.com' }`)
|
|
2822
|
+
* @returns The matched document, or `null`
|
|
2823
|
+
*/
|
|
2824
|
+
async findOne(filter) {
|
|
2825
|
+
return this._collection.run(
|
|
2826
|
+
async (collection) => {
|
|
2827
|
+
return collection.findOne(filter);
|
|
2828
|
+
},
|
|
2829
|
+
this._token,
|
|
2830
|
+
this._region
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
/**
|
|
2834
|
+
* Returns `true` when a document with the given `_id` exists in the collection.
|
|
2835
|
+
* Uses `countDocuments` rather than fetching the full document for efficiency.
|
|
2969
2836
|
*
|
|
2970
|
-
* @
|
|
2971
|
-
*
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2837
|
+
* @param id - The `_id` to check
|
|
2838
|
+
* @returns `true` if the document exists, `false` otherwise
|
|
2839
|
+
*/
|
|
2840
|
+
async exists(id) {
|
|
2841
|
+
if (!id) return false;
|
|
2842
|
+
const n = await this._collection.run(
|
|
2843
|
+
(collection) => {
|
|
2844
|
+
return collection.countDocuments({ _id: id });
|
|
2845
|
+
},
|
|
2846
|
+
this._token,
|
|
2847
|
+
this._region
|
|
2848
|
+
);
|
|
2849
|
+
return n > 0;
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Returns the number of documents in the collection matching `filter`.
|
|
2853
|
+
* Passing no filter (or an empty object) counts every document.
|
|
2978
2854
|
*
|
|
2979
|
-
* @
|
|
2980
|
-
*
|
|
2981
|
-
* // {
|
|
2982
|
-
* // token: 'abc123token',
|
|
2983
|
-
* // tokenLength: 11,
|
|
2984
|
-
* // isValid: true,
|
|
2985
|
-
* // expiry: '2024-01-01T12:00:00.000Z',
|
|
2986
|
-
* // timeUntilExpiry: 3600000
|
|
2987
|
-
* // }
|
|
2855
|
+
* @param filter - Optional query filter (default: `{}`)
|
|
2856
|
+
* @returns Total number of matching documents
|
|
2988
2857
|
*/
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2858
|
+
async count(filter = {}) {
|
|
2859
|
+
return this._collection.run(
|
|
2860
|
+
(collection) => {
|
|
2861
|
+
return collection.countDocuments(filter);
|
|
2862
|
+
},
|
|
2863
|
+
this._token,
|
|
2864
|
+
this._region
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
/**
|
|
2868
|
+
* Inserts or updates a document.
|
|
2869
|
+
*
|
|
2870
|
+
* - **Insert** (no `id`): stamps both `_created_at` and `_updated_at`, runs full schema
|
|
2871
|
+
* validation, then calls `insertOne`. Returns the new document's `_id`.
|
|
2872
|
+
* - **Update** (with `id`): stamps `_updated_at`, runs **partial** validation (only columns
|
|
2873
|
+
* present in the payload are checked — required fields that are absent are not enforced
|
|
2874
|
+
* because they already exist in the stored document), then calls `updateOne` with
|
|
2875
|
+
* `upsert: true`. Returns the same `id` that was passed in.
|
|
2876
|
+
*
|
|
2877
|
+
* @param payload - Document fields to write
|
|
2878
|
+
* @param id - When provided, updates the document with this `_id`; otherwise inserts
|
|
2879
|
+
* @returns The `_id` of the inserted or updated document
|
|
2880
|
+
*/
|
|
2881
|
+
async save(payload = {}, id = "") {
|
|
2882
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2883
|
+
if (id) {
|
|
2884
|
+
const updatePayload = { ...payload, _updated_at: now };
|
|
2885
|
+
this._validatePartial(updatePayload);
|
|
2886
|
+
await this._collection.run(
|
|
2887
|
+
async (collection) => {
|
|
2888
|
+
await collection.updateOne({ _id: id }, updatePayload, { upsert: true });
|
|
2889
|
+
},
|
|
2890
|
+
this._token,
|
|
2891
|
+
this._region
|
|
2892
|
+
);
|
|
2893
|
+
return id;
|
|
2995
2894
|
}
|
|
2996
|
-
|
|
2895
|
+
const insertPayload = { ...payload, _created_at: now, _updated_at: now };
|
|
2896
|
+
this._collection.validate(insertPayload);
|
|
2897
|
+
return this._collection.run(
|
|
2898
|
+
async (collection) => {
|
|
2899
|
+
return collection.insertOne(insertPayload);
|
|
2900
|
+
},
|
|
2901
|
+
this._token,
|
|
2902
|
+
this._region
|
|
2903
|
+
);
|
|
2997
2904
|
}
|
|
2998
2905
|
/**
|
|
2999
|
-
*
|
|
3000
|
-
* Supports both JWT tokens (with automatic expiry detection) and plain tokens (24h default expiry).
|
|
2906
|
+
* Deletes the document with the given `_id`. No-ops silently when `id` is empty.
|
|
3001
2907
|
*
|
|
3002
|
-
* @param
|
|
3003
|
-
|
|
2908
|
+
* @param id - The `_id` of the document to delete
|
|
2909
|
+
*/
|
|
2910
|
+
async delete(id = "") {
|
|
2911
|
+
if (!id) return;
|
|
2912
|
+
await this._collection.run(
|
|
2913
|
+
async (collection) => {
|
|
2914
|
+
await collection.deleteOne({ _id: id });
|
|
2915
|
+
},
|
|
2916
|
+
this._token,
|
|
2917
|
+
this._region
|
|
2918
|
+
);
|
|
2919
|
+
}
|
|
2920
|
+
/**
|
|
2921
|
+
* Inserts multiple documents in a single `insertMany` call.
|
|
2922
|
+
* Each payload is stamped with `_created_at` / `_updated_at` and validated
|
|
2923
|
+
* against the full collection schema before the bulk write is sent to the DB.
|
|
3004
2924
|
*
|
|
3005
|
-
* @
|
|
3006
|
-
*
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
2925
|
+
* @param payloads - Array of document payloads to insert
|
|
2926
|
+
* @returns Array of inserted `_id` values in insertion order
|
|
2927
|
+
*/
|
|
2928
|
+
async insertAll(payloads) {
|
|
2929
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2930
|
+
const insertPayloads = payloads.map((payload) => {
|
|
2931
|
+
const doc = { ...payload, _created_at: now, _updated_at: now };
|
|
2932
|
+
this._collection.validate(doc);
|
|
2933
|
+
return doc;
|
|
2934
|
+
});
|
|
2935
|
+
return this._collection.run(
|
|
2936
|
+
async (collection) => {
|
|
2937
|
+
return collection.insertMany(insertPayloads);
|
|
2938
|
+
},
|
|
2939
|
+
this._token,
|
|
2940
|
+
this._region
|
|
2941
|
+
);
|
|
2942
|
+
}
|
|
2943
|
+
/**
|
|
2944
|
+
* Applies a partial update to all documents matching `filter` using a single `updateMany` call.
|
|
2945
|
+
* Stamps `_updated_at` on every matched document and runs partial validation on the payload
|
|
2946
|
+
* (only the columns present in the payload are checked).
|
|
3015
2947
|
*
|
|
3016
|
-
* @
|
|
3017
|
-
*
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
2948
|
+
* @param payload - Fields to update on every matched document
|
|
2949
|
+
* @param filter - Query condition selecting which documents to update (default: `{}` — all documents)
|
|
2950
|
+
*/
|
|
2951
|
+
async updateAll(payload, filter = {}) {
|
|
2952
|
+
const updatePayload = { ...payload, _updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2953
|
+
this._validatePartial(updatePayload);
|
|
2954
|
+
await this._collection.run(
|
|
2955
|
+
async (collection) => {
|
|
2956
|
+
await collection.updateMany(filter, updatePayload);
|
|
2957
|
+
},
|
|
2958
|
+
this._token,
|
|
2959
|
+
this._region
|
|
2960
|
+
);
|
|
2961
|
+
}
|
|
2962
|
+
/**
|
|
2963
|
+
* Deletes all documents matching `filter` using a single `deleteMany` call.
|
|
2964
|
+
* Passing no filter (or an empty object) deletes every document in the collection.
|
|
3027
2965
|
*
|
|
3028
|
-
* @
|
|
3029
|
-
* // Null or invalid token
|
|
3030
|
-
* const nullTokenInfo = BearerToken.info(null);
|
|
3031
|
-
* // returns: {
|
|
3032
|
-
* // token: null,
|
|
3033
|
-
* // tokenLength: 0,
|
|
3034
|
-
* // isValid: false,
|
|
3035
|
-
* // expiry: null,
|
|
3036
|
-
* // timeUntilExpiry: null
|
|
3037
|
-
* // }
|
|
2966
|
+
* @param filter - Optional query filter (default: `{}`)
|
|
3038
2967
|
*/
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
};
|
|
2968
|
+
async deleteAll(filter = {}) {
|
|
2969
|
+
await this._collection.run(
|
|
2970
|
+
async (collection) => {
|
|
2971
|
+
await collection.deleteMany(filter);
|
|
2972
|
+
},
|
|
2973
|
+
this._token,
|
|
2974
|
+
this._region
|
|
2975
|
+
);
|
|
3048
2976
|
}
|
|
3049
2977
|
/**
|
|
3050
|
-
*
|
|
3051
|
-
*
|
|
3052
|
-
*
|
|
3053
|
-
*
|
|
3054
|
-
* @
|
|
2978
|
+
* Validates only the columns whose keys are present in `record`.
|
|
2979
|
+
* Required columns that are absent from the payload are intentionally skipped —
|
|
2980
|
+
* they already exist in the stored document and are not being changed.
|
|
2981
|
+
*
|
|
2982
|
+
* Used by the update path of {@link save} and {@link updateAll}.
|
|
3055
2983
|
*/
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
2984
|
+
_validatePartial(record) {
|
|
2985
|
+
for (const col of this._collection.getColumns()) {
|
|
2986
|
+
const key = col.getName();
|
|
2987
|
+
if (key in record) {
|
|
2988
|
+
col.validate(record[key]);
|
|
2989
|
+
}
|
|
3059
2990
|
}
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
2991
|
+
}
|
|
2992
|
+
};
|
|
2993
|
+
__name(_AbdbRepository, "AbdbRepository");
|
|
2994
|
+
var AbdbRepository = _AbdbRepository;
|
|
2995
|
+
var abdb_repository_default = AbdbRepository;
|
|
2996
|
+
|
|
2997
|
+
// src/framework/publish-event/index.ts
|
|
2998
|
+
import { Events } from "@adobe/aio-sdk";
|
|
2999
|
+
import { CloudEvent } from "cloudevents";
|
|
3000
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3001
|
+
|
|
3002
|
+
// src/framework/custom-logger/index.ts
|
|
3003
|
+
var _CustomLogger = class _CustomLogger {
|
|
3004
|
+
/**
|
|
3005
|
+
* @param logger - External logger instance (can be null)
|
|
3006
|
+
*/
|
|
3007
|
+
constructor(logger = null) {
|
|
3008
|
+
this.logger = logger;
|
|
3009
|
+
}
|
|
3010
|
+
/**
|
|
3011
|
+
* Log debug message if logger is available
|
|
3012
|
+
* @param message - Debug message to log
|
|
3013
|
+
* @param args - Additional arguments to pass to logger
|
|
3014
|
+
*/
|
|
3015
|
+
debug(message, ...args) {
|
|
3016
|
+
if (this.logger && typeof this.logger.debug === "function") {
|
|
3017
|
+
this.logger.debug(message, ...args);
|
|
3063
3018
|
}
|
|
3064
|
-
return true;
|
|
3065
3019
|
}
|
|
3066
3020
|
/**
|
|
3067
|
-
*
|
|
3068
|
-
* @
|
|
3069
|
-
* @param
|
|
3070
|
-
|
|
3021
|
+
* Log info message if logger is available
|
|
3022
|
+
* @param message - Info message to log
|
|
3023
|
+
* @param args - Additional arguments to pass to logger
|
|
3024
|
+
*/
|
|
3025
|
+
info(message, ...args) {
|
|
3026
|
+
if (this.logger && typeof this.logger.info === "function") {
|
|
3027
|
+
this.logger.info(message, ...args);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
/**
|
|
3031
|
+
* Log error message if logger is available
|
|
3032
|
+
* @param message - Error message to log
|
|
3033
|
+
* @param args - Additional arguments to pass to logger
|
|
3034
|
+
*/
|
|
3035
|
+
error(message, ...args) {
|
|
3036
|
+
if (this.logger && typeof this.logger.error === "function") {
|
|
3037
|
+
this.logger.error(message, ...args);
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
/**
|
|
3041
|
+
* Get the underlying logger instance
|
|
3042
|
+
* @returns the logger instance or null
|
|
3043
|
+
*/
|
|
3044
|
+
getLogger() {
|
|
3045
|
+
return this.logger;
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
__name(_CustomLogger, "CustomLogger");
|
|
3049
|
+
var CustomLogger = _CustomLogger;
|
|
3050
|
+
var custom_logger_default = CustomLogger;
|
|
3051
|
+
|
|
3052
|
+
// src/framework/publish-event/index.ts
|
|
3053
|
+
var _PublishEvent = class _PublishEvent {
|
|
3054
|
+
/**
|
|
3055
|
+
* Creates a new PublishEvent instance
|
|
3056
|
+
*
|
|
3057
|
+
* @param imsOrgId - Adobe IMS Organization ID
|
|
3058
|
+
* @param apiKey - Adobe API Key (Client ID)
|
|
3059
|
+
* @param accessToken - Adobe Access Token
|
|
3060
|
+
* @param logger - Optional logger instance
|
|
3061
|
+
*/
|
|
3062
|
+
constructor(imsOrgId, apiKey, accessToken, logger = null) {
|
|
3063
|
+
if (!imsOrgId?.trim()) {
|
|
3064
|
+
throw new Error("imsOrgId is required and cannot be empty");
|
|
3065
|
+
}
|
|
3066
|
+
if (!apiKey?.trim()) {
|
|
3067
|
+
throw new Error("apiKey is required and cannot be empty");
|
|
3068
|
+
}
|
|
3069
|
+
if (!accessToken?.trim()) {
|
|
3070
|
+
throw new Error("accessToken is required and cannot be empty");
|
|
3071
|
+
}
|
|
3072
|
+
this.imsOrgId = imsOrgId;
|
|
3073
|
+
this.apiKey = apiKey;
|
|
3074
|
+
this.accessToken = accessToken;
|
|
3075
|
+
this.customLogger = new custom_logger_default(logger);
|
|
3076
|
+
this.customLogger.debug("PublishEvent initialized with valid configuration");
|
|
3077
|
+
}
|
|
3078
|
+
/**
|
|
3079
|
+
* Publishes a CloudEvent to Adobe I/O Events
|
|
3080
|
+
*
|
|
3081
|
+
* @param providerId - The Adobe I/O Events provider ID
|
|
3082
|
+
* @param eventCode - The event type identifier (e.g., 'commerce.order.created')
|
|
3083
|
+
* @param payload - The event payload data
|
|
3084
|
+
* @param eventId - Optional custom event ID; if not provided, a UUID will be generated
|
|
3085
|
+
* @param subject - Optional subject for the event
|
|
3086
|
+
* @returns Promise<PublishEventResult> - The publish result
|
|
3087
|
+
*
|
|
3088
|
+
* @throws Error when providerId or eventCode is invalid or publishing fails
|
|
3089
|
+
*/
|
|
3090
|
+
async execute(providerId, eventCode, payload, eventId, subject) {
|
|
3091
|
+
try {
|
|
3092
|
+
if (!providerId?.trim()) {
|
|
3093
|
+
throw new Error("providerId is required and cannot be empty");
|
|
3094
|
+
}
|
|
3095
|
+
if (!eventCode?.trim()) {
|
|
3096
|
+
throw new Error("eventCode is required and cannot be empty");
|
|
3097
|
+
}
|
|
3098
|
+
if (payload === null || payload === void 0) {
|
|
3099
|
+
throw new Error("payload is required");
|
|
3100
|
+
}
|
|
3101
|
+
this.customLogger.info(`Publishing event to provider: ${providerId}`);
|
|
3102
|
+
eventId = eventId ?? uuidv4();
|
|
3103
|
+
const cloudEvent = new CloudEvent({
|
|
3104
|
+
id: eventId,
|
|
3105
|
+
source: `urn:uuid:${providerId}`,
|
|
3106
|
+
datacontenttype: "application/json",
|
|
3107
|
+
type: eventCode,
|
|
3108
|
+
data: payload,
|
|
3109
|
+
...subject && { subject }
|
|
3110
|
+
});
|
|
3111
|
+
this.customLogger.debug(`Constructed CloudEvent with ID: ${eventId}`);
|
|
3112
|
+
const eventsClient = await Events.init(this.imsOrgId, this.apiKey, this.accessToken);
|
|
3113
|
+
this.customLogger.debug("Adobe I/O Events client initialized successfully");
|
|
3114
|
+
await eventsClient.publishEvent(cloudEvent);
|
|
3115
|
+
const publishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3116
|
+
this.customLogger.info(`Event published successfully with ID: ${eventId}`);
|
|
3117
|
+
return {
|
|
3118
|
+
eventId,
|
|
3119
|
+
status: "published",
|
|
3120
|
+
publishedAt
|
|
3121
|
+
};
|
|
3122
|
+
} catch (error) {
|
|
3123
|
+
this.customLogger.error(`Failed to publish event: ${error.message}`);
|
|
3124
|
+
return {
|
|
3125
|
+
eventId: uuidv4(),
|
|
3126
|
+
// Generate ID for tracking even failed events
|
|
3127
|
+
status: "failed",
|
|
3128
|
+
publishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3129
|
+
error: error.message
|
|
3130
|
+
};
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
__name(_PublishEvent, "PublishEvent");
|
|
3135
|
+
var PublishEvent = _PublishEvent;
|
|
3136
|
+
var publish_event_default = PublishEvent;
|
|
3137
|
+
|
|
3138
|
+
// src/framework/webhook-action/response/types.ts
|
|
3139
|
+
var WebhookActionOperation = /* @__PURE__ */ ((WebhookActionOperation2) => {
|
|
3140
|
+
WebhookActionOperation2["SUCCESS"] = "success";
|
|
3141
|
+
WebhookActionOperation2["EXCEPTION"] = "exception";
|
|
3142
|
+
WebhookActionOperation2["ADD"] = "add";
|
|
3143
|
+
WebhookActionOperation2["REPLACE"] = "replace";
|
|
3144
|
+
WebhookActionOperation2["REMOVE"] = "remove";
|
|
3145
|
+
return WebhookActionOperation2;
|
|
3146
|
+
})(WebhookActionOperation || {});
|
|
3147
|
+
|
|
3148
|
+
// src/framework/webhook-action/response/index.ts
|
|
3149
|
+
var _WebhookActionResponse = class _WebhookActionResponse {
|
|
3150
|
+
/**
|
|
3151
|
+
* Creates a success response indicating the webhook was processed successfully.
|
|
3152
|
+
*
|
|
3153
|
+
* Use this method when the webhook has been processed without errors and
|
|
3154
|
+
* no modifications to the payload are needed.
|
|
3155
|
+
*
|
|
3156
|
+
* @returns A success response object
|
|
3157
|
+
*
|
|
3158
|
+
* @example
|
|
3159
|
+
* ```typescript
|
|
3160
|
+
* const handler = WebhookAction.execute('process-order', [], [], async (params) => {
|
|
3161
|
+
* // Process the order...
|
|
3162
|
+
* await processOrder(params.order);
|
|
3163
|
+
*
|
|
3164
|
+
* // Return success
|
|
3165
|
+
* return {
|
|
3166
|
+
* statusCode: 200,
|
|
3167
|
+
* body: WebhookActionResponse.success()
|
|
3168
|
+
* };
|
|
3169
|
+
* });
|
|
3170
|
+
* ```
|
|
3171
|
+
*/
|
|
3172
|
+
static success() {
|
|
3173
|
+
return {
|
|
3174
|
+
op: "success" /* SUCCESS */
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
/**
|
|
3178
|
+
* Creates an exception response to report an error during webhook processing.
|
|
3179
|
+
*
|
|
3180
|
+
* Use this method to notify Adobe Commerce that an error occurred while
|
|
3181
|
+
* processing the webhook. This helps with debugging and error tracking.
|
|
3182
|
+
*
|
|
3183
|
+
* @param message - Optional error message describing what went wrong
|
|
3184
|
+
* @param exceptionClass - Optional exception class name for categorization (e.g., 'Magento\\Framework\\Exception\\LocalizedException')
|
|
3185
|
+
* @returns An exception response object
|
|
3186
|
+
*
|
|
3187
|
+
* @example
|
|
3188
|
+
* ```typescript
|
|
3189
|
+
* const handler = WebhookAction.execute('validate-product', [], [], async (params) => {
|
|
3190
|
+
* const product = await findProduct(params.sku);
|
|
3191
|
+
*
|
|
3192
|
+
* if (!product) {
|
|
3193
|
+
* return {
|
|
3194
|
+
* statusCode: 404,
|
|
3195
|
+
* body: WebhookActionResponse.exception(
|
|
3196
|
+
* `Product with SKU ${params.sku} not found`,
|
|
3197
|
+
* 'Magento\\Framework\\Exception\\NoSuchEntityException'
|
|
3198
|
+
* )
|
|
3199
|
+
* };
|
|
3200
|
+
* }
|
|
3201
|
+
*
|
|
3202
|
+
* return { statusCode: 200, body: WebhookActionResponse.success() };
|
|
3203
|
+
* });
|
|
3204
|
+
* ```
|
|
3071
3205
|
*/
|
|
3072
|
-
static
|
|
3073
|
-
|
|
3074
|
-
|
|
3206
|
+
static exception(message, exceptionClass) {
|
|
3207
|
+
const response = {
|
|
3208
|
+
op: "exception" /* EXCEPTION */
|
|
3209
|
+
};
|
|
3210
|
+
if (message !== void 0) {
|
|
3211
|
+
response.message = message;
|
|
3075
3212
|
}
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
if (parts.length === 3) {
|
|
3079
|
-
const payload = JSON.parse(Buffer.from(parts[1] || "", "base64").toString());
|
|
3080
|
-
if (payload.expires_in) {
|
|
3081
|
-
return new Date(Date.now() + parseInt(payload.expires_in));
|
|
3082
|
-
}
|
|
3083
|
-
if (payload.exp) {
|
|
3084
|
-
return new Date(payload.exp * 1e3);
|
|
3085
|
-
}
|
|
3086
|
-
}
|
|
3087
|
-
return new Date(Date.now() + 24 * 60 * 60 * 1e3);
|
|
3088
|
-
} catch (error) {
|
|
3089
|
-
console.warn("[WARN] Could not parse token expiry, using default 24h");
|
|
3090
|
-
return new Date(Date.now() + 24 * 60 * 60 * 1e3);
|
|
3213
|
+
if (exceptionClass !== void 0) {
|
|
3214
|
+
response.class = exceptionClass;
|
|
3091
3215
|
}
|
|
3216
|
+
return response;
|
|
3092
3217
|
}
|
|
3093
|
-
};
|
|
3094
|
-
__name(_BearerToken, "BearerToken");
|
|
3095
|
-
var BearerToken = _BearerToken;
|
|
3096
|
-
var bearer_token_default = BearerToken;
|
|
3097
|
-
|
|
3098
|
-
// src/framework/ims-token/index.ts
|
|
3099
|
-
var _ImsToken = class _ImsToken {
|
|
3100
3218
|
/**
|
|
3101
|
-
* Creates
|
|
3219
|
+
* Creates a response to add new data to the webhook payload.
|
|
3102
3220
|
*
|
|
3103
|
-
*
|
|
3104
|
-
*
|
|
3221
|
+
* Use this method to inject additional data into the webhook payload that
|
|
3222
|
+
* will be processed by Adobe Commerce. The data is added at the specified
|
|
3223
|
+
* path using dot notation.
|
|
3105
3224
|
*
|
|
3106
|
-
* @param
|
|
3107
|
-
* @param
|
|
3108
|
-
* @param
|
|
3109
|
-
* @
|
|
3110
|
-
*
|
|
3111
|
-
* @
|
|
3112
|
-
*
|
|
3113
|
-
*
|
|
3114
|
-
*
|
|
3225
|
+
* @param path - Dot-notation path where the value should be added (e.g., 'order.items', 'customer.addresses')
|
|
3226
|
+
* @param value - The value to add at the specified path
|
|
3227
|
+
* @param instance - Optional instance identifier for tracking or reference purposes
|
|
3228
|
+
* @returns An add response object
|
|
3229
|
+
*
|
|
3230
|
+
* @example
|
|
3231
|
+
* ```typescript
|
|
3232
|
+
* const handler = WebhookAction.execute('enrich-order', [], [], async (params) => {
|
|
3233
|
+
* // Add loyalty points to the order
|
|
3234
|
+
* return {
|
|
3235
|
+
* statusCode: 200,
|
|
3236
|
+
* body: WebhookActionResponse.add(
|
|
3237
|
+
* 'order.loyalty',
|
|
3238
|
+
* { points: 150, tier: 'gold' },
|
|
3239
|
+
* params.order.id
|
|
3240
|
+
* )
|
|
3241
|
+
* };
|
|
3242
|
+
* });
|
|
3243
|
+
* ```
|
|
3115
3244
|
*/
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
this.key = cacheKey || "ims_token";
|
|
3127
|
-
this.tokenContext = tokenContext || "ims-context";
|
|
3245
|
+
static add(path, value, instance) {
|
|
3246
|
+
const response = {
|
|
3247
|
+
op: "add" /* ADD */,
|
|
3248
|
+
path,
|
|
3249
|
+
value
|
|
3250
|
+
};
|
|
3251
|
+
if (instance !== void 0) {
|
|
3252
|
+
response.instance = instance;
|
|
3253
|
+
}
|
|
3254
|
+
return response;
|
|
3128
3255
|
}
|
|
3129
3256
|
/**
|
|
3130
|
-
*
|
|
3257
|
+
* Creates a response to replace existing data in the webhook payload.
|
|
3131
3258
|
*
|
|
3132
|
-
*
|
|
3133
|
-
* The
|
|
3134
|
-
* See class documentation for migration examples.
|
|
3259
|
+
* Use this method to modify existing fields in the webhook payload.
|
|
3260
|
+
* The existing value at the specified path will be replaced with the new value.
|
|
3135
3261
|
*
|
|
3136
|
-
*
|
|
3137
|
-
*
|
|
3262
|
+
* @param path - Dot-notation path to the field that should be replaced (e.g., 'product.price', 'order.status')
|
|
3263
|
+
* @param value - The new value to replace the existing value
|
|
3264
|
+
* @param instance - Optional instance identifier for tracking or reference purposes
|
|
3265
|
+
* @returns A replace response object
|
|
3138
3266
|
*
|
|
3139
|
-
* @returns A promise that resolves to the IMS token string or null if generation fails
|
|
3140
3267
|
* @example
|
|
3141
3268
|
* ```typescript
|
|
3142
|
-
* const
|
|
3143
|
-
*
|
|
3144
|
-
*
|
|
3145
|
-
*
|
|
3269
|
+
* const handler = WebhookAction.execute('adjust-price', [], [], async (params) => {
|
|
3270
|
+
* // Apply dynamic pricing
|
|
3271
|
+
* const newPrice = await calculateDiscountedPrice(params.product.price);
|
|
3272
|
+
*
|
|
3273
|
+
* return {
|
|
3274
|
+
* statusCode: 200,
|
|
3275
|
+
* body: WebhookActionResponse.replace(
|
|
3276
|
+
* 'product.price',
|
|
3277
|
+
* newPrice,
|
|
3278
|
+
* params.product.id
|
|
3279
|
+
* )
|
|
3280
|
+
* };
|
|
3281
|
+
* });
|
|
3146
3282
|
* ```
|
|
3147
3283
|
*/
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
this.customLogger.info("No cached token found, generating new IMS token");
|
|
3157
|
-
let result = {
|
|
3158
|
-
token: null,
|
|
3159
|
-
expire_in: 86399
|
|
3160
|
-
// Default fallback, will be overridden by actual token expiry
|
|
3161
|
-
};
|
|
3162
|
-
const response = await this.getImsToken();
|
|
3163
|
-
if (response !== null) {
|
|
3164
|
-
result = response;
|
|
3165
|
-
}
|
|
3166
|
-
if (result.token !== null) {
|
|
3167
|
-
this.customLogger.info(`Generated new IMS token, caching for ${result.expire_in} seconds`);
|
|
3168
|
-
await this.setValue(result);
|
|
3169
|
-
}
|
|
3170
|
-
return result.token;
|
|
3171
|
-
} catch (error) {
|
|
3172
|
-
this.customLogger.error(`Failed to execute IMS token generation: ${error.message}`);
|
|
3173
|
-
return null;
|
|
3284
|
+
static replace(path, value, instance) {
|
|
3285
|
+
const response = {
|
|
3286
|
+
op: "replace" /* REPLACE */,
|
|
3287
|
+
path,
|
|
3288
|
+
value
|
|
3289
|
+
};
|
|
3290
|
+
if (instance !== void 0) {
|
|
3291
|
+
response.instance = instance;
|
|
3174
3292
|
}
|
|
3293
|
+
return response;
|
|
3175
3294
|
}
|
|
3176
3295
|
/**
|
|
3177
|
-
*
|
|
3296
|
+
* Creates a response to remove data from the webhook payload.
|
|
3178
3297
|
*
|
|
3179
|
-
*
|
|
3180
|
-
*
|
|
3298
|
+
* Use this method to remove fields from the webhook payload before it's
|
|
3299
|
+
* processed by Adobe Commerce. This is useful for filtering sensitive data
|
|
3300
|
+
* or removing unnecessary information.
|
|
3301
|
+
*
|
|
3302
|
+
* @param path - Dot-notation path to the field that should be removed (e.g., 'items.0', 'customer.internal_notes')
|
|
3303
|
+
* @returns A remove response object
|
|
3304
|
+
*
|
|
3305
|
+
* @example
|
|
3306
|
+
* ```typescript
|
|
3307
|
+
* const handler = WebhookAction.execute('sanitize-customer', [], [], async (params) => {
|
|
3308
|
+
* // Remove internal notes before processing
|
|
3309
|
+
* return {
|
|
3310
|
+
* statusCode: 200,
|
|
3311
|
+
* body: WebhookActionResponse.remove('customer.internal_notes')
|
|
3312
|
+
* };
|
|
3313
|
+
* });
|
|
3314
|
+
* ```
|
|
3315
|
+
*
|
|
3316
|
+
* @example
|
|
3317
|
+
* ```typescript
|
|
3318
|
+
* // Remove an item from an array
|
|
3319
|
+
* return {
|
|
3320
|
+
* statusCode: 200,
|
|
3321
|
+
* body: WebhookActionResponse.remove('order.items.2')
|
|
3322
|
+
* };
|
|
3323
|
+
* ```
|
|
3181
3324
|
*/
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
this.clientSecret,
|
|
3188
|
-
this.technicalAccountId,
|
|
3189
|
-
this.technicalAccountEmail,
|
|
3190
|
-
this.imsOrgId,
|
|
3191
|
-
this.scopes,
|
|
3192
|
-
this.tokenContext
|
|
3193
|
-
);
|
|
3194
|
-
if (token !== null && token !== void 0) {
|
|
3195
|
-
this.customLogger.debug("Received token from AdobeAuth, parsing with BearerToken.info");
|
|
3196
|
-
const tokenInfo = bearer_token_default.info(token);
|
|
3197
|
-
if (!tokenInfo.isValid) {
|
|
3198
|
-
this.customLogger.error("Received invalid or expired token from IMS");
|
|
3199
|
-
return null;
|
|
3200
|
-
}
|
|
3201
|
-
const expireInSeconds = tokenInfo.timeUntilExpiry ? Math.floor(tokenInfo.timeUntilExpiry / 1e3) : 86399;
|
|
3202
|
-
this.customLogger.debug(`Token expires in ${expireInSeconds} seconds`);
|
|
3203
|
-
return {
|
|
3204
|
-
token,
|
|
3205
|
-
expire_in: expireInSeconds
|
|
3206
|
-
};
|
|
3207
|
-
}
|
|
3208
|
-
this.customLogger.error("Received null or undefined token from IMS");
|
|
3209
|
-
return null;
|
|
3210
|
-
} catch (error) {
|
|
3211
|
-
this.customLogger.error(`Failed to get IMS token: ${error.message}`);
|
|
3212
|
-
return null;
|
|
3213
|
-
}
|
|
3325
|
+
static remove(path) {
|
|
3326
|
+
return {
|
|
3327
|
+
op: "remove" /* REMOVE */,
|
|
3328
|
+
path
|
|
3329
|
+
};
|
|
3214
3330
|
}
|
|
3331
|
+
};
|
|
3332
|
+
__name(_WebhookActionResponse, "WebhookActionResponse");
|
|
3333
|
+
var WebhookActionResponse = _WebhookActionResponse;
|
|
3334
|
+
var response_default2 = WebhookActionResponse;
|
|
3335
|
+
|
|
3336
|
+
// src/framework/webhook-action/types.ts
|
|
3337
|
+
var SignatureVerification = /* @__PURE__ */ ((SignatureVerification2) => {
|
|
3338
|
+
SignatureVerification2["ENABLED"] = "enabled";
|
|
3339
|
+
SignatureVerification2["DISABLED"] = "disabled";
|
|
3340
|
+
return SignatureVerification2;
|
|
3341
|
+
})(SignatureVerification || {});
|
|
3342
|
+
|
|
3343
|
+
// src/framework/webhook-action/index.ts
|
|
3344
|
+
import crypto from "crypto";
|
|
3345
|
+
var _WebhookAction = class _WebhookAction {
|
|
3215
3346
|
/**
|
|
3216
|
-
*
|
|
3217
|
-
*
|
|
3218
|
-
* @deprecated This method has issues with State API reliability. Use application-level caching instead.
|
|
3219
|
-
*
|
|
3220
|
-
* **Known Issues:**
|
|
3221
|
-
* - State API may not be available in all runtime environments
|
|
3222
|
-
* - Hard-coded 10-minute buffer may not suit all use cases
|
|
3223
|
-
* - Minimum 60-minute TTL is opinionated and inflexible
|
|
3224
|
-
* - No retry mechanism for State API failures
|
|
3347
|
+
* Execute a webhook action with validation and response handling.
|
|
3225
3348
|
*
|
|
3226
|
-
* @param
|
|
3227
|
-
* @
|
|
3228
|
-
* @
|
|
3349
|
+
* @param name - Name of the webhook action
|
|
3350
|
+
* @param requiredParams - Required parameters in the webhook payload
|
|
3351
|
+
* @param requiredHeaders - Required headers (e.g., signature headers)
|
|
3352
|
+
* @param signatureVerification - Enable/disable signature verification
|
|
3353
|
+
* @param action - Webhook action function returning WebhookActionResponse
|
|
3354
|
+
* @returns Function that handles the webhook HTTP request
|
|
3229
3355
|
*/
|
|
3230
|
-
async
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3356
|
+
static execute(name = "webhook", requiredParams = [], requiredHeaders = [], signatureVerification = "disabled" /* DISABLED */, action = async () => response_default2.success()) {
|
|
3357
|
+
const httpMethods = ["POST" /* POST */];
|
|
3358
|
+
const callback = /* @__PURE__ */ __name(async (params, ctx) => {
|
|
3359
|
+
const { logger, telemetry } = ctx;
|
|
3360
|
+
if (signatureVerification === "enabled" /* ENABLED */) {
|
|
3361
|
+
const verificationErrorMessage = await _WebhookAction.verifySignatureWithInstrumentation(
|
|
3362
|
+
name,
|
|
3363
|
+
params,
|
|
3364
|
+
telemetry
|
|
3365
|
+
);
|
|
3366
|
+
if (verificationErrorMessage) {
|
|
3367
|
+
logger.error({
|
|
3368
|
+
message: "Webhook action signature verification failed",
|
|
3369
|
+
error: verificationErrorMessage
|
|
3370
|
+
});
|
|
3371
|
+
const verificationErrorResponse = response_default2.exception(verificationErrorMessage);
|
|
3372
|
+
return response_default.success(JSON.stringify(verificationErrorResponse));
|
|
3373
|
+
}
|
|
3374
|
+
params = {
|
|
3375
|
+
...params,
|
|
3376
|
+
...JSON.parse(atob(params.__ow_body))
|
|
3377
|
+
};
|
|
3236
3378
|
}
|
|
3237
|
-
const
|
|
3238
|
-
|
|
3239
|
-
|
|
3379
|
+
const errorMessage = _WebhookAction.validateWithInstrumentation(
|
|
3380
|
+
name,
|
|
3381
|
+
params,
|
|
3382
|
+
requiredParams,
|
|
3383
|
+
requiredHeaders,
|
|
3384
|
+
telemetry
|
|
3240
3385
|
);
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3386
|
+
if (errorMessage) {
|
|
3387
|
+
logger.error({
|
|
3388
|
+
message: "Webhook action validation failed",
|
|
3389
|
+
error: errorMessage
|
|
3390
|
+
});
|
|
3391
|
+
const errorMessageResponse = response_default2.exception(errorMessage);
|
|
3392
|
+
return response_default.success(JSON.stringify(errorMessageResponse));
|
|
3393
|
+
}
|
|
3394
|
+
const response = await _WebhookAction.executeActionWithInstrumentation(
|
|
3395
|
+
name,
|
|
3396
|
+
action,
|
|
3397
|
+
params,
|
|
3398
|
+
ctx
|
|
3399
|
+
);
|
|
3400
|
+
return response_default.success(JSON.stringify(response));
|
|
3401
|
+
}, "callback");
|
|
3402
|
+
runtime_action_default.setActionType("webhook-action");
|
|
3403
|
+
runtime_action_default.setActionTypeName("Webhook action");
|
|
3404
|
+
return runtime_action_default.execute(name, httpMethods, [], [], callback);
|
|
3247
3405
|
}
|
|
3248
3406
|
/**
|
|
3249
|
-
*
|
|
3407
|
+
* Executes webhook action with OpenTelemetry instrumentation
|
|
3250
3408
|
*
|
|
3251
|
-
*
|
|
3409
|
+
* This method wraps the webhook action execution with distributed tracing instrumentation,
|
|
3410
|
+
* creating a span that tracks execution metrics and results in New Relic.
|
|
3252
3411
|
*
|
|
3253
|
-
*
|
|
3254
|
-
*
|
|
3255
|
-
*
|
|
3256
|
-
*
|
|
3412
|
+
* @param name - Webhook action name used for span naming
|
|
3413
|
+
* @param action - The webhook action function to execute
|
|
3414
|
+
* @param params - Request parameters
|
|
3415
|
+
* @param ctx - Context object with logger, headers, and telemetry
|
|
3416
|
+
* @returns Promise resolving to the webhook action response(s)
|
|
3257
3417
|
*
|
|
3258
|
-
* @returns A promise that resolves to the cached token string or null if not found
|
|
3259
3418
|
* @private
|
|
3260
3419
|
*/
|
|
3261
|
-
async
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3420
|
+
static async executeActionWithInstrumentation(name, action, params, ctx) {
|
|
3421
|
+
const instrumentedWebhookAction = ctx.telemetry.instrument(
|
|
3422
|
+
`webhook.action.${name}.execute`,
|
|
3423
|
+
async (actionName, actionFn, actionParams, context2) => {
|
|
3424
|
+
const span = context2.telemetry.getCurrentSpan();
|
|
3425
|
+
if (span) {
|
|
3426
|
+
span.setAttribute("webhook.action.name", actionName);
|
|
3427
|
+
span.setAttribute("webhook.action.has_headers", !!context2.headers);
|
|
3428
|
+
span.addEvent("webhook-execution-started");
|
|
3429
|
+
}
|
|
3430
|
+
const response = await actionFn(actionParams, context2);
|
|
3431
|
+
if (span) {
|
|
3432
|
+
span.setAttribute("webhook.action.response_is_array", Array.isArray(response));
|
|
3433
|
+
if (Array.isArray(response)) {
|
|
3434
|
+
span.setAttribute("webhook.action.response_count", response.length);
|
|
3435
|
+
}
|
|
3436
|
+
span.addEvent("webhook-execution-completed", {
|
|
3437
|
+
isArray: Array.isArray(response),
|
|
3438
|
+
count: Array.isArray(response) ? response.length : 1
|
|
3439
|
+
});
|
|
3440
|
+
}
|
|
3441
|
+
return response;
|
|
3273
3442
|
}
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
this.customLogger.error(`Failed to retrieve cached IMS token: ${error.message}`);
|
|
3277
|
-
}
|
|
3278
|
-
return null;
|
|
3443
|
+
);
|
|
3444
|
+
return instrumentedWebhookAction(name, action, params, ctx);
|
|
3279
3445
|
}
|
|
3280
3446
|
/**
|
|
3281
|
-
*
|
|
3447
|
+
* Verifies webhook signature with OpenTelemetry instrumentation
|
|
3282
3448
|
*
|
|
3283
|
-
*
|
|
3284
|
-
*
|
|
3449
|
+
* This method wraps the signature verification logic with distributed tracing instrumentation,
|
|
3450
|
+
* creating a span that tracks verification metrics and results in New Relic.
|
|
3285
3451
|
*
|
|
3286
|
-
*
|
|
3287
|
-
* -
|
|
3288
|
-
*
|
|
3289
|
-
*
|
|
3452
|
+
* @param name - Webhook action name used for span naming
|
|
3453
|
+
* @param params - Request parameters including headers, body, and public key
|
|
3454
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
3455
|
+
* @returns Error message if verification fails, null if successful
|
|
3290
3456
|
*
|
|
3291
|
-
* @returns A promise that resolves to the state instance or null if initialization fails
|
|
3292
3457
|
* @private
|
|
3293
3458
|
*/
|
|
3294
|
-
async
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3459
|
+
static async verifySignatureWithInstrumentation(name, params, telemetry) {
|
|
3460
|
+
const instrumentedVerifySignature = telemetry.instrument(
|
|
3461
|
+
`webhook.action.${name}.verify-signature`,
|
|
3462
|
+
async (actionName, actionParams, actionTelemetry) => {
|
|
3463
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
3464
|
+
if (span) {
|
|
3465
|
+
span.setAttribute("webhook.signature.enabled", true);
|
|
3466
|
+
span.setAttribute(
|
|
3467
|
+
"webhook.signature.header_present",
|
|
3468
|
+
!!actionParams.__ow_headers?.["x-adobe-commerce-webhook-signature"]
|
|
3469
|
+
);
|
|
3470
|
+
span.setAttribute("webhook.signature.has_body", !!actionParams.__ow_body);
|
|
3471
|
+
span.setAttribute(
|
|
3472
|
+
"webhook.signature.has_public_key",
|
|
3473
|
+
!!(actionParams.PUBLIC_KEY || actionParams.PUBLIC_KEY_BASE64)
|
|
3474
|
+
);
|
|
3475
|
+
span.addEvent("signature-verification-started");
|
|
3476
|
+
}
|
|
3477
|
+
const verificationError = await _WebhookAction.verifySignature(actionParams);
|
|
3478
|
+
if (span) {
|
|
3479
|
+
span.setAttribute("webhook.signature.valid", verificationError === null);
|
|
3480
|
+
if (verificationError) {
|
|
3481
|
+
span.setAttribute("webhook.signature.error", verificationError);
|
|
3482
|
+
}
|
|
3483
|
+
span.addEvent("signature-verification-completed", {
|
|
3484
|
+
valid: verificationError === null
|
|
3485
|
+
});
|
|
3486
|
+
}
|
|
3487
|
+
return verificationError;
|
|
3302
3488
|
}
|
|
3303
|
-
|
|
3304
|
-
return
|
|
3489
|
+
);
|
|
3490
|
+
return instrumentedVerifySignature(name, params, telemetry);
|
|
3305
3491
|
}
|
|
3306
|
-
};
|
|
3307
|
-
__name(_ImsToken, "ImsToken");
|
|
3308
|
-
var ImsToken = _ImsToken;
|
|
3309
|
-
var ims_token_default = ImsToken;
|
|
3310
|
-
|
|
3311
|
-
// src/integration/rest-client/index.ts
|
|
3312
|
-
import fetch from "node-fetch";
|
|
3313
|
-
var _RestClient = class _RestClient {
|
|
3314
3492
|
/**
|
|
3315
|
-
*
|
|
3316
|
-
*
|
|
3317
|
-
*
|
|
3318
|
-
*
|
|
3319
|
-
*
|
|
3320
|
-
* @param
|
|
3321
|
-
* @
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3493
|
+
* Validates webhook parameters and headers with OpenTelemetry instrumentation
|
|
3494
|
+
*
|
|
3495
|
+
* This method wraps the validation logic with distributed tracing instrumentation,
|
|
3496
|
+
* creating a span that tracks validation metrics and results in New Relic.
|
|
3497
|
+
*
|
|
3498
|
+
* @param name - Webhook action name used for span naming
|
|
3499
|
+
* @param params - Request parameters
|
|
3500
|
+
* @param requiredParams - List of required parameter names to validate
|
|
3501
|
+
* @param requiredHeaders - List of required header names to validate
|
|
3502
|
+
* @param telemetry - Telemetry instance for instrumentation
|
|
3503
|
+
* @returns Error message if validation fails, empty string if successful
|
|
3504
|
+
*
|
|
3505
|
+
* @private
|
|
3506
|
+
*/
|
|
3507
|
+
static validateWithInstrumentation(name, params, requiredParams, requiredHeaders, telemetry) {
|
|
3508
|
+
const instrumentedValidate = telemetry.instrument(
|
|
3509
|
+
`webhook.action.${name}.validate`,
|
|
3510
|
+
(actionName, actionParams, actionRequiredParams, actionRequiredHeaders, actionTelemetry) => {
|
|
3511
|
+
const span = actionTelemetry.getCurrentSpan();
|
|
3512
|
+
if (span) {
|
|
3513
|
+
span.setAttribute("webhook.validation.required_params", actionRequiredParams.join(","));
|
|
3514
|
+
span.setAttribute("webhook.validation.required_headers", actionRequiredHeaders.join(","));
|
|
3515
|
+
}
|
|
3516
|
+
const errorMessage = validator_default.checkMissingRequestInputs(
|
|
3517
|
+
actionParams,
|
|
3518
|
+
actionRequiredParams,
|
|
3519
|
+
actionRequiredHeaders
|
|
3520
|
+
) ?? "";
|
|
3521
|
+
if (span) {
|
|
3522
|
+
span.setAttribute("webhook.validation.passed", errorMessage === "");
|
|
3523
|
+
if (errorMessage) {
|
|
3524
|
+
span.setAttribute("webhook.validation.error", errorMessage);
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
return errorMessage;
|
|
3350
3528
|
}
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
body,
|
|
3354
|
-
headers: requestHeaders
|
|
3355
|
-
};
|
|
3356
|
-
}
|
|
3357
|
-
return await fetch(endpoint, options);
|
|
3529
|
+
);
|
|
3530
|
+
return instrumentedValidate(name, params, requiredParams, requiredHeaders, telemetry);
|
|
3358
3531
|
}
|
|
3359
3532
|
/**
|
|
3360
|
-
*
|
|
3533
|
+
* Verify webhook signature using a public key and SHA256
|
|
3361
3534
|
*
|
|
3362
|
-
*
|
|
3363
|
-
*
|
|
3535
|
+
* This method validates the authenticity of webhook requests by verifying
|
|
3536
|
+
* the signature against the request body using the provided public key.
|
|
3537
|
+
*
|
|
3538
|
+
* @param params - Request parameters including headers, body, and public key
|
|
3539
|
+
* @returns Error message if verification fails, null if successful
|
|
3540
|
+
*
|
|
3541
|
+
* @private
|
|
3364
3542
|
*/
|
|
3365
|
-
async
|
|
3366
|
-
|
|
3367
|
-
|
|
3543
|
+
static async verifySignature(params) {
|
|
3544
|
+
const signature = params.__ow_headers["x-adobe-commerce-webhook-signature"] || "";
|
|
3545
|
+
if (!signature) {
|
|
3546
|
+
return "Header `x-adobe-commerce-webhook-signature` not found. Make sure Webhooks signature is enabled in the Commerce instance.";
|
|
3368
3547
|
}
|
|
3369
|
-
|
|
3370
|
-
|
|
3548
|
+
const body = params.__ow_body;
|
|
3549
|
+
if (!body) {
|
|
3550
|
+
return "Request body not found. Make sure the action is configured with `raw-http: true`.";
|
|
3371
3551
|
}
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
return await response.json();
|
|
3376
|
-
}
|
|
3552
|
+
let publicKey = params.PUBLIC_KEY;
|
|
3553
|
+
if (!publicKey && params.PUBLIC_KEY_BASE64) {
|
|
3554
|
+
publicKey = atob(params.PUBLIC_KEY_BASE64);
|
|
3377
3555
|
}
|
|
3378
|
-
if (
|
|
3379
|
-
|
|
3380
|
-
|
|
3556
|
+
if (!publicKey) {
|
|
3557
|
+
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.";
|
|
3558
|
+
}
|
|
3559
|
+
try {
|
|
3560
|
+
const verifier = crypto.createVerify("SHA256");
|
|
3561
|
+
verifier.update(body);
|
|
3562
|
+
const isSignatureValid = verifier.verify(publicKey, signature, "base64");
|
|
3563
|
+
if (!isSignatureValid) {
|
|
3564
|
+
return "The signature is invalid.";
|
|
3565
|
+
}
|
|
3566
|
+
} catch (error) {
|
|
3567
|
+
return "The signature is invalid.";
|
|
3381
3568
|
}
|
|
3382
3569
|
return null;
|
|
3383
3570
|
}
|
|
3571
|
+
};
|
|
3572
|
+
__name(_WebhookAction, "WebhookAction");
|
|
3573
|
+
var WebhookAction = _WebhookAction;
|
|
3574
|
+
var webhook_action_default = WebhookAction;
|
|
3575
|
+
|
|
3576
|
+
// src/framework/ims-token/index.ts
|
|
3577
|
+
import { State } from "@adobe/aio-sdk";
|
|
3578
|
+
|
|
3579
|
+
// src/commerce/adobe-auth/index.ts
|
|
3580
|
+
import { context, getToken } from "@adobe/aio-lib-ims";
|
|
3581
|
+
var _AdobeAuth = class _AdobeAuth {
|
|
3384
3582
|
/**
|
|
3385
|
-
*
|
|
3583
|
+
* Retrieves an authentication token from Adobe IMS
|
|
3386
3584
|
*
|
|
3387
|
-
* @param
|
|
3388
|
-
* @param
|
|
3389
|
-
* @param
|
|
3390
|
-
* @
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
}
|
|
3396
|
-
/**
|
|
3397
|
-
* A generic method to make POST rest call
|
|
3585
|
+
* @param clientId - The client ID for the Adobe IMS integration
|
|
3586
|
+
* @param clientSecret - The client secret for the Adobe IMS integration
|
|
3587
|
+
* @param technicalAccountId - The technical account ID for the Adobe IMS integration
|
|
3588
|
+
* @param technicalAccountEmail - The technical account email for the Adobe IMS integration
|
|
3589
|
+
* @param imsOrgId - The IMS organization ID
|
|
3590
|
+
* @param scopes - Array of permission scopes to request for the token
|
|
3591
|
+
* @param currentContext - The context name for storing the configuration (defaults to 'onboarding-config')
|
|
3592
|
+
* @returns Promise<string> - A promise that resolves to the authentication token
|
|
3398
3593
|
*
|
|
3399
|
-
* @
|
|
3400
|
-
*
|
|
3401
|
-
*
|
|
3402
|
-
*
|
|
3403
|
-
*
|
|
3594
|
+
* @example
|
|
3595
|
+
* const token = await AdobeAuth.getToken(
|
|
3596
|
+
* 'your-client-id',
|
|
3597
|
+
* 'your-client-secret',
|
|
3598
|
+
* 'your-technical-account-id',
|
|
3599
|
+
* 'your-technical-account-email',
|
|
3600
|
+
* 'your-ims-org-id',
|
|
3601
|
+
* ['AdobeID', 'openid', 'adobeio_api']
|
|
3602
|
+
* );
|
|
3404
3603
|
*/
|
|
3405
|
-
async
|
|
3406
|
-
const
|
|
3407
|
-
|
|
3604
|
+
static async getToken(clientId, clientSecret, technicalAccountId, technicalAccountEmail, imsOrgId, scopes, currentContext = "onboarding-config") {
|
|
3605
|
+
const config = {
|
|
3606
|
+
client_id: clientId,
|
|
3607
|
+
client_secrets: [clientSecret],
|
|
3608
|
+
technical_account_id: technicalAccountId,
|
|
3609
|
+
technical_account_email: technicalAccountEmail,
|
|
3610
|
+
ims_org_id: imsOrgId,
|
|
3611
|
+
scopes
|
|
3612
|
+
};
|
|
3613
|
+
await context.setCurrent(currentContext);
|
|
3614
|
+
await context.set(currentContext, config);
|
|
3615
|
+
return await getToken();
|
|
3408
3616
|
}
|
|
3617
|
+
};
|
|
3618
|
+
__name(_AdobeAuth, "AdobeAuth");
|
|
3619
|
+
var AdobeAuth = _AdobeAuth;
|
|
3620
|
+
var adobe_auth_default = AdobeAuth;
|
|
3621
|
+
|
|
3622
|
+
// src/integration/bearer-token/index.ts
|
|
3623
|
+
var _BearerToken = class _BearerToken {
|
|
3409
3624
|
/**
|
|
3410
|
-
*
|
|
3625
|
+
* Extracts the Bearer token from HTTP request headers and returns detailed token information.
|
|
3626
|
+
* Supports both standard HTTP headers and OpenWhisk action parameter formats.
|
|
3411
3627
|
*
|
|
3412
|
-
* @param
|
|
3413
|
-
* @
|
|
3414
|
-
*
|
|
3415
|
-
* @
|
|
3416
|
-
*
|
|
3628
|
+
* @param headersOrParams - Either a standard headers object or OpenWhisk action parameters
|
|
3629
|
+
* @returns Detailed token information object
|
|
3630
|
+
*
|
|
3631
|
+
* @example
|
|
3632
|
+
* // Standard HTTP headers approach
|
|
3633
|
+
* const headers = {
|
|
3634
|
+
* authorization: 'Bearer abc123token'
|
|
3635
|
+
* };
|
|
3636
|
+
* const tokenInfo = BearerToken.extract(headers);
|
|
3637
|
+
*
|
|
3638
|
+
* @example
|
|
3639
|
+
* // OpenWhisk action parameters (backward compatibility)
|
|
3640
|
+
* const params = {
|
|
3641
|
+
* __ow_headers: {
|
|
3642
|
+
* authorization: 'Bearer abc123token'
|
|
3643
|
+
* }
|
|
3644
|
+
* };
|
|
3645
|
+
* const tokenInfo = BearerToken.extract(params);
|
|
3646
|
+
*
|
|
3647
|
+
* @example
|
|
3648
|
+
* // Both return the same result:
|
|
3649
|
+
* // {
|
|
3650
|
+
* // token: 'abc123token',
|
|
3651
|
+
* // tokenLength: 11,
|
|
3652
|
+
* // isValid: true,
|
|
3653
|
+
* // expiry: '2024-01-01T12:00:00.000Z',
|
|
3654
|
+
* // timeUntilExpiry: 3600000
|
|
3655
|
+
* // }
|
|
3417
3656
|
*/
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3657
|
+
static extract(headersOrParams) {
|
|
3658
|
+
let token = null;
|
|
3659
|
+
if (headersOrParams.authorization?.startsWith("Bearer ")) {
|
|
3660
|
+
token = headersOrParams.authorization.substring("Bearer ".length);
|
|
3661
|
+
} else if (headersOrParams.__ow_headers?.authorization?.startsWith("Bearer ")) {
|
|
3662
|
+
token = headersOrParams.__ow_headers.authorization.substring("Bearer ".length);
|
|
3663
|
+
}
|
|
3664
|
+
return _BearerToken.info(token);
|
|
3421
3665
|
}
|
|
3422
3666
|
/**
|
|
3423
|
-
*
|
|
3667
|
+
* Analyzes a Bearer token and returns detailed information including validity and expiry.
|
|
3668
|
+
* Supports both JWT tokens (with automatic expiry detection) and plain tokens (24h default expiry).
|
|
3424
3669
|
*
|
|
3425
|
-
* @param
|
|
3426
|
-
* @
|
|
3427
|
-
*
|
|
3428
|
-
* @
|
|
3670
|
+
* @param token - The Bearer token string (or null). Can be JWT or plain token.
|
|
3671
|
+
* @returns Detailed token information object
|
|
3672
|
+
*
|
|
3673
|
+
* @example
|
|
3674
|
+
* // Plain token (gets 24h default expiry)
|
|
3675
|
+
* const plainTokenInfo = BearerToken.info('abc123token');
|
|
3676
|
+
* // returns: {
|
|
3677
|
+
* // token: 'abc123token',
|
|
3678
|
+
* // tokenLength: 11,
|
|
3679
|
+
* // isValid: true,
|
|
3680
|
+
* // expiry: '2024-01-02T12:00:00.000Z', // 24h from now
|
|
3681
|
+
* // timeUntilExpiry: 86400000 // milliseconds until expiry
|
|
3682
|
+
* // }
|
|
3683
|
+
*
|
|
3684
|
+
* @example
|
|
3685
|
+
* // JWT token (automatic expiry detection from 'exp' or 'expires_in' claims)
|
|
3686
|
+
* const jwtToken = 'eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MDQ0Njc2MDB9.signature';
|
|
3687
|
+
* const jwtTokenInfo = BearerToken.info(jwtToken);
|
|
3688
|
+
* // returns: {
|
|
3689
|
+
* // token: 'eyJhbGciOiJIUzI1NiJ9...',
|
|
3690
|
+
* // tokenLength: 45,
|
|
3691
|
+
* // isValid: true, // false if expired
|
|
3692
|
+
* // expiry: '2024-01-05T12:00:00.000Z', // from JWT exp claim
|
|
3693
|
+
* // timeUntilExpiry: 172800000 // actual time until expiry
|
|
3694
|
+
* // }
|
|
3695
|
+
*
|
|
3696
|
+
* @example
|
|
3697
|
+
* // Null or invalid token
|
|
3698
|
+
* const nullTokenInfo = BearerToken.info(null);
|
|
3699
|
+
* // returns: {
|
|
3700
|
+
* // token: null,
|
|
3701
|
+
* // tokenLength: 0,
|
|
3702
|
+
* // isValid: false,
|
|
3703
|
+
* // expiry: null,
|
|
3704
|
+
* // timeUntilExpiry: null
|
|
3705
|
+
* // }
|
|
3429
3706
|
*/
|
|
3430
|
-
|
|
3431
|
-
const
|
|
3432
|
-
return
|
|
3707
|
+
static info(token) {
|
|
3708
|
+
const tokenExpiry = _BearerToken._calculateExpiry(token);
|
|
3709
|
+
return {
|
|
3710
|
+
token,
|
|
3711
|
+
tokenLength: token ? token.length : 0,
|
|
3712
|
+
isValid: _BearerToken._isTokenValid(token, tokenExpiry),
|
|
3713
|
+
expiry: tokenExpiry ? tokenExpiry.toISOString() : null,
|
|
3714
|
+
timeUntilExpiry: tokenExpiry ? Math.max(0, tokenExpiry.getTime() - Date.now()) : null
|
|
3715
|
+
};
|
|
3433
3716
|
}
|
|
3434
3717
|
/**
|
|
3435
|
-
*
|
|
3436
|
-
*
|
|
3437
|
-
* @param
|
|
3438
|
-
* @param
|
|
3439
|
-
* @
|
|
3440
|
-
* @param payload
|
|
3441
|
-
* @returns {Promise<any>}
|
|
3442
|
-
* @deprecated Use makeRequest() and parseResponse() methods instead
|
|
3718
|
+
* Checks if the given token is valid and not expired
|
|
3719
|
+
* @private
|
|
3720
|
+
* @param token - The bearer token string
|
|
3721
|
+
* @param tokenExpiry - The token expiry date
|
|
3722
|
+
* @returns {boolean} True if token is valid
|
|
3443
3723
|
*/
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
headers
|
|
3448
|
-
};
|
|
3449
|
-
if (payload !== null) {
|
|
3450
|
-
options = {
|
|
3451
|
-
...options,
|
|
3452
|
-
body: JSON.stringify(payload),
|
|
3453
|
-
headers: {
|
|
3454
|
-
...headers,
|
|
3455
|
-
"Content-Type": "application/json"
|
|
3456
|
-
}
|
|
3457
|
-
};
|
|
3724
|
+
static _isTokenValid(token, tokenExpiry) {
|
|
3725
|
+
if (!token) {
|
|
3726
|
+
return false;
|
|
3458
3727
|
}
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3728
|
+
if (tokenExpiry && Date.now() >= tokenExpiry.getTime()) {
|
|
3729
|
+
console.log("\u23F0 Token has expired");
|
|
3730
|
+
return false;
|
|
3462
3731
|
}
|
|
3463
|
-
|
|
3732
|
+
return true;
|
|
3733
|
+
}
|
|
3734
|
+
/**
|
|
3735
|
+
* Calculates token expiry from JWT token or uses default for non-JWT tokens
|
|
3736
|
+
* @private
|
|
3737
|
+
* @param token - The token string (JWT or plain token)
|
|
3738
|
+
* @returns Date object representing token expiry
|
|
3739
|
+
*/
|
|
3740
|
+
static _calculateExpiry(token) {
|
|
3741
|
+
if (!token) {
|
|
3464
3742
|
return null;
|
|
3465
3743
|
}
|
|
3466
|
-
|
|
3467
|
-
const
|
|
3468
|
-
if (
|
|
3469
|
-
|
|
3744
|
+
try {
|
|
3745
|
+
const parts = token.split(".");
|
|
3746
|
+
if (parts.length === 3) {
|
|
3747
|
+
const payload = JSON.parse(Buffer.from(parts[1] || "", "base64").toString());
|
|
3748
|
+
if (payload.expires_in) {
|
|
3749
|
+
return new Date(Date.now() + parseInt(payload.expires_in));
|
|
3750
|
+
}
|
|
3751
|
+
if (payload.exp) {
|
|
3752
|
+
return new Date(payload.exp * 1e3);
|
|
3753
|
+
}
|
|
3470
3754
|
}
|
|
3755
|
+
return new Date(Date.now() + 24 * 60 * 60 * 1e3);
|
|
3756
|
+
} catch (error) {
|
|
3757
|
+
console.warn("[WARN] Could not parse token expiry, using default 24h");
|
|
3758
|
+
return new Date(Date.now() + 24 * 60 * 60 * 1e3);
|
|
3471
3759
|
}
|
|
3472
|
-
if (typeof response.text === "function") {
|
|
3473
|
-
const text = await response.text();
|
|
3474
|
-
return text;
|
|
3475
|
-
}
|
|
3476
|
-
return null;
|
|
3477
3760
|
}
|
|
3478
3761
|
};
|
|
3479
|
-
__name(
|
|
3480
|
-
var
|
|
3481
|
-
var
|
|
3762
|
+
__name(_BearerToken, "BearerToken");
|
|
3763
|
+
var BearerToken = _BearerToken;
|
|
3764
|
+
var bearer_token_default = BearerToken;
|
|
3482
3765
|
|
|
3483
|
-
// src/framework/
|
|
3484
|
-
var
|
|
3766
|
+
// src/framework/ims-token/index.ts
|
|
3767
|
+
var _ImsToken = class _ImsToken {
|
|
3485
3768
|
/**
|
|
3486
|
-
* Creates an instance of
|
|
3769
|
+
* Creates an instance of ImsToken
|
|
3487
3770
|
*
|
|
3488
|
-
* @
|
|
3771
|
+
* @deprecated Use `AdobeAuth.getToken()` directly and implement caching in your application.
|
|
3772
|
+
* See class documentation for migration examples.
|
|
3773
|
+
*
|
|
3774
|
+
* @param clientId - OAuth client ID for Adobe IMS authentication
|
|
3775
|
+
* @param clientSecret - OAuth client secret for Adobe IMS authentication
|
|
3776
|
+
* @param technicalAccountId - Technical account ID for service-to-service authentication
|
|
3777
|
+
* @param technicalAccountEmail - Technical account email for service-to-service authentication
|
|
3489
3778
|
* @param imsOrgId - IMS organization ID
|
|
3490
|
-
* @param
|
|
3779
|
+
* @param scopes - Array of scopes required for the token
|
|
3491
3780
|
* @param logger - Optional logger instance for logging operations
|
|
3492
|
-
* @
|
|
3493
|
-
*
|
|
3494
|
-
* const service = new RuntimeApiGatewayService(
|
|
3495
|
-
* 'test-namespace',
|
|
3496
|
-
* 'org-id@AdobeOrg',
|
|
3497
|
-
* 'bearer-token-string',
|
|
3498
|
-
* logger
|
|
3499
|
-
* );
|
|
3500
|
-
* ```
|
|
3781
|
+
* @param cacheKey - Optional custom cache key for token storage (defaults to 'runtime_api_gateway_token')
|
|
3782
|
+
* @param tokenContext - Optional token context for authentication (defaults to 'runtime-api-gateway-context')
|
|
3501
3783
|
*/
|
|
3502
|
-
constructor(
|
|
3503
|
-
|
|
3784
|
+
constructor(clientId, clientSecret, technicalAccountId, technicalAccountEmail, imsOrgId, scopes, logger = null, cacheKey, tokenContext) {
|
|
3785
|
+
/** State property for managing token state */
|
|
3786
|
+
this.state = void 0;
|
|
3787
|
+
this.clientId = clientId;
|
|
3788
|
+
this.clientSecret = clientSecret;
|
|
3789
|
+
this.technicalAccountId = technicalAccountId;
|
|
3790
|
+
this.technicalAccountEmail = technicalAccountEmail;
|
|
3504
3791
|
this.imsOrgId = imsOrgId;
|
|
3505
|
-
this.
|
|
3506
|
-
this.restClient = new rest_client_default();
|
|
3792
|
+
this.scopes = scopes;
|
|
3507
3793
|
this.customLogger = new custom_logger_default(logger);
|
|
3794
|
+
this.key = cacheKey || "ims_token";
|
|
3795
|
+
this.tokenContext = tokenContext || "ims-context";
|
|
3508
3796
|
}
|
|
3509
3797
|
/**
|
|
3510
|
-
*
|
|
3798
|
+
* Executes IMS token generation or retrieves a cached token
|
|
3511
3799
|
*
|
|
3512
|
-
* @
|
|
3513
|
-
*
|
|
3514
|
-
*
|
|
3515
|
-
*/
|
|
3516
|
-
buildEndpoint(endpoint) {
|
|
3517
|
-
return `${_RuntimeApiGatewayService.BASE_URL}/${this.namespace}/${endpoint}`;
|
|
3518
|
-
}
|
|
3519
|
-
/**
|
|
3520
|
-
* Gets the authenticated headers for API requests
|
|
3800
|
+
* @deprecated Use `AdobeAuth.getToken()` directly and implement caching in your application.
|
|
3801
|
+
* The built-in caching mechanism has reliability issues with the State API.
|
|
3802
|
+
* See class documentation for migration examples.
|
|
3521
3803
|
*
|
|
3522
|
-
*
|
|
3523
|
-
*
|
|
3524
|
-
*/
|
|
3525
|
-
getAuthenticatedHeaders() {
|
|
3526
|
-
return {
|
|
3527
|
-
"Content-Type": "application/json",
|
|
3528
|
-
Authorization: `Bearer ${this.imsToken}`,
|
|
3529
|
-
"x-gw-ims-org-id": this.imsOrgId
|
|
3530
|
-
};
|
|
3531
|
-
}
|
|
3532
|
-
/**
|
|
3533
|
-
* Performs a GET request to the Runtime API Gateway
|
|
3804
|
+
* This method first checks for a cached token. If a valid cached token exists,
|
|
3805
|
+
* it returns that. Otherwise, it generates a new token, caches it, and returns it.
|
|
3534
3806
|
*
|
|
3535
|
-
* @
|
|
3536
|
-
* @param additionalHeaders - Optional additional headers to include in the request
|
|
3537
|
-
* @returns A promise that resolves with the API response data
|
|
3538
|
-
* @throws {Error} If the API request fails
|
|
3807
|
+
* @returns A promise that resolves to the IMS token string or null if generation fails
|
|
3539
3808
|
* @example
|
|
3540
3809
|
* ```typescript
|
|
3541
|
-
* const
|
|
3542
|
-
*
|
|
3543
|
-
*
|
|
3544
|
-
*
|
|
3545
|
-
* const data = await service.get('v1/my-endpoint', { 'Custom-Header': 'value' });
|
|
3810
|
+
* const token = await imsToken.execute();
|
|
3811
|
+
* if (token) {
|
|
3812
|
+
* console.log('Token obtained:', token);
|
|
3813
|
+
* }
|
|
3546
3814
|
* ```
|
|
3547
3815
|
*/
|
|
3548
|
-
async
|
|
3816
|
+
async execute() {
|
|
3549
3817
|
try {
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
this.customLogger.info("
|
|
3557
|
-
|
|
3818
|
+
this.customLogger.info("Starting IMS token generation/retrieval process");
|
|
3819
|
+
const currentValue = await this.getValue();
|
|
3820
|
+
if (currentValue !== null) {
|
|
3821
|
+
this.customLogger.info("Found cached IMS token, returning cached value");
|
|
3822
|
+
return currentValue;
|
|
3823
|
+
}
|
|
3824
|
+
this.customLogger.info("No cached token found, generating new IMS token");
|
|
3825
|
+
let result = {
|
|
3826
|
+
token: null,
|
|
3827
|
+
expire_in: 86399
|
|
3828
|
+
// Default fallback, will be overridden by actual token expiry
|
|
3829
|
+
};
|
|
3830
|
+
const response = await this.getImsToken();
|
|
3831
|
+
if (response !== null) {
|
|
3832
|
+
result = response;
|
|
3833
|
+
}
|
|
3834
|
+
if (result.token !== null) {
|
|
3835
|
+
this.customLogger.info(`Generated new IMS token, caching for ${result.expire_in} seconds`);
|
|
3836
|
+
await this.setValue(result);
|
|
3837
|
+
}
|
|
3838
|
+
return result.token;
|
|
3558
3839
|
} catch (error) {
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
throw new Error(`Runtime API gateway GET request failed: ${errorMessage}`);
|
|
3840
|
+
this.customLogger.error(`Failed to execute IMS token generation: ${error.message}`);
|
|
3841
|
+
return null;
|
|
3562
3842
|
}
|
|
3563
3843
|
}
|
|
3564
3844
|
/**
|
|
3565
|
-
*
|
|
3566
|
-
*
|
|
3567
|
-
* @param endpoint - API endpoint path (e.g., 'v1/my-endpoint')
|
|
3568
|
-
* @param payload - The data to send in the request body
|
|
3569
|
-
* @param additionalHeaders - Optional additional headers to include in the request
|
|
3570
|
-
* @returns A promise that resolves with the API response data
|
|
3571
|
-
* @throws {Error} If the API request fails
|
|
3572
|
-
* @example
|
|
3573
|
-
* ```typescript
|
|
3574
|
-
* const service = new RuntimeApiGatewayService(...);
|
|
3575
|
-
* const result = await service.post('v1/my-endpoint', { key: 'value' });
|
|
3845
|
+
* Generates a new IMS token from Adobe IMS service
|
|
3576
3846
|
*
|
|
3577
|
-
*
|
|
3578
|
-
*
|
|
3579
|
-
* ```
|
|
3847
|
+
* @returns A promise that resolves to ImsTokenResult or null if generation fails
|
|
3848
|
+
* @private
|
|
3580
3849
|
*/
|
|
3581
|
-
async
|
|
3850
|
+
async getImsToken() {
|
|
3582
3851
|
try {
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3852
|
+
this.customLogger.debug(`Calling AdobeAuth.getToken with context: ${this.tokenContext}`);
|
|
3853
|
+
const token = await adobe_auth_default.getToken(
|
|
3854
|
+
this.clientId,
|
|
3855
|
+
this.clientSecret,
|
|
3856
|
+
this.technicalAccountId,
|
|
3857
|
+
this.technicalAccountEmail,
|
|
3858
|
+
this.imsOrgId,
|
|
3859
|
+
this.scopes,
|
|
3860
|
+
this.tokenContext
|
|
3861
|
+
);
|
|
3862
|
+
if (token !== null && token !== void 0) {
|
|
3863
|
+
this.customLogger.debug("Received token from AdobeAuth, parsing with BearerToken.info");
|
|
3864
|
+
const tokenInfo = bearer_token_default.info(token);
|
|
3865
|
+
if (!tokenInfo.isValid) {
|
|
3866
|
+
this.customLogger.error("Received invalid or expired token from IMS");
|
|
3867
|
+
return null;
|
|
3868
|
+
}
|
|
3869
|
+
const expireInSeconds = tokenInfo.timeUntilExpiry ? Math.floor(tokenInfo.timeUntilExpiry / 1e3) : 86399;
|
|
3870
|
+
this.customLogger.debug(`Token expires in ${expireInSeconds} seconds`);
|
|
3871
|
+
return {
|
|
3872
|
+
token,
|
|
3873
|
+
expire_in: expireInSeconds
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3876
|
+
this.customLogger.error("Received null or undefined token from IMS");
|
|
3877
|
+
return null;
|
|
3592
3878
|
} catch (error) {
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
throw new Error(`Runtime API gateway POST request failed: ${errorMessage}`);
|
|
3879
|
+
this.customLogger.error(`Failed to get IMS token: ${error.message}`);
|
|
3880
|
+
return null;
|
|
3596
3881
|
}
|
|
3597
3882
|
}
|
|
3598
3883
|
/**
|
|
3599
|
-
*
|
|
3884
|
+
* Caches the IMS token in the state store with TTL
|
|
3600
3885
|
*
|
|
3601
|
-
* @
|
|
3602
|
-
* @param payload - The data to send in the request body
|
|
3603
|
-
* @param additionalHeaders - Optional additional headers to include in the request
|
|
3604
|
-
* @returns A promise that resolves with the API response data
|
|
3605
|
-
* @throws {Error} If the API request fails
|
|
3606
|
-
* @example
|
|
3607
|
-
* ```typescript
|
|
3608
|
-
* const service = new RuntimeApiGatewayService(...);
|
|
3609
|
-
* const updated = await service.put('v1/my-endpoint', { id: 1, name: 'updated' });
|
|
3886
|
+
* @deprecated This method has issues with State API reliability. Use application-level caching instead.
|
|
3610
3887
|
*
|
|
3611
|
-
*
|
|
3612
|
-
*
|
|
3613
|
-
*
|
|
3888
|
+
* **Known Issues:**
|
|
3889
|
+
* - State API may not be available in all runtime environments
|
|
3890
|
+
* - Hard-coded 10-minute buffer may not suit all use cases
|
|
3891
|
+
* - Minimum 60-minute TTL is opinionated and inflexible
|
|
3892
|
+
* - No retry mechanism for State API failures
|
|
3893
|
+
*
|
|
3894
|
+
* @param result - The token result containing the token and expiration time
|
|
3895
|
+
* @returns A promise that resolves to true if caching succeeded, false otherwise
|
|
3896
|
+
* @private
|
|
3614
3897
|
*/
|
|
3615
|
-
async
|
|
3898
|
+
async setValue(result) {
|
|
3616
3899
|
try {
|
|
3617
|
-
const
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
const
|
|
3623
|
-
this.customLogger.debug(
|
|
3624
|
-
|
|
3625
|
-
|
|
3900
|
+
const state = await this.getState();
|
|
3901
|
+
if (state === null) {
|
|
3902
|
+
this.customLogger.info("State API not available, skipping token caching");
|
|
3903
|
+
return true;
|
|
3904
|
+
}
|
|
3905
|
+
const ttlWithBuffer = Math.max(result.expire_in - 600, 3600);
|
|
3906
|
+
this.customLogger.debug(
|
|
3907
|
+
`Caching IMS token with TTL: ${ttlWithBuffer} seconds (original: ${result.expire_in})`
|
|
3908
|
+
);
|
|
3909
|
+
await state.put(this.key, result.token, { ttl: ttlWithBuffer });
|
|
3910
|
+
return true;
|
|
3626
3911
|
} catch (error) {
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
throw new Error(`Runtime API gateway PUT request failed: ${errorMessage}`);
|
|
3912
|
+
this.customLogger.error(`Failed to cache IMS token: ${error.message}`);
|
|
3913
|
+
return true;
|
|
3630
3914
|
}
|
|
3631
3915
|
}
|
|
3632
3916
|
/**
|
|
3633
|
-
*
|
|
3917
|
+
* Retrieves a cached IMS token from the state store
|
|
3634
3918
|
*
|
|
3635
|
-
* @
|
|
3636
|
-
* @param additionalHeaders - Optional additional headers to include in the request
|
|
3637
|
-
* @returns A promise that resolves with the API response data
|
|
3638
|
-
* @throws {Error} If the API request fails
|
|
3639
|
-
* @example
|
|
3640
|
-
* ```typescript
|
|
3641
|
-
* const service = new RuntimeApiGatewayService(...);
|
|
3642
|
-
* const deleted = await service.delete('v1/my-endpoint');
|
|
3919
|
+
* @deprecated This method has issues with State API reliability. Use application-level caching instead.
|
|
3643
3920
|
*
|
|
3644
|
-
*
|
|
3645
|
-
*
|
|
3646
|
-
*
|
|
3921
|
+
* **Known Issues:**
|
|
3922
|
+
* - State API may not be available in all runtime environments
|
|
3923
|
+
* - No validation of cached token expiry
|
|
3924
|
+
* - Silent failures may return null without proper error context
|
|
3925
|
+
*
|
|
3926
|
+
* @returns A promise that resolves to the cached token string or null if not found
|
|
3927
|
+
* @private
|
|
3647
3928
|
*/
|
|
3648
|
-
async
|
|
3929
|
+
async getValue() {
|
|
3649
3930
|
try {
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
this.customLogger.error(`DELETE request failed: ${errorMessage}`);
|
|
3661
|
-
throw new Error(`Runtime API gateway DELETE request failed: ${errorMessage}`);
|
|
3662
|
-
}
|
|
3663
|
-
}
|
|
3664
|
-
};
|
|
3665
|
-
__name(_RuntimeApiGatewayService, "RuntimeApiGatewayService");
|
|
3666
|
-
/** Base URL for the Adobe I/O Runtime APIs */
|
|
3667
|
-
_RuntimeApiGatewayService.BASE_URL = "https://adobeioruntime.net/apis";
|
|
3668
|
-
var RuntimeApiGatewayService = _RuntimeApiGatewayService;
|
|
3669
|
-
|
|
3670
|
-
// src/framework/abdb/column/types.ts
|
|
3671
|
-
var AbdbColumnType = /* @__PURE__ */ ((AbdbColumnType2) => {
|
|
3672
|
-
AbdbColumnType2["STRING"] = "STRING";
|
|
3673
|
-
AbdbColumnType2["NUMBER"] = "NUMBER";
|
|
3674
|
-
AbdbColumnType2["BOOLEAN"] = "BOOLEAN";
|
|
3675
|
-
AbdbColumnType2["DATETIME"] = "DATETIME";
|
|
3676
|
-
return AbdbColumnType2;
|
|
3677
|
-
})(AbdbColumnType || {});
|
|
3678
|
-
|
|
3679
|
-
// src/framework/abdb/column/index.ts
|
|
3680
|
-
var ISO_8601_DATETIME = /^\d{4}-\d{2}-\d{2}([Tt ]\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}(:?\d{2})?)?)?$/;
|
|
3681
|
-
var _AbdbColumn = class _AbdbColumn {
|
|
3682
|
-
/**
|
|
3683
|
-
* @param options - Field definition
|
|
3684
|
-
* @throws {Error} When `name` is missing or blank, or `type` is missing / not a member of {@link AbdbColumnType}
|
|
3685
|
-
*/
|
|
3686
|
-
constructor(options) {
|
|
3687
|
-
if (!options?.name?.trim()) {
|
|
3688
|
-
throw new Error('AbdbColumn: "name" is required and cannot be empty');
|
|
3689
|
-
}
|
|
3690
|
-
if (!options.type || !Object.values(AbdbColumnType).includes(options.type)) {
|
|
3691
|
-
throw new Error('AbdbColumn: "type" is required');
|
|
3692
|
-
}
|
|
3693
|
-
this._name = options.name.trim();
|
|
3694
|
-
this._type = options.type;
|
|
3695
|
-
this._description = void 0;
|
|
3696
|
-
this._isRequired = options.isRequired ?? false;
|
|
3697
|
-
if (options.description !== void 0) {
|
|
3698
|
-
const trimmed = options.description.trim();
|
|
3699
|
-
if (trimmed.length > 0) {
|
|
3700
|
-
this._description = trimmed;
|
|
3931
|
+
this.customLogger.debug("Checking for cached IMS token");
|
|
3932
|
+
const state = await this.getState();
|
|
3933
|
+
if (state === null) {
|
|
3934
|
+
this.customLogger.debug("State API not available, cannot retrieve cached token");
|
|
3935
|
+
return null;
|
|
3936
|
+
}
|
|
3937
|
+
const value = await state.get(this.key);
|
|
3938
|
+
if (value !== void 0 && value.value) {
|
|
3939
|
+
this.customLogger.debug("Found cached IMS token");
|
|
3940
|
+
return value.value;
|
|
3701
3941
|
}
|
|
3942
|
+
this.customLogger.debug("No cached IMS token found");
|
|
3943
|
+
} catch (error) {
|
|
3944
|
+
this.customLogger.error(`Failed to retrieve cached IMS token: ${error.message}`);
|
|
3702
3945
|
}
|
|
3703
|
-
|
|
3704
|
-
}
|
|
3705
|
-
/**
|
|
3706
|
-
* Returns the trimmed field name.
|
|
3707
|
-
*/
|
|
3708
|
-
getName() {
|
|
3709
|
-
return this._name;
|
|
3710
|
-
}
|
|
3711
|
-
/**
|
|
3712
|
-
* Returns the expected value type for this column.
|
|
3713
|
-
*/
|
|
3714
|
-
getType() {
|
|
3715
|
-
return this._type;
|
|
3716
|
-
}
|
|
3717
|
-
/**
|
|
3718
|
-
* Returns the optional description, if one was provided and non-whitespace.
|
|
3719
|
-
*
|
|
3720
|
-
* @returns Description string, or `undefined` when not set
|
|
3721
|
-
*/
|
|
3722
|
-
getDescription() {
|
|
3723
|
-
return this._description;
|
|
3946
|
+
return null;
|
|
3724
3947
|
}
|
|
3725
3948
|
/**
|
|
3726
|
-
*
|
|
3949
|
+
* Initializes and returns the state store instance
|
|
3727
3950
|
*
|
|
3728
|
-
* @
|
|
3729
|
-
|
|
3730
|
-
getIsRequired() {
|
|
3731
|
-
return this._isRequired;
|
|
3732
|
-
}
|
|
3733
|
-
/**
|
|
3734
|
-
* Serializes this column to a plain object for JSON (config files, APIs, persistence).
|
|
3735
|
-
*/
|
|
3736
|
-
toJSON() {
|
|
3737
|
-
const json = {
|
|
3738
|
-
name: this._name,
|
|
3739
|
-
type: this._type
|
|
3740
|
-
};
|
|
3741
|
-
if (this._description !== void 0) {
|
|
3742
|
-
json.description = this._description;
|
|
3743
|
-
}
|
|
3744
|
-
if (this._isRequired) {
|
|
3745
|
-
json.isRequired = true;
|
|
3746
|
-
}
|
|
3747
|
-
return json;
|
|
3748
|
-
}
|
|
3749
|
-
/**
|
|
3750
|
-
* Creates a new column with the same or overridden options. Does not mutate this instance.
|
|
3951
|
+
* @deprecated This method relies on State API which has reliability issues.
|
|
3952
|
+
* Use application-level caching instead.
|
|
3751
3953
|
*
|
|
3752
|
-
*
|
|
3954
|
+
* **Known Issues:**
|
|
3955
|
+
* - State API initialization may fail silently
|
|
3956
|
+
* - Not available outside Adobe I/O Runtime environments
|
|
3957
|
+
* - Caches the state instance which may become stale
|
|
3753
3958
|
*
|
|
3754
|
-
* @
|
|
3755
|
-
* @
|
|
3959
|
+
* @returns A promise that resolves to the state instance or null if initialization fails
|
|
3960
|
+
* @private
|
|
3756
3961
|
*/
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3962
|
+
async getState() {
|
|
3963
|
+
if (this.state === void 0) {
|
|
3964
|
+
try {
|
|
3965
|
+
this.customLogger.debug("Initializing State API for token caching");
|
|
3966
|
+
this.state = await State.init();
|
|
3967
|
+
} catch (error) {
|
|
3968
|
+
this.customLogger.error(`Failed to initialize State API: ${error.message}`);
|
|
3969
|
+
this.state = null;
|
|
3970
|
+
}
|
|
3766
3971
|
}
|
|
3767
|
-
return
|
|
3972
|
+
return this.state;
|
|
3768
3973
|
}
|
|
3974
|
+
};
|
|
3975
|
+
__name(_ImsToken, "ImsToken");
|
|
3976
|
+
var ImsToken = _ImsToken;
|
|
3977
|
+
var ims_token_default = ImsToken;
|
|
3978
|
+
|
|
3979
|
+
// src/integration/rest-client/index.ts
|
|
3980
|
+
import fetch from "node-fetch";
|
|
3981
|
+
var _RestClient = class _RestClient {
|
|
3769
3982
|
/**
|
|
3770
|
-
*
|
|
3771
|
-
*
|
|
3772
|
-
* **Missing values** (`undefined`, `null`, or empty / whitespace-only strings) are handled first:
|
|
3773
|
-
* if `isRequired` is `false`, validation succeeds; if `true`, throws.
|
|
3774
|
-
*
|
|
3775
|
-
* **Type rules** (when a non-missing value is present):
|
|
3776
|
-
* - `STRING`: primitive string
|
|
3777
|
-
* - `NUMBER`: finite number, or a non-empty string whose trim parses to a finite number via `Number`
|
|
3778
|
-
* - `BOOLEAN`: primitive boolean, or non-empty trimmed string `"true"` / `"false"` (case-insensitive)
|
|
3779
|
-
* - `DATETIME`: valid `Date`, finite numeric timestamp (ms), or non-empty trimmed ISO 8601 date/datetime string
|
|
3983
|
+
* A completely raw method to make HTTP requests
|
|
3780
3984
|
*
|
|
3781
|
-
* @param
|
|
3782
|
-
* @
|
|
3985
|
+
* @param endpoint
|
|
3986
|
+
* @param method
|
|
3987
|
+
* @param headers
|
|
3988
|
+
* @param payload
|
|
3989
|
+
* @returns {Promise<Response>}
|
|
3783
3990
|
*/
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3991
|
+
async makeRequest(endpoint, method = "GET", headers = {}, payload = null) {
|
|
3992
|
+
let options = {
|
|
3993
|
+
method,
|
|
3994
|
+
headers
|
|
3995
|
+
};
|
|
3996
|
+
if (payload !== null) {
|
|
3997
|
+
let body;
|
|
3998
|
+
let contentType;
|
|
3999
|
+
if (payload instanceof URLSearchParams) {
|
|
4000
|
+
body = payload.toString();
|
|
4001
|
+
contentType = headers["Content-Type"] || "application/x-www-form-urlencoded";
|
|
4002
|
+
} else if (typeof FormData !== "undefined" && payload instanceof FormData) {
|
|
4003
|
+
body = payload;
|
|
4004
|
+
contentType = headers["Content-Type"];
|
|
4005
|
+
} else if (typeof payload === "string") {
|
|
4006
|
+
body = payload;
|
|
4007
|
+
contentType = headers["Content-Type"] || "text/plain";
|
|
4008
|
+
} else if (payload instanceof Buffer || payload instanceof ArrayBuffer || typeof Uint8Array !== "undefined" && payload instanceof Uint8Array) {
|
|
4009
|
+
body = payload;
|
|
4010
|
+
contentType = headers["Content-Type"] || "application/octet-stream";
|
|
4011
|
+
} else {
|
|
4012
|
+
body = JSON.stringify(payload);
|
|
4013
|
+
contentType = headers["Content-Type"] || "application/json";
|
|
3788
4014
|
}
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
case "STRING" /* STRING */:
|
|
3793
|
-
this._validateStringValue(value);
|
|
3794
|
-
return;
|
|
3795
|
-
case "NUMBER" /* NUMBER */:
|
|
3796
|
-
this._validateNumberValue(value);
|
|
3797
|
-
return;
|
|
3798
|
-
case "BOOLEAN" /* BOOLEAN */:
|
|
3799
|
-
this._validateBooleanValue(value);
|
|
3800
|
-
return;
|
|
3801
|
-
case "DATETIME" /* DATETIME */:
|
|
3802
|
-
this._validateDateTimeValue(value);
|
|
3803
|
-
return;
|
|
3804
|
-
default: {
|
|
3805
|
-
const _unreachable = this._type;
|
|
3806
|
-
throw new Error(`AbdbColumn: unhandled type "${_unreachable}"`);
|
|
4015
|
+
const requestHeaders = { ...headers };
|
|
4016
|
+
if (contentType) {
|
|
4017
|
+
requestHeaders["Content-Type"] = contentType;
|
|
3807
4018
|
}
|
|
4019
|
+
options = {
|
|
4020
|
+
...options,
|
|
4021
|
+
body,
|
|
4022
|
+
headers: requestHeaders
|
|
4023
|
+
};
|
|
3808
4024
|
}
|
|
4025
|
+
return await fetch(endpoint, options);
|
|
3809
4026
|
}
|
|
3810
4027
|
/**
|
|
3811
|
-
*
|
|
4028
|
+
* A method to parse HTTP response
|
|
3812
4029
|
*
|
|
3813
|
-
* @
|
|
4030
|
+
* @param response
|
|
4031
|
+
* @returns {Promise<any>}
|
|
3814
4032
|
*/
|
|
3815
|
-
|
|
3816
|
-
if (
|
|
3817
|
-
|
|
4033
|
+
async parseResponse(response) {
|
|
4034
|
+
if (!response.ok) {
|
|
4035
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
3818
4036
|
}
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
/**
|
|
3822
|
-
* @throws {Error} When `value` is not a primitive string
|
|
3823
|
-
*/
|
|
3824
|
-
_validateStringValue(value) {
|
|
3825
|
-
if (typeof value !== "string") {
|
|
3826
|
-
throw new Error(`AbdbColumn: "${this._name}" expects string, got ${typeof value}`);
|
|
4037
|
+
if (response.status === 204 || response.headers?.get("content-length") === "0") {
|
|
4038
|
+
return null;
|
|
3827
4039
|
}
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
_validateNumberValue(value) {
|
|
3833
|
-
if (typeof value === "number") {
|
|
3834
|
-
if (Number.isFinite(value)) {
|
|
3835
|
-
return;
|
|
4040
|
+
if (typeof response.json === "function") {
|
|
4041
|
+
const contentType = response.headers?.get("content-type");
|
|
4042
|
+
if (!contentType || contentType.includes("application/json") || contentType.includes("application/hal+json")) {
|
|
4043
|
+
return await response.json();
|
|
3836
4044
|
}
|
|
3837
|
-
throw new Error(`AbdbColumn: "${this._name}" expects a finite number, got ${String(value)}`);
|
|
3838
4045
|
}
|
|
3839
|
-
if (typeof
|
|
3840
|
-
const
|
|
3841
|
-
|
|
3842
|
-
if (Number.isFinite(n)) {
|
|
3843
|
-
return;
|
|
3844
|
-
}
|
|
3845
|
-
throw new Error(
|
|
3846
|
-
`AbdbColumn: "${this._name}" expects a finite number or numeric string, got ${JSON.stringify(value)}`
|
|
3847
|
-
);
|
|
4046
|
+
if (typeof response.text === "function") {
|
|
4047
|
+
const text = await response.text();
|
|
4048
|
+
return text;
|
|
3848
4049
|
}
|
|
3849
|
-
|
|
3850
|
-
`AbdbColumn: "${this._name}" expects a finite number or numeric string, got ${typeof value}`
|
|
3851
|
-
);
|
|
4050
|
+
return null;
|
|
3852
4051
|
}
|
|
3853
4052
|
/**
|
|
3854
|
-
*
|
|
4053
|
+
* A generic method to make GET rest call
|
|
4054
|
+
*
|
|
4055
|
+
* @param endpoint
|
|
4056
|
+
* @param headers
|
|
4057
|
+
* @param parsed
|
|
4058
|
+
* @returns {Promise<Response | any>}
|
|
3855
4059
|
*/
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
}
|
|
3860
|
-
if (typeof value === "string") {
|
|
3861
|
-
const t = value.trim().toLowerCase();
|
|
3862
|
-
if (t === "true" || t === "false") {
|
|
3863
|
-
return;
|
|
3864
|
-
}
|
|
3865
|
-
throw new Error(
|
|
3866
|
-
`AbdbColumn: "${this._name}" expects boolean or "true"/"false" string, got ${JSON.stringify(value)}`
|
|
3867
|
-
);
|
|
3868
|
-
}
|
|
3869
|
-
throw new Error(
|
|
3870
|
-
`AbdbColumn: "${this._name}" expects boolean or "true"/"false" string, got ${typeof value}`
|
|
3871
|
-
);
|
|
4060
|
+
async get(endpoint, headers = {}, parsed = true) {
|
|
4061
|
+
const response = await this.makeRequest(endpoint, "GET", headers);
|
|
4062
|
+
return parsed ? await this.parseResponse(response) : response;
|
|
3872
4063
|
}
|
|
3873
4064
|
/**
|
|
3874
|
-
*
|
|
4065
|
+
* A generic method to make POST rest call
|
|
4066
|
+
*
|
|
4067
|
+
* @param endpoint
|
|
4068
|
+
* @param headers
|
|
4069
|
+
* @param payload
|
|
4070
|
+
* @param parsed
|
|
4071
|
+
* @returns {Promise<Response | any>}
|
|
3875
4072
|
*/
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
return;
|
|
3880
|
-
}
|
|
3881
|
-
throw new Error(`AbdbColumn: "${this._name}" expects a valid Date`);
|
|
3882
|
-
}
|
|
3883
|
-
if (typeof value === "number") {
|
|
3884
|
-
if (Number.isFinite(value)) {
|
|
3885
|
-
return;
|
|
3886
|
-
}
|
|
3887
|
-
throw new Error(
|
|
3888
|
-
`AbdbColumn: "${this._name}" expects a finite numeric timestamp, got ${value}`
|
|
3889
|
-
);
|
|
3890
|
-
}
|
|
3891
|
-
if (typeof value === "string") {
|
|
3892
|
-
const s = value.trim();
|
|
3893
|
-
if (!ISO_8601_DATETIME.test(s)) {
|
|
3894
|
-
throw new Error(
|
|
3895
|
-
`AbdbColumn: "${this._name}" expects ISO 8601 datetime string, got ${JSON.stringify(value)}`
|
|
3896
|
-
);
|
|
3897
|
-
}
|
|
3898
|
-
const t = Date.parse(s);
|
|
3899
|
-
if (Number.isNaN(t)) {
|
|
3900
|
-
throw new Error(
|
|
3901
|
-
`AbdbColumn: "${this._name}" expects ISO 8601 datetime string, got ${JSON.stringify(value)}`
|
|
3902
|
-
);
|
|
3903
|
-
}
|
|
3904
|
-
return;
|
|
3905
|
-
}
|
|
3906
|
-
throw new Error(
|
|
3907
|
-
`AbdbColumn: "${this._name}" expects Date, finite numeric timestamp, or ISO 8601 datetime string, got ${typeof value}`
|
|
3908
|
-
);
|
|
4073
|
+
async post(endpoint, headers = {}, payload = null, parsed = true) {
|
|
4074
|
+
const response = await this.makeRequest(endpoint, "POST", headers, payload);
|
|
4075
|
+
return parsed ? await this.parseResponse(response) : response;
|
|
3909
4076
|
}
|
|
3910
|
-
};
|
|
3911
|
-
__name(_AbdbColumn, "AbdbColumn");
|
|
3912
|
-
var AbdbColumn = _AbdbColumn;
|
|
3913
|
-
var column_default = AbdbColumn;
|
|
3914
|
-
|
|
3915
|
-
// src/framework/abdb/collection/index.ts
|
|
3916
|
-
import { init as initDb, DbError } from "@adobe/aio-lib-db";
|
|
3917
|
-
var _AbdbCollection = class _AbdbCollection {
|
|
3918
4077
|
/**
|
|
3919
|
-
*
|
|
4078
|
+
* A generic method to make PUT rest call
|
|
3920
4079
|
*
|
|
3921
|
-
* @param
|
|
3922
|
-
* @param
|
|
3923
|
-
* @
|
|
4080
|
+
* @param endpoint
|
|
4081
|
+
* @param headers
|
|
4082
|
+
* @param payload
|
|
4083
|
+
* @param parsed
|
|
4084
|
+
* @returns {Promise<Response | any>}
|
|
3924
4085
|
*/
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
if (callback) {
|
|
3929
|
-
callback(this);
|
|
3930
|
-
}
|
|
4086
|
+
async put(endpoint, headers = {}, payload = null, parsed = true) {
|
|
4087
|
+
const response = await this.makeRequest(endpoint, "PUT", headers, payload);
|
|
4088
|
+
return parsed ? await this.parseResponse(response) : response;
|
|
3931
4089
|
}
|
|
3932
4090
|
/**
|
|
3933
|
-
*
|
|
4091
|
+
* A generic method to make DELETE rest call
|
|
4092
|
+
*
|
|
4093
|
+
* @param endpoint
|
|
4094
|
+
* @param headers
|
|
4095
|
+
* @param parsed
|
|
4096
|
+
* @returns {Promise<Response | any>}
|
|
3934
4097
|
*/
|
|
3935
|
-
|
|
3936
|
-
|
|
4098
|
+
async delete(endpoint, headers = {}, parsed = true) {
|
|
4099
|
+
const response = await this.makeRequest(endpoint, "DELETE", headers);
|
|
4100
|
+
return parsed ? await this.parseResponse(response) : response;
|
|
3937
4101
|
}
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
4102
|
+
/**
|
|
4103
|
+
* A generic method to make rest call
|
|
4104
|
+
*
|
|
4105
|
+
* @param endpoint
|
|
4106
|
+
* @param method
|
|
4107
|
+
* @param headers
|
|
4108
|
+
* @param payload
|
|
4109
|
+
* @returns {Promise<any>}
|
|
4110
|
+
* @deprecated Use makeRequest() and parseResponse() methods instead
|
|
4111
|
+
*/
|
|
4112
|
+
async apiCall(endpoint, method = "POST", headers = {}, payload = null) {
|
|
4113
|
+
let options = {
|
|
4114
|
+
method,
|
|
4115
|
+
headers
|
|
4116
|
+
};
|
|
4117
|
+
if (payload !== null) {
|
|
4118
|
+
options = {
|
|
4119
|
+
...options,
|
|
4120
|
+
body: JSON.stringify(payload),
|
|
4121
|
+
headers: {
|
|
4122
|
+
...headers,
|
|
4123
|
+
"Content-Type": "application/json"
|
|
4124
|
+
}
|
|
4125
|
+
};
|
|
3942
4126
|
}
|
|
3943
|
-
const
|
|
3944
|
-
if (
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
}
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
columnOptions.isRequired = descriptionOrOptions.isRequired;
|
|
4127
|
+
const response = await fetch(endpoint, options);
|
|
4128
|
+
if (!response.ok) {
|
|
4129
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
4130
|
+
}
|
|
4131
|
+
if (response.status === 204 || response.headers?.get("content-length") === "0") {
|
|
4132
|
+
return null;
|
|
4133
|
+
}
|
|
4134
|
+
if (typeof response.json === "function") {
|
|
4135
|
+
const contentType = response.headers?.get("content-type");
|
|
4136
|
+
if (!contentType || contentType.includes("application/json") || contentType.includes("application/hal+json")) {
|
|
4137
|
+
return await response.json();
|
|
3955
4138
|
}
|
|
3956
4139
|
}
|
|
3957
|
-
|
|
3958
|
-
|
|
4140
|
+
if (typeof response.text === "function") {
|
|
4141
|
+
const text = await response.text();
|
|
4142
|
+
return text;
|
|
4143
|
+
}
|
|
4144
|
+
return null;
|
|
3959
4145
|
}
|
|
4146
|
+
};
|
|
4147
|
+
__name(_RestClient, "RestClient");
|
|
4148
|
+
var RestClient = _RestClient;
|
|
4149
|
+
var rest_client_default = RestClient;
|
|
4150
|
+
|
|
4151
|
+
// src/framework/runtime-api-gateway-service/index.ts
|
|
4152
|
+
var _RuntimeApiGatewayService = class _RuntimeApiGatewayService {
|
|
3960
4153
|
/**
|
|
3961
|
-
*
|
|
4154
|
+
* Creates an instance of RuntimeApiGatewayService
|
|
4155
|
+
*
|
|
4156
|
+
* @param namespace - The Adobe I/O Runtime namespace identifier
|
|
4157
|
+
* @param imsOrgId - IMS organization ID
|
|
4158
|
+
* @param imsToken - Bearer token string for authentication
|
|
4159
|
+
* @param logger - Optional logger instance for logging operations
|
|
4160
|
+
* @example
|
|
4161
|
+
* ```typescript
|
|
4162
|
+
* const service = new RuntimeApiGatewayService(
|
|
4163
|
+
* 'test-namespace',
|
|
4164
|
+
* 'org-id@AdobeOrg',
|
|
4165
|
+
* 'bearer-token-string',
|
|
4166
|
+
* logger
|
|
4167
|
+
* );
|
|
4168
|
+
* ```
|
|
3962
4169
|
*/
|
|
3963
|
-
|
|
3964
|
-
|
|
4170
|
+
constructor(namespace, imsOrgId, imsToken, logger = null) {
|
|
4171
|
+
this.namespace = namespace;
|
|
4172
|
+
this.imsOrgId = imsOrgId;
|
|
4173
|
+
this.imsToken = imsToken;
|
|
4174
|
+
this.restClient = new rest_client_default();
|
|
4175
|
+
this.customLogger = new custom_logger_default(logger);
|
|
3965
4176
|
}
|
|
3966
4177
|
/**
|
|
3967
|
-
*
|
|
4178
|
+
* Builds the complete API endpoint URL
|
|
3968
4179
|
*
|
|
3969
|
-
* @param
|
|
3970
|
-
* @returns The
|
|
4180
|
+
* @param endpoint - API endpoint path (e.g., 'v1/my-endpoint')
|
|
4181
|
+
* @returns The fully constructed endpoint URL
|
|
4182
|
+
* @private
|
|
3971
4183
|
*/
|
|
3972
|
-
|
|
3973
|
-
return this.
|
|
4184
|
+
buildEndpoint(endpoint) {
|
|
4185
|
+
return `${_RuntimeApiGatewayService.BASE_URL}/${this.namespace}/${endpoint}`;
|
|
3974
4186
|
}
|
|
3975
4187
|
/**
|
|
3976
|
-
*
|
|
4188
|
+
* Gets the authenticated headers for API requests
|
|
3977
4189
|
*
|
|
3978
|
-
* @
|
|
4190
|
+
* @returns Headers object including authentication
|
|
4191
|
+
* @private
|
|
3979
4192
|
*/
|
|
3980
|
-
|
|
3981
|
-
return
|
|
4193
|
+
getAuthenticatedHeaders() {
|
|
4194
|
+
return {
|
|
4195
|
+
"Content-Type": "application/json",
|
|
4196
|
+
Authorization: `Bearer ${this.imsToken}`,
|
|
4197
|
+
"x-gw-ims-org-id": this.imsOrgId
|
|
4198
|
+
};
|
|
3982
4199
|
}
|
|
3983
4200
|
/**
|
|
3984
|
-
*
|
|
3985
|
-
* column. Use {@link validateAll} when you need all errors reported at once.
|
|
4201
|
+
* Performs a GET request to the Runtime API Gateway
|
|
3986
4202
|
*
|
|
3987
|
-
*
|
|
3988
|
-
*
|
|
4203
|
+
* @param endpoint - API endpoint path (e.g., 'v1/my-endpoint')
|
|
4204
|
+
* @param additionalHeaders - Optional additional headers to include in the request
|
|
4205
|
+
* @returns A promise that resolves with the API response data
|
|
4206
|
+
* @throws {Error} If the API request fails
|
|
4207
|
+
* @example
|
|
4208
|
+
* ```typescript
|
|
4209
|
+
* const service = new RuntimeApiGatewayService(...);
|
|
4210
|
+
* const data = await service.get('v1/my-endpoint');
|
|
3989
4211
|
*
|
|
3990
|
-
*
|
|
3991
|
-
*
|
|
4212
|
+
* // With additional headers
|
|
4213
|
+
* const data = await service.get('v1/my-endpoint', { 'Custom-Header': 'value' });
|
|
4214
|
+
* ```
|
|
3992
4215
|
*/
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
4216
|
+
async get(endpoint, additionalHeaders = {}) {
|
|
4217
|
+
try {
|
|
4218
|
+
const url = this.buildEndpoint(endpoint);
|
|
4219
|
+
this.customLogger.info(`Performing GET request to: ${url}`);
|
|
4220
|
+
const headers = { ...this.getAuthenticatedHeaders(), ...additionalHeaders };
|
|
4221
|
+
this.customLogger.debug(`GET headers: ${JSON.stringify(headers)}`);
|
|
4222
|
+
const response = await this.restClient.get(url, headers, false);
|
|
4223
|
+
this.customLogger.debug(`GET response: ${JSON.stringify(response)}`);
|
|
4224
|
+
this.customLogger.info("GET request completed successfully");
|
|
4225
|
+
return response;
|
|
4226
|
+
} catch (error) {
|
|
4227
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
4228
|
+
this.customLogger.error(`GET request failed: ${errorMessage}`);
|
|
4229
|
+
throw new Error(`Runtime API gateway GET request failed: ${errorMessage}`);
|
|
3996
4230
|
}
|
|
3997
4231
|
}
|
|
3998
4232
|
/**
|
|
3999
|
-
*
|
|
4000
|
-
* stopping at the first failure. Useful at API boundaries where reporting all problems at once
|
|
4001
|
-
* gives a better developer or end-user experience.
|
|
4002
|
-
*
|
|
4003
|
-
* Returns an empty array when the record is fully valid.
|
|
4004
|
-
*
|
|
4005
|
-
* @param record - Key/value payload whose keys are column names
|
|
4006
|
-
* @returns Array of validation error messages, one per failing column (insertion order)
|
|
4233
|
+
* Performs a POST request to the Runtime API Gateway
|
|
4007
4234
|
*
|
|
4235
|
+
* @param endpoint - API endpoint path (e.g., 'v1/my-endpoint')
|
|
4236
|
+
* @param payload - The data to send in the request body
|
|
4237
|
+
* @param additionalHeaders - Optional additional headers to include in the request
|
|
4238
|
+
* @returns A promise that resolves with the API response data
|
|
4239
|
+
* @throws {Error} If the API request fails
|
|
4008
4240
|
* @example
|
|
4009
4241
|
* ```typescript
|
|
4010
|
-
* const
|
|
4011
|
-
*
|
|
4012
|
-
*
|
|
4013
|
-
*
|
|
4242
|
+
* const service = new RuntimeApiGatewayService(...);
|
|
4243
|
+
* const result = await service.post('v1/my-endpoint', { key: 'value' });
|
|
4244
|
+
*
|
|
4245
|
+
* // With additional headers
|
|
4246
|
+
* const result = await service.post('v1/my-endpoint', { key: 'value' }, { 'Custom-Header': 'value' });
|
|
4014
4247
|
* ```
|
|
4015
4248
|
*/
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4249
|
+
async post(endpoint, payload, additionalHeaders = {}) {
|
|
4250
|
+
try {
|
|
4251
|
+
const url = this.buildEndpoint(endpoint);
|
|
4252
|
+
this.customLogger.info(`Performing POST request to: ${url}`);
|
|
4253
|
+
this.customLogger.debug(`POST payload: ${JSON.stringify(payload)}`);
|
|
4254
|
+
const headers = { ...this.getAuthenticatedHeaders(), ...additionalHeaders };
|
|
4255
|
+
this.customLogger.debug(`POST headers: ${JSON.stringify(headers)}`);
|
|
4256
|
+
const response = await this.restClient.post(url, headers, payload, false);
|
|
4257
|
+
this.customLogger.debug(`POST response: ${JSON.stringify(response)}`);
|
|
4258
|
+
this.customLogger.info("POST request completed successfully");
|
|
4259
|
+
return response;
|
|
4260
|
+
} catch (error) {
|
|
4261
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
4262
|
+
this.customLogger.error(`POST request failed: ${errorMessage}`);
|
|
4263
|
+
throw new Error(`Runtime API gateway POST request failed: ${errorMessage}`);
|
|
4024
4264
|
}
|
|
4025
|
-
return errors;
|
|
4026
4265
|
}
|
|
4027
4266
|
/**
|
|
4028
|
-
*
|
|
4029
|
-
* handle and the **client**, then closes the client in a `finally` block.
|
|
4267
|
+
* Performs a PUT request to the Runtime API Gateway
|
|
4030
4268
|
*
|
|
4031
|
-
*
|
|
4032
|
-
*
|
|
4269
|
+
* @param endpoint - API endpoint path (e.g., 'v1/my-endpoint')
|
|
4270
|
+
* @param payload - The data to send in the request body
|
|
4271
|
+
* @param additionalHeaders - Optional additional headers to include in the request
|
|
4272
|
+
* @returns A promise that resolves with the API response data
|
|
4273
|
+
* @throws {Error} If the API request fails
|
|
4274
|
+
* @example
|
|
4275
|
+
* ```typescript
|
|
4276
|
+
* const service = new RuntimeApiGatewayService(...);
|
|
4277
|
+
* const updated = await service.put('v1/my-endpoint', { id: 1, name: 'updated' });
|
|
4033
4278
|
*
|
|
4034
|
-
*
|
|
4035
|
-
*
|
|
4036
|
-
*
|
|
4037
|
-
* @returns Promise resolving to the callback's return value
|
|
4038
|
-
* @throws {Error} On init, connect, `DbError`, or callback failure
|
|
4279
|
+
* // With additional headers
|
|
4280
|
+
* const updated = await service.put('v1/my-endpoint', { id: 1 }, { 'Custom-Header': 'value' });
|
|
4281
|
+
* ```
|
|
4039
4282
|
*/
|
|
4040
|
-
async
|
|
4041
|
-
let client;
|
|
4283
|
+
async put(endpoint, payload, additionalHeaders = {}) {
|
|
4042
4284
|
try {
|
|
4043
|
-
const
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4285
|
+
const url = this.buildEndpoint(endpoint);
|
|
4286
|
+
this.customLogger.info(`Performing PUT request to: ${url}`);
|
|
4287
|
+
this.customLogger.debug(`PUT payload: ${JSON.stringify(payload)}`);
|
|
4288
|
+
const headers = { ...this.getAuthenticatedHeaders(), ...additionalHeaders };
|
|
4289
|
+
this.customLogger.debug(`PUT headers: ${JSON.stringify(headers)}`);
|
|
4290
|
+
const response = await this.restClient.put(url, headers, payload, false);
|
|
4291
|
+
this.customLogger.debug(`PUT response: ${JSON.stringify(response)}`);
|
|
4292
|
+
this.customLogger.info("PUT request completed successfully");
|
|
4293
|
+
return response;
|
|
4047
4294
|
} catch (error) {
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
}
|
|
4051
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
4052
|
-
throw new Error(`AbdbCollection: unexpected error: ${detail}`);
|
|
4053
|
-
} finally {
|
|
4054
|
-
await client?.close();
|
|
4295
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
4296
|
+
this.customLogger.error(`PUT request failed: ${errorMessage}`);
|
|
4297
|
+
throw new Error(`Runtime API gateway PUT request failed: ${errorMessage}`);
|
|
4055
4298
|
}
|
|
4056
4299
|
}
|
|
4057
4300
|
/**
|
|
4058
|
-
*
|
|
4059
|
-
*
|
|
4060
|
-
* @throws {Error} If the name is empty or not `[a-zA-Z0-9_]`
|
|
4061
|
-
*/
|
|
4062
|
-
_validateCollectionName(name) {
|
|
4063
|
-
return this._validateIdentifier(
|
|
4064
|
-
name,
|
|
4065
|
-
'AbdbCollection: "name" is required and cannot be empty',
|
|
4066
|
-
"AbdbCollection: name must contain only alphanumeric characters and underscores"
|
|
4067
|
-
);
|
|
4068
|
-
}
|
|
4069
|
-
/**
|
|
4070
|
-
* Validates and normalizes a column name before {@link addColumn} stores it.
|
|
4301
|
+
* Performs a DELETE request to the Runtime API Gateway
|
|
4071
4302
|
*
|
|
4072
|
-
* @
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
}
|
|
4081
|
-
/**
|
|
4082
|
-
* Shared validation for collection and column identifiers.
|
|
4303
|
+
* @param endpoint - API endpoint path (e.g., 'v1/my-endpoint')
|
|
4304
|
+
* @param additionalHeaders - Optional additional headers to include in the request
|
|
4305
|
+
* @returns A promise that resolves with the API response data
|
|
4306
|
+
* @throws {Error} If the API request fails
|
|
4307
|
+
* @example
|
|
4308
|
+
* ```typescript
|
|
4309
|
+
* const service = new RuntimeApiGatewayService(...);
|
|
4310
|
+
* const deleted = await service.delete('v1/my-endpoint');
|
|
4083
4311
|
*
|
|
4084
|
-
*
|
|
4085
|
-
*
|
|
4086
|
-
*
|
|
4087
|
-
* @returns The trimmed identifier
|
|
4312
|
+
* // With additional headers
|
|
4313
|
+
* const deleted = await service.delete('v1/my-endpoint', { 'Custom-Header': 'value' });
|
|
4314
|
+
* ```
|
|
4088
4315
|
*/
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4316
|
+
async delete(endpoint, additionalHeaders = {}) {
|
|
4317
|
+
try {
|
|
4318
|
+
const url = this.buildEndpoint(endpoint);
|
|
4319
|
+
this.customLogger.info(`Performing DELETE request to: ${url}`);
|
|
4320
|
+
const headers = { ...this.getAuthenticatedHeaders(), ...additionalHeaders };
|
|
4321
|
+
this.customLogger.debug(`DELETE headers: ${JSON.stringify(headers)}`);
|
|
4322
|
+
const response = await this.restClient.delete(url, headers, false);
|
|
4323
|
+
this.customLogger.debug(`DELETE response: ${JSON.stringify(response)}`);
|
|
4324
|
+
this.customLogger.info("DELETE request completed successfully");
|
|
4325
|
+
return response;
|
|
4326
|
+
} catch (error) {
|
|
4327
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
4328
|
+
this.customLogger.error(`DELETE request failed: ${errorMessage}`);
|
|
4329
|
+
throw new Error(`Runtime API gateway DELETE request failed: ${errorMessage}`);
|
|
4096
4330
|
}
|
|
4097
|
-
return trimmed;
|
|
4098
4331
|
}
|
|
4099
4332
|
};
|
|
4100
|
-
__name(
|
|
4101
|
-
|
|
4102
|
-
|
|
4333
|
+
__name(_RuntimeApiGatewayService, "RuntimeApiGatewayService");
|
|
4334
|
+
/** Base URL for the Adobe I/O Runtime APIs */
|
|
4335
|
+
_RuntimeApiGatewayService.BASE_URL = "https://adobeioruntime.net/apis";
|
|
4336
|
+
var RuntimeApiGatewayService = _RuntimeApiGatewayService;
|
|
4103
4337
|
|
|
4104
4338
|
// src/integration/onboard-events/index.ts
|
|
4105
4339
|
import { Core as Core2 } from "@adobe/aio-sdk";
|
|
@@ -9229,6 +9463,7 @@ export {
|
|
|
9229
9463
|
collection_default as AbdbCollection,
|
|
9230
9464
|
column_default as AbdbColumn,
|
|
9231
9465
|
AbdbColumnType,
|
|
9466
|
+
abdb_repository_default as AbdbRepository,
|
|
9232
9467
|
AdminUiSdk,
|
|
9233
9468
|
adobe_auth_default as AdobeAuth,
|
|
9234
9469
|
adobe_commerce_client_default as AdobeCommerceClient,
|