strapi-plugin-bulk-updater 1.4.2 → 1.4.3

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.
@@ -908,9 +908,72 @@ function discoverComponentAttributes(strapi, compUid, prefix, currentDepth, maxD
908
908
  }
909
909
  return Array.from(new Set(discoveredAttrs));
910
910
  }
911
+ function parseDynamicZonePath(path) {
912
+ if (!path) return { cleanPath: "", rootKey: "", subPath: "" };
913
+ const bracketMatch = path.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
914
+ if (bracketMatch) {
915
+ const rootKey = bracketMatch[1];
916
+ const selector = bracketMatch[2];
917
+ const subPath = bracketMatch[3];
918
+ const cleanPath = `${rootKey}.${subPath}`;
919
+ if (/^\d+$/.test(selector)) {
920
+ return { cleanPath, rootKey, subPath, targetIndex: parseInt(selector, 10) };
921
+ }
922
+ return { cleanPath, rootKey, subPath, targetComponentUid: selector };
923
+ }
924
+ const parts = path.split(".");
925
+ return {
926
+ cleanPath: path,
927
+ rootKey: parts[0] || "",
928
+ subPath: parts.slice(1).join(".")
929
+ };
930
+ }
931
+ function formatComponentName(compUid) {
932
+ if (!compUid) return "Component";
933
+ const name = compUid.includes(".") ? compUid.split(".")[1] : compUid;
934
+ return name.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
935
+ }
936
+ function getAllNestedValues(obj, path) {
937
+ if (!obj || !path) return [];
938
+ const parsed = parseDynamicZonePath(path);
939
+ const parts = parsed.cleanPath.split(".");
940
+ const [head, ...tail] = parts;
941
+ const rootTarget = obj[head];
942
+ if (Array.isArray(rootTarget)) {
943
+ const results = [];
944
+ rootTarget.forEach((item, idx) => {
945
+ if (!item || typeof item !== "object") return;
946
+ if (parsed.targetIndex !== void 0 && idx !== parsed.targetIndex) return;
947
+ if (parsed.targetComponentUid && item.__component !== parsed.targetComponentUid) return;
948
+ const subVal = tail.length > 0 ? getDeepProperty(item, tail.join(".")) : item;
949
+ if (subVal !== void 0) {
950
+ results.push({
951
+ index: idx,
952
+ componentUid: item.__component,
953
+ componentName: formatComponentName(item.__component),
954
+ value: subVal,
955
+ path: `${head}[#${idx}${item.__component ? ` ${formatComponentName(item.__component)}` : ""}].${tail.join(".")}`
956
+ });
957
+ }
958
+ });
959
+ return results;
960
+ }
961
+ const singleVal = getDeepProperty(obj, parsed.cleanPath);
962
+ if (singleVal !== void 0) {
963
+ return [
964
+ {
965
+ index: 0,
966
+ value: singleVal,
967
+ path: parsed.cleanPath
968
+ }
969
+ ];
970
+ }
971
+ return [];
972
+ }
911
973
  function isDeepComponentField(contentType, allComponents, attribute) {
912
- if (!attribute || !attribute.includes(".")) return false;
913
- const parts = attribute.split(".");
974
+ if (!attribute || !attribute.includes(".") && !attribute.includes("[")) return false;
975
+ const parsed = parseDynamicZonePath(attribute);
976
+ const parts = parsed.cleanPath.split(".");
914
977
  function checkPath(currentSchema, pathParts) {
915
978
  if (pathParts.length === 0) return true;
916
979
  if (!currentSchema || !currentSchema.attributes) return false;
@@ -926,6 +989,10 @@ function isDeepComponentField(contentType, allComponents, attribute) {
926
989
  }
927
990
  if (attrDef.type === "dynamiczone") {
928
991
  const compUids = Array.isArray(attrDef.components) ? attrDef.components : [];
992
+ if (parsed.targetComponentUid) {
993
+ const comp = allComponents[parsed.targetComponentUid];
994
+ return comp ? checkPath(comp, tail) : false;
995
+ }
929
996
  return compUids.some((compUid) => {
930
997
  const comp = allComponents[compUid];
931
998
  return checkPath(comp, tail);
@@ -937,8 +1004,9 @@ function isDeepComponentField(contentType, allComponents, attribute) {
937
1004
  }
938
1005
  function getLeafAttributeSchema(contentType, allComponents, attribute) {
939
1006
  if (!attribute) return null;
940
- if (!attribute.includes(".")) return contentType?.attributes?.[attribute] || null;
941
- const parts = attribute.split(".");
1007
+ const parsed = parseDynamicZonePath(attribute);
1008
+ if (!parsed.cleanPath.includes(".")) return contentType?.attributes?.[parsed.cleanPath] || null;
1009
+ const parts = parsed.cleanPath.split(".");
942
1010
  function resolveLeaf(currentSchema, pathParts) {
943
1011
  if (!currentSchema || !currentSchema.attributes) return null;
944
1012
  const [head, ...tail] = pathParts;
@@ -952,6 +1020,9 @@ function getLeafAttributeSchema(contentType, allComponents, attribute) {
952
1020
  }
953
1021
  if (attrDef.type === "dynamiczone") {
954
1022
  const compUids = Array.isArray(attrDef.components) ? attrDef.components : [];
1023
+ if (parsed.targetComponentUid) {
1024
+ return resolveLeaf(allComponents[parsed.targetComponentUid], tail);
1025
+ }
955
1026
  for (const compUid of compUids) {
956
1027
  const found = resolveLeaf(allComponents[compUid], tail);
957
1028
  if (found) return found;
@@ -961,22 +1032,28 @@ function getLeafAttributeSchema(contentType, allComponents, attribute) {
961
1032
  }
962
1033
  return resolveLeaf(contentType, parts);
963
1034
  }
964
- function setDeepProperty(obj, pathParts, value) {
1035
+ function setDeepProperty(obj, pathParts, value, options) {
965
1036
  if (pathParts.length === 0) return value;
966
1037
  const [head, ...tail] = pathParts;
967
1038
  if (Array.isArray(obj)) {
968
1039
  if (/^\d+$/.test(head)) {
969
1040
  const idx = parseInt(head, 10);
970
1041
  const copy = [...obj];
971
- copy[idx] = setDeepProperty(copy[idx] || {}, tail, value);
1042
+ copy[idx] = setDeepProperty(copy[idx] || {}, tail, value, options);
972
1043
  return copy;
973
1044
  }
974
- return obj.map((item) => {
1045
+ return obj.map((item, idx) => {
975
1046
  if (!item || typeof item !== "object") return item;
1047
+ if (options?.targetIndexes && options.targetIndexes.length > 0) {
1048
+ if (!options.targetIndexes.includes(idx)) return item;
1049
+ }
1050
+ if (options?.targetComponentUid) {
1051
+ if (item.__component !== options.targetComponentUid) return item;
1052
+ }
976
1053
  if (head in item || tail.length > 0 && typeof item[head] === "object") {
977
1054
  return {
978
1055
  ...item,
979
- [head]: setDeepProperty(item[head], tail, value)
1056
+ [head]: setDeepProperty(item[head], tail, value, options)
980
1057
  };
981
1058
  }
982
1059
  return item;
@@ -988,16 +1065,17 @@ function setDeepProperty(obj, pathParts, value) {
988
1065
  } else {
989
1066
  const subTarget = current[head];
990
1067
  if (Array.isArray(subTarget)) {
991
- current[head] = setDeepProperty(subTarget, tail, value);
1068
+ current[head] = setDeepProperty(subTarget, tail, value, options);
992
1069
  } else {
993
- current[head] = setDeepProperty(subTarget || {}, tail, value);
1070
+ current[head] = setDeepProperty(subTarget || {}, tail, value, options);
994
1071
  }
995
1072
  }
996
1073
  return current;
997
1074
  }
998
1075
  function getDeepProperty(obj, path) {
999
1076
  if (!obj || !path) return void 0;
1000
- const parts = path.split(".");
1077
+ const parsed = parseDynamicZonePath(path);
1078
+ const parts = parsed.cleanPath.split(".");
1001
1079
  function extract(current, remainingParts) {
1002
1080
  if (current === void 0 || current === null) return void 0;
1003
1081
  if (remainingParts.length === 0) return current;
@@ -1006,7 +1084,10 @@ function getDeepProperty(obj, path) {
1006
1084
  if (/^\d+$/.test(head)) {
1007
1085
  return extract(current[parseInt(head, 10)], tail);
1008
1086
  }
1009
- for (const item of current) {
1087
+ for (let idx = 0; idx < current.length; idx++) {
1088
+ const item = current[idx];
1089
+ if (parsed.targetIndex !== void 0 && idx !== parsed.targetIndex) continue;
1090
+ if (parsed.targetComponentUid && item?.__component !== parsed.targetComponentUid) continue;
1010
1091
  const found = extract(item?.[head], tail);
1011
1092
  if (found !== void 0 && found !== null) return found;
1012
1093
  }
@@ -1171,7 +1252,12 @@ async function executeBatchInternal(strapi, {
1171
1252
  for (const item of selectedEntries) {
1172
1253
  if (!item.uid || !item.documentId) continue;
1173
1254
  const key = `${item.uid}@@@${item.documentId}`;
1174
- targetsMap.set(key, { uid: item.uid, documentId: item.documentId });
1255
+ targetsMap.set(key, {
1256
+ uid: item.uid,
1257
+ documentId: item.documentId,
1258
+ dynamicZoneIndexes: item.dynamicZoneIndexes,
1259
+ targetComponentUid: item.targetComponentUid
1260
+ });
1175
1261
  }
1176
1262
  if (targetsMap.size === 0) {
1177
1263
  throw new Error("No collections or entries were selected for update.");
@@ -1230,10 +1316,13 @@ async function executeBatchInternal(strapi, {
1230
1316
  try {
1231
1317
  const currentEntry = await getDocumentDeduplicated(target.uid, target.documentId);
1232
1318
  const entryLabel = currentEntry?.name || currentEntry?.title || currentEntry?.email || `Entry #${target.documentId}`;
1233
- const isNested = attribute.includes(".");
1319
+ const parsed = parseDynamicZonePath(attribute);
1320
+ const isNested = attribute.includes(".") || attribute.includes("[");
1234
1321
  const isComponentNested = isNested && isDeepComponentField(contentType, strapi.components, attribute);
1235
- const parts = isNested ? attribute.split(".") : [];
1236
- const rootKey = isNested ? parts[0] : "";
1322
+ const parts = isNested ? parsed.cleanPath.split(".") : [];
1323
+ const rootKey = parsed.rootKey;
1324
+ const targetComponentUid = target.targetComponentUid || parsed.targetComponentUid;
1325
+ const targetIndexes = target.dynamicZoneIndexes;
1237
1326
  const attrDef = contentType?.attributes?.[attribute];
1238
1327
  const hasAttribute = Boolean(attrDef) || isComponentNested;
1239
1328
  if (hasAttribute) {
@@ -1246,12 +1335,25 @@ async function executeBatchInternal(strapi, {
1246
1335
  const currentEntryWithPopulate = await strapi.documents(target.uid).findOne(findOneArgs);
1247
1336
  const isRootDz = contentType?.attributes?.[rootKey]?.type === "dynamiczone";
1248
1337
  const existingRoot = currentEntryWithPopulate?.[rootKey] || (isRootDz ? [] : {});
1249
- const currentVal2 = getDeepProperty(existingRoot, parts.slice(1).join("."));
1338
+ let affectedMatches = [];
1339
+ if (isRootDz && Array.isArray(existingRoot)) {
1340
+ affectedMatches = getAllNestedValues({ [rootKey]: existingRoot }, attribute);
1341
+ if (targetIndexes && targetIndexes.length > 0) {
1342
+ affectedMatches = affectedMatches.filter((m) => targetIndexes.includes(m.index));
1343
+ }
1344
+ if (targetComponentUid) {
1345
+ affectedMatches = affectedMatches.filter((m) => m.componentUid === targetComponentUid);
1346
+ }
1347
+ }
1348
+ const currentVal2 = isRootDz ? affectedMatches.length > 0 ? affectedMatches.map((m) => m.value).join(", ") : getDeepProperty(existingRoot, parts.slice(1).join(".")) : getDeepProperty(existingRoot, parts.slice(1).join("."));
1250
1349
  let newValue2 = value;
1251
1350
  if (regexObj && typeof currentVal2 === "string") {
1252
1351
  newValue2 = currentVal2.replace(regexObj, value);
1253
1352
  }
1254
- const updatedRoot = setDeepProperty(existingRoot, parts.slice(1), newValue2);
1353
+ const updatedRoot = setDeepProperty(existingRoot, parts.slice(1), newValue2, {
1354
+ targetIndexes,
1355
+ targetComponentUid
1356
+ });
1255
1357
  await strapi.documents(target.uid).update({
1256
1358
  documentId: target.documentId,
1257
1359
  data: {
@@ -1275,6 +1377,14 @@ async function executeBatchInternal(strapi, {
1275
1377
  userId: currentUser?.id,
1276
1378
  statusOverride: publishOnApproval && isDraftAndPublishComp ? "published" : void 0
1277
1379
  });
1380
+ const dzDiffs = isRootDz && affectedMatches.length > 0 ? affectedMatches.map((m) => ({
1381
+ index: m.index,
1382
+ componentUid: m.componentUid,
1383
+ componentName: m.componentName,
1384
+ attribute: `${rootKey}[#${m.index} ${m.componentName}].${parts.slice(1).join(".")}`,
1385
+ oldValue: m.value,
1386
+ newValue: regexObj && typeof m.value === "string" ? m.value.replace(regexObj, value) : value
1387
+ })) : [];
1278
1388
  return {
1279
1389
  uid: target.uid,
1280
1390
  collectionName: displayName,
@@ -1284,7 +1394,8 @@ async function executeBatchInternal(strapi, {
1284
1394
  oldValue: currentVal2 ?? null,
1285
1395
  newValue: newValue2,
1286
1396
  status: "updated",
1287
- message: `Successfully updated nested field ${attribute}`
1397
+ message: isRootDz ? `Successfully updated ${affectedMatches.length} dynamic zone item(s) in ${attribute}` : `Successfully updated nested field ${attribute}`,
1398
+ dynamicZoneDiffs: dzDiffs
1288
1399
  };
1289
1400
  }
1290
1401
  if (attrDef.type === "relation") {
@@ -1313,13 +1424,13 @@ async function executeBatchInternal(strapi, {
1313
1424
  newDisplay = "(cleared / unlinked)";
1314
1425
  } else {
1315
1426
  try {
1316
- const parsed = JSON.parse(value);
1317
- if (parsed && typeof parsed === "object" && parsed.mode) {
1318
- mode = parsed.mode;
1319
- targetDocIds = Array.isArray(parsed.documentIds) ? parsed.documentIds : parsed.documentId ? [parsed.documentId] : [];
1320
- newDisplay = parsed.label || targetDocIds.join(", ") || `[${mode}]`;
1321
- } else if (Array.isArray(parsed)) {
1322
- targetDocIds = parsed;
1427
+ const parsed2 = JSON.parse(value);
1428
+ if (parsed2 && typeof parsed2 === "object" && parsed2.mode) {
1429
+ mode = parsed2.mode;
1430
+ targetDocIds = Array.isArray(parsed2.documentIds) ? parsed2.documentIds : parsed2.documentId ? [parsed2.documentId] : [];
1431
+ newDisplay = parsed2.label || targetDocIds.join(", ") || `[${mode}]`;
1432
+ } else if (Array.isArray(parsed2)) {
1433
+ targetDocIds = parsed2;
1323
1434
  newDisplay = targetDocIds.join(", ");
1324
1435
  } else {
1325
1436
  targetDocIds = [value];
@@ -1463,6 +1574,26 @@ async function executeBatchInternal(strapi, {
1463
1574
  ...r,
1464
1575
  isLive: r.status === "updated"
1465
1576
  }));
1577
+ const flattenedDiffs = [];
1578
+ for (const r of results) {
1579
+ if (r.dynamicZoneDiffs && r.dynamicZoneDiffs.length > 0) {
1580
+ for (const dz of r.dynamicZoneDiffs) {
1581
+ flattenedDiffs.push({
1582
+ uid: r.uid,
1583
+ collectionName: r.collectionName,
1584
+ documentId: r.documentId,
1585
+ label: `${r.label} [Block #${dz.index} (${dz.componentName})]`,
1586
+ attribute: dz.attribute,
1587
+ oldValue: dz.oldValue,
1588
+ newValue: dz.newValue,
1589
+ status: "updated",
1590
+ message: `Updated ${dz.attribute}`
1591
+ });
1592
+ }
1593
+ } else {
1594
+ flattenedDiffs.push(r);
1595
+ }
1596
+ }
1466
1597
  const batchSummary = {
1467
1598
  batchId,
1468
1599
  attribute,
@@ -1474,7 +1605,7 @@ async function executeBatchInternal(strapi, {
1474
1605
  };
1475
1606
  await strapi.plugin("bulk-updater").service("audit").recordBatch(
1476
1607
  batchSummary,
1477
- results,
1608
+ flattenedDiffs,
1478
1609
  currentUser
1479
1610
  );
1480
1611
  return {
@@ -1742,6 +1873,19 @@ const bulkUpdaterService = ({ strapi }) => {
1742
1873
  colAttributesMeta
1743
1874
  );
1744
1875
  nestedComponentAttrs.push(...compAttrs);
1876
+ const scopedAttrs = discoverComponentAttributes(
1877
+ strapi,
1878
+ compUid,
1879
+ `${attrKey}[${compUid}]`,
1880
+ 1,
1881
+ 5,
1882
+ systemFields,
1883
+ allContentTypes,
1884
+ relationsByAttr,
1885
+ allRelationsMeta,
1886
+ colAttributesMeta
1887
+ );
1888
+ nestedComponentAttrs.push(...scopedAttrs);
1745
1889
  }
1746
1890
  return false;
1747
1891
  }
@@ -1956,13 +2100,20 @@ const bulkUpdaterService = ({ strapi }) => {
1956
2100
  const attrs = contentType.attributes || {};
1957
2101
  let colHasAttribute = false;
1958
2102
  let isNestedAttr = false;
2103
+ let isRootDz = false;
1959
2104
  let parts = [];
1960
2105
  if (targetFilterAttr) {
1961
- if (targetFilterAttr.includes(".")) {
2106
+ const parsed = parseDynamicZonePath(targetFilterAttr);
2107
+ if (parsed.rootKey && contentType.attributes?.[parsed.rootKey]?.type === "dynamiczone") {
2108
+ isRootDz = true;
2109
+ colHasAttribute = true;
2110
+ isNestedAttr = true;
2111
+ parts = parsed.cleanPath.split(".");
2112
+ } else if (targetFilterAttr.includes(".") || targetFilterAttr.includes("[")) {
1962
2113
  isNestedAttr = isDeepComponentField(contentType, strapi.components, targetFilterAttr);
1963
2114
  if (isNestedAttr) {
1964
2115
  colHasAttribute = true;
1965
- parts = targetFilterAttr.split(".");
2116
+ parts = parsed.cleanPath.split(".");
1966
2117
  }
1967
2118
  } else if (attrs[targetFilterAttr]) {
1968
2119
  colHasAttribute = true;
@@ -2019,8 +2170,11 @@ const bulkUpdaterService = ({ strapi }) => {
2019
2170
  }
2020
2171
  if (condition !== void 0) {
2021
2172
  if (isNestedAttr) {
2022
- const deepFilter = buildDeepFilter(parts, condition);
2023
- Object.assign(filters, deepFilter);
2173
+ if (isRootDz) ;
2174
+ else {
2175
+ const deepFilter = buildDeepFilter(parts, condition);
2176
+ Object.assign(filters, deepFilter);
2177
+ }
2024
2178
  } else {
2025
2179
  if (normOp === "empty" && isStringLike) {
2026
2180
  filters.$or = [
@@ -2094,39 +2248,100 @@ const bulkUpdaterService = ({ strapi }) => {
2094
2248
  } else {
2095
2249
  sortClause["createdAt"] = "desc";
2096
2250
  }
2097
- const findManyArgs = {
2098
- start,
2099
- limit,
2100
- filters: Object.keys(filters).length > 0 ? filters : void 0,
2101
- sort: sortClause
2102
- };
2103
- if (populateOption) {
2104
- findManyArgs.populate = populateOption;
2105
- }
2106
- const [entries, totalEntries] = await Promise.all([
2107
- strapi.documents(uid).findMany(findManyArgs),
2108
- strapi.documents(uid).count({
2109
- filters: Object.keys(filters).length > 0 ? filters : void 0
2110
- })
2111
- ]);
2112
- const totalPages = Math.ceil(totalEntries / limit);
2113
- result = {
2114
- uid,
2115
- displayName: contentType.info?.displayName || uid,
2116
- isSingleType: false,
2117
- draftAndPublish: Boolean(contentType.options?.draftAndPublish),
2118
- entries: (entries || []).map((entry) => ({
2119
- id: entry.id,
2120
- documentId: entry.documentId,
2121
- ...entry
2122
- })),
2123
- pagination: {
2124
- page: currentPage,
2125
- pageSize: limit,
2126
- total: totalEntries,
2127
- pageCount: totalPages
2251
+ if (isRootDz && normOp && normOp !== "none") {
2252
+ const findManyArgs = {
2253
+ limit: 500,
2254
+ filters: Object.keys(filters).length > 0 ? filters : void 0,
2255
+ sort: sortClause
2256
+ };
2257
+ if (populateOption) {
2258
+ findManyArgs.populate = populateOption;
2128
2259
  }
2129
- };
2260
+ const rawEntries = await strapi.documents(uid).findMany(findManyArgs);
2261
+ const qVal = (filterQuery || "").trim().toLowerCase();
2262
+ const passesDzFilter = (entry) => {
2263
+ const matches = getAllNestedValues(entry, targetFilterAttr);
2264
+ if (normOp === "empty") {
2265
+ return matches.length === 0 || matches.every((m) => m.value === void 0 || m.value === null || String(m.value).trim() === "");
2266
+ }
2267
+ if (normOp === "notempty") {
2268
+ return matches.some((m) => m.value !== void 0 && m.value !== null && String(m.value).trim() !== "");
2269
+ }
2270
+ return matches.some((m) => {
2271
+ if (m.value === void 0 || m.value === null) return false;
2272
+ const valStr = String(m.value).toLowerCase();
2273
+ switch (normOp) {
2274
+ case "equals":
2275
+ return valStr === qVal;
2276
+ case "notequals":
2277
+ return valStr !== qVal;
2278
+ case "contains":
2279
+ return valStr.includes(qVal);
2280
+ case "notcontains":
2281
+ return !valStr.includes(qVal);
2282
+ case "startswith":
2283
+ return valStr.startsWith(qVal);
2284
+ case "endswith":
2285
+ return valStr.endsWith(qVal);
2286
+ default:
2287
+ return true;
2288
+ }
2289
+ });
2290
+ };
2291
+ const matchedEntries = (rawEntries || []).filter(passesDzFilter);
2292
+ const totalEntries = matchedEntries.length;
2293
+ const paginatedEntries = matchedEntries.slice(start, start + limit);
2294
+ result = {
2295
+ uid,
2296
+ displayName: contentType.info?.displayName || uid,
2297
+ isSingleType: false,
2298
+ draftAndPublish: Boolean(contentType.options?.draftAndPublish),
2299
+ entries: paginatedEntries.map((entry) => ({
2300
+ id: entry.id,
2301
+ documentId: entry.documentId,
2302
+ ...entry
2303
+ })),
2304
+ pagination: {
2305
+ page: currentPage,
2306
+ pageSize: limit,
2307
+ total: totalEntries,
2308
+ pageCount: Math.ceil(totalEntries / limit) || 1
2309
+ }
2310
+ };
2311
+ } else {
2312
+ const findManyArgs = {
2313
+ start,
2314
+ limit,
2315
+ filters: Object.keys(filters).length > 0 ? filters : void 0,
2316
+ sort: sortClause
2317
+ };
2318
+ if (populateOption) {
2319
+ findManyArgs.populate = populateOption;
2320
+ }
2321
+ const [entries, totalEntries] = await Promise.all([
2322
+ strapi.documents(uid).findMany(findManyArgs),
2323
+ strapi.documents(uid).count({
2324
+ filters: Object.keys(filters).length > 0 ? filters : void 0
2325
+ })
2326
+ ]);
2327
+ result = {
2328
+ uid,
2329
+ displayName: contentType.info?.displayName || uid,
2330
+ isSingleType: false,
2331
+ draftAndPublish: Boolean(contentType.options?.draftAndPublish),
2332
+ entries: (entries || []).map((entry) => ({
2333
+ id: entry.id,
2334
+ documentId: entry.documentId,
2335
+ ...entry
2336
+ })),
2337
+ pagination: {
2338
+ page: currentPage,
2339
+ pageSize: limit,
2340
+ total: totalEntries,
2341
+ pageCount: Math.ceil(totalEntries / limit) || 1
2342
+ }
2343
+ };
2344
+ }
2130
2345
  }
2131
2346
  if (queryCache.size >= MAX_QUERY_CACHE_SIZE) {
2132
2347
  const toDelete = Array.from(queryCache.keys()).slice(0, 100);