@hot-updater/plugin-core 0.29.5 → 0.29.6

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.
@@ -3,6 +3,7 @@ const require_calculatePagination = require("./calculatePagination.cjs");
3
3
  const require_createDatabasePlugin = require("./createDatabasePlugin.cjs");
4
4
  const require_filterCompatibleAppVersions = require("./filterCompatibleAppVersions.cjs");
5
5
  const require_queryBundles = require("./queryBundles.cjs");
6
+ const require_paginateBundles = require("./paginateBundles.cjs");
6
7
  let _hot_updater_js = require("@hot-updater/js");
7
8
  let es_toolkit = require("es-toolkit");
8
9
  let semver = require("semver");
@@ -48,6 +49,135 @@ function resolveStorageTarget({ targetAppVersion, fingerprintHash }) {
48
49
  if (!target) throw new Error("target not found");
49
50
  return target;
50
51
  }
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);
74
+ }
75
+ function getSupportedManagementScope(where, orderBy) {
76
+ if (!isDefaultManagementOrder(orderBy) || hasUnsupportedManagementFilters(where)) return null;
77
+ return {
78
+ channel: where?.channel,
79
+ platform: where?.platform
80
+ };
81
+ }
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`;
99
+ }
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;
115
+ }
116
+ function createEmptyManagementResult(limit) {
117
+ return {
118
+ data: [],
119
+ pagination: require_calculatePagination.calculatePagination(0, {
120
+ limit,
121
+ offset: 0
122
+ })
123
+ };
124
+ }
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
+ };
180
+ }
51
181
  /**
52
182
  * Creates a blob storage-based database plugin with lazy initialization.
53
183
  *
@@ -57,22 +187,282 @@ function resolveStorageTarget({ targetAppVersion, fingerprintHash }) {
57
187
  */
58
188
  const createBlobDatabasePlugin = ({ name, factory }) => {
59
189
  return (config, hooks) => {
190
+ const managementIndexPageSize = resolveManagementIndexPageSize(config);
60
191
  const { listObjects, loadObject, uploadObject, deleteObject, invalidatePaths, apiBasePath } = factory(config);
61
192
  const bundlesMap = /* @__PURE__ */ new Map();
62
193
  const pendingBundlesMap = /* @__PURE__ */ new Map();
194
+ const managementRootCache = /* @__PURE__ */ new Map();
63
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);
241
+ }
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
+ };
284
+ const findPageIndexContainingId = (pages, id) => {
285
+ return pages.findIndex((page) => id.localeCompare(page.firstId) <= 0 && id.localeCompare(page.lastId) >= 0);
286
+ };
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
442
+ });
443
+ data.push(...currentPageIndex === pageIndex ? page.slice(startInPage) : page);
444
+ }
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
+ };
64
457
  async function reloadBundles() {
65
458
  bundlesMap.clear();
66
- const platformPromises = PLATFORMS.map(async (platform) => {
67
- const filePromises = (await listUpdateJsonKeys(platform)).map(async (key) => {
68
- return (await loadObject(key) ?? []).map((bundle) => ({
69
- ...bundle,
70
- _updateJsonKey: key
71
- }));
72
- });
73
- return (await Promise.all(filePromises)).flat();
459
+ const filePromises = (await listObjects("")).filter((key) => /^[^/]+\/(?:ios|android)\/[^/]+\/update\.json$/.test(key)).map(async (key) => {
460
+ return (await loadObject(key) ?? []).map((bundle) => ({
461
+ ...bundle,
462
+ _updateJsonKey: key
463
+ }));
74
464
  });
75
- const allBundles = (await Promise.all(platformPromises)).flat();
465
+ const allBundles = (await Promise.all(filePromises)).flat();
76
466
  for (const bundle of allBundles) bundlesMap.set(bundle.id, bundle);
77
467
  for (const [id, bundle] of pendingBundlesMap.entries()) bundlesMap.set(id, bundle);
78
468
  return (0, es_toolkit.orderBy)(allBundles, [(v) => v.id], ["desc"]);
@@ -107,17 +497,6 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
107
497
  if (JSON.stringify(oldTargetVersions) !== JSON.stringify(newTargetVersions)) await uploadObject(targetKey, newTargetVersions);
108
498
  }
109
499
  }
110
- /**
111
- * Lists update.json keys for a given platform.
112
- *
113
- * - If a channel is provided, only that channel's update.json files are listed.
114
- * - Otherwise, all channels for the given platform are returned.
115
- */
116
- async function listUpdateJsonKeys(platform, channel) {
117
- const prefix = channel ? platform ? `${channel}/${platform}/` : `${channel}/` : "";
118
- const pattern = channel ? platform ? new RegExp(`^${channel}/${platform}/[^/]+/update\\.json$`) : new RegExp(`^${channel}/[^/]+/[^/]+/update\\.json$`) : platform ? new RegExp(`^[^/]+/${platform}/[^/]+/update\\.json$`) : /^[^/]+\/[^/]+\/[^/]+\/update\.json$/;
119
- return listObjects(prefix).then((keys) => keys.filter((key) => pattern.test(key)));
120
- }
121
500
  const getAppVersionUpdateInfo = async ({ appVersion, bundleId, channel = "production", cohort, minBundleId, platform }) => {
122
501
  const matchingVersions = require_filterCompatibleAppVersions.filterCompatibleAppVersions(await loadObject(`${channel}/${platform}/target-app-versions.json`) ?? [], appVersion);
123
502
  return (0, _hot_updater_js.getUpdateInfo)((await Promise.allSettled(matchingVersions.map(async (targetAppVersion) => {
@@ -165,41 +544,56 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
165
544
  return require_createDatabasePlugin.createDatabasePlugin({
166
545
  name,
167
546
  factory: () => ({
547
+ supportsCursorPagination: true,
168
548
  async getBundleById(bundleId) {
169
549
  const pendingBundle = pendingBundlesMap.get(bundleId);
170
550
  if (pendingBundle) return removeBundleInternalKeys(pendingBundle);
171
551
  const bundle = bundlesMap.get(bundleId);
172
552
  if (bundle) return removeBundleInternalKeys(bundle);
173
- return (await reloadBundles()).find((bundle) => bundle.id === bundleId) ?? null;
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
+ const matchedBundle = (await reloadBundles()).find((item) => item.id === bundleId);
565
+ if (!matchedBundle) return null;
566
+ return removeBundleInternalKeys(matchedBundle);
174
567
  },
175
568
  async getUpdateInfo(args) {
176
569
  if (args._updateStrategy === "appVersion") return getAppVersionUpdateInfo(args);
177
570
  return getFingerprintUpdateInfo(args);
178
571
  },
179
572
  async getBundles(options) {
180
- let allBundles = await reloadBundles();
181
- const { where, limit, offset, orderBy } = options;
182
- if (where) allBundles = allBundles.filter((bundle) => require_queryBundles.bundleMatchesQueryWhere(bundle, where));
183
- const cleanBundles = require_queryBundles.sortBundles(allBundles.map(removeBundleInternalKeys), orderBy);
184
- const total = cleanBundles.length;
185
- let paginatedData = cleanBundles;
186
- if (offset > 0) paginatedData = paginatedData.slice(offset);
187
- if (limit) paginatedData = paginatedData.slice(0, limit);
188
- return {
189
- data: paginatedData,
190
- pagination: require_calculatePagination.calculatePagination(total, {
573
+ 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,
191
580
  limit,
192
- offset
193
- })
194
- };
581
+ offset,
582
+ cursor
583
+ });
584
+ }
585
+ let allBundles = await loadAllBundlesForManagementFallback();
586
+ if (where) allBundles = allBundles.filter((bundle) => require_queryBundles.bundleMatchesQueryWhere(bundle, where));
587
+ return require_paginateBundles.paginateBundles({
588
+ bundles: allBundles,
589
+ limit,
590
+ offset,
591
+ cursor,
592
+ orderBy
593
+ });
195
594
  },
196
595
  async getChannels() {
197
- const total = (await reloadBundles()).length;
198
- const result = await this.getBundles({
199
- limit: total,
200
- offset: 0
201
- });
202
- return [...new Set(result.data.map((bundle) => bundle.channel))];
596
+ return (await loadManagementScopeRoot({}))?.channels ?? [];
203
597
  },
204
598
  async commitBundle({ changedSets }) {
205
599
  if (changedSets.length === 0) return;
@@ -296,6 +690,20 @@ const createBlobDatabasePlugin = ({ name, factory }) => {
296
690
  await uploadObject(key, currentBundles);
297
691
  })();
298
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);
299
707
  const encondedPaths = /* @__PURE__ */ new Set();
300
708
  for (const path of pathsToInvalidate) encondedPaths.add(encodeURI(path));
301
709
  await invalidatePaths(Array.from(encondedPaths));
@@ -1,6 +1,9 @@
1
1
  import { DatabasePlugin, DatabasePluginHooks } from "./types/index.cjs";
2
2
 
3
3
  //#region src/createBlobDatabasePlugin.d.ts
4
+ interface BlobDatabasePluginConfig {
5
+ managementIndexPageSize?: number;
6
+ }
4
7
  interface BlobOperations {
5
8
  listObjects: (prefix: string) => Promise<string[]>;
6
9
  loadObject: <T>(key: string) => Promise<T | null>;
@@ -24,4 +27,4 @@ declare const createBlobDatabasePlugin: <TConfig>({
24
27
  factory: (config: TConfig) => BlobOperations;
25
28
  }) => (config: TConfig, hooks?: DatabasePluginHooks) => () => DatabasePlugin<unknown>;
26
29
  //#endregion
27
- export { BlobOperations, createBlobDatabasePlugin };
30
+ export { BlobDatabasePluginConfig, BlobOperations, createBlobDatabasePlugin };
@@ -1,6 +1,9 @@
1
1
  import { DatabasePlugin, DatabasePluginHooks } from "./types/index.mjs";
2
2
 
3
3
  //#region src/createBlobDatabasePlugin.d.ts
4
+ interface BlobDatabasePluginConfig {
5
+ managementIndexPageSize?: number;
6
+ }
4
7
  interface BlobOperations {
5
8
  listObjects: (prefix: string) => Promise<string[]>;
6
9
  loadObject: <T>(key: string) => Promise<T | null>;
@@ -24,4 +27,4 @@ declare const createBlobDatabasePlugin: <TConfig>({
24
27
  factory: (config: TConfig) => BlobOperations;
25
28
  }) => (config: TConfig, hooks?: DatabasePluginHooks) => () => DatabasePlugin<unknown>;
26
29
  //#endregion
27
- export { BlobOperations, createBlobDatabasePlugin };
30
+ export { BlobDatabasePluginConfig, BlobOperations, createBlobDatabasePlugin };