@adobe/spacecat-shared-data-access 3.17.0 → 3.18.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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [@adobe/spacecat-shared-data-access-v3.18.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.17.0...@adobe/spacecat-shared-data-access-v3.18.0) (2026-03-12)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **data-access:** add saveMany() to BaseCollection for chunked bulk upsert ([#1428](https://github.com/adobe/spacecat-shared/issues/1428)) ([802bfec](https://github.com/adobe/spacecat-shared/commit/802bfecb21981e577fc1e72b38c24002edcd8bb1))
|
|
6
|
+
|
|
1
7
|
## [@adobe/spacecat-shared-data-access-v3.17.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-data-access-v3.16.0...@adobe/spacecat-shared-data-access-v3.17.0) (2026-03-12)
|
|
2
8
|
|
|
3
9
|
### Features
|
package/package.json
CHANGED
|
@@ -876,6 +876,48 @@ class BaseCollection {
|
|
|
876
876
|
}
|
|
877
877
|
}
|
|
878
878
|
|
|
879
|
+
/**
|
|
880
|
+
* Saves multiple model instances to the database in chunked batches.
|
|
881
|
+
* Each chunk is persisted via a single PostgREST upsert call, avoiding
|
|
882
|
+
* the thundering-herd problem caused by N concurrent individual saves.
|
|
883
|
+
*
|
|
884
|
+
* Chunks are processed sequentially. If a chunk fails, the error is
|
|
885
|
+
* propagated immediately — chunks already persisted are NOT rolled back.
|
|
886
|
+
* Callers must account for partial saves on failure.
|
|
887
|
+
*
|
|
888
|
+
* @param {BaseModel[]} items - Model instances with in-memory mutations to persist.
|
|
889
|
+
* @param {Object} [options] - Options.
|
|
890
|
+
* @param {number} [options.chunkSize=25] - Max items per upsert request.
|
|
891
|
+
* Keep low for entities with large payloads (e.g. Suggestion.data JSONB).
|
|
892
|
+
* @returns {Promise<void>}
|
|
893
|
+
* @throws {DataAccessError} If any chunk fails. Previously completed chunks
|
|
894
|
+
* are already persisted — callers must account for partial saves.
|
|
895
|
+
*/
|
|
896
|
+
async saveMany(items, { chunkSize = 25 } = {}) {
|
|
897
|
+
if (!isNonEmptyArray(items)) {
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
const effectiveChunkSize = Math.max(1, Math.floor(chunkSize)) || 25;
|
|
902
|
+
const totalChunks = Math.ceil(items.length / effectiveChunkSize);
|
|
903
|
+
|
|
904
|
+
if (totalChunks > 1) {
|
|
905
|
+
this.log.info(`[${this.entityName}] saveMany: saving ${items.length} items in ${totalChunks} chunks of ${effectiveChunkSize}`);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
for (let i = 0; i < items.length; i += effectiveChunkSize) {
|
|
909
|
+
const chunkIndex = Math.floor(i / effectiveChunkSize) + 1;
|
|
910
|
+
const chunk = items.slice(i, i + effectiveChunkSize);
|
|
911
|
+
try {
|
|
912
|
+
// eslint-disable-next-line no-await-in-loop
|
|
913
|
+
await this._saveMany(chunk);
|
|
914
|
+
} catch (error) {
|
|
915
|
+
this.log.error(`[${this.entityName}] saveMany: chunk ${chunkIndex}/${totalChunks} failed — ${i} of ${items.length} items already persisted`);
|
|
916
|
+
throw error;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
879
921
|
async _saveMany(items) {
|
|
880
922
|
if (!isNonEmptyArray(items)) {
|
|
881
923
|
const message = `Failed to save many [${this.entityName}]: items must be a non-empty array`;
|
|
@@ -62,7 +62,8 @@ export interface BatchGetOptions {
|
|
|
62
62
|
export interface BaseCollection<T extends BaseModel> {
|
|
63
63
|
_onCreate(item: T): void;
|
|
64
64
|
_onCreateMany(items: MultiStatusCreateResult<T>): void;
|
|
65
|
-
_saveMany(items: T[]): Promise<
|
|
65
|
+
_saveMany(items: T[]): Promise<void>;
|
|
66
|
+
saveMany(items: T[], options?: { chunkSize?: number }): Promise<void>;
|
|
66
67
|
all(sortKeys?: object, options?: QueryOptions): Promise<T[] | PaginatedResult<T>>;
|
|
67
68
|
allByIndexKeys(keys: object, options?: QueryOptions): Promise<T[] | PaginatedResult<T>>;
|
|
68
69
|
batchGetByKeys(keys: object[], options?: BatchGetOptions): Promise<{ data: T[]; unprocessed: object[] }>;
|