@adobe-commerce/aio-toolkit 1.1.1 → 1.2.0

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
@@ -3667,6 +3667,440 @@ __name(_RuntimeApiGatewayService, "RuntimeApiGatewayService");
3667
3667
  _RuntimeApiGatewayService.BASE_URL = "https://adobeioruntime.net/apis";
3668
3668
  var RuntimeApiGatewayService = _RuntimeApiGatewayService;
3669
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;
3701
+ }
3702
+ }
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;
3724
+ }
3725
+ /**
3726
+ * Returns whether a value is mandatory for {@link AbdbColumn#validate}.
3727
+ *
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.
3751
+ *
3752
+ * Passing `description: ' '` (blank / whitespace) clears the description on the new column.
3753
+ *
3754
+ * @param overrides - Partial options; omitted keys keep current values
3755
+ * @returns A new {@link AbdbColumn} instance
3756
+ */
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 });
3766
+ }
3767
+ return new _AbdbColumn({ name, type, isRequired });
3768
+ }
3769
+ /**
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
3780
+ *
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
3783
+ */
3784
+ validate(value) {
3785
+ if (this._isMissingValue(value)) {
3786
+ if (this._isRequired) {
3787
+ throw new Error(`AbdbColumn: "${this._name}" is required`);
3788
+ }
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}"`);
3807
+ }
3808
+ }
3809
+ }
3810
+ /**
3811
+ * Whether `value` counts as absent for requiredness checks (before type validation).
3812
+ *
3813
+ * @returns `true` for `undefined`, `null`, or a string that is empty after trim
3814
+ */
3815
+ _isMissingValue(value) {
3816
+ if (value === void 0 || value === null) {
3817
+ return true;
3818
+ }
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}`);
3827
+ }
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;
3836
+ }
3837
+ throw new Error(`AbdbColumn: "${this._name}" expects a finite number, got ${String(value)}`);
3838
+ }
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
+ );
3848
+ }
3849
+ throw new Error(
3850
+ `AbdbColumn: "${this._name}" expects a finite number or numeric string, got ${typeof value}`
3851
+ );
3852
+ }
3853
+ /**
3854
+ * @throws {Error} When not a boolean or `"true"` / `"false"` string
3855
+ */
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
+ );
3872
+ }
3873
+ /**
3874
+ * @throws {Error} When not a valid `Date`, finite timestamp, or ISO 8601 string
3875
+ */
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
+ );
3909
+ }
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
+ /**
3919
+ * Creates a collection and optionally configures it in a callback (e.g. chained {@link addColumn} calls).
3920
+ *
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
3924
+ */
3925
+ constructor(name, callback) {
3926
+ this._name = this._validateCollectionName(name);
3927
+ this._columns = /* @__PURE__ */ new Map();
3928
+ if (callback) {
3929
+ callback(this);
3930
+ }
3931
+ }
3932
+ /**
3933
+ * Returns this collection's name.
3934
+ */
3935
+ getName() {
3936
+ return this._name;
3937
+ }
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}"`);
3942
+ }
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;
3955
+ }
3956
+ }
3957
+ this._columns.set(trimmed, new column_default(columnOptions));
3958
+ return this;
3959
+ }
3960
+ /**
3961
+ * Returns a defensive copy of columns in insertion order.
3962
+ */
3963
+ getColumns() {
3964
+ return Array.from(this._columns.values());
3965
+ }
3966
+ /**
3967
+ * Returns the column registered under `name`, or `undefined` if no such column exists.
3968
+ *
3969
+ * @param name - Column name to look up (exact match after trimming)
3970
+ * @returns The {@link AbdbColumn} instance, or `undefined`
3971
+ */
3972
+ getColumn(name) {
3973
+ return this._columns.get(name.trim());
3974
+ }
3975
+ /**
3976
+ * Returns `true` when a column with the given name has been registered.
3977
+ *
3978
+ * @param name - Column name to check (exact match after trimming)
3979
+ */
3980
+ hasColumn(name) {
3981
+ return this._columns.has(name.trim());
3982
+ }
3983
+ /**
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.
3986
+ *
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.
3989
+ *
3990
+ * @param record - Key/value payload whose keys are column names
3991
+ * @throws {Error} When any column validation fails (requiredness or type)
3992
+ */
3993
+ validate(record) {
3994
+ for (const col of this._columns.values()) {
3995
+ col.validate(record[col.getName()]);
3996
+ }
3997
+ }
3998
+ /**
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)
4007
+ *
4008
+ * @example
4009
+ * ```typescript
4010
+ * const errors = orders.validateAll(payload);
4011
+ * if (errors.length > 0) {
4012
+ * return { status: 400, body: { errors } };
4013
+ * }
4014
+ * ```
4015
+ */
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
+ }
4024
+ }
4025
+ return errors;
4026
+ }
4027
+ /**
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.
4030
+ *
4031
+ * **Errors:** `DbError` instances are rethrown as `AbdbCollection: database error: …`;
4032
+ * any other value is wrapped as `AbdbCollection: unexpected error: …`.
4033
+ *
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
4039
+ */
4040
+ async run(callback, token, region = "amer") {
4041
+ let client;
4042
+ 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);
4047
+ } 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();
4055
+ }
4056
+ }
4057
+ /**
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.
4071
+ *
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.
4083
+ *
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
4088
+ */
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);
4096
+ }
4097
+ return trimmed;
4098
+ }
4099
+ };
4100
+ __name(_AbdbCollection, "AbdbCollection");
4101
+ var AbdbCollection = _AbdbCollection;
4102
+ var collection_default = AbdbCollection;
4103
+
3670
4104
  // src/integration/onboard-events/index.ts
3671
4105
  import { Core as Core2 } from "@adobe/aio-sdk";
3672
4106
 
@@ -8792,6 +9226,9 @@ var _AdminUiSdk = class _AdminUiSdk {
8792
9226
  __name(_AdminUiSdk, "AdminUiSdk");
8793
9227
  var AdminUiSdk = _AdminUiSdk;
8794
9228
  export {
9229
+ collection_default as AbdbCollection,
9230
+ column_default as AbdbColumn,
9231
+ AbdbColumnType,
8795
9232
  AdminUiSdk,
8796
9233
  adobe_auth_default as AdobeAuth,
8797
9234
  adobe_commerce_client_default as AdobeCommerceClient,