@hot-updater/plugin-core 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.
@@ -1,14 +1,31 @@
1
1
  const require_runtime = require("./_virtual/_rolldown/runtime.cjs");
2
- const require_calculatePagination = require("./calculatePagination.cjs");
2
+ const require_queryBundles = require("./queryBundles.cjs");
3
3
  const require_createDatabasePlugin = require("./createDatabasePlugin.cjs");
4
4
  const require_filterCompatibleAppVersions = require("./filterCompatibleAppVersions.cjs");
5
- const require_queryBundles = require("./queryBundles.cjs");
6
5
  const require_paginateBundles = require("./paginateBundles.cjs");
7
- let _hot_updater_js = require("@hot-updater/js");
6
+ const require_resolveUpdateInfoFromBundles = require("./resolveUpdateInfoFromBundles.cjs");
8
7
  let es_toolkit = require("es-toolkit");
9
8
  let semver = require("semver");
10
9
  semver = require_runtime.__toESM(semver);
11
10
  //#region src/createBlobDatabasePlugin.ts
11
+ const STORAGE_OPERATION_CONCURRENCY = 8;
12
+ async function mapWithConcurrency(items, concurrency, mapper) {
13
+ const results = [];
14
+ let nextIndex = 0;
15
+ const workerCount = Math.min(concurrency, items.length);
16
+ await Promise.all(Array.from({ length: workerCount }, async () => {
17
+ while (true) {
18
+ const index = nextIndex;
19
+ nextIndex += 1;
20
+ if (index >= items.length) break;
21
+ results[index] = await mapper(items[index], index);
22
+ }
23
+ }));
24
+ return results;
25
+ }
26
+ async function forEachWithConcurrency(items, concurrency, mapper) {
27
+ await mapWithConcurrency(items, concurrency, mapper);
28
+ }
12
29
  function removeBundleInternalKeys(bundle) {
13
30
  const { _updateJsonKey, _oldUpdateJsonKey, ...pureBundle } = bundle;
14
31
  return pureBundle;
@@ -49,134 +66,43 @@ function resolveStorageTarget({ targetAppVersion, fingerprintHash }) {
49
66
  if (!target) throw new Error("target not found");
50
67
  return target;
51
68
  }
52
- const DEFAULT_DESC_ORDER = {
53
- field: "id",
54
- direction: "desc"
55
- };
56
- const MANAGEMENT_INDEX_PREFIX = "_index";
57
- const MANAGEMENT_INDEX_VERSION = 1;
58
- const DEFAULT_MANAGEMENT_INDEX_PAGE_SIZE = 128;
59
- const ALL_SCOPE_CACHE_KEY = "*|*";
60
- function resolveManagementIndexPageSize(config) {
61
- const pageSize = config.managementIndexPageSize ?? DEFAULT_MANAGEMENT_INDEX_PAGE_SIZE;
62
- if (!Number.isInteger(pageSize) || pageSize < 1) throw new Error("managementIndexPageSize must be a positive integer.");
63
- return pageSize;
64
- }
65
- function sortManagedBundles(bundles, orderBy = DEFAULT_DESC_ORDER) {
66
- return require_queryBundles.sortBundles(bundles, orderBy);
67
- }
68
- function isDefaultManagementOrder(orderBy) {
69
- return orderBy === void 0 || orderBy.field === DEFAULT_DESC_ORDER.field && orderBy.direction === DEFAULT_DESC_ORDER.direction;
70
- }
71
- function hasUnsupportedManagementFilters(where) {
72
- if (!where) return false;
73
- return Boolean(where.enabled !== void 0 || where.id !== void 0 || where.targetAppVersion !== void 0 || where.targetAppVersionIn !== void 0 || where.targetAppVersionNotNull !== void 0 || where.fingerprintHash !== void 0);
69
+ function targetVersionMutationKey(bundle) {
70
+ return `${bundle.channel}/${bundle.platform}`;
74
71
  }
75
- function getSupportedManagementScope(where, orderBy) {
76
- if (!isDefaultManagementOrder(orderBy) || hasUnsupportedManagementFilters(where)) return null;
77
- return {
78
- channel: where?.channel,
79
- platform: where?.platform
72
+ function getTargetVersionMutation(mutations, bundle) {
73
+ const key = targetVersionMutationKey(bundle);
74
+ const existingMutation = mutations.get(key);
75
+ if (existingMutation) return existingMutation;
76
+ const mutation = {
77
+ additions: /* @__PURE__ */ new Set(),
78
+ channel: bundle.channel,
79
+ platform: bundle.platform,
80
+ removals: /* @__PURE__ */ new Set()
80
81
  };
82
+ mutations.set(key, mutation);
83
+ return mutation;
81
84
  }
82
- function encodeScopePart(value) {
83
- return encodeURIComponent(value);
84
- }
85
- function getManagementScopeCacheKey({ channel, platform }) {
86
- return `${channel ?? "*"}|${platform ?? "*"}`;
87
- }
88
- function getManagementScopePrefix({ channel, platform }) {
89
- if (channel && platform) return `${MANAGEMENT_INDEX_PREFIX}/channel/${encodeScopePart(channel)}/platform/${platform}`;
90
- if (channel) return `${MANAGEMENT_INDEX_PREFIX}/channel/${encodeScopePart(channel)}`;
91
- if (platform) return `${MANAGEMENT_INDEX_PREFIX}/platform/${platform}`;
92
- return `${MANAGEMENT_INDEX_PREFIX}/all`;
93
- }
94
- function getManagementRootKey(scope) {
95
- return `${getManagementScopePrefix(scope)}/root.json`;
96
- }
97
- function getManagementPageKey(scope, pageIndex) {
98
- return `${getManagementScopePrefix(scope)}/pages/${String(pageIndex).padStart(4, "0")}.json`;
85
+ function addTargetVersionAddition(mutations, bundle) {
86
+ const targetAppVersion = normalizeTargetAppVersion(bundle.targetAppVersion);
87
+ if (targetAppVersion == null) return;
88
+ getTargetVersionMutation(mutations, bundle).additions.add(targetAppVersion);
99
89
  }
100
- function createBundleWithUpdateJsonKey(bundle) {
101
- const target = resolveStorageTarget(bundle);
102
- return {
103
- ...bundle,
104
- _updateJsonKey: `${bundle.channel}/${bundle.platform}/${target}/update.json`
105
- };
106
- }
107
- function getPageStartOffsets(pages) {
108
- const startOffsets = [];
109
- let offset = 0;
110
- for (const page of pages) {
111
- startOffsets.push(offset);
112
- offset += page.count;
113
- }
114
- return startOffsets;
90
+ function addTargetVersionRemoval(mutations, bundle) {
91
+ const targetAppVersion = normalizeTargetAppVersion(bundle.targetAppVersion);
92
+ if (targetAppVersion == null) return;
93
+ getTargetVersionMutation(mutations, bundle).removals.add(targetAppVersion);
115
94
  }
116
- function createEmptyManagementResult(limit) {
117
- return {
118
- data: [],
119
- pagination: require_calculatePagination.calculatePagination(0, {
120
- limit,
121
- offset: 0
122
- })
123
- };
95
+ function getManagementListPrefixes(where) {
96
+ if (where?.channel && where.platform) return [`${where.channel}/${where.platform}/`];
97
+ if (where?.channel) return [`${where.channel}/`];
98
+ return [""];
124
99
  }
125
- function buildManagementIndexArtifacts(allBundles, pageSize) {
126
- const sortedAllBundles = sortManagedBundles(allBundles);
127
- const pages = /* @__PURE__ */ new Map();
128
- const scopes = [];
129
- const channels = [...new Set(sortedAllBundles.map((bundle) => bundle.channel))].sort();
130
- const addScope = (scope, scopeBundles, options) => {
131
- if (!options?.includeChannels && scopeBundles.length === 0) return;
132
- const pageKeys = [];
133
- const pageDescriptors = [];
134
- for (let pageIndex = 0; pageIndex * pageSize < scopeBundles.length; pageIndex++) {
135
- const page = scopeBundles.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
136
- const key = getManagementPageKey(scope, pageIndex);
137
- pages.set(key, page);
138
- pageKeys.push(key);
139
- pageDescriptors.push({
140
- key,
141
- count: page.length,
142
- firstId: page[0].id,
143
- lastId: page.at(-1).id
144
- });
145
- }
146
- const root = {
147
- version: MANAGEMENT_INDEX_VERSION,
148
- pageSize,
149
- total: scopeBundles.length,
150
- pages: pageDescriptors,
151
- ...options?.includeChannels ? { channels } : {}
152
- };
153
- scopes.push({
154
- cacheKey: getManagementScopeCacheKey(scope),
155
- rootKey: getManagementRootKey(scope),
156
- root,
157
- pageKeys
158
- });
159
- };
160
- addScope({}, sortedAllBundles, { includeChannels: true });
161
- for (const channel of channels) {
162
- const channelBundles = sortedAllBundles.filter((bundle) => bundle.channel === channel);
163
- addScope({ channel }, channelBundles);
164
- for (const platform of ["ios", "android"]) {
165
- const scopedBundles = channelBundles.filter((bundle) => bundle.platform === platform);
166
- addScope({
167
- channel,
168
- platform
169
- }, scopedBundles);
170
- }
171
- }
172
- for (const platform of ["ios", "android"]) {
173
- const platformBundles = sortedAllBundles.filter((bundle) => bundle.platform === platform);
174
- addScope({ platform }, platformBundles);
175
- }
176
- return {
177
- pages,
178
- scopes
179
- };
100
+ const DEFAULT_DESC_ORDER = {
101
+ field: "id",
102
+ direction: "desc"
103
+ };
104
+ function sortManagedBundles(bundles, orderBy = DEFAULT_DESC_ORDER) {
105
+ return require_queryBundles.sortBundles(bundles, orderBy);
180
106
  }
181
107
  /**
182
108
  * Creates a blob storage-based database plugin with lazy initialization.
@@ -187,339 +113,90 @@ function buildManagementIndexArtifacts(allBundles, pageSize) {
187
113
  */
188
114
  const createBlobDatabasePlugin = ({ name, factory }) => {
189
115
  return (config, hooks) => {
190
- const managementIndexPageSize = resolveManagementIndexPageSize(config);
191
- const { listObjects, loadObject, uploadObject, deleteObject, invalidatePaths, apiBasePath } = factory(config);
116
+ const { listObjects, loadObject, uploadObject, deleteObject, shouldSkipLoadObjectError, invalidatePaths, apiBasePath } = factory(config);
192
117
  const bundlesMap = /* @__PURE__ */ new Map();
193
118
  const pendingBundlesMap = /* @__PURE__ */ new Map();
194
- const managementRootCache = /* @__PURE__ */ new Map();
195
- const PLATFORMS = ["ios", "android"];
196
- const getAllManagementArtifact = (artifacts) => {
197
- const allArtifact = artifacts.scopes.find((scope) => scope.cacheKey === ALL_SCOPE_CACHE_KEY);
198
- if (!allArtifact) throw new Error("all-bundles management index artifact not found");
199
- return allArtifact;
200
- };
201
- const replaceManagementRootCache = (artifacts) => {
202
- managementRootCache.clear();
203
- for (const scope of artifacts.scopes) managementRootCache.set(scope.cacheKey, scope.root);
204
- };
205
- const createHydratedBundle = (bundle) => {
206
- const hydratedBundle = createBundleWithUpdateJsonKey(bundle);
207
- bundlesMap.set(hydratedBundle.id, hydratedBundle);
208
- return hydratedBundle;
209
- };
210
- const loadStoredManagementRoot = async (scope) => {
211
- const cacheKey = getManagementScopeCacheKey(scope);
212
- const storedRoot = await loadObject(getManagementRootKey(scope));
213
- if (storedRoot) {
214
- managementRootCache.set(cacheKey, storedRoot);
215
- return storedRoot;
216
- }
217
- managementRootCache.delete(cacheKey);
218
- return null;
219
- };
220
- const loadManagementPage = async (descriptor, pageCache) => {
221
- if (pageCache?.has(descriptor.key)) return pageCache.get(descriptor.key) ?? null;
222
- const page = await loadObject(descriptor.key);
223
- pageCache?.set(descriptor.key, page);
224
- return page;
225
- };
226
- const loadBundleFromManagementRoot = async (root, bundleId) => {
227
- const pageIndex = findPageIndexContainingId(root.pages, bundleId);
228
- if (pageIndex < 0) return null;
229
- const descriptor = root.pages[pageIndex];
230
- const page = await loadManagementPage(descriptor);
231
- if (!page) return null;
232
- return page.find((item) => item.id === bundleId) ?? null;
233
- };
234
- const loadAllBundlesFromRoot = async (root) => {
235
- const allBundles = [];
236
- const pageCache = /* @__PURE__ */ new Map();
237
- for (const descriptor of root.pages) {
238
- const page = await loadManagementPage(descriptor, pageCache);
239
- if (!page) return null;
240
- allBundles.push(...page);
119
+ const locallyDeletedBundleIds = /* @__PURE__ */ new Set();
120
+ const loadOptionalObject = async (key) => {
121
+ try {
122
+ return await loadObject(key);
123
+ } catch (error) {
124
+ if (shouldSkipLoadObjectError?.(error, key)) return null;
125
+ throw error;
241
126
  }
242
- return allBundles;
243
- };
244
- const persistManagementIndexArtifacts = async (nextArtifacts, previousArtifacts) => {
245
- for (const [key, page] of nextArtifacts.pages.entries()) await uploadObject(key, page);
246
- for (const scope of nextArtifacts.scopes) await uploadObject(scope.rootKey, scope.root);
247
- if (!previousArtifacts) return;
248
- const nextPageKeys = new Set(nextArtifacts.pages.keys());
249
- const nextRootKeys = new Set(nextArtifacts.scopes.map((scope) => scope.rootKey));
250
- for (const [key] of previousArtifacts.pages.entries()) if (!nextPageKeys.has(key)) await deleteObject(key).catch(() => {});
251
- for (const scope of previousArtifacts.scopes) if (!nextRootKeys.has(scope.rootKey)) await deleteObject(scope.rootKey).catch(() => {});
252
- };
253
- const ensureAllManagementRoot = async () => {
254
- const storedAllRoot = await loadStoredManagementRoot({});
255
- if (storedAllRoot && storedAllRoot.pageSize === managementIndexPageSize) return storedAllRoot;
256
- const rebuiltBundles = sortManagedBundles((await reloadBundles()).map((bundle) => removeBundleInternalKeys(bundle)));
257
- const nextArtifacts = buildManagementIndexArtifacts(rebuiltBundles, managementIndexPageSize);
258
- await persistManagementIndexArtifacts(nextArtifacts, storedAllRoot ? buildManagementIndexArtifacts(rebuiltBundles, storedAllRoot.pageSize) : void 0);
259
- replaceManagementRootCache(nextArtifacts);
260
- return getAllManagementArtifact(nextArtifacts).root;
261
- };
262
- const loadManagementScopeRoot = async (scope) => {
263
- const cacheKey = getManagementScopeCacheKey(scope);
264
- if (cacheKey === ALL_SCOPE_CACHE_KEY) return ensureAllManagementRoot();
265
- const storedRoot = await loadStoredManagementRoot(scope);
266
- if (storedRoot) return storedRoot;
267
- await ensureAllManagementRoot();
268
- const storedScopedRoot = await loadStoredManagementRoot(scope);
269
- if (storedScopedRoot) return storedScopedRoot;
270
- managementRootCache.set(cacheKey, null);
271
- return null;
272
- };
273
- const loadAllBundlesForManagementFallback = async () => {
274
- const allRoot = await loadManagementScopeRoot({});
275
- if (allRoot) {
276
- const pagedBundles = await loadAllBundlesFromRoot(allRoot);
277
- if (pagedBundles) return pagedBundles;
278
- }
279
- return sortManagedBundles((await reloadBundles()).map((bundle) => removeBundleInternalKeys(bundle)));
280
- };
281
- const loadCurrentBundlesForIndexRebuild = async () => {
282
- return loadAllBundlesForManagementFallback();
283
127
  };
284
- const findPageIndexContainingId = (pages, id) => {
285
- return pages.findIndex((page) => id.localeCompare(page.firstId) <= 0 && id.localeCompare(page.lastId) >= 0);
128
+ const loadAllBundlesForManagementFallback = async (where) => {
129
+ return sortManagedBundles((await reloadBundles(getManagementListPrefixes(where))).map((bundle) => removeBundleInternalKeys(bundle)));
286
130
  };
287
- const readPagedBundles = async ({ root, limit, offset, cursor }) => {
288
- if (root.total === 0 || root.pages.length === 0) return createEmptyManagementResult(limit);
289
- const pageStartOffsets = getPageStartOffsets(root.pages);
290
- const pageCache = /* @__PURE__ */ new Map();
291
- if (offset !== void 0) {
292
- const normalizedOffset = Math.max(0, offset);
293
- if (normalizedOffset >= root.total) return {
294
- data: [],
295
- pagination: require_calculatePagination.calculatePagination(root.total, {
296
- limit,
297
- offset: normalizedOffset
298
- })
299
- };
300
- let pageIndex = 0;
301
- for (let index = pageStartOffsets.length - 1; index >= 0; index--) if ((pageStartOffsets[index] ?? 0) <= normalizedOffset) {
302
- pageIndex = index;
303
- break;
304
- }
305
- const startInPage = normalizedOffset - (pageStartOffsets[pageIndex] ?? 0);
306
- const data = [];
307
- for (let currentPageIndex = pageIndex; currentPageIndex < root.pages.length && (limit <= 0 || data.length < limit); currentPageIndex++) {
308
- const descriptor = root.pages[currentPageIndex];
309
- const page = await loadManagementPage(descriptor, pageCache);
310
- if (!page) return require_paginateBundles.paginateBundles({
311
- bundles: await loadAllBundlesForManagementFallback(),
312
- limit,
313
- offset: normalizedOffset
314
- });
315
- data.push(...currentPageIndex === pageIndex ? page.slice(startInPage) : page);
316
- }
317
- const paginatedData = limit > 0 ? data.slice(0, limit) : data;
318
- return {
319
- data: paginatedData,
320
- pagination: {
321
- ...require_calculatePagination.calculatePagination(root.total, {
322
- limit,
323
- offset: normalizedOffset
324
- }),
325
- ...paginatedData.length > 0 && normalizedOffset + paginatedData.length < root.total ? { nextCursor: paginatedData.at(-1)?.id } : {},
326
- ...paginatedData.length > 0 && normalizedOffset > 0 ? { previousCursor: paginatedData[0]?.id } : {}
327
- }
328
- };
329
- }
330
- if (cursor?.after) {
331
- let pageIndex = root.pages.findIndex((page) => {
332
- const containsCursor = cursor.after.localeCompare(page.firstId) <= 0 && cursor.after.localeCompare(page.lastId) >= 0;
333
- const wholePageEligible = cursor.after.localeCompare(page.firstId) > 0;
334
- return containsCursor || wholePageEligible;
335
- });
336
- if (pageIndex < 0) return {
337
- data: [],
338
- pagination: {
339
- ...require_calculatePagination.calculatePagination(root.total, {
340
- limit,
341
- offset: root.total
342
- }),
343
- previousCursor: cursor.after
344
- }
345
- };
346
- const data = [];
347
- let startIndex = null;
348
- while (pageIndex < root.pages.length && (limit <= 0 || data.length < limit)) {
349
- const descriptor = root.pages[pageIndex];
350
- const page = await loadManagementPage(descriptor, pageCache);
351
- if (!page) return require_paginateBundles.paginateBundles({
352
- bundles: await loadAllBundlesForManagementFallback(),
353
- limit,
354
- cursor
355
- });
356
- const containsCursor = cursor.after.localeCompare(descriptor.firstId) <= 0 && cursor.after.localeCompare(descriptor.lastId) >= 0;
357
- let eligiblePageBundles = page;
358
- if (containsCursor) {
359
- const startInPage = page.findIndex((bundle) => bundle.id.localeCompare(cursor.after) < 0);
360
- if (startInPage < 0) eligiblePageBundles = [];
361
- else {
362
- eligiblePageBundles = page.slice(startInPage);
363
- startIndex ??= (pageStartOffsets[pageIndex] ?? 0) + startInPage;
364
- }
365
- } else if (eligiblePageBundles.length > 0) startIndex ??= pageStartOffsets[pageIndex] ?? 0;
366
- data.push(...eligiblePageBundles);
367
- if (limit > 0 && data.length >= limit) break;
368
- pageIndex += 1;
369
- }
370
- const paginatedData = limit > 0 ? data.slice(0, limit) : data;
371
- const resolvedStartIndex = startIndex ?? root.total;
372
- return {
373
- data: paginatedData,
374
- pagination: {
375
- ...require_calculatePagination.calculatePagination(root.total, {
376
- limit,
377
- offset: resolvedStartIndex
378
- }),
379
- ...paginatedData.length > 0 && resolvedStartIndex + paginatedData.length < root.total ? { nextCursor: paginatedData.at(-1)?.id } : {},
380
- ...paginatedData.length > 0 && resolvedStartIndex > 0 ? { previousCursor: paginatedData[0]?.id } : {}
381
- }
382
- };
383
- }
384
- if (cursor?.before) {
385
- let pageIndex = -1;
386
- for (let index = root.pages.length - 1; index >= 0; index--) {
387
- const page = root.pages[index];
388
- const containsCursor = cursor.before.localeCompare(page.firstId) <= 0 && cursor.before.localeCompare(page.lastId) >= 0;
389
- const wholePageEligible = cursor.before.localeCompare(page.lastId) < 0;
390
- if (containsCursor || wholePageEligible) {
391
- pageIndex = index;
392
- break;
393
- }
394
- }
395
- if (pageIndex < 0) return createEmptyManagementResult(limit);
396
- let startIndex = null;
397
- let collected = [];
398
- while (pageIndex >= 0 && (limit <= 0 || collected.length < limit)) {
399
- const descriptor = root.pages[pageIndex];
400
- const page = await loadManagementPage(descriptor, pageCache);
401
- if (!page) return require_paginateBundles.paginateBundles({
402
- bundles: await loadAllBundlesForManagementFallback(),
403
- limit,
404
- cursor
405
- });
406
- const eligiblePageBundles = cursor.before.localeCompare(descriptor.firstId) <= 0 && cursor.before.localeCompare(descriptor.lastId) >= 0 ? page.filter((bundle) => bundle.id.localeCompare(cursor.before) > 0) : page;
407
- collected = [...eligiblePageBundles, ...collected];
408
- if (eligiblePageBundles.length > 0) startIndex = pageStartOffsets[pageIndex] ?? 0;
409
- if (limit > 0 && collected.length >= limit) break;
410
- pageIndex -= 1;
411
- }
412
- if (startIndex === null || collected.length === 0) return createEmptyManagementResult(limit);
413
- let paginatedData = collected;
414
- if (limit > 0 && collected.length > limit) {
415
- const dropCount = collected.length - limit;
416
- paginatedData = collected.slice(dropCount);
417
- startIndex += dropCount;
418
- }
419
- const pagination = require_calculatePagination.calculatePagination(root.total, {
420
- limit,
421
- offset: startIndex
422
- });
423
- return {
424
- data: paginatedData,
425
- pagination: {
426
- ...pagination,
427
- ...paginatedData.length > 0 && startIndex + paginatedData.length < root.total ? { nextCursor: paginatedData.at(-1)?.id } : {},
428
- ...paginatedData.length > 0 && startIndex > 0 ? { previousCursor: paginatedData[0]?.id } : {}
429
- }
430
- };
431
- }
432
- const pageIndex = 0;
433
- const startInPage = 0;
434
- const data = [];
435
- for (let currentPageIndex = pageIndex; currentPageIndex < root.pages.length && (limit <= 0 || data.length < limit); currentPageIndex++) {
436
- const descriptor = root.pages[currentPageIndex];
437
- const page = await loadManagementPage(descriptor, pageCache);
438
- if (!page) return require_paginateBundles.paginateBundles({
439
- bundles: await loadAllBundlesForManagementFallback(),
440
- limit,
441
- cursor
131
+ const cacheBundlesFromObject = (key, bundles) => {
132
+ for (const bundle of bundles) {
133
+ if (locallyDeletedBundleIds.has(bundle.id) || pendingBundlesMap.has(bundle.id)) continue;
134
+ bundlesMap.set(bundle.id, {
135
+ ...bundle,
136
+ _updateJsonKey: key
442
137
  });
443
- data.push(...currentPageIndex === pageIndex ? page.slice(startInPage) : page);
444
138
  }
445
- const paginatedData = limit > 0 ? data.slice(0, limit) : data;
446
- return {
447
- data: paginatedData,
448
- pagination: {
449
- ...require_calculatePagination.calculatePagination(root.total, {
450
- limit,
451
- offset: 0
452
- }),
453
- ...paginatedData.length > 0 && paginatedData.length < root.total ? { nextCursor: paginatedData.at(-1)?.id } : {}
454
- }
455
- };
456
139
  };
457
- async function reloadBundles() {
140
+ const loadBundleObject = async (key) => {
141
+ const bundles = await loadOptionalObject(key) ?? [];
142
+ cacheBundlesFromObject(key, bundles);
143
+ return bundles;
144
+ };
145
+ async function reloadBundles(prefixes = [""]) {
458
146
  bundlesMap.clear();
459
- const filePromises = (await listObjects("")).filter((key) => /^[^/]+\/(?:ios|android)\/[^/]+\/update\.json$/.test(key)).map(async (key) => {
460
- return (await loadObject(key) ?? []).map((bundle) => ({
147
+ pendingBundlesMap.clear();
148
+ locallyDeletedBundleIds.clear();
149
+ const allBundles = (await mapWithConcurrency((await mapWithConcurrency(prefixes, STORAGE_OPERATION_CONCURRENCY, (prefix) => listObjects(prefix))).flat().filter((key) => /^[^/]+\/(?:ios|android)\/[^/]+\/update\.json$/.test(key)), STORAGE_OPERATION_CONCURRENCY, async (key) => {
150
+ return (await loadBundleObject(key)).map((bundle) => ({
461
151
  ...bundle,
462
152
  _updateJsonKey: key
463
153
  }));
464
- });
465
- const allBundles = (await Promise.all(filePromises)).flat();
154
+ })).flat();
466
155
  for (const bundle of allBundles) bundlesMap.set(bundle.id, bundle);
467
156
  for (const [id, bundle] of pendingBundlesMap.entries()) bundlesMap.set(id, bundle);
468
- return (0, es_toolkit.orderBy)(allBundles, [(v) => v.id], ["desc"]);
157
+ return (0, es_toolkit.orderBy)(Array.from(bundlesMap.values()), [(v) => v.id], ["desc"]);
469
158
  }
470
- /**
471
- * Updates target-app-versions.json for each channel on the given platform.
472
- * Returns true if the file was updated, false if no changes were made.
473
- */
474
- async function updateTargetVersionsForPlatform(platform) {
475
- const updateJsonPattern = new RegExp(`^[^/]+/${platform}/[^/]+/update\\.json$`);
476
- const targetVersionsPattern = new RegExp(`^[^/]+/${platform}/target-app-versions\\.json$`);
477
- const allKeys = await listObjects("");
478
- const updateJsonKeys = allKeys.filter((key) => updateJsonPattern.test(key));
479
- const targetVersionsKeys = allKeys.filter((key) => targetVersionsPattern.test(key));
480
- const keysByChannel = updateJsonKeys.reduce((acc, key) => {
481
- const channel = key.split("/")[0];
482
- acc[channel] = acc[channel] || [];
483
- acc[channel].push(key);
484
- return acc;
485
- }, {});
486
- for (const key of targetVersionsKeys) {
487
- const channel = key.split("/")[0];
488
- if (!keysByChannel[channel]) keysByChannel[channel] = [];
489
- }
490
- for (const channel of Object.keys(keysByChannel)) {
491
- const updateKeys = keysByChannel[channel];
159
+ async function applyTargetVersionMutations(mutations) {
160
+ await Promise.all(Array.from(mutations.values()).map(async ({ additions, channel, platform, removals }) => {
492
161
  const targetKey = `${channel}/${platform}/target-app-versions.json`;
493
- const currentVersions = updateKeys.map((key) => key.split("/")[2]);
494
- const oldTargetVersions = await loadObject(targetKey) ?? [];
495
- const newTargetVersions = oldTargetVersions.filter((v) => currentVersions.includes(v));
496
- for (const v of currentVersions) if (!newTargetVersions.includes(v)) newTargetVersions.push(v);
162
+ const oldTargetVersions = await loadOptionalObject(targetKey) ?? [];
163
+ const newTargetVersions = oldTargetVersions.filter((version) => !removals.has(version) || additions.has(version));
164
+ for (const version of additions) if (!newTargetVersions.includes(version)) newTargetVersions.push(version);
497
165
  if (JSON.stringify(oldTargetVersions) !== JSON.stringify(newTargetVersions)) await uploadObject(targetKey, newTargetVersions);
498
- }
166
+ }));
499
167
  }
500
- const getAppVersionUpdateInfo = async ({ appVersion, bundleId, channel = "production", cohort, minBundleId, platform }) => {
501
- const matchingVersions = require_filterCompatibleAppVersions.filterCompatibleAppVersions(await loadObject(`${channel}/${platform}/target-app-versions.json`) ?? [], appVersion);
502
- return (0, _hot_updater_js.getUpdateInfo)((await Promise.allSettled(matchingVersions.map(async (targetAppVersion) => {
503
- return await loadObject(`${channel}/${platform}/${normalizeTargetAppVersion(targetAppVersion) ?? targetAppVersion}/update.json`) ?? [];
504
- }))).filter((entry) => entry.status === "fulfilled").flatMap((entry) => entry.value), {
505
- _updateStrategy: "appVersion",
506
- appVersion,
507
- bundleId,
508
- channel,
509
- cohort,
510
- minBundleId,
511
- platform
168
+ const getAppVersionUpdateInfo = async ({ appVersion, bundleId, channel = "production", cohort, minBundleId, platform }, context) => {
169
+ const bundles = (await mapWithConcurrency(require_filterCompatibleAppVersions.filterCompatibleAppVersions(await loadOptionalObject(`${channel}/${platform}/target-app-versions.json`) ?? [], appVersion), STORAGE_OPERATION_CONCURRENCY, async (targetAppVersion) => {
170
+ return loadBundleObject(`${channel}/${platform}/${normalizeTargetAppVersion(targetAppVersion) ?? targetAppVersion}/update.json`);
171
+ })).flat();
172
+ return require_resolveUpdateInfoFromBundles.resolveUpdateInfoFromBundles({
173
+ args: {
174
+ _updateStrategy: "appVersion",
175
+ appVersion,
176
+ bundleId,
177
+ channel,
178
+ cohort,
179
+ minBundleId,
180
+ platform
181
+ },
182
+ bundles,
183
+ context
512
184
  });
513
185
  };
514
- const getFingerprintUpdateInfo = async ({ bundleId, channel = "production", cohort, fingerprintHash, minBundleId, platform }) => {
515
- return (0, _hot_updater_js.getUpdateInfo)(await loadObject(`${channel}/${platform}/${fingerprintHash}/update.json`) ?? [], {
516
- _updateStrategy: "fingerprint",
517
- bundleId,
518
- channel,
519
- cohort,
520
- fingerprintHash,
521
- minBundleId,
522
- platform
186
+ const getFingerprintUpdateInfo = async ({ bundleId, channel = "production", cohort, fingerprintHash, minBundleId, platform }, context) => {
187
+ const bundles = await loadBundleObject(`${channel}/${platform}/${fingerprintHash}/update.json`);
188
+ return require_resolveUpdateInfoFromBundles.resolveUpdateInfoFromBundles({
189
+ args: {
190
+ _updateStrategy: "fingerprint",
191
+ bundleId,
192
+ channel,
193
+ cohort,
194
+ fingerprintHash,
195
+ minBundleId,
196
+ platform
197
+ },
198
+ bundles,
199
+ context
523
200
  });
524
201
  };
525
202
  const addAppVersionInvalidationPaths = (pathsToInvalidate, { platform, channel, targetAppVersion }) => {
@@ -541,48 +218,27 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
541
218
  targetAppVersion
542
219
  });
543
220
  };
544
- return require_createDatabasePlugin.createDatabasePlugin({
221
+ const createPlugin = require_createDatabasePlugin.createDatabasePlugin({
545
222
  name,
546
223
  factory: () => ({
547
224
  supportsCursorPagination: true,
548
225
  async getBundleById(bundleId) {
226
+ if (locallyDeletedBundleIds.has(bundleId)) return null;
549
227
  const pendingBundle = pendingBundlesMap.get(bundleId);
550
228
  if (pendingBundle) return removeBundleInternalKeys(pendingBundle);
551
229
  const bundle = bundlesMap.get(bundleId);
552
230
  if (bundle) return removeBundleInternalKeys(bundle);
553
- const allRoot = await loadManagementScopeRoot({});
554
- if (allRoot) {
555
- const matchedBundle = await loadBundleFromManagementRoot(allRoot, bundleId);
556
- if (matchedBundle) return removeBundleInternalKeys(createHydratedBundle(matchedBundle));
557
- managementRootCache.delete(ALL_SCOPE_CACHE_KEY);
558
- const refreshedAllRoot = await loadStoredManagementRoot({});
559
- if (refreshedAllRoot) {
560
- const refreshedBundle = await loadBundleFromManagementRoot(refreshedAllRoot, bundleId);
561
- if (refreshedBundle) return removeBundleInternalKeys(createHydratedBundle(refreshedBundle));
562
- }
563
- }
564
231
  const matchedBundle = (await reloadBundles()).find((item) => item.id === bundleId);
565
232
  if (!matchedBundle) return null;
566
233
  return removeBundleInternalKeys(matchedBundle);
567
234
  },
568
- async getUpdateInfo(args) {
569
- if (args._updateStrategy === "appVersion") return getAppVersionUpdateInfo(args);
570
- return getFingerprintUpdateInfo(args);
235
+ async getUpdateInfo(args, context) {
236
+ if (args._updateStrategy === "appVersion") return getAppVersionUpdateInfo(args, context);
237
+ return getFingerprintUpdateInfo(args, context);
571
238
  },
572
239
  async getBundles(options) {
573
240
  const { where, limit, offset, orderBy, cursor } = options;
574
- const scope = getSupportedManagementScope(where, orderBy);
575
- if (scope) {
576
- const root = await loadManagementScopeRoot(scope);
577
- if (!root) return createEmptyManagementResult(limit);
578
- return readPagedBundles({
579
- root,
580
- limit,
581
- offset,
582
- cursor
583
- });
584
- }
585
- let allBundles = await loadAllBundlesForManagementFallback();
241
+ let allBundles = await loadAllBundlesForManagementFallback(where);
586
242
  if (where) allBundles = allBundles.filter((bundle) => require_queryBundles.bundleMatchesQueryWhere(bundle, where));
587
243
  return require_paginateBundles.paginateBundles({
588
244
  bundles: allBundles,
@@ -593,18 +249,16 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
593
249
  });
594
250
  },
595
251
  async getChannels() {
596
- return (await loadManagementScopeRoot({}))?.channels ?? [];
252
+ return [...new Set((await loadAllBundlesForManagementFallback()).map((bundle) => bundle.channel))].sort();
597
253
  },
598
254
  async commitBundle({ changedSets }) {
599
255
  if (changedSets.length === 0) return;
600
256
  const changedBundlesByKey = {};
601
257
  const removalsByKey = {};
258
+ const targetVersionRemovalsByKey = {};
602
259
  const pathsToInvalidate = /* @__PURE__ */ new Set();
603
- let isTargetAppVersionChanged = false;
604
- let isChannelChanged = false;
260
+ const targetVersionMutations = /* @__PURE__ */ new Map();
605
261
  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
262
  if (operation === "insert") {
609
263
  const target = resolveStorageTarget(data);
610
264
  const key = `${data.channel}/${data.platform}/${target}/update.json`;
@@ -612,10 +266,12 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
612
266
  ...data,
613
267
  _updateJsonKey: key
614
268
  };
269
+ locallyDeletedBundleIds.delete(data.id);
615
270
  bundlesMap.set(data.id, bundleWithKey);
616
271
  pendingBundlesMap.set(data.id, bundleWithKey);
617
272
  changedBundlesByKey[key] = changedBundlesByKey[key] || [];
618
273
  changedBundlesByKey[key].push(removeBundleInternalKeys(bundleWithKey));
274
+ addTargetVersionAddition(targetVersionMutations, data);
619
275
  addLookupInvalidationPaths(pathsToInvalidate, data);
620
276
  continue;
621
277
  }
@@ -625,9 +281,12 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
625
281
  if (!bundle) throw new Error("Bundle to delete not found");
626
282
  bundlesMap.delete(data.id);
627
283
  pendingBundlesMap.delete(data.id);
284
+ locallyDeletedBundleIds.add(data.id);
628
285
  const key = bundle._updateJsonKey;
629
286
  removalsByKey[key] = removalsByKey[key] || [];
630
287
  removalsByKey[key].push(bundle.id);
288
+ targetVersionRemovalsByKey[key] = targetVersionRemovalsByKey[key] || [];
289
+ targetVersionRemovalsByKey[key].push(bundle);
631
290
  addLookupInvalidationPaths(pathsToInvalidate, bundle);
632
291
  continue;
633
292
  }
@@ -644,11 +303,14 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
644
303
  const oldKey = bundle._updateJsonKey;
645
304
  removalsByKey[oldKey] = removalsByKey[oldKey] || [];
646
305
  removalsByKey[oldKey].push(bundle.id);
306
+ targetVersionRemovalsByKey[oldKey] = targetVersionRemovalsByKey[oldKey] || [];
307
+ targetVersionRemovalsByKey[oldKey].push(bundle);
647
308
  changedBundlesByKey[newKey] = changedBundlesByKey[newKey] || [];
648
309
  updatedBundle._oldUpdateJsonKey = oldKey;
649
310
  updatedBundle._updateJsonKey = newKey;
650
311
  bundlesMap.set(data.id, updatedBundle);
651
312
  pendingBundlesMap.set(data.id, updatedBundle);
313
+ locallyDeletedBundleIds.delete(data.id);
652
314
  changedBundlesByKey[newKey].push(removeBundleInternalKeys(updatedBundle));
653
315
  const oldChannel = bundle.channel;
654
316
  const nextChannel = updatedBundle.channel;
@@ -659,6 +321,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
659
321
  channel: nextChannel
660
322
  });
661
323
  }
324
+ addTargetVersionAddition(targetVersionMutations, updatedBundle);
662
325
  addLookupInvalidationPaths(pathsToInvalidate, updatedBundle);
663
326
  if (bundle.targetAppVersion && bundle.targetAppVersion !== updatedBundle.targetAppVersion) addLookupInvalidationPaths(pathsToInvalidate, bundle);
664
327
  continue;
@@ -666,20 +329,24 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
666
329
  const currentKey = bundle._updateJsonKey;
667
330
  bundlesMap.set(data.id, updatedBundle);
668
331
  pendingBundlesMap.set(data.id, updatedBundle);
332
+ locallyDeletedBundleIds.delete(data.id);
669
333
  changedBundlesByKey[currentKey] = changedBundlesByKey[currentKey] || [];
670
334
  changedBundlesByKey[currentKey].push(removeBundleInternalKeys(updatedBundle));
671
335
  addLookupInvalidationPaths(pathsToInvalidate, updatedBundle);
336
+ addTargetVersionAddition(targetVersionMutations, updatedBundle);
672
337
  if (bundle.targetAppVersion && bundle.targetAppVersion !== updatedBundle.targetAppVersion) addLookupInvalidationPaths(pathsToInvalidate, bundle);
673
338
  }
674
339
  }
675
- for (const oldKey of Object.keys(removalsByKey)) await (async () => {
676
- const updatedBundles = (await loadObject(oldKey) ?? []).filter((b) => !removalsByKey[oldKey].includes(b.id));
340
+ await forEachWithConcurrency(Object.keys(removalsByKey), STORAGE_OPERATION_CONCURRENCY, async (oldKey) => {
341
+ const updatedBundles = (await loadOptionalObject(oldKey) ?? []).filter((b) => !removalsByKey[oldKey].includes(b.id));
677
342
  updatedBundles.sort((a, b) => b.id.localeCompare(a.id));
678
- if (updatedBundles.length === 0) await deleteObject(oldKey);
679
- else await uploadObject(oldKey, updatedBundles);
680
- })();
681
- for (const key of Object.keys(changedBundlesByKey)) await (async () => {
682
- const currentBundles = await loadObject(key) ?? [];
343
+ if (updatedBundles.length === 0) {
344
+ await deleteObject(oldKey);
345
+ for (const removedBundle of targetVersionRemovalsByKey[oldKey] ?? []) addTargetVersionRemoval(targetVersionMutations, removedBundle);
346
+ } else await uploadObject(oldKey, updatedBundles);
347
+ });
348
+ await forEachWithConcurrency(Object.keys(changedBundlesByKey), STORAGE_OPERATION_CONCURRENCY, async (key) => {
349
+ const currentBundles = await loadOptionalObject(key) ?? [];
683
350
  const pureBundles = changedBundlesByKey[key].map((bundle) => bundle);
684
351
  for (const changedBundle of pureBundles) {
685
352
  const index = currentBundles.findIndex((b) => b.id === changedBundle.id);
@@ -688,22 +355,8 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
688
355
  }
689
356
  currentBundles.sort((a, b) => b.id.localeCompare(a.id));
690
357
  await uploadObject(key, currentBundles);
691
- })();
692
- if (isTargetAppVersionChanged || isChannelChanged) for (const platform of PLATFORMS) await updateTargetVersionsForPlatform(platform);
693
- const currentIndexBundles = await loadCurrentBundlesForIndexRebuild();
694
- const nextIndexMap = new Map(currentIndexBundles.map((bundle) => [bundle.id, bundle]));
695
- for (const { operation, data } of changedSets) {
696
- if (operation === "delete") {
697
- nextIndexMap.delete(data.id);
698
- continue;
699
- }
700
- nextIndexMap.set(data.id, data);
701
- }
702
- const nextIndexBundles = sortManagedBundles(Array.from(nextIndexMap.values()));
703
- const previousArtifacts = buildManagementIndexArtifacts(currentIndexBundles, managementIndexPageSize);
704
- const nextArtifacts = buildManagementIndexArtifacts(nextIndexBundles, managementIndexPageSize);
705
- await persistManagementIndexArtifacts(nextArtifacts, previousArtifacts);
706
- replaceManagementRootCache(nextArtifacts);
358
+ });
359
+ if (targetVersionMutations.size > 0) await applyTargetVersionMutations(targetVersionMutations);
707
360
  const encondedPaths = /* @__PURE__ */ new Set();
708
361
  for (const path of pathsToInvalidate) encondedPaths.add(encodeURI(path));
709
362
  await invalidatePaths(Array.from(encondedPaths));
@@ -711,6 +364,9 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
711
364
  }
712
365
  })
713
366
  })({}, hooks);
367
+ return () => {
368
+ return createPlugin();
369
+ };
714
370
  };
715
371
  };
716
372
  //#endregion