@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.
- package/dist/adapters/drizzle.cjs +7 -7
- package/dist/adapters/drizzle.mjs +2 -0
- package/dist/adapters/kysely.cjs +7 -7
- package/dist/adapters/kysely.mjs +2 -0
- package/dist/adapters/mongodb.cjs +7 -7
- package/dist/adapters/mongodb.mjs +2 -0
- package/dist/adapters/prisma.cjs +7 -7
- package/dist/adapters/prisma.mjs +2 -0
- package/dist/calculatePagination.cjs +1 -3
- package/dist/{calculatePagination.js → calculatePagination.mjs} +1 -2
- package/dist/db/index.cjs +24 -15
- package/dist/db/index.d.cts +12 -9
- package/dist/db/index.d.mts +30 -0
- package/dist/db/index.mjs +45 -0
- package/dist/db/ormCore.cjs +247 -138
- package/dist/db/ormCore.d.cts +35 -17
- package/dist/db/ormCore.d.mts +44 -0
- package/dist/db/ormCore.mjs +386 -0
- package/dist/db/pluginCore.cjs +145 -40
- package/dist/db/pluginCore.mjs +176 -0
- package/dist/db/types.cjs +1 -3
- package/dist/db/types.d.cts +14 -21
- package/dist/db/types.d.mts +24 -0
- package/dist/db/{types.js → types.mjs} +1 -2
- package/dist/handler.cjs +117 -48
- package/dist/handler.d.cts +28 -18
- package/dist/handler.d.mts +47 -0
- package/dist/handler.mjs +217 -0
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +4 -0
- package/dist/internalRouter.cjs +54 -0
- package/dist/internalRouter.mjs +52 -0
- package/dist/node.cjs +2 -3
- package/dist/node.d.cts +0 -1
- package/dist/{node.d.ts → node.d.mts} +1 -2
- package/dist/{node.js → node.mjs} +1 -2
- package/dist/route.cjs +7 -0
- package/dist/route.mjs +7 -0
- package/dist/runtime.cjs +42 -0
- package/dist/runtime.d.cts +21 -0
- package/dist/runtime.d.mts +21 -0
- package/dist/runtime.mjs +40 -0
- package/dist/schema/v0_21_0.cjs +1 -5
- package/dist/schema/{v0_21_0.js → v0_21_0.mjs} +1 -3
- package/dist/schema/v0_29_0.cjs +24 -0
- package/dist/schema/v0_29_0.mjs +24 -0
- package/dist/types/{index.d.ts → index.d.mts} +1 -1
- package/package.json +18 -18
- package/src/db/index.spec.ts +64 -29
- package/src/db/index.ts +55 -35
- package/src/db/ormCore.ts +438 -210
- package/src/db/ormUpdateCheck.bench.ts +261 -0
- package/src/db/pluginCore.ts +298 -49
- package/src/db/pluginUpdateCheck.bench.ts +250 -0
- package/src/db/types.ts +52 -27
- package/src/{handler-standalone-integration.spec.ts → handler-standalone.integration.spec.ts} +106 -0
- package/src/handler.spec.ts +156 -0
- package/src/handler.ts +296 -77
- package/src/internalRouter.ts +104 -0
- package/src/route.ts +7 -0
- package/src/runtime.spec.ts +277 -0
- package/src/runtime.ts +121 -0
- package/src/schema/v0_29_0.ts +26 -0
- package/dist/_virtual/rolldown_runtime.cjs +0 -25
- package/dist/adapters/drizzle.js +0 -3
- package/dist/adapters/kysely.js +0 -3
- package/dist/adapters/mongodb.js +0 -3
- package/dist/adapters/prisma.js +0 -3
- package/dist/db/index.d.ts +0 -27
- package/dist/db/index.js +0 -36
- package/dist/db/ormCore.d.ts +0 -26
- package/dist/db/ormCore.js +0 -273
- package/dist/db/pluginCore.js +0 -69
- package/dist/db/types.d.ts +0 -31
- package/dist/handler.d.ts +0 -37
- package/dist/handler.js +0 -146
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -5
- /package/dist/adapters/{drizzle.d.ts → drizzle.d.mts} +0 -0
- /package/dist/adapters/{kysely.d.ts → kysely.d.mts} +0 -0
- /package/dist/adapters/{mongodb.d.ts → mongodb.d.mts} +0 -0
- /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 };
|
package/dist/db/pluginCore.cjs
CHANGED
|
@@ -1,60 +1,166 @@
|
|
|
1
|
-
|
|
2
|
-
let
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
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;
|