@hot-updater/plugin-core 0.31.4 → 0.33.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/assetStorageLayout.cjs +34 -0
- package/dist/assetStorageLayout.d.cts +31 -0
- package/dist/assetStorageLayout.d.mts +31 -0
- package/dist/assetStorageLayout.mjs +30 -0
- package/dist/compressionFormat.cjs +2 -2
- package/dist/contentAddressedAssets.cjs +7 -0
- package/dist/contentAddressedAssets.d.cts +10 -0
- package/dist/contentAddressedAssets.d.mts +10 -0
- package/dist/contentAddressedAssets.mjs +7 -0
- package/dist/createBlobDatabasePlugin.cjs +155 -55
- package/dist/createBlobDatabasePlugin.d.cts +1 -0
- package/dist/createBlobDatabasePlugin.d.mts +1 -0
- package/dist/createBlobDatabasePlugin.mjs +154 -54
- package/dist/createDatabasePluginGetUpdateInfo.cjs +11 -3
- package/dist/createDatabasePluginGetUpdateInfo.mjs +11 -3
- package/dist/createStoragePlugin.cjs +3 -0
- package/dist/createStoragePlugin.mjs +3 -0
- package/dist/index.cjs +12 -0
- package/dist/index.d.cts +6 -2
- package/dist/index.d.mts +6 -2
- package/dist/index.mjs +5 -1
- package/dist/legacyAssetStorageLayout.cjs +12 -0
- package/dist/legacyAssetStorageLayout.mjs +12 -0
- package/dist/requestUpdateBundleState.cjs +20 -0
- package/dist/requestUpdateBundleState.d.cts +6 -0
- package/dist/requestUpdateBundleState.d.mts +6 -0
- package/dist/requestUpdateBundleState.mjs +19 -0
- package/dist/resolveUpdateInfoFromBundles.cjs +14 -0
- package/dist/resolveUpdateInfoFromBundles.d.cts +16 -0
- package/dist/resolveUpdateInfoFromBundles.d.mts +16 -0
- package/dist/resolveUpdateInfoFromBundles.mjs +13 -0
- package/dist/semverSatisfies.cjs +1 -1
- package/dist/types/index.d.cts +47 -1
- package/dist/types/index.d.mts +47 -1
- package/package.json +4 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const require_contentAddressedAssets = require("./contentAddressedAssets.cjs");
|
|
2
|
+
const require_legacyAssetStorageLayout = require("./legacyAssetStorageLayout.cjs");
|
|
3
|
+
//#region src/assetStorageLayout.ts
|
|
4
|
+
const createStorageUriWithRelativePath = ({ baseStorageUri, relativePath }) => {
|
|
5
|
+
const storageUrl = new URL(baseStorageUri);
|
|
6
|
+
storageUrl.pathname = `${storageUrl.pathname.replace(/\/+$/, "")}/${relativePath.replace(/\\/g, "/").split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
7
|
+
return storageUrl.toString();
|
|
8
|
+
};
|
|
9
|
+
const getAssetStorageLayout = (assetBaseStorageUri) => {
|
|
10
|
+
const pathname = new URL(assetBaseStorageUri).pathname.replace(/\/+$/, "");
|
|
11
|
+
return pathname.endsWith("/assets") || pathname === "/assets" ? "content-addressed" : "legacy-files";
|
|
12
|
+
};
|
|
13
|
+
const isContentAddressedAssetBaseStorageUri = (assetBaseStorageUri) => getAssetStorageLayout(assetBaseStorageUri) === "content-addressed";
|
|
14
|
+
const getManifestAssetStoragePath = ({ assetBaseStorageUri, assetPath, fileHash }) => {
|
|
15
|
+
if (getAssetStorageLayout(assetBaseStorageUri) === "content-addressed") return require_contentAddressedAssets.getContentAddressedAssetStoragePath({
|
|
16
|
+
assetPath,
|
|
17
|
+
fileHash
|
|
18
|
+
});
|
|
19
|
+
return require_legacyAssetStorageLayout.getLegacyManifestAssetStoragePath({ assetPath });
|
|
20
|
+
};
|
|
21
|
+
const resolveManifestAssetStorageUri = ({ assetBaseStorageUri, assetPath, fileHash }) => createStorageUriWithRelativePath({
|
|
22
|
+
baseStorageUri: assetBaseStorageUri,
|
|
23
|
+
relativePath: getManifestAssetStoragePath({
|
|
24
|
+
assetBaseStorageUri,
|
|
25
|
+
assetPath,
|
|
26
|
+
fileHash
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
exports.createStorageUriWithRelativePath = createStorageUriWithRelativePath;
|
|
31
|
+
exports.getAssetStorageLayout = getAssetStorageLayout;
|
|
32
|
+
exports.getManifestAssetStoragePath = getManifestAssetStoragePath;
|
|
33
|
+
exports.isContentAddressedAssetBaseStorageUri = isContentAddressedAssetBaseStorageUri;
|
|
34
|
+
exports.resolveManifestAssetStorageUri = resolveManifestAssetStorageUri;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/assetStorageLayout.d.ts
|
|
2
|
+
type AssetStorageLayout = "content-addressed" | "legacy-files";
|
|
3
|
+
declare const createStorageUriWithRelativePath: ({
|
|
4
|
+
baseStorageUri,
|
|
5
|
+
relativePath
|
|
6
|
+
}: {
|
|
7
|
+
baseStorageUri: string;
|
|
8
|
+
relativePath: string;
|
|
9
|
+
}) => string;
|
|
10
|
+
declare const getAssetStorageLayout: (assetBaseStorageUri: string) => AssetStorageLayout;
|
|
11
|
+
declare const isContentAddressedAssetBaseStorageUri: (assetBaseStorageUri: string) => boolean;
|
|
12
|
+
declare const getManifestAssetStoragePath: ({
|
|
13
|
+
assetBaseStorageUri,
|
|
14
|
+
assetPath,
|
|
15
|
+
fileHash
|
|
16
|
+
}: {
|
|
17
|
+
assetBaseStorageUri: string;
|
|
18
|
+
assetPath: string;
|
|
19
|
+
fileHash: string;
|
|
20
|
+
}) => string;
|
|
21
|
+
declare const resolveManifestAssetStorageUri: ({
|
|
22
|
+
assetBaseStorageUri,
|
|
23
|
+
assetPath,
|
|
24
|
+
fileHash
|
|
25
|
+
}: {
|
|
26
|
+
assetBaseStorageUri: string;
|
|
27
|
+
assetPath: string;
|
|
28
|
+
fileHash: string;
|
|
29
|
+
}) => string;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { AssetStorageLayout, createStorageUriWithRelativePath, getAssetStorageLayout, getManifestAssetStoragePath, isContentAddressedAssetBaseStorageUri, resolveManifestAssetStorageUri };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/assetStorageLayout.d.ts
|
|
2
|
+
type AssetStorageLayout = "content-addressed" | "legacy-files";
|
|
3
|
+
declare const createStorageUriWithRelativePath: ({
|
|
4
|
+
baseStorageUri,
|
|
5
|
+
relativePath
|
|
6
|
+
}: {
|
|
7
|
+
baseStorageUri: string;
|
|
8
|
+
relativePath: string;
|
|
9
|
+
}) => string;
|
|
10
|
+
declare const getAssetStorageLayout: (assetBaseStorageUri: string) => AssetStorageLayout;
|
|
11
|
+
declare const isContentAddressedAssetBaseStorageUri: (assetBaseStorageUri: string) => boolean;
|
|
12
|
+
declare const getManifestAssetStoragePath: ({
|
|
13
|
+
assetBaseStorageUri,
|
|
14
|
+
assetPath,
|
|
15
|
+
fileHash
|
|
16
|
+
}: {
|
|
17
|
+
assetBaseStorageUri: string;
|
|
18
|
+
assetPath: string;
|
|
19
|
+
fileHash: string;
|
|
20
|
+
}) => string;
|
|
21
|
+
declare const resolveManifestAssetStorageUri: ({
|
|
22
|
+
assetBaseStorageUri,
|
|
23
|
+
assetPath,
|
|
24
|
+
fileHash
|
|
25
|
+
}: {
|
|
26
|
+
assetBaseStorageUri: string;
|
|
27
|
+
assetPath: string;
|
|
28
|
+
fileHash: string;
|
|
29
|
+
}) => string;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { AssetStorageLayout, createStorageUriWithRelativePath, getAssetStorageLayout, getManifestAssetStoragePath, isContentAddressedAssetBaseStorageUri, resolveManifestAssetStorageUri };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getContentAddressedAssetStoragePath } from "./contentAddressedAssets.mjs";
|
|
2
|
+
import { getLegacyManifestAssetStoragePath } from "./legacyAssetStorageLayout.mjs";
|
|
3
|
+
//#region src/assetStorageLayout.ts
|
|
4
|
+
const createStorageUriWithRelativePath = ({ baseStorageUri, relativePath }) => {
|
|
5
|
+
const storageUrl = new URL(baseStorageUri);
|
|
6
|
+
storageUrl.pathname = `${storageUrl.pathname.replace(/\/+$/, "")}/${relativePath.replace(/\\/g, "/").split("/").filter(Boolean).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
7
|
+
return storageUrl.toString();
|
|
8
|
+
};
|
|
9
|
+
const getAssetStorageLayout = (assetBaseStorageUri) => {
|
|
10
|
+
const pathname = new URL(assetBaseStorageUri).pathname.replace(/\/+$/, "");
|
|
11
|
+
return pathname.endsWith("/assets") || pathname === "/assets" ? "content-addressed" : "legacy-files";
|
|
12
|
+
};
|
|
13
|
+
const isContentAddressedAssetBaseStorageUri = (assetBaseStorageUri) => getAssetStorageLayout(assetBaseStorageUri) === "content-addressed";
|
|
14
|
+
const getManifestAssetStoragePath = ({ assetBaseStorageUri, assetPath, fileHash }) => {
|
|
15
|
+
if (getAssetStorageLayout(assetBaseStorageUri) === "content-addressed") return getContentAddressedAssetStoragePath({
|
|
16
|
+
assetPath,
|
|
17
|
+
fileHash
|
|
18
|
+
});
|
|
19
|
+
return getLegacyManifestAssetStoragePath({ assetPath });
|
|
20
|
+
};
|
|
21
|
+
const resolveManifestAssetStorageUri = ({ assetBaseStorageUri, assetPath, fileHash }) => createStorageUriWithRelativePath({
|
|
22
|
+
baseStorageUri: assetBaseStorageUri,
|
|
23
|
+
relativePath: getManifestAssetStoragePath({
|
|
24
|
+
assetBaseStorageUri,
|
|
25
|
+
assetPath,
|
|
26
|
+
fileHash
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
//#endregion
|
|
30
|
+
export { createStorageUriWithRelativePath, getAssetStorageLayout, getManifestAssetStoragePath, isContentAddressedAssetBaseStorageUri, resolveManifestAssetStorageUri };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
|
|
2
2
|
let node_path = require("node:path");
|
|
3
|
-
node_path = require_runtime.__toESM(node_path
|
|
3
|
+
node_path = require_runtime.__toESM(node_path);
|
|
4
4
|
let mime = require("mime");
|
|
5
|
-
mime = require_runtime.__toESM(mime
|
|
5
|
+
mime = require_runtime.__toESM(mime);
|
|
6
6
|
//#region src/compressionFormat.ts
|
|
7
7
|
/**
|
|
8
8
|
* Compression formats registry
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/contentAddressedAssets.ts
|
|
2
|
+
const getContentAddressedAssetStoragePath = ({ assetPath, fileHash }) => {
|
|
3
|
+
const extension = assetPath.endsWith(".br") ? ".br" : assetPath.includes(".") ? `.${assetPath.split(".").pop()}` : "";
|
|
4
|
+
return `sha256/${fileHash.slice(0, 2)}/${fileHash}${extension}`;
|
|
5
|
+
};
|
|
6
|
+
//#endregion
|
|
7
|
+
exports.getContentAddressedAssetStoragePath = getContentAddressedAssetStoragePath;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/contentAddressedAssets.ts
|
|
2
|
+
const getContentAddressedAssetStoragePath = ({ assetPath, fileHash }) => {
|
|
3
|
+
const extension = assetPath.endsWith(".br") ? ".br" : assetPath.includes(".") ? `.${assetPath.split(".").pop()}` : "";
|
|
4
|
+
return `sha256/${fileHash.slice(0, 2)}/${fileHash}${extension}`;
|
|
5
|
+
};
|
|
6
|
+
//#endregion
|
|
7
|
+
export { getContentAddressedAssetStoragePath };
|
|
@@ -4,11 +4,29 @@ const require_createDatabasePlugin = require("./createDatabasePlugin.cjs");
|
|
|
4
4
|
const require_filterCompatibleAppVersions = require("./filterCompatibleAppVersions.cjs");
|
|
5
5
|
const require_queryBundles = require("./queryBundles.cjs");
|
|
6
6
|
const require_paginateBundles = require("./paginateBundles.cjs");
|
|
7
|
-
|
|
7
|
+
const require_resolveUpdateInfoFromBundles = require("./resolveUpdateInfoFromBundles.cjs");
|
|
8
8
|
let es_toolkit = require("es-toolkit");
|
|
9
9
|
let semver = require("semver");
|
|
10
|
-
semver = require_runtime.__toESM(semver
|
|
10
|
+
semver = require_runtime.__toESM(semver);
|
|
11
11
|
//#region src/createBlobDatabasePlugin.ts
|
|
12
|
+
const STORAGE_OPERATION_CONCURRENCY = 8;
|
|
13
|
+
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
14
|
+
const results = [];
|
|
15
|
+
let nextIndex = 0;
|
|
16
|
+
const workerCount = Math.min(concurrency, items.length);
|
|
17
|
+
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
18
|
+
while (true) {
|
|
19
|
+
const index = nextIndex;
|
|
20
|
+
nextIndex += 1;
|
|
21
|
+
if (index >= items.length) break;
|
|
22
|
+
results[index] = await mapper(items[index], index);
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
27
|
+
async function forEachWithConcurrency(items, concurrency, mapper) {
|
|
28
|
+
await mapWithConcurrency(items, concurrency, mapper);
|
|
29
|
+
}
|
|
12
30
|
function removeBundleInternalKeys(bundle) {
|
|
13
31
|
const { _updateJsonKey, _oldUpdateJsonKey, ...pureBundle } = bundle;
|
|
14
32
|
return pureBundle;
|
|
@@ -57,6 +75,12 @@ const MANAGEMENT_INDEX_PREFIX = "_index";
|
|
|
57
75
|
const MANAGEMENT_INDEX_VERSION = 1;
|
|
58
76
|
const DEFAULT_MANAGEMENT_INDEX_PAGE_SIZE = 128;
|
|
59
77
|
const ALL_SCOPE_CACHE_KEY = "*|*";
|
|
78
|
+
function summarizeManagementIndexArtifacts(artifacts) {
|
|
79
|
+
return {
|
|
80
|
+
pagesWritten: artifacts.pages.size,
|
|
81
|
+
scopesWritten: artifacts.scopes.length
|
|
82
|
+
};
|
|
83
|
+
}
|
|
60
84
|
function resolveManagementIndexPageSize(config) {
|
|
61
85
|
const pageSize = config.managementIndexPageSize ?? DEFAULT_MANAGEMENT_INDEX_PAGE_SIZE;
|
|
62
86
|
if (!Number.isInteger(pageSize) || pageSize < 1) throw new Error("managementIndexPageSize must be a positive integer.");
|
|
@@ -188,11 +212,18 @@ function buildManagementIndexArtifacts(allBundles, pageSize) {
|
|
|
188
212
|
const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
189
213
|
return (config, hooks) => {
|
|
190
214
|
const managementIndexPageSize = resolveManagementIndexPageSize(config);
|
|
191
|
-
const { listObjects, loadObject, uploadObject, deleteObject, invalidatePaths, apiBasePath } = factory(config);
|
|
215
|
+
const { listObjects, loadObject, uploadObject, deleteObject, shouldSkipLoadObjectError, invalidatePaths, apiBasePath } = factory(config);
|
|
192
216
|
const bundlesMap = /* @__PURE__ */ new Map();
|
|
193
217
|
const pendingBundlesMap = /* @__PURE__ */ new Map();
|
|
194
218
|
const managementRootCache = /* @__PURE__ */ new Map();
|
|
195
|
-
const
|
|
219
|
+
const loadOptionalObject = async (key) => {
|
|
220
|
+
try {
|
|
221
|
+
return await loadObject(key);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (shouldSkipLoadObjectError?.(error, key)) return null;
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
};
|
|
196
227
|
const getAllManagementArtifact = (artifacts) => {
|
|
197
228
|
const allArtifact = artifacts.scopes.find((scope) => scope.cacheKey === ALL_SCOPE_CACHE_KEY);
|
|
198
229
|
if (!allArtifact) throw new Error("all-bundles management index artifact not found");
|
|
@@ -209,7 +240,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
209
240
|
};
|
|
210
241
|
const loadStoredManagementRoot = async (scope) => {
|
|
211
242
|
const cacheKey = getManagementScopeCacheKey(scope);
|
|
212
|
-
const storedRoot = await
|
|
243
|
+
const storedRoot = await loadOptionalObject(getManagementRootKey(scope));
|
|
213
244
|
if (storedRoot) {
|
|
214
245
|
managementRootCache.set(cacheKey, storedRoot);
|
|
215
246
|
return storedRoot;
|
|
@@ -219,7 +250,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
219
250
|
};
|
|
220
251
|
const loadManagementPage = async (descriptor, pageCache) => {
|
|
221
252
|
if (pageCache?.has(descriptor.key)) return pageCache.get(descriptor.key) ?? null;
|
|
222
|
-
const page = await
|
|
253
|
+
const page = await loadOptionalObject(descriptor.key);
|
|
223
254
|
pageCache?.set(descriptor.key, page);
|
|
224
255
|
return page;
|
|
225
256
|
};
|
|
@@ -242,13 +273,13 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
242
273
|
return allBundles;
|
|
243
274
|
};
|
|
244
275
|
const persistManagementIndexArtifacts = async (nextArtifacts, previousArtifacts) => {
|
|
245
|
-
|
|
246
|
-
|
|
276
|
+
await forEachWithConcurrency(Array.from(nextArtifacts.pages.entries()), STORAGE_OPERATION_CONCURRENCY, ([key, page]) => uploadObject(key, page));
|
|
277
|
+
await forEachWithConcurrency(nextArtifacts.scopes, STORAGE_OPERATION_CONCURRENCY, (scope) => uploadObject(scope.rootKey, scope.root));
|
|
247
278
|
if (!previousArtifacts) return;
|
|
248
279
|
const nextPageKeys = new Set(nextArtifacts.pages.keys());
|
|
249
280
|
const nextRootKeys = new Set(nextArtifacts.scopes.map((scope) => scope.rootKey));
|
|
250
|
-
|
|
251
|
-
|
|
281
|
+
await forEachWithConcurrency(Array.from(previousArtifacts.pages.keys()).filter((key) => !nextPageKeys.has(key)), STORAGE_OPERATION_CONCURRENCY, (key) => deleteObject(key).catch(() => {}));
|
|
282
|
+
await forEachWithConcurrency(previousArtifacts.scopes.filter((scope) => !nextRootKeys.has(scope.rootKey)), STORAGE_OPERATION_CONCURRENCY, (scope) => deleteObject(scope.rootKey).catch(() => {}));
|
|
252
283
|
};
|
|
253
284
|
const ensureAllManagementRoot = async () => {
|
|
254
285
|
const storedAllRoot = await loadStoredManagementRoot({});
|
|
@@ -281,6 +312,26 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
281
312
|
const loadCurrentBundlesForIndexRebuild = async () => {
|
|
282
313
|
return loadAllBundlesForManagementFallback();
|
|
283
314
|
};
|
|
315
|
+
const loadBundlesFromCanonicalManifests = async () => {
|
|
316
|
+
return sortManagedBundles((await reloadBundles()).map((bundle) => removeBundleInternalKeys(bundle)));
|
|
317
|
+
};
|
|
318
|
+
const loadStoredBundlesForIndexRebuild = loadBundlesFromCanonicalManifests;
|
|
319
|
+
const loadCanonicalBundlesForIndexRepair = loadBundlesFromCanonicalManifests;
|
|
320
|
+
const compareBundleIndex = ({ canonicalBundles, indexedBundles, rootMissing }) => {
|
|
321
|
+
const canonicalIds = new Set(canonicalBundles.map((bundle) => bundle.id));
|
|
322
|
+
const indexedIds = new Set(indexedBundles?.map((bundle) => bundle.id) ?? []);
|
|
323
|
+
const missingBundleIds = Array.from(canonicalIds).filter((id) => !indexedIds.has(id)).sort((left, right) => right.localeCompare(left));
|
|
324
|
+
const extraBundleIds = Array.from(indexedIds).filter((id) => !canonicalIds.has(id)).sort((left, right) => right.localeCompare(left));
|
|
325
|
+
return {
|
|
326
|
+
status: missingBundleIds.length === 0 && extraBundleIds.length === 0 && !rootMissing ? "ok" : rootMissing ? "missing" : "stale",
|
|
327
|
+
canonicalBundles: canonicalBundles.length,
|
|
328
|
+
indexedBundles: indexedBundles?.length ?? 0,
|
|
329
|
+
missingBundles: missingBundleIds.length,
|
|
330
|
+
extraBundles: extraBundleIds.length,
|
|
331
|
+
missingBundleIds: missingBundleIds.slice(0, 20),
|
|
332
|
+
extraBundleIds: extraBundleIds.slice(0, 20)
|
|
333
|
+
};
|
|
334
|
+
};
|
|
284
335
|
const findPageIndexContainingId = (pages, id) => {
|
|
285
336
|
return pages.findIndex((page) => id.localeCompare(page.firstId) <= 0 && id.localeCompare(page.lastId) >= 0);
|
|
286
337
|
};
|
|
@@ -456,16 +507,15 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
456
507
|
};
|
|
457
508
|
async function reloadBundles() {
|
|
458
509
|
bundlesMap.clear();
|
|
459
|
-
const
|
|
460
|
-
return (await
|
|
510
|
+
const allBundles = (await mapWithConcurrency((await listObjects("")).filter((key) => /^[^/]+\/(?:ios|android)\/[^/]+\/update\.json$/.test(key)), STORAGE_OPERATION_CONCURRENCY, async (key) => {
|
|
511
|
+
return (await loadOptionalObject(key) ?? []).map((bundle) => ({
|
|
461
512
|
...bundle,
|
|
462
513
|
_updateJsonKey: key
|
|
463
514
|
}));
|
|
464
|
-
});
|
|
465
|
-
const allBundles = (await Promise.all(filePromises)).flat();
|
|
515
|
+
})).flat();
|
|
466
516
|
for (const bundle of allBundles) bundlesMap.set(bundle.id, bundle);
|
|
467
517
|
for (const [id, bundle] of pendingBundlesMap.entries()) bundlesMap.set(id, bundle);
|
|
468
|
-
return (0, es_toolkit.orderBy)(
|
|
518
|
+
return (0, es_toolkit.orderBy)(Array.from(bundlesMap.values()), [(v) => v.id], ["desc"]);
|
|
469
519
|
}
|
|
470
520
|
/**
|
|
471
521
|
* Updates target-app-versions.json for each channel on the given platform.
|
|
@@ -491,35 +541,44 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
491
541
|
const updateKeys = keysByChannel[channel];
|
|
492
542
|
const targetKey = `${channel}/${platform}/target-app-versions.json`;
|
|
493
543
|
const currentVersions = updateKeys.map((key) => key.split("/")[2]);
|
|
494
|
-
const oldTargetVersions = await
|
|
544
|
+
const oldTargetVersions = await loadOptionalObject(targetKey) ?? [];
|
|
495
545
|
const newTargetVersions = oldTargetVersions.filter((v) => currentVersions.includes(v));
|
|
496
546
|
for (const v of currentVersions) if (!newTargetVersions.includes(v)) newTargetVersions.push(v);
|
|
497
547
|
if (JSON.stringify(oldTargetVersions) !== JSON.stringify(newTargetVersions)) await uploadObject(targetKey, newTargetVersions);
|
|
498
548
|
}
|
|
499
549
|
}
|
|
500
|
-
const getAppVersionUpdateInfo = async ({ appVersion, bundleId, channel = "production", cohort, minBundleId, platform }) => {
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
550
|
+
const getAppVersionUpdateInfo = async ({ appVersion, bundleId, channel = "production", cohort, minBundleId, platform }, context) => {
|
|
551
|
+
const bundles = (await mapWithConcurrency(require_filterCompatibleAppVersions.filterCompatibleAppVersions(await loadOptionalObject(`${channel}/${platform}/target-app-versions.json`) ?? [], appVersion), STORAGE_OPERATION_CONCURRENCY, async (targetAppVersion) => {
|
|
552
|
+
return await loadOptionalObject(`${channel}/${platform}/${normalizeTargetAppVersion(targetAppVersion) ?? targetAppVersion}/update.json`) ?? [];
|
|
553
|
+
})).flat();
|
|
554
|
+
return require_resolveUpdateInfoFromBundles.resolveUpdateInfoFromBundles({
|
|
555
|
+
args: {
|
|
556
|
+
_updateStrategy: "appVersion",
|
|
557
|
+
appVersion,
|
|
558
|
+
bundleId,
|
|
559
|
+
channel,
|
|
560
|
+
cohort,
|
|
561
|
+
minBundleId,
|
|
562
|
+
platform
|
|
563
|
+
},
|
|
564
|
+
bundles,
|
|
565
|
+
context
|
|
512
566
|
});
|
|
513
567
|
};
|
|
514
|
-
const getFingerprintUpdateInfo = async ({ bundleId, channel = "production", cohort, fingerprintHash, minBundleId, platform }) => {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
568
|
+
const getFingerprintUpdateInfo = async ({ bundleId, channel = "production", cohort, fingerprintHash, minBundleId, platform }, context) => {
|
|
569
|
+
const bundles = await loadOptionalObject(`${channel}/${platform}/${fingerprintHash}/update.json`) ?? [];
|
|
570
|
+
return require_resolveUpdateInfoFromBundles.resolveUpdateInfoFromBundles({
|
|
571
|
+
args: {
|
|
572
|
+
_updateStrategy: "fingerprint",
|
|
573
|
+
bundleId,
|
|
574
|
+
channel,
|
|
575
|
+
cohort,
|
|
576
|
+
fingerprintHash,
|
|
577
|
+
minBundleId,
|
|
578
|
+
platform
|
|
579
|
+
},
|
|
580
|
+
bundles,
|
|
581
|
+
context
|
|
523
582
|
});
|
|
524
583
|
};
|
|
525
584
|
const addAppVersionInvalidationPaths = (pathsToInvalidate, { platform, channel, targetAppVersion }) => {
|
|
@@ -541,7 +600,35 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
541
600
|
targetAppVersion
|
|
542
601
|
});
|
|
543
602
|
};
|
|
544
|
-
|
|
603
|
+
const bundleIndexDiagnostics = {
|
|
604
|
+
async check() {
|
|
605
|
+
const canonicalBundles = await loadCanonicalBundlesForIndexRepair();
|
|
606
|
+
const allRoot = await loadStoredManagementRoot({});
|
|
607
|
+
return compareBundleIndex({
|
|
608
|
+
canonicalBundles,
|
|
609
|
+
indexedBundles: allRoot ? await loadAllBundlesFromRoot(allRoot) : null,
|
|
610
|
+
rootMissing: !allRoot
|
|
611
|
+
});
|
|
612
|
+
},
|
|
613
|
+
async repair() {
|
|
614
|
+
const canonicalBundles = await loadCanonicalBundlesForIndexRepair();
|
|
615
|
+
const previousRoot = await loadStoredManagementRoot({});
|
|
616
|
+
const previousBundles = previousRoot ? await loadAllBundlesFromRoot(previousRoot) : null;
|
|
617
|
+
const previousArtifacts = previousRoot && previousBundles ? buildManagementIndexArtifacts(previousBundles, previousRoot.pageSize) : void 0;
|
|
618
|
+
const nextArtifacts = buildManagementIndexArtifacts(canonicalBundles, managementIndexPageSize);
|
|
619
|
+
const indexedObjectKeys = await listObjects(`${MANAGEMENT_INDEX_PREFIX}/`);
|
|
620
|
+
const nextObjectKeys = new Set([...nextArtifacts.pages.keys(), ...nextArtifacts.scopes.map((scope) => scope.rootKey)]);
|
|
621
|
+
await persistManagementIndexArtifacts(nextArtifacts, previousArtifacts);
|
|
622
|
+
await forEachWithConcurrency(indexedObjectKeys.filter((key) => !nextObjectKeys.has(key)), STORAGE_OPERATION_CONCURRENCY, (key) => deleteObject(key).catch(() => {}));
|
|
623
|
+
replaceManagementRootCache(nextArtifacts);
|
|
624
|
+
return {
|
|
625
|
+
scannedBundles: canonicalBundles.length,
|
|
626
|
+
indexedBundles: canonicalBundles.length,
|
|
627
|
+
...summarizeManagementIndexArtifacts(nextArtifacts)
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
const createPlugin = require_createDatabasePlugin.createDatabasePlugin({
|
|
545
632
|
name,
|
|
546
633
|
factory: () => ({
|
|
547
634
|
supportsCursorPagination: true,
|
|
@@ -565,9 +652,9 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
565
652
|
if (!matchedBundle) return null;
|
|
566
653
|
return removeBundleInternalKeys(matchedBundle);
|
|
567
654
|
},
|
|
568
|
-
async getUpdateInfo(args) {
|
|
569
|
-
if (args._updateStrategy === "appVersion") return getAppVersionUpdateInfo(args);
|
|
570
|
-
return getFingerprintUpdateInfo(args);
|
|
655
|
+
async getUpdateInfo(args, context) {
|
|
656
|
+
if (args._updateStrategy === "appVersion") return getAppVersionUpdateInfo(args, context);
|
|
657
|
+
return getFingerprintUpdateInfo(args, context);
|
|
571
658
|
},
|
|
572
659
|
async getBundles(options) {
|
|
573
660
|
const { where, limit, offset, orderBy, cursor } = options;
|
|
@@ -600,11 +687,8 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
600
687
|
const changedBundlesByKey = {};
|
|
601
688
|
const removalsByKey = {};
|
|
602
689
|
const pathsToInvalidate = /* @__PURE__ */ new Set();
|
|
603
|
-
|
|
604
|
-
let isChannelChanged = false;
|
|
690
|
+
const targetVersionPlatforms = /* @__PURE__ */ new Set();
|
|
605
691
|
for (const { operation, data } of changedSets) {
|
|
606
|
-
if (data.targetAppVersion !== void 0) isTargetAppVersionChanged = true;
|
|
607
|
-
if (operation === "update" && data.channel !== void 0) isChannelChanged = true;
|
|
608
692
|
if (operation === "insert") {
|
|
609
693
|
const target = resolveStorageTarget(data);
|
|
610
694
|
const key = `${data.channel}/${data.platform}/${target}/update.json`;
|
|
@@ -616,6 +700,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
616
700
|
pendingBundlesMap.set(data.id, bundleWithKey);
|
|
617
701
|
changedBundlesByKey[key] = changedBundlesByKey[key] || [];
|
|
618
702
|
changedBundlesByKey[key].push(removeBundleInternalKeys(bundleWithKey));
|
|
703
|
+
if (data.targetAppVersion !== void 0) targetVersionPlatforms.add(data.platform);
|
|
619
704
|
addLookupInvalidationPaths(pathsToInvalidate, data);
|
|
620
705
|
continue;
|
|
621
706
|
}
|
|
@@ -628,6 +713,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
628
713
|
const key = bundle._updateJsonKey;
|
|
629
714
|
removalsByKey[key] = removalsByKey[key] || [];
|
|
630
715
|
removalsByKey[key].push(bundle.id);
|
|
716
|
+
if (bundle.targetAppVersion !== void 0) targetVersionPlatforms.add(bundle.platform);
|
|
631
717
|
addLookupInvalidationPaths(pathsToInvalidate, bundle);
|
|
632
718
|
continue;
|
|
633
719
|
}
|
|
@@ -659,6 +745,10 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
659
745
|
channel: nextChannel
|
|
660
746
|
});
|
|
661
747
|
}
|
|
748
|
+
if (bundle.targetAppVersion !== void 0 || updatedBundle.targetAppVersion !== void 0) {
|
|
749
|
+
targetVersionPlatforms.add(bundle.platform);
|
|
750
|
+
targetVersionPlatforms.add(updatedBundle.platform);
|
|
751
|
+
}
|
|
662
752
|
addLookupInvalidationPaths(pathsToInvalidate, updatedBundle);
|
|
663
753
|
if (bundle.targetAppVersion && bundle.targetAppVersion !== updatedBundle.targetAppVersion) addLookupInvalidationPaths(pathsToInvalidate, bundle);
|
|
664
754
|
continue;
|
|
@@ -672,14 +762,14 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
672
762
|
if (bundle.targetAppVersion && bundle.targetAppVersion !== updatedBundle.targetAppVersion) addLookupInvalidationPaths(pathsToInvalidate, bundle);
|
|
673
763
|
}
|
|
674
764
|
}
|
|
675
|
-
|
|
676
|
-
const updatedBundles = (await
|
|
765
|
+
await forEachWithConcurrency(Object.keys(removalsByKey), STORAGE_OPERATION_CONCURRENCY, async (oldKey) => {
|
|
766
|
+
const updatedBundles = (await loadOptionalObject(oldKey) ?? []).filter((b) => !removalsByKey[oldKey].includes(b.id));
|
|
677
767
|
updatedBundles.sort((a, b) => b.id.localeCompare(a.id));
|
|
678
768
|
if (updatedBundles.length === 0) await deleteObject(oldKey);
|
|
679
769
|
else await uploadObject(oldKey, updatedBundles);
|
|
680
|
-
})
|
|
681
|
-
|
|
682
|
-
const currentBundles = await
|
|
770
|
+
});
|
|
771
|
+
await forEachWithConcurrency(Object.keys(changedBundlesByKey), STORAGE_OPERATION_CONCURRENCY, async (key) => {
|
|
772
|
+
const currentBundles = await loadOptionalObject(key) ?? [];
|
|
683
773
|
const pureBundles = changedBundlesByKey[key].map((bundle) => bundle);
|
|
684
774
|
for (const changedBundle of pureBundles) {
|
|
685
775
|
const index = currentBundles.findIndex((b) => b.id === changedBundle.id);
|
|
@@ -688,10 +778,11 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
688
778
|
}
|
|
689
779
|
currentBundles.sort((a, b) => b.id.localeCompare(a.id));
|
|
690
780
|
await uploadObject(key, currentBundles);
|
|
691
|
-
})
|
|
692
|
-
if (
|
|
693
|
-
const
|
|
694
|
-
const
|
|
781
|
+
});
|
|
782
|
+
if (targetVersionPlatforms.size > 0) await Promise.all(Array.from(targetVersionPlatforms).map((platform) => updateTargetVersionsForPlatform(platform)));
|
|
783
|
+
const previousIndexBundles = await loadCurrentBundlesForIndexRebuild();
|
|
784
|
+
const storedIndexBundles = await loadStoredBundlesForIndexRebuild();
|
|
785
|
+
const nextIndexMap = new Map(storedIndexBundles.map((bundle) => [bundle.id, bundle]));
|
|
695
786
|
for (const { operation, data } of changedSets) {
|
|
696
787
|
if (operation === "delete") {
|
|
697
788
|
nextIndexMap.delete(data.id);
|
|
@@ -700,7 +791,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
700
791
|
nextIndexMap.set(data.id, data);
|
|
701
792
|
}
|
|
702
793
|
const nextIndexBundles = sortManagedBundles(Array.from(nextIndexMap.values()));
|
|
703
|
-
const previousArtifacts = buildManagementIndexArtifacts(
|
|
794
|
+
const previousArtifacts = buildManagementIndexArtifacts(previousIndexBundles, managementIndexPageSize);
|
|
704
795
|
const nextArtifacts = buildManagementIndexArtifacts(nextIndexBundles, managementIndexPageSize);
|
|
705
796
|
await persistManagementIndexArtifacts(nextArtifacts, previousArtifacts);
|
|
706
797
|
replaceManagementRootCache(nextArtifacts);
|
|
@@ -711,6 +802,15 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
|
|
|
711
802
|
}
|
|
712
803
|
})
|
|
713
804
|
})({}, hooks);
|
|
805
|
+
return () => {
|
|
806
|
+
const plugin = createPlugin();
|
|
807
|
+
Object.defineProperty(plugin, "diagnostics", {
|
|
808
|
+
configurable: true,
|
|
809
|
+
enumerable: true,
|
|
810
|
+
value: { bundleIndex: bundleIndexDiagnostics }
|
|
811
|
+
});
|
|
812
|
+
return plugin;
|
|
813
|
+
};
|
|
714
814
|
};
|
|
715
815
|
};
|
|
716
816
|
//#endregion
|
|
@@ -9,6 +9,7 @@ interface BlobOperations {
|
|
|
9
9
|
loadObject: <T>(key: string) => Promise<T | null>;
|
|
10
10
|
uploadObject: <T>(key: string, data: T) => Promise<void>;
|
|
11
11
|
deleteObject: (key: string) => Promise<void>;
|
|
12
|
+
shouldSkipLoadObjectError?: (error: unknown, key: string) => boolean;
|
|
12
13
|
invalidatePaths: (paths: string[]) => Promise<void>;
|
|
13
14
|
apiBasePath: string;
|
|
14
15
|
}
|
|
@@ -9,6 +9,7 @@ interface BlobOperations {
|
|
|
9
9
|
loadObject: <T>(key: string) => Promise<T | null>;
|
|
10
10
|
uploadObject: <T>(key: string, data: T) => Promise<void>;
|
|
11
11
|
deleteObject: (key: string) => Promise<void>;
|
|
12
|
+
shouldSkipLoadObjectError?: (error: unknown, key: string) => boolean;
|
|
12
13
|
invalidatePaths: (paths: string[]) => Promise<void>;
|
|
13
14
|
apiBasePath: string;
|
|
14
15
|
}
|