@hot-updater/cloudflare 0.29.5 → 0.29.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/d1Database.ts CHANGED
@@ -11,6 +11,7 @@ import type {
11
11
  import {
12
12
  calculatePagination,
13
13
  createDatabasePlugin,
14
+ createDatabasePluginGetUpdateInfo,
14
15
  } from "@hot-updater/plugin-core";
15
16
  import Cloudflare from "cloudflare";
16
17
  import minify from "pg-minify";
@@ -230,7 +231,90 @@ export const d1Database = createDatabasePlugin<D1DatabaseConfig>({
230
231
  return rows.map(transformRowToBundle);
231
232
  }
232
233
 
234
+ async function queryBundlesForUpdateInfo(
235
+ conditions: QueryConditions,
236
+ ): Promise<Bundle[]> {
237
+ const { sql: whereClause, params } = buildWhereClause(conditions);
238
+ const sql = minify(`
239
+ SELECT * FROM bundles
240
+ ${whereClause}
241
+ `);
242
+
243
+ const result = await cf.d1.database.query(config.databaseId, {
244
+ account_id: config.accountId,
245
+ sql,
246
+ params,
247
+ });
248
+
249
+ const rows = await resolvePage<SnakeCaseBundle>(result);
250
+ return rows.map(transformRowToBundle);
251
+ }
252
+
253
+ async function getTargetAppVersionsForUpdateInfo({
254
+ platform,
255
+ channel,
256
+ minBundleId,
257
+ }: {
258
+ platform: Bundle["platform"];
259
+ channel: string;
260
+ minBundleId: string;
261
+ }): Promise<string[]> {
262
+ const sql = minify(`
263
+ SELECT target_app_version
264
+ FROM bundles
265
+ WHERE channel = ?
266
+ AND platform = ?
267
+ AND enabled = 1
268
+ AND id >= ?
269
+ AND target_app_version IS NOT NULL
270
+ GROUP BY target_app_version
271
+ `);
272
+
273
+ const result = await cf.d1.database.query(config.databaseId, {
274
+ account_id: config.accountId,
275
+ sql,
276
+ params: [channel, platform, minBundleId],
277
+ });
278
+
279
+ const rows = await resolvePage<{ target_app_version: string }>(result);
280
+ return rows.map((row) => row.target_app_version);
281
+ }
282
+
233
283
  return {
284
+ getUpdateInfo: createDatabasePluginGetUpdateInfo({
285
+ listTargetAppVersions: getTargetAppVersionsForUpdateInfo,
286
+ getBundlesByTargetAppVersions(
287
+ { platform, channel, minBundleId },
288
+ targetAppVersions,
289
+ ) {
290
+ return queryBundlesForUpdateInfo({
291
+ enabled: true,
292
+ platform,
293
+ channel,
294
+ id: {
295
+ gte: minBundleId,
296
+ },
297
+ targetAppVersionIn: targetAppVersions,
298
+ });
299
+ },
300
+ getBundlesByFingerprint({
301
+ platform,
302
+ channel,
303
+ minBundleId,
304
+ fingerprintHash,
305
+ }) {
306
+ return queryBundlesForUpdateInfo({
307
+ enabled: true,
308
+ platform,
309
+ channel,
310
+ id: {
311
+ gte: minBundleId,
312
+ },
313
+ fingerprintHash,
314
+ });
315
+ },
316
+ }),
317
+
234
318
  async getBundleById(bundleId) {
235
319
  const sql = minify(/* sql */ `
236
320
  SELECT * FROM bundles WHERE id = ? LIMIT 1`);
@@ -250,7 +334,11 @@ export const d1Database = createDatabasePlugin<D1DatabaseConfig>({
250
334
  },
251
335
 
252
336
  async getBundles(options) {
253
- const { where = {}, limit, offset, orderBy } = options;
337
+ const { where = {}, limit, orderBy } = options;
338
+ const offset =
339
+ (("offset" in options ? options.offset : undefined) as
340
+ | number
341
+ | undefined) ?? 0;
254
342
 
255
343
  // 1. Get total count for pagination
256
344
  const totalCount = await getTotalCount(where);
@@ -1 +1 @@
1
- This folder contains the built output assets for the worker "hot-updater" generated at 2026-04-08T00:31:53.298Z.
1
+ This folder contains the built output assets for the worker "hot-updater" generated at 2026-04-11T04:57:19.076Z.
@@ -3318,12 +3318,78 @@ __name(mergeWith, "mergeWith");
3318
3318
 
3319
3319
  // ../plugin-core/dist/createDatabasePlugin.mjs
3320
3320
  var REPLACE_ON_UPDATE_KEYS = ["targetCohorts"];
3321
+ var DEFAULT_DESC_ORDER = {
3322
+ field: "id",
3323
+ direction: "desc"
3324
+ };
3325
+ function normalizePage(value) {
3326
+ if (!Number.isInteger(value) || value === void 0 || value < 1) return;
3327
+ return value;
3328
+ }
3329
+ __name(normalizePage, "normalizePage");
3321
3330
  function mergeBundleUpdate(baseBundle, patch) {
3322
3331
  return mergeWith(baseBundle, patch, (_targetValue, sourceValue, key) => {
3323
3332
  if (REPLACE_ON_UPDATE_KEYS.includes(key)) return sourceValue;
3324
3333
  });
3325
3334
  }
3326
3335
  __name(mergeBundleUpdate, "mergeBundleUpdate");
3336
+ function mergeIdFilter(base, patch) {
3337
+ return {
3338
+ ...base,
3339
+ ...patch
3340
+ };
3341
+ }
3342
+ __name(mergeIdFilter, "mergeIdFilter");
3343
+ function mergeWhereWithIdFilter(where, idFilter) {
3344
+ return {
3345
+ ...where,
3346
+ id: mergeIdFilter(where?.id, idFilter)
3347
+ };
3348
+ }
3349
+ __name(mergeWhereWithIdFilter, "mergeWhereWithIdFilter");
3350
+ function buildCursorPageQuery(where, cursor, orderBy) {
3351
+ const direction = orderBy.direction;
3352
+ if (cursor.after) return {
3353
+ reverseData: false,
3354
+ where: mergeWhereWithIdFilter(where, { [direction === "desc" ? "lt" : "gt"]: cursor.after }),
3355
+ orderBy
3356
+ };
3357
+ if (cursor.before) return {
3358
+ reverseData: true,
3359
+ where: mergeWhereWithIdFilter(where, { [direction === "desc" ? "gt" : "lt"]: cursor.before }),
3360
+ orderBy: {
3361
+ field: orderBy.field,
3362
+ direction: direction === "desc" ? "asc" : "desc"
3363
+ }
3364
+ };
3365
+ return {
3366
+ reverseData: false,
3367
+ where: where ?? {},
3368
+ orderBy
3369
+ };
3370
+ }
3371
+ __name(buildCursorPageQuery, "buildCursorPageQuery");
3372
+ function buildCountBeforeWhere(where, firstBundleId, orderBy) {
3373
+ return mergeWhereWithIdFilter(where, { [orderBy.direction === "desc" ? "gt" : "lt"]: firstBundleId });
3374
+ }
3375
+ __name(buildCountBeforeWhere, "buildCountBeforeWhere");
3376
+ function createPaginatedResult(total, limit, startIndex, data) {
3377
+ const pagination = calculatePagination(total, {
3378
+ limit,
3379
+ offset: startIndex
3380
+ });
3381
+ const nextCursor = data.length > 0 && startIndex + data.length < total ? data.at(-1)?.id : void 0;
3382
+ const previousCursor = data.length > 0 && startIndex > 0 ? data[0]?.id : void 0;
3383
+ return {
3384
+ data,
3385
+ pagination: {
3386
+ ...pagination,
3387
+ ...nextCursor ? { nextCursor } : {},
3388
+ ...previousCursor ? { previousCursor } : {}
3389
+ }
3390
+ };
3391
+ }
3392
+ __name(createPaginatedResult, "createPaginatedResult");
3327
3393
  function createDatabasePlugin(options) {
3328
3394
  return (config2, hooks) => {
3329
3395
  let cachedMethods = null;
@@ -3339,6 +3405,59 @@ function createDatabasePlugin(options) {
3339
3405
  data
3340
3406
  });
3341
3407
  }, "markChanged");
3408
+ const runGetBundles = /* @__PURE__ */ __name(async (options2, context2) => {
3409
+ if (context2 === void 0) return getMethods().getBundles(options2);
3410
+ return getMethods().getBundles(options2, context2);
3411
+ }, "runGetBundles");
3412
+ const getBundlesWithLegacyCursorFallback = /* @__PURE__ */ __name(async (options2, context2) => {
3413
+ const orderBy = options2.orderBy ?? DEFAULT_DESC_ORDER;
3414
+ const baseWhere = options2.where;
3415
+ const total = (await runGetBundles({
3416
+ where: baseWhere,
3417
+ limit: 1,
3418
+ offset: 0,
3419
+ orderBy
3420
+ }, context2)).pagination.total;
3421
+ if (!options2.cursor?.after && !options2.cursor?.before) {
3422
+ const firstPage = await runGetBundles({
3423
+ where: baseWhere,
3424
+ limit: options2.limit,
3425
+ offset: 0,
3426
+ orderBy
3427
+ }, context2);
3428
+ return createPaginatedResult(total, options2.limit, 0, firstPage.data);
3429
+ }
3430
+ const { where, orderBy: queryOrderBy, reverseData } = buildCursorPageQuery(baseWhere, options2.cursor, orderBy);
3431
+ const cursorPage = await runGetBundles({
3432
+ where,
3433
+ limit: options2.limit,
3434
+ offset: 0,
3435
+ orderBy: queryOrderBy
3436
+ }, context2);
3437
+ const data = reverseData ? cursorPage.data.slice().reverse() : cursorPage.data;
3438
+ if (data.length === 0) {
3439
+ const emptyStartIndex = options2.cursor.after ? total : 0;
3440
+ return {
3441
+ data,
3442
+ pagination: {
3443
+ ...calculatePagination(total, {
3444
+ limit: options2.limit,
3445
+ offset: emptyStartIndex
3446
+ }),
3447
+ ...options2.cursor.after ? { previousCursor: options2.cursor.after } : {},
3448
+ ...options2.cursor.before ? { nextCursor: options2.cursor.before } : {}
3449
+ }
3450
+ };
3451
+ }
3452
+ const firstBundleId = data[0].id;
3453
+ const countBeforeResult = await runGetBundles({
3454
+ where: buildCountBeforeWhere(baseWhere, firstBundleId, orderBy),
3455
+ limit: 1,
3456
+ offset: 0,
3457
+ orderBy
3458
+ }, context2);
3459
+ return createPaginatedResult(total, options2.limit, countBeforeResult.pagination.total, data);
3460
+ }, "getBundlesWithLegacyCursorFallback");
3342
3461
  const plugin = {
3343
3462
  name: options.name,
3344
3463
  async getBundleById(bundleId, context2) {
@@ -3346,8 +3465,35 @@ function createDatabasePlugin(options) {
3346
3465
  return getMethods().getBundleById(bundleId, context2);
3347
3466
  },
3348
3467
  async getBundles(options2, context2) {
3349
- if (context2 === void 0) return getMethods().getBundles(options2);
3350
- return getMethods().getBundles(options2, context2);
3468
+ if (typeof options2 === "object" && options2 !== null && "offset" in options2 && options2.offset !== void 0) throw new Error("Bundle offset pagination has been removed. Use cursor.after or cursor.before instead.");
3469
+ const methods = getMethods();
3470
+ const normalizedOptions = {
3471
+ ...options2,
3472
+ page: normalizePage(options2.page),
3473
+ orderBy: options2.orderBy ?? DEFAULT_DESC_ORDER
3474
+ };
3475
+ if (normalizedOptions.page !== void 0) {
3476
+ const { page, ...pageOptions } = normalizedOptions;
3477
+ const requestedOffset = (page - 1) * normalizedOptions.limit;
3478
+ let pageResult = await runGetBundles({
3479
+ ...pageOptions,
3480
+ offset: requestedOffset
3481
+ }, context2);
3482
+ const total = pageResult.pagination.total;
3483
+ const totalPages = total === 0 ? 0 : Math.ceil(total / normalizedOptions.limit);
3484
+ const maxOffset = totalPages === 0 ? 0 : (Math.max(1, totalPages) - 1) * normalizedOptions.limit;
3485
+ const resolvedOffset = Math.min(requestedOffset, maxOffset);
3486
+ if (resolvedOffset !== requestedOffset) pageResult = await runGetBundles({
3487
+ ...pageOptions,
3488
+ offset: resolvedOffset
3489
+ }, context2);
3490
+ return createPaginatedResult(total, normalizedOptions.limit, resolvedOffset, pageResult.data);
3491
+ }
3492
+ if (methods.supportsCursorPagination) {
3493
+ if (context2 === void 0) return methods.getBundles(normalizedOptions);
3494
+ return methods.getBundles(normalizedOptions, context2);
3495
+ }
3496
+ return getBundlesWithLegacyCursorFallback(normalizedOptions, context2);
3351
3497
  },
3352
3498
  async getChannels(context2) {
3353
3499
  if (context2 === void 0) return getMethods().getChannels();
@@ -3428,6 +3574,14 @@ var semverSatisfies = /* @__PURE__ */ __name((targetAppVersion, currentVersion)
3428
3574
  return import_semver.default.satisfies(currentCoerce.version, targetAppVersion);
3429
3575
  }, "semverSatisfies");
3430
3576
 
3577
+ // ../plugin-core/dist/filterCompatibleAppVersions.mjs
3578
+ init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
3579
+ init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
3580
+ init_performance2();
3581
+ var filterCompatibleAppVersions = /* @__PURE__ */ __name((targetAppVersionList, currentVersion) => {
3582
+ return targetAppVersionList.filter((version2) => semverSatisfies(version2, currentVersion)).sort((a, b) => b.localeCompare(a));
3583
+ }, "filterCompatibleAppVersions");
3584
+
3431
3585
  // ../js/dist/index.mjs
3432
3586
  init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
3433
3587
  init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
@@ -4716,6 +4870,107 @@ var import_semver2 = /* @__PURE__ */ __toESM2((/* @__PURE__ */ __commonJSMin((ex
4716
4870
  rcompareIdentifiers: identifiers.rcompareIdentifiers
4717
4871
  };
4718
4872
  }))(), 1);
4873
+ var semverSatisfies2 = /* @__PURE__ */ __name((targetAppVersion, currentVersion) => {
4874
+ const currentCoerce = import_semver2.default.coerce(currentVersion);
4875
+ if (!currentCoerce) return false;
4876
+ return import_semver2.default.satisfies(currentCoerce.version, targetAppVersion);
4877
+ }, "semverSatisfies");
4878
+ var INIT_BUNDLE_ROLLBACK_UPDATE_INFO = {
4879
+ message: null,
4880
+ id: NIL_UUID,
4881
+ shouldForceUpdate: true,
4882
+ status: "ROLLBACK",
4883
+ storageUri: null,
4884
+ fileHash: null
4885
+ };
4886
+ var makeResponse = /* @__PURE__ */ __name((bundle, status) => ({
4887
+ id: bundle.id,
4888
+ message: bundle.message,
4889
+ shouldForceUpdate: status === "ROLLBACK" ? true : bundle.shouldForceUpdate,
4890
+ status,
4891
+ storageUri: bundle.storageUri,
4892
+ fileHash: bundle.fileHash
4893
+ }), "makeResponse");
4894
+ var isEligibleUpdateCandidate = /* @__PURE__ */ __name((bundle, cohort) => {
4895
+ return isCohortEligibleForUpdate(bundle.id, cohort, bundle.rolloutCohortCount, bundle.targetCohorts);
4896
+ }, "isEligibleUpdateCandidate");
4897
+ var findLatestEligibleUpdateCandidate = /* @__PURE__ */ __name((bundles, bundleId, cohort) => {
4898
+ let updateCandidate = null;
4899
+ for (const bundle of bundles) if (bundle.id.localeCompare(bundleId) > 0 && isEligibleUpdateCandidate(bundle, cohort) && (!updateCandidate || bundle.id.localeCompare(updateCandidate.id) > 0)) updateCandidate = bundle;
4900
+ return updateCandidate;
4901
+ }, "findLatestEligibleUpdateCandidate");
4902
+ var getUpdateInfo = /* @__PURE__ */ __name(async (bundles, args) => {
4903
+ switch (args._updateStrategy) {
4904
+ case "appVersion":
4905
+ return appVersionStrategy(bundles, args);
4906
+ case "fingerprint":
4907
+ return fingerprintStrategy(bundles, args);
4908
+ default:
4909
+ return null;
4910
+ }
4911
+ }, "getUpdateInfo");
4912
+ var appVersionStrategy = /* @__PURE__ */ __name(async (bundles, { channel: channel2 = "production", minBundleId = NIL_UUID, platform: platform2, appVersion, bundleId, cohort }) => {
4913
+ const candidateBundles = [];
4914
+ for (const b of bundles) {
4915
+ if (b.platform !== platform2 || b.channel !== channel2 || !b.targetAppVersion || !semverSatisfies2(b.targetAppVersion, appVersion) || !b.enabled || minBundleId && b.id.localeCompare(minBundleId) < 0) continue;
4916
+ candidateBundles.push(b);
4917
+ }
4918
+ if (candidateBundles.length === 0) {
4919
+ if (bundleId === NIL_UUID || minBundleId && bundleId.localeCompare(minBundleId) <= 0) return null;
4920
+ return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
4921
+ }
4922
+ let rollbackCandidate = null;
4923
+ let currentBundle;
4924
+ for (const b of candidateBundles) if (b.id === bundleId) currentBundle = b;
4925
+ else if (bundleId !== NIL_UUID && b.id.localeCompare(bundleId) < 0) {
4926
+ if (!rollbackCandidate || b.id.localeCompare(rollbackCandidate.id) > 0) rollbackCandidate = b;
4927
+ }
4928
+ const updateCandidate = findLatestEligibleUpdateCandidate(candidateBundles, bundleId, cohort);
4929
+ const currentBundleEligible = currentBundle ? isEligibleUpdateCandidate(currentBundle, cohort) : false;
4930
+ if (bundleId === NIL_UUID) {
4931
+ if (updateCandidate) return makeResponse(updateCandidate, "UPDATE");
4932
+ return null;
4933
+ }
4934
+ if (currentBundleEligible) {
4935
+ if (updateCandidate) return makeResponse(updateCandidate, "UPDATE");
4936
+ return null;
4937
+ }
4938
+ if (updateCandidate) return makeResponse(updateCandidate, "UPDATE");
4939
+ if (rollbackCandidate) return makeResponse(rollbackCandidate, "ROLLBACK");
4940
+ if (minBundleId && bundleId.localeCompare(minBundleId) <= 0) return null;
4941
+ return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
4942
+ }, "appVersionStrategy");
4943
+ var fingerprintStrategy = /* @__PURE__ */ __name(async (bundles, { channel: channel2 = "production", minBundleId = NIL_UUID, platform: platform2, fingerprintHash, bundleId, cohort }) => {
4944
+ const candidateBundles = [];
4945
+ for (const b of bundles) {
4946
+ if (b.platform !== platform2 || b.channel !== channel2 || !b.fingerprintHash || b.fingerprintHash !== fingerprintHash || !b.enabled || minBundleId && b.id.localeCompare(minBundleId) < 0) continue;
4947
+ candidateBundles.push(b);
4948
+ }
4949
+ if (candidateBundles.length === 0) {
4950
+ if (bundleId === NIL_UUID || minBundleId && bundleId.localeCompare(minBundleId) <= 0) return null;
4951
+ return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
4952
+ }
4953
+ let rollbackCandidate = null;
4954
+ let currentBundle;
4955
+ for (const b of candidateBundles) if (b.id === bundleId) currentBundle = b;
4956
+ else if (bundleId !== NIL_UUID && b.id.localeCompare(bundleId) < 0) {
4957
+ if (!rollbackCandidate || b.id.localeCompare(rollbackCandidate.id) > 0) rollbackCandidate = b;
4958
+ }
4959
+ const updateCandidate = findLatestEligibleUpdateCandidate(candidateBundles, bundleId, cohort);
4960
+ const currentBundleEligible = currentBundle ? isEligibleUpdateCandidate(currentBundle, cohort) : false;
4961
+ if (bundleId === NIL_UUID) {
4962
+ if (updateCandidate) return makeResponse(updateCandidate, "UPDATE");
4963
+ return null;
4964
+ }
4965
+ if (currentBundleEligible) {
4966
+ if (updateCandidate) return makeResponse(updateCandidate, "UPDATE");
4967
+ return null;
4968
+ }
4969
+ if (updateCandidate) return makeResponse(updateCandidate, "UPDATE");
4970
+ if (rollbackCandidate) return makeResponse(rollbackCandidate, "ROLLBACK");
4971
+ if (minBundleId && bundleId.localeCompare(minBundleId) <= 0) return null;
4972
+ return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
4973
+ }, "fingerprintStrategy");
4719
4974
  var encoder = new TextEncoder();
4720
4975
  var decoder = new TextDecoder();
4721
4976
  function concat(...buffers) {
@@ -5863,6 +6118,32 @@ var signToken = /* @__PURE__ */ __name(async (key, jwtSecret) => {
5863
6118
  return await new SignJWT({ key }).setProtectedHeader({ alg: "HS256" }).setExpirationTime("60s").sign(secretKey);
5864
6119
  }, "signToken");
5865
6120
 
6121
+ // ../plugin-core/dist/createDatabasePluginGetUpdateInfo.mjs
6122
+ init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_process();
6123
+ init_virtual_unenv_global_polyfill_cloudflare_unenv_preset_node_console();
6124
+ init_performance2();
6125
+ var normalizeAppVersionArgs = /* @__PURE__ */ __name((args) => ({
6126
+ ...args,
6127
+ channel: args.channel ?? "production",
6128
+ minBundleId: args.minBundleId ?? NIL_UUID
6129
+ }), "normalizeAppVersionArgs");
6130
+ var normalizeFingerprintArgs = /* @__PURE__ */ __name((args) => ({
6131
+ ...args,
6132
+ channel: args.channel ?? "production",
6133
+ minBundleId: args.minBundleId ?? NIL_UUID
6134
+ }), "normalizeFingerprintArgs");
6135
+ var createDatabasePluginGetUpdateInfo = /* @__PURE__ */ __name(({ getBundlesByFingerprint, getBundlesByTargetAppVersions, listTargetAppVersions }) => {
6136
+ return async (args, context2) => {
6137
+ if (args._updateStrategy === "appVersion") {
6138
+ const normalizedArgs2 = normalizeAppVersionArgs(args);
6139
+ const compatibleAppVersions = filterCompatibleAppVersions(await listTargetAppVersions(normalizedArgs2, context2), normalizedArgs2.appVersion);
6140
+ return getUpdateInfo(compatibleAppVersions.length > 0 ? await getBundlesByTargetAppVersions(normalizedArgs2, compatibleAppVersions, context2) : [], normalizedArgs2);
6141
+ }
6142
+ const normalizedArgs = normalizeFingerprintArgs(args);
6143
+ return getUpdateInfo(await getBundlesByFingerprint(normalizedArgs, context2), normalizedArgs);
6144
+ };
6145
+ }, "createDatabasePluginGetUpdateInfo");
6146
+
5866
6147
  // ../../packages/server/src/db/pluginCore.ts
5867
6148
  var PAGE_SIZE = 100;
5868
6149
  var DESC_ORDER = { field: "id", direction: "desc" };
@@ -5905,7 +6186,7 @@ var sortBundles = /* @__PURE__ */ __name((bundles, orderBy) => {
5905
6186
  return direction === "asc" ? result : -result;
5906
6187
  });
5907
6188
  }, "sortBundles");
5908
- var makeResponse = /* @__PURE__ */ __name((bundle, status) => ({
6189
+ var makeResponse2 = /* @__PURE__ */ __name((bundle, status) => ({
5909
6190
  id: bundle.id,
5910
6191
  message: bundle.message,
5911
6192
  shouldForceUpdate: status === "ROLLBACK" ? true : bundle.shouldForceUpdate,
@@ -5913,7 +6194,7 @@ var makeResponse = /* @__PURE__ */ __name((bundle, status) => ({
5913
6194
  storageUri: bundle.storageUri,
5914
6195
  fileHash: bundle.fileHash
5915
6196
  }), "makeResponse");
5916
- var INIT_BUNDLE_ROLLBACK_UPDATE_INFO = {
6197
+ var INIT_BUNDLE_ROLLBACK_UPDATE_INFO2 = {
5917
6198
  message: null,
5918
6199
  id: NIL_UUID,
5919
6200
  shouldForceUpdate: true,
@@ -5959,14 +6240,18 @@ function createPluginDatabaseCore(getPlugin, resolveFileUrl, options) {
5959
6240
  isCandidate,
5960
6241
  context: context2
5961
6242
  }) => {
5962
- let offset = 0;
6243
+ let after;
5963
6244
  while (true) {
5964
6245
  const { data, pagination } = await getSortedBundlePage(
5965
6246
  {
5966
6247
  where: queryWhere,
5967
6248
  limit: PAGE_SIZE,
5968
- offset,
5969
- orderBy: DESC_ORDER
6249
+ orderBy: DESC_ORDER,
6250
+ ...after ? {
6251
+ cursor: {
6252
+ after
6253
+ }
6254
+ } : {}
5970
6255
  },
5971
6256
  context2
5972
6257
  );
@@ -5976,14 +6261,14 @@ function createPluginDatabaseCore(getPlugin, resolveFileUrl, options) {
5976
6261
  }
5977
6262
  if (args.bundleId === NIL_UUID) {
5978
6263
  if (isEligibleForUpdate(bundle, args.cohort)) {
5979
- return makeResponse(bundle, "UPDATE");
6264
+ return makeResponse2(bundle, "UPDATE");
5980
6265
  }
5981
6266
  continue;
5982
6267
  }
5983
6268
  const compareResult = bundle.id.localeCompare(args.bundleId);
5984
6269
  if (compareResult > 0) {
5985
6270
  if (isEligibleForUpdate(bundle, args.cohort)) {
5986
- return makeResponse(bundle, "UPDATE");
6271
+ return makeResponse2(bundle, "UPDATE");
5987
6272
  }
5988
6273
  continue;
5989
6274
  }
@@ -5993,12 +6278,15 @@ function createPluginDatabaseCore(getPlugin, resolveFileUrl, options) {
5993
6278
  }
5994
6279
  continue;
5995
6280
  }
5996
- return makeResponse(bundle, "ROLLBACK");
6281
+ return makeResponse2(bundle, "ROLLBACK");
5997
6282
  }
5998
6283
  if (!pagination.hasNextPage) {
5999
6284
  break;
6000
6285
  }
6001
- offset += PAGE_SIZE;
6286
+ after = data.at(-1)?.id;
6287
+ if (!after) {
6288
+ break;
6289
+ }
6002
6290
  }
6003
6291
  if (args.bundleId === NIL_UUID) {
6004
6292
  return null;
@@ -6006,7 +6294,7 @@ function createPluginDatabaseCore(getPlugin, resolveFileUrl, options) {
6006
6294
  if (args.minBundleId && args.bundleId.localeCompare(args.minBundleId) <= 0) {
6007
6295
  return null;
6008
6296
  }
6009
- return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
6297
+ return INIT_BUNDLE_ROLLBACK_UPDATE_INFO2;
6010
6298
  }, "findUpdateInfoByScanning");
6011
6299
  const getBaseWhere = /* @__PURE__ */ __name(({
6012
6300
  platform: platform2,
@@ -6321,7 +6609,21 @@ var handleGetBundles = /* @__PURE__ */ __name(async (_params, request, api, cont
6321
6609
  const channel2 = url.searchParams.get("channel") ?? void 0;
6322
6610
  const platform2 = url.searchParams.get("platform");
6323
6611
  const limit = Number(url.searchParams.get("limit")) || 50;
6324
- const offset = Number(url.searchParams.get("offset")) || 0;
6612
+ const pageParam = url.searchParams.get("page");
6613
+ const offset = url.searchParams.get("offset");
6614
+ const after = url.searchParams.get("after") ?? void 0;
6615
+ const before = url.searchParams.get("before") ?? void 0;
6616
+ const page = pageParam === null ? void 0 : Number.isInteger(Number(pageParam)) && Number(pageParam) > 0 ? Number(pageParam) : null;
6617
+ if (offset !== null) {
6618
+ throw new HandlerBadRequestError(
6619
+ "The 'offset' query parameter has been removed. Use 'after' or 'before' cursor pagination instead."
6620
+ );
6621
+ }
6622
+ if (page === null) {
6623
+ throw new HandlerBadRequestError(
6624
+ "The 'page' query parameter must be a positive integer."
6625
+ );
6626
+ }
6325
6627
  if (platform2 !== null && !isPlatform(platform2)) {
6326
6628
  throw new HandlerBadRequestError(
6327
6629
  `Invalid platform: ${platform2}. Expected 'ios' or 'android'.`
@@ -6334,7 +6636,11 @@ var handleGetBundles = /* @__PURE__ */ __name(async (_params, request, api, cont
6334
6636
  ...platform2 && { platform: platform2 }
6335
6637
  },
6336
6638
  limit,
6337
- offset
6639
+ page,
6640
+ cursor: after || before ? {
6641
+ after,
6642
+ before
6643
+ } : void 0
6338
6644
  },
6339
6645
  context2
6340
6646
  );
@@ -8892,7 +9198,71 @@ var d1WorkerDatabase = /* @__PURE__ */ __name(() => createDatabasePlugin({
8892
9198
  const result = await config2.getDb(context2).prepare(sql).bind(...params).first();
8893
9199
  return result ?? null;
8894
9200
  }, "queryFirst");
9201
+ const queryBundlesForUpdateInfo = /* @__PURE__ */ __name(async (conditions, context2) => {
9202
+ const { sql: whereClause, params } = buildWhereClause(conditions);
9203
+ const rows = await queryAll(
9204
+ `
9205
+ SELECT * FROM bundles
9206
+ ${whereClause}
9207
+ `,
9208
+ params,
9209
+ context2
9210
+ );
9211
+ return rows.map(transformRowToBundle);
9212
+ }, "queryBundlesForUpdateInfo");
9213
+ const getTargetAppVersionsForUpdateInfo = /* @__PURE__ */ __name(async ({
9214
+ platform: platform2,
9215
+ channel: channel2,
9216
+ minBundleId
9217
+ }, context2) => {
9218
+ const rows = await queryAll(
9219
+ `
9220
+ SELECT target_app_version
9221
+ FROM bundles
9222
+ WHERE channel = ?
9223
+ AND platform = ?
9224
+ AND enabled = 1
9225
+ AND id >= ?
9226
+ AND target_app_version IS NOT NULL
9227
+ GROUP BY target_app_version
9228
+ `,
9229
+ [channel2, platform2, minBundleId],
9230
+ context2
9231
+ );
9232
+ return rows.map((row) => row.target_app_version);
9233
+ }, "getTargetAppVersionsForUpdateInfo");
8895
9234
  return {
9235
+ getUpdateInfo: createDatabasePluginGetUpdateInfo({
9236
+ listTargetAppVersions: getTargetAppVersionsForUpdateInfo,
9237
+ getBundlesByTargetAppVersions({ platform: platform2, channel: channel2, minBundleId }, targetAppVersions, context2) {
9238
+ return queryBundlesForUpdateInfo(
9239
+ {
9240
+ enabled: true,
9241
+ platform: platform2,
9242
+ channel: channel2,
9243
+ id: {
9244
+ gte: minBundleId
9245
+ },
9246
+ targetAppVersionIn: targetAppVersions
9247
+ },
9248
+ context2
9249
+ );
9250
+ },
9251
+ getBundlesByFingerprint({ platform: platform2, channel: channel2, minBundleId, fingerprintHash }, context2) {
9252
+ return queryBundlesForUpdateInfo(
9253
+ {
9254
+ enabled: true,
9255
+ platform: platform2,
9256
+ channel: channel2,
9257
+ id: {
9258
+ gte: minBundleId
9259
+ },
9260
+ fingerprintHash
9261
+ },
9262
+ context2
9263
+ );
9264
+ }
9265
+ }),
8896
9266
  async getBundleById(bundleId, context2) {
8897
9267
  const row = await queryFirst(
8898
9268
  "SELECT * FROM bundles WHERE id = ? LIMIT 1",
@@ -8902,7 +9272,8 @@ var d1WorkerDatabase = /* @__PURE__ */ __name(() => createDatabasePlugin({
8902
9272
  return row ? transformRowToBundle(row) : null;
8903
9273
  },
8904
9274
  async getBundles(options, context2) {
8905
- const { where, limit, offset, orderBy } = options;
9275
+ const { where, limit, orderBy } = options;
9276
+ const offset = ("offset" in options ? options.offset : void 0) ?? 0;
8906
9277
  const { sql: whereClause, params } = buildWhereClause(where);
8907
9278
  const orderSql = orderBy?.direction === "asc" ? "ORDER BY id ASC" : "ORDER BY id DESC";
8908
9279
  const countRows = await queryAll(