@adobe-commerce/aio-toolkit 1.2.1 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,99 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.2] - 2026-04-01
9
+
10
+ ### 🐛 Bug Fixes
11
+
12
+ - **fix(abdb): Fixed `AbdbRepository` update path — `_updated_at` was placed outside `$set`**
13
+
14
+ `save(payload, id)` delegated to `updateOne` with `{ $set: payload }` as the payload, but `updateOne` then spread that into `{ $set: payload, _updated_at: now }`. The `_updated_at` key landed at the top level alongside `$set`, making it an invalid MongoDB update document. The `$set` wrapping is now done inside `updateOne` and `update`, so timestamps are always nested correctly.
15
+
16
+ - **fix(abdb): Fixed `update` method passing an array to `updateMany`**
17
+
18
+ `update` (previously `updateAll`) accepted `Array<Partial<T>> | Partial<T>` and always converted the input to an array before passing it as the second argument to `updateMany`. MongoDB's `updateMany` expects a **single** update document, not an array — this caused silent failures at runtime. The method now accepts a single `Partial<T>` and wraps it in `{ $set: ... }`.
19
+
20
+ - **fix(abdb): Fixed full schema validation on partial update payloads**
21
+
22
+ `updateOne` and `update` were calling `this._collection.validate()` (full validation — all required fields must be present) on update payloads. This caused spurious validation errors when patching a subset of fields. Both methods now use `_validatePartial`, which only checks the columns present in the payload.
23
+
24
+ ### ✨ Features
25
+
26
+ - **feat(abdb): Expanded `AbdbRepository` API with granular single- and bulk-operation methods**
27
+
28
+ Five new public methods covering the full MongoDB CRUD surface:
29
+
30
+ | New method | Description |
31
+ |---|---|
32
+ | `findById(id)` | Shorthand for `findOne({ _id: new ObjectId(id) })` |
33
+ | `insertOne(payload)` | Single-document insert via `insertOne`; stamps timestamps and validates |
34
+ | `updateOne(payload, filter?, options?)` | Single-document update via `updateOne` with `$set` wrapping and partial validation |
35
+ | `deleteOne(filter?)` | Single-document delete via `deleteOne`; returns raw result |
36
+ | `deleteById(id)` | Shorthand for `deleteOne({ _id: new ObjectId(id) })` |
37
+
38
+ - **feat(abdb): `save` delegates to `insertOne` / `updateOne`** — eliminates duplicated timestamp-stamping and validation logic.
39
+
40
+ - **feat(abdb): Split `exists` into `isIdExists` and `exists(filter, options?)`**
41
+
42
+ The original `exists(id: string)` only supported lookup by `_id`. It has been renamed to `isIdExists(id)` (preserving the empty-id guard that returns `false` without hitting the DB), and a new general-purpose `exists(filter?, options?)` method has been added that accepts any filter and proxies to `count`.
43
+
44
+ - **feat(abdb): `count` now accepts an optional `options` parameter**
45
+
46
+ `count(filter?, options?)` now forwards `options` to the underlying `countDocuments` call, enabling fine-grained control such as `{ limit: 1 }` for fast existence probes.
47
+
48
+ ### ⚠️ Breaking Changes
49
+
50
+ - **`exists(id: string)` renamed to `isIdExists(id: string)`**
51
+
52
+ `exists` now accepts a general-purpose filter object. Any caller using `repo.exists(someId)` must switch to `repo.isIdExists(someId)`.
53
+
54
+ - **`delete(id: string)` → `delete(filter: AbdbRepositoryFilter)`**
55
+
56
+ `delete` previously accepted a string `_id` and silently no-oped when the id was empty. It now accepts a filter object and uses `deleteMany` under the hood (consistent with `deleteAll`). Use `deleteById(id)` for the old single-by-id behaviour.
57
+
58
+ - **`insertAll` renamed to `insert`** — identical behaviour, new name aligns with `insertOne`.
59
+
60
+ - **`updateAll` renamed to `update`** — identical behaviour (plus bug fixes above), new name aligns with `updateOne`.
61
+
62
+ - **Return types of all mutation methods changed to `Promise<Record<string, any>>`**
63
+
64
+ `save`, `insert`, `insertOne`, `update`, `updateOne`, `delete`, `deleteOne`, `deleteById`, and `deleteAll` previously returned `Promise<string>`, `Promise<string[]>`, `Promise<number>`, or `Promise<void>`. All now return the raw MongoDB result document so callers have access to `acknowledged`, `insertedId`, `insertedIds`, `modifiedCount`, `deletedCount`, etc.
65
+
66
+ ### 💡 Migration Guide
67
+
68
+ ```typescript
69
+ // existence check by id — OLD
70
+ const found = await repo.exists(id);
71
+ // existence check by id — NEW
72
+ const found = await repo.isIdExists(id);
73
+
74
+ // general existence check (new) — by any filter
75
+ const active = await repo.exists({ status: 'active' });
76
+
77
+ // delete by id — OLD
78
+ await repo.delete(id);
79
+ // delete by id — NEW
80
+ await repo.deleteById(id);
81
+
82
+ // bulk insert — OLD
83
+ const ids = await repo.insertAll([...]);
84
+ // bulk insert — NEW
85
+ const result = await repo.insert([...]); // result.insertedIds contains the ids
86
+
87
+ // bulk update — OLD
88
+ await repo.updateAll({ active: false }, { email: 'bob@example.com' });
89
+ // bulk update — NEW
90
+ await repo.update({ active: false }, { email: 'bob@example.com' });
91
+
92
+ // return value of save (insert path) — OLD: string _id
93
+ const id = await repo.save({ name: 'Jane', email: 'jane@example.com' });
94
+ // return value of save (insert path) — NEW: raw insertOne result
95
+ const result = await repo.save({ name: 'Jane', email: 'jane@example.com' });
96
+ // result.insertedId contains the new _id
97
+ ```
98
+
99
+ ---
100
+
8
101
  ## [1.2.1] - 2026-03-31
9
102
 
10
103
  ### ✨ Features
package/README.md CHANGED
@@ -848,7 +848,7 @@ A generic CRUD repository that wraps an `AbdbCollection` and holds the IMS token
848
848
  ##### Basic usage
849
849
 
850
850
  ```javascript
851
- const { AbdbRepository, AbdbColumnType } = require('@adobe-commerce/aio-toolkit');
851
+ const { AbdbRepository } = require('@adobe-commerce/aio-toolkit');
852
852
  const { generateAccessToken } = require('@adobe/aio-sdk').Core.AuthClient;
853
853
  const UserCollection = require('@lib/UserCollection');
854
854
 
@@ -858,11 +858,12 @@ exports.main = async (params) => {
858
858
 
859
859
  const repo = new AbdbRepository(new UserCollection(), accessToken);
860
860
 
861
- // Insert — returns the new document _id
862
- const id = await repo.save({ first_name: 'Jane', last_name: 'Doe', email: 'jane@example.com' });
861
+ // Insert — returns raw insertOne result (result.insertedId contains the new _id)
862
+ const insertResult = await repo.save({ first_name: 'Jane', last_name: 'Doe', email: 'jane@example.com' });
863
+ const id = insertResult.insertedId;
863
864
 
864
- // Read one by _id
865
- const doc = await repo.findOne({ _id: id });
865
+ // Read by _id (shorthand)
866
+ const doc = await repo.findById(id);
866
867
 
867
868
  // Read one by any field
868
869
  const byEmail = await repo.findOne({ email: 'jane@example.com' });
@@ -874,8 +875,8 @@ exports.main = async (params) => {
874
875
  // Partial update — only provided fields are validated; required fields already in the DB are not re-checked
875
876
  await repo.save({ first_name: 'Janet' }, id);
876
877
 
877
- // Delete
878
- await repo.delete(id);
878
+ // Delete by _id
879
+ await repo.deleteById(id);
879
880
  };
880
881
  ```
881
882
 
@@ -896,21 +897,26 @@ These are added once per collection instance, so re-using the same collection ac
896
897
 
897
898
  | Method | Description | Returns |
898
899
  |---|---|---|
899
- | `save(payload)` | Insert a new document (full validation) | `Promise<string>`new `_id` |
900
- | `save(payload, id)` | Partial update by `_id` (only present fields validated) | `Promise<string>`same `id` |
901
- | `findOne(filter)` | First document matching filter, e.g. `{ _id: 'x' }` | `Promise<T \| null>` |
900
+ | `save(payload)` | Insert via `insertOne` (full validation, stamps both timestamps) | `Promise<Record<string, any>>` raw `insertOne` result |
901
+ | `save(payload, id)` | Update via `updateOne` with `upsert: true` (partial validation, stamps `_updated_at`) | `Promise<Record<string, any>>` raw `updateOne` result |
902
+ | `insertOne(payload)` | Single-document insert (full validation, stamps both timestamps) | `Promise<Record<string, any>>` raw `insertOne` result (e.g. `insertedId`) |
903
+ | `updateOne(payload, filter?, options?)` | Single-document update via `{ $set: payload }` (partial validation, stamps `_updated_at`) | `Promise<Record<string, any>>` — raw `updateOne` result (e.g. `modifiedCount`) |
904
+ | `findOne(filter)` | First document matching filter, e.g. `{ email: 'a@b.com' }` | `Promise<T \| null>` |
905
+ | `findById(id)` | Shorthand for `findOne({ _id: new ObjectId(id) })` | `Promise<T \| null>` |
902
906
  | `find(filter?)` | All documents matching filter (default: all) | `Promise<T[]>` |
903
- | `exists(id)` | Lightweight existence check via `countDocuments` | `Promise<boolean>` |
904
- | `delete(id)` | Delete document by `_id`; no-ops for empty id | `Promise<void>` |
905
- | `count(filter?)` | Count matching documents (default: all) | `Promise<number>` |
907
+ | `isIdExists(id)` | Returns `true` if a document with the given `_id` exists (no-op for empty id) | `Promise<boolean>` |
908
+ | `exists(filter?, options?)` | Returns `true` if any document matching `filter` exists via `countDocuments` | `Promise<boolean>` |
909
+ | `deleteOne(filter?)` | Delete first matching document via `deleteOne` | `Promise<Record<string, any>>` — raw `deleteOne` result |
910
+ | `deleteById(id)` | Shorthand for `deleteOne({ _id: new ObjectId(id) })` | `Promise<Record<string, any>>` |
911
+ | `count(filter?, options?)` | Count matching documents via `countDocuments` (default: all) | `Promise<number>` |
906
912
 
907
913
  **Bulk operations (single DB round-trip each):**
908
914
 
909
915
  | Method | Description | Returns |
910
916
  |---|---|---|
911
- | `insertAll(payloads[])` | Bulk insert via `insertMany` — each payload stamped and validated | `Promise<string[]>`inserted ids |
912
- | `updateAll(payload, filter?)` | Bulk partial update via `updateMany` — stamps `_updated_at` | `Promise<void>` |
913
- | `deleteAll(filter?)` | Bulk delete via `deleteMany` (default: all documents) | `Promise<void>` |
917
+ | `insert(payloads[])` | Bulk insert via `insertMany` — each payload stamped and validated | `Promise<Record<string, any>>` raw `insertMany` result (e.g. `insertedIds`) |
918
+ | `update(payload, filter?, options?)` | Bulk update via `updateMany` with `{ $set: payload }` — stamps `_updated_at`, partial validation | `Promise<Record<string, any>>` — raw `updateMany` result (e.g. `modifiedCount`) |
919
+ | `delete(filter?)` | Bulk delete via `deleteMany` (default: all documents) | `Promise<Record<string, any>>` — raw `deleteMany` result (e.g. `deletedCount`) |
914
920
 
915
921
  **Accessors:**
916
922
 
@@ -930,19 +936,22 @@ const repo = new AbdbRepository(new UserCollection(), accessToken, 'emea');
930
936
 
931
937
  ```javascript
932
938
  // Insert multiple documents in one DB call
933
- const ids = await repo.insertAll([
939
+ const result = await repo.insert([
934
940
  { first_name: 'Alice', last_name: 'Smith', email: 'alice@example.com' },
935
941
  { first_name: 'Bob', last_name: 'Jones', email: 'bob@example.com' },
936
942
  ]);
943
+ console.log(result.insertedIds); // array of inserted _id values
937
944
 
938
945
  // Update all matching documents in one DB call
939
- await repo.updateAll({ active: false }, { email: 'bob@example.com' });
946
+ const updateResult = await repo.update({ active: false }, { email: 'bob@example.com' });
947
+ console.log(updateResult.modifiedCount); // number of documents updated
940
948
 
941
949
  // Delete all matching documents in one DB call
942
- await repo.deleteAll({ active: false });
950
+ const deleteResult = await repo.delete({ active: false });
951
+ console.log(deleteResult.deletedCount); // number of documents deleted
943
952
 
944
953
  // Delete everything in the collection
945
- await repo.deleteAll();
954
+ await repo.delete();
946
955
  ```
947
956
 
948
957
  ### 🏪 Commerce Components
package/dist/index.d.mts CHANGED
@@ -281,13 +281,18 @@ declare class AbdbRepository<T extends AbdbRecord = AbdbRecord> {
281
281
  getCollection(): AbdbCollection;
282
282
  find(filter?: AbdbRepositoryFilter): Promise<T[]>;
283
283
  findOne(filter: AbdbRepositoryFilter): Promise<T | null>;
284
- exists(id: string): Promise<boolean>;
285
- count(filter?: AbdbRepositoryFilter): Promise<number>;
286
- save(payload?: Partial<T>, id?: string): Promise<string>;
287
- delete(id?: string): Promise<void>;
288
- insertAll(payloads: Array<Partial<T>>): Promise<string[]>;
289
- updateAll(payload: Partial<T>, filter?: AbdbRepositoryFilter): Promise<void>;
290
- deleteAll(filter?: AbdbRepositoryFilter): Promise<void>;
284
+ findById(id: string): Promise<T | null>;
285
+ delete(filter?: AbdbRepositoryFilter): Promise<Record<string, any>>;
286
+ deleteOne(filter?: AbdbRepositoryFilter): Promise<Record<string, any>>;
287
+ deleteById(id: string): Promise<Record<string, any>>;
288
+ insert(payloads: Array<Partial<T>>): Promise<Record<string, any>>;
289
+ insertOne(payload: Partial<T>): Promise<Record<string, any>>;
290
+ update(payload: Partial<T>, filter?: AbdbRepositoryFilter, options?: Record<string, any>): Promise<Record<string, any>>;
291
+ updateOne(payload: Partial<T>, filter?: AbdbRepositoryFilter, options?: Record<string, any>): Promise<Record<string, any>>;
292
+ save(payload?: Partial<T>, id?: string): Promise<Record<string, any>>;
293
+ isIdExists(id: string): Promise<boolean>;
294
+ exists(filter?: AbdbRepositoryFilter, options?: Record<string, any>): Promise<boolean>;
295
+ count(filter?: AbdbRepositoryFilter, options?: Record<string, any>): Promise<number>;
291
296
  private _validatePartial;
292
297
  }
293
298
 
package/dist/index.d.ts CHANGED
@@ -281,13 +281,18 @@ declare class AbdbRepository<T extends AbdbRecord = AbdbRecord> {
281
281
  getCollection(): AbdbCollection;
282
282
  find(filter?: AbdbRepositoryFilter): Promise<T[]>;
283
283
  findOne(filter: AbdbRepositoryFilter): Promise<T | null>;
284
- exists(id: string): Promise<boolean>;
285
- count(filter?: AbdbRepositoryFilter): Promise<number>;
286
- save(payload?: Partial<T>, id?: string): Promise<string>;
287
- delete(id?: string): Promise<void>;
288
- insertAll(payloads: Array<Partial<T>>): Promise<string[]>;
289
- updateAll(payload: Partial<T>, filter?: AbdbRepositoryFilter): Promise<void>;
290
- deleteAll(filter?: AbdbRepositoryFilter): Promise<void>;
284
+ findById(id: string): Promise<T | null>;
285
+ delete(filter?: AbdbRepositoryFilter): Promise<Record<string, any>>;
286
+ deleteOne(filter?: AbdbRepositoryFilter): Promise<Record<string, any>>;
287
+ deleteById(id: string): Promise<Record<string, any>>;
288
+ insert(payloads: Array<Partial<T>>): Promise<Record<string, any>>;
289
+ insertOne(payload: Partial<T>): Promise<Record<string, any>>;
290
+ update(payload: Partial<T>, filter?: AbdbRepositoryFilter, options?: Record<string, any>): Promise<Record<string, any>>;
291
+ updateOne(payload: Partial<T>, filter?: AbdbRepositoryFilter, options?: Record<string, any>): Promise<Record<string, any>>;
292
+ save(payload?: Partial<T>, id?: string): Promise<Record<string, any>>;
293
+ isIdExists(id: string): Promise<boolean>;
294
+ exists(filter?: AbdbRepositoryFilter, options?: Record<string, any>): Promise<boolean>;
295
+ count(filter?: AbdbRepositoryFilter, options?: Record<string, any>): Promise<number>;
291
296
  private _validatePartial;
292
297
  }
293
298