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