@hot-updater/server 0.28.0 → 0.29.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.
Files changed (84) hide show
  1. package/dist/adapters/drizzle.cjs +7 -7
  2. package/dist/adapters/drizzle.mjs +2 -0
  3. package/dist/adapters/kysely.cjs +7 -7
  4. package/dist/adapters/kysely.mjs +2 -0
  5. package/dist/adapters/mongodb.cjs +7 -7
  6. package/dist/adapters/mongodb.mjs +2 -0
  7. package/dist/adapters/prisma.cjs +7 -7
  8. package/dist/adapters/prisma.mjs +2 -0
  9. package/dist/calculatePagination.cjs +1 -3
  10. package/dist/{calculatePagination.js → calculatePagination.mjs} +1 -2
  11. package/dist/db/index.cjs +24 -15
  12. package/dist/db/index.d.cts +12 -9
  13. package/dist/db/index.d.mts +30 -0
  14. package/dist/db/index.mjs +45 -0
  15. package/dist/db/ormCore.cjs +247 -138
  16. package/dist/db/ormCore.d.cts +35 -17
  17. package/dist/db/ormCore.d.mts +44 -0
  18. package/dist/db/ormCore.mjs +386 -0
  19. package/dist/db/pluginCore.cjs +145 -40
  20. package/dist/db/pluginCore.mjs +176 -0
  21. package/dist/db/types.cjs +1 -3
  22. package/dist/db/types.d.cts +14 -21
  23. package/dist/db/types.d.mts +24 -0
  24. package/dist/db/{types.js → types.mjs} +1 -2
  25. package/dist/handler.cjs +117 -48
  26. package/dist/handler.d.cts +28 -18
  27. package/dist/handler.d.mts +47 -0
  28. package/dist/handler.mjs +217 -0
  29. package/dist/index.cjs +5 -5
  30. package/dist/index.d.cts +3 -3
  31. package/dist/index.d.mts +5 -0
  32. package/dist/index.mjs +4 -0
  33. package/dist/internalRouter.cjs +54 -0
  34. package/dist/internalRouter.mjs +52 -0
  35. package/dist/node.cjs +2 -3
  36. package/dist/node.d.cts +0 -1
  37. package/dist/{node.d.ts → node.d.mts} +1 -2
  38. package/dist/{node.js → node.mjs} +1 -2
  39. package/dist/route.cjs +7 -0
  40. package/dist/route.mjs +7 -0
  41. package/dist/runtime.cjs +42 -0
  42. package/dist/runtime.d.cts +21 -0
  43. package/dist/runtime.d.mts +21 -0
  44. package/dist/runtime.mjs +40 -0
  45. package/dist/schema/v0_21_0.cjs +1 -5
  46. package/dist/schema/{v0_21_0.js → v0_21_0.mjs} +1 -3
  47. package/dist/schema/v0_29_0.cjs +24 -0
  48. package/dist/schema/v0_29_0.mjs +24 -0
  49. package/dist/types/{index.d.ts → index.d.mts} +1 -1
  50. package/package.json +18 -18
  51. package/src/db/index.spec.ts +64 -29
  52. package/src/db/index.ts +55 -35
  53. package/src/db/ormCore.ts +438 -210
  54. package/src/db/ormUpdateCheck.bench.ts +261 -0
  55. package/src/db/pluginCore.ts +298 -49
  56. package/src/db/pluginUpdateCheck.bench.ts +250 -0
  57. package/src/db/types.ts +52 -27
  58. package/src/{handler-standalone-integration.spec.ts → handler-standalone.integration.spec.ts} +106 -0
  59. package/src/handler.spec.ts +156 -0
  60. package/src/handler.ts +296 -77
  61. package/src/internalRouter.ts +104 -0
  62. package/src/route.ts +7 -0
  63. package/src/runtime.spec.ts +277 -0
  64. package/src/runtime.ts +121 -0
  65. package/src/schema/v0_29_0.ts +26 -0
  66. package/dist/_virtual/rolldown_runtime.cjs +0 -25
  67. package/dist/adapters/drizzle.js +0 -3
  68. package/dist/adapters/kysely.js +0 -3
  69. package/dist/adapters/mongodb.js +0 -3
  70. package/dist/adapters/prisma.js +0 -3
  71. package/dist/db/index.d.ts +0 -27
  72. package/dist/db/index.js +0 -36
  73. package/dist/db/ormCore.d.ts +0 -26
  74. package/dist/db/ormCore.js +0 -273
  75. package/dist/db/pluginCore.js +0 -69
  76. package/dist/db/types.d.ts +0 -31
  77. package/dist/handler.d.ts +0 -37
  78. package/dist/handler.js +0 -146
  79. package/dist/index.d.ts +0 -5
  80. package/dist/index.js +0 -5
  81. /package/dist/adapters/{drizzle.d.ts → drizzle.d.mts} +0 -0
  82. /package/dist/adapters/{kysely.d.ts → kysely.d.mts} +0 -0
  83. /package/dist/adapters/{mongodb.d.ts → mongodb.d.mts} +0 -0
  84. /package/dist/adapters/{prisma.d.ts → prisma.d.mts} +0 -0
@@ -0,0 +1,261 @@
1
+ import { PGlite } from "@electric-sql/pglite";
2
+ import { Kysely } from "kysely";
3
+ import { PGliteDialect } from "kysely-pglite-dialect";
4
+ import { bench, describe } from "vitest";
5
+ import { filterCompatibleAppVersions } from "../../../../plugins/plugin-core/src";
6
+ import type {
7
+ AppVersionGetBundlesArgs,
8
+ Platform,
9
+ UpdateInfo,
10
+ } from "../../../core/src";
11
+ import {
12
+ DEFAULT_ROLLOUT_COHORT_COUNT,
13
+ isCohortEligibleForUpdate,
14
+ NIL_UUID,
15
+ } from "../../../core/src";
16
+ import { kyselyAdapter } from "../adapters/kysely";
17
+ import { createOrmDatabaseCore } from "./ormCore";
18
+
19
+ const BUNDLE_COUNT = 8_000;
20
+ const BENCH_APP_VERSION = "1.0.0";
21
+ const BENCH_PLATFORM = "ios" as const;
22
+ const BENCH_CHANNEL = "production";
23
+
24
+ interface BenchBundleRow {
25
+ id: string;
26
+ platform: Platform;
27
+ should_force_update: boolean;
28
+ enabled: boolean;
29
+ file_hash: string;
30
+ git_commit_hash: string;
31
+ message: string;
32
+ channel: string;
33
+ storage_uri: string;
34
+ target_app_version: string | null;
35
+ fingerprint_hash: string | null;
36
+ metadata: Record<string, unknown>;
37
+ rollout_cohort_count: number | null;
38
+ target_cohorts: string[] | null;
39
+ }
40
+
41
+ const createBundleRow = (index: number): BenchBundleRow => ({
42
+ id: `00000000-0000-0000-0000-${String(index).padStart(12, "0")}`,
43
+ platform: BENCH_PLATFORM,
44
+ should_force_update: false,
45
+ enabled: true,
46
+ file_hash: `hash-${index}`,
47
+ git_commit_hash: `commit-${index}`,
48
+ message: `bundle-${index}`,
49
+ channel: BENCH_CHANNEL,
50
+ storage_uri: `s3://bench/bundles/${index}.zip`,
51
+ target_app_version: "*",
52
+ fingerprint_hash: `fingerprint-${index % 10}`,
53
+ metadata: { index },
54
+ rollout_cohort_count: DEFAULT_ROLLOUT_COHORT_COUNT,
55
+ target_cohorts: null,
56
+ });
57
+
58
+ interface BenchDatabase {
59
+ bundles: BenchBundleRow;
60
+ }
61
+
62
+ const parseTargetCohorts = (value: unknown): string[] | null => {
63
+ if (!value) return null;
64
+ if (Array.isArray(value)) {
65
+ return value.filter((entry): entry is string => typeof entry === "string");
66
+ }
67
+ if (typeof value === "string") {
68
+ try {
69
+ const parsed = JSON.parse(value) as unknown;
70
+ if (Array.isArray(parsed)) {
71
+ return parsed.filter(
72
+ (entry): entry is string => typeof entry === "string",
73
+ );
74
+ }
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ return null;
80
+ };
81
+
82
+ const oldOrmCoreGetUpdateInfo = async (
83
+ db: Kysely<BenchDatabase>,
84
+ args: AppVersionGetBundlesArgs,
85
+ ): Promise<UpdateInfo | null> => {
86
+ const toUpdateInfo = (
87
+ row: {
88
+ id: string;
89
+ should_force_update: boolean;
90
+ message: string | null;
91
+ storage_uri: string | null;
92
+ file_hash: string;
93
+ },
94
+ status: "UPDATE" | "ROLLBACK",
95
+ ): UpdateInfo => ({
96
+ id: row.id,
97
+ shouldForceUpdate:
98
+ status === "ROLLBACK" ? true : Boolean(row.should_force_update),
99
+ message: row.message ?? null,
100
+ status,
101
+ storageUri: row.storage_uri ?? null,
102
+ fileHash: row.file_hash ?? null,
103
+ });
104
+
105
+ const isEligibleForUpdate = (
106
+ row: {
107
+ id: string;
108
+ rollout_cohort_count?: number | null;
109
+ target_cohorts?: unknown | null;
110
+ },
111
+ cohort: string | undefined,
112
+ ) => {
113
+ return isCohortEligibleForUpdate(
114
+ row.id,
115
+ cohort,
116
+ row.rollout_cohort_count ?? null,
117
+ parseTargetCohorts(row.target_cohorts),
118
+ );
119
+ };
120
+
121
+ const versionRows = await db
122
+ .selectFrom("bundles")
123
+ .select("target_app_version")
124
+ .where("platform", "=", args.platform)
125
+ .execute();
126
+
127
+ const allTargetVersions = Array.from(
128
+ new Set(
129
+ versionRows
130
+ .map((row) => row.target_app_version)
131
+ .filter((value): value is string => Boolean(value)),
132
+ ),
133
+ );
134
+
135
+ const compatibleVersions = filterCompatibleAppVersions(
136
+ allTargetVersions,
137
+ args.appVersion,
138
+ );
139
+
140
+ const baseRows =
141
+ compatibleVersions.length === 0
142
+ ? []
143
+ : await db
144
+ .selectFrom("bundles")
145
+ .select([
146
+ "id",
147
+ "should_force_update",
148
+ "message",
149
+ "storage_uri",
150
+ "file_hash",
151
+ "rollout_cohort_count",
152
+ "target_cohorts",
153
+ "target_app_version",
154
+ ])
155
+ .where("enabled", "=", true)
156
+ .where("platform", "=", args.platform)
157
+ .where("id", ">=", args.minBundleId ?? NIL_UUID)
158
+ .where("channel", "=", args.channel ?? BENCH_CHANNEL)
159
+ .where("target_app_version", "is not", null)
160
+ .execute();
161
+
162
+ const candidates = baseRows
163
+ .filter((row) =>
164
+ row.target_app_version
165
+ ? compatibleVersions.includes(row.target_app_version)
166
+ : false,
167
+ )
168
+ .sort((a, b) => b.id.localeCompare(a.id));
169
+
170
+ const updateCandidate =
171
+ candidates.find(
172
+ (row) =>
173
+ row.id.localeCompare(args.bundleId) > 0 &&
174
+ isEligibleForUpdate(row, args.cohort),
175
+ ) ?? null;
176
+
177
+ if (args.bundleId === NIL_UUID) {
178
+ return updateCandidate ? toUpdateInfo(updateCandidate, "UPDATE") : null;
179
+ }
180
+
181
+ const currentBundle = candidates.find((row) => row.id === args.bundleId);
182
+ const currentBundleEligible = currentBundle
183
+ ? isEligibleForUpdate(currentBundle, args.cohort)
184
+ : false;
185
+ const rollbackCandidate =
186
+ candidates.find((row) => row.id.localeCompare(args.bundleId) < 0) ?? null;
187
+
188
+ if (currentBundleEligible) {
189
+ return updateCandidate ? toUpdateInfo(updateCandidate, "UPDATE") : null;
190
+ }
191
+ if (updateCandidate) {
192
+ return toUpdateInfo(updateCandidate, "UPDATE");
193
+ }
194
+ if (rollbackCandidate) {
195
+ return toUpdateInfo(rollbackCandidate, "ROLLBACK");
196
+ }
197
+ if (args.minBundleId && args.bundleId.localeCompare(args.minBundleId) <= 0) {
198
+ return null;
199
+ }
200
+ return {
201
+ id: NIL_UUID,
202
+ message: null,
203
+ shouldForceUpdate: true,
204
+ status: "ROLLBACK",
205
+ storageUri: null,
206
+ fileHash: null,
207
+ };
208
+ };
209
+
210
+ const pg = new PGlite();
211
+ const kysely = new Kysely<BenchDatabase>({ dialect: new PGliteDialect(pg) });
212
+ const ormCore = createOrmDatabaseCore({
213
+ database: kyselyAdapter({
214
+ db: kysely,
215
+ provider: "postgresql",
216
+ }),
217
+ resolveFileUrl: async () => null,
218
+ });
219
+
220
+ const migration = await ormCore.createMigrator().migrateToLatest({
221
+ mode: "from-schema",
222
+ updateSettings: true,
223
+ });
224
+ await migration.execute();
225
+
226
+ const rows = Array.from({ length: BUNDLE_COUNT }, (_, index) =>
227
+ createBundleRow(index + 1),
228
+ );
229
+
230
+ for (let index = 0; index < rows.length; index += 500) {
231
+ await kysely
232
+ .insertInto("bundles")
233
+ .values(rows.slice(index, index + 500))
234
+ .execute();
235
+ }
236
+
237
+ describe("orm update check benchmark", () => {
238
+ const args: AppVersionGetBundlesArgs = {
239
+ _updateStrategy: "appVersion",
240
+ appVersion: BENCH_APP_VERSION,
241
+ bundleId: NIL_UUID,
242
+ platform: BENCH_PLATFORM,
243
+ channel: BENCH_CHANNEL,
244
+ };
245
+
246
+ bench(
247
+ "ormCore legacy appVersion scan",
248
+ async () => {
249
+ await oldOrmCoreGetUpdateInfo(kysely, args);
250
+ },
251
+ { warmupIterations: 3, iterations: 15 },
252
+ );
253
+
254
+ bench(
255
+ "ormCore current paged appVersion scan",
256
+ async () => {
257
+ await ormCore.api.getUpdateInfo(args);
258
+ },
259
+ { warmupIterations: 3, iterations: 15 },
260
+ );
261
+ });
@@ -1,104 +1,353 @@
1
1
  import type {
2
2
  AppUpdateInfo,
3
+ AppVersionGetBundlesArgs,
3
4
  Bundle,
5
+ FingerprintGetBundlesArgs,
4
6
  GetBundlesArgs,
7
+ Platform,
5
8
  UpdateInfo,
6
9
  } from "@hot-updater/core";
7
- import { getUpdateInfo as getUpdateInfoJS } from "@hot-updater/js";
8
- import type { DatabasePlugin } from "@hot-updater/plugin-core";
10
+ import { isCohortEligibleForUpdate, NIL_UUID } from "@hot-updater/core";
11
+ import {
12
+ type DatabaseBundleQueryOptions,
13
+ type DatabaseBundleQueryOrder,
14
+ type DatabaseBundleQueryWhere,
15
+ type DatabasePlugin,
16
+ type HotUpdaterContext,
17
+ semverSatisfies,
18
+ } from "@hot-updater/plugin-core";
9
19
  import type { DatabaseAPI } from "./types";
10
20
 
11
- export function createPluginDatabaseCore(
12
- plugin: DatabasePlugin,
13
- resolveFileUrl: (storageUri: string | null) => Promise<string | null>,
21
+ const PAGE_SIZE = 100;
22
+
23
+ const DESC_ORDER = { field: "id", direction: "desc" } as const;
24
+
25
+ const bundleMatchesQueryWhere = (
26
+ bundle: Bundle,
27
+ where: DatabaseBundleQueryWhere | undefined,
28
+ ) => {
29
+ if (!where) return true;
30
+ if (where.channel !== undefined && bundle.channel !== where.channel)
31
+ return false;
32
+ if (where.platform !== undefined && bundle.platform !== where.platform)
33
+ return false;
34
+ if (where.enabled !== undefined && bundle.enabled !== where.enabled)
35
+ return false;
36
+ if (where.id?.eq !== undefined && bundle.id !== where.id.eq) return false;
37
+ if (where.id?.gt !== undefined && bundle.id.localeCompare(where.id.gt) <= 0)
38
+ return false;
39
+ if (where.id?.gte !== undefined && bundle.id.localeCompare(where.id.gte) < 0)
40
+ return false;
41
+ if (where.id?.lt !== undefined && bundle.id.localeCompare(where.id.lt) >= 0)
42
+ return false;
43
+ if (where.id?.lte !== undefined && bundle.id.localeCompare(where.id.lte) > 0)
44
+ return false;
45
+ if (where.id?.in && !where.id.in.includes(bundle.id)) return false;
46
+ if (where.targetAppVersionNotNull && bundle.targetAppVersion === null) {
47
+ return false;
48
+ }
49
+ if (
50
+ where.targetAppVersion !== undefined &&
51
+ bundle.targetAppVersion !== where.targetAppVersion
52
+ ) {
53
+ return false;
54
+ }
55
+ if (
56
+ where.targetAppVersionIn &&
57
+ !where.targetAppVersionIn.includes(bundle.targetAppVersion ?? "")
58
+ ) {
59
+ return false;
60
+ }
61
+ if (
62
+ where.fingerprintHash !== undefined &&
63
+ bundle.fingerprintHash !== where.fingerprintHash
64
+ ) {
65
+ return false;
66
+ }
67
+ return true;
68
+ };
69
+
70
+ const sortBundles = (
71
+ bundles: Bundle[],
72
+ orderBy: DatabaseBundleQueryOrder | undefined,
73
+ ) => {
74
+ const direction = orderBy?.direction ?? "desc";
75
+ return bundles.slice().sort((a, b) => {
76
+ const result = a.id.localeCompare(b.id);
77
+ return direction === "asc" ? result : -result;
78
+ });
79
+ };
80
+
81
+ const makeResponse = (
82
+ bundle: Bundle,
83
+ status: "UPDATE" | "ROLLBACK",
84
+ ): UpdateInfo => ({
85
+ id: bundle.id,
86
+ message: bundle.message,
87
+ shouldForceUpdate: status === "ROLLBACK" ? true : bundle.shouldForceUpdate,
88
+ status,
89
+ storageUri: bundle.storageUri,
90
+ fileHash: bundle.fileHash,
91
+ });
92
+
93
+ const INIT_BUNDLE_ROLLBACK_UPDATE_INFO: UpdateInfo = {
94
+ message: null,
95
+ id: NIL_UUID,
96
+ shouldForceUpdate: true,
97
+ status: "ROLLBACK",
98
+ storageUri: null,
99
+ fileHash: null,
100
+ };
101
+
102
+ export function createPluginDatabaseCore<TContext = unknown>(
103
+ plugin: DatabasePlugin<TContext>,
104
+ resolveFileUrl: (
105
+ storageUri: string | null,
106
+ context?: HotUpdaterContext<TContext>,
107
+ ) => Promise<string | null>,
14
108
  ): {
15
- api: DatabaseAPI;
109
+ api: DatabaseAPI<TContext>;
16
110
  adapterName: string;
17
111
  createMigrator: () => never;
18
112
  generateSchema: () => never;
19
113
  } {
20
- const api: DatabaseAPI = {
21
- async getBundleById(id: string): Promise<Bundle | null> {
22
- return plugin.getBundleById(id);
23
- },
114
+ const getSortedBundlePage = async (
115
+ options: DatabaseBundleQueryOptions,
116
+ context?: HotUpdaterContext<TContext>,
117
+ ): Promise<Awaited<ReturnType<DatabasePlugin<TContext>["getBundles"]>>> => {
118
+ const result = await plugin.getBundles(
119
+ {
120
+ ...options,
121
+ orderBy: options.orderBy ?? DESC_ORDER,
122
+ },
123
+ context,
124
+ );
125
+
126
+ return {
127
+ ...result,
128
+ data: sortBundles(result.data, options.orderBy ?? DESC_ORDER),
129
+ };
130
+ };
131
+
132
+ const isEligibleForUpdate = (
133
+ bundle: Bundle,
134
+ cohort: string | undefined,
135
+ ): boolean => {
136
+ return isCohortEligibleForUpdate(
137
+ bundle.id,
138
+ cohort,
139
+ bundle.rolloutCohortCount,
140
+ bundle.targetCohorts,
141
+ );
142
+ };
143
+
144
+ const findUpdateInfoByScanning = async ({
145
+ args,
146
+ queryWhere,
147
+ isCandidate,
148
+ context,
149
+ }: {
150
+ args: AppVersionGetBundlesArgs | FingerprintGetBundlesArgs;
151
+ queryWhere: DatabaseBundleQueryWhere;
152
+ isCandidate: (bundle: Bundle) => boolean;
153
+ context?: HotUpdaterContext<TContext>;
154
+ }): Promise<UpdateInfo | null> => {
155
+ let offset = 0;
156
+
157
+ while (true) {
158
+ const { data, pagination } = await getSortedBundlePage(
159
+ {
160
+ where: queryWhere,
161
+ limit: PAGE_SIZE,
162
+ offset,
163
+ orderBy: DESC_ORDER,
164
+ },
165
+ context,
166
+ );
167
+
168
+ for (const bundle of data) {
169
+ if (
170
+ !bundleMatchesQueryWhere(bundle, queryWhere) ||
171
+ !isCandidate(bundle)
172
+ ) {
173
+ continue;
174
+ }
175
+
176
+ if (args.bundleId === NIL_UUID) {
177
+ if (isEligibleForUpdate(bundle, args.cohort)) {
178
+ return makeResponse(bundle, "UPDATE");
179
+ }
180
+ continue;
181
+ }
182
+
183
+ const compareResult = bundle.id.localeCompare(args.bundleId);
24
184
 
25
- async getUpdateInfo(args: GetBundlesArgs): Promise<UpdateInfo | null> {
26
- const where: { channel?: string; platform?: string } = {};
185
+ if (compareResult > 0) {
186
+ if (isEligibleForUpdate(bundle, args.cohort)) {
187
+ return makeResponse(bundle, "UPDATE");
188
+ }
189
+ continue;
190
+ }
27
191
 
28
- if ("platform" in args && args.platform) {
29
- where.platform = args.platform;
192
+ if (compareResult === 0) {
193
+ if (isEligibleForUpdate(bundle, args.cohort)) {
194
+ return null;
195
+ }
196
+ continue;
197
+ }
198
+
199
+ return makeResponse(bundle, "ROLLBACK");
200
+ }
201
+
202
+ if (!pagination.hasNextPage) {
203
+ break;
30
204
  }
31
205
 
32
- const channel =
33
- "channel" in args && args.channel ? args.channel : "production";
34
- where.channel = channel;
206
+ offset += PAGE_SIZE;
207
+ }
208
+
209
+ if (args.bundleId === NIL_UUID) {
210
+ return null;
211
+ }
212
+
213
+ if (
214
+ args.minBundleId &&
215
+ args.bundleId.localeCompare(args.minBundleId) <= 0
216
+ ) {
217
+ return null;
218
+ }
219
+
220
+ return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
221
+ };
222
+
223
+ const getBaseWhere = ({
224
+ platform,
225
+ channel,
226
+ minBundleId,
227
+ }: {
228
+ platform: Platform;
229
+ channel: string;
230
+ minBundleId: string;
231
+ }): DatabaseBundleQueryWhere => ({
232
+ platform,
233
+ channel,
234
+ enabled: true,
235
+ id: {
236
+ gte: minBundleId,
237
+ },
238
+ });
239
+
240
+ const api: DatabaseAPI<TContext> = {
241
+ async getBundleById(
242
+ id: string,
243
+ context?: HotUpdaterContext<TContext>,
244
+ ): Promise<Bundle | null> {
245
+ return plugin.getBundleById(id, context);
246
+ },
35
247
 
36
- const { pagination } = await plugin.getBundles({
37
- where,
38
- limit: 1,
39
- offset: 0,
248
+ async getUpdateInfo(
249
+ args: GetBundlesArgs,
250
+ context?: HotUpdaterContext<TContext>,
251
+ ): Promise<UpdateInfo | null> {
252
+ const channel = args.channel ?? "production";
253
+ const minBundleId = args.minBundleId ?? NIL_UUID;
254
+ const baseWhere = getBaseWhere({
255
+ platform: args.platform,
256
+ channel,
257
+ minBundleId,
40
258
  });
41
259
 
42
- if (pagination.total === 0) {
43
- return getUpdateInfoJS([], args);
260
+ if (args._updateStrategy === "fingerprint") {
261
+ return findUpdateInfoByScanning({
262
+ args,
263
+ queryWhere: {
264
+ ...baseWhere,
265
+ fingerprintHash: args.fingerprintHash,
266
+ },
267
+ context,
268
+ isCandidate: (bundle) => {
269
+ return (
270
+ bundle.enabled &&
271
+ bundle.platform === args.platform &&
272
+ bundle.channel === channel &&
273
+ bundle.id.localeCompare(minBundleId) >= 0 &&
274
+ bundle.fingerprintHash === args.fingerprintHash
275
+ );
276
+ },
277
+ });
44
278
  }
45
279
 
46
- const { data } = await plugin.getBundles({
47
- where,
48
- limit: pagination.total,
49
- offset: 0,
280
+ return findUpdateInfoByScanning({
281
+ args,
282
+ queryWhere: {
283
+ ...baseWhere,
284
+ },
285
+ context,
286
+ isCandidate: (bundle) => {
287
+ return (
288
+ bundle.enabled &&
289
+ bundle.platform === args.platform &&
290
+ bundle.channel === channel &&
291
+ bundle.id.localeCompare(minBundleId) >= 0 &&
292
+ !!bundle.targetAppVersion &&
293
+ semverSatisfies(bundle.targetAppVersion, args.appVersion)
294
+ );
295
+ },
50
296
  });
51
-
52
- const bundles = data;
53
- return getUpdateInfoJS(bundles, args);
54
297
  },
55
298
 
56
299
  async getAppUpdateInfo(
57
300
  args: GetBundlesArgs,
301
+ context?: HotUpdaterContext<TContext>,
58
302
  ): Promise<AppUpdateInfo | null> {
59
- const info = await this.getUpdateInfo(args);
303
+ const info = await this.getUpdateInfo(args, context);
60
304
  if (!info) {
61
305
  return null;
62
306
  }
63
307
  const { storageUri, ...rest } = info as UpdateInfo & {
64
308
  storageUri: string | null;
65
309
  };
66
- const fileUrl = await resolveFileUrl(storageUri ?? null);
310
+ const fileUrl = await resolveFileUrl(storageUri ?? null, context);
67
311
  return { ...rest, fileUrl };
68
312
  },
69
313
 
70
- async getChannels(): Promise<string[]> {
71
- return plugin.getChannels();
314
+ async getChannels(
315
+ context?: HotUpdaterContext<TContext>,
316
+ ): Promise<string[]> {
317
+ return plugin.getChannels(context);
72
318
  },
73
319
 
74
- async getBundles(options: {
75
- where?: { channel?: string; platform?: string };
76
- limit: number;
77
- offset: number;
78
- }) {
79
- return plugin.getBundles(options);
320
+ async getBundles(options, context?: HotUpdaterContext<TContext>) {
321
+ return plugin.getBundles(options, context);
80
322
  },
81
323
 
82
- async insertBundle(bundle: Bundle): Promise<void> {
83
- await plugin.appendBundle(bundle);
84
- await plugin.commitBundle();
324
+ async insertBundle(
325
+ bundle: Bundle,
326
+ context?: HotUpdaterContext<TContext>,
327
+ ): Promise<void> {
328
+ await plugin.appendBundle(bundle, context);
329
+ await plugin.commitBundle(context);
85
330
  },
86
331
 
87
332
  async updateBundleById(
88
333
  bundleId: string,
89
334
  newBundle: Partial<Bundle>,
335
+ context?: HotUpdaterContext<TContext>,
90
336
  ): Promise<void> {
91
- await plugin.updateBundle(bundleId, newBundle);
92
- await plugin.commitBundle();
337
+ await plugin.updateBundle(bundleId, newBundle, context);
338
+ await plugin.commitBundle(context);
93
339
  },
94
340
 
95
- async deleteBundleById(bundleId: string): Promise<void> {
96
- const bundle = await plugin.getBundleById(bundleId);
341
+ async deleteBundleById(
342
+ bundleId: string,
343
+ context?: HotUpdaterContext<TContext>,
344
+ ): Promise<void> {
345
+ const bundle = await plugin.getBundleById(bundleId, context);
97
346
  if (!bundle) {
98
347
  return;
99
348
  }
100
- await plugin.deleteBundle(bundle);
101
- await plugin.commitBundle();
349
+ await plugin.deleteBundle(bundle, context);
350
+ await plugin.commitBundle(context);
102
351
  },
103
352
  };
104
353