@hot-updater/server 0.28.0 → 0.29.0

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,386 @@
1
+ import { calculatePagination } from "../calculatePagination.mjs";
2
+ import { v0_21_0 } from "../schema/v0_21_0.mjs";
3
+ import { v0_29_0 } from "../schema/v0_29_0.mjs";
4
+ import { DEFAULT_ROLLOUT_COHORT_COUNT, NIL_UUID, isCohortEligibleForUpdate } from "@hot-updater/core";
5
+ import { semverSatisfies } from "@hot-updater/plugin-core";
6
+ import { fumadb } from "fumadb";
7
+ //#region src/db/ormCore.ts
8
+ const parseTargetCohorts = (value) => {
9
+ if (!value) return null;
10
+ if (Array.isArray(value)) return value.filter((v) => typeof v === "string");
11
+ if (typeof value === "string") try {
12
+ const parsed = JSON.parse(value);
13
+ if (Array.isArray(parsed)) return parsed.filter((v) => typeof v === "string");
14
+ } catch {
15
+ return null;
16
+ }
17
+ return null;
18
+ };
19
+ const schemas = [v0_21_0, v0_29_0];
20
+ const getLastItem = (items) => items.at(-1);
21
+ const HotUpdaterDB = fumadb({
22
+ namespace: "hot_updater",
23
+ schemas
24
+ });
25
+ function createOrmDatabaseCore({ database, resolveFileUrl }) {
26
+ const client = HotUpdaterDB.client(database);
27
+ const UPDATE_CHECK_PAGE_SIZE = 100;
28
+ const isMongoAdapter = client.adapter.name.toLowerCase().includes("mongodb");
29
+ const ensureORM = async () => {
30
+ const lastSchemaVersion = getLastItem(schemas).version;
31
+ try {
32
+ const currentVersion = await client.createMigrator().getVersion();
33
+ if (currentVersion === void 0) throw new Error("Database is not initialized. Please run 'npx hot-updater migrate' to set up the database schema.");
34
+ if (currentVersion !== lastSchemaVersion) throw new Error(`Database schema version mismatch. Expected version ${lastSchemaVersion}, but database is on version ${currentVersion}. Please run 'npx hot-updater migrate' to update your database schema.`);
35
+ return client.orm(lastSchemaVersion);
36
+ } catch (error) {
37
+ if (error instanceof Error && error.message.includes("doesn't support migration")) return client.orm(lastSchemaVersion);
38
+ throw error;
39
+ }
40
+ };
41
+ const buildBundleWhere = (where) => (b) => {
42
+ if (where?.id?.in && where.id.in.length === 0) return false;
43
+ if (where?.targetAppVersionIn && where.targetAppVersionIn.length === 0) return false;
44
+ const conditions = [];
45
+ if (where?.channel !== void 0) conditions.push(b("channel", "=", where.channel));
46
+ if (where?.platform !== void 0) conditions.push(b("platform", "=", where.platform));
47
+ if (where?.enabled !== void 0) conditions.push(b("enabled", "=", where.enabled));
48
+ if (where?.id?.eq !== void 0) conditions.push(b("id", "=", where.id.eq));
49
+ if (where?.id?.gt !== void 0) conditions.push(b("id", ">", where.id.gt));
50
+ if (where?.id?.gte !== void 0) conditions.push(b("id", ">=", where.id.gte));
51
+ if (where?.id?.lt !== void 0) conditions.push(b("id", "<", where.id.lt));
52
+ if (where?.id?.lte !== void 0) conditions.push(b("id", "<=", where.id.lte));
53
+ if (where?.id?.in) conditions.push(b("id", "in", where.id.in));
54
+ if (where?.targetAppVersionNotNull) conditions.push(b.isNotNull("target_app_version"));
55
+ if (where?.targetAppVersion !== void 0) conditions.push(where.targetAppVersion === null ? b.isNull("target_app_version") : b("target_app_version", "=", where.targetAppVersion));
56
+ if (where?.targetAppVersionIn) conditions.push(b("target_app_version", "in", where.targetAppVersionIn));
57
+ if (where?.fingerprintHash !== void 0) conditions.push(where.fingerprintHash === null ? b.isNull("fingerprint_hash") : b("fingerprint_hash", "=", where.fingerprintHash));
58
+ return conditions.length > 0 ? b.and(...conditions) : true;
59
+ };
60
+ return {
61
+ api: {
62
+ async getBundleById(id) {
63
+ const result = await (await ensureORM()).findFirst("bundles", {
64
+ select: [
65
+ "id",
66
+ "platform",
67
+ "should_force_update",
68
+ "enabled",
69
+ "file_hash",
70
+ "git_commit_hash",
71
+ "message",
72
+ "channel",
73
+ "storage_uri",
74
+ "target_app_version",
75
+ "fingerprint_hash",
76
+ "metadata",
77
+ "rollout_cohort_count",
78
+ "target_cohorts"
79
+ ],
80
+ where: (b) => b("id", "=", id)
81
+ });
82
+ if (!result) return null;
83
+ return {
84
+ id: result.id,
85
+ platform: result.platform,
86
+ shouldForceUpdate: Boolean(result.should_force_update),
87
+ enabled: Boolean(result.enabled),
88
+ fileHash: result.file_hash,
89
+ gitCommitHash: result.git_commit_hash ?? null,
90
+ message: result.message ?? null,
91
+ channel: result.channel,
92
+ storageUri: result.storage_uri,
93
+ targetAppVersion: result.target_app_version ?? null,
94
+ fingerprintHash: result.fingerprint_hash ?? null,
95
+ rolloutCohortCount: result.rollout_cohort_count ?? DEFAULT_ROLLOUT_COHORT_COUNT,
96
+ targetCohorts: parseTargetCohorts(result.target_cohorts)
97
+ };
98
+ },
99
+ async getUpdateInfo(args) {
100
+ const orm = await ensureORM();
101
+ const toUpdateInfo = (row, status) => ({
102
+ id: row.id,
103
+ shouldForceUpdate: status === "ROLLBACK" ? true : Boolean(row.should_force_update),
104
+ message: row.message ?? null,
105
+ status,
106
+ storageUri: row.storage_uri ?? null,
107
+ fileHash: row.file_hash ?? null
108
+ });
109
+ const INIT_BUNDLE_ROLLBACK_UPDATE_INFO = {
110
+ id: NIL_UUID,
111
+ message: null,
112
+ shouldForceUpdate: true,
113
+ status: "ROLLBACK",
114
+ storageUri: null,
115
+ fileHash: null
116
+ };
117
+ const isEligibleForUpdate = (row, cohort) => {
118
+ return isCohortEligibleForUpdate(row.id, cohort, row.rollout_cohort_count ?? null, parseTargetCohorts(row.target_cohorts));
119
+ };
120
+ const findUpdateInfoByScanning = async ({ args, where, isCandidate }) => {
121
+ if (isMongoAdapter) {
122
+ const rows = await orm.findMany("bundles", {
123
+ select: [
124
+ "id",
125
+ "should_force_update",
126
+ "message",
127
+ "storage_uri",
128
+ "file_hash",
129
+ "rollout_cohort_count",
130
+ "target_cohorts",
131
+ "target_app_version",
132
+ "fingerprint_hash"
133
+ ],
134
+ where: buildBundleWhere(where)
135
+ });
136
+ rows.sort((a, b) => b.id.localeCompare(a.id));
137
+ for (const row of rows) {
138
+ if (!isCandidate(row)) continue;
139
+ if (args.bundleId === NIL_UUID) {
140
+ if (isEligibleForUpdate(row, args.cohort)) return toUpdateInfo(row, "UPDATE");
141
+ continue;
142
+ }
143
+ const compareResult = row.id.localeCompare(args.bundleId);
144
+ if (compareResult > 0) {
145
+ if (isEligibleForUpdate(row, args.cohort)) return toUpdateInfo(row, "UPDATE");
146
+ continue;
147
+ }
148
+ if (compareResult === 0) {
149
+ if (isEligibleForUpdate(row, args.cohort)) return null;
150
+ continue;
151
+ }
152
+ return toUpdateInfo(row, "ROLLBACK");
153
+ }
154
+ if (args.bundleId === NIL_UUID) return null;
155
+ if (args.minBundleId && args.bundleId.localeCompare(args.minBundleId) <= 0) return null;
156
+ return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
157
+ }
158
+ let offset = 0;
159
+ while (true) {
160
+ const rows = await orm.findMany("bundles", {
161
+ select: [
162
+ "id",
163
+ "should_force_update",
164
+ "message",
165
+ "storage_uri",
166
+ "file_hash",
167
+ "rollout_cohort_count",
168
+ "target_cohorts",
169
+ "target_app_version",
170
+ "fingerprint_hash"
171
+ ],
172
+ where: buildBundleWhere(where),
173
+ orderBy: [["id", "desc"]],
174
+ limit: UPDATE_CHECK_PAGE_SIZE,
175
+ offset
176
+ });
177
+ for (const row of rows) {
178
+ if (!isCandidate(row)) continue;
179
+ if (args.bundleId === NIL_UUID) {
180
+ if (isEligibleForUpdate(row, args.cohort)) return toUpdateInfo(row, "UPDATE");
181
+ continue;
182
+ }
183
+ const compareResult = row.id.localeCompare(args.bundleId);
184
+ if (compareResult > 0) {
185
+ if (isEligibleForUpdate(row, args.cohort)) return toUpdateInfo(row, "UPDATE");
186
+ continue;
187
+ }
188
+ if (compareResult === 0) {
189
+ if (isEligibleForUpdate(row, args.cohort)) return null;
190
+ continue;
191
+ }
192
+ return toUpdateInfo(row, "ROLLBACK");
193
+ }
194
+ if (rows.length < UPDATE_CHECK_PAGE_SIZE) break;
195
+ offset += UPDATE_CHECK_PAGE_SIZE;
196
+ }
197
+ if (args.bundleId === NIL_UUID) return null;
198
+ if (args.minBundleId && args.bundleId.localeCompare(args.minBundleId) <= 0) return null;
199
+ return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
200
+ };
201
+ const appVersionStrategy = async ({ platform, appVersion, bundleId, minBundleId = NIL_UUID, channel = "production", cohort }) => {
202
+ return findUpdateInfoByScanning({
203
+ args: {
204
+ _updateStrategy: "appVersion",
205
+ platform,
206
+ appVersion,
207
+ bundleId,
208
+ minBundleId,
209
+ channel,
210
+ cohort
211
+ },
212
+ where: {
213
+ enabled: true,
214
+ platform,
215
+ channel,
216
+ id: { gte: minBundleId },
217
+ targetAppVersionNotNull: true
218
+ },
219
+ isCandidate: (row) => !!row.target_app_version && semverSatisfies(row.target_app_version, appVersion)
220
+ });
221
+ };
222
+ const fingerprintStrategy = async ({ platform, fingerprintHash, bundleId, minBundleId = NIL_UUID, channel = "production", cohort }) => {
223
+ return findUpdateInfoByScanning({
224
+ args: {
225
+ _updateStrategy: "fingerprint",
226
+ platform,
227
+ fingerprintHash,
228
+ bundleId,
229
+ minBundleId,
230
+ channel,
231
+ cohort
232
+ },
233
+ where: {
234
+ enabled: true,
235
+ platform,
236
+ channel,
237
+ id: { gte: minBundleId },
238
+ fingerprintHash
239
+ },
240
+ isCandidate: (row) => row.fingerprint_hash === fingerprintHash
241
+ });
242
+ };
243
+ if (args._updateStrategy === "appVersion") return appVersionStrategy(args);
244
+ if (args._updateStrategy === "fingerprint") return fingerprintStrategy(args);
245
+ return null;
246
+ },
247
+ async getAppUpdateInfo(args, context) {
248
+ const info = await this.getUpdateInfo(args);
249
+ if (!info) return null;
250
+ const { storageUri, ...rest } = info;
251
+ const fileUrl = await resolveFileUrl(storageUri ?? null, context);
252
+ return {
253
+ ...rest,
254
+ fileUrl
255
+ };
256
+ },
257
+ async getChannels() {
258
+ const rows = await (await ensureORM()).findMany("bundles", {
259
+ select: ["channel"],
260
+ orderBy: [["channel", "asc"]]
261
+ });
262
+ const set = new Set(rows?.map((r) => r.channel) ?? []);
263
+ return Array.from(set);
264
+ },
265
+ async getBundles(options) {
266
+ const orm = await ensureORM();
267
+ const { where, limit, offset, orderBy } = options;
268
+ const total = await orm.count("bundles", { where: buildBundleWhere(where) });
269
+ const selectedColumns = [
270
+ "id",
271
+ "platform",
272
+ "should_force_update",
273
+ "enabled",
274
+ "file_hash",
275
+ "git_commit_hash",
276
+ "message",
277
+ "channel",
278
+ "storage_uri",
279
+ "target_app_version",
280
+ "fingerprint_hash",
281
+ "metadata",
282
+ "rollout_cohort_count",
283
+ "target_cohorts"
284
+ ];
285
+ return {
286
+ data: (isMongoAdapter ? (await orm.findMany("bundles", {
287
+ select: selectedColumns,
288
+ where: buildBundleWhere(where)
289
+ })).sort((a, b) => {
290
+ const direction = orderBy?.direction ?? "desc";
291
+ const result = a.id.localeCompare(b.id);
292
+ return direction === "asc" ? result : -result;
293
+ }).slice(offset, offset + limit) : await orm.findMany("bundles", {
294
+ select: selectedColumns,
295
+ where: buildBundleWhere(where),
296
+ orderBy: [[orderBy?.field ?? "id", orderBy?.direction ?? "desc"]],
297
+ limit,
298
+ offset
299
+ })).map((r) => ({
300
+ id: r.id,
301
+ platform: r.platform,
302
+ shouldForceUpdate: Boolean(r.should_force_update),
303
+ enabled: Boolean(r.enabled),
304
+ fileHash: r.file_hash,
305
+ gitCommitHash: r.git_commit_hash ?? null,
306
+ message: r.message ?? null,
307
+ channel: r.channel,
308
+ storageUri: r.storage_uri,
309
+ targetAppVersion: r.target_app_version ?? null,
310
+ fingerprintHash: r.fingerprint_hash ?? null,
311
+ rolloutCohortCount: r.rollout_cohort_count ?? DEFAULT_ROLLOUT_COHORT_COUNT,
312
+ targetCohorts: parseTargetCohorts(r.target_cohorts)
313
+ })),
314
+ pagination: calculatePagination(total, {
315
+ limit,
316
+ offset
317
+ })
318
+ };
319
+ },
320
+ async insertBundle(bundle) {
321
+ const orm = await ensureORM();
322
+ const values = {
323
+ id: bundle.id,
324
+ platform: bundle.platform,
325
+ should_force_update: bundle.shouldForceUpdate,
326
+ enabled: bundle.enabled,
327
+ file_hash: bundle.fileHash,
328
+ git_commit_hash: bundle.gitCommitHash,
329
+ message: bundle.message,
330
+ channel: bundle.channel,
331
+ storage_uri: bundle.storageUri,
332
+ target_app_version: bundle.targetAppVersion,
333
+ fingerprint_hash: bundle.fingerprintHash,
334
+ metadata: bundle.metadata ?? {},
335
+ rollout_cohort_count: bundle.rolloutCohortCount ?? DEFAULT_ROLLOUT_COHORT_COUNT,
336
+ target_cohorts: bundle.targetCohorts ?? null
337
+ };
338
+ const { id, ...updateValues } = values;
339
+ await orm.upsert("bundles", {
340
+ where: (b) => b("id", "=", id),
341
+ create: values,
342
+ update: updateValues
343
+ });
344
+ },
345
+ async updateBundleById(bundleId, newBundle) {
346
+ const orm = await ensureORM();
347
+ const current = await this.getBundleById(bundleId);
348
+ if (!current) throw new Error("targetBundleId not found");
349
+ const merged = {
350
+ ...current,
351
+ ...newBundle
352
+ };
353
+ const values = {
354
+ id: merged.id,
355
+ platform: merged.platform,
356
+ should_force_update: merged.shouldForceUpdate,
357
+ enabled: merged.enabled,
358
+ file_hash: merged.fileHash,
359
+ git_commit_hash: merged.gitCommitHash,
360
+ message: merged.message,
361
+ channel: merged.channel,
362
+ storage_uri: merged.storageUri,
363
+ target_app_version: merged.targetAppVersion,
364
+ fingerprint_hash: merged.fingerprintHash,
365
+ metadata: merged.metadata ?? {},
366
+ rollout_cohort_count: merged.rolloutCohortCount ?? DEFAULT_ROLLOUT_COHORT_COUNT,
367
+ target_cohorts: merged.targetCohorts ?? null
368
+ };
369
+ const { id: id2, ...updateValues2 } = values;
370
+ await orm.upsert("bundles", {
371
+ where: (b) => b("id", "=", id2),
372
+ create: values,
373
+ update: updateValues2
374
+ });
375
+ },
376
+ async deleteBundleById(bundleId) {
377
+ await (await ensureORM()).deleteMany("bundles", { where: (b) => b("id", "=", bundleId) });
378
+ }
379
+ },
380
+ adapterName: client.adapter.name,
381
+ createMigrator: () => client.createMigrator(),
382
+ generateSchema: client.generateSchema
383
+ };
384
+ }
385
+ //#endregion
386
+ export { HotUpdaterDB, createOrmDatabaseCore };
@@ -1,60 +1,166 @@
1
- const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
2
- let __hot_updater_js = require("@hot-updater/js");
3
- __hot_updater_js = require_rolldown_runtime.__toESM(__hot_updater_js);
4
-
1
+ let _hot_updater_core = require("@hot-updater/core");
2
+ let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
5
3
  //#region src/db/pluginCore.ts
4
+ const PAGE_SIZE = 100;
5
+ const DESC_ORDER = {
6
+ field: "id",
7
+ direction: "desc"
8
+ };
9
+ const bundleMatchesQueryWhere = (bundle, where) => {
10
+ if (!where) return true;
11
+ if (where.channel !== void 0 && bundle.channel !== where.channel) return false;
12
+ if (where.platform !== void 0 && bundle.platform !== where.platform) return false;
13
+ if (where.enabled !== void 0 && bundle.enabled !== where.enabled) return false;
14
+ if (where.id?.eq !== void 0 && bundle.id !== where.id.eq) return false;
15
+ if (where.id?.gt !== void 0 && bundle.id.localeCompare(where.id.gt) <= 0) return false;
16
+ if (where.id?.gte !== void 0 && bundle.id.localeCompare(where.id.gte) < 0) return false;
17
+ if (where.id?.lt !== void 0 && bundle.id.localeCompare(where.id.lt) >= 0) return false;
18
+ if (where.id?.lte !== void 0 && bundle.id.localeCompare(where.id.lte) > 0) return false;
19
+ if (where.id?.in && !where.id.in.includes(bundle.id)) return false;
20
+ if (where.targetAppVersionNotNull && bundle.targetAppVersion === null) return false;
21
+ if (where.targetAppVersion !== void 0 && bundle.targetAppVersion !== where.targetAppVersion) return false;
22
+ if (where.targetAppVersionIn && !where.targetAppVersionIn.includes(bundle.targetAppVersion ?? "")) return false;
23
+ if (where.fingerprintHash !== void 0 && bundle.fingerprintHash !== where.fingerprintHash) return false;
24
+ return true;
25
+ };
26
+ const sortBundles = (bundles, orderBy) => {
27
+ const direction = orderBy?.direction ?? "desc";
28
+ return bundles.slice().sort((a, b) => {
29
+ const result = a.id.localeCompare(b.id);
30
+ return direction === "asc" ? result : -result;
31
+ });
32
+ };
33
+ const makeResponse = (bundle, status) => ({
34
+ id: bundle.id,
35
+ message: bundle.message,
36
+ shouldForceUpdate: status === "ROLLBACK" ? true : bundle.shouldForceUpdate,
37
+ status,
38
+ storageUri: bundle.storageUri,
39
+ fileHash: bundle.fileHash
40
+ });
41
+ const INIT_BUNDLE_ROLLBACK_UPDATE_INFO = {
42
+ message: null,
43
+ id: _hot_updater_core.NIL_UUID,
44
+ shouldForceUpdate: true,
45
+ status: "ROLLBACK",
46
+ storageUri: null,
47
+ fileHash: null
48
+ };
6
49
  function createPluginDatabaseCore(plugin, resolveFileUrl) {
50
+ const getSortedBundlePage = async (options, context) => {
51
+ const result = await plugin.getBundles({
52
+ ...options,
53
+ orderBy: options.orderBy ?? DESC_ORDER
54
+ }, context);
55
+ return {
56
+ ...result,
57
+ data: sortBundles(result.data, options.orderBy ?? DESC_ORDER)
58
+ };
59
+ };
60
+ const isEligibleForUpdate = (bundle, cohort) => {
61
+ return (0, _hot_updater_core.isCohortEligibleForUpdate)(bundle.id, cohort, bundle.rolloutCohortCount, bundle.targetCohorts);
62
+ };
63
+ const findUpdateInfoByScanning = async ({ args, queryWhere, isCandidate, context }) => {
64
+ let offset = 0;
65
+ while (true) {
66
+ const { data, pagination } = await getSortedBundlePage({
67
+ where: queryWhere,
68
+ limit: PAGE_SIZE,
69
+ offset,
70
+ orderBy: DESC_ORDER
71
+ }, context);
72
+ for (const bundle of data) {
73
+ if (!bundleMatchesQueryWhere(bundle, queryWhere) || !isCandidate(bundle)) continue;
74
+ if (args.bundleId === _hot_updater_core.NIL_UUID) {
75
+ if (isEligibleForUpdate(bundle, args.cohort)) return makeResponse(bundle, "UPDATE");
76
+ continue;
77
+ }
78
+ const compareResult = bundle.id.localeCompare(args.bundleId);
79
+ if (compareResult > 0) {
80
+ if (isEligibleForUpdate(bundle, args.cohort)) return makeResponse(bundle, "UPDATE");
81
+ continue;
82
+ }
83
+ if (compareResult === 0) {
84
+ if (isEligibleForUpdate(bundle, args.cohort)) return null;
85
+ continue;
86
+ }
87
+ return makeResponse(bundle, "ROLLBACK");
88
+ }
89
+ if (!pagination.hasNextPage) break;
90
+ offset += PAGE_SIZE;
91
+ }
92
+ if (args.bundleId === _hot_updater_core.NIL_UUID) return null;
93
+ if (args.minBundleId && args.bundleId.localeCompare(args.minBundleId) <= 0) return null;
94
+ return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
95
+ };
96
+ const getBaseWhere = ({ platform, channel, minBundleId }) => ({
97
+ platform,
98
+ channel,
99
+ enabled: true,
100
+ id: { gte: minBundleId }
101
+ });
7
102
  return {
8
103
  api: {
9
- async getBundleById(id) {
10
- return plugin.getBundleById(id);
104
+ async getBundleById(id, context) {
105
+ return plugin.getBundleById(id, context);
11
106
  },
12
- async getUpdateInfo(args) {
13
- const where = {};
14
- if ("platform" in args && args.platform) where.platform = args.platform;
15
- where.channel = "channel" in args && args.channel ? args.channel : "production";
16
- const { pagination } = await plugin.getBundles({
17
- where,
18
- limit: 1,
19
- offset: 0
107
+ async getUpdateInfo(args, context) {
108
+ const channel = args.channel ?? "production";
109
+ const minBundleId = args.minBundleId ?? _hot_updater_core.NIL_UUID;
110
+ const baseWhere = getBaseWhere({
111
+ platform: args.platform,
112
+ channel,
113
+ minBundleId
114
+ });
115
+ if (args._updateStrategy === "fingerprint") return findUpdateInfoByScanning({
116
+ args,
117
+ queryWhere: {
118
+ ...baseWhere,
119
+ fingerprintHash: args.fingerprintHash
120
+ },
121
+ context,
122
+ isCandidate: (bundle) => {
123
+ return bundle.enabled && bundle.platform === args.platform && bundle.channel === channel && bundle.id.localeCompare(minBundleId) >= 0 && bundle.fingerprintHash === args.fingerprintHash;
124
+ }
20
125
  });
21
- if (pagination.total === 0) return (0, __hot_updater_js.getUpdateInfo)([], args);
22
- const { data } = await plugin.getBundles({
23
- where,
24
- limit: pagination.total,
25
- offset: 0
126
+ return findUpdateInfoByScanning({
127
+ args,
128
+ queryWhere: { ...baseWhere },
129
+ context,
130
+ isCandidate: (bundle) => {
131
+ return bundle.enabled && bundle.platform === args.platform && bundle.channel === channel && bundle.id.localeCompare(minBundleId) >= 0 && !!bundle.targetAppVersion && (0, _hot_updater_plugin_core.semverSatisfies)(bundle.targetAppVersion, args.appVersion);
132
+ }
26
133
  });
27
- return (0, __hot_updater_js.getUpdateInfo)(data, args);
28
134
  },
29
- async getAppUpdateInfo(args) {
30
- const info = await this.getUpdateInfo(args);
135
+ async getAppUpdateInfo(args, context) {
136
+ const info = await this.getUpdateInfo(args, context);
31
137
  if (!info) return null;
32
- const { storageUri,...rest } = info;
33
- const fileUrl = await resolveFileUrl(storageUri ?? null);
138
+ const { storageUri, ...rest } = info;
139
+ const fileUrl = await resolveFileUrl(storageUri ?? null, context);
34
140
  return {
35
141
  ...rest,
36
142
  fileUrl
37
143
  };
38
144
  },
39
- async getChannels() {
40
- return plugin.getChannels();
145
+ async getChannels(context) {
146
+ return plugin.getChannels(context);
41
147
  },
42
- async getBundles(options) {
43
- return plugin.getBundles(options);
148
+ async getBundles(options, context) {
149
+ return plugin.getBundles(options, context);
44
150
  },
45
- async insertBundle(bundle) {
46
- await plugin.appendBundle(bundle);
47
- await plugin.commitBundle();
151
+ async insertBundle(bundle, context) {
152
+ await plugin.appendBundle(bundle, context);
153
+ await plugin.commitBundle(context);
48
154
  },
49
- async updateBundleById(bundleId, newBundle) {
50
- await plugin.updateBundle(bundleId, newBundle);
51
- await plugin.commitBundle();
155
+ async updateBundleById(bundleId, newBundle, context) {
156
+ await plugin.updateBundle(bundleId, newBundle, context);
157
+ await plugin.commitBundle(context);
52
158
  },
53
- async deleteBundleById(bundleId) {
54
- const bundle = await plugin.getBundleById(bundleId);
159
+ async deleteBundleById(bundleId, context) {
160
+ const bundle = await plugin.getBundleById(bundleId, context);
55
161
  if (!bundle) return;
56
- await plugin.deleteBundle(bundle);
57
- await plugin.commitBundle();
162
+ await plugin.deleteBundle(bundle, context);
163
+ await plugin.commitBundle(context);
58
164
  }
59
165
  },
60
166
  adapterName: plugin.name,
@@ -66,6 +172,5 @@ function createPluginDatabaseCore(plugin, resolveFileUrl) {
66
172
  }
67
173
  };
68
174
  }
69
-
70
175
  //#endregion
71
- exports.createPluginDatabaseCore = createPluginDatabaseCore;
176
+ exports.createPluginDatabaseCore = createPluginDatabaseCore;