@hot-updater/aws 0.32.0 → 0.33.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.cjs CHANGED
@@ -23,16 +23,16 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  //#endregion
24
24
  let _aws_sdk_client_cloudfront = require("@aws-sdk/client-cloudfront");
25
25
  let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
26
- let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
27
26
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
28
27
  let fs_promises = require("fs/promises");
29
28
  fs_promises = __toESM(fs_promises);
30
29
  let path = require("path");
31
30
  path = __toESM(path);
31
+ let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
32
32
  let _aws_sdk_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
33
33
  let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
34
34
  let _aws_sdk_cloudfront_signer = require("@aws-sdk/cloudfront-signer");
35
- //#region ../../node_modules/.pnpm/mime@4.0.4/node_modules/mime/dist/types/other.js
35
+ //#region ../../node_modules/.pnpm/mime@4.1.0/node_modules/mime/dist/types/other.js
36
36
  const types$1 = {
37
37
  "application/prs.cww": ["cww"],
38
38
  "application/prs.xsf+xml": ["xsf"],
@@ -71,6 +71,7 @@ const types$1 = {
71
71
  "application/vnd.aristanetworks.swi": ["swi"],
72
72
  "application/vnd.astraea-software.iota": ["iota"],
73
73
  "application/vnd.audiograph": ["aep"],
74
+ "application/vnd.autodesk.fbx": ["fbx"],
74
75
  "application/vnd.balsamiq.bmml+xml": ["bmml"],
75
76
  "application/vnd.blueice.multipass": ["mpm"],
76
77
  "application/vnd.bmi": ["bmi"],
@@ -106,6 +107,7 @@ const types$1 = {
106
107
  "application/vnd.dart": ["dart"],
107
108
  "application/vnd.data-vision.rdz": ["rdz"],
108
109
  "application/vnd.dbf": ["dbf"],
110
+ "application/vnd.dcmp+xml": ["dcmp"],
109
111
  "application/vnd.dece.data": [
110
112
  "uvf",
111
113
  "uvvf",
@@ -159,6 +161,7 @@ const types$1 = {
159
161
  "application/vnd.fuzzysheet": ["fzs"],
160
162
  "application/vnd.genomatix.tuxedo": ["txd"],
161
163
  "application/vnd.geogebra.file": ["ggb"],
164
+ "application/vnd.geogebra.slides": ["ggs"],
162
165
  "application/vnd.geogebra.tool": ["ggt"],
163
166
  "application/vnd.geometry-explorer": ["gex", "gre"],
164
167
  "application/vnd.geonext": ["gxt"],
@@ -166,10 +169,17 @@ const types$1 = {
166
169
  "application/vnd.geospace": ["g3w"],
167
170
  "application/vnd.gmx": ["gmx"],
168
171
  "application/vnd.google-apps.document": ["gdoc"],
172
+ "application/vnd.google-apps.drawing": ["gdraw"],
173
+ "application/vnd.google-apps.form": ["gform"],
174
+ "application/vnd.google-apps.jam": ["gjam"],
175
+ "application/vnd.google-apps.map": ["gmap"],
169
176
  "application/vnd.google-apps.presentation": ["gslides"],
177
+ "application/vnd.google-apps.script": ["gscript"],
178
+ "application/vnd.google-apps.site": ["gsite"],
170
179
  "application/vnd.google-apps.spreadsheet": ["gsheet"],
171
180
  "application/vnd.google-earth.kml+xml": ["kml"],
172
181
  "application/vnd.google-earth.kmz": ["kmz"],
182
+ "application/vnd.gov.sk.xmldatacontainer+xml": ["xdcf"],
173
183
  "application/vnd.grafeq": ["gqf", "gqs"],
174
184
  "application/vnd.groove-account": ["gac"],
175
185
  "application/vnd.groove-help": ["ghf"],
@@ -296,6 +306,7 @@ const types$1 = {
296
306
  "application/vnd.ms-powerpoint.slideshow.macroenabled.12": ["ppsm"],
297
307
  "application/vnd.ms-powerpoint.template.macroenabled.12": ["potm"],
298
308
  "application/vnd.ms-project": ["*mpp", "mpt"],
309
+ "application/vnd.ms-visio.viewer": ["vdx"],
299
310
  "application/vnd.ms-word.document.macroenabled.12": ["docm"],
300
311
  "application/vnd.ms-word.template.macroenabled.12": ["dotm"],
301
312
  "application/vnd.ms-works": [
@@ -310,6 +321,7 @@ const types$1 = {
310
321
  "application/vnd.musician": ["mus"],
311
322
  "application/vnd.muvee.style": ["msty"],
312
323
  "application/vnd.mynfc": ["taglet"],
324
+ "application/vnd.nato.bindingdataobject+xml": ["bdo"],
313
325
  "application/vnd.neurolanguage.nlu": ["nlu"],
314
326
  "application/vnd.nitf": ["ntf", "nitf"],
315
327
  "application/vnd.noblenet-directory": ["nnd"],
@@ -369,6 +381,9 @@ const types$1 = {
369
381
  "application/vnd.pocketlearn": ["plf"],
370
382
  "application/vnd.powerbuilder6": ["pbd"],
371
383
  "application/vnd.previewsystems.box": ["box"],
384
+ "application/vnd.procrate.brushset": ["brushset"],
385
+ "application/vnd.procreate.brush": ["brush"],
386
+ "application/vnd.procreate.dream": ["drm"],
372
387
  "application/vnd.proteus.magazine": ["mgz"],
373
388
  "application/vnd.publishare-delta-tree": ["qps"],
374
389
  "application/vnd.pvi.ptid1": ["ptid"],
@@ -452,7 +467,9 @@ const types$1 = {
452
467
  "vsd",
453
468
  "vst",
454
469
  "vss",
455
- "vsw"
470
+ "vsw",
471
+ "vsdx",
472
+ "vtx"
456
473
  ],
457
474
  "application/vnd.visionary": ["vis"],
458
475
  "application/vnd.vsf": ["vsf"],
@@ -492,6 +509,7 @@ const types$1 = {
492
509
  "application/x-bcpio": ["bcpio"],
493
510
  "application/x-bdoc": ["*bdoc"],
494
511
  "application/x-bittorrent": ["torrent"],
512
+ "application/x-blender": ["blend"],
495
513
  "application/x-blorb": ["blb", "blorb"],
496
514
  "application/x-bzip": ["bz"],
497
515
  "application/x-bzip2": ["bz2", "boz"],
@@ -508,6 +526,7 @@ const types$1 = {
508
526
  "application/x-chess-pgn": ["pgn"],
509
527
  "application/x-chrome-extension": ["crx"],
510
528
  "application/x-cocoa": ["cco"],
529
+ "application/x-compressed": ["*rar"],
511
530
  "application/x-conference": ["nsc"],
512
531
  "application/x-cpio": ["cpio"],
513
532
  "application/x-csh": ["csh"],
@@ -552,6 +571,7 @@ const types$1 = {
552
571
  "application/x-hdf": ["hdf"],
553
572
  "application/x-httpd-php": ["php"],
554
573
  "application/x-install-instructions": ["install"],
574
+ "application/x-ipynb+json": ["ipynb"],
555
575
  "application/x-iso9660-image": ["*iso"],
556
576
  "application/x-iwork-keynote-sffkey": ["*key"],
557
577
  "application/x-iwork-numbers-sffnumbers": ["*numbers"],
@@ -648,6 +668,7 @@ const types$1 = {
648
668
  "application/x-xliff+xml": ["*xlf"],
649
669
  "application/x-xpinstall": ["xpi"],
650
670
  "application/x-xz": ["xz"],
671
+ "application/x-zip-compressed": ["*zip"],
651
672
  "application/x-zmachine": [
652
673
  "z1",
653
674
  "z2",
@@ -696,6 +717,7 @@ const types$1 = {
696
717
  "image/prs.pti": ["pti"],
697
718
  "image/vnd.adobe.photoshop": ["psd"],
698
719
  "image/vnd.airzip.accelerator.azv": ["azv"],
720
+ "image/vnd.blockfact.facti": ["facti"],
699
721
  "image/vnd.dece.graphic": [
700
722
  "uvi",
701
723
  "uvvi",
@@ -723,6 +745,7 @@ const types$1 = {
723
745
  "image/vnd.xiff": ["xif"],
724
746
  "image/vnd.zbrush.pcx": ["pcx"],
725
747
  "image/x-3ds": ["3ds"],
748
+ "image/x-adobe-dng": ["dng"],
726
749
  "image/x-cmu-raster": ["ras"],
727
750
  "image/x-cmx": ["cmx"],
728
751
  "image/x-freehand": [
@@ -748,12 +771,13 @@ const types$1 = {
748
771
  "image/x-xpixmap": ["xpm"],
749
772
  "image/x-xwindowdump": ["xwd"],
750
773
  "message/vnd.wfa.wsc": ["wsc"],
774
+ "model/vnd.bary": ["bary"],
751
775
  "model/vnd.cld": ["cld"],
752
776
  "model/vnd.collada+xml": ["dae"],
753
777
  "model/vnd.dwf": ["dwf"],
754
778
  "model/vnd.gdl": ["gdl"],
755
779
  "model/vnd.gtw": ["gtw"],
756
- "model/vnd.mts": ["mts"],
780
+ "model/vnd.mts": ["*mts"],
757
781
  "model/vnd.opengex": ["ogex"],
758
782
  "model/vnd.parasolid.transmit.binary": ["x_b"],
759
783
  "model/vnd.parasolid.transmit.text": ["x_t"],
@@ -846,7 +870,7 @@ const types$1 = {
846
870
  };
847
871
  Object.freeze(types$1);
848
872
  //#endregion
849
- //#region ../../node_modules/.pnpm/mime@4.0.4/node_modules/mime/dist/types/standard.js
873
+ //#region ../../node_modules/.pnpm/mime@4.1.0/node_modules/mime/dist/types/standard.js
850
874
  const types = {
851
875
  "application/andrew-inset": ["ez"],
852
876
  "application/appinstaller": ["appinstaller"],
@@ -877,6 +901,7 @@ const types = {
877
901
  "application/dash+xml": ["mpd"],
878
902
  "application/dash-patch+xml": ["mpp"],
879
903
  "application/davmount+xml": ["davmount"],
904
+ "application/dicom": ["dcm"],
880
905
  "application/docbook+xml": ["dbk"],
881
906
  "application/dssc+der": ["dssc"],
882
907
  "application/dssc+xml": ["xdssc"],
@@ -980,7 +1005,9 @@ const types = {
980
1005
  "onetoc",
981
1006
  "onetoc2",
982
1007
  "onetmp",
983
- "onepkg"
1008
+ "onepkg",
1009
+ "one",
1010
+ "onea"
984
1011
  ],
985
1012
  "application/oxps": ["oxps"],
986
1013
  "application/p2p-overlay+xml": ["relo"],
@@ -1091,6 +1118,7 @@ const types = {
1091
1118
  "application/yang": ["yang"],
1092
1119
  "application/yin+xml": ["yin"],
1093
1120
  "application/zip": ["zip"],
1121
+ "application/zip+dotlottie": ["lottie"],
1094
1122
  "audio/3gpp": ["*3gpp"],
1095
1123
  "audio/aac": ["adts", "aac"],
1096
1124
  "audio/adpcm": ["adp"],
@@ -1104,7 +1132,11 @@ const types = {
1104
1132
  ],
1105
1133
  "audio/mobile-xmf": ["mxmf"],
1106
1134
  "audio/mp3": ["*mp3"],
1107
- "audio/mp4": ["m4a", "mp4a"],
1135
+ "audio/mp4": [
1136
+ "m4a",
1137
+ "mp4a",
1138
+ "m4b"
1139
+ ],
1108
1140
  "audio/mpeg": [
1109
1141
  "mpga",
1110
1142
  "mp2",
@@ -1148,19 +1180,21 @@ const types = {
1148
1180
  "image/heif": ["heif"],
1149
1181
  "image/heif-sequence": ["heifs"],
1150
1182
  "image/hej2k": ["hej2"],
1151
- "image/hsj2": ["hsj2"],
1152
1183
  "image/ief": ["ief"],
1184
+ "image/jaii": ["jaii"],
1185
+ "image/jais": ["jais"],
1153
1186
  "image/jls": ["jls"],
1154
1187
  "image/jp2": ["jp2", "jpg2"],
1155
1188
  "image/jpeg": [
1156
- "jpeg",
1157
1189
  "jpg",
1190
+ "jpeg",
1158
1191
  "jpe"
1159
1192
  ],
1160
1193
  "image/jph": ["jph"],
1161
1194
  "image/jphc": ["jhc"],
1162
1195
  "image/jpm": ["jpm", "jpgm"],
1163
1196
  "image/jpx": ["jpx", "jpf"],
1197
+ "image/jxl": ["jxl"],
1164
1198
  "image/jxr": ["jxr"],
1165
1199
  "image/jxra": ["jxra"],
1166
1200
  "image/jxrs": ["jxrs"],
@@ -1170,6 +1204,7 @@ const types = {
1170
1204
  "image/jxss": ["jxss"],
1171
1205
  "image/ktx": ["ktx"],
1172
1206
  "image/ktx2": ["ktx2"],
1207
+ "image/pjpeg": ["jfif"],
1173
1208
  "image/png": ["png"],
1174
1209
  "image/sgi": ["sgi"],
1175
1210
  "image/svg+xml": ["svg", "svgz"],
@@ -1183,7 +1218,12 @@ const types = {
1183
1218
  "message/global-delivery-status": ["u8dsn"],
1184
1219
  "message/global-disposition-notification": ["u8mdn"],
1185
1220
  "message/global-headers": ["u8hdr"],
1186
- "message/rfc822": ["eml", "mime"],
1221
+ "message/rfc822": [
1222
+ "eml",
1223
+ "mime",
1224
+ "mht",
1225
+ "mhtml"
1226
+ ],
1187
1227
  "model/3mf": ["3mf"],
1188
1228
  "model/gltf+json": ["gltf"],
1189
1229
  "model/gltf-binary": ["glb"],
@@ -1197,6 +1237,13 @@ const types = {
1197
1237
  "model/mtl": ["mtl"],
1198
1238
  "model/obj": ["obj"],
1199
1239
  "model/prc": ["prc"],
1240
+ "model/step": [
1241
+ "step",
1242
+ "stp",
1243
+ "stpnc",
1244
+ "p21",
1245
+ "210"
1246
+ ],
1200
1247
  "model/step+xml": ["stpx"],
1201
1248
  "model/step+zip": ["stpz"],
1202
1249
  "model/step-xml+zip": ["stpxz"],
@@ -1272,7 +1319,12 @@ const types = {
1272
1319
  "video/jpeg": ["jpgv"],
1273
1320
  "video/jpm": ["*jpm", "*jpgm"],
1274
1321
  "video/mj2": ["mj2", "mjp2"],
1275
- "video/mp2t": ["ts"],
1322
+ "video/mp2t": [
1323
+ "ts",
1324
+ "m2t",
1325
+ "m2ts",
1326
+ "mts"
1327
+ ],
1276
1328
  "video/mp4": [
1277
1329
  "mp4",
1278
1330
  "mp4v",
@@ -1291,7 +1343,7 @@ const types = {
1291
1343
  };
1292
1344
  Object.freeze(types);
1293
1345
  //#endregion
1294
- //#region ../../node_modules/.pnpm/mime@4.0.4/node_modules/mime/dist/src/Mime.js
1346
+ //#region ../../node_modules/.pnpm/mime@4.1.0/node_modules/mime/dist/src/Mime.js
1295
1347
  var __classPrivateFieldGet = function(receiver, state, kind, f) {
1296
1348
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
1297
1349
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
@@ -1328,8 +1380,8 @@ var Mime = class {
1328
1380
  }
1329
1381
  getType(path) {
1330
1382
  if (typeof path !== "string") return null;
1331
- const last = path.replace(/^.*[/\\]/, "").toLowerCase();
1332
- const ext = last.replace(/^.*\./, "").toLowerCase();
1383
+ const last = path.replace(/^.*[/\\]/s, "").toLowerCase();
1384
+ const ext = last.replace(/^.*\./s, "").toLowerCase();
1333
1385
  const hasPath = last.length < path.length;
1334
1386
  if (!(ext.length < last.length - 1) && hasPath) return null;
1335
1387
  return __classPrivateFieldGet(this, _Mime_extensionToType, "f").get(ext) ?? null;
@@ -1360,7 +1412,7 @@ var Mime = class {
1360
1412
  };
1361
1413
  _Mime_extensionToType = /* @__PURE__ */ new WeakMap(), _Mime_typeToExtension = /* @__PURE__ */ new WeakMap(), _Mime_typeToExtensions = /* @__PURE__ */ new WeakMap();
1362
1414
  //#endregion
1363
- //#region ../../node_modules/.pnpm/mime@4.0.4/node_modules/mime/dist/src/index.js
1415
+ //#region ../../node_modules/.pnpm/mime@4.1.0/node_modules/mime/dist/src/index.js
1364
1416
  var src_default = new Mime(types, types$1)._freeze();
1365
1417
  //#endregion
1366
1418
  //#region src/runtimeAwsConfig.ts
@@ -1411,7 +1463,76 @@ const streamToString = (stream) => {
1411
1463
  //#region src/s3Database.ts
1412
1464
  const DEFAULT_INVALIDATION_POLL_INTERVAL_MS = 2e3;
1413
1465
  const DEFAULT_INVALIDATION_TIMEOUT_MS = 300 * 1e3;
1466
+ const S3_LIST_OBJECTS_CONCURRENCY = 4;
1467
+ const S3_DIRECTORY_DELIMITER = "/";
1414
1468
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1469
+ const getS3ErrorProperty = (error, key) => {
1470
+ if (typeof error !== "object" || error === null) return;
1471
+ const value = error[key];
1472
+ return typeof value === "string" ? value : void 0;
1473
+ };
1474
+ const isArchivedS3ObjectError = (error) => {
1475
+ if (!(error instanceof Error)) return false;
1476
+ return error.name === "InvalidObjectState" || getS3ErrorProperty(error, "Code") === "InvalidObjectState";
1477
+ };
1478
+ const createArchivedS3ObjectError = ({ bucket, key, error }) => {
1479
+ const storageClass = getS3ErrorProperty(error, "StorageClass") ?? "archived storage";
1480
+ const nextError = new Error(`S3 object "${key}" in bucket "${bucket}" is archived (${storageClass}) and cannot be read. Restore the object in S3 or exclude Hot Updater metadata from lifecycle archival: "**/target-app-versions.json" and "**/update.json".`, { cause: error });
1481
+ nextError.name = "S3ArchivedObjectError";
1482
+ return nextError;
1483
+ };
1484
+ function normalizeBasePath(basePath) {
1485
+ return basePath?.replace(/^\/+|\/+$/g, "") ?? "";
1486
+ }
1487
+ function createDatabaseKeyBuilder(basePath) {
1488
+ const normalizedBasePath = normalizeBasePath(basePath);
1489
+ const toStorageKey = (key) => [normalizedBasePath, key].filter(Boolean).join("/");
1490
+ const fromStorageKey = (key) => {
1491
+ if (!normalizedBasePath) return key;
1492
+ const prefix = `${normalizedBasePath}/`;
1493
+ return key.startsWith(prefix) ? key.slice(prefix.length) : key;
1494
+ };
1495
+ return {
1496
+ fromStorageKey,
1497
+ toStorageKey
1498
+ };
1499
+ }
1500
+ function normalizeDirectoryPrefix(prefix) {
1501
+ if (!prefix) return "";
1502
+ return prefix.endsWith(S3_DIRECTORY_DELIMITER) ? prefix : `${prefix}${S3_DIRECTORY_DELIMITER}`;
1503
+ }
1504
+ function getRelativeDirectoryPrefix(prefix, rootPrefix) {
1505
+ if (!rootPrefix) return prefix;
1506
+ return prefix.startsWith(rootPrefix) ? prefix.slice(rootPrefix.length) : prefix;
1507
+ }
1508
+ function getDirectoryDepth(prefix, rootPrefix) {
1509
+ return getRelativeDirectoryPrefix(prefix, rootPrefix).split(S3_DIRECTORY_DELIMITER).filter(Boolean).length;
1510
+ }
1511
+ function getLastDirectorySegment(prefix) {
1512
+ return prefix.split(S3_DIRECTORY_DELIMITER).filter(Boolean).at(-1);
1513
+ }
1514
+ function isPlatformDirectoryPrefix(prefix) {
1515
+ const lastSegment = getLastDirectorySegment(prefix);
1516
+ return lastSegment === "ios" || lastSegment === "android";
1517
+ }
1518
+ function isUpdateJsonKey(key) {
1519
+ return key.endsWith(`${S3_DIRECTORY_DELIMITER}update.json`);
1520
+ }
1521
+ async function mapWithConcurrency(items, concurrency, mapper) {
1522
+ const results = [];
1523
+ let nextIndex = 0;
1524
+ const workerCount = Math.min(concurrency, items.length);
1525
+ await Promise.all(Array.from({ length: workerCount }, async () => {
1526
+ while (nextIndex < items.length) {
1527
+ const index = nextIndex;
1528
+ nextIndex += 1;
1529
+ const item = items[index];
1530
+ if (item === void 0) break;
1531
+ results[index] = await mapper(item, index);
1532
+ }
1533
+ }));
1534
+ return results.filter((result) => result !== void 0);
1535
+ }
1415
1536
  /**
1416
1537
  * Loads JSON data from S3.
1417
1538
  * Returns null if NoSuchKey error occurs.
@@ -1427,6 +1548,11 @@ async function loadJsonFromS3(client, bucket, key) {
1427
1548
  return JSON.parse(bodyContents);
1428
1549
  } catch (e) {
1429
1550
  if (e instanceof _aws_sdk_client_s3.NoSuchKey) return null;
1551
+ if (isArchivedS3ObjectError(e)) throw createArchivedS3ObjectError({
1552
+ bucket,
1553
+ key,
1554
+ error: e
1555
+ });
1430
1556
  throw e;
1431
1557
  }
1432
1558
  }
@@ -1434,31 +1560,47 @@ async function loadJsonFromS3(client, bucket, key) {
1434
1560
  * Converts data to JSON string and uploads to S3.
1435
1561
  */
1436
1562
  async function uploadJsonToS3(client, bucket, key, data) {
1437
- await new _aws_sdk_lib_storage.Upload({
1438
- client,
1439
- params: {
1440
- Bucket: bucket,
1441
- Key: key,
1442
- Body: JSON.stringify(data),
1443
- ContentType: src_default.getType(key) ?? "application/json",
1444
- CacheControl: "max-age=31536000"
1445
- }
1446
- }).done();
1563
+ const Body = JSON.stringify(data);
1564
+ const ContentType = src_default.getType(key) ?? "application/json";
1565
+ await client.send(new _aws_sdk_client_s3.PutObjectCommand({
1566
+ Bucket: bucket,
1567
+ Key: key,
1568
+ Body,
1569
+ ContentType,
1570
+ CacheControl: "max-age=31536000"
1571
+ }));
1447
1572
  }
1448
- async function listObjectsInS3(client, bucketName, prefix) {
1449
- let continuationToken;
1450
- const keys = [];
1451
- do {
1452
- const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
1453
- Bucket: bucketName,
1454
- Prefix: prefix,
1455
- ContinuationToken: continuationToken
1456
- }));
1457
- const found = (response.Contents ?? []).map((item) => item.Key).filter((key) => !!key);
1458
- keys.push(...found);
1459
- continuationToken = response.NextContinuationToken;
1460
- } while (continuationToken);
1461
- return keys;
1573
+ async function listObjectsInS3(client, bucketName, prefix, rootPrefix = "") {
1574
+ const normalizedRootPrefix = normalizeDirectoryPrefix(rootPrefix);
1575
+ const listPrefix = async (currentPrefix) => {
1576
+ let continuationToken;
1577
+ const keys = [];
1578
+ const commonPrefixes = /* @__PURE__ */ new Set();
1579
+ do {
1580
+ const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
1581
+ Bucket: bucketName,
1582
+ Prefix: currentPrefix,
1583
+ Delimiter: S3_DIRECTORY_DELIMITER,
1584
+ ContinuationToken: continuationToken
1585
+ }));
1586
+ const found = (response.Contents ?? []).map((item) => item.Key).filter((key) => !!key);
1587
+ keys.push(...found);
1588
+ for (const commonPrefix of response.CommonPrefixes ?? []) if (commonPrefix.Prefix) commonPrefixes.add(commonPrefix.Prefix);
1589
+ continuationToken = response.NextContinuationToken;
1590
+ } while (continuationToken);
1591
+ return {
1592
+ commonPrefixes: Array.from(commonPrefixes),
1593
+ keys
1594
+ };
1595
+ };
1596
+ const collectUpdateJsonKeys = async (currentPrefix) => {
1597
+ const { commonPrefixes, keys } = await listPrefix(currentPrefix);
1598
+ const depth = getDirectoryDepth(currentPrefix, normalizedRootPrefix);
1599
+ if (depth >= 2) return [...keys.filter(isUpdateJsonKey), ...commonPrefixes.map((commonPrefix) => `${commonPrefix}update.json`)];
1600
+ return (await mapWithConcurrency(depth === 1 ? commonPrefixes.filter(isPlatformDirectoryPrefix) : commonPrefixes, S3_LIST_OBJECTS_CONCURRENCY, (nextPrefix) => collectUpdateJsonKeys(nextPrefix))).flat();
1601
+ };
1602
+ const normalizedPrefix = normalizeDirectoryPrefix(prefix);
1603
+ return Array.from(new Set(await collectUpdateJsonKeys(normalizedPrefix)));
1462
1604
  }
1463
1605
  async function deleteObjectInS3(client, bucketName, key) {
1464
1606
  await client.send(new _aws_sdk_client_s3.DeleteObjectCommand({
@@ -1510,18 +1652,21 @@ async function invalidateCloudFront(client, distributionId, paths, options) {
1510
1652
  const s3Database = (0, _hot_updater_plugin_core.createBlobDatabasePlugin)({
1511
1653
  name: "s3Database",
1512
1654
  factory: (config) => {
1513
- const { bucketName, cloudfrontDistributionId, apiBasePath = "/api/check-update", shouldWaitForInvalidation = false, ...s3Config } = config;
1655
+ const { basePath, bucketName, cloudfrontDistributionId, apiBasePath = "/api/check-update", shouldWaitForInvalidation = false, ...s3Config } = config;
1514
1656
  const client = new _aws_sdk_client_s3.S3Client(applyS3RuntimeAwsConfig(s3Config));
1657
+ const { fromStorageKey, toStorageKey } = createDatabaseKeyBuilder(basePath);
1658
+ const rootPrefix = toStorageKey("");
1515
1659
  const cloudfrontClient = cloudfrontDistributionId ? new _aws_sdk_client_cloudfront.CloudFrontClient({
1516
1660
  credentials: s3Config.credentials,
1517
1661
  region: s3Config.region
1518
1662
  }) : void 0;
1519
1663
  return {
1520
1664
  apiBasePath,
1521
- listObjects: (prefix) => listObjectsInS3(client, bucketName, prefix),
1522
- loadObject: (key) => loadJsonFromS3(client, bucketName, key),
1523
- uploadObject: (key, data) => uploadJsonToS3(client, bucketName, key, data),
1524
- deleteObject: (key) => deleteObjectInS3(client, bucketName, key),
1665
+ listObjects: (prefix) => listObjectsInS3(client, bucketName, toStorageKey(prefix), rootPrefix).then((keys) => keys.map(fromStorageKey)),
1666
+ loadObject: (key) => loadJsonFromS3(client, bucketName, toStorageKey(key)),
1667
+ uploadObject: (key, data) => uploadJsonToS3(client, bucketName, toStorageKey(key), data),
1668
+ deleteObject: (key) => deleteObjectInS3(client, bucketName, toStorageKey(key)),
1669
+ shouldSkipLoadObjectError: (error) => error instanceof Error && error.name === "S3ArchivedObjectError",
1525
1670
  invalidatePaths: (pathsToInvalidate) => {
1526
1671
  if (cloudfrontClient && cloudfrontDistributionId && pathsToInvalidate.length > 0) return invalidateCloudFront(cloudfrontClient, cloudfrontDistributionId, pathsToInvalidate, { shouldWaitForInvalidation });
1527
1672
  return Promise.resolve();
package/dist/index.d.cts CHANGED
@@ -1,10 +1,14 @@
1
1
  import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
- import { BlobDatabasePluginConfig, RuntimeStoragePlugin, StoragePluginHooks, StorageResolveContext } from "@hot-updater/plugin-core";
2
+ import { RuntimeStoragePlugin, StoragePluginHooks, StorageResolveContext } from "@hot-updater/plugin-core";
3
3
  import { S3ClientConfig } from "@aws-sdk/client-s3";
4
4
 
5
5
  //#region src/s3Database.d.ts
6
- interface S3DatabaseConfig extends S3ClientConfig, BlobDatabasePluginConfig {
6
+ interface S3DatabaseConfig extends S3ClientConfig {
7
7
  bucketName: string;
8
+ /**
9
+ * Base path where database objects will be stored in the bucket.
10
+ */
11
+ basePath?: string;
8
12
  /**
9
13
  * CloudFront distribution ID used for cache invalidation.
10
14
  *
package/dist/index.d.mts CHANGED
@@ -1,10 +1,14 @@
1
1
  import { S3ClientConfig } from "@aws-sdk/client-s3";
2
2
  import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
3
- import { BlobDatabasePluginConfig, RuntimeStoragePlugin, StoragePluginHooks, StorageResolveContext } from "@hot-updater/plugin-core";
3
+ import { RuntimeStoragePlugin, StoragePluginHooks, StorageResolveContext } from "@hot-updater/plugin-core";
4
4
 
5
5
  //#region src/s3Database.d.ts
6
- interface S3DatabaseConfig extends S3ClientConfig, BlobDatabasePluginConfig {
6
+ interface S3DatabaseConfig extends S3ClientConfig {
7
7
  bucketName: string;
8
+ /**
9
+ * Base path where database objects will be stored in the bucket.
10
+ */
11
+ basePath?: string;
8
12
  /**
9
13
  * CloudFront distribution ID used for cache invalidation.
10
14
  *