@hot-updater/plugin-core 0.33.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,8 +1,7 @@
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
6
  const require_resolveUpdateInfoFromBundles = require("./resolveUpdateInfoFromBundles.cjs");
8
7
  let es_toolkit = require("es-toolkit");
@@ -67,140 +66,43 @@ function resolveStorageTarget({ targetAppVersion, fingerprintHash }) {
67
66
  if (!target) throw new Error("target not found");
68
67
  return target;
69
68
  }
70
- const DEFAULT_DESC_ORDER = {
71
- field: "id",
72
- direction: "desc"
73
- };
74
- const MANAGEMENT_INDEX_PREFIX = "_index";
75
- const MANAGEMENT_INDEX_VERSION = 1;
76
- const DEFAULT_MANAGEMENT_INDEX_PAGE_SIZE = 128;
77
- const ALL_SCOPE_CACHE_KEY = "*|*";
78
- function summarizeManagementIndexArtifacts(artifacts) {
79
- return {
80
- pagesWritten: artifacts.pages.size,
81
- scopesWritten: artifacts.scopes.length
82
- };
83
- }
84
- function resolveManagementIndexPageSize(config) {
85
- const pageSize = config.managementIndexPageSize ?? DEFAULT_MANAGEMENT_INDEX_PAGE_SIZE;
86
- if (!Number.isInteger(pageSize) || pageSize < 1) throw new Error("managementIndexPageSize must be a positive integer.");
87
- return pageSize;
88
- }
89
- function sortManagedBundles(bundles, orderBy = DEFAULT_DESC_ORDER) {
90
- return require_queryBundles.sortBundles(bundles, orderBy);
91
- }
92
- function isDefaultManagementOrder(orderBy) {
93
- return orderBy === void 0 || orderBy.field === DEFAULT_DESC_ORDER.field && orderBy.direction === DEFAULT_DESC_ORDER.direction;
69
+ function targetVersionMutationKey(bundle) {
70
+ return `${bundle.channel}/${bundle.platform}`;
94
71
  }
95
- function hasUnsupportedManagementFilters(where) {
96
- if (!where) return false;
97
- 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);
98
- }
99
- function getSupportedManagementScope(where, orderBy) {
100
- if (!isDefaultManagementOrder(orderBy) || hasUnsupportedManagementFilters(where)) return null;
101
- return {
102
- channel: where?.channel,
103
- 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()
104
81
  };
82
+ mutations.set(key, mutation);
83
+ return mutation;
105
84
  }
106
- function encodeScopePart(value) {
107
- return encodeURIComponent(value);
108
- }
109
- function getManagementScopeCacheKey({ channel, platform }) {
110
- return `${channel ?? "*"}|${platform ?? "*"}`;
111
- }
112
- function getManagementScopePrefix({ channel, platform }) {
113
- if (channel && platform) return `${MANAGEMENT_INDEX_PREFIX}/channel/${encodeScopePart(channel)}/platform/${platform}`;
114
- if (channel) return `${MANAGEMENT_INDEX_PREFIX}/channel/${encodeScopePart(channel)}`;
115
- if (platform) return `${MANAGEMENT_INDEX_PREFIX}/platform/${platform}`;
116
- return `${MANAGEMENT_INDEX_PREFIX}/all`;
117
- }
118
- function getManagementRootKey(scope) {
119
- return `${getManagementScopePrefix(scope)}/root.json`;
120
- }
121
- function getManagementPageKey(scope, pageIndex) {
122
- return `${getManagementScopePrefix(scope)}/pages/${String(pageIndex).padStart(4, "0")}.json`;
123
- }
124
- function createBundleWithUpdateJsonKey(bundle) {
125
- const target = resolveStorageTarget(bundle);
126
- return {
127
- ...bundle,
128
- _updateJsonKey: `${bundle.channel}/${bundle.platform}/${target}/update.json`
129
- };
85
+ function addTargetVersionAddition(mutations, bundle) {
86
+ const targetAppVersion = normalizeTargetAppVersion(bundle.targetAppVersion);
87
+ if (targetAppVersion == null) return;
88
+ getTargetVersionMutation(mutations, bundle).additions.add(targetAppVersion);
130
89
  }
131
- function getPageStartOffsets(pages) {
132
- const startOffsets = [];
133
- let offset = 0;
134
- for (const page of pages) {
135
- startOffsets.push(offset);
136
- offset += page.count;
137
- }
138
- 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);
139
94
  }
140
- function createEmptyManagementResult(limit) {
141
- return {
142
- data: [],
143
- pagination: require_calculatePagination.calculatePagination(0, {
144
- limit,
145
- offset: 0
146
- })
147
- };
95
+ function getManagementListPrefixes(where) {
96
+ if (where?.channel && where.platform) return [`${where.channel}/${where.platform}/`];
97
+ if (where?.channel) return [`${where.channel}/`];
98
+ return [""];
148
99
  }
149
- function buildManagementIndexArtifacts(allBundles, pageSize) {
150
- const sortedAllBundles = sortManagedBundles(allBundles);
151
- const pages = /* @__PURE__ */ new Map();
152
- const scopes = [];
153
- const channels = [...new Set(sortedAllBundles.map((bundle) => bundle.channel))].sort();
154
- const addScope = (scope, scopeBundles, options) => {
155
- if (!options?.includeChannels && scopeBundles.length === 0) return;
156
- const pageKeys = [];
157
- const pageDescriptors = [];
158
- for (let pageIndex = 0; pageIndex * pageSize < scopeBundles.length; pageIndex++) {
159
- const page = scopeBundles.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize);
160
- const key = getManagementPageKey(scope, pageIndex);
161
- pages.set(key, page);
162
- pageKeys.push(key);
163
- pageDescriptors.push({
164
- key,
165
- count: page.length,
166
- firstId: page[0].id,
167
- lastId: page.at(-1).id
168
- });
169
- }
170
- const root = {
171
- version: MANAGEMENT_INDEX_VERSION,
172
- pageSize,
173
- total: scopeBundles.length,
174
- pages: pageDescriptors,
175
- ...options?.includeChannels ? { channels } : {}
176
- };
177
- scopes.push({
178
- cacheKey: getManagementScopeCacheKey(scope),
179
- rootKey: getManagementRootKey(scope),
180
- root,
181
- pageKeys
182
- });
183
- };
184
- addScope({}, sortedAllBundles, { includeChannels: true });
185
- for (const channel of channels) {
186
- const channelBundles = sortedAllBundles.filter((bundle) => bundle.channel === channel);
187
- addScope({ channel }, channelBundles);
188
- for (const platform of ["ios", "android"]) {
189
- const scopedBundles = channelBundles.filter((bundle) => bundle.platform === platform);
190
- addScope({
191
- channel,
192
- platform
193
- }, scopedBundles);
194
- }
195
- }
196
- for (const platform of ["ios", "android"]) {
197
- const platformBundles = sortedAllBundles.filter((bundle) => bundle.platform === platform);
198
- addScope({ platform }, platformBundles);
199
- }
200
- return {
201
- pages,
202
- scopes
203
- };
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);
204
106
  }
205
107
  /**
206
108
  * Creates a blob storage-based database plugin with lazy initialization.
@@ -211,11 +113,10 @@ function buildManagementIndexArtifacts(allBundles, pageSize) {
211
113
  */
212
114
  const createBlobDatabasePlugin = ({ name, factory }) => {
213
115
  return (config, hooks) => {
214
- const managementIndexPageSize = resolveManagementIndexPageSize(config);
215
116
  const { listObjects, loadObject, uploadObject, deleteObject, shouldSkipLoadObjectError, invalidatePaths, apiBasePath } = factory(config);
216
117
  const bundlesMap = /* @__PURE__ */ new Map();
217
118
  const pendingBundlesMap = /* @__PURE__ */ new Map();
218
- const managementRootCache = /* @__PURE__ */ new Map();
119
+ const locallyDeletedBundleIds = /* @__PURE__ */ new Set();
219
120
  const loadOptionalObject = async (key) => {
220
121
  try {
221
122
  return await loadObject(key);
@@ -224,291 +125,29 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
224
125
  throw error;
225
126
  }
226
127
  };
227
- const getAllManagementArtifact = (artifacts) => {
228
- const allArtifact = artifacts.scopes.find((scope) => scope.cacheKey === ALL_SCOPE_CACHE_KEY);
229
- if (!allArtifact) throw new Error("all-bundles management index artifact not found");
230
- return allArtifact;
231
- };
232
- const replaceManagementRootCache = (artifacts) => {
233
- managementRootCache.clear();
234
- for (const scope of artifacts.scopes) managementRootCache.set(scope.cacheKey, scope.root);
235
- };
236
- const createHydratedBundle = (bundle) => {
237
- const hydratedBundle = createBundleWithUpdateJsonKey(bundle);
238
- bundlesMap.set(hydratedBundle.id, hydratedBundle);
239
- return hydratedBundle;
240
- };
241
- const loadStoredManagementRoot = async (scope) => {
242
- const cacheKey = getManagementScopeCacheKey(scope);
243
- const storedRoot = await loadOptionalObject(getManagementRootKey(scope));
244
- if (storedRoot) {
245
- managementRootCache.set(cacheKey, storedRoot);
246
- return storedRoot;
247
- }
248
- managementRootCache.delete(cacheKey);
249
- return null;
250
- };
251
- const loadManagementPage = async (descriptor, pageCache) => {
252
- if (pageCache?.has(descriptor.key)) return pageCache.get(descriptor.key) ?? null;
253
- const page = await loadOptionalObject(descriptor.key);
254
- pageCache?.set(descriptor.key, page);
255
- return page;
256
- };
257
- const loadBundleFromManagementRoot = async (root, bundleId) => {
258
- const pageIndex = findPageIndexContainingId(root.pages, bundleId);
259
- if (pageIndex < 0) return null;
260
- const descriptor = root.pages[pageIndex];
261
- const page = await loadManagementPage(descriptor);
262
- if (!page) return null;
263
- return page.find((item) => item.id === bundleId) ?? null;
264
- };
265
- const loadAllBundlesFromRoot = async (root) => {
266
- const allBundles = [];
267
- const pageCache = /* @__PURE__ */ new Map();
268
- for (const descriptor of root.pages) {
269
- const page = await loadManagementPage(descriptor, pageCache);
270
- if (!page) return null;
271
- allBundles.push(...page);
272
- }
273
- return allBundles;
274
- };
275
- const persistManagementIndexArtifacts = async (nextArtifacts, previousArtifacts) => {
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));
278
- if (!previousArtifacts) return;
279
- const nextPageKeys = new Set(nextArtifacts.pages.keys());
280
- const nextRootKeys = new Set(nextArtifacts.scopes.map((scope) => scope.rootKey));
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(() => {}));
283
- };
284
- const ensureAllManagementRoot = async () => {
285
- const storedAllRoot = await loadStoredManagementRoot({});
286
- if (storedAllRoot && storedAllRoot.pageSize === managementIndexPageSize) return storedAllRoot;
287
- const rebuiltBundles = sortManagedBundles((await reloadBundles()).map((bundle) => removeBundleInternalKeys(bundle)));
288
- const nextArtifacts = buildManagementIndexArtifacts(rebuiltBundles, managementIndexPageSize);
289
- await persistManagementIndexArtifacts(nextArtifacts, storedAllRoot ? buildManagementIndexArtifacts(rebuiltBundles, storedAllRoot.pageSize) : void 0);
290
- replaceManagementRootCache(nextArtifacts);
291
- return getAllManagementArtifact(nextArtifacts).root;
292
- };
293
- const loadManagementScopeRoot = async (scope) => {
294
- const cacheKey = getManagementScopeCacheKey(scope);
295
- if (cacheKey === ALL_SCOPE_CACHE_KEY) return ensureAllManagementRoot();
296
- const storedRoot = await loadStoredManagementRoot(scope);
297
- if (storedRoot) return storedRoot;
298
- await ensureAllManagementRoot();
299
- const storedScopedRoot = await loadStoredManagementRoot(scope);
300
- if (storedScopedRoot) return storedScopedRoot;
301
- managementRootCache.set(cacheKey, null);
302
- return null;
303
- };
304
- const loadAllBundlesForManagementFallback = async () => {
305
- const allRoot = await loadManagementScopeRoot({});
306
- if (allRoot) {
307
- const pagedBundles = await loadAllBundlesFromRoot(allRoot);
308
- if (pagedBundles) return pagedBundles;
309
- }
310
- return sortManagedBundles((await reloadBundles()).map((bundle) => removeBundleInternalKeys(bundle)));
311
- };
312
- const loadCurrentBundlesForIndexRebuild = async () => {
313
- return loadAllBundlesForManagementFallback();
128
+ const loadAllBundlesForManagementFallback = async (where) => {
129
+ return sortManagedBundles((await reloadBundles(getManagementListPrefixes(where))).map((bundle) => removeBundleInternalKeys(bundle)));
314
130
  };
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
- };
335
- const findPageIndexContainingId = (pages, id) => {
336
- return pages.findIndex((page) => id.localeCompare(page.firstId) <= 0 && id.localeCompare(page.lastId) >= 0);
337
- };
338
- const readPagedBundles = async ({ root, limit, offset, cursor }) => {
339
- if (root.total === 0 || root.pages.length === 0) return createEmptyManagementResult(limit);
340
- const pageStartOffsets = getPageStartOffsets(root.pages);
341
- const pageCache = /* @__PURE__ */ new Map();
342
- if (offset !== void 0) {
343
- const normalizedOffset = Math.max(0, offset);
344
- if (normalizedOffset >= root.total) return {
345
- data: [],
346
- pagination: require_calculatePagination.calculatePagination(root.total, {
347
- limit,
348
- offset: normalizedOffset
349
- })
350
- };
351
- let pageIndex = 0;
352
- for (let index = pageStartOffsets.length - 1; index >= 0; index--) if ((pageStartOffsets[index] ?? 0) <= normalizedOffset) {
353
- pageIndex = index;
354
- break;
355
- }
356
- const startInPage = normalizedOffset - (pageStartOffsets[pageIndex] ?? 0);
357
- const data = [];
358
- for (let currentPageIndex = pageIndex; currentPageIndex < root.pages.length && (limit <= 0 || data.length < limit); currentPageIndex++) {
359
- const descriptor = root.pages[currentPageIndex];
360
- const page = await loadManagementPage(descriptor, pageCache);
361
- if (!page) return require_paginateBundles.paginateBundles({
362
- bundles: await loadAllBundlesForManagementFallback(),
363
- limit,
364
- offset: normalizedOffset
365
- });
366
- data.push(...currentPageIndex === pageIndex ? page.slice(startInPage) : page);
367
- }
368
- const paginatedData = limit > 0 ? data.slice(0, limit) : data;
369
- return {
370
- data: paginatedData,
371
- pagination: {
372
- ...require_calculatePagination.calculatePagination(root.total, {
373
- limit,
374
- offset: normalizedOffset
375
- }),
376
- ...paginatedData.length > 0 && normalizedOffset + paginatedData.length < root.total ? { nextCursor: paginatedData.at(-1)?.id } : {},
377
- ...paginatedData.length > 0 && normalizedOffset > 0 ? { previousCursor: paginatedData[0]?.id } : {}
378
- }
379
- };
380
- }
381
- if (cursor?.after) {
382
- let pageIndex = root.pages.findIndex((page) => {
383
- const containsCursor = cursor.after.localeCompare(page.firstId) <= 0 && cursor.after.localeCompare(page.lastId) >= 0;
384
- const wholePageEligible = cursor.after.localeCompare(page.firstId) > 0;
385
- return containsCursor || wholePageEligible;
386
- });
387
- if (pageIndex < 0) return {
388
- data: [],
389
- pagination: {
390
- ...require_calculatePagination.calculatePagination(root.total, {
391
- limit,
392
- offset: root.total
393
- }),
394
- previousCursor: cursor.after
395
- }
396
- };
397
- const data = [];
398
- let startIndex = null;
399
- while (pageIndex < root.pages.length && (limit <= 0 || data.length < limit)) {
400
- const descriptor = root.pages[pageIndex];
401
- const page = await loadManagementPage(descriptor, pageCache);
402
- if (!page) return require_paginateBundles.paginateBundles({
403
- bundles: await loadAllBundlesForManagementFallback(),
404
- limit,
405
- cursor
406
- });
407
- const containsCursor = cursor.after.localeCompare(descriptor.firstId) <= 0 && cursor.after.localeCompare(descriptor.lastId) >= 0;
408
- let eligiblePageBundles = page;
409
- if (containsCursor) {
410
- const startInPage = page.findIndex((bundle) => bundle.id.localeCompare(cursor.after) < 0);
411
- if (startInPage < 0) eligiblePageBundles = [];
412
- else {
413
- eligiblePageBundles = page.slice(startInPage);
414
- startIndex ??= (pageStartOffsets[pageIndex] ?? 0) + startInPage;
415
- }
416
- } else if (eligiblePageBundles.length > 0) startIndex ??= pageStartOffsets[pageIndex] ?? 0;
417
- data.push(...eligiblePageBundles);
418
- if (limit > 0 && data.length >= limit) break;
419
- pageIndex += 1;
420
- }
421
- const paginatedData = limit > 0 ? data.slice(0, limit) : data;
422
- const resolvedStartIndex = startIndex ?? root.total;
423
- return {
424
- data: paginatedData,
425
- pagination: {
426
- ...require_calculatePagination.calculatePagination(root.total, {
427
- limit,
428
- offset: resolvedStartIndex
429
- }),
430
- ...paginatedData.length > 0 && resolvedStartIndex + paginatedData.length < root.total ? { nextCursor: paginatedData.at(-1)?.id } : {},
431
- ...paginatedData.length > 0 && resolvedStartIndex > 0 ? { previousCursor: paginatedData[0]?.id } : {}
432
- }
433
- };
434
- }
435
- if (cursor?.before) {
436
- let pageIndex = -1;
437
- for (let index = root.pages.length - 1; index >= 0; index--) {
438
- const page = root.pages[index];
439
- const containsCursor = cursor.before.localeCompare(page.firstId) <= 0 && cursor.before.localeCompare(page.lastId) >= 0;
440
- const wholePageEligible = cursor.before.localeCompare(page.lastId) < 0;
441
- if (containsCursor || wholePageEligible) {
442
- pageIndex = index;
443
- break;
444
- }
445
- }
446
- if (pageIndex < 0) return createEmptyManagementResult(limit);
447
- let startIndex = null;
448
- let collected = [];
449
- while (pageIndex >= 0 && (limit <= 0 || collected.length < limit)) {
450
- const descriptor = root.pages[pageIndex];
451
- const page = await loadManagementPage(descriptor, pageCache);
452
- if (!page) return require_paginateBundles.paginateBundles({
453
- bundles: await loadAllBundlesForManagementFallback(),
454
- limit,
455
- cursor
456
- });
457
- const eligiblePageBundles = cursor.before.localeCompare(descriptor.firstId) <= 0 && cursor.before.localeCompare(descriptor.lastId) >= 0 ? page.filter((bundle) => bundle.id.localeCompare(cursor.before) > 0) : page;
458
- collected = [...eligiblePageBundles, ...collected];
459
- if (eligiblePageBundles.length > 0) startIndex = pageStartOffsets[pageIndex] ?? 0;
460
- if (limit > 0 && collected.length >= limit) break;
461
- pageIndex -= 1;
462
- }
463
- if (startIndex === null || collected.length === 0) return createEmptyManagementResult(limit);
464
- let paginatedData = collected;
465
- if (limit > 0 && collected.length > limit) {
466
- const dropCount = collected.length - limit;
467
- paginatedData = collected.slice(dropCount);
468
- startIndex += dropCount;
469
- }
470
- const pagination = require_calculatePagination.calculatePagination(root.total, {
471
- limit,
472
- offset: startIndex
473
- });
474
- return {
475
- data: paginatedData,
476
- pagination: {
477
- ...pagination,
478
- ...paginatedData.length > 0 && startIndex + paginatedData.length < root.total ? { nextCursor: paginatedData.at(-1)?.id } : {},
479
- ...paginatedData.length > 0 && startIndex > 0 ? { previousCursor: paginatedData[0]?.id } : {}
480
- }
481
- };
482
- }
483
- const pageIndex = 0;
484
- const startInPage = 0;
485
- const data = [];
486
- for (let currentPageIndex = pageIndex; currentPageIndex < root.pages.length && (limit <= 0 || data.length < limit); currentPageIndex++) {
487
- const descriptor = root.pages[currentPageIndex];
488
- const page = await loadManagementPage(descriptor, pageCache);
489
- if (!page) return require_paginateBundles.paginateBundles({
490
- bundles: await loadAllBundlesForManagementFallback(),
491
- limit,
492
- 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
493
137
  });
494
- data.push(...currentPageIndex === pageIndex ? page.slice(startInPage) : page);
495
138
  }
496
- const paginatedData = limit > 0 ? data.slice(0, limit) : data;
497
- return {
498
- data: paginatedData,
499
- pagination: {
500
- ...require_calculatePagination.calculatePagination(root.total, {
501
- limit,
502
- offset: 0
503
- }),
504
- ...paginatedData.length > 0 && paginatedData.length < root.total ? { nextCursor: paginatedData.at(-1)?.id } : {}
505
- }
506
- };
507
139
  };
508
- 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 = [""]) {
509
146
  bundlesMap.clear();
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) => ({
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) => ({
512
151
  ...bundle,
513
152
  _updateJsonKey: key
514
153
  }));
@@ -517,39 +156,18 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
517
156
  for (const [id, bundle] of pendingBundlesMap.entries()) bundlesMap.set(id, bundle);
518
157
  return (0, es_toolkit.orderBy)(Array.from(bundlesMap.values()), [(v) => v.id], ["desc"]);
519
158
  }
520
- /**
521
- * Updates target-app-versions.json for each channel on the given platform.
522
- * Returns true if the file was updated, false if no changes were made.
523
- */
524
- async function updateTargetVersionsForPlatform(platform) {
525
- const updateJsonPattern = new RegExp(`^[^/]+/${platform}/[^/]+/update\\.json$`);
526
- const targetVersionsPattern = new RegExp(`^[^/]+/${platform}/target-app-versions\\.json$`);
527
- const allKeys = await listObjects("");
528
- const updateJsonKeys = allKeys.filter((key) => updateJsonPattern.test(key));
529
- const targetVersionsKeys = allKeys.filter((key) => targetVersionsPattern.test(key));
530
- const keysByChannel = updateJsonKeys.reduce((acc, key) => {
531
- const channel = key.split("/")[0];
532
- acc[channel] = acc[channel] || [];
533
- acc[channel].push(key);
534
- return acc;
535
- }, {});
536
- for (const key of targetVersionsKeys) {
537
- const channel = key.split("/")[0];
538
- if (!keysByChannel[channel]) keysByChannel[channel] = [];
539
- }
540
- for (const channel of Object.keys(keysByChannel)) {
541
- const updateKeys = keysByChannel[channel];
159
+ async function applyTargetVersionMutations(mutations) {
160
+ await Promise.all(Array.from(mutations.values()).map(async ({ additions, channel, platform, removals }) => {
542
161
  const targetKey = `${channel}/${platform}/target-app-versions.json`;
543
- const currentVersions = updateKeys.map((key) => key.split("/")[2]);
544
162
  const oldTargetVersions = await loadOptionalObject(targetKey) ?? [];
545
- const newTargetVersions = oldTargetVersions.filter((v) => currentVersions.includes(v));
546
- for (const v of currentVersions) if (!newTargetVersions.includes(v)) newTargetVersions.push(v);
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);
547
165
  if (JSON.stringify(oldTargetVersions) !== JSON.stringify(newTargetVersions)) await uploadObject(targetKey, newTargetVersions);
548
- }
166
+ }));
549
167
  }
550
168
  const getAppVersionUpdateInfo = async ({ appVersion, bundleId, channel = "production", cohort, minBundleId, platform }, context) => {
551
169
  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`) ?? [];
170
+ return loadBundleObject(`${channel}/${platform}/${normalizeTargetAppVersion(targetAppVersion) ?? targetAppVersion}/update.json`);
553
171
  })).flat();
554
172
  return require_resolveUpdateInfoFromBundles.resolveUpdateInfoFromBundles({
555
173
  args: {
@@ -566,7 +184,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
566
184
  });
567
185
  };
568
186
  const getFingerprintUpdateInfo = async ({ bundleId, channel = "production", cohort, fingerprintHash, minBundleId, platform }, context) => {
569
- const bundles = await loadOptionalObject(`${channel}/${platform}/${fingerprintHash}/update.json`) ?? [];
187
+ const bundles = await loadBundleObject(`${channel}/${platform}/${fingerprintHash}/update.json`);
570
188
  return require_resolveUpdateInfoFromBundles.resolveUpdateInfoFromBundles({
571
189
  args: {
572
190
  _updateStrategy: "fingerprint",
@@ -600,54 +218,16 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
600
218
  targetAppVersion
601
219
  });
602
220
  };
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
221
  const createPlugin = require_createDatabasePlugin.createDatabasePlugin({
632
222
  name,
633
223
  factory: () => ({
634
224
  supportsCursorPagination: true,
635
225
  async getBundleById(bundleId) {
226
+ if (locallyDeletedBundleIds.has(bundleId)) return null;
636
227
  const pendingBundle = pendingBundlesMap.get(bundleId);
637
228
  if (pendingBundle) return removeBundleInternalKeys(pendingBundle);
638
229
  const bundle = bundlesMap.get(bundleId);
639
230
  if (bundle) return removeBundleInternalKeys(bundle);
640
- const allRoot = await loadManagementScopeRoot({});
641
- if (allRoot) {
642
- const matchedBundle = await loadBundleFromManagementRoot(allRoot, bundleId);
643
- if (matchedBundle) return removeBundleInternalKeys(createHydratedBundle(matchedBundle));
644
- managementRootCache.delete(ALL_SCOPE_CACHE_KEY);
645
- const refreshedAllRoot = await loadStoredManagementRoot({});
646
- if (refreshedAllRoot) {
647
- const refreshedBundle = await loadBundleFromManagementRoot(refreshedAllRoot, bundleId);
648
- if (refreshedBundle) return removeBundleInternalKeys(createHydratedBundle(refreshedBundle));
649
- }
650
- }
651
231
  const matchedBundle = (await reloadBundles()).find((item) => item.id === bundleId);
652
232
  if (!matchedBundle) return null;
653
233
  return removeBundleInternalKeys(matchedBundle);
@@ -658,18 +238,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
658
238
  },
659
239
  async getBundles(options) {
660
240
  const { where, limit, offset, orderBy, cursor } = options;
661
- const scope = getSupportedManagementScope(where, orderBy);
662
- if (scope) {
663
- const root = await loadManagementScopeRoot(scope);
664
- if (!root) return createEmptyManagementResult(limit);
665
- return readPagedBundles({
666
- root,
667
- limit,
668
- offset,
669
- cursor
670
- });
671
- }
672
- let allBundles = await loadAllBundlesForManagementFallback();
241
+ let allBundles = await loadAllBundlesForManagementFallback(where);
673
242
  if (where) allBundles = allBundles.filter((bundle) => require_queryBundles.bundleMatchesQueryWhere(bundle, where));
674
243
  return require_paginateBundles.paginateBundles({
675
244
  bundles: allBundles,
@@ -680,14 +249,15 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
680
249
  });
681
250
  },
682
251
  async getChannels() {
683
- return (await loadManagementScopeRoot({}))?.channels ?? [];
252
+ return [...new Set((await loadAllBundlesForManagementFallback()).map((bundle) => bundle.channel))].sort();
684
253
  },
685
254
  async commitBundle({ changedSets }) {
686
255
  if (changedSets.length === 0) return;
687
256
  const changedBundlesByKey = {};
688
257
  const removalsByKey = {};
258
+ const targetVersionRemovalsByKey = {};
689
259
  const pathsToInvalidate = /* @__PURE__ */ new Set();
690
- const targetVersionPlatforms = /* @__PURE__ */ new Set();
260
+ const targetVersionMutations = /* @__PURE__ */ new Map();
691
261
  for (const { operation, data } of changedSets) {
692
262
  if (operation === "insert") {
693
263
  const target = resolveStorageTarget(data);
@@ -696,11 +266,12 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
696
266
  ...data,
697
267
  _updateJsonKey: key
698
268
  };
269
+ locallyDeletedBundleIds.delete(data.id);
699
270
  bundlesMap.set(data.id, bundleWithKey);
700
271
  pendingBundlesMap.set(data.id, bundleWithKey);
701
272
  changedBundlesByKey[key] = changedBundlesByKey[key] || [];
702
273
  changedBundlesByKey[key].push(removeBundleInternalKeys(bundleWithKey));
703
- if (data.targetAppVersion !== void 0) targetVersionPlatforms.add(data.platform);
274
+ addTargetVersionAddition(targetVersionMutations, data);
704
275
  addLookupInvalidationPaths(pathsToInvalidate, data);
705
276
  continue;
706
277
  }
@@ -710,10 +281,12 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
710
281
  if (!bundle) throw new Error("Bundle to delete not found");
711
282
  bundlesMap.delete(data.id);
712
283
  pendingBundlesMap.delete(data.id);
284
+ locallyDeletedBundleIds.add(data.id);
713
285
  const key = bundle._updateJsonKey;
714
286
  removalsByKey[key] = removalsByKey[key] || [];
715
287
  removalsByKey[key].push(bundle.id);
716
- if (bundle.targetAppVersion !== void 0) targetVersionPlatforms.add(bundle.platform);
288
+ targetVersionRemovalsByKey[key] = targetVersionRemovalsByKey[key] || [];
289
+ targetVersionRemovalsByKey[key].push(bundle);
717
290
  addLookupInvalidationPaths(pathsToInvalidate, bundle);
718
291
  continue;
719
292
  }
@@ -730,11 +303,14 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
730
303
  const oldKey = bundle._updateJsonKey;
731
304
  removalsByKey[oldKey] = removalsByKey[oldKey] || [];
732
305
  removalsByKey[oldKey].push(bundle.id);
306
+ targetVersionRemovalsByKey[oldKey] = targetVersionRemovalsByKey[oldKey] || [];
307
+ targetVersionRemovalsByKey[oldKey].push(bundle);
733
308
  changedBundlesByKey[newKey] = changedBundlesByKey[newKey] || [];
734
309
  updatedBundle._oldUpdateJsonKey = oldKey;
735
310
  updatedBundle._updateJsonKey = newKey;
736
311
  bundlesMap.set(data.id, updatedBundle);
737
312
  pendingBundlesMap.set(data.id, updatedBundle);
313
+ locallyDeletedBundleIds.delete(data.id);
738
314
  changedBundlesByKey[newKey].push(removeBundleInternalKeys(updatedBundle));
739
315
  const oldChannel = bundle.channel;
740
316
  const nextChannel = updatedBundle.channel;
@@ -745,10 +321,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
745
321
  channel: nextChannel
746
322
  });
747
323
  }
748
- if (bundle.targetAppVersion !== void 0 || updatedBundle.targetAppVersion !== void 0) {
749
- targetVersionPlatforms.add(bundle.platform);
750
- targetVersionPlatforms.add(updatedBundle.platform);
751
- }
324
+ addTargetVersionAddition(targetVersionMutations, updatedBundle);
752
325
  addLookupInvalidationPaths(pathsToInvalidate, updatedBundle);
753
326
  if (bundle.targetAppVersion && bundle.targetAppVersion !== updatedBundle.targetAppVersion) addLookupInvalidationPaths(pathsToInvalidate, bundle);
754
327
  continue;
@@ -756,17 +329,21 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
756
329
  const currentKey = bundle._updateJsonKey;
757
330
  bundlesMap.set(data.id, updatedBundle);
758
331
  pendingBundlesMap.set(data.id, updatedBundle);
332
+ locallyDeletedBundleIds.delete(data.id);
759
333
  changedBundlesByKey[currentKey] = changedBundlesByKey[currentKey] || [];
760
334
  changedBundlesByKey[currentKey].push(removeBundleInternalKeys(updatedBundle));
761
335
  addLookupInvalidationPaths(pathsToInvalidate, updatedBundle);
336
+ addTargetVersionAddition(targetVersionMutations, updatedBundle);
762
337
  if (bundle.targetAppVersion && bundle.targetAppVersion !== updatedBundle.targetAppVersion) addLookupInvalidationPaths(pathsToInvalidate, bundle);
763
338
  }
764
339
  }
765
340
  await forEachWithConcurrency(Object.keys(removalsByKey), STORAGE_OPERATION_CONCURRENCY, async (oldKey) => {
766
341
  const updatedBundles = (await loadOptionalObject(oldKey) ?? []).filter((b) => !removalsByKey[oldKey].includes(b.id));
767
342
  updatedBundles.sort((a, b) => b.id.localeCompare(a.id));
768
- if (updatedBundles.length === 0) await deleteObject(oldKey);
769
- else await uploadObject(oldKey, updatedBundles);
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);
770
347
  });
771
348
  await forEachWithConcurrency(Object.keys(changedBundlesByKey), STORAGE_OPERATION_CONCURRENCY, async (key) => {
772
349
  const currentBundles = await loadOptionalObject(key) ?? [];
@@ -779,22 +356,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
779
356
  currentBundles.sort((a, b) => b.id.localeCompare(a.id));
780
357
  await uploadObject(key, currentBundles);
781
358
  });
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]));
786
- for (const { operation, data } of changedSets) {
787
- if (operation === "delete") {
788
- nextIndexMap.delete(data.id);
789
- continue;
790
- }
791
- nextIndexMap.set(data.id, data);
792
- }
793
- const nextIndexBundles = sortManagedBundles(Array.from(nextIndexMap.values()));
794
- const previousArtifacts = buildManagementIndexArtifacts(previousIndexBundles, managementIndexPageSize);
795
- const nextArtifacts = buildManagementIndexArtifacts(nextIndexBundles, managementIndexPageSize);
796
- await persistManagementIndexArtifacts(nextArtifacts, previousArtifacts);
797
- replaceManagementRootCache(nextArtifacts);
359
+ if (targetVersionMutations.size > 0) await applyTargetVersionMutations(targetVersionMutations);
798
360
  const encondedPaths = /* @__PURE__ */ new Set();
799
361
  for (const path of pathsToInvalidate) encondedPaths.add(encodeURI(path));
800
362
  await invalidatePaths(Array.from(encondedPaths));
@@ -803,13 +365,7 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
803
365
  })
804
366
  })({}, hooks);
805
367
  return () => {
806
- const plugin = createPlugin();
807
- Object.defineProperty(plugin, "diagnostics", {
808
- configurable: true,
809
- enumerable: true,
810
- value: { bundleIndex: bundleIndexDiagnostics }
811
- });
812
- return plugin;
368
+ return createPlugin();
813
369
  };
814
370
  };
815
371
  };