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